/* * Copyright (C) 2026 Peter Mikkelsen * * This file is part of aplwc. * * Aplwc is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Aplwc is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with aplwc. If not, see . */ #include "aplwc_internal.h" struct scan_state { struct aplwc *aplwc; struct string **text; size_t offset; int skip_number_and_identifier; }; static void init_scan_state(struct scan_state *state, struct aplwc *aplwc, struct string **text) { state->aplwc = aplwc; state->text = text; state->offset = 0; state->skip_number_and_identifier = 0; } static int is_space(uint32_t c) { return c == ' '; } static int is_digit(uint32_t c) { return (c >= '0') && (c <= '9'); } static uint32_t peek(struct scan_state *state) { return string_char(*state->text, state->offset); } static uint32_t advance(struct scan_state *state) { if(peek(state) != INVALID_CHAR){ state->offset += char_length(*state->text, state->offset); state->skip_number_and_identifier = 0; } return peek(state); } static enum aplwc_rc newsym(struct scan_state *state, size_t from, size_t length, symbol_t *id) { struct string *s; if((s = substring(state->aplwc, *state->text, from, length)) == NULL) return state->aplwc->last_error; return getsym(state->aplwc, s, id); } static void skip_space(struct scan_state *state) { while(is_space(peek(state))) advance(state); } #define T tokens->tokens[token_index] static enum aplwc_rc single_sym(struct scan_state *state, struct token_list *tokens, size_t token_index) { uint32_t c; size_t start; symbol_t sym; enum aplwc_rc rc; MEMPTRS(state->aplwc, 1); MEMPTR(0, tokens); rc = APLWC_OK; c = peek(state); start = state->offset; switch(c){ case 0x2190: T.tag = TOKEN_TAG_LEFT_ARROW; break; case 0x22C4: T.tag = TOKEN_TAG_DIAMOND; break; case 0x2207: T.tag = TOKEN_TAG_DEL; break; case 0x2336: T.tag = TOKEN_TAG_IBEAM; break; case 0x2395: T.tag = TOKEN_TAG_QUAD; break; case '\n': T.tag = TOKEN_TAG_NEWLINE; break; case '(': T.tag = TOKEN_TAG_LEFT_PAREN; break; case ')': T.tag = TOKEN_TAG_RIGHT_PAREN; break; default: if(!is_pattern_syntax(c)){ rc = APLWC_INVALID_SYNTAX; goto end; } T.tag = TOKEN_TAG_UNKNOWN_SYM; if((rc = newsym(state, start, 1, &sym)) != APLWC_OK) goto end; T.data.symbol = sym; break; } advance(state); end: MEMPTRS_EXIT(state->aplwc); return rc; } static enum aplwc_rc identifier(struct scan_state *state, struct token_list *tokens, size_t token_index) { uint32_t c; size_t start, len; enum aplwc_rc rc; symbol_t sym; MEMPTRS(state->aplwc, 1); MEMPTR(0, tokens); rc = APLWC_INVALID_SYNTAX; if(state->skip_number_and_identifier || (!is_id_start(peek(state)))) goto end; start = state->offset; len = 0; do{ c = advance(state); len++; }while(is_id_continue(c)); T.tag = TOKEN_TAG_IDENTIFIER; if((rc = newsym(state, start, len, &sym)) != APLWC_OK) goto end; T.data.symbol = sym; state->skip_number_and_identifier = 1; rc = APLWC_OK; end: MEMPTRS_EXIT(state->aplwc); return rc; } static enum aplwc_rc keyword(struct scan_state *state, struct token_list *tokens, size_t token_index) { uint32_t c; size_t start, len; enum aplwc_rc rc; symbol_t sym; enum keyword kw; MEMPTRS(state->aplwc, 1); MEMPTR(0, tokens); rc = APLWC_INVALID_SYNTAX; if(peek(state) != ':') goto end; c = advance(state); start = state->offset; for(len = 0; is_id_continue(c); c = advance(state)) len++; rc = APLWC_OK; T.tag = TOKEN_TAG_KEYWORD; if((rc = newsym(state, start, len, &sym)) != APLWC_OK) goto end; for(kw = 0; kw < N_KWDS; kw++){ if(sym == state->aplwc->keyword_symbols[kw]){ T.data.keyword = kw; break; } } if(kw == N_KWDS) rc = APLWC_INVALID_SYNTAX; end: MEMPTRS_EXIT(state->aplwc); return rc; } static enum aplwc_rc number(struct scan_state *state, struct token_list *tokens, size_t token_index) { uint32_t c; int has_digits; enum aplwc_rc rc; MEMPTRS(state->aplwc, 1); MEMPTR(0, tokens); rc = APLWC_INVALID_SYNTAX; c = peek(state); if(state->skip_number_and_identifier) goto end; has_digits = 0; if(c == 0xAF) /* High minus */ c = advance(state); if(is_digit(c)){ /* integer part */ has_digits = 1; do c = advance(state); while(is_digit(c)); } if(c == '.'){ c = advance(state); if(is_digit(c)){ /* fractional part */ has_digits = 1; do c = advance(state); while(is_digit(c)); } } if(has_digits == 0) goto end; T.tag = TOKEN_TAG_NUMBER; state->skip_number_and_identifier = 1; rc = APLWC_OK; end: MEMPTRS_EXIT(state->aplwc); return rc; } static enum aplwc_rc text(struct scan_state *state, struct token_list *tokens, size_t token_index) { uint32_t c; enum aplwc_rc rc; MEMPTRS(state->aplwc, 1); MEMPTR(0, tokens); rc = APLWC_INVALID_SYNTAX;; c = peek(state); if(c != '\'') goto end; do c = advance(state); while((c != '\n') && (c != INVALID_CHAR) && (c != '\'')); if(c == '\'') advance(state); else goto end; T.tag = TOKEN_TAG_TEXT; rc = APLWC_OK; end: MEMPTRS_EXIT(state->aplwc); return rc; } static enum aplwc_rc comment(struct scan_state *state, struct token_list *tokens, size_t token_index) { uint32_t c; enum aplwc_rc rc; MEMPTRS(state->aplwc, 1); MEMPTR(0, tokens); rc = APLWC_INVALID_SYNTAX; c = peek(state); if(c != 0x235D) goto end; do c = advance(state); while(c != '\n'); T.tag = TOKEN_TAG_COMMENT; rc = APLWC_OK; end: MEMPTRS_EXIT(state->aplwc); return rc; } static enum aplwc_rc next_token(struct scan_state *state, struct token_list *tokens, size_t token_index) { enum aplwc_rc rc; size_t i; enum aplwc_rc (*scans[])(struct scan_state *, struct token_list *, size_t) = { identifier, keyword, number, text, comment, single_sym /* Must be last */ }; MEMPTRS(state->aplwc, 1); MEMPTR(0, tokens); skip_space(state); T.start = state->offset; rc = APLWC_INVALID_SYNTAX; for(i = 0; (rc == APLWC_INVALID_SYNTAX) && (i < NELEM(scans)); i++){ rc = scans[i](state, tokens, token_index); switch(rc){ case APLWC_OK: break; case APLWC_INVALID_SYNTAX: state->offset = T.start; break; default: goto end; } } T.end = state->offset; skip_space(state); end: MEMPTRS_EXIT(state->aplwc); return rc; } #undef T static enum aplwc_rc check(struct aplwc *aplwc, struct string *text, size_t *n) { struct scan_state state; struct token_list *tokens; enum aplwc_rc ret; size_t count; MEMPTRS(aplwc, 2); MEMPTR(0, text); if((tokens = memory_alloc_token_list(aplwc, 1)) == NULL){ ret = aplwc->last_error; goto end; } MEMPTR(1, tokens); tokens->text = text; tokens->length = 1; init_scan_state(&state, aplwc, &text); for(count = 0; peek(&state) != INVALID_CHAR; count++){ ret = next_token(&state, tokens, 0); if(ret != APLWC_OK) goto end; } *n = count; ret = APLWC_OK; end: MEMPTRS_EXIT(aplwc); return ret; } static enum aplwc_rc scan(struct aplwc *aplwc, struct string *text, struct token_list *tokens) { struct scan_state state; enum aplwc_rc ret; size_t count; MEMPTRS(aplwc, 2); MEMPTR(0, text); MEMPTR(1, tokens); init_scan_state(&state, aplwc, &text); ret = APLWC_OK; for(count = 0; (ret == APLWC_OK) && (peek(&state) != INVALID_CHAR); count++){ assert(count < tokens->length); ret = next_token(&state, tokens, count); } MEMPTRS_EXIT(aplwc); return ret; } struct token_list * scan_string(struct aplwc *aplwc, struct string *text, int oneline) { enum aplwc_rc rc; struct token_list *tokens; size_t n, i; MEMPTRS(aplwc, 2); MEMPTR(0, text); tokens = NULL; n = 0; if((rc = check(aplwc, text, &n)) != APLWC_OK) goto end; tokens = memory_alloc_token_list(aplwc, n); if(tokens == NULL){ rc = aplwc->last_error; goto end; } MEMPTR(1, tokens); tokens->text = text; tokens->length = n; if((rc = scan(aplwc, text, tokens)) != APLWC_OK) goto end; for(i = 0; oneline && (i < n); i++){ if(tokens->tokens[i].tag != TOKEN_TAG_NEWLINE) continue; for(i++; i < n; i++){ if(tokens->tokens[i].tag != TOKEN_TAG_NEWLINE){ rc = APLWC_INVALID_SYNTAX; goto end; } } } end: aplwc->last_error = rc; if(aplwc->last_error != APLWC_OK) tokens = NULL; MEMPTRS_EXIT(aplwc); return tokens; } static void resolve_unknown(struct aplwc *aplwc, struct aplwc_module *module, struct token_list *tokens, size_t i) { symbol_t s; struct module_member *m; struct string *boot; MEMPTRS(aplwc, 3); MEMPTR(0, module); MEMPTR(1, tokens); assert(tokens->tokens[i].tag == TOKEN_TAG_UNKNOWN_SYM); if(module == NULL){ /* Try to look into the Boot module */ if((boot = chars_to_string(aplwc, "Boot", -1)) == NULL) goto end; MEMPTR(2, boot); if((module = module_lookup(aplwc, boot)) == NULL) goto end; } s = tokens->tokens[i].data.symbol; for(m = module->members; m; m = m->link){ if(m->name == s){ found: tokens->tokens[i].tag = TOKEN_TAG_IDENTIFIER; goto end; } } for(m = module->imports; m; m = m->link){ if(m->name == s) goto found; } aplwc->last_error = APLWC_INVALID_SYNTAX; end: MEMPTRS_EXIT(aplwc); } enum aplwc_rc check_tokens(struct aplwc *aplwc, struct aplwc_module *module, struct token_list *tokens) { size_t i; MEMPTRS(aplwc, 2); MEMPTR(0, module); MEMPTR(1, tokens); for(i = 0, aplwc->last_error = APLWC_OK; (aplwc->last_error == APLWC_OK) && (i < tokens->length); i++){ switch(tokens->tokens[i].tag){ case TOKEN_TAG_UNKNOWN_SYM: resolve_unknown(aplwc, module, tokens, i); if(tokens->tokens[i].tag == TOKEN_TAG_UNKNOWN_SYM) assert(aplwc->last_error != APLWC_OK); break; default: break; } } MEMPTRS_EXIT(aplwc); return aplwc->last_error; } enum aplwc_rc keyword_init(struct aplwc *aplwc) { enum keyword kw; const char *cstr; struct string *str; symbol_t sym; aplwc->last_error = APLWC_OK; for(kw = 0; kw < N_KWDS; kw++){ switch(kw){ case KW_MODULE: cstr = "module"; break; case KW_SYMBOL: cstr = "symbol"; break; case KW_EXPORT: cstr = "export"; break; } if((str = chars_to_string(aplwc, cstr, -1)) == NULL) break; if(getsym(aplwc, str, &sym) != APLWC_OK) break; aplwc->keyword_symbols[kw] = sym; } return aplwc->last_error; }