From: cassowarii Date: Sat, 27 Jun 2026 04:26:02 +0000 (-0700) Subject: basic parser, get that squared away X-Git-Url: https://www.git.cassowary.me/gitweb.cgi?a=commitdiff_plain;h=b4c83233cc718ada1d4c257e8d6ec306857ff79a;p=sarabande.git basic parser, get that squared away the error reporting could use some work --- diff --git a/src/parse/ast.h b/src/parse/ast.h index 89db788..2ed4424 100644 --- a/src/parse/ast.h +++ b/src/parse/ast.h @@ -11,23 +11,29 @@ typedef enum sbAstType { AST_VAL_FLOAT, AST_VAL_SYMBOL, AST_VAL_BOOLEAN, + AST_VAL_FUNC, + AST_VAL_OBJ, AST_NODE_NAME, AST_NODE_SEQ, AST_NODE_OP, AST_NODE_DEF, AST_NODE_LET, + AST_NODE_ASSIGN, AST_NODE_IF, AST_NODE_THENELSE, AST_NODE_WHILE, + AST_NODE_REPEAT, + AST_NODE_CASE, + AST_NODE_MATCH, AST_NODE_LIST, AST_NODE_HASH, AST_NODE_HASHENTRY, AST_NODE_NEXT, - AST_NODE_GROUPING, AST_NODE_MULTIVAL, AST_NODE_FUNCCALL, AST_NODE_METHODCALL, AST_NODE_SEND, + AST_NODE_RETURN, AST_NODE_ELLIPSIS, } sbAstType; @@ -56,6 +62,8 @@ typedef enum sbAstOp { AST_OP_OR, AST_OP_AND, AST_OP_NOT, + AST_OP_IN, + AST_OP_SPLAT, } sbAstOp; typedef struct sbAstNode { @@ -66,15 +74,20 @@ typedef struct sbAstNode { hInteger i; double fl; struct { - const struct sbAstNode *left; - const struct sbAstNode *right; + struct sbAstNode *left; + struct sbAstNode *right; } seq; struct { sbAstOp type; - const struct sbAstNode *left; - const struct sbAstNode *right; + struct sbAstNode *left; + struct sbAstNode *right; } op; + struct { + struct sbAstNode *left; + struct sbAstNode *center; + struct sbAstNode *right; + } tri; }; } sbAstNode; -typedef const sbAstNode *sbAst; +typedef sbAstNode *sbAst; diff --git a/src/parse/parser.c b/src/parse/parser.c index 578aa52..83c09ee 100644 --- a/src/parse/parser.c +++ b/src/parse/parser.c @@ -52,7 +52,7 @@ typedef struct opspelling { const char *name; } opspelling; -static const sbAstNode SENTINEL_VALUE = {0}; +static sbAstNode SENTINEL_VALUE = {0}; static sbAst NO_NODE = &SENTINEL_VALUE; static sbLexToken peek_ahead(hParser pr, usize count) { @@ -71,17 +71,6 @@ static sbLexToken next_token(hParser pr) { return sbTokenQueue_at(&pr->input_queue, 0); } -static flag accept(hParser pr, sbTokenType type, sbLexToken *tok_out) { - sbLexToken up_next = peek_ahead(pr, 0); - if (up_next.type == type) { - next_token(pr); - if (tok_out) *tok_out = up_next; - return TRUE; - } else { - return FALSE; - } -} - static flag expect(hParser pr, sbTokenType type, sbLexToken *tok_out) { sbLexToken up_next = peek_ahead(pr, 0); if (up_next.type == type) { @@ -89,7 +78,6 @@ static flag expect(hParser pr, sbTokenType type, sbLexToken *tok_out) { if (tok_out) *tok_out = up_next; return TRUE; } else { - fprintf(stderr, "syntax error ! unexpected type %d\n", up_next.type); // TODO add line information, etc. return FALSE; } } @@ -128,6 +116,31 @@ static sbAst seq_node(hParser pr, sbAstType type, sbAst left, sbAst right) { return new_node(pr, &n); } +static sbAst tri_node(hParser pr, sbAstType type, sbAst left, sbAst center, sbAst right) { + sbAstNode n = (sbAstNode) { + .type = type, + .tri.left = left, + .tri.center = center, + .tri.right = right, + }; + return new_node(pr, &n); +} + +static sbAst wrap_node(hParser pr, sbAstType type, sbAst left) { + sbAstNode n = (sbAstNode) { + .type = type, + .seq.left = left, + }; + return new_node(pr, &n); +} + +static sbAst atomic_node(hParser pr, sbAstType type) { + sbAstNode n = { + .type = type, + }; + return new_node(pr, &n); +} + static sbAst name_node(hParser pr, sbLexToken token) { if (token.type != T_IDENTIFIER) { PANIC("can't create name node with token of type %d\n", token.type); @@ -144,6 +157,7 @@ static binop binops[] = { { T_PIPE, 6, 7, AST_OP_PIPE }, { T_rOR, 10, 11, AST_OP_OR }, { T_rAND, 20, 21, AST_OP_AND }, + { T_rIN, 25, 26, AST_OP_IN }, { T_DOUBLEEQUALS, 30, 31, AST_OP_EQ }, { T_GREATER, 30, 31, AST_OP_GT }, { T_LESS, 30, 31, AST_OP_LT }, @@ -175,6 +189,7 @@ static opspelling op_spellings[] = { { AST_OP_OR, "or" }, { AST_OP_AND, "and" }, { AST_OP_NOT, "not" }, + { AST_OP_IN, "in" }, { AST_OP_EQ, "==" }, { AST_OP_NE, "!=" }, { AST_OP_GT, ">" }, @@ -200,14 +215,101 @@ const int NUM_BINOPS = sizeof(binops) / sizeof(binops[0]); const int NUM_UNOPS = sizeof(unops) / sizeof(unops[0]); const int NUM_SPELLINGS = sizeof(op_spellings) / sizeof(op_spellings[0]); +static sbAst parse_expr(hParser pr, u8 min_precedence); +static sbAst parse_comma_exprs(hParser pr, sbAst after) { + sbAst result = NO_NODE; + sbAst *put_here = &result; + sbAst expr; + + if (after) { + *put_here = seq_node(pr, AST_NODE_MULTIVAL, after, NO_NODE); + put_here = &(*put_here)->seq.right; + } + + do { + if (expect(pr, T_ELLIPSIS, NULL)) { + sbAst splatted_expr = parse_expr(pr, 0); + if (splatted_expr != NO_NODE) { + /* "...something" */ + expr = unop_node(pr, AST_OP_SPLAT, splatted_expr); + } else { + /* plain "..." */ + expr = atomic_node(pr, AST_NODE_ELLIPSIS); + } + } else { + expr = parse_expr(pr, 0); + + /* this 'break' allows empty () and trailing comma */ + if (expr == NO_NODE) break; + } + + *put_here = seq_node(pr, AST_NODE_MULTIVAL, expr, NO_NODE); + put_here = &(*put_here)->seq.right; + } while (expect(pr, T_COMMA, NULL)); + + return result; +} + +static sbAst parse_name(hParser pr) { + sbLexToken t = peek_ahead(pr, 0); + if (t.type == T_IDENTIFIER) { + return name_node(pr, next_token(pr)); + } + return NO_NODE; +} + +static sbAst parse_block(hParser pr); + /* https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html */ static sbAst parse_expr(hParser pr, u8 min_precedence) { sbLexToken t = peek_ahead(pr, 0); sbAst lhs = NO_NODE; - if (t.type == T_IDENTIFIER) { - expect(pr, T_IDENTIFIER, NULL); - sbAstNode n = { .type = AST_NODE_NAME, .symb = t.symb }; + if (t.type == T_rNIL) { + sbAstNode n = { .type = AST_VAL_NIL }; + lhs = new_node(pr, &n); + } else if (t.type == T_rTRUE || t.type == T_rFALSE) { + sbAstNode n = { .type = AST_VAL_BOOLEAN, .i = (t.type == T_rTRUE) }; + lhs = new_node(pr, &n); + } else if (t.type == T_INTEGER) { + sbAstNode n = { .type = AST_VAL_INT, .i = t.i }; lhs = new_node(pr, &n); + } else if (t.type == T_FLOAT) { + sbAstNode n = { .type = AST_VAL_FLOAT, .fl = t.fl }; + lhs = new_node(pr, &n); + } else if (t.type == T_SYMBOL) { + sbAstNode n = { .type = AST_VAL_SYMBOL, .symb = t.symb }; + lhs = new_node(pr, &n); + } else if (t.type == T_STRING) { + sbAstNode n = { .type = AST_VAL_STRING, .str = t.hstr }; + lhs = new_node(pr, &n); + } else if (t.type == T_IDENTIFIER) { + lhs = parse_name(pr); + } else if (t.type == T_FATARROW) { + next_token(pr); + if (!expect(pr, T_LPAREN, NULL)) { + fprintf(stderr, "expected '(' after '=>' token\n"); + return NO_NODE; + } + sbAst params = parse_comma_exprs(pr, NULL); + if (!expect(pr, T_RPAREN, NULL)) { + fprintf(stderr, "expected ')' after function parameters\n"); + return NO_NODE; + } + sbAst body = parse_block(pr); + return seq_node(pr, AST_VAL_FUNC, params, body); + } else if (t.type == T_SQUIGARROW) { + next_token(pr); + if (!expect(pr, T_LPAREN, NULL)) { + fprintf(stderr, "expected '(' after '~>' token\n"); + return NO_NODE; + } + sbAst params = parse_comma_exprs(pr, NULL); + if (!expect(pr, T_RPAREN, NULL)) { + fprintf(stderr, "expected ')' after function parameters\n"); + return NO_NODE; + } + sbAst body = parse_block(pr); + return seq_node(pr, AST_VAL_OBJ, params, body); } else if (t.type == T_LPAREN) { next_token(pr); lhs = parse_expr(pr, 0); @@ -226,7 +328,7 @@ static sbAst parse_expr(hParser pr, u8 min_precedence) { } if (!prefix) { - fprintf(stderr, "expected identifier, '(', or operator!\n"); + /* thing we're looking at isn't an expression, but this might be ok */ return NO_NODE; } @@ -263,11 +365,10 @@ static sbAst parse_expr(hParser pr, u8 min_precedence) { sbAst rhs; if (op.type == T_LPAREN) { /* function call */ - rhs = parse_expr(pr, 0); + rhs = parse_comma_exprs(pr, NULL); ast_type = AST_NODE_FUNCCALL; if (!expect(pr, T_RPAREN, NULL)) { - /* TODO handle commas: we should have a "parse comma separated expr" thing */ - fprintf(stderr, "expected ')'\n"); + fprintf(stderr, "expected ')' after function parameter list\n"); return NO_NODE; } } else if (op.type == T_LBRACKET) { @@ -284,13 +385,11 @@ static sbAst parse_expr(hParser pr, u8 min_precedence) { return NO_NODE; } sbAst name = name_node(pr, method_name); - printf("method name: %s\n", sbSymbol_name(method_name.symb)); if (!expect(pr, T_LPAREN, NULL)) { fprintf(stderr, "expected opening-parenthesis after '.%s'\n", sbSymbol_name(method_name.symb)); return NO_NODE; } - // TODO handle commas - sbAst params = parse_expr(pr, 0); + sbAst params = parse_comma_exprs(pr, NULL); if (!expect(pr, T_RPAREN, NULL)) { fprintf(stderr, "expected closing-parenthesis after '.%s(...'\n", sbSymbol_name(method_name.symb)); return NO_NODE; @@ -311,7 +410,178 @@ static sbAst parse_expr(hParser pr, u8 min_precedence) { return lhs; } +static sbAst parse_stmt(hParser pr); +static sbAst parse_stmtseq(hParser pr); + +static sbAst parse_block(hParser pr) { + if (!expect(pr, '{', NULL)) { + fprintf(stderr, "syntax error! expecting '{'\n"); + return NO_NODE; + } + + sbAst result = parse_stmtseq(pr); + + if (!expect(pr, '}', NULL)) { + fprintf(stderr, "syntax error! expecting '}'\n"); + return NO_NODE; + } + + return result; +} + +static sbAst parse_stmtseq(hParser pr) { + sbAst result = NO_NODE; + sbAst *put_here = &result; + + do { + sbAst stmt = parse_stmt(pr); + if (stmt == NO_NODE) { + /* if failed to parse a statement, try eating an + * additional semicolon first */ + if (expect(pr, ';', NULL)) continue; + break; + } + *put_here = seq_node(pr, AST_NODE_SEQ, stmt, NO_NODE); + put_here = &(*put_here)->seq.right; + } while (expect(pr, ';', NULL)); + + return result; +} + +static sbAst with_trailing_conditional(hParser pr, sbAst stmt) { + sbAst result = stmt; + + if (expect(pr, T_rIF, NULL)) { + sbAst condition = parse_expr(pr, 0); + result = tri_node(pr, AST_NODE_IF, condition, stmt, NO_NODE); + } else if (expect(pr, T_rUNLESS, NULL)) { + sbAst condition = parse_expr(pr, 0); + result = tri_node(pr, AST_NODE_IF, unop_node(pr, AST_OP_NOT, condition), stmt, NO_NODE); + } + + return result; +} + +static sbAst parse_stmt(hParser pr) { + sbLexToken t = peek_ahead(pr, 0); + if (t.type == T_rIF) { + next_token(pr); + sbAst condition = parse_expr(pr, 0); + sbAst then_body = parse_block(pr); + sbAst else_body = NO_NODE; + if (expect(pr, T_rELSE, NULL)) { + sbLexToken t = peek_ahead(pr, 0); + if (t.type == T_rIF) { + /* else if ...as above... */ + else_body = parse_stmt(pr); + } else { + else_body = parse_block(pr); + } + } + return tri_node(pr, AST_NODE_IF, condition, then_body, else_body); + } else if (t.type == T_rUNLESS) { + next_token(pr); + sbAst condition = parse_expr(pr, 0); + /* won't permit unless..else. it is too confusing. unless can only have 1 block */ + sbAst unless_body = parse_block(pr); + return tri_node(pr, AST_NODE_IF, unop_node(pr, AST_OP_NOT, condition), unless_body, NO_NODE); + } else if (t.type == T_rWHILE) { + next_token(pr); + sbAst condition = parse_expr(pr, 0); + sbAst body = parse_block(pr); + return seq_node(pr, AST_NODE_WHILE, condition, body); + } else if (t.type == T_rUNTIL) { + next_token(pr); + sbAst condition = parse_expr(pr, 0); + sbAst body = parse_block(pr); + return seq_node(pr, AST_NODE_WHILE, unop_node(pr, AST_OP_NOT, condition), body); + } else if (t.type == T_rREPEAT) { + next_token(pr); + sbAst body = parse_block(pr); + if (expect(pr, T_rWHILE, NULL)) { + /* repeat..while */ + sbAst condition = parse_expr(pr, 0); + return seq_node(pr, AST_NODE_REPEAT, body, condition); + } else if (expect(pr, T_rUNTIL, NULL)) { + /* repeat..until */ + sbAst condition = parse_expr(pr, 0); + return seq_node(pr, AST_NODE_REPEAT, body, condition); + } else { + fprintf(stderr, "syntax error! 'while' or 'until' required after 'repeat { ... }'\n"); + return NO_NODE; + } + } else if (t.type == T_rCASE) { + next_token(pr); + sbAst values = parse_comma_exprs(pr, NULL); + sbAst body = parse_block(pr); + return seq_node(pr, AST_NODE_CASE, values, body); + } else if (t.type == T_rMATCH) { + next_token(pr); + sbAst pattern = parse_comma_exprs(pr, NULL); + sbAst guard_clause = NO_NODE; + if (expect(pr, T_rIF, NULL)) { + next_token(pr); + guard_clause = parse_expr(pr, 0); + } else if (expect(pr, T_rUNLESS, NULL)) { + next_token(pr); + guard_clause = parse_expr(pr, 0); + guard_clause = unop_node(pr, AST_OP_NOT, guard_clause); + } + sbAst body = parse_block(pr); + return tri_node(pr, AST_NODE_MATCH, pattern, guard_clause, body); + } else if (t.type == T_rLET) { + next_token(pr); + sbAst bindings = parse_comma_exprs(pr, NULL); + if (!expect(pr, T_EQUALS, NULL)) { + fprintf(stderr, "expected '=' after let ...\n"); + return NO_NODE; + } + sbAst values = parse_comma_exprs(pr, NULL); + return seq_node(pr, AST_NODE_LET, bindings, values); + } else if (t.type == T_rDEF) { + next_token(pr); + sbAst name = parse_name(pr); + if (!expect(pr, T_LPAREN, NULL)) { + fprintf(stderr, "expected '(' after 'def' and name\n"); + return NO_NODE; + } + sbAst params = parse_comma_exprs(pr, NULL); + if (!expect(pr, T_RPAREN, NULL)) { + fprintf(stderr, "expected ')' after function parameters\n"); + return NO_NODE; + } + sbAst body = parse_block(pr); + sbAst func_node = seq_node(pr, AST_VAL_FUNC, params, body); + return seq_node(pr, AST_NODE_DEF, name, func_node); + } else if (t.type == T_rRETURN) { + next_token(pr); + sbAst returned_val = parse_comma_exprs(pr, 0); + sbAst return_node = wrap_node(pr, AST_NODE_RETURN, returned_val); + return_node = with_trailing_conditional(pr, return_node); + return return_node; + } else { + sbAst expr = parse_expr(pr, 0); + if (peek_ahead(pr, 0).type == ',') { + next_token(pr); + expr = parse_comma_exprs(pr, expr); + } + if (peek_ahead(pr, 0).type == '=') { + next_token(pr); + sbAst assigned_values = parse_comma_exprs(pr, 0); + expr = seq_node(pr, AST_NODE_ASSIGN, expr, assigned_values); + } + if (expr != NO_NODE) { + expr = with_trailing_conditional(pr, expr); + return expr; + } + } + + return NO_NODE; +} + static void print_ast_node(sbAst n, int indent) { + if (n == NULL) return; + printf("\n"); for (int i = 0; i < indent; i++) { printf(" "); @@ -319,6 +589,10 @@ static void print_ast_node(sbAst n, int indent) { if (n->type == AST_NODE_NAME) { printf("%s", sbSymbol_name(n->symb)); + } else if (n->type == AST_NODE_MULTIVAL) { + print_ast_node(n->seq.left, indent + 1); + printf(","); + print_ast_node(n->seq.right, indent + 1); } else { printf("("); if (n->type == AST_NODE_OP) { diff --git a/src/parse/parser.h b/src/parse/parser.h index 5cbf389..3847ef9 100644 --- a/src/parse/parser.h +++ b/src/parse/parser.h @@ -11,7 +11,7 @@ typedef struct sbParser { typedef sbParser *hParser; -const sbAstNode *sbParser_parse_file(hParser pr, const char *filename); +sbAstNode *sbParser_parse_file(hParser pr, const char *filename); void sbParser_initialize(hParser pr); diff --git a/src/parse/scanner.c b/src/parse/scanner.c index fb6e50f..f444e10 100644 --- a/src/parse/scanner.c +++ b/src/parse/scanner.c @@ -33,6 +33,7 @@ static struct ReservedWord reserved_words[] = { { "in", T_rIN }, { "match", T_rMATCH }, { "not", T_rNOT }, + { "nil", T_rNIL }, { "or", T_rOR }, { "repeat", T_rREPEAT }, { "return", T_rRETURN }, diff --git a/src/parse/token.h b/src/parse/token.h index 24e2c42..7fa61ed 100644 --- a/src/parse/token.h +++ b/src/parse/token.h @@ -72,6 +72,7 @@ typedef enum sbTokenType { T_rIN, // in T_rLET, // let T_rMATCH, // match + T_rNIL, // nil T_rNOT, // not T_rOR, // or T_rREPEAT, // repeat