ox

The Ox programming language, compiler and tools (WIP)
Log | Files | Refs | README | LICENSE

commit 0669e8dc447ea5541917817d17b02464d505279c
parent 94c9ed18098ee36420987ef6fddf04b0d15ca9aa
Author: citbl <citbl@citbl.org>
Date:   Tue, 25 Nov 2025 19:38:21 +1000

wip variadic and comp time types

Diffstat:
M.zed/debug.json | 2+-
Moxdesign.ox | 16++++++++--------
Msrc/gen/gen.c | 85++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/lexer.c | 8++++++++
Msrc/parser/decl.c | 8+++++++-
Msrc/parser/expr.c | 11+++++++++--
Msrc/parser/parser.c | 35+++++++++++++++++++++++------------
Msrc/sem.c | 16++++++++--------
Msrc/types.h | 5+++++
Mtests/ex-for-simple2.ox | 6++++--
Atests/ex-variadic.ox | 8++++++++
Mtests/ex6.ox | 2+-
12 files changed, 138 insertions(+), 64 deletions(-)

diff --git a/.zed/debug.json b/.zed/debug.json @@ -11,7 +11,7 @@ "cwd": "$ZED_WORKTREE_ROOT" }, "program": "$ZED_WORKTREE_ROOT/oxc", - "args": ["$ZED_WORKTREE_ROOT/tests/ex-call-many-args.ox"], + "args": ["$ZED_WORKTREE_ROOT/tests/ex-for-simple2.ox"], "request": "launch", "adapter": "CodeLLDB" } diff --git a/oxdesign.ox b/oxdesign.ox @@ -1,6 +1,6 @@ -i32 ~x = 42; // make a integer 32bit that is a variable (can be updated) `~` initialised with 2. +~i32 x = 42; // make a integer 32bit that is a variable (can be updated) `~` initialised with 2. i32 x = 42; // like a "let" statement or final variable, runtime constant. -i32 #x = 42; // compile time constant +#i32 x = 42; // compile time constant // give me the raw pointer of x and assign it to y i32* y = &x; @@ -192,20 +192,20 @@ str website_data = await try fetch_data(url: "fleacebook.com") or Error e => pri fx flip(i32 a, b) (i32, i32)! { - + } -// APPENDIX +// APPENDIX // keywords -// return, break, continue, if, else, for, while, loop, goto, defer, heap, free, -// type, ext, union, arr, vec, set, typedef, fx, fn, maybe, mut, ref, ptr, voidptr, +// return, break, continue, if, else, for, while, loop, goto, defer, heap, free, +// type, ext, union, arr, vec, set, typedef, fx, fn, maybe, mut, ref, ptr, voidptr, // unsafe, inline // types -// all of the +// all of the // stdlib.local auto imported // equivalent of `use always { print, print_, warn, warn_, fatal }` // print, print_, warn, warn_, fatal @@ -232,7 +232,7 @@ extern c { // don't use `const char*` etc. and warn against it at compile time // use cstr or ccstr instead to avoid the baggage of weird // pointer precedence and const position - // + // } fn main() { diff --git a/src/gen/gen.c b/src/gen/gen.c @@ -236,7 +236,8 @@ handle_ident_call(Gen* gen, Node* node) Symbol* sym = find_symbol(gen, gen->scope, node->data.ident.name); if (sym == NULL) { - softpanic("handle_ident_call: undefined variable: %s\n", + panic_at(node, + "handle_ident_call: undefined variable: %s\n", span_str(gen->src, node->data.ident.name, (char[IDENTSZ]) { 0 })); } return gcc_jit_lvalue_as_rvalue(sym->d.lvalue); @@ -402,26 +403,36 @@ handle_unary_expr(Gen* gen, Node* node) Node* opnd = node->data.unary_expr.operand; Symbol* sym = find_symbol(gen, gen->scope, opnd->data.ident.name); if (sym == NULL) { - softpanic("handle_unary_expr: undefined variable: %s\n", + panic_at(node, + "handle_unary_expr: undefined variable: %s\n", span_str(gen->src, opnd->data.ident.name, (char[IDENTSZ]) { 0 })); } gcc_jit_lvalue* lv = sym->d.lvalue; - gcc_jit_rvalue* orig = gcc_jit_lvalue_as_rvalue(sym->d.lvalue); + if (lv == NULL) { + printf("handle_unary_expr: no lvalue\n"); + return NULL; + } + gcc_jit_rvalue* orig = gcc_jit_lvalue_as_rvalue(lv); gcc_jit_type* ty = gcc_jit_rvalue_get_type(orig); gcc_jit_rvalue* one = gcc_jit_context_one(ctx, ty); gcc_jit_rvalue* inc = gcc_jit_context_new_binary_op(ctx, NULL, GCC_JIT_BINARY_OP_PLUS, ty, orig, one); switch (node->data.unary_expr.op) { case OPER_POSTINC: { // i++ + + if (!sym->is_variadic) { + panic_at(node, + "OPER_POSTINC: cannot change non variadic variable %s\n", + span_str(gen->src, opnd->data.ident_type.name, (char[IDENTSZ]) { 0 })); + } else if (sym->is_comp_time) { + panic_at(node, + "OPER_POSTINC: cannot change comp time constant %s\n", + span_str(gen->src, opnd->data.ident_type.name, (char[IDENTSZ]) { 0 })); + } gcc_jit_block_add_assignment(bb, NULL, lv, inc); return orig; // return value before incr as ++ is postfix - if (lv == NULL) { - printf("handle_unary_expr: no lvalue\n"); - return NULL; - } - printf("wef\n"); break; } default: @@ -437,7 +448,7 @@ emit_literal_string(Gen* gen, Node* node) { size_t len = node->data.string.value.end - node->data.string.value.start; char* str = calloc(len + 1, sizeof(char)); - if (str == NULL) panic("emit_literal_string: could not alloc"); + if (str == NULL) panic_at(node, "emit_literal_string: could not alloc"); memcpy(str, gen->src + node->data.string.value.start, len); str[len] = '\0'; return gcc_jit_context_new_string_literal(gen->ctx, str); @@ -538,8 +549,9 @@ lower_builtin_print(Gen* gen, Node* node) gcc_jit_rvalue** args = (gcc_jit_rvalue**)calloc(MAXARGS, sizeof(gcc_jit_rvalue*)); if (argc > MAXARGS) { - softpanic("we do not currently support more than 16 args to a " - "print call"); + panic_at(node, + "we do not currently support more than 16 args to a " + "print call"); } for (size_t i = 0; i < argc; i++) { @@ -559,8 +571,8 @@ lower_builtin_print(Gen* gen, Node* node) if (ty == type_int) { arg = gcc_jit_context_new_cast(gen->ctx, loc_from_node(gen, node), arg, type_cstr); } else if (ty == type_f32) { // TODO print() for other types - // variadics already promote float→double; - // double is + // variadics already promote float→double; + // double is } else if (ty == type_cstr) { // leave as const char* } else { @@ -757,7 +769,7 @@ ox_type_to_c_type(Gen* gen, Node* node) } else if (strcmp(type_name, "void") == 0) { return type_void; } else { - softpanic("unhandled type in gen %s", type_name); + panic_at(node, "unhandled type in gen %s", type_name); } return NULL; } @@ -975,7 +987,9 @@ build_var_decl_statement(Gen* gen, Node* node) sym->ctype = declared_type; sym->d.lvalue = var_decl; sym->english_type = type_var; - add_symbol(gen, sym); + sym->is_variadic = node->data.var_decl.type->data.ident_type.is_variadic; + sym->is_comp_time = node->data.var_decl.type->data.ident_type.is_comp_time; + add_symbol(gen, sym); // add var_decl symbol return false; } @@ -1021,19 +1035,29 @@ build_statement(Gen* gen, Node* node) Symbol* sym = gen->scope->symbols[i]; if (span_ident_same(sym->name, node->data.var_assign.lhs->data.ident.name, gen->src)) { // we found the symbol we are assigning to + // TODO check for variadic and comp_time + if (!sym->is_variadic) { + const char* name = span_str(gen->src, sym->name, (char[IDENTSZ]) { 0 }); + panic_at(node, "symbol %s is not variadic, cannot mutate.", name); + } + if (sym->is_comp_time) { + const char* name = span_str(gen->src, sym->name, (char[IDENTSZ]) { 0 }); + panic_at(node, "symbol %s is comp time, cannot mutate.", name); + } + gcc_jit_lvalue* lvalue = sym->d.lvalue; // check the type of lvalue matches the rvalue - // gcc_jit_type* ltype = sym->ctype; - // gcc_jit_type* rtype = gcc_jit_rvalue_get_type(rvalue); - // if (rtype != ltype) { - // panic_at(node, - // "right hand side of assigment " - // "doesn't match the " - // "type of the left hand side: %s, %s", - // get_english_type(ltype), - // get_english_type(rtype)); - // } + gcc_jit_type* ltype = sym->ctype; + gcc_jit_type* rtype = gcc_jit_rvalue_get_type(rvalue); + if (rtype != ltype) { + panic_at(node, + "right hand side of assigment " + "doesn't match the " + "type of the left hand side: %s, %s", + get_english_type(ltype), + get_english_type(rtype)); + } if (lvalue) gcc_jit_block_add_assignment(gen->curr_block, loc, lvalue, rvalue); } } @@ -1041,7 +1065,10 @@ build_statement(Gen* gen, Node* node) } case NODE_BINARY_EXPR: { Symbol* sym = find_symbol(gen, gen->scope, node->data.binary_expr.lhs->data.ident.name); - if (!sym) softpanic("undefined variable: %s\n", span_str(gen->src, node->data.ident.name, (char[IDENTSZ]) { 0 })); + if (!sym) + panic_at(node, + "NODE_BINARY_EXPR: Undefined symbol: %s\n", + span_str(gen->src, node->data.ident.name, (char[IDENTSZ]) { 0 })); gcc_jit_lvalue* lv = sym->d.lvalue; gcc_jit_rvalue* rhs = handle_expr(gen, node->data.binary_expr.rhs); @@ -1074,7 +1101,7 @@ build_statement(Gen* gen, Node* node) case NODE_BLOCK: build_block(gen, node); default: - printf("build_statement unhandled, %s\n", node_type_str(node->type)); + printf("--- WARNING: build_statement unhandled, %s\n", node_type_str(node->type)); break; } return false; @@ -1185,7 +1212,7 @@ build_func_decl(Gen* gen, Node* node) sym->ctype = type_voidp; // whatever you use for function type metadata sym->d.funcvalue = func; sym->english_type = type_func; - add_symbol(gen, sym); // to the 'parent scope', before we push_scope + add_symbol(gen, sym); // add function symbol -- to the 'parent scope', before we push_scope // ENTER FUNCTION SCOPE push_scope(gen); @@ -1204,7 +1231,7 @@ build_func_decl(Gen* gen, Node* node) ps->ctype = declared_type; ps->d.param = p; // <-- this is associated with 'func' ps->english_type = type_var; // or type_param if you distinguish - add_symbol(gen, ps); + add_symbol(gen, ps); // add param symbol } print_symbols_here(gen); diff --git a/src/lexer.c b/src/lexer.c @@ -203,6 +203,14 @@ next_token(Lexer* lex) nudge(lex); type = TOKEN_PERCENT; break; + case '#': + nudge(lex); + type = TOKEN_COMP_TIME; + break; + case '~': + nudge(lex); + type = TOKEN_VARIADIC; + break; case '/': nudge(lex); type = TOKEN_SLASH; diff --git a/src/parser/decl.c b/src/parser/decl.c @@ -9,6 +9,10 @@ Node* parse_type(Parser* par) { + bool comp_time = (peek(par).type == TOKEN_COMP_TIME); + bool variadic = (peek(par).type == TOKEN_VARIADIC); + if (comp_time || variadic) { consume(par); } + Token tok = expect(par, TOKEN_IDENT); // @later we will resolve types later, incl. custom vs. system, allow for now @@ -22,7 +26,9 @@ parse_type(Parser* par) if (node == NULL) panic("parse_type: alloc failed"); node->type = NODE_TYPE; node->scope = NULL; - node->data.ident.name = (Span) { .start = tok.start, .end = tok.end }; + node->data.ident_type.name = (Span) { .start = tok.start, .end = tok.end }; + node->data.ident_type.is_comp_time = comp_time; + node->data.ident_type.is_variadic = variadic; return node; } diff --git a/src/parser/expr.c b/src/parser/expr.c @@ -76,12 +76,19 @@ Node* parse_ident(Parser* par) { Token tok = consume(par); + + bool comp_time = (tok.type == TOKEN_COMP_TIME); + bool variadic = (tok.type == TOKEN_VARIADIC); + if (comp_time || variadic) { tok = consume(par); } + assert(tok.type == TOKEN_IDENT); Node* ident_node = (Node*)calloc(1, sizeof(Node)); if (ident_node == NULL) panic("parse_ident: alloc failed"); ident_node->type = NODE_IDENT; ident_node->scope = NULL; - ident_node->data.ident.name = (Span) { .start = tok.start, .end = tok.end }; + ident_node->data.ident_type.name = (Span) { .start = tok.start, .end = tok.end }; + ident_node->data.ident_type.is_comp_time = comp_time; + ident_node->data.ident_type.is_variadic = variadic; // const char* name = span_str( // par->src, (Span) { .start = tok.start, .end = tok.end }, (char[IDENTSZ]) { 0 }); @@ -182,7 +189,7 @@ parse_primary(Parser* par) Token tok = peek(par); if (tok.type == TOKEN_STRING_LITERAL) { return make_string_node(par); } if (tok.type == TOKEN_INT_LITERAL || tok.type == TOKEN_FLOAT_LITERAL) { return make_number_node(par); } - if (tok.type == TOKEN_IDENT) { return parse_ident(par); } + if (tok.type == TOKEN_IDENT || tok.type == TOKEN_VARIADIC || tok.type == TOKEN_COMP_TIME) { return parse_ident(par); } if (tok.type == TOKEN_LPAREN) { consume(par); // consume '(' Node* node = parse_expression(par); diff --git a/src/parser/parser.c b/src/parser/parser.c @@ -12,9 +12,12 @@ Parser parser_init(Lexer* lex) { - return (Parser) { - .pos = 0, .tokens = lex->tokens, .token_count = lex->token_count, .src = lex->src, .src_len = lex->src_len, .filename = lex->filename - }; + return (Parser) { .pos = 0, + .tokens = lex->tokens, + .token_count = lex->token_count, + .src = lex->src, + .src_len = lex->src_len, + .filename = lex->filename }; } Token @@ -54,7 +57,13 @@ expect(Parser* par, TokenType type) if (tok.type != type) { const char* name = range_str(par->src, tok.start, tok.end, (char[IDENTSZ]) { 0 }); printf("name: %s\n", name); - panic("Expected %s, but got '%s' (%d) at %s:%zu:%zu", token_type_str(type), name, tok.type, par->filename, tok.line, tok.col); + panic("Expected %s, but got '%s' (%d) at %s:%zu:%zu", + token_type_str(type), + name, + tok.type, + par->filename, + tok.line, + tok.col); assert(tok.type == type); } return consume(par); @@ -221,9 +230,10 @@ parse_statement(Parser* par) return parse_block(par); } - if (tok.type == TOKEN_IDENT && tok2.type == TOKEN_IDENT) { return parse_decl_or_func_decl(par); } + bool tok_is_a_type = (tok.type == TOKEN_IDENT || tok.type == TOKEN_VARIADIC || tok.type == TOKEN_COMP_TIME); - if (tok.type == TOKEN_IDENT && tok2.type == TOKEN_EQUAL) { return parse_assignment(par); } + if (tok_is_a_type && tok2.type == TOKEN_IDENT) { return parse_decl_or_func_decl(par); } + if (tok_is_a_type && tok2.type == TOKEN_EQUAL) { return parse_assignment(par); } switch (tok.type) { case TOKEN_RETURN: @@ -279,7 +289,7 @@ parse_block(Parser* par) Node* parse_declaration_statement(Parser* par) { - Node* type = parse_type(par); // consumes the type (e.g., "float") + Node* type_node = parse_type(par); // consumes the type (e.g., "float") Token ident = expect(par, TOKEN_IDENT); // variable or function name if (match(par, TOKEN_LPAREN)) { perror("called a var decl but this looks to be a func decl"); } @@ -288,7 +298,7 @@ parse_declaration_statement(Parser* par) var->type = NODE_VAR_DECL; var->scope = NULL; var->data.var_decl.name = (Span) { ident.start, ident.end }; - var->data.var_decl.type = type; + var->data.var_decl.type = type_node; Token next_tok = peek(par); if (next_tok.type == TOKEN_EQUAL) { consume(par); @@ -304,7 +314,7 @@ parse_declaration_statement(Parser* par) Node* parse_decl_or_func_decl(Parser* par) { - Node* type = parse_type(par); // consumes the type (e.g., "float") + Node* type_node = parse_type(par); // consumes the type (e.g., "float") Token ident = expect(par, TOKEN_IDENT); // variable or function name if (match(par, TOKEN_LPAREN)) { // function @@ -326,7 +336,7 @@ parse_decl_or_func_decl(Parser* par) fn->data.function_decl.body = body; fn->data.function_decl.name = (Span) { ident.start, ident.end }; - fn->data.function_decl.return_type = type; + fn->data.function_decl.return_type = type_node; fn->filename = par->filename; fn->line = ident.line; fn->col = ident.col; @@ -338,7 +348,7 @@ parse_decl_or_func_decl(Parser* par) var->type = NODE_VAR_DECL; var->scope = NULL; var->data.var_decl.name = (Span) { ident.start, ident.end }; - var->data.var_decl.type = type; + var->data.var_decl.type = type_node; var->filename = par->filename; var->line = ident.line; var->col = ident.col; @@ -382,7 +392,8 @@ parser_parse(Ast* ast, Parser* par) if (node == NULL) break; if (program->data.program.len == program->data.program.cap) { program->data.program.cap *= 2; - program->data.program.decl = (Node**)realloc(program->data.program.decl, program->data.program.cap * sizeof(Node*)); + program->data.program.decl + = (Node**)realloc(program->data.program.decl, program->data.program.cap * sizeof(Node*)); assert(program->data.program.decl != NULL && "realloc failed"); } program->data.program.decl[program->data.program.len++] = node; diff --git a/src/sem.c b/src/sem.c @@ -25,8 +25,8 @@ scope_init(Node* node) .owner = node, .id = next_id++ }; - if (s.symbols == NULL) panic("scope_init: could not alloc"); - if (s.children == NULL) panic("scope_init: could not alloc"); + if (s.symbols == NULL) panic_at(node, "scope_init: could not alloc"); + if (s.children == NULL) panic_at(node, "scope_init: could not alloc"); return s; } @@ -35,7 +35,7 @@ new_scope_from_scope(Scope* parent_scope, Node* node) { // new scope Scope* scope = (Scope*)calloc(1, sizeof(Scope)); - if (scope == NULL) panic("new_scope_from_scope: could not alloc"); + if (scope == NULL) panic_at(node, "new_scope_from_scope: could not alloc"); scope->id = next_id++; scope->owner = node; @@ -43,12 +43,12 @@ new_scope_from_scope(Scope* parent_scope, Node* node) // init symbols list scope->symbols = (Symbol**)calloc(CALLOC_SZ, sizeof(Symbol*)); - if (scope->symbols == NULL) panic("new_scope_from_scope: symbols: could not alloc"); + if (scope->symbols == NULL) panic_at(node, "new_scope_from_scope: symbols: could not alloc"); scope->cap = CALLOC_SZ; scope->len = 0; scope->children = (Scope**)calloc(CALLOC_SZ, sizeof(Scope*)); - if (scope->children == NULL) panic("new_scope_from_scope: children: could not alloc"); + if (scope->children == NULL) panic_at(node, "new_scope_from_scope: children: could not alloc"); scope->ch_cap = CALLOC_SZ; scope->ch_len = 0; @@ -88,10 +88,10 @@ scope_var(Scope* scope, Ast* ast, Node* node) const char* type_name = span_str(ast->src, node->data.var_decl.type->data.ident.name, (char[IDENTSZ]) { 0 }); Symbol* sym = (Symbol*)calloc(1, sizeof(Symbol)); - if (sym == NULL) panic("scope_var: symbol: could not alloc"); + if (sym == NULL) panic_at(node, "scope_var: symbol: could not alloc"); TypeInfo* type = (TypeInfo*)calloc(1, sizeof(TypeInfo)); - if (type == NULL) panic("scope_var: type: could not alloc"); + if (type == NULL) panic_at(node, "scope_var: type: could not alloc"); if (strcmp(type_name, "float") == 0) { type->type = SYMTYPE_FLOAT; @@ -143,7 +143,7 @@ scope_var(Scope* scope, Ast* ast, Node* node) if (type_name[0] >= 'A' && type_name[0] <= 'Z') { type->type = SYMTYPE_USER; } else { - panic("sem: not yet defined type '%s' for variable '%s'", type_name, var_name); + panic_at(node, "sem: not yet defined type '%s' for variable '%s'", type_name, var_name); } } diff --git a/src/types.h b/src/types.h @@ -23,6 +23,8 @@ typedef enum { TOKEN_INT_LITERAL, TOKEN_FLOAT_LITERAL, TOKEN_STRING_LITERAL, + TOKEN_COMP_TIME, + TOKEN_VARIADIC, TOKEN_SLASH, TOKEN_STAR, TOKEN_PLUS, @@ -179,6 +181,7 @@ typedef struct Node { struct { double value; } number; struct { Span value; } string; struct { Span name; } ident; + struct { Span name; bool is_comp_time; bool is_variadic; } ident_type; /* clang-format on */ } data; } Node; @@ -296,6 +299,8 @@ typedef struct Symbol { gcc_jit_type* ctype; const char* english_type; + bool is_variadic; + bool is_comp_time; union { gcc_jit_function* funcvalue; gcc_jit_lvalue* lvalue; diff --git a/tests/ex-for-simple2.ox b/tests/ex-for-simple2.ox @@ -1,6 +1,8 @@ void main() { - for(int i = 0; i < 5; i++) { - print(i); + ~int x; + print(x); + for(; x < 5; x++) { + print(x); print("bozo!"); } } diff --git a/tests/ex-variadic.ox b/tests/ex-variadic.ox @@ -0,0 +1,8 @@ +void main() { + ~i64 variadic_num = 5; + variadic_num = 6; + print(variadic_num); + + i64 let_num = 11; + let_num = 12; +} diff --git a/tests/ex6.ox b/tests/ex6.ox @@ -1,7 +1,7 @@ // simple loop int main() { - for (int a = 0; a < 10; a++) { + for (~int a = 0; a < 10; a++) { print(a); print("hi"); }