$ git commit -m "$(cat <<'EOF' Phase 5: scripting language (tree-walking interpreter) Adds a shared tokenizer (kernel/lang/lexer.c, reused by the Phase 6 compiler) and a dynamic scripting language on top of it: recursive- descent parser, generic AST, tree-walking evaluator. Supports ints, strings (+ concatenation), variables, if/else, while, user-defined functions, and OS builtins (print, fread, fwrite, flist) backed by tinyfs. Shell gains `run <file>`. Fixes a real bug caught by the new fixture test: eval_binop handled TOK_PLUS only in the string-concat branch, so int+int silently fell through to `default: return 0` - making every arithmetic sum evaluate to 0 and turning `i = i + 1` into an infinite loop. Also fixes a boot regression hit while growing the kernel size budget (64 -> 512 sectors) to make room for this phase: stage2's single INT13h AH=0x42 read of the whole kernel silently failed once the transfer grew past whatever SeaBIOS's per-call sector limit is. load_kernel now reads in bounded 64-sector chunks, which is the correct general fix rather than a fragile smaller magic number. TINYFS_FS_START_LBA moves 128 -> 640 to match the larger kernel budget, and the Makefile now fails the build loudly instead of silently truncating if the kernel image ever exceeds its sector budget again. tests/hello.tsh + tests/expected/hello.tsh.txt exercise the language end-to-end via the shell's `run` command in tools/run_tests.sh. EOF )"
[main f4702ba] Phase 5: scripting language (tree-walking interpreter) 16 files changed, 1126 insertions(+), 17 deletions(-) create mode 100644 kernel/lang/lexer.c create mode 100644 kernel/lang/lexer.h create mode 100644 kernel/lang/script/ast.c create mode 100644 kernel/lang/script/ast.h create mode 100644 kernel/lang/script/eval.c create mode 100644 kernel/lang/script/eval.h create mode 100644 kernel/lang/script/parser.c create mode 100644 kernel/lang/script/parser.h create mode 100644 tests/expected/hello.tsh.txt create mode 100644 tests/hello.tsh
{
"taskId": "6",
"status": "completed"
}Updated task #6 status
{
"taskId": "7",
"status": "in_progress"
}Updated task #7 status
# tiny compiler bytecode ISA
A stack machine, similar in spirit to tinyfs: minimal, fixed-size, no
dynamic growth. Values are 32-bit signed integers only - the compiled
language (unlike the scripting language) has no string type.
## Design invariant
Every expression, once compiled, leaves **exactly one** value on the value
stack. Statement-level wrappers (`OP_POP` after an expression-statement)
and binary/unary ops (pop N, push 1) all rely on this. Assignment is an
expression too: it evaluates to the assigned value, via `<value> DUP
STORE_*` rather than a plain `STORE_*` that would leave nothing behind.
## VM state
Four fixed-size arrays, no heap allocation at run time:
- **value stack** - expression operands/results.
- **locals stack** - one contiguous region per active call frame (params
then other locals, slot-indexed - names are resolved to slots at compile
time, the VM has no notion of variable names).
- **call stack** - `{return_pc, locals_base}` per active call, for nested
and recursive calls.
- **globals** - fixed array, slot-indexed, for variables assigned outside
any function.
Functions cannot see globals or an enclosing scope - same "no closures"
rule as the scripting language's interpreter (kernel/lang/script/eval.c),
so both languages behave consistently.
## Opcodes
| op | value | operand | stack effect |
|---|---|---|---|
| `OP_HALT` | 0 | - | stops execution |
| `OP_PUSH_CONST` | 1 | i32 | push const |
| `OP_POP` | 2 | - | pop |
| `OP_DUP` | 3 | - | push top again |
| `OP_LOAD_LOCAL` | 4 | u8 slot | push locals[base+slot] |
| `OP_STORE_LOCAL` | 5 | u8 slot | pop -> locals[base+slot] |
| `OP_LOAD_GLOBAL` | 6 | u8 slot | push globals[slot] |
| `OP_STORE_GLOBAL` | 7 | u8 slot | pop -> globals[slot] |
| `OP_ADD`..`OP_OR` | 8-20 | - | pop 2, push 1 (`+ - * / % == != < <= > >= && \|\|`) |
| `OP_NEG` | 21 | - | pop 1, push 1 |
| `OP_NOT` | 22 | - | pop 1, push 1 |
| `OP_JMP` | 23 | u16 addr | `pc = addr` |
| `OP_JMP_IF_FALSE` | 24 | u16 addr | pop cond; if false, `pc = addr` |
| `OP_CALL` | 25 | u16 func index | pop `param_count` args, push new frame, jump to entry |
| `OP_RET` | 26 | - | pop return value, pop frame, push return value back |
| `OP_SYS_PRINT` | 27 | - | pop 1, print as decimal + newline, push 0 |
`OP_AND`/`OP_OR` are eager (both operands always evaluated), unlike the
scripting language's short-circuit `&&`/`||` - a deliberate simplification
for the compiled path.
Division/modulo by zero yields 0 rather than trapping, matching the
scripting language's behavior.
## File format (`.tbc`)
```
struct tbc_header { // 12 bytes, packed
uint32_t magic; // 'T','B','C','1'
uint16_t entry_pc; // where top-level code starts, within code[]
uint16_t global_count;
uint16_t func_count;
uint16_t code_size;
};
struct tbc_func { // 4 bytes, packed, func_count of these follow the header
uint16_t entry_addr; // offset within code[]
uint8_t param_count;
uint8_t local_count; // includes params, which occupy slots 0..param_count-1
};
uint8_t code[code_size]; // follows the func table
```
The whole file must fit in one tinyfs file (`TINYFS_MAX_FILE_SIZE`, 4KB).
## Source language
C-like and statically scoped: `int`-only variables (no declarations -
first assignment creates the variable, in whichever scope - global or
current function - is active), `if`/`else`, `while`, `func name(params)
{...}` with `return`, arithmetic/comparison/logical operators, and one
builtin, `print(expr)`. Parser: `kernel/lang/compiler/parser.c`, reusing
the scripting language's AST node type (`kernel/lang/script/ast.h`) since
the tree shape is identical minus string literals. Codegen:
`kernel/lang/compiler/codegen.c`. VM: `kernel/lang/vm.c`.
Shell usage: `build prog.tc prog.tbc` compiles, `exec prog.tbc` runs it.
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/docs/bytecode-isa.md (file state is current in your context — no need to Read it back)
#pragma once
/* Shared between kernel/lang/compiler/codegen.c (producer) and
* kernel/lang/vm.c (consumer). See docs/bytecode-isa.md. */
#include <stdint.h>
#define TBC_MAGIC 0x31434254u /* "TBC1" */
typedef enum {
OP_HALT = 0,
OP_PUSH_CONST,
OP_POP,
OP_DUP,
OP_LOAD_LOCAL,
OP_STORE_LOCAL,
OP_LOAD_GLOBAL,
OP_STORE_GLOBAL,
OP_ADD,
OP_SUB,
OP_MUL,
OP_DIV,
OP_MOD,
OP_EQ,
OP_NEQ,
OP_LT,
OP_LE,
OP_GT,
OP_GE,
OP_AND,
OP_OR,
OP_NEG,
OP_NOT,
OP_JMP,
OP_JMP_IF_FALSE,
OP_CALL,
OP_RET,
OP_SYS_PRINT,
} bc_opcode_t;
typedef struct {
uint32_t magic;
uint16_t entry_pc;
uint16_t global_count;
uint16_t func_count;
uint16_t code_size;
} __attribute__((packed)) tbc_header_t;
typedef struct {
uint16_t entry_addr;
uint8_t param_count;
uint8_t local_count;
} __attribute__((packed)) tbc_func_t;
#define VM_FUNCS_MAX 16
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/bytecode.h (file state is current in your context — no need to Read it back)
$ mkdir -p /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/compiler
(Bash completed with no output)
#pragma once
#include "../lexer.h"
#include "../script/ast.h"
typedef struct {
lexer_t lexer;
token_t cur;
int error;
char error_msg[64];
} tc_parser_t;
void tc_parser_init(tc_parser_t *p, const char *src);
/* Parses the whole source as a top-level AST_BLOCK of statements. Check
* p->error afterwards. Unlike the scripting language's parser, string
* literals are a parse error here - the compiled language is int-only. */
ast_node_t *tc_parse_program(tc_parser_t *p);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/compiler/parser.h (file state is current in your context — no need to Read it back)
#include "parser.h"
#include "../../string.h"
static void copy_name(char *dst, const char *src) {
int i = 0;
while (src[i] && i < TOKEN_TEXT_MAX - 1) {
dst[i] = src[i];
i++;
}
dst[i] = '\0';
}
static void advance(tc_parser_t *p) {
p->cur = lexer_next(&p->lexer);
}
void tc_parser_init(tc_parser_t *p, const char *src) {
lexer_init(&p->lexer, src);
p->error = 0;
p->error_msg[0] = '\0';
advance(p);
}
static void parse_error(tc_parser_t *p, const char *msg) {
if (!p->error) {
p->error = 1;
copy_name(p->error_msg, msg);
}
}
static int check(tc_parser_t *p, token_type_t t) {
return p->cur.type == t;
}
static int match(tc_parser_t *p, token_type_t t) {
if (check(p, t)) {
advance(p);
return 1;
}
return 0;
}
static void expect(tc_parser_t *p, token_type_t t) {
if (!match(p, t)) {
parse_error(p, "unexpected token");
}
}
static int is_kw(tc_parser_t *p, const char *kw) {
return check(p, TOK_IDENT) && strcmp(p->cur.text, kw) == 0;
}
static ast_node_t *parse_expression(tc_parser_t *p);
static ast_node_t *parse_statement(tc_parser_t *p);
static ast_node_t *parse_block(tc_parser_t *p) {
expect(p, TOK_LBRACE);
ast_node_t *blk = ast_new(AST_BLOCK);
ast_node_t *tail = NULL;
while (!check(p, TOK_RBRACE) && !check(p, TOK_EOF) && !p->error) {
ast_node_t *s = parse_statement(p);
if (!blk->list) { blk->list = s; } else { tail->next = s; }
tail = s;
}
expect(p, TOK_RBRACE);
return blk;
}
static ast_node_t *parse_primary(tc_parser_t *p) {
if (check(p, TOK_NUMBER)) {
ast_node_t *n = ast_new(AST_NUM);
n->num = p->cur.number;
advance(p);
return n;
}
if (check(p, TOK_STRING)) {
parse_error(p, "strings are not supported in the compiled language");
advance(p);
return ast_new(AST_NUM);
}
if (check(p, TOK_IDENT)) {
char name[TOKEN_TEXT_MAX];
copy_name(name, p->cur.text);
advance(p);
if (check(p, TOK_LPAREN)) {
advance(p);
ast_node_t *n = ast_new(AST_CALL);
copy_name(n->name, name);
ast_node_t *tail = NULL;
if (!check(p, TOK_RPAREN)) {
for (;;) {
ast_node_t *arg = parse_expression(p);
if (!n->list) { n->list = arg; } else { tail->next = arg; }
tail = arg;
if (!match(p, TOK_COMMA)) {
break;
}
}
}
expect(p, TOK_RPAREN);
return n;
}
ast_node_t *n = ast_new(AST_VAR);
copy_name(n->name, name);
return n;
}
if (match(p, TOK_LPAREN)) {
ast_node_t *n = parse_expression(p);
expect(p, TOK_RPAREN);
return n;
}
parse_error(p, "expected expression");
advance(p);
return ast_new(AST_NUM);
}
static ast_node_t *parse_unary(tc_parser_t *p) {
if (check(p, TOK_MINUS) || check(p, TOK_NOT)) {
token_type_t op = p->cur.type;
advance(p);
ast_node_t *n = ast_new(AST_UNOP);
n->op = op;
n->a = parse_unary(p);
return n;
}
return parse_primary(p);
}
static ast_node_t *parse_binop_level(tc_parser_t *p, ast_node_t *(*next)(tc_parser_t *),
const token_type_t *ops, int nops) {
ast_node_t *left = next(p);
for (;;) {
int matched = 0;
for (int i = 0; i < nops; i++) {
if (check(p, ops[i])) {
token_type_t op = p->cur.type;
advance(p);
ast_node_t *right = next(p);
ast_node_t *n = ast_new(AST_BINOP);
n->op = op;
n->a = left;
n->b = right;
left = n;
matched = 1;
break;
}
}
if (!matched) {
return left;
}
}
}
static ast_node_t *parse_multiplicative(tc_parser_t *p) {
static const token_type_t ops[] = { TOK_STAR, TOK_SLASH, TOK_PERCENT };
return parse_binop_level(p, parse_unary, ops, 3);
}
static ast_node_t *parse_additive(tc_parser_t *p) {
static const token_type_t ops[] = { TOK_PLUS, TOK_MINUS };
return parse_binop_level(p, parse_multiplicative, ops, 2);
}
static ast_node_t *parse_relational(tc_parser_t *p) {
static const token_type_t ops[] = { TOK_LT, TOK_LE, TOK_GT, TOK_GE };
return parse_binop_level(p, parse_additive, ops, 4);
}
static ast_node_t *parse_equality(tc_parser_t *p) {
static const token_type_t ops[] = { TOK_EQ, TOK_NEQ };
return parse_binop_level(p, parse_relational, ops, 2);
}
static ast_node_t *parse_and(tc_parser_t *p) {
static const token_type_t ops[] = { TOK_AND };
return parse_binop_level(p, parse_equality, ops, 1);
}
static ast_node_t *parse_or(tc_parser_t *p) {
static const token_type_t ops[] = { TOK_OR };
return parse_binop_level(p, parse_and, ops, 1);
}
static ast_node_t *parse_assignment(tc_parser_t *p) {
if (check(p, TOK_IDENT)) {
tc_parser_t save = *p;
char name[TOKEN_TEXT_MAX];
copy_name(name, p->cur.text);
advance(p);
if (check(p, TOK_ASSIGN)) {
advance(p);
ast_node_t *value = parse_assignment(p);
ast_node_t *n = ast_new(AST_ASSIGN);
copy_name(n->name, name);
n->a = value;
return n;
}
*p = save;
}
return parse_or(p);
}
static ast_node_t *parse_expression(tc_parser_t *p) {
return parse_assignment(p);
}
static ast_node_t *parse_statement(tc_parser_t *p) {
if (check(p, TOK_LBRACE)) {
return parse_block(p);
}
if (is_kw(p, "if")) {
advance(p);
expect(p, TOK_LPAREN);
ast_node_t *cond = parse_expression(p);
expect(p, TOK_RPAREN);
ast_node_t *then_blk = parse_block(p);
ast_node_t *else_blk = NULL;
if (is_kw(p, "else")) {
advance(p);
else_blk = is_kw(p, "if") ? parse_statement(p) : parse_block(p);
}
ast_node_t *n = ast_new(AST_IF);
n->a = cond;
n->b = then_blk;
n->c = else_blk;
return n;
}
if (is_kw(p, "while")) {
advance(p);
expect(p, TOK_LPAREN);
ast_node_t *cond = parse_expression(p);
expect(p, TOK_RPAREN);
ast_node_t *body = parse_block(p);
ast_node_t *n = ast_new(AST_WHILE);
n->a = cond;
n->b = body;
return n;
}
if (is_kw(p, "func")) {
advance(p);
char name[TOKEN_TEXT_MAX];
if (check(p, TOK_IDENT)) {
copy_name(name, p->cur.text);
advance(p);
} else {
parse_error(p, "expected function name");
name[0] = '\0';
}
expect(p, TOK_LPAREN);
ast_node_t *params = NULL;
ast_node_t *ptail = NULL;
if (!check(p, TOK_RPAREN)) {
for (;;) {
if (check(p, TOK_IDENT)) {
ast_node_t *pn = ast_new(AST_VAR);
copy_name(pn->name, p->cur.text);
advance(p);
if (!params) { params = pn; } else { ptail->next = pn; }
ptail = pn;
} else {
parse_error(p, "expected parameter name");
break;
}
if (!match(p, TOK_COMMA)) {
break;
}
}
}
expect(p, TOK_RPAREN);
ast_node_t *body = parse_block(p);
ast_node_t *n = ast_new(AST_FUNC_DEF);
copy_name(n->name, name);
n->list = params;
n->b = body;
return n;
}
if (is_kw(p, "return")) {
advance(p);
ast_node_t *n = ast_new(AST_RETURN);
if (!check(p, TOK_SEMI)) {
n->a = parse_expression(p);
}
expect(p, TOK_SEMI);
return n;
}
ast_node_t *e = parse_expression(p);
expect(p, TOK_SEMI);
ast_node_t *n = ast_new(AST_EXPR_STMT);
n->a = e;
return n;
}
ast_node_t *tc_parse_program(tc_parser_t *p) {
ast_node_t *prog = ast_new(AST_BLOCK);
ast_node_t *tail = NULL;
while (!check(p, TOK_EOF) && !p->error) {
ast_node_t *s = parse_statement(p);
if (!prog->list) { prog->list = s; } else { tail->next = s; }
tail = s;
}
return prog;
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/compiler/parser.c (file state is current in your context — no need to Read it back)
#pragma once
#include <stdint.h>
#include "../script/ast.h"
/* Compiles a parsed program into a .tbc byte buffer (see
* docs/bytecode-isa.md). Returns 0 on success and fills *out_len; on
* failure returns -1 and fills error_msg. out_buf must be at least
* out_buf_size bytes. */
int codegen_compile(ast_node_t *prog, uint8_t *out_buf, uint32_t out_buf_size,
uint32_t *out_len, char *error_msg, uint32_t error_msg_size);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/compiler/codegen.h (file state is current in your context — no need to Read it back)
- #define VM_FUNCS_MAX 16 + #define VM_FUNCS_MAX 16 + #define VM_GLOBALS_MAX 32 + #define VM_LOCALS_PER_FRAME_MAX 32 /* param_count + other locals, per function */ + #define VM_MAX_CALL_ARGS 8
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/bytecode.h has been updated successfully. (file state is current in your context — no need to Read it back)
#include "codegen.h"
#include "../../string.h"
#include "../bytecode.h"
#include "../lexer.h"
typedef struct {
uint8_t *code;
uint32_t code_cap;
uint32_t code_len;
char func_names[VM_FUNCS_MAX][TOKEN_TEXT_MAX];
tbc_func_t funcs[VM_FUNCS_MAX];
int func_count;
char global_names[VM_GLOBALS_MAX][TOKEN_TEXT_MAX];
int global_count;
char local_names[VM_LOCALS_PER_FRAME_MAX][TOKEN_TEXT_MAX];
int local_count;
int in_function;
int error;
char error_msg[64];
} compiler_t;
static void copy_name(char *dst, const char *src) {
int i = 0;
while (src[i] && i < TOKEN_TEXT_MAX - 1) {
dst[i] = src[i];
i++;
}
dst[i] = '\0';
}
static void compile_error(compiler_t *c, const char *msg) {
if (!c->error) {
c->error = 1;
copy_name(c->error_msg, msg); /* error_msg is 64 bytes, plenty for these */
}
}
static void emit_byte(compiler_t *c, uint8_t b) {
if (c->code_len >= c->code_cap) {
compile_error(c, "compiled program too large");
return;
}
c->code[c->code_len++] = b;
}
static void emit_op(compiler_t *c, bc_opcode_t op) {
emit_byte(c, (uint8_t)op);
}
static void emit_u16(compiler_t *c, uint16_t v) {
emit_byte(c, (uint8_t)(v & 0xFF));
emit_byte(c, (uint8_t)((v >> 8) & 0xFF));
}
static void emit_i32(compiler_t *c, int32_t v) {
uint32_t u = (uint32_t)v;
emit_byte(c, (uint8_t)(u & 0xFF));
emit_byte(c, (uint8_t)((u >> 8) & 0xFF));
emit_byte(c, (uint8_t)((u >> 16) & 0xFF));
emit_byte(c, (uint8_t)((u >> 24) & 0xFF));
}
static void patch_u16(compiler_t *c, uint32_t pos, uint16_t v) {
c->code[pos] = (uint8_t)(v & 0xFF);
c->code[pos + 1] = (uint8_t)((v >> 8) & 0xFF);
}
static int find_local(compiler_t *c, const char *name) {
for (int i = 0; i < c->local_count; i++) {
if (strcmp(c->local_names[i], name) == 0) {
return i;
}
}
return -1;
}
static int find_or_add_local(compiler_t *c, const char *name) {
int i = find_local(c, name);
if (i >= 0) {
return i;
}
if (c->local_count >= VM_LOCALS_PER_FRAME_MAX) {
compile_error(c, "too many local variables in one function");
return 0;
}
copy_name(c->local_names[c->local_count], name);
return c->local_count++;
}
static int find_global(compiler_t *c, const char *name) {
for (int i = 0; i < c->global_count; i++) {
if (strcmp(c->global_names[i], name) == 0) {
return i;
}
}
return -1;
}
static int find_or_add_global(compiler_t *c, const char *name) {
int i = find_global(c, name);
if (i >= 0) {
return i;
}
if (c->global_count >= VM_GLOBALS_MAX) {
compile_error(c, "too many global variables");
return 0;
}
copy_name(c->global_names[c->global_count], name);
return c->global_count++;
}
static int find_func_index(compiler_t *c, const char *name) {
for (int i = 0; i < c->func_count; i++) {
if (strcmp(c->func_names[i], name) == 0) {
return i;
}
}
return -1;
}
static void compile_expr(compiler_t *c, ast_node_t *node);
static void compile_stmt(compiler_t *c, ast_node_t *node);
static void compile_var_load(compiler_t *c, const char *name) {
if (c->in_function) {
int slot = find_local(c, name);
if (slot < 0) {
compile_error(c, "undefined local variable");
slot = 0;
}
emit_op(c, OP_LOAD_LOCAL);
emit_byte(c, (uint8_t)slot);
} else {
int slot = find_global(c, name);
if (slot < 0) {
compile_error(c, "undefined global variable");
slot = 0;
}
emit_op(c, OP_LOAD_GLOBAL);
emit_byte(c, (uint8_t)slot);
}
}
static void compile_var_store(compiler_t *c, const char *name) {
if (c->in_function) {
int slot = find_or_add_local(c, name);
emit_op(c, OP_STORE_LOCAL);
emit_byte(c, (uint8_t)slot);
} else {
int slot = find_or_add_global(c, name);
emit_op(c, OP_STORE_GLOBAL);
emit_byte(c, (uint8_t)slot);
}
}
static const bc_opcode_t BINOP_TABLE[] = {
[TOK_PLUS] = OP_ADD, [TOK_MINUS] = OP_SUB, [TOK_STAR] = OP_MUL,
[TOK_SLASH] = OP_DIV, [TOK_PERCENT] = OP_MOD,
[TOK_EQ] = OP_EQ, [TOK_NEQ] = OP_NEQ,
[TOK_LT] = OP_LT, [TOK_LE] = OP_LE, [TOK_GT] = OP_GT, [TOK_GE] = OP_GE,
[TOK_AND] = OP_AND, [TOK_OR] = OP_OR,
};
static void compile_expr(compiler_t *c, ast_node_t *node) {
switch (node->kind) {
case AST_NUM:
emit_op(c, OP_PUSH_CONST);
emit_i32(c, node->num);
return;
case AST_STR:
compile_error(c, "strings are not supported in the compiled language");
return;
case AST_VAR:
compile_var_load(c, node->name);
return;
case AST_ASSIGN:
compile_expr(c, node->a);
emit_op(c, OP_DUP); /* assignment evaluates to the assigned value */
compile_var_store(c, node->name);
return;
case AST_UNOP:
compile_expr(c, node->a);
emit_op(c, node->op == TOK_MINUS ? OP_NEG : OP_NOT);
return;
case AST_BINOP: {
compile_expr(c, node->a);
compile_expr(c, node->b);
if ((size_t)node->op >= sizeof(BINOP_TABLE) / sizeof(BINOP_TABLE[0])) {
compile_error(c, "unsupported operator");
return;
}
emit_op(c, BINOP_TABLE[node->op]);
return;
}
case AST_CALL: {
int argc = 0;
for (ast_node_t *a = node->list; a; a = a->next) {
compile_expr(c, a);
argc++;
}
if (strcmp(node->name, "print") == 0) {
if (argc != 1) {
compile_error(c, "print takes exactly one argument");
}
emit_op(c, OP_SYS_PRINT);
return;
}
int idx = find_func_index(c, node->name);
if (idx < 0) {
compile_error(c, "call to unknown function");
return;
}
if (argc != c->funcs[idx].param_count) {
compile_error(c, "argument count mismatch");
return;
}
if (argc > VM_MAX_CALL_ARGS) {
compile_error(c, "too many arguments");
return;
}
emit_op(c, OP_CALL);
emit_u16(c, (uint16_t)idx);
return;
}
default:
compile_error(c, "expression expected");
return;
}
}
static void compile_stmt(compiler_t *c, ast_node_t *node) {
switch (node->kind) {
case AST_BLOCK:
for (ast_node_t *s = node->list; s; s = s->next) {
compile_stmt(c, s);
}
return;
case AST_IF: {
compile_expr(c, node->a);
emit_op(c, OP_JMP_IF_FALSE);
uint32_t else_jump = c->code_len;
emit_u16(c, 0);
compile_stmt(c, node->b);
if (node->c) {
emit_op(c, OP_JMP);
uint32_t end_jump = c->code_len;
emit_u16(c, 0);
patch_u16(c, else_jump, (uint16_t)c->code_len);
compile_stmt(c, node->c);
patch_u16(c, end_jump, (uint16_t)c->code_len);
} else {
patch_u16(c, else_jump, (uint16_t)c->code_len);
}
return;
}
case AST_WHILE: {
uint32_t loop_start = c->code_len;
compile_expr(c, node->a);
emit_op(c, OP_JMP_IF_FALSE);
uint32_t exit_jump = c->code_len;
emit_u16(c, 0);
compile_stmt(c, node->b);
emit_op(c, OP_JMP);
emit_u16(c, (uint16_t)loop_start);
patch_u16(c, exit_jump, (uint16_t)c->code_len);
return;
}
case AST_RETURN:
if (node->a) {
compile_expr(c, node->a);
} else {
emit_op(c, OP_PUSH_CONST);
emit_i32(c, 0);
}
emit_op(c, OP_RET);
return;
case AST_EXPR_STMT:
compile_expr(c, node->a);
emit_op(c, OP_POP);
return;
case AST_FUNC_DEF:
compile_error(c, "nested functions are not supported");
return;
default:
compile_error(c, "statement expected");
return;
}
}
static void compile_function(compiler_t *c, ast_node_t *def) {
int idx = find_func_index(c, def->name);
tbc_func_t *fe = &c->funcs[idx];
fe->entry_addr = (uint16_t)c->code_len;
c->in_function = 1;
c->local_count = 0;
int param_count = 0;
for (ast_node_t *pn = def->list; pn; pn = pn->next) {
find_or_add_local(c, pn->name);
param_count++;
}
fe->param_count = (uint8_t)param_count;
compile_stmt(c, def->b);
/* Implicit `return 0;` if the body falls through without one. */
emit_op(c, OP_PUSH_CONST);
emit_i32(c, 0);
emit_op(c, OP_RET);
fe->local_count = (uint8_t)c->local_count;
c->in_function = 0;
}
int codegen_compile(ast_node_t *prog, uint8_t *out_buf, uint32_t out_buf_size,
uint32_t *out_len, char *error_msg, uint32_t error_msg_size) {
static uint8_t code_buf[4096];
compiler_t c;
memset(&c, 0, sizeof(c));
c.code = code_buf;
c.code_cap = sizeof(code_buf);
for (ast_node_t *s = prog->list; s; s = s->next) {
if (s->kind == AST_FUNC_DEF) {
if (c.func_count >= VM_FUNCS_MAX) {
compile_error(&c, "too many functions");
break;
}
copy_name(c.func_names[c.func_count], s->name);
c.func_count++;
}
}
for (ast_node_t *s = prog->list; s && !c.error; s = s->next) {
if (s->kind == AST_FUNC_DEF) {
compile_function(&c, s);
}
}
uint16_t entry_pc = (uint16_t)c.code_len;
c.in_function = 0;
for (ast_node_t *s = prog->list; s && !c.error; s = s->next) {
if (s->kind != AST_FUNC_DEF) {
compile_stmt(&c, s);
}
}
emit_op(&c, OP_HALT);
if (c.error) {
copy_name(error_msg, c.error_msg);
(void)error_msg_size;
return -1;
}
tbc_header_t hdr;
hdr.magic = TBC_MAGIC;
hdr.entry_pc = entry_pc;
hdr.global_count = (uint16_t)c.global_count;
hdr.func_count = (uint16_t)c.func_count;
hdr.code_size = (uint16_t)c.code_len;
uint32_t total = sizeof(hdr) + (uint32_t)c.func_count * sizeof(tbc_func_t) + c.code_len;
if (total > out_buf_size) {
copy_name(error_msg, "compiled program too large for output buffer");
return -1;
}
uint32_t off = 0;
memcpy(out_buf + off, &hdr, sizeof(hdr));
off += sizeof(hdr);
for (int i = 0; i < c.func_count; i++) {
memcpy(out_buf + off, &c.funcs[i], sizeof(tbc_func_t));
off += sizeof(tbc_func_t);
}
memcpy(out_buf + off, c.code, c.code_len);
off += c.code_len;
*out_len = off;
return 0;
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/compiler/codegen.c (file state is current in your context — no need to Read it back)
#pragma once #include <stdint.h> /* Executes a compiled .tbc program (see docs/bytecode-isa.md). Reports * errors to console+serial the same way script_run does. */ void vm_run(const uint8_t *bytecode, uint32_t len);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/vm.h (file state is current in your context — no need to Read it back)
- #define VM_FUNCS_MAX 16 - #define VM_GLOBALS_MAX 32 - #define VM_LOCALS_PER_FRAME_MAX 32 /* param_count + other locals, per function */ - #define VM_MAX_CALL_ARGS 8 + #define VM_FUNCS_MAX 16 + #define VM_GLOBALS_MAX 32 + #define VM_LOCALS_PER_FRAME_MAX 32 /* param_count + other locals, per function */ + #define VM_MAX_CALL_ARGS 8 + + /* vm.c run-time limits */ + #define VM_VALUE_STACK_MAX 256 + #define VM_LOCALS_STACK_MAX 512 /* sum of live frames' local_count */ + #define VM_CALL_STACK_MAX 32
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/bytecode.h has been updated successfully. (file state is current in your context — no need to Read it back)
#include "vm.h"
#include "../console.h"
#include "../serial.h"
#include "../string.h"
#include "bytecode.h"
typedef struct {
uint16_t return_pc;
uint16_t locals_base;
} vm_frame_t;
typedef struct {
int32_t value_stack[VM_VALUE_STACK_MAX];
int sp;
int32_t locals[VM_LOCALS_STACK_MAX];
int locals_top;
int locals_base;
vm_frame_t call_stack[VM_CALL_STACK_MAX];
int call_depth;
int32_t globals[VM_GLOBALS_MAX];
} vm_t;
static void report_error(const char *msg) {
console_write("vm: error: ");
console_write(msg);
console_write("\n");
serial_write("vm: error: ");
serial_write(msg);
serial_write("\n");
}
static void vm_push(vm_t *vm, int32_t v) {
if (vm->sp >= VM_VALUE_STACK_MAX) {
return; /* stack overflow - silently clamp, tiny scope */
}
vm->value_stack[vm->sp++] = v;
}
static int32_t vm_pop(vm_t *vm) {
if (vm->sp <= 0) {
return 0;
}
return vm->value_stack[--vm->sp];
}
void vm_run(const uint8_t *file, uint32_t len) {
if (len < sizeof(tbc_header_t)) {
report_error("truncated .tbc file");
return;
}
tbc_header_t hdr;
memcpy(&hdr, file, sizeof(hdr));
if (hdr.magic != TBC_MAGIC) {
report_error("bad .tbc magic");
return;
}
if (hdr.func_count > VM_FUNCS_MAX) {
report_error("too many functions for this VM");
return;
}
uint32_t off = sizeof(hdr);
tbc_func_t funcs[VM_FUNCS_MAX];
for (int i = 0; i < hdr.func_count; i++) {
if (off + sizeof(tbc_func_t) > len) {
report_error("truncated .tbc function table");
return;
}
memcpy(&funcs[i], file + off, sizeof(tbc_func_t));
off += sizeof(tbc_func_t);
}
if (off + hdr.code_size > len) {
report_error("truncated .tbc code section");
return;
}
const uint8_t *code = file + off;
uint32_t code_size = hdr.code_size;
static vm_t vm;
memset(&vm, 0, sizeof(vm));
uint32_t pc = hdr.entry_pc;
for (;;) {
if (pc >= code_size) {
report_error("program counter ran off the end of code");
return;
}
uint8_t op = code[pc++];
switch ((bc_opcode_t)op) {
case OP_HALT:
return;
case OP_PUSH_CONST: {
int32_t v;
memcpy(&v, code + pc, sizeof(v));
pc += sizeof(v);
vm_push(&vm, v);
break;
}
case OP_POP:
vm_pop(&vm);
break;
case OP_DUP: {
int32_t v = vm.sp > 0 ? vm.value_stack[vm.sp - 1] : 0;
vm_push(&vm, v);
break;
}
case OP_LOAD_LOCAL: {
uint8_t slot = code[pc++];
vm_push(&vm, vm.locals[vm.locals_base + slot]);
break;
}
case OP_STORE_LOCAL: {
uint8_t slot = code[pc++];
vm.locals[vm.locals_base + slot] = vm.sp > 0 ? vm.value_stack[vm.sp - 1] : 0;
vm_pop(&vm);
break;
}
case OP_LOAD_GLOBAL: {
uint8_t slot = code[pc++];
vm_push(&vm, vm.globals[slot]);
break;
}
case OP_STORE_GLOBAL: {
uint8_t slot = code[pc++];
vm.globals[slot] = vm.sp > 0 ? vm.value_stack[vm.sp - 1] : 0;
vm_pop(&vm);
break;
}
case OP_ADD: { int32_t b = vm_pop(&vm), a = vm_pop(&vm); vm_push(&vm, a + b); break; }
case OP_SUB: { int32_t b = vm_pop(&vm), a = vm_pop(&vm); vm_push(&vm, a - b); break; }
case OP_MUL: { int32_t b = vm_pop(&vm), a = vm_pop(&vm); vm_push(&vm, a * b); break; }
case OP_DIV: { int32_t b = vm_pop(&vm), a = vm_pop(&vm); vm_push(&vm, b != 0 ? a / b : 0); break; }
case OP_MOD: { int32_t b = vm_pop(&vm), a = vm_pop(&vm); vm_push(&vm, b != 0 ? a % b : 0); break; }
case OP_EQ: { int32_t b = vm_pop(&vm), a = vm_pop(&vm); vm_push(&vm, a == b); break; }
case OP_NEQ: { int32_t b = vm_pop(&vm), a = vm_pop(&vm); vm_push(&vm, a != b); break; }
case OP_LT: { int32_t b = vm_pop(&vm), a = vm_pop(&vm); vm_push(&vm, a < b); break; }
case OP_LE: { int32_t b = vm_pop(&vm), a = vm_pop(&vm); vm_push(&vm, a <= b); break; }
case OP_GT: { int32_t b = vm_pop(&vm), a = vm_pop(&vm); vm_push(&vm, a > b); break; }
case OP_GE: { int32_t b = vm_pop(&vm), a = vm_pop(&vm); vm_push(&vm, a >= b); break; }
case OP_AND: { int32_t b = vm_pop(&vm), a = vm_pop(&vm); vm_push(&vm, (a != 0) && (b != 0)); break; }
case OP_OR: { int32_t b = vm_pop(&vm), a = vm_pop(&vm); vm_push(&vm, (a != 0) || (b != 0)); break; }
case OP_NEG: { int32_t a = vm_pop(&vm); vm_push(&vm, -a); break; }
case OP_NOT: { int32_t a = vm_pop(&vm); vm_push(&vm, !a); break; }
case OP_JMP: {
uint16_t addr;
memcpy(&addr, code + pc, sizeof(addr));
pc = addr;
break;
}
case OP_JMP_IF_FALSE: {
uint16_t addr;
memcpy(&addr, code + pc, sizeof(addr));
pc += sizeof(addr);
int32_t cond = vm_pop(&vm);
if (!cond) {
pc = addr;
}
break;
}
case OP_CALL: {
uint16_t func_idx;
memcpy(&func_idx, code + pc, sizeof(func_idx));
pc += sizeof(func_idx);
if (func_idx >= hdr.func_count) {
report_error("call to invalid function index");
return;
}
tbc_func_t *fe = &funcs[func_idx];
int32_t args[VM_MAX_CALL_ARGS];
int argc = fe->param_count;
for (int i = argc - 1; i >= 0; i--) {
args[i] = vm_pop(&vm);
}
if (vm.call_depth >= VM_CALL_STACK_MAX) {
report_error("call stack overflow");
return;
}
vm.call_stack[vm.call_depth].return_pc = (uint16_t)pc;
vm.call_stack[vm.call_depth].locals_base = (uint16_t)vm.locals_base;
vm.call_depth++;
if (vm.locals_top + fe->local_count > VM_LOCALS_STACK_MAX) {
report_error("locals stack overflow");
return;
}
int new_base = vm.locals_top;
for (int i = 0; i < fe->local_count; i++) {
vm.locals[new_base + i] = (i < argc) ? args[i] : 0;
}
vm.locals_base = new_base;
vm.locals_top = new_base + fe->local_count;
pc = fe->entry_addr;
break;
}
case OP_RET: {
int32_t retval = vm_pop(&vm);
if (vm.call_depth <= 0) {
report_error("return with no active call frame");
return;
}
vm.call_depth--;
vm.locals_top = vm.locals_base;
pc = vm.call_stack[vm.call_depth].return_pc;
vm.locals_base = vm.call_stack[vm.call_depth].locals_base;
vm_push(&vm, retval);
break;
}
case OP_SYS_PRINT: {
int32_t v = vm_pop(&vm);
char buf[16];
itoa(v, buf, 10);
console_write(buf);
console_write("\n");
serial_write(buf);
serial_write("\n");
vm_push(&vm, 0);
break;
}
default:
report_error("invalid opcode");
return;
}
}
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/vm.c (file state is current in your context — no need to Read it back)
1 #include "shell.h"
2
3 #include <stdint.h>
4
5 #include "console.h"
6 #include "fs/tinyfs.h"
7 #include "keyboard.h"
8 #include "lang/script/eval.h"
9 #include "serial.h"
10 #include "string.h"
11
12 #define LINE_MAX 128
13
14 static inline void outb(uint16_t port, uint8_t val) {
15 __asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
16 }
17
18 static inline uint8_t inb(uint16_t port) {
19 uint8_t ret;
20 __asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
21 return ret;
22 }
23
24 static void echo_char(char c) {
25 console_putc(c);
26 serial_putc(c);
27 }
28
29 static void print_line(const char *s) {
30 console_write(s);
31 console_write("\n");
32 serial_write(s);
33 serial_write("\n");
34 }
35
36 static int read_line(char *buf, int max_len) {
37 int len = 0;
38 for (;;) {
39 char c = keyboard_getchar();
40 if (c == '\n') {
41 echo_char('\n');
42 buf[len] = '\0';
43 return len;
44 } else if (c == '\b') {
45 if (len > 0) {
46 len--;
47 echo_char('\b');
48 echo_char(' ');
49 echo_char('\b');
50 }
51 } else if (c >= 32 && c < 127 && len < max_len - 1) {
52 buf[len++] = c;
53 echo_char(c);
54 }
55 }
56 }
57
58 static void skip_spaces(const char **p) {
59 while (**p == ' ') {
60 (*p)++;
61 }
62 }
63
64 static int next_word(const char **p, char *out, int max_len) {
65 skip_spaces(p);
66 int n = 0;
67 while (**p && **p != ' ' && n < max_len - 1) {
68 out[n++] = *(*p)++;
69 }
70 out[n] = '\0';
71 return n;
72 }
73
74 static void cmd_help(void) {
75 print_line("commands: ls, cat <file>, write <file> <text>, rm <file>, run <file>, help, clear, reboot");
76 }
77
78 static void cmd_ls(void) {
79 tinyfs_dirent_t entries[TINYFS_NUM_INODES];
80 int n = tinyfs_list(entries, TINYFS_NUM_INODES);
81 char numbuf[16];
82 for (int i = 0; i < n; i++) {
83 console_write(entries[i].name);
84 console_write(" ");
85 console_write(itoa((int)entries[i].size, numbuf, 10));
86 console_write("\n");
87 serial_write(entries[i].name);
88 serial_write(" ");
89 serial_write(itoa((int)entries[i].size, numbuf, 10));
90 serial_write("\n");
91 }
92 }
93
94 static void cmd_cat(const char *name) {
95 static char buf[TINYFS_MAX_FILE_SIZE + 1];
96 uint32_t len = 0;
97 if (tinyfs_read(name, buf, sizeof(buf) - 1, &len) != 0) {
98 print_line("cat: no such file");
99 return;
100 }
101 buf[len] = '\0';
102 console_write(buf);
103 serial_write(buf);
104 if (len == 0 || buf[len - 1] != '\n') {
105 console_write("\n");
106 serial_write("\n");
107 }
108 }
109
110 static void cmd_write(const char *name, const char *contents) {
111 tinyfs_create(name); /* ignored if it already exists - write() below overwrites it */
112 uint32_t len = (uint32_t)strlen(contents);
113 if (len > TINYFS_MAX_FILE_SIZE) {
114 print_line("write: content too long");
115 return;
116 }
117 if (tinyfs_write(name, contents, len) != 0) {
118 print_line("write: failed (no space or bad name)");
119 return;
120 }
121 print_line("write: ok");
122 }
123
124 static void cmd_run(const char *name) {
125 static char buf[TINYFS_MAX_FILE_SIZE + 1];
126 uint32_t len = 0;
127 if (tinyfs_read(name, buf, sizeof(buf) - 1, &len) != 0) {
128 print_line("run: no such file");
129 return;
130 }
131 buf[len] = '\0';
132 script_run(buf);
133 }
134
135 static void cmd_rm(const char *name) {
136 if (tinyfs_delete(name) != 0) {
137 print_line("rm: no such file");
138 } else {
139 print_line("rm: ok");
140 }
141 }
142
143 static void cmd_reboot(void) {
144 print_line("rebooting...");
145 uint8_t status;
146 do {
147 status = inb(0x64);
148 } while (status & 0x02);
149 outb(0x64, 0xFE); /* pulse the keyboard controller's reset line */
150 for (;;) {
151 __asm__ volatile ("hlt");
152 }
153 }
154
155 void shell_run(void) {
156 static char line[LINE_MAX];
157
158 print_line("tiny-os shell. type 'help' for commands.");
159
160 for (;;) {
161 console_write("> ");
162 serial_write("> ");
163 read_line(line, sizeof(line));
164
165 const char *p = line;
166 char cmd[32];
167 if (next_word(&p, cmd, sizeof(cmd)) == 0) {
168 continue;
169 }
170
171 if (strcmp(cmd, "help") == 0) {
172 cmd_help();
173 } else if (strcmp(cmd, "ls") == 0) {
174 cmd_ls();
175 } else if (strcmp(cmd, "clear") == 0) {
176 console_clear();
177 } else if (strcmp(cmd, "reboot") == 0) {
178 cmd_reboot();
179 } else if (strcmp(cmd, "cat") == 0) {
180 char name[TINYFS_NAME_MAX];
181 if (next_word(&p, name, sizeof(name)) == 0) {
182 print_line("usage: cat <file>");
183 } else {
184 cmd_cat(name);
185 }
186 } else if (strcmp(cmd, "rm") == 0) {
187 char name[TINYFS_NAME_MAX];
188 if (next_word(&p, name, sizeof(name)) == 0) {
189 print_line("usage: rm <file>");
190 } else {
191 cmd_rm(name);
192 }
193 } else if (strcmp(cmd, "run") == 0) {
194 char name[TINYFS_NAME_MAX];
195 if (next_word(&p, name, sizeof(name)) == 0) {
196 print_line("usage: run <file>");
197 } else {
198 cmd_run(name);
199 }
200 } else if (strcmp(cmd, "write") == 0) {
201 char name[TINYFS_NAME_MAX];
202 if (next_word(&p, name, sizeof(name)) == 0) {
203 print_line("usage: write <file> <text>");
204 } else {
205 skip_spaces(&p);
206 cmd_write(name, p);
207 }
208 } else {
209 print_line("unknown command (type 'help')");
210 }
211 }
212 }
213 - #include "console.h" - #include "fs/tinyfs.h" - #include "keyboard.h" - #include "lang/script/eval.h" - #include "serial.h" - #include "string.h" + #include "console.h" + #include "fs/tinyfs.h" + #include "keyboard.h" + #include "lang/compiler/codegen.h" + #include "lang/compiler/parser.h" + #include "lang/script/eval.h" + #include "lang/vm.h" + #include "serial.h" + #include "string.h"
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/shell/shell.c has been updated successfully. (file state is current in your context — no need to Read it back)
- static void cmd_rm(const char *name) {
+ static void cmd_build(const char *src_name, const char *out_name) {
+ static char src[TINYFS_MAX_FILE_SIZE + 1];
+ uint32_t src_len = 0;
+ if (tinyfs_read(src_name, src, sizeof(src) - 1, &src_len) != 0) {
+ print_line("build: no such file");
+ return;
+ }
+ src[src_len] = '\0';
+
+ tc_parser_t p;
+ tc_parser_init(&p, src);
+ ast_node_t *prog = tc_parse_program(&p);
+ if (p.error) {
+ console_write("build: parse error: ");
+ console_write(p.error_msg);
+ console_write("\n");
+ serial_write("build: parse error: ");
+ serial_write(p.error_msg);
+ serial_write("\n");
+ return;
+ }
+
+ static uint8_t out_buf[TINYFS_MAX_FILE_SIZE];
+ uint32_t out_len = 0;
+ char err[64];
+ if (codegen_compile(prog, out_buf, sizeof(out_buf), &out_len, err, sizeof(err)) != 0) {
+ console_write("build: compile error: ");
+ console_write(err);
+ console_write("\n");
+ serial_write("build: compile error: ");
+ serial_write(err);
+ serial_write("\n");
+ return;
+ }
+
+ tinyfs_create(out_name);
+ if (tinyfs_write(out_name, out_buf, out_len) != 0) {
+ print_line("build: failed to write output file");
+ return;
+ }
+ print_line("build: ok");
+ }
+
+ static void cmd_exec(const char *name) {
+ static uint8_t buf[TINYFS_MAX_FILE_SIZE];
+ uint32_t len = 0;
+ if (tinyfs_read(name, buf, sizeof(buf), &len) != 0) {
+ print_line("exec: no such file");
+ return;
+ }
+ vm_run(buf, len);
+ }
+
+ static void cmd_rm(const char *name) {The file /home/martin/workspace/prototypes/boot-to-claude/kernel/shell/shell.c has been updated successfully. (file state is current in your context — no need to Read it back)
- static void cmd_help(void) {
- print_line("commands: ls, cat <file>, write <file> <text>, rm <file>, run <file>, help, clear, reboot");
- }
+ static void cmd_help(void) {
+ print_line("commands: ls, cat <file>, write <file> <text>, rm <file>, run <file>,");
+ print_line(" build <src> <out>, exec <file>, help, clear, reboot");
+ }The file /home/martin/workspace/prototypes/boot-to-claude/kernel/shell/shell.c has been updated successfully. (file state is current in your context — no need to Read it back)
- } else if (strcmp(cmd, "write") == 0) {
- char name[TINYFS_NAME_MAX];
- if (next_word(&p, name, sizeof(name)) == 0) {
- print_line("usage: write <file> <text>");
- } else {
- skip_spaces(&p);
- cmd_write(name, p);
- }
- } else {
+ } else if (strcmp(cmd, "write") == 0) {
+ char name[TINYFS_NAME_MAX];
+ if (next_word(&p, name, sizeof(name)) == 0) {
+ print_line("usage: write <file> <text>");
+ } else {
+ skip_spaces(&p);
+ cmd_write(name, p);
+ }
+ } else if (strcmp(cmd, "build") == 0) {
+ char src_name[TINYFS_NAME_MAX];
+ char out_name[TINYFS_NAME_MAX];
+ if (next_word(&p, src_name, sizeof(src_name)) == 0 ||
+ next_word(&p, out_name, sizeof(out_name)) == 0) {
+ print_line("usage: build <src> <out>");
+ } else {
+ cmd_build(src_name, out_name);
+ }
+ } else if (strcmp(cmd, "exec") == 0) {
+ char name[TINYFS_NAME_MAX];
+ if (next_word(&p, name, sizeof(name)) == 0) {
+ print_line("usage: exec <file>");
+ } else {
+ cmd_exec(name);
+ }
+ } else {The file /home/martin/workspace/prototypes/boot-to-claude/kernel/shell/shell.c has been updated successfully. (file state is current in your context — no need to Read it back)
- KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \ - kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \ - kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c \ - kernel/keyboard.c kernel/shell/shell.c \ - kernel/lang/lexer.c kernel/lang/script/ast.c \ - kernel/lang/script/parser.c kernel/lang/script/eval.c + KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \ + kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \ + kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c \ + kernel/keyboard.c kernel/shell/shell.c \ + kernel/lang/lexer.c kernel/lang/script/ast.c \ + kernel/lang/script/parser.c kernel/lang/script/eval.c \ + kernel/lang/compiler/parser.c kernel/lang/compiler/codegen.c \ + kernel/lang/vm.c
The file /home/martin/workspace/prototypes/boot-to-claude/Makefile has been updated successfully. (file state is current in your context — no need to Read it back)
- BUILD_SUBDIRS := $(BUILD) $(BUILD)/fs $(BUILD)/shell $(BUILD)/lang $(BUILD)/lang/script + BUILD_SUBDIRS := $(BUILD) $(BUILD)/fs $(BUILD)/shell $(BUILD)/lang $(BUILD)/lang/script $(BUILD)/lang/compiler
The file /home/martin/workspace/prototypes/boot-to-claude/Makefile has been updated successfully. (file state is current in your context — no need to Read it back)
$ make clean >/dev/null && make 2>&1
mkdir -p build nasm -f bin -o build/boot.bin boot/boot.S nasm -f bin -o build/stage2.bin boot/stage2.S mkdir -p build/fs mkdir -p build/shell mkdir -p build/lang mkdir -p build/lang/script mkdir -p build/lang/compiler nasm -f elf32 -o build/entry.o kernel/entry.S nasm -f elf32 -o build/idt_stubs.o kernel/idt_stubs.S gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/kmain.o kernel/kmain.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/console.o kernel/console.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/serial.o kernel/serial.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/gdt.o kernel/gdt.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/idt.o kernel/idt.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/string.o kernel/string.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/memory.o kernel/memory.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/selftest.o kernel/selftest.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/ata.o kernel/ata.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/fs/tinyfs.o kernel/fs/tinyfs.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/fs/tinyfs_disk.o kernel/fs/tinyfs_disk.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/keyboard.o kernel/keyboard.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/shell/shell.o kernel/shell/shell.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/lexer.o kernel/lang/lexer.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/script/ast.o kernel/lang/script/ast.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/script/parser.o kernel/lang/script/parser.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/script/eval.o kernel/lang/script/eval.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/compiler/parser.o kernel/lang/compiler/parser.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/compiler/codegen.o kernel/lang/compiler/codegen.c gcc -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib -Wall -Wextra -O2 -std=c11 -Ikernel -c -o build/lang/vm.o kernel/lang/vm.c ld -m elf_i386 -T kernel/link.ld -nostdlib -o build/kernel.elf build/entry.o build/idt_stubs.o build/kmain.o build/console.o build/serial.o build/gdt.o build/idt.o build/string.o build/memory.o build/selftest.o build/ata.o build/fs/tinyfs.o build/fs/tinyfs_disk.o build/keyboard.o build/shell/shell.o build/lang/lexer.o build/lang/script/ast.o build/lang/script/parser.o build/lang/script/eval.o build/lang/compiler/parser.o build/lang/compiler/codegen.o build/lang/vm.o ld: warning: build/kernel.elf has a LOAD segment with RWX permissions objcopy -O binary build/kernel.elf build/kernel.bin gcc -Wall -Wextra -O2 -c -o build/host_tinyfs.o kernel/fs/tinyfs.c gcc -Wall -Wextra -O2 -c -o build/host_mkfs_tinyfs.o tools/mkfs_tinyfs.c gcc -o build/mkfs_tinyfs build/host_tinyfs.o build/host_mkfs_tinyfs.o dd if=/dev/zero of=build/disk.img bs=512 count=4096 status=none dd if=build/boot.bin of=build/disk.img conv=notrunc status=none dd if=build/stage2.bin of=build/disk.img bs=512 seek=1 conv=notrunc status=none dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh mkfs_tinyfs: formatted build/disk.img (3446 data blocks available)
$ stat -c%s build/kernel.bin
47416
x = 2 + 3 * 4;
print(x);
func add(a, b) {
return a + b;
}
print(add(3, 4));
func fib(n) {
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
print(fib(10));
i = 0;
sum = 0;
while (i < 5) {
sum = sum + i;
i = i + 1;
}
print(sum);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/hello.tc (file state is current in your context — no need to Read it back)
14 7 55 10
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/expected/hello.tc.txt (file state is current in your context — no need to Read it back)
- MKFS_SEED_FILES := tests/hello.tsh - MKFS_SEED_SPECS := tests/hello.tsh:hello.tsh + MKFS_SEED_FILES := tests/hello.tsh tests/hello.tc + MKFS_SEED_SPECS := tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc
The file /home/martin/workspace/prototypes/boot-to-claude/Makefile has been updated successfully. (file state is current in your context — no need to Read it back)
- type_line "run hello.tsh" - check_fixture_output tests/expected/hello.tsh.txt - - echo "run_tests: PASS" + type_line "run hello.tsh" + check_fixture_output tests/expected/hello.tsh.txt + + type_line "build hello.tc hello.tbc" + wait_for "build: ok" + + type_line "exec hello.tbc" + check_fixture_output tests/expected/hello.tc.txt + + echo "run_tests: PASS"
The file /home/martin/workspace/prototypes/boot-to-claude/tools/run_tests.sh has been updated successfully. (file state is current in your context — no need to Read it back)
$ make 2>&1 | tail -3 && ./tools/run_tests.sh 2>&1 | tail -40
dd if=build/kernel.bin of=build/disk.img bs=512 seek=5 conv=notrunc status=none build/mkfs_tinyfs build/disk.img tests/hello.tsh:hello.tsh tests/hello.tc:hello.tc mkfs_tinyfs: formatted build/disk.img (3446 data blocks available) run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 1900847 (bash)
$ pgrep -af qemu-system-i386 || echo "no stray qemu"; git add -A && git status --short
1900990 /bin/bash -c source /home/martin/.claude/shell-snapshots/snapshot-bash-1784307599835-hlkzgk.sh 2>/dev/null || true && shopt -u extglob 2>/dev/null || true && eval 'pgrep -af qemu-system-i386 || echo "no stray qemu"; git add -A && git status --short' < /dev/null && pwd -P >| /tmp/claude-e2ac-cwd M Makefile A docs/bytecode-isa.md A kernel/lang/bytecode.h A kernel/lang/compiler/codegen.c A kernel/lang/compiler/codegen.h A kernel/lang/compiler/parser.c A kernel/lang/compiler/parser.h A kernel/lang/vm.c A kernel/lang/vm.h M kernel/shell/shell.c A tests/expected/hello.tc.txt A tests/hello.tc M tools/run_tests.sh