/* * 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_letter(uint32_t c) { return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')); } 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) { 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, int check_only) { 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: T.tag = TOKEN_TAG_UNKNOWN_SYM; if(!check_only){ 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, int check_only) { 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_letter(peek(state)))) goto end; start = state->offset; len = 0; do{ c = advance(state); len++; }while(is_letter(c) || is_digit(c) || (c == '_')); T.tag = TOKEN_TAG_IDENTIFIER; if(!check_only){ 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, int check_only) { 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; len = 0; do{ c = advance(state); len++; }while(is_letter(c)); rc = APLWC_OK; T.tag = TOKEN_TAG_KEYWORD; if(!check_only){ 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, int check_only) { uint32_t c; int has_digits; enum aplwc_rc rc; MEMPTRS(state->aplwc, 1); MEMPTR(0, tokens); UNUSED_PARAMETER(check_only); 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, int check_only) { uint32_t c; enum aplwc_rc rc; MEMPTRS(state->aplwc, 1); MEMPTR(0, tokens); UNUSED_PARAMETER(check_only); rc = APLWC_INVALID_SYNTAX;; c = peek(state); if(c != '\'') goto end; do c = advance(state); while((c != '\n') && (c != 0) && (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, int check_only) { uint32_t c; enum aplwc_rc rc; MEMPTRS(state->aplwc, 1); MEMPTR(0, tokens); UNUSED_PARAMETER(check_only); 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, int check_only) { enum aplwc_rc rc; size_t i; enum aplwc_rc (*scans[])(struct scan_state *, struct token_list *, size_t, int) = { 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, check_only); switch(rc){ case APLWC_OK: break; case APLWC_INVALID_SYNTAX: state->offset = tokens->tokens[token_index].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); init_scan_state(&state, aplwc, &text); for(count = 0; peek(&state); count++){ ret = next_token(&state, tokens, 0, 1); 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); count++){ ret = next_token(&state, tokens, count, 0); } 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; 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; tokens = NULL; goto end; } } } end: aplwc->last_error = rc; MEMPTRS_EXIT(aplwc); return tokens; } static void resolve_unknown(struct aplwc_module *module, struct token *t) { symbol_t s; struct module_member *m; assert(t && (t->tag == TOKEN_TAG_UNKNOWN_SYM)); if(module == NULL) return; s = t->data.symbol; for(m = module->members; m; m = m->link){ if(m->name == s){ found: t->tag = TOKEN_TAG_IDENTIFIER; return; } } for(m = module->imports; m; m = m->link){ if(m->name == s) goto found; } } enum aplwc_rc check_tokens(struct aplwc_module *module, struct token_list *tokens) { size_t i; enum aplwc_rc rc; for(i = 0, rc = APLWC_OK; (rc == APLWC_OK) && (i < tokens->length); i++){ switch(tokens->tokens[i].tag){ case TOKEN_TAG_UNKNOWN_SYM: resolve_unknown(module, &tokens->tokens[i]); if(tokens->tokens[i].tag == TOKEN_TAG_UNKNOWN_SYM) return APLWC_INVALID_SYNTAX; break; default: break; } } return rc; } 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; case N_KWDS: assert(0); 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; }