Basic framework for IR code generation
authorcassowarii <cassowary@cassowary.me>
Mon, 29 Jun 2026 02:54:10 +0000 (19:54 -0700)
committercassowarii <cassowary@cassowary.me>
Mon, 29 Jun 2026 02:54:10 +0000 (19:54 -0700)
13 files changed:
.gitignore
src/compile/ir.c [new file with mode: 0644]
src/compile/ir.h [new file with mode: 0644]
src/main.c
src/mem/debug.c [new file with mode: 0644]
src/mem/debug.h [new file with mode: 0644]
src/parse/ast.h
src/parse/filereader.h
src/parse/lexer.c
src/parse/lexer.h
src/parse/parser.c
src/parse/parser.h
src/parse/scanner.h

index 1f2492e..5b29976 100644 (file)
@@ -1,3 +1,4 @@
 *.swp
 build/
 obj/
+unused/
diff --git a/src/compile/ir.c b/src/compile/ir.c
new file mode 100644 (file)
index 0000000..eff3d89
--- /dev/null
@@ -0,0 +1,398 @@
+#include "compile/ir.h"
+
+#include "parse/ast.h"
+#include "data/symbol.h"
+
+#include "mem/debug.h"
+
+#define IF_NOT 1
+#define IF_YES 0
+#define ALWAYS 0
+
+typedef struct varmapentry {
+  const char *name;
+  usize name_len;
+  sbIrVariable *var;
+} varmapentry;
+
+static void compile_ast_stmtseq(hIrChunk ck, sbAst seqast);
+static sbIrChunk *new_chunk(hIrProgram ir);
+static void chunk_deinitialize(hIrChunk ck);
+static void print_stmt(sbIrStmt *s);
+
+void sbIrProgram_initialize(hIrProgram ir, usize initial_arena_size) {
+  *ir = (sbIrProgram) {0};
+  sbArena_initialize(&ir->arena, initial_arena_size);
+  sbBuffer_initialize(&ir->chunks, 4096);
+}
+
+void sbIrProgram_deinitialize(hIrProgram ir) {
+  usize nchunks = ir->chunks.size / sizeof(sbIrChunk*);
+  for (usize i = 0; i < nchunks; i++) {
+    sbIrChunk *ck = ((sbIrChunk**)ir->chunks.data)[i];
+    chunk_deinitialize(ck);
+  }
+
+  sbArena_deinitialize(&ir->arena);
+  sbBuffer_deinitialize(&ir->chunks);
+  *ir = (sbIrProgram) {0};
+}
+
+void sbIrProgram_compile_ast(hIrProgram ir, sbAst ast) {
+  sbIrChunk *ck = new_chunk(ir);
+  compile_ast_stmtseq(ck, ast);
+}
+
+void sbIrProgram_print(hIrProgram ir) {
+  usize nchunks = ir->chunks.size / sizeof(sbIrChunk*);
+
+  for (usize i = 0; i < nchunks; i++) {
+    sbIrChunk *ck = ((sbIrChunk**)ir->chunks.data)[i];
+    printf("\nCHUNK %zu (%p)\n", ck->id, ck);
+
+    usize nstmts = ck->stmts.size / sizeof(sbIrStmt*);
+    for (usize j = 0; j < nstmts; j++) {
+      sbIrStmt *s = ((sbIrStmt**)ck->stmts.data)[j];
+      print_stmt(s);
+    }
+
+    printf("\n");
+  }
+}
+
+/* --- */
+
+static sbIrChunk *new_chunk(hIrProgram ir) {
+  usize nchunks = ir->chunks.size / sizeof(sbIrChunk*);
+  sbIrChunk *chunk = sbArena_alloc(&ir->arena, sizeof(sbIrChunk));
+  sbBuffer_initialize(&chunk->stmts, 1024);
+  chunk->id = nchunks;
+  chunk->program = ir;
+  sbBuffer_append(&ir->chunks, &chunk, sizeof(sbIrChunk*));
+  return chunk;
+}
+
+static void chunk_deinitialize(hIrChunk ck) {
+  sbBuffer_deinitialize(&ck->stmts);
+}
+
+/* we can introduce new variables into a scope using LET */
+static sbIrVariable *create_var(hIrChunk ck, const char *name, usize name_len) {
+  sbIrVariable *new_var = sbArena_alloc(&ck->program->arena, sizeof(sbIrVariable));
+
+  ck->variable_count ++;
+  *new_var = (sbIrVariable) {0};
+  new_var->created_index = ck->variable_count,
+
+  sbBuffer_append(&ck->program->varmapping, &(varmapentry) {
+    .name = name,
+    .name_len = name_len,
+    .var = new_var,
+  }, sizeof(varmapentry));
+
+  return new_var;
+}
+
+/* find variables that already exist */
+static sbIrVariable *var_name(hIrChunk ck, const char *name, usize name_len) {
+  usize nvars = ck->program->varmapping.size / sizeof(varmapentry);
+  for (usize i = 0; i < nvars; i++) {
+    varmapentry *entry = &((varmapentry*)ck->program->varmapping.data)[i];
+    if (sbstrncmp(name, entry->name, name_len) == 0) {
+      return entry->var;
+    }
+  }
+
+  return NULL;
+}
+
+static sbIrLabel *new_label(hIrChunk ck) {
+  sbIrLabel *l = sbArena_alloc(&ck->program->arena, sizeof(sbIrLabel));
+  ck->label_count ++;
+  l->id = ck->label_count;
+  return l;
+}
+
+static sbIrExpr *new_expr(hIrChunk ck, sbIrExpr *expr) {
+  sbIrExpr *where_to_put = sbArena_alloc(&ck->program->arena, sizeof(sbIrExpr));
+  memcpy(where_to_put, expr, sizeof(sbIrExpr));
+  return where_to_put;
+}
+
+static sbIrExpr *expr_var(hIrChunk ck, const char *name, usize name_len) {
+  sbIrVariable *var_by_name = var_name(ck, name, name_len);
+  /* TODO do a different thing */
+  if (!var_by_name) {
+    fprintf(stderr, "unknown variable name! '%s'\n", name);
+    return NULL;
+  }
+  return new_expr(ck, &(sbIrExpr) {
+    .type = IR_E_VAR,
+    .var = var_by_name,
+  });
+}
+
+static sbIrExpr *expr_value(hIrChunk ck, hV *value) {
+  return new_expr(ck, &(sbIrExpr) {
+    .type = IR_E_VALUE,
+    .value = *value,
+  });
+}
+
+static sbIrExpr *expr_op(hIrChunk ck, sbAstOp op, sbIrExpr *left, sbIrExpr *right) {
+  return new_expr(ck, &(sbIrExpr) {
+    .type = IR_E_OP,
+    .op.type = op,
+    .op.left = left,
+    .op.right = right,
+  });
+}
+
+static void put_ir_stmt(hIrChunk ck, sbIrStmt *stmt) {
+  sbIrStmt *where_to_put = sbArena_alloc(&ck->program->arena, sizeof(sbIrStmt));
+  memcpy(where_to_put, stmt, sizeof(sbIrStmt));
+  sbBuffer_append(&ck->stmts, &where_to_put, sizeof(sbIrStmt*));
+}
+
+static void put_label(hIrChunk ck, sbIrLabel *l) {
+  put_ir_stmt(ck, &(sbIrStmt) {
+    .type = IR_S_LABEL,
+    .label = l,
+  });
+}
+
+static void put_jump(hIrChunk ck, flag inverted, sbIrExpr *condition, sbIrLabel *label) {
+  put_ir_stmt(ck, &(sbIrStmt) {
+    .type = IR_S_JUMP,
+    .jump.inverted = inverted,
+    .jump.condition = condition,
+    .jump.label = label,
+  });
+}
+
+static void put_expr(hIrChunk ck, sbIrExpr *expr) {
+  put_ir_stmt(ck, &(sbIrStmt) {
+    .type = IR_S_EXPR,
+    .expr = expr,
+  });
+}
+
+static void put_assign(hIrChunk ck, sbIrVariable *assign_to, sbIrExpr *expr) {
+  put_ir_stmt(ck, &(sbIrStmt) {
+    .type = IR_S_ASSIGN,
+    .assign.var = assign_to,
+    .assign.expr = expr,
+  });
+}
+
+static void put_return(hIrChunk ck) {
+  put_ir_stmt(ck, &(sbIrStmt) {
+    .type = IR_S_RETURN,
+  });
+}
+
+static void compile_ast_stmt(hIrChunk ck, sbAst stmtast);
+static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst exprast);
+
+static void compile_ast_stmtseq(hIrChunk ck, sbAst seqast) {
+  usize varmapsize = ck->program->varmapping.size;
+
+  sbAst considering = seqast;
+  while (considering != NO_NODE) {
+    /* walk down the tree a line at a time */
+    if (considering->type != AST_NODE_SEQ) {
+      PANIC("compile_ast_stmtseq expects a SEQ node! (received instead #%d)", seqast->type);
+    }
+
+    compile_ast_stmt(ck, considering->seq.left);
+    considering = considering->seq.right;
+  }
+
+  /* after the block is done, we need to reset the varmapping to not include
+   * any variables that were introduced inside this block */
+  sbBuffer_set_size(&ck->program->varmapping, varmapsize);
+}
+
+void compile_ast_stmt(hIrChunk ck, sbAst node) {
+  sbIrExpr *E1;//, *E2;
+  sbIrLabel *L1, *L2;
+  switch (node->type) {
+    case AST_NODE_WHILE:
+      /*
+       *    WHILE condition { things }
+       * linearizes to:
+       *    JUMP TO LABEL 2 UNCONDITIONALLY
+       *    LABEL 1
+       *    things
+       *    LABEL 2
+       *    JUMP TO LABEL 1 IF condition
+       *
+       * UNTIL is rewritten to WHILE NOT at the parsing stage.
+       */
+      /* TODO: 'break' and 'next' inside loops need
+       * to be handled by passing in labels to this
+       * stmtseq. If we have no labels, we're not in
+       * a loop, and should error. */
+      L1 = new_label(ck);
+      L2 = new_label(ck);
+      E1 = compile_ast_expr(ck, node->seq.left);
+      put_jump(ck, ALWAYS, NULL, L2);
+      put_label(ck, L1);
+      compile_ast_stmtseq(ck, node->seq.right);
+      put_label(ck, L2);
+      put_jump(ck, IF_YES, E1, L1);
+      break;
+
+    case AST_NODE_IF:
+      /*
+       *    IF condition { things }
+       * linearizes to:
+       *    JUMP TO LABEL 1 IF NOT condition
+       *    things
+       *    LABEL 1
+       *
+       *    IF condition { things1 } ELSE { things2 }
+       * linearizes to:
+       *    JUMP TO LABEL 1 IF NOT condition
+       *    things1
+       *    JUMP TO LABEL 2 UNCONDITIONALLY
+       *    LABEL 1
+       *    things2
+       *    LABEL 2
+       *
+       * UNLESS is rewritten to IF NOT at the parsing stage.
+       */
+      if (node->tri.right == NO_NODE) {
+        /* no else branch */
+        E1 = compile_ast_expr(ck, node->tri.left);
+        L1 = new_label(ck);
+        put_jump(ck, IF_NOT, E1, L1);
+        compile_ast_stmtseq(ck, node->tri.center);
+        put_label(ck, L1);
+      } else {
+        /* yes else branch */
+        E1 = compile_ast_expr(ck, node->tri.left);
+        L1 = new_label(ck);
+        L2 = new_label(ck);
+        put_jump(ck, IF_NOT, E1, L1);
+        compile_ast_stmtseq(ck, node->tri.center);
+        put_jump(ck, ALWAYS, NULL, L2);
+        put_label(ck, L1);
+        compile_ast_stmtseq(ck, node->tri.right);
+        put_label(ck, L2);
+      }
+      break;
+
+    case AST_NODE_REPEAT:
+      /*
+       *    REPEAT { things } WHILE condition
+       * linearizes to:
+       *    LABEL 1
+       *    things
+       *    JUMP TO LABEL 1 IF condition
+       *
+       *    REPEAT { things }
+       * linearizes to:
+       *    LABEL 1
+       *    things
+       *    JUMP TO LABEL 1 UNCONDITIONALLY
+       *
+       * REPEAT..UNTIL is rewritten to REPEAT..WHILE NOT
+       * at the parsing stage.
+       */
+      L1 = new_label(ck);
+      if (node->seq.right) {
+        E1 = compile_ast_expr(ck, node->seq.right);
+        put_label(ck, L1);
+        compile_ast_stmtseq(ck, node->seq.left);
+        put_jump(ck, IF_YES, E1, L1);
+      } else {
+        put_label(ck, L1);
+        compile_ast_stmtseq(ck, node->seq.left);
+        put_jump(ck, ALWAYS, NULL, L1);
+      }
+      break;
+
+    default:
+      /* probably an expr node */
+      put_expr(ck, compile_ast_expr(ck, node));
+  }
+}
+
+static sbIrExpr *compile_ast_expr(hIrChunk ck, sbAst node) {
+  if (node == NO_NODE) return NULL;
+
+  if (node->type == AST_NODE_OP) {
+    sbIrExpr *left = NULL, *right = NULL;
+    if (node->op.left != NO_NODE) {
+      left = compile_ast_expr(ck, node->op.left);
+    }
+    if (node->op.right != NO_NODE) {
+      right = compile_ast_expr(ck, node->op.right);
+    }
+    return expr_op(ck, node->op.type, left, right);
+  } else if (node->type == AST_VAL_INT) {
+    return expr_value(ck, &HVINT(node->i));
+  } else if (node->type == AST_NODE_NAME) {
+    /* TODO: I don't want to use strlen. Can we remember the lengths of symbols? */
+    return expr_var(ck, sbSymbol_name(node->symb), strlen(sbSymbol_name(node->symb)));
+  } else {
+    PANIC("todo!");
+  }
+}
+
+static void print_expr(sbIrExpr *e) {
+  printf("[ ");
+  switch(e->type) {
+    case IR_E_VAR:
+      printf("variable %zu", e->var->created_index);
+      break;
+    case IR_E_VALUE:
+      if (e->value.type == IT_INTEGER) {
+        printf("%lld", e->value.integer);
+      } else {
+        printf("(some value)");
+      }
+      break;
+    case IR_E_OP:
+      print_expr(e->op.left);
+      printf(" %c ", e->op.type);
+      if (e->op.right) {
+        print_expr(e->op.right);
+      }
+      break;
+    default:
+      printf("(something)");
+  }
+  printf(" ]");
+}
+
+static void print_stmt(sbIrStmt *s) {
+  switch (s->type) {
+    case IR_S_LABEL:
+      printf("label %zu:\n", s->label->id);
+      break;
+    case IR_S_JUMP:
+      printf("jump to label %zu", s->jump.label->id);
+      if (s->jump.condition) {
+        if (s->jump.inverted) {
+          printf(" if not ");
+        } else {
+          printf(" if ");
+        }
+        print_expr(s->jump.condition);
+      }
+      printf("\n");
+      break;
+    case IR_S_RETURN:
+      printf("return\n");
+      break;
+    case IR_S_EXPR:
+      print_expr(s->expr);
+      printf("\n");
+      break;
+    case IR_S_ASSIGN:
+    default:
+      printf("do something\n");
+  }
+}
diff --git a/src/compile/ir.h b/src/compile/ir.h
new file mode 100644 (file)
index 0000000..12d73e9
--- /dev/null
@@ -0,0 +1,126 @@
+#ifndef __SARABANDE_IRTYPES_H__
+#define __SARABANDE_IRTYPES_H__
+
+#include "common.h"
+
+#include "parse/ast.h"
+
+/* Phase 5 of the compiler.
+ * (The first four phases are in the `parse` module.)
+ * Now that we made it past the harrowing syntax phase, we are
+ * ready to start actually trying to compile the code. */
+
+/* Convert AST parse tree to a "linearized" series of chunks
+ * consisting of simple operations like assignments and jumps.
+ * Expressions are still nested (we will flatten them in the next
+ * phase.) Variables and labels / jump targets are still left
+ * "blank" -- we will decide "where" they go once we have a whole
+ * program to look at. */
+
+/* One "chunk" in IR here should correspond to one "block" in the
+ * VM module, with the same semantics: you can do local jumps
+ * inside them, but jumping between them is due to call / return.
+ * Each one represents a function body; we separate nested functions
+ * out into their own things, and refer to them by ID. */
+
+/* We will also do semantic analysis here, but since it is a dynamic
+ * language, semantic analysis is fairly weak; in particular, we do
+ * detect when variables are not in the current scope, but we assume
+ * they exist via magical context and will show up at runtime. I sure
+ * hope so, at least! */
+
+typedef enum sbIrStmtType {
+  IR_S_NULL,
+  IR_S_LABEL,
+  IR_S_EXPR,
+  IR_S_ASSIGN,
+  IR_S_JUMP,
+  IR_S_RETURN,
+} sbIrStmtType;
+
+typedef enum sbIrExprType {
+  IR_E_NULL,
+  IR_E_VALUE,
+  IR_E_OP,
+  IR_E_VAR,
+  IR_E_FUNC,
+} sbIrExprType;
+
+typedef struct sbIrLabel {
+  flag found_yet;
+  usize id;
+  usize block_position;
+} sbIrLabel;
+
+typedef struct sbIrVariable {
+  usize created_index;
+  usize first_appearance;
+  usize last_appearance;
+  usize assigned_index;
+} sbIrVariable;
+
+typedef struct sbIrJump {
+  flag inverted;
+  struct sbIrExpr *condition;
+  sbIrLabel *label;
+} sbIrJump;
+
+typedef struct sbIrExpr {
+  sbIrExprType type;
+  union {
+    hV value;
+    struct sbIrChunk *func;
+    sbIrVariable *var;
+    struct {
+      sbAstOp type;
+      struct sbIrExpr *left;
+      struct sbIrExpr *right;
+    } op;
+  };
+} sbIrExpr;
+
+typedef struct sbIrAssign {
+  sbIrVariable *var;
+  sbIrExpr *expr;
+} sbIrAssign;
+
+typedef struct sbIrStmt {
+  usize position;
+  sbIrStmtType type;
+  union {
+    sbIrExpr *expr;
+    sbIrLabel *label;
+    sbIrAssign assign;
+    sbIrJump jump;
+  };
+} sbIrStmt;
+
+struct sbIrProgram;
+
+typedef struct sbIrChunk {
+  struct sbIrProgram *program;
+  usize id;
+  usize label_count;
+  usize variable_count;
+  sbBuffer stmts;
+} sbIrChunk;
+
+typedef struct sbIrProgram {
+  sbArena arena;
+  sbBuffer varmapping;
+  sbBuffer chunks;
+} sbIrProgram;
+
+typedef struct sbIrProgram *hIrProgram;
+
+typedef struct sbIrChunk *hIrChunk;
+
+void sbIrProgram_initialize(hIrProgram ir, usize initial_arena_size);
+
+void sbIrProgram_deinitialize(hIrProgram ir);
+
+void sbIrProgram_compile_ast(hIrProgram ir, sbAst ast);
+
+void sbIrProgram_print(hIrProgram ir);
+
+#endif
index fd2caf7..e66bfb4 100644 (file)
@@ -4,6 +4,7 @@
 #include "data/string.h"
 #include "data/hashtable.h"
 #include "data/symbol.h"
+#include "compile/ir.h"
 #include "vm/exec.h"
 
 int main(int argc, char **argv) {
@@ -19,14 +20,28 @@ int main(int argc, char **argv) {
 
         if (parse_result == NULL) {
           fprintf(stderr, "fatal error: Could not open script '%s'\n", argv[1]);
+          return -2;
         } else if (parse_result->type == AST_ERROR) {
           fprintf(stderr, "fatal error: Could not run '%s' due to syntax errors.\n", argv[1]);
-        } else {
-          printf("wow! the syntax is good!\n");
+          return -1;
         }
 
+        /* syntax is valid, now try to compile */
+
+        sbIrProgram ir = {0};
+
+        sbIrProgram_initialize(&ir, 32768);
+
+        sbIrProgram_compile_ast(&ir, parse_result);
+
+        /* free AST, don't need it anymore */
         sbParser_deinitialize(&pr);
 
+        // print out program
+        sbIrProgram_print(&ir);
+
+        sbIrProgram_deinitialize(&ir);
+
         sbSymbol_sys_deinit();
         sbHash_sys_deinit();
         sbString_sys_deinit();
diff --git a/src/mem/debug.c b/src/mem/debug.c
new file mode 100644 (file)
index 0000000..15bec01
--- /dev/null
@@ -0,0 +1,8 @@
+#include "mem/debug.h"
+
+void print_memory_bytes(void *where, usize how_many) {
+  for (usize i = 0; i < how_many; i++) {
+    printf("%02X ", ((u8*)(char*)where)[i]);
+  }
+  printf("\n");
+}
diff --git a/src/mem/debug.h b/src/mem/debug.h
new file mode 100644 (file)
index 0000000..d8e9627
--- /dev/null
@@ -0,0 +1,3 @@
+#include "common.h"
+
+void print_memory_bytes(void *where, usize how_many);
index 1cef746..ac9fcd9 100644 (file)
@@ -1,3 +1,6 @@
+#ifndef __SARABANDE_AST_H__
+#define __SARABANDE_AST_H__
+
 #include "common.h"
 
 #include "data/value.h"
@@ -99,3 +102,8 @@ typedef struct sbAstNode {
 } sbAstNode;
 
 typedef sbAstNode *sbAst;
+
+extern sbAst NO_NODE;
+extern sbAst ERROR_NODE;
+
+#endif
index fa90516..4b44451 100644 (file)
@@ -1,5 +1,12 @@
 #include "common.h"
 
+/* Phase 1 of the compiler.
+ * This is exceedingly simple. We just read data from a file a line at
+ * a time, and dribble out one character at a time to the next phase
+ * when it asks us to. We can also report back what a certain line
+ * said, if a later phase wants to highlight a syntax error, as long
+ * as we haven't gone too far ahead. */
+
 typedef struct sbFileReader *hFileReader;
 
 hFileReader sbFileReader_open(const char *filename);
index 569d1ca..56369c7 100644 (file)
@@ -226,6 +226,7 @@ static flag block_header_can_end_after(sbTokenType type) {
         || type == T_FATARROW
         || type == T_SQUIGARROW
         || type == T_rCASE
+        || type == T_rREPEAT
         || type == T_ELLIPSIS
         || type == T_rDO;
 }
@@ -398,7 +399,11 @@ static void compute_next_token(hLexer lx) {
     }
 
     if (begins_brace_terminated_state(token.type)) {
-        brackets_stack_push(lx, 'B');
+        /* only start brace-terminated state if we are actually starting a new line,
+         * or if we are after an "else" (special case for else if) */
+        if (lx->last_token_seen.type == ';' || lx->last_token_seen.type == T_rELSE) {
+            brackets_stack_push(lx, 'B');
+        }
     }
 
     if (close_invisible_parens_before(token.type)) {
index 98bd427..812f187 100644 (file)
@@ -4,6 +4,14 @@
 #include "token.h"
 #include "scanner.h"
 
+/* Phase 3 of the compiler.
+ * "Lexer" isn't a very good name for this phase. This takes
+ * the stream of undifferentiated tokens coming from scanner
+ * and does some basic transformations on the stream to add in
+ * things like parentheses and semicolons which are implied by
+ * the structure of the code but not specified. Maybe it's a
+ * little too cute in places, but I kind of like it. */
+
 typedef struct sbLexer {
     sbScanner scanner;
     sbTokenQueue input_queue;
index 8bbc813..ba2ed36 100644 (file)
@@ -59,9 +59,9 @@ typedef struct tokenspelling {
 } tokenspelling;
 
 static sbAstNode NONE_SENTINEL_VALUE = {0};
-static sbAst NO_NODE = &NONE_SENTINEL_VALUE;
 static sbAstNode ERROR_SENTINEL_VALUE = { .type = AST_ERROR };
-static sbAst ERROR_NODE = &ERROR_SENTINEL_VALUE;
+sbAst NO_NODE = &NONE_SENTINEL_VALUE;
+sbAst ERROR_NODE = &ERROR_SENTINEL_VALUE;
 
 static tokenspelling token_spellings[] = {
   { T_NEWLINE, "newline" },
@@ -298,6 +298,7 @@ static sbAst unop_node(hParser pr, sbAstOp operation, sbAst child) {
     .type = AST_NODE_OP,
     .op.type = operation,
     .op.left = child,
+    .op.right = NO_NODE,
   };
   return new_node(pr, &n);
 }
@@ -335,6 +336,7 @@ static sbAst wrap_node(hParser pr, sbAstType type, sbAst left) {
   sbAstNode n = (sbAstNode) {
     .type = type,
     .seq.left = left,
+    .seq.right = NO_NODE,
   };
   return new_node(pr, &n);
 }
@@ -698,7 +700,8 @@ static sbAst parse_stmt(hParser pr) {
       sbLexToken t = peek_ahead(pr, 0);
       if (t.type == T_rIF) {
         /* else if ...as above... */
-        else_body = parse_stmt(pr);
+        /* (we pretend like the second 'if' is inside a block) */
+        else_body = seq_node(pr, AST_NODE_SEQ, parse_stmt(pr), NO_NODE);
       } else {
         else_body = parse_block(pr);
       }
@@ -733,7 +736,7 @@ static sbAst parse_stmt(hParser pr) {
     } else if (expect(pr, T_rUNTIL)) {
       /* repeat { ... } until */
       sbAst condition = parse_expr(pr, 0);
-      return seq_node(pr, AST_NODE_REPEAT, body, condition);
+      return seq_node(pr, AST_NODE_REPEAT, body, unop_node(pr, AST_OP_NOT, condition));
     } else {
       /* repeat { ... } # infinite loop */
       return seq_node(pr, AST_NODE_REPEAT, body, NO_NODE);
index 7b1b087..b61664d 100644 (file)
@@ -3,6 +3,14 @@
 #include "parse/ast.h"
 #include "parse/lexer.h"
 
+/* Phase 4 of the compiler.
+ * We take the stream of tokens coming from lexer (some of which tokens
+ * are not actually real and are ghosts) and attempt to form them into a
+ * coherent syntax that actually has some kind of structure. If we cannot
+ * figure out how to parse a given token, we return a syntax error to
+ * the user and compilation stops here. (The earlier stages can send us
+ * errors as well through the means of a few special "error" tokens.) */
+
 typedef struct sbParser {
   sbLexer lexer;
   sbTokenQueue input_queue;
index ec1b07e..91c392d 100644 (file)
@@ -7,6 +7,12 @@
 #include "token.h"
 #include "mem/mem.h"
 
+/* Phase 2 of the compiler.
+ * This takes the stream of single characters coming from filereader
+ * and groups them together into individual tokens which have a type.
+ * We recognize things like operators, identifiers, numbers, and
+ * strings here. */
+
 typedef struct sbScanner {
     sbLexToken next_token;
     hFileReader file_reader;