From: cassowarii Date: Mon, 29 Jun 2026 02:54:10 +0000 (-0700) Subject: Basic framework for IR code generation X-Git-Url: https://git.cassowary.me/gitweb.cgi?a=commitdiff_plain;h=0f0c8a23b5349adadbc525cd5f9fb69e25fc50ea;p=sarabande.git Basic framework for IR code generation --- diff --git a/.gitignore b/.gitignore index 1f2492e..5b29976 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.swp build/ obj/ +unused/ diff --git a/src/compile/ir.c b/src/compile/ir.c new file mode 100644 index 0000000..eff3d89 --- /dev/null +++ b/src/compile/ir.c @@ -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 index 0000000..12d73e9 --- /dev/null +++ b/src/compile/ir.h @@ -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 diff --git a/src/main.c b/src/main.c index fd2caf7..e66bfb4 100644 --- a/src/main.c +++ b/src/main.c @@ -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 index 0000000..15bec01 --- /dev/null +++ b/src/mem/debug.c @@ -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 index 0000000..d8e9627 --- /dev/null +++ b/src/mem/debug.h @@ -0,0 +1,3 @@ +#include "common.h" + +void print_memory_bytes(void *where, usize how_many); diff --git a/src/parse/ast.h b/src/parse/ast.h index 1cef746..ac9fcd9 100644 --- a/src/parse/ast.h +++ b/src/parse/ast.h @@ -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 diff --git a/src/parse/filereader.h b/src/parse/filereader.h index fa90516..4b44451 100644 --- a/src/parse/filereader.h +++ b/src/parse/filereader.h @@ -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); diff --git a/src/parse/lexer.c b/src/parse/lexer.c index 569d1ca..56369c7 100644 --- a/src/parse/lexer.c +++ b/src/parse/lexer.c @@ -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)) { diff --git a/src/parse/lexer.h b/src/parse/lexer.h index 98bd427..812f187 100644 --- a/src/parse/lexer.h +++ b/src/parse/lexer.h @@ -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; diff --git a/src/parse/parser.c b/src/parse/parser.c index 8bbc813..ba2ed36 100644 --- a/src/parse/parser.c +++ b/src/parse/parser.c @@ -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); diff --git a/src/parse/parser.h b/src/parse/parser.h index 7b1b087..b61664d 100644 --- a/src/parse/parser.h +++ b/src/parse/parser.h @@ -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; diff --git a/src/parse/scanner.h b/src/parse/scanner.h index ec1b07e..91c392d 100644 --- a/src/parse/scanner.h +++ b/src/parse/scanner.h @@ -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;