expression parser mostly complete
authorcassowarii <cassowary@cassowary.me>
Fri, 26 Jun 2026 19:47:35 +0000 (12:47 -0700)
committercassowarii <cassowary@cassowary.me>
Fri, 26 Jun 2026 19:47:35 +0000 (12:47 -0700)
src/parse/ast.h
src/parse/lexer.c
src/parse/parser.c

index c552c4e..89db788 100644 (file)
@@ -33,20 +33,29 @@ typedef enum sbAstType {
 
 typedef enum sbAstOp {
   AST_OP_NULL,
-  AST_OP_PLUS = '+',
-  AST_OP_MINUS = '-',
-  AST_OP_TIMES = '*',
+  AST_OP_ADD = '+',
+  AST_OP_SUB = '-',
+  AST_OP_MUL = '*',
   AST_OP_DIV = '/',
   AST_OP_MOD = '%',
   AST_OP_EQ = '=',
   AST_OP_LT = '<',
   AST_OP_GT = '>',
+  AST_OP_PIPE = '|',
   AST_OP_LE = 128,
   AST_OP_GE,
   AST_OP_NE,
   AST_OP_FLDIV,
+  AST_OP_POW,
+  AST_OP_RANGE,
   AST_OP_DIVBY,
-  AST_OP_PN,
+  AST_OP_INDEX,
+  AST_OP_SCOPE,
+  AST_OP_UNPLUS,
+  AST_OP_UNMINUS,
+  AST_OP_OR,
+  AST_OP_AND,
+  AST_OP_NOT,
 } sbAstOp;
 
 typedef struct sbAstNode {
@@ -57,11 +66,11 @@ typedef struct sbAstNode {
     hInteger i;
     double fl;
     struct {
-      const struct sbAstNode *this;
-      const struct sbAstNode *next;
+      const struct sbAstNode *left;
+      const struct sbAstNode *right;
     } seq;
     struct {
-      sbTokenType type;
+      sbAstOp type;
       const struct sbAstNode *left;
       const struct sbAstNode *right;
     } op;
index fb5ab94..d6101c0 100644 (file)
@@ -219,6 +219,20 @@ static flag block_header_can_end_after(sbTokenType type) {
         || type == T_rDO;
 }
 
+/* these are operators that look weird if they get captured inside
+ * of parentheses. */
+static flag close_invisible_parens_before(sbTokenType type) {
+    return type == T_rAND
+        || type == T_rOR
+        || type == T_DOUBLEEQUALS
+        || type == T_NOTEQUALS
+        || type == T_LESS
+        || type == T_GREATER
+        || type == T_LESSEQUALS
+        || type == T_GREATEREQUALS
+        || type == T_PIPE;
+}
+
 /*static flag is_reserved_word(sbTokenType type) {
     for (int i = 0; i < N_RESERVED_WORDS; i++) {
         if (type == reserved_words[i].token_type) return 1;
@@ -379,6 +393,10 @@ static void compute_next_token(hLexer lx) {
         brackets_stack_push(lx, 'B');
     }
 
+    if (close_invisible_parens_before(token.type)) {
+        unstack_all_invisible_parentheses(lx);
+    }
+
     //printf("\nstack: %s\n", lx->brackets_stack.data);
 
     /* --- HERE IS WHERE THE TOKEN ACTUALLY GETS OUTPUT TO THE STREAM --- */
index 11fe90b..578aa52 100644 (file)
@@ -38,8 +38,20 @@ typedef struct binop {
   sbTokenType type;
   u8 left_precedence;
   u8 right_precedence;
+  sbAstOp ast_op;
 } binop;
 
+typedef struct unop {
+  sbTokenType type;
+  u8 precedence;
+  sbAstOp ast_op;
+} unop;
+
+typedef struct opspelling {
+  sbAstOp ast_op;
+  const char *name;
+} opspelling;
+
 static const sbAstNode SENTINEL_VALUE = {0};
 static sbAst NO_NODE = &SENTINEL_VALUE;
 
@@ -88,7 +100,7 @@ static sbAst new_node(hParser pr, sbAstNode *n) {
   return new_node;
 }
 
-static sbAst unop_node(hParser pr, sbTokenType operation, sbAstNode *child) {
+static sbAst unop_node(hParser pr, sbAstOp operation, sbAst child) {
   sbAstNode n = (sbAstNode) {
     .type = AST_NODE_OP,
     .op.type = operation,
@@ -97,7 +109,7 @@ static sbAst unop_node(hParser pr, sbTokenType operation, sbAstNode *child) {
   return new_node(pr, &n);
 }
 
-static sbAst binop_node(hParser pr, sbTokenType operation, sbAst left, sbAst right) {
+static sbAst binop_node(hParser pr, sbAstOp operation, sbAst left, sbAst right) {
   sbAstNode n = (sbAstNode) {
     .type = AST_NODE_OP,
     .op.type = operation,
@@ -107,28 +119,86 @@ static sbAst binop_node(hParser pr, sbTokenType operation, sbAst left, sbAst rig
   return new_node(pr, &n);
 }
 
+static sbAst seq_node(hParser pr, sbAstType type, sbAst left, sbAst right) {
+  sbAstNode n = (sbAstNode) {
+    .type = type,
+    .seq.left = left,
+    .seq.right = right,
+  };
+  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);
+  }
+
+  sbAstNode n = (sbAstNode) {
+    .type = AST_NODE_NAME,
+    .symb = token.symb,
+  };
+  return new_node(pr, &n);
+}
+
 static binop binops[] = {
-  { T_rOR, 3, 4 },
-  { T_rAND, 6, 7 },
-  { T_DOUBLEEQUALS, 10, 11 },
-  { T_GREATER, 10, 11 },
-  { T_LESS, 10, 11 },
-  { T_GREATEREQUALS, 10, 11 },
-  { T_LESSEQUALS, 10, 11 },
-  { T_NOTEQUALS, 10, 11 },
-  { T_PLUS, 15, 16 },
-  { T_MINUS, 15, 16 },
-  { T_ASTERISK, 30, 31 },
-  { T_SLASH, 30, 31 },
-  { T_PERCENT, 30, 31 },
-  { T_DOUBLESLASH, 30, 31 },
-  { T_DOUBLEPERCENT, 30, 31 },
-  { T_DOUBLEASTERISK, 51, 50 },
-  { T_TWODOT, 60, 61 },
-  { T_PAAMAYIM_NEKUDOTAYIM, 70, 71 },
+  { T_PIPE, 6, 7, AST_OP_PIPE },
+  { T_rOR, 10, 11, AST_OP_OR },
+  { T_rAND, 20, 21, AST_OP_AND },
+  { T_DOUBLEEQUALS, 30, 31, AST_OP_EQ },
+  { T_GREATER, 30, 31, AST_OP_GT },
+  { T_LESS, 30, 31, AST_OP_LT },
+  { T_GREATEREQUALS, 30, 31, AST_OP_GE },
+  { T_LESSEQUALS, 30, 31, AST_OP_LE },
+  { T_NOTEQUALS, 30, 31, AST_OP_NE },
+  { T_DOUBLEPERCENT, 30, 31, AST_OP_DIVBY },
+  { T_PLUS, 45, 46, AST_OP_ADD },
+  { T_MINUS, 45, 46, AST_OP_SUB },
+  { T_ASTERISK, 60, 61, AST_OP_MUL },
+  { T_SLASH, 60, 61, AST_OP_DIV },
+  { T_PERCENT, 60, 61, AST_OP_MOD },
+  { T_DOUBLESLASH, 60, 61, AST_OP_FLDIV },
+  { T_DOUBLEASTERISK, 71, 70, AST_OP_POW },
+  { T_TWODOT, 80, 81, AST_OP_RANGE },
+  { T_DOT, 90, 91, AST_OP_NULL },
+  { T_LPAREN, 90, 91, AST_OP_NULL },
+  { T_LBRACKET, 90, 91, AST_OP_INDEX },
+  { T_PAAMAYIM_NEKUDOTAYIM, 100, 101, AST_OP_SCOPE },
+};
+
+static unop unops[] = {
+  { T_rNOT, 25, AST_OP_NOT },
+  { T_PLUS, 85, AST_OP_UNPLUS },
+  { T_MINUS, 85, AST_OP_UNMINUS },
+};
+
+static opspelling op_spellings[] = {
+  { AST_OP_OR, "or" },
+  { AST_OP_AND, "and" },
+  { AST_OP_NOT, "not" },
+  { AST_OP_EQ, "==" },
+  { AST_OP_NE, "!=" },
+  { AST_OP_GT, ">" },
+  { AST_OP_LT, "<" },
+  { AST_OP_GE, ">=" },
+  { AST_OP_LE, "<=" },
+  { AST_OP_DIVBY, "%%" },
+  { AST_OP_ADD, "+" },
+  { AST_OP_UNPLUS, "+@" },
+  { AST_OP_SUB, "-" },
+  { AST_OP_UNMINUS, "-@" },
+  { AST_OP_MUL, "*" },
+  { AST_OP_DIV, "/" },
+  { AST_OP_MOD, "%" },
+  { AST_OP_FLDIV, "//" },
+  { AST_OP_POW, "**" },
+  { AST_OP_RANGE, ".." },
+  { AST_OP_INDEX, "[]" },
+  { AST_OP_SCOPE, "::" },
 };
 
 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]);
 
 /* https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html */
 static sbAst parse_expr(hParser pr, u8 min_precedence) {
@@ -146,63 +216,151 @@ static sbAst parse_expr(hParser pr, u8 min_precedence) {
       return NO_NODE;
     }
   } else {
-    fprintf(stderr, "expected identifier!\n");
-    return NO_NODE;
+    sbLexToken op = peek_ahead(pr, 0);
+    unop *prefix = NULL;
+    for (int i = 0; i < NUM_UNOPS; i++) {
+      if (unops[i].type == op.type) {
+        prefix = &unops[i];
+        break;
+      }
+    }
+
+    if (!prefix) {
+      fprintf(stderr, "expected identifier, '(', or operator!\n");
+      return NO_NODE;
+    }
+
+    next_token(pr);
+
+    sbAst content = parse_expr(pr, prefix->precedence);
+
+    lhs = unop_node(pr, prefix->ast_op, content);
   }
 
   while (1) {
     sbLexToken op = peek_ahead(pr, 0);
-    if (op.type == T_EOF || op.type == T_SEMICOLON) {
-      break;
-    } else {
-      binop *infix = NULL;
-      for (int i = 0; i < NUM_BINOPS; i++) {
-        if (binops[i].type == op.type) {
-          infix = &binops[i];
-          break;
-        }
-      }
-
-      if (!infix) {
-        /* something that isn't an infix operator: means end of expr */
+    sbAstType ast_type = AST_NODE_OP;
+    binop *infix = NULL;
+    for (int i = 0; i < NUM_BINOPS; i++) {
+      if (binops[i].type == op.type) {
+        infix = &binops[i];
         break;
       }
+    }
 
-      if (infix->left_precedence < min_precedence) {
-        /* end of this expr because something with lower precedence */
-        break;
-      }
+    if (!infix) {
+      /* something that isn't an infix operator: means end of expr */
+      break;
+    }
 
-      next_token(pr); /* consume operator token */
+    if (infix->left_precedence < min_precedence) {
+      /* end of this expr because something with lower precedence */
+      break;
+    }
 
-      sbAst rhs = parse_expr(pr, infix->right_precedence);
+    next_token(pr); /* consume operator token */
+
+    sbAst rhs;
+    if (op.type == T_LPAREN) {
+      /* function call */
+      rhs = parse_expr(pr, 0);
+      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");
+        return NO_NODE;
+      }
+    } else if (op.type == T_LBRACKET) {
+      /* indexing */
+      rhs = parse_expr(pr, 0);
+      if (!expect(pr, T_RBRACKET, NULL)) {
+        fprintf(stderr, "expected ']'\n");
+        return NO_NODE;
+      }
+    } else if (op.type == T_DOT) {
+      sbLexToken method_name = {0};
+      if (!expect(pr, T_IDENTIFIER, &method_name)) {
+        fprintf(stderr, "expected identifier after '.' token\n");
+        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);
+      if (!expect(pr, T_RPAREN, NULL)) {
+        fprintf(stderr, "expected closing-parenthesis after '.%s(...'\n", sbSymbol_name(method_name.symb));
+        return NO_NODE;
+      }
+      ast_type = AST_NODE_METHODCALL;
+      rhs = seq_node(pr, AST_NODE_NEXT, name, params);
+    } else {
+      rhs = parse_expr(pr, infix->right_precedence);
+    }
 
-      lhs = binop_node(pr, infix->type, lhs, rhs);
+    if (ast_type == AST_NODE_OP) {
+      lhs = binop_node(pr, infix->ast_op, lhs, rhs);
+    } else {
+      lhs = seq_node(pr, ast_type, lhs, rhs);
     }
   }
 
   return lhs;
 }
 
-static void print_ast_node(sbAst n) {
+static void print_ast_node(sbAst n, int indent) {
+  printf("\n");
+  for (int i = 0; i < indent; i++) {
+    printf("  ");
+  }
+
   if (n->type == AST_NODE_NAME) {
     printf("%s", sbSymbol_name(n->symb));
   } else {
     printf("(");
     if (n->type == AST_NODE_OP) {
-      printf("%c ", n->op.type);
-      print_ast_node(n->op.left);
-      printf(" ");
-      print_ast_node(n->op.right);
+      flag found = 0;
+      for (int i = 0; i < NUM_SPELLINGS; i++) {
+        if (op_spellings[i].ast_op == n->op.type) {
+          printf("%s", op_spellings[i].name);
+          found = 1;
+          break;
+        }
+      }
+      if (!found) printf("??");
+      print_ast_node(n->op.left, indent + 1);
+      if (n->op.right) {
+        print_ast_node(n->op.right, indent + 1);
+      }
+    } else if (n->type == AST_NODE_FUNCCALL) {
+      print_ast_node(n->seq.left, indent + 1);
+      printf("(");
+      print_ast_node(n->seq.right, indent + 1);
+      printf(")");
+    } else if (n->type == AST_NODE_METHODCALL) {
+      print_ast_node(n->seq.left, indent + 1);
+      printf(".");
+      print_ast_node(n->seq.right->seq.left, indent + 1);
+      printf("(");
+      print_ast_node(n->seq.right->seq.right, indent + 1);
+      printf(")");
     } else {
       printf("?<%d>", n->type);
     }
+
+    printf("\n");
+    for (int i = 0; i < indent; i++) {
+      printf("  ");
+    }
     printf(")");
   }
 }
 
 static sbAst do_parse(hParser pr) {
   sbAst program = parse_expr(pr, 0);
-  print_ast_node(program);
+  print_ast_node(program, 0);
   return program;
 }