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) {
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) {
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;
}
}
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);
{ 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 },
{ 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, ">" },
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);
}
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;
}
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) {
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;
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(" ");
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) {