{
"taskId": "5",
"status": "completed"
}Updated task #5 status
{
"taskId": "6",
"status": "in_progress"
}Updated task #6 status
$ mkdir -p /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/script
(Bash completed with no output)
#pragma once
/* Shared tokenizer used by both the scripting language (kernel/lang/script)
* and the tiny compiler (kernel/lang/compiler). Keywords are not baked in
* here - callers recognize keywords by comparing TOK_IDENT text, since the
* two languages don't share a keyword set. */
#include <stdint.h>
typedef enum {
TOK_EOF,
TOK_IDENT,
TOK_NUMBER,
TOK_STRING,
TOK_LPAREN,
TOK_RPAREN,
TOK_LBRACE,
TOK_RBRACE,
TOK_COMMA,
TOK_SEMI,
TOK_PLUS,
TOK_MINUS,
TOK_STAR,
TOK_SLASH,
TOK_PERCENT,
TOK_ASSIGN,
TOK_EQ,
TOK_NEQ,
TOK_LT,
TOK_LE,
TOK_GT,
TOK_GE,
TOK_NOT,
TOK_AND,
TOK_OR,
TOK_ERROR,
} token_type_t;
#define TOKEN_TEXT_MAX 32
typedef struct {
token_type_t type;
char text[TOKEN_TEXT_MAX]; /* TOK_IDENT */
int32_t number; /* TOK_NUMBER */
char *string; /* TOK_STRING - kmalloc'd, owned by the AST node it ends up in */
} token_t;
typedef struct {
const char *src;
int pos;
} lexer_t;
void lexer_init(lexer_t *lx, const char *src);
token_t lexer_next(lexer_t *lx);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/lexer.h (file state is current in your context — no need to Read it back)
#include "lexer.h"
#include "../memory.h"
#include "../string.h"
static int is_alpha(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
}
static int is_digit(char c) {
return c >= '0' && c <= '9';
}
static int is_alnum(char c) {
return is_alpha(c) || is_digit(c);
}
static void skip_ws_and_comments(lexer_t *lx) {
for (;;) {
char c = lx->src[lx->pos];
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
lx->pos++;
} else if (c == '#') {
while (lx->src[lx->pos] && lx->src[lx->pos] != '\n') {
lx->pos++;
}
} else {
break;
}
}
}
void lexer_init(lexer_t *lx, const char *src) {
lx->src = src;
lx->pos = 0;
}
token_t lexer_next(lexer_t *lx) {
token_t tok;
tok.string = NULL;
tok.number = 0;
tok.text[0] = '\0';
skip_ws_and_comments(lx);
char c = lx->src[lx->pos];
if (c == '\0') {
tok.type = TOK_EOF;
return tok;
}
if (is_alpha(c)) {
int start = lx->pos;
while (is_alnum(lx->src[lx->pos])) {
lx->pos++;
}
int len = lx->pos - start;
if (len >= TOKEN_TEXT_MAX) {
len = TOKEN_TEXT_MAX - 1;
}
memcpy(tok.text, lx->src + start, (size_t)len);
tok.text[len] = '\0';
tok.type = TOK_IDENT;
return tok;
}
if (is_digit(c)) {
int32_t val = 0;
while (is_digit(lx->src[lx->pos])) {
val = val * 10 + (lx->src[lx->pos] - '0');
lx->pos++;
}
tok.number = val;
tok.type = TOK_NUMBER;
return tok;
}
if (c == '"') {
lx->pos++;
int start = lx->pos;
while (lx->src[lx->pos] && lx->src[lx->pos] != '"') {
lx->pos++;
}
int len = lx->pos - start;
char *s = kmalloc((size_t)len + 1);
memcpy(s, lx->src + start, (size_t)len);
s[len] = '\0';
if (lx->src[lx->pos] == '"') {
lx->pos++;
}
tok.string = s;
tok.type = TOK_STRING;
return tok;
}
lx->pos++;
switch (c) {
case '(': tok.type = TOK_LPAREN; break;
case ')': tok.type = TOK_RPAREN; break;
case '{': tok.type = TOK_LBRACE; break;
case '}': tok.type = TOK_RBRACE; break;
case ',': tok.type = TOK_COMMA; break;
case ';': tok.type = TOK_SEMI; break;
case '+': tok.type = TOK_PLUS; break;
case '-': tok.type = TOK_MINUS; break;
case '*': tok.type = TOK_STAR; break;
case '/': tok.type = TOK_SLASH; break;
case '%': tok.type = TOK_PERCENT; break;
case '=':
if (lx->src[lx->pos] == '=') { lx->pos++; tok.type = TOK_EQ; }
else { tok.type = TOK_ASSIGN; }
break;
case '!':
if (lx->src[lx->pos] == '=') { lx->pos++; tok.type = TOK_NEQ; }
else { tok.type = TOK_NOT; }
break;
case '<':
if (lx->src[lx->pos] == '=') { lx->pos++; tok.type = TOK_LE; }
else { tok.type = TOK_LT; }
break;
case '>':
if (lx->src[lx->pos] == '=') { lx->pos++; tok.type = TOK_GE; }
else { tok.type = TOK_GT; }
break;
case '&':
if (lx->src[lx->pos] == '&') { lx->pos++; tok.type = TOK_AND; }
else { tok.type = TOK_ERROR; }
break;
case '|':
if (lx->src[lx->pos] == '|') { lx->pos++; tok.type = TOK_OR; }
else { tok.type = TOK_ERROR; }
break;
default:
tok.type = TOK_ERROR;
break;
}
return tok;
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/lexer.c (file state is current in your context — no need to Read it back)
#pragma once
#include "../lexer.h"
typedef enum {
AST_NUM,
AST_STR,
AST_VAR,
AST_ASSIGN,
AST_BINOP,
AST_UNOP,
AST_CALL,
AST_IF,
AST_WHILE,
AST_FUNC_DEF,
AST_RETURN,
AST_BLOCK,
AST_EXPR_STMT,
} ast_kind_t;
typedef struct ast_node ast_node_t;
/* One generic node shape for the whole tree, in the tradition of tiny
* interpreters: `a`/`b`/`c` are kind-specific children, `list` is a
* singly-linked chain (block statements, call args, function params),
* and `next` links siblings within such a chain. See eval.c/parser.c for
* which fields each AST_* kind actually uses. */
struct ast_node {
ast_kind_t kind;
token_type_t op; /* AST_BINOP / AST_UNOP */
int32_t num; /* AST_NUM */
char *str; /* AST_STR - shares the token's kmalloc'd buffer */
char name[TOKEN_TEXT_MAX]; /* AST_VAR / AST_ASSIGN / AST_CALL / AST_FUNC_DEF */
ast_node_t *a, *b, *c;
ast_node_t *list;
ast_node_t *next;
};
ast_node_t *ast_new(ast_kind_t kind);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/script/ast.h (file state is current in your context — no need to Read it back)
#include "ast.h"
#include "../../memory.h"
#include "../../string.h"
/* Nodes are allocated from the kernel heap and never freed - a script's
* whole AST lives for the duration of one `run`. Fine for tiny scripts;
* the heap resets on reboot. */
ast_node_t *ast_new(ast_kind_t kind) {
ast_node_t *n = kmalloc(sizeof(ast_node_t));
memset(n, 0, sizeof(*n));
n->kind = kind;
return n;
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/script/ast.c (file state is current in your context — no need to Read it back)
#pragma once
#include "../lexer.h"
#include "ast.h"
typedef struct {
lexer_t lexer;
token_t cur;
int error;
char error_msg[64];
} parser_t;
void parser_init(parser_t *p, const char *src);
/* Parses the whole source as a top-level AST_BLOCK of statements. Check
* p->error afterwards before evaluating the result. */
ast_node_t *parse_program(parser_t *p);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/script/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(parser_t *p) {
p->cur = lexer_next(&p->lexer);
}
void parser_init(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(parser_t *p, const char *msg) {
if (!p->error) {
p->error = 1;
copy_name(p->error_msg, msg); /* error_msg is 64 bytes, plenty for our short messages */
}
}
static int check(parser_t *p, token_type_t t) {
return p->cur.type == t;
}
static int match(parser_t *p, token_type_t t) {
if (check(p, t)) {
advance(p);
return 1;
}
return 0;
}
static void expect(parser_t *p, token_type_t t) {
if (!match(p, t)) {
parse_error(p, "unexpected token");
}
}
static int is_kw(parser_t *p, const char *kw) {
return check(p, TOK_IDENT) && strcmp(p->cur.text, kw) == 0;
}
static ast_node_t *parse_expression(parser_t *p);
static ast_node_t *parse_statement(parser_t *p);
static ast_node_t *parse_block(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(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)) {
ast_node_t *n = ast_new(AST_STR);
n->str = p->cur.string;
advance(p);
return n;
}
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); /* avoid getting stuck */
return ast_new(AST_NUM);
}
static ast_node_t *parse_unary(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(parser_t *p, ast_node_t *(*next)(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(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(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(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(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(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(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(parser_t *p) {
if (check(p, TOK_IDENT)) {
parser_t save = *p; /* plain-old-data struct, safe to snapshot/restore */
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); /* right-associative */
ast_node_t *n = ast_new(AST_ASSIGN);
copy_name(n->name, name);
n->a = value;
return n;
}
*p = save; /* not an assignment - rewind and parse as a normal expression */
}
return parse_or(p);
}
static ast_node_t *parse_expression(parser_t *p) {
return parse_assignment(p);
}
static ast_node_t *parse_statement(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 *parse_program(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/script/parser.c (file state is current in your context — no need to Read it back)
#pragma once /* Parses and executes a script's source text, printing output to the * console + serial as it runs. Parse/runtime errors are reported the * same way rather than propagated - there is no caller that needs a * status code yet. */ void script_run(const char *src);
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/script/eval.h (file state is current in your context — no need to Read it back)
#include "eval.h"
#include "../../console.h"
#include "../../fs/tinyfs.h"
#include "../../memory.h"
#include "../../serial.h"
#include "../../string.h"
#include "ast.h"
#include "parser.h"
typedef enum { VAL_INT, VAL_STR } value_type_t;
typedef struct {
value_type_t type;
union {
int32_t i;
char *s;
} as;
} value_t;
#define MAX_VARS 32
typedef struct {
char names[MAX_VARS][TOKEN_TEXT_MAX];
value_t values[MAX_VARS];
int count;
} env_t;
#define MAX_FUNCS 16
typedef struct {
char name[TOKEN_TEXT_MAX];
ast_node_t *def;
} func_entry_t;
typedef struct {
func_entry_t funcs[MAX_FUNCS];
int func_count;
} interp_t;
typedef enum { EXEC_NORMAL, EXEC_RETURN } exec_signal_t;
typedef struct {
exec_signal_t signal;
value_t return_value;
} exec_result_t;
static const value_t VAL_ZERO = { VAL_INT, { .i = 0 } };
static int env_find(env_t *env, const char *name) {
for (int i = 0; i < env->count; i++) {
if (strcmp(env->names[i], name) == 0) {
return i;
}
}
return -1;
}
static value_t env_get(env_t *env, const char *name) {
int i = env_find(env, name);
return i < 0 ? VAL_ZERO : env->values[i];
}
static void env_set(env_t *env, const char *name, value_t v) {
int i = env_find(env, name);
if (i >= 0) {
env->values[i] = v;
return;
}
if (env->count < MAX_VARS) {
int n = 0;
while (name[n] && n < TOKEN_TEXT_MAX - 1) {
env->names[env->count][n] = name[n];
n++;
}
env->names[env->count][n] = '\0';
env->values[env->count] = v;
env->count++;
}
/* else: variable table full - silently dropped, tiny scope */
}
static int to_int(value_t v) {
if (v.type == VAL_INT) {
return v.as.i;
}
return (v.as.s && v.as.s[0]) ? 1 : 0;
}
static char *str_concat(const char *a, const char *b) {
size_t la = strlen(a);
size_t lb = strlen(b);
char *r = kmalloc(la + lb + 1);
memcpy(r, a, la);
memcpy(r + la, b, lb);
r[la + lb] = '\0';
return r;
}
static void print_value(value_t v) {
if (v.type == VAL_STR) {
console_write(v.as.s);
serial_write(v.as.s);
} else {
char buf[16];
itoa(v.as.i, buf, 10);
console_write(buf);
serial_write(buf);
}
}
static value_t eval_binop(token_type_t op, value_t l, value_t r) {
if (op == TOK_PLUS && (l.type == VAL_STR || r.type == VAL_STR)) {
char lb[16], rb[16];
const char *ls = (l.type == VAL_STR) ? l.as.s : itoa(l.as.i, lb, 10);
const char *rs = (r.type == VAL_STR) ? r.as.s : itoa(r.as.i, rb, 10);
return (value_t){ VAL_STR, { .s = str_concat(ls, rs) } };
}
if (op == TOK_EQ || op == TOK_NEQ) {
int eq;
if (l.type == VAL_STR && r.type == VAL_STR) {
eq = strcmp(l.as.s, r.as.s) == 0;
} else if (l.type == VAL_INT && r.type == VAL_INT) {
eq = l.as.i == r.as.i;
} else {
eq = 0;
}
return (value_t){ VAL_INT, { .i = (op == TOK_EQ) ? eq : !eq } };
}
int li = to_int(l);
int ri = to_int(r);
switch (op) {
case TOK_MINUS: return (value_t){ VAL_INT, { .i = li - ri } };
case TOK_STAR: return (value_t){ VAL_INT, { .i = li * ri } };
case TOK_SLASH: return (value_t){ VAL_INT, { .i = ri != 0 ? li / ri : 0 } };
case TOK_PERCENT: return (value_t){ VAL_INT, { .i = ri != 0 ? li % ri : 0 } };
case TOK_LT: return (value_t){ VAL_INT, { .i = li < ri } };
case TOK_LE: return (value_t){ VAL_INT, { .i = li <= ri } };
case TOK_GT: return (value_t){ VAL_INT, { .i = li > ri } };
case TOK_GE: return (value_t){ VAL_INT, { .i = li >= ri } };
default: return VAL_ZERO;
}
}
static ast_node_t *find_func(interp_t *interp, const char *name) {
for (int i = 0; i < interp->func_count; i++) {
if (strcmp(interp->funcs[i].name, name) == 0) {
return interp->funcs[i].def;
}
}
return NULL;
}
static void register_func(interp_t *interp, ast_node_t *def) {
if (interp->func_count >= MAX_FUNCS) {
return;
}
func_entry_t *e = &interp->funcs[interp->func_count++];
int n = 0;
while (def->name[n] && n < TOKEN_TEXT_MAX - 1) {
e->name[n] = def->name[n];
n++;
}
e->name[n] = '\0';
e->def = def;
}
static value_t eval_expr(interp_t *interp, env_t *env, ast_node_t *node);
static exec_result_t exec_stmt(interp_t *interp, env_t *env, ast_node_t *node);
static exec_result_t exec_block(interp_t *interp, env_t *env, ast_node_t *block) {
exec_result_t r = { EXEC_NORMAL, VAL_ZERO };
for (ast_node_t *s = block->list; s; s = s->next) {
r = exec_stmt(interp, env, s);
if (r.signal == EXEC_RETURN) {
return r;
}
}
return r;
}
static value_t eval_call(interp_t *interp, env_t *env, ast_node_t *node) {
value_t args[8];
int argc = 0;
for (ast_node_t *a = node->list; a && argc < 8; a = a->next) {
args[argc++] = eval_expr(interp, env, a);
}
if (strcmp(node->name, "print") == 0) {
for (int i = 0; i < argc; i++) {
if (i > 0) {
console_write(" ");
serial_write(" ");
}
print_value(args[i]);
}
console_write("\n");
serial_write("\n");
return VAL_ZERO;
}
if (strcmp(node->name, "fread") == 0 && argc >= 1 && args[0].type == VAL_STR) {
static char buf[TINYFS_MAX_FILE_SIZE + 1];
uint32_t len = 0;
if (tinyfs_read(args[0].as.s, buf, sizeof(buf) - 1, &len) == 0) {
buf[len] = '\0';
char *out = kmalloc(len + 1);
memcpy(out, buf, len + 1);
return (value_t){ VAL_STR, { .s = out } };
}
return (value_t){ VAL_STR, { .s = "" } };
}
if (strcmp(node->name, "fwrite") == 0 && argc >= 2 && args[0].type == VAL_STR) {
const char *content = (args[1].type == VAL_STR) ? args[1].as.s : "";
tinyfs_create(args[0].as.s); /* ignored if it already exists */
int rc = tinyfs_write(args[0].as.s, content, (uint32_t)strlen(content));
return (value_t){ VAL_INT, { .i = rc == 0 } };
}
if (strcmp(node->name, "flist") == 0) {
tinyfs_dirent_t entries[TINYFS_NUM_INODES];
int n = tinyfs_list(entries, TINYFS_NUM_INODES);
for (int i = 0; i < n; i++) {
console_write(entries[i].name);
console_write("\n");
serial_write(entries[i].name);
serial_write("\n");
}
return (value_t){ VAL_INT, { .i = n } };
}
ast_node_t *def = find_func(interp, node->name);
if (!def) {
return VAL_ZERO; /* unknown function - tiny scope, no error propagation */
}
env_t local;
local.count = 0;
int i = 0;
for (ast_node_t *pn = def->list; pn; pn = pn->next, i++) {
env_set(&local, pn->name, i < argc ? args[i] : VAL_ZERO);
}
exec_result_t r = exec_block(interp, &local, def->b);
return r.signal == EXEC_RETURN ? r.return_value : VAL_ZERO;
}
static value_t eval_expr(interp_t *interp, env_t *env, ast_node_t *node) {
switch (node->kind) {
case AST_NUM:
return (value_t){ VAL_INT, { .i = node->num } };
case AST_STR:
return (value_t){ VAL_STR, { .s = node->str } };
case AST_VAR:
return env_get(env, node->name);
case AST_ASSIGN: {
value_t v = eval_expr(interp, env, node->a);
env_set(env, node->name, v);
return v;
}
case AST_UNOP: {
value_t v = eval_expr(interp, env, node->a);
if (node->op == TOK_MINUS) {
return (value_t){ VAL_INT, { .i = -to_int(v) } };
}
return (value_t){ VAL_INT, { .i = !to_int(v) } };
}
case AST_BINOP: {
if (node->op == TOK_AND) {
value_t l = eval_expr(interp, env, node->a);
if (!to_int(l)) {
return VAL_ZERO;
}
value_t r = eval_expr(interp, env, node->b);
return (value_t){ VAL_INT, { .i = to_int(r) != 0 } };
}
if (node->op == TOK_OR) {
value_t l = eval_expr(interp, env, node->a);
if (to_int(l)) {
return (value_t){ VAL_INT, { .i = 1 } };
}
value_t r = eval_expr(interp, env, node->b);
return (value_t){ VAL_INT, { .i = to_int(r) != 0 } };
}
value_t l = eval_expr(interp, env, node->a);
value_t r = eval_expr(interp, env, node->b);
return eval_binop(node->op, l, r);
}
case AST_CALL:
return eval_call(interp, env, node);
default:
return VAL_ZERO;
}
}
static exec_result_t exec_stmt(interp_t *interp, env_t *env, ast_node_t *node) {
exec_result_t normal = { EXEC_NORMAL, VAL_ZERO };
switch (node->kind) {
case AST_BLOCK:
return exec_block(interp, env, node);
case AST_IF: {
value_t c = eval_expr(interp, env, node->a);
if (to_int(c)) {
return exec_stmt(interp, env, node->b);
}
if (node->c) {
return exec_stmt(interp, env, node->c);
}
return normal;
}
case AST_WHILE:
while (to_int(eval_expr(interp, env, node->a))) {
exec_result_t r = exec_stmt(interp, env, node->b);
if (r.signal == EXEC_RETURN) {
return r;
}
}
return normal;
case AST_FUNC_DEF:
/* top-level defs are pre-registered by script_run; a nested
* func statement (inside a block) registers here instead */
register_func(interp, node);
return normal;
case AST_RETURN: {
exec_result_t r = { EXEC_RETURN, VAL_ZERO };
if (node->a) {
r.return_value = eval_expr(interp, env, node->a);
}
return r;
}
case AST_EXPR_STMT:
eval_expr(interp, env, node->a);
return normal;
default:
return normal;
}
}
void script_run(const char *src) {
parser_t p;
parser_init(&p, src);
ast_node_t *prog = parse_program(&p);
if (p.error) {
console_write("script: parse error: ");
console_write(p.error_msg);
console_write("\n");
serial_write("script: parse error: ");
serial_write(p.error_msg);
serial_write("\n");
return;
}
interp_t interp;
interp.func_count = 0;
env_t global;
global.count = 0;
for (ast_node_t *s = prog->list; s; s = s->next) {
if (s->kind == AST_FUNC_DEF) {
register_func(&interp, s);
}
}
for (ast_node_t *s = prog->list; s; s = s->next) {
if (s->kind != AST_FUNC_DEF) {
exec_result_t r = exec_stmt(&interp, &global, s);
if (r.signal == EXEC_RETURN) {
break;
}
}
}
}
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/script/eval.c (file state is current in your context — no need to Read it back)
- #include "console.h" - #include "fs/tinyfs.h" - #include "keyboard.h" - #include "serial.h" - #include "string.h" + #include "console.h" + #include "fs/tinyfs.h" + #include "keyboard.h" + #include "lang/script/eval.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_run(const char *name) {
+ static char buf[TINYFS_MAX_FILE_SIZE + 1];
+ uint32_t len = 0;
+ if (tinyfs_read(name, buf, sizeof(buf) - 1, &len) != 0) {
+ print_line("run: no such file");
+ return;
+ }
+ buf[len] = '\0';
+ script_run(buf);
+ }
+
+ 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)
- } else if (strcmp(cmd, "rm") == 0) {
- char name[TINYFS_NAME_MAX];
- if (next_word(&p, name, sizeof(name)) == 0) {
- print_line("usage: rm <file>");
- } else {
- cmd_rm(name);
- }
+ } else if (strcmp(cmd, "rm") == 0) {
+ char name[TINYFS_NAME_MAX];
+ if (next_word(&p, name, sizeof(name)) == 0) {
+ print_line("usage: rm <file>");
+ } else {
+ cmd_rm(name);
+ }
+ } else if (strcmp(cmd, "run") == 0) {
+ char name[TINYFS_NAME_MAX];
+ if (next_word(&p, name, sizeof(name)) == 0) {
+ print_line("usage: run <file>");
+ } else {
+ cmd_run(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)
- print_line("commands: ls, cat <file>, write <file> <text>, rm <file>, help, clear, reboot");
+ print_line("commands: ls, cat <file>, write <file> <text>, rm <file>, run <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)
1 /* Host-side build tool: formats the tinyfs region of a disk image and
2 * seeds it with initial files. Not part of the OS - infrastructure, like
3 * the assembler/linker/QEMU, run at build time on the host. Reuses
4 * kernel/fs/tinyfs.c verbatim (compiled natively here, freestanding in
5 * the kernel) so there is exactly one implementation of the fs format. */
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10
11 #include "../kernel/fs/tinyfs.h"
12
13 static FILE *disk;
14
15 static void disk_block_read(uint32_t block, void *buf) {
16 long offset = (long)(TINYFS_FS_START_LBA + block) * TINYFS_BLOCK_SIZE;
17 if (fseek(disk, offset, SEEK_SET) != 0 ||
18 fread(buf, 1, TINYFS_BLOCK_SIZE, disk) != TINYFS_BLOCK_SIZE) {
19 fprintf(stderr, "mkfs_tinyfs: read failed at block %u\n", block);
20 exit(1);
21 }
22 }
23
24 static void disk_block_write(uint32_t block, const void *buf) {
25 long offset = (long)(TINYFS_FS_START_LBA + block) * TINYFS_BLOCK_SIZE;
26 if (fseek(disk, offset, SEEK_SET) != 0 ||
27 fwrite(buf, 1, TINYFS_BLOCK_SIZE, disk) != TINYFS_BLOCK_SIZE) {
28 fprintf(stderr, "mkfs_tinyfs: write failed at block %u\n", block);
29 exit(1);
30 }
31 }
32
33 static void seed_file(const char *name, const char *contents) {
34 tinyfs_create(name);
35 tinyfs_write(name, contents, (uint32_t)strlen(contents));
36 }
37
38 int main(int argc, char **argv) {
39 if (argc != 2) {
40 fprintf(stderr, "usage: %s <disk-image>\n", argv[0]);
41 return 1;
42 }
43
44 disk = fopen(argv[1], "r+b");
45 if (!disk) {
46 perror("fopen");
47 return 1;
48 }
49
50 tinyfs_init(disk_block_read, disk_block_write);
51 tinyfs_format();
52
53 seed_file("hello.txt", "Hello, tinyfs!\n");
54
55 fclose(disk);
56 printf("mkfs_tinyfs: formatted %s (%u data blocks available)\n",
57 argv[1], (unsigned)TINYFS_DATA_BLOCKS);
58 return 0;
59 }
60 - static void seed_file(const char *name, const char *contents) {
- tinyfs_create(name);
- tinyfs_write(name, contents, (uint32_t)strlen(contents));
- }
-
- int main(int argc, char **argv) {
- if (argc != 2) {
- fprintf(stderr, "usage: %s <disk-image>\n", argv[0]);
- return 1;
- }
-
- disk = fopen(argv[1], "r+b");
- if (!disk) {
- perror("fopen");
- return 1;
- }
-
- tinyfs_init(disk_block_read, disk_block_write);
- tinyfs_format();
-
- seed_file("hello.txt", "Hello, tinyfs!\n");
-
- fclose(disk);
- printf("mkfs_tinyfs: formatted %s (%u data blocks available)\n",
- argv[1], (unsigned)TINYFS_DATA_BLOCKS);
- return 0;
- }
+ static void seed_file(const char *name, const char *contents) {
+ tinyfs_create(name);
+ tinyfs_write(name, contents, (uint32_t)strlen(contents));
+ }
+
+ /* Seeds a tinyfs file straight from a host file, for test fixtures
+ * (tests/*.tsh, tests/*.tc) so their source lives in one place instead of
+ * being duplicated as a C string literal here. */
+ static void seed_file_from_host(const char *host_path, const char *fs_name) {
+ FILE *f = fopen(host_path, "rb");
+ if (!f) {
+ fprintf(stderr, "mkfs_tinyfs: cannot open seed file %s\n", host_path);
+ exit(1);
+ }
+ char buf[TINYFS_MAX_FILE_SIZE];
+ size_t n = fread(buf, 1, sizeof(buf), f);
+ fclose(f);
+
+ tinyfs_create(fs_name);
+ tinyfs_write(fs_name, buf, (uint32_t)n);
+ }
+
+ int main(int argc, char **argv) {
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <disk-image> [host-path:fs-name ...]\n", argv[0]);
+ return 1;
+ }
+
+ disk = fopen(argv[1], "r+b");
+ if (!disk) {
+ perror("fopen");
+ return 1;
+ }
+
+ tinyfs_init(disk_block_read, disk_block_write);
+ tinyfs_format();
+
+ seed_file("hello.txt", "Hello, tinyfs!\n");
+
+ for (int i = 2; i < argc; i++) {
+ char spec[256];
+ size_t speclen = strlen(argv[i]);
+ if (speclen >= sizeof(spec)) {
+ fprintf(stderr, "mkfs_tinyfs: seed spec too long: %s\n", argv[i]);
+ return 1;
+ }
+ memcpy(spec, argv[i], speclen + 1);
+
+ char *sep = strchr(spec, ':');
+ if (!sep) {
+ fprintf(stderr, "mkfs_tinyfs: bad seed spec %s (want host-path:fs-name)\n", argv[i]);
+ return 1;
+ }
+ *sep = '\0';
+ seed_file_from_host(spec, sep + 1);
+ }
+
+ fclose(disk);
+ printf("mkfs_tinyfs: formatted %s (%u data blocks available)\n",
+ argv[1], (unsigned)TINYFS_DATA_BLOCKS);
+ return 0;
+ }The file /home/martin/workspace/prototypes/boot-to-claude/tools/mkfs_tinyfs.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ mkdir -p /home/martin/workspace/prototypes/boot-to-claude/tests/expected
(Bash completed with no output)
print("hello from tinyfs script");
x = 2 + 3 * 4;
print("x =", x);
func add(a, b) {
return a + b;
}
print("add(3,4) =", add(3, 4));
i = 0;
while (i < 3) {
print("loop", i);
i = i + 1;
}
fwrite("script_out.txt", "written by script");
print(fread("script_out.txt"));
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/hello.tsh (file state is current in your context — no need to Read it back)
hello from tinyfs script x = 14 add(3,4) = 7 loop 0 loop 1 loop 2 written by script
File created successfully at: /home/martin/workspace/prototypes/boot-to-claude/tests/expected/hello.tsh.txt (file state is current in your context — no need to Read it back)
- MKFS_BIN := $(BUILD)/mkfs_tinyfs - MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_mkfs_tinyfs.o + MKFS_BIN := $(BUILD)/mkfs_tinyfs + MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_mkfs_tinyfs.o + MKFS_SEEDS := tests/hello.tsh:hello.tsh
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)
- $(DISK_IMG): $(BOOT_BIN) $(STAGE2_BIN) $(KERNEL_BIN) $(MKFS_BIN) - dd if=/dev/zero of=$@ bs=512 count=$(DISK_SECTORS) status=none - dd if=$(BOOT_BIN) of=$@ conv=notrunc status=none - dd if=$(STAGE2_BIN) of=$@ bs=512 seek=1 conv=notrunc status=none - dd if=$(KERNEL_BIN) of=$@ bs=512 seek=$(KERNEL_START_SECTOR) conv=notrunc status=none - $(MKFS_BIN) $@ + $(DISK_IMG): $(BOOT_BIN) $(STAGE2_BIN) $(KERNEL_BIN) $(MKFS_BIN) $(MKFS_SEEDS:%=%) + dd if=/dev/zero of=$@ bs=512 count=$(DISK_SECTORS) status=none + dd if=$(BOOT_BIN) of=$@ conv=notrunc status=none + dd if=$(STAGE2_BIN) of=$@ bs=512 seek=1 conv=notrunc status=none + dd if=$(KERNEL_BIN) of=$@ bs=512 seek=$(KERNEL_START_SECTOR) conv=notrunc status=none + $(MKFS_BIN) $@ $(MKFS_SEEDS)
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)
- MKFS_BIN := $(BUILD)/mkfs_tinyfs - MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_mkfs_tinyfs.o - MKFS_SEEDS := tests/hello.tsh:hello.tsh + MKFS_BIN := $(BUILD)/mkfs_tinyfs + MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_mkfs_tinyfs.o + # host-path:fs-name pairs seeded into every disk image; the host paths + # alone are listed separately so they can be used as make prerequisites + MKFS_SEED_FILES := tests/hello.tsh + MKFS_SEED_SPECS := tests/hello.tsh:hello.tsh
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)
- $(DISK_IMG): $(BOOT_BIN) $(STAGE2_BIN) $(KERNEL_BIN) $(MKFS_BIN) $(MKFS_SEEDS:%=%) - dd if=/dev/zero of=$@ bs=512 count=$(DISK_SECTORS) status=none - dd if=$(BOOT_BIN) of=$@ conv=notrunc status=none - dd if=$(STAGE2_BIN) of=$@ bs=512 seek=1 conv=notrunc status=none - dd if=$(KERNEL_BIN) of=$@ bs=512 seek=$(KERNEL_START_SECTOR) conv=notrunc status=none - $(MKFS_BIN) $@ $(MKFS_SEEDS) + $(DISK_IMG): $(BOOT_BIN) $(STAGE2_BIN) $(KERNEL_BIN) $(MKFS_BIN) $(MKFS_SEED_FILES) + dd if=/dev/zero of=$@ bs=512 count=$(DISK_SECTORS) status=none + dd if=$(BOOT_BIN) of=$@ conv=notrunc status=none + dd if=$(STAGE2_BIN) of=$@ bs=512 seek=1 conv=notrunc status=none + dd if=$(KERNEL_BIN) of=$@ bs=512 seek=$(KERNEL_START_SECTOR) conv=notrunc status=none + $(MKFS_BIN) $@ $(MKFS_SEED_SPECS)
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)
- 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_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
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) $(BUILD)/fs $(BUILD)/shell: - mkdir -p $@ - - $(BOOT_BIN): boot/boot.S | $(BUILD) - $(AS) -f bin -o $@ $< - - $(STAGE2_BIN): boot/stage2.S | $(BUILD) - $(AS) -f bin -o $@ $< - - $(BUILD)/%.o: kernel/%.S | $(BUILD) $(BUILD)/fs $(BUILD)/shell - $(AS) -f elf32 -o $@ $< - - $(BUILD)/%.o: kernel/%.c | $(BUILD) $(BUILD)/fs $(BUILD)/shell - $(CC) $(CFLAGS) -c -o $@ $< + BUILD_SUBDIRS := $(BUILD) $(BUILD)/fs $(BUILD)/shell $(BUILD)/lang $(BUILD)/lang/script + + $(BUILD_SUBDIRS): + mkdir -p $@ + + $(BOOT_BIN): boot/boot.S | $(BUILD) + $(AS) -f bin -o $@ $< + + $(STAGE2_BIN): boot/stage2.S | $(BUILD) + $(AS) -f bin -o $@ $< + + $(BUILD)/%.o: kernel/%.S | $(BUILD_SUBDIRS) + $(AS) -f elf32 -o $@ $< + + $(BUILD)/%.o: kernel/%.c | $(BUILD_SUBDIRS) + $(CC) $(CFLAGS) -c -o $@ $<
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() {
- python3 tools/qemu_type.py "$MON" "$1"$'\n'
- }
+ type_line() {
+ python3 tools/qemu_type.py "$MON" "$1"$'\n'
+ }
+
+ # Checks that every non-blank line in tests/expected/<fixture> appears
+ # (in order) somewhere in the serial log - used for .tsh/.tc fixtures run
+ # through the shell's `run`/`build`+`exec` commands.
+ check_fixture_output() {
+ local expected_file="$1"
+ while IFS= read -r want; do
+ [ -z "$want" ] && continue
+ wait_for "$want" 30
+ done < "$expected_file"
+ }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)
- type_line "help" - wait_for "commands:" - - echo "run_tests: PASS" + type_line "help" + wait_for "commands:" + + type_line "run hello.tsh" + check_fixture_output tests/expected/hello.tsh.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 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 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 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 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 tools/mkfs_tinyfs.c:39:10: warning: ‘/*’ within comment [-Wcomment] 39 | * (tests/*.tsh, tests/*.tc) so their source lives in one place instead of tools/mkfs_tinyfs.c:39:23: warning: ‘/*’ within comment [-Wcomment] 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 (3958 data blocks available)
- /* Seeds a tinyfs file straight from a host file, for test fixtures - * (tests/*.tsh, tests/*.tc) so their source lives in one place instead of - * being duplicated as a C string literal here. */ + /* Seeds a tinyfs file straight from a host file, for test fixtures under + * tests/ so their source lives in one place instead of being duplicated + * as a C string literal here. */
The file /home/martin/workspace/prototypes/boot-to-claude/tools/mkfs_tinyfs.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ stat -c%s build/kernel.bin && ./tools/run_tests.sh
Exit code 1 28428 run_tests: FAIL - timed out waiting for 'x = 14' --- serial log --- tiny-os: kernel entered selftest: running SELFTEST PASS tiny-os shell. type 'help' for commands. > ls hello.txt 15 hello.tsh 281 > cat hello.txt Hello, tinyfs! > write greet.txt hi there write: ok > cat greet.txt hi there > rm greet.txt rm: ok > cat greet.txt cat: no such file > help commands: ls, cat <file>, write <file> <text>, rm <file>, run <file>, help, clear, reboot > run hello.tsh hello from tinyfs script x = 0 add(3,4) = 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop ... [20011 characters truncated] ... op 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop 0 loop
$ pgrep -af qemu-system-i386 || echo "no stray qemu processes"
1888311 /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 processes"' < /dev/null && pwd -P >| /tmp/claude-f695-cwd
- int li = to_int(l);
- int ri = to_int(r);
- switch (op) {
- case TOK_MINUS: return (value_t){ VAL_INT, { .i = li - ri } };
+ int li = to_int(l);
+ int ri = to_int(r);
+ switch (op) {
+ case TOK_PLUS: return (value_t){ VAL_INT, { .i = li + ri } };
+ case TOK_MINUS: return (value_t){ VAL_INT, { .i = li - ri } };The file /home/martin/workspace/prototypes/boot-to-claude/kernel/lang/script/eval.c has been updated successfully. (file state is current in your context — no need to Read it back)
$ make 2>&1 | tail -5 && ./tools/run_tests.sh 2>&1 | tail -40
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 (3958 data blocks available) run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 1888423 (bash)
$ stat -c%s build/kernel.bin
28452
1 ; Stage 2 bootloader. Loaded by stage1 at 0x0000:0x7e00, still 16-bit real mode. 2 ; Job: enable A20, load the kernel from disk into memory below 1MB, build a 3 ; flat GDT, switch to 32-bit protected mode, and jump into the kernel entry. 4 5 BITS 16 6 ORG 0x7e00 7 8 KERNEL_LOAD_ADDR equ 0x10000 ; 64KB - plenty of conventional memory headroom 9 KERNEL_START_LBA equ 5 ; right after stage1 (LBA 0) + stage2 (LBA 1-4) 10 KERNEL_SECTORS equ 64 ; 32KB kernel image budget for now 11 12 stage2_start: 13 mov si, msg_stage2 14 call print_str16 15
- KERNEL_LOAD_ADDR equ 0x10000 ; 64KB - plenty of conventional memory headroom - KERNEL_START_LBA equ 5 ; right after stage1 (LBA 0) + stage2 (LBA 1-4) - KERNEL_SECTORS equ 64 ; 32KB kernel image budget for now + KERNEL_LOAD_ADDR equ 0x10000 ; 64KB - plenty of conventional memory headroom + KERNEL_START_LBA equ 5 ; right after stage1 (LBA 0) + stage2 (LBA 1-4) + KERNEL_SECTORS equ 512 ; 256KB kernel image budget (must match Makefile/tinyfs.h)
The file /home/martin/workspace/prototypes/boot-to-claude/boot/stage2.S has been updated successfully. (file state is current in your context — no need to Read it back)
- #define TINYFS_DISK_SECTORS 4096 - #define TINYFS_FS_START_LBA 128 /* leaves 64KB for boot sector + kernel */ + #define TINYFS_DISK_SECTORS 4096 + #define TINYFS_FS_START_LBA 640 /* leaves room for boot sector + a 256KB kernel budget */
The file /home/martin/workspace/prototypes/boot-to-claude/kernel/fs/tinyfs.h has been updated successfully. (file state is current in your context — no need to Read it back)
1 AS := nasm 2 CC := gcc 3 HOSTCC := gcc 4 LD := ld 5 OBJCOPY := objcopy 6 QEMU := qemu-system-i386 7 BUILD := build 8 9 CFLAGS := -m32 -ffreestanding -fno-pic -fno-stack-protector -fno-builtin -nostdlib \ 10 -Wall -Wextra -O2 -std=c11 -Ikernel 11 LDFLAGS := -m elf_i386 -T kernel/link.ld -nostdlib 12 13 # Must match TINYFS_DISK_SECTORS / TINYFS_FS_START_LBA in kernel/fs/tinyfs.h 14 DISK_SECTORS := 4096 15 KERNEL_START_SECTOR := 5 16 17 BOOT_BIN := $(BUILD)/boot.bin 18 STAGE2_BIN := $(BUILD)/stage2.bin 19 DISK_IMG := $(BUILD)/disk.img 20 21 KERNEL_C_SRCS := kernel/kmain.c kernel/console.c kernel/serial.c kernel/gdt.c \ 22 kernel/idt.c kernel/string.c kernel/memory.c kernel/selftest.c \ 23 kernel/ata.c kernel/fs/tinyfs.c kernel/fs/tinyfs_disk.c \ 24 kernel/keyboard.c kernel/shell/shell.c \ 25 kernel/lang/lexer.c kernel/lang/script/ast.c \ 26 kernel/lang/script/parser.c kernel/lang/script/eval.c 27 KERNEL_ASM_SRCS := kernel/entry.S kernel/idt_stubs.S 28 KERNEL_OBJS := $(patsubst kernel/%.S,$(BUILD)/%.o,$(KERNEL_ASM_SRCS)) \ 29 $(patsubst kernel/%.c,$(BUILD)/%.o,$(KERNEL_C_SRCS)) 30 KERNEL_ELF := $(BUILD)/kernel.elf 31 KERNEL_BIN := $(BUILD)/kernel.bin 32 33 MKFS_BIN := $(BUILD)/mkfs_tinyfs 34 MKFS_OBJS := $(BUILD)/host_tinyfs.o $(BUILD)/host_mkfs_tinyfs.o 35 # host-path:fs-name pairs seeded into every disk image; the host paths 36 # alone are listed separately so they can be used as make prerequisites 37 MKFS_SEED_FILES := tests/hello.tsh 38 MKFS_SEED_SPECS := tests/hello.tsh:hello.tsh 39 40 .PHONY: all run clean 41 42 all: $(DISK_IMG) 43 44 BUILD_SUBDIRS := $(BUILD) $(BUILD)/fs $(BUILD)/shell $(BUILD)/lang $(BUILD)/lang/script 45 46 $(BUILD_SUBDIRS): 47 mkdir -p $@ 48 49 $(BOOT_BIN): boot/boot.S | $(BUILD) 50 $(AS) -f bin -o $@ $< 51 52 $(STAGE2_BIN): boot/stage2.S | $(BUILD) 53 $(AS) -f bin -o $@ $< 54 55 $(BUILD)/%.o: kernel/%.S | $(BUILD_SUBDIRS) 56 $(AS) -f elf32 -o $@ $< 57 58 $(BUILD)/%.o: kernel/%.c | $(BUILD_SUBDIRS) 59 $(CC) $(CFLAGS) -c -o $@ $< 60 61 $(KERNEL_ELF): $(KERNEL_OBJS) kernel/link.ld 62 $(LD) $(LDFLAGS) -o $@ $(KERNEL_OBJS) 63 64 $(KERNEL_BIN): $(KERNEL_ELF) 65 $(OBJCOPY) -O binary $< $@ 66 67 # --- host-side mkfs tool (build-time infrastructure, not kernel code) --- 68 69 $(BUILD)/host_tinyfs.o: kernel/fs/tinyfs.c | $(BUILD) 70 $(HOSTCC) -Wall -Wextra -O2 -c -o $@ $< 71 72 $(BUILD)/host_mkfs_tinyfs.o: tools/mkfs_tinyfs.c | $(BUILD) 73 $(HOSTCC) -Wall -Wextra -O2 -c -o $@ $< 74 75 $(MKFS_BIN): $(MKFS_OBJS) 76 $(HOSTCC) -o $@ $(MKFS_OBJS) 77 78 $(DISK_IMG): $(BOOT_BIN) $(STAGE2_BIN) $(KERNEL_BIN) $(MKFS_BIN) $(MKFS_SEED_FILES) 79 dd if=/dev/zero of=$@ bs=512 count=$(DISK_SECTORS) status=none 80 dd if=$(BOOT_BIN) of=$@ conv=notrunc status=none 81 dd if=$(STAGE2_BIN) of=$@ bs=512 seek=1 conv=notrunc status=none 82 dd if=$(KERNEL_BIN) of=$@ bs=512 seek=$(KERNEL_START_SECTOR) conv=notrunc status=none 83 $(MKFS_BIN) $@ $(MKFS_SEED_SPECS) 84 85 run: $(DISK_IMG) 86 $(QEMU) -drive file=$(DISK_IMG),format=raw -display none -serial stdio -no-reboot 87 88 clean: 89 rm -rf $(BUILD) 90
- # Must match TINYFS_DISK_SECTORS / TINYFS_FS_START_LBA in kernel/fs/tinyfs.h - DISK_SECTORS := 4096 - KERNEL_START_SECTOR := 5 + # Must match TINYFS_DISK_SECTORS / TINYFS_FS_START_LBA in kernel/fs/tinyfs.h + # and KERNEL_START_LBA / KERNEL_SECTORS in boot/stage2.S + DISK_SECTORS := 4096 + KERNEL_START_SECTOR := 5 + KERNEL_MAX_SECTORS := 512
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)
- $(KERNEL_BIN): $(KERNEL_ELF) - $(OBJCOPY) -O binary $< $@ + $(KERNEL_BIN): $(KERNEL_ELF) + $(OBJCOPY) -O binary $< $@ + @size=$$(stat -c%s $@); budget=$$(( $(KERNEL_MAX_SECTORS) * 512 )); \ + if [ "$$size" -gt "$$budget" ]; then \ + echo "error: $@ is $$size bytes, exceeds the $$budget-byte ($(KERNEL_MAX_SECTORS)-sector) boot budget" >&2; \ + echo " bump KERNEL_SECTORS in boot/stage2.S, KERNEL_MAX_SECTORS here, and TINYFS_FS_START_LBA in kernel/fs/tinyfs.h together" >&2; \ + exit 1; \ + fi
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 | tail -20
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 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 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)
$ ./tools/run_tests.sh 2>&1 | tail -10
run_tests: FAIL - timed out waiting for 'SELFTEST PASS' --- serial log --- qemu-system-i386: terminating on signal 15 from pid 1889181 (bash)
$ timeout 5 qemu-system-i386 -drive file=build/disk.img,format=raw -display none -serial stdio -no-reboot -d int -D /tmp/qemu.log 2>&1; echo "---exceptions---"; grep -m5 "v=" /tmp/qemu.log 2>/dev/null
qemu-system-i386: terminating on signal 15 from pid 1889407 (timeout) ---exceptions---
1 ; Stage 2 bootloader. Loaded by stage1 at 0x0000:0x7e00, still 16-bit real mode. 2 ; Job: enable A20, load the kernel from disk into memory below 1MB, build a 3 ; flat GDT, switch to 32-bit protected mode, and jump into the kernel entry. 4 5 BITS 16 6 ORG 0x7e00 7 8 KERNEL_LOAD_ADDR equ 0x10000 ; 64KB - plenty of conventional memory headroom 9 KERNEL_START_LBA equ 5 ; right after stage1 (LBA 0) + stage2 (LBA 1-4) 10 KERNEL_SECTORS equ 512 ; 256KB kernel image budget (must match Makefile/tinyfs.h) 11 12 stage2_start: 13 mov si, msg_stage2 14 call print_str16 15 16 call enable_a20 17 call load_kernel 18 19 cli 20 lgdt [gdt_descriptor] 21 22 mov eax, cr0 23 or eax, 1 24 mov cr0, eax 25 26 jmp CODE_SEG:protected_mode_entry 27 28 ; ---------------- 16-bit helpers ---------------- 29 30 print_str16: 31 pusha 32 .loop: 33 lodsb 34 or al, al 35 jz .done 36 mov ah, 0x0e 37 mov bh, 0 38 int 0x10 39 jmp .loop 40 .done: 41 popa 42 ret 43 44 enable_a20: 45 in al, 0x92 46 or al, 2 47 out 0x92, al 48 ret 49 50 load_kernel: 51 mov si, dap 52 mov ah, 0x42 53 mov dl, 0x80 54 int 0x13 55 jc disk_error 56 ret 57 58 disk_error: 59 mov si, msg_disk_err 60 call print_str16 61 .hang: 62 hlt 63 jmp .hang 64 65 align 4 66 dap: 67 db 0x10 68 db 0 69 dw KERNEL_SECTORS 70 dw KERNEL_LOAD_ADDR & 0xFFFF ; offset (0x0000) 71 dw (KERNEL_LOAD_ADDR >> 4) & 0xFFFF ; segment (0x1000) 72 dq KERNEL_START_LBA 73 74 msg_stage2: db "stage2 ", 0 75 msg_disk_err: db "kernel disk err", 0 76 77 ; ---------------- GDT: flat 4GB code/data ---------------- 78 79 align 8 80 gdt_start: 81 dq 0 ; null descriptor 82 gdt_code: 83 dw 0xFFFF, 0 84 db 0, 10011010b, 11001111b, 0 85 gdt_data: 86 dw 0xFFFF, 0 87 db 0, 10010010b, 11001111b, 0 88 gdt_end: 89 90 gdt_descriptor: 91 dw gdt_end - gdt_start - 1 92 dd gdt_start 93 94 CODE_SEG equ gdt_code - gdt_start 95 DATA_SEG equ gdt_data - gdt_start 96 97 ; ---------------- 32-bit protected mode entry ---------------- 98 99 BITS 32 100 protected_mode_entry: 101 mov ax, DATA_SEG 102 mov ds, ax 103 mov es, ax 104 mov fs, ax 105 mov gs, ax 106 mov ss, ax 107 mov esp, 0x90000 108 109 jmp KERNEL_LOAD_ADDR 110 111 times 2048-($-$$) db 0 ; pad to STAGE2_SECTORS (4) * 512 bytes 112
- load_kernel: - mov si, dap - mov ah, 0x42 - mov dl, 0x80 - int 0x13 - jc disk_error - ret - - disk_error: - mov si, msg_disk_err - call print_str16 - .hang: - hlt - jmp .hang - - align 4 - dap: - db 0x10 - db 0 - dw KERNEL_SECTORS - dw KERNEL_LOAD_ADDR & 0xFFFF ; offset (0x0000) - dw (KERNEL_LOAD_ADDR >> 4) & 0xFFFF ; segment (0x1000) - dq KERNEL_START_LBA - - msg_stage2: db "stage2 ", 0 - msg_disk_err: db "kernel disk err", 0 + ; Reads the kernel in CHUNK_SECTORS-sized pieces rather than one single + ; INT13h AH=0x42 call - BIOSes (including SeaBIOS) commonly cap how many + ; sectors a single extended read will reliably transfer well below 512, + ; and silently misbehave past that rather than returning an error. + CHUNK_SECTORS equ 64 + + load_kernel: + mov word [kernel_remaining], KERNEL_SECTORS + mov dword [kernel_lba], KERNEL_START_LBA + mov word [kernel_dest_seg], (KERNEL_LOAD_ADDR >> 4) + + .loop: + mov ax, [kernel_remaining] + cmp ax, 0 + je .done + cmp ax, CHUNK_SECTORS + jbe .chunk_size_ok + mov ax, CHUNK_SECTORS + .chunk_size_ok: + ; ax = sectors to transfer this iteration + + mov [dap_sectors], ax + mov si, [kernel_dest_seg] + mov [dap_segment], si + mov word [dap_offset], 0 + mov ebx, [kernel_lba] + mov [dap_lba], ebx + mov dword [dap_lba + 4], 0 + + push ax + mov si, dap + mov ah, 0x42 + mov dl, 0x80 + int 0x13 + jc disk_error + pop ax + + movzx eax, ax ; sectors transferred, zero-extended + add dword [kernel_lba], eax + sub word [kernel_remaining], ax + mov cx, ax + shl cx, 5 ; sectors * (512/16) = paragraphs advanced + add word [kernel_dest_seg], cx + + jmp .loop + .done: + ret + + disk_error: + mov si, msg_disk_err + call print_str16 + .hang: + hlt + jmp .hang + + kernel_remaining: dw 0 + kernel_lba: dd 0 + kernel_dest_seg: dw 0 + + align 4 + dap: + dap_size: db 0x10 + db 0 + dap_sectors: dw 0 + dap_offset: dw 0 + dap_segment: dw 0 + dap_lba: dq 0 + + msg_stage2: db "stage2 ", 0 + msg_disk_err: db "kernel disk err", 0
The file /home/martin/workspace/prototypes/boot-to-claude/boot/stage2.S has been updated successfully. (file state is current in your context — no need to Read it back)
$ make 2>&1 | tail -10 && ./tools/run_tests.sh 2>&1 | tail -20
nasm -f bin -o build/stage2.bin boot/stage2.S 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) run_tests: PASS qemu-system-i386: terminating on signal 15 from pid 1892564 (bash)
$ git add -A && git status --short
M Makefile M boot/stage2.S M kernel/fs/tinyfs.h A kernel/lang/lexer.c A kernel/lang/lexer.h A kernel/lang/script/ast.c A kernel/lang/script/ast.h A kernel/lang/script/eval.c A kernel/lang/script/eval.h A kernel/lang/script/parser.c A kernel/lang/script/parser.h M kernel/shell/shell.c A tests/expected/hello.tsh.txt A tests/hello.tsh M tools/mkfs_tinyfs.c M tools/run_tests.sh