ox

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

commit 22dfcdf909035989a981063ad5fc84204fa7201f
parent 2db3f9c4a41b9f00e1f02ab71206edd808630fdf
Author: citbl <citbl@citbl.org>
Date:   Sun, 30 Nov 2025 13:30:15 +1000

while loops and truthy bools

Diffstat:
MTODO | 2++
Mmakefile | 1+
Moxdesign.ox | 41+++++++++++++++++++++++++++++++++--------
Msrc/gen/gen.c | 94++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/lexer.c | 9+++++++++
Msrc/parser.h | 1+
Msrc/parser/ast.c | 12++++++++++++
Msrc/parser/expr.c | 3+++
Msrc/parser/parser_utils.c | 9++++++++-
Msrc/types.h | 4++++
Atests/ex-bool.ox | 7+++++++
Mtests/ex-for-simple2.ox | 3++-
Mtests/ex-variadic.ox | 1-
Atests/ex-while.ox | 22++++++++++++++++++++++
Mtests/ex1.ox | 23+++++++++++++++++++++--
15 files changed, 211 insertions(+), 21 deletions(-)

diff --git a/TODO b/TODO @@ -19,6 +19,8 @@ [-] implement all or most of C's into libgccjit [-] ARC memory management, new keyword. +[ ] Instantiating anything should initialise it +[ ] instantiating anything in a loop should be a warning (store curr_loop in ctx) @cruft [x] redo arguments as list and not linked list, handle in parse and in gen (2 places in gen?) diff --git a/makefile b/makefile @@ -59,3 +59,4 @@ test-hmap: clean default MallocNanoZone=0 ./oxc --test-hmap again: clean default + diff --git a/oxdesign.ox b/oxdesign.ox @@ -1,10 +1,34 @@ -~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 +var a i64 = 42; // make a integer 32bit that is a variable (can be updated) `~` initialised with 2. +let b i64 = 42; // like a "let" statement or final variable, runtime constant. +const C i32 = 42; // compile time constant -// give me the raw pointer of x and assign it to y -i32* y = &x; +let x i32 = 35; +var y i32 = 12; +y = 15; +let x = i32(35); +let y = 55; // auto i64 +let z = 40.3; // auto f64 (double) +let a = "some"; +let b = str("some"); +let c = 'c'; + +let x<i32> = <i32>12; + +let ages<arr<i32>>[14] = []; +let names<set<str>> = {}; + + +fx add (x i32, y i32) i32 { + return x + y; +} + +fx flip(a some, b some) (some, some) => b, a + + +fx add (i i32, j i32) i32 { + return i + j; +} // default types // i8, i16, i32, i64, @@ -13,9 +37,10 @@ i32* y = &x; // str, chr, bool, // arrays (fixed) and lists (dynamic) -arr<i32>[16] ages; // fixed slice of 16 -vec<i32> bobs; // dynamic list -set<str> names; // sets +let arr<i32>[16] ages; // fixed slice of 16 +let vec<i32> bobs; // dynamic list +let set<str> names; // sets + struct Person { i32 age, diff --git a/src/gen/gen.c b/src/gen/gen.c @@ -9,6 +9,7 @@ #include <assert.h> #include <sys/param.h> +static gcc_jit_type* type_boolean; static gcc_jit_type* type_int; static gcc_jit_type* type_i8; static gcc_jit_type* type_i16; @@ -108,6 +109,8 @@ gen_init(Scope* scope, const char* src, Node* node, bool quiet) GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, /*0-3 for O3*/ 0); + type_boolean = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_BOOL); + type_int = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_INT64_T); type_i8 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_INT8_T); type_i16 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_INT16_T); @@ -444,6 +447,13 @@ handle_unary_expr(Gen* gen, Node* node) } static gcc_jit_rvalue* +emit_literal_bool(Gen* gen, Node* node) +{ + int v = node->data.boolean.value ? 1 : 0; + return gcc_jit_context_new_rvalue_from_int(gen->ctx, type_boolean, v); +} + +static gcc_jit_rvalue* emit_literal_string(Gen* gen, Node* node) { size_t len = node->data.string.value.end - node->data.string.value.start; @@ -478,6 +488,8 @@ build_program(Gen* gen, Node* node) static gcc_jit_rvalue* lower_builtin_print(Gen* gen, Node* node) { + printf("lower_builtin_print\n"); + size_t argc = node->data.call_expr.len; // for (size_t i = 0; i < argc; i++) { @@ -494,16 +506,13 @@ lower_builtin_print(Gen* gen, Node* node) gcc_jit_rvalue* args[] = { arg }; return gcc_jit_context_new_call(gen->ctx, loc, gen->puts_fn, 1, args); } - // print a integer - else if (is_intlike(gen->ctx, t)) { + // print an integer or bool + else if (is_intlike(gen->ctx, t) || t == type_boolean) { // cast to int for a clean %d // gcc_jit_type* t_int // = gcc_jit_context_get_type(gen->ctx, GCC_JIT_TYPE_INT64_T); gcc_jit_rvalue* fmt = gcc_jit_context_new_string_literal(gen->ctx, "%d\n"); - gcc_jit_rvalue* ival = arg; //(t == t_int) - //? arg - //: gcc_jit_context_new_cast(gen->ctx, - //: NULL, arg, t_int); + gcc_jit_rvalue* ival = arg; gcc_jit_rvalue* args[] = { fmt, ival }; return gcc_jit_context_new_call(gen->ctx, loc, gen->printf_fn, 2, args); } @@ -575,6 +584,8 @@ lower_builtin_print(Gen* gen, Node* node) // double is } else if (ty == type_cstr) { // leave as const char* + } else if (ty == type_boolean) { + arg = gcc_jit_context_new_cast(gen->ctx, loc_from_node(gen, node), arg, type_cstr); } else { // fallback: pass pointer as void* arg = gcc_jit_context_new_cast(gen->ctx, @@ -696,6 +707,8 @@ handle_expr(Gen* gen, Node* node) case NODE_STRING_LITERAL: return emit_literal_string(gen, node); break; + case NODE_BOOL_LITERAL: + return emit_literal_bool(gen, node); case NODE_CALL_EXPR: return handle_func_call(gen, node); break; @@ -789,7 +802,53 @@ static bool build_statement(Gen*, Node*); static gcc_jit_rvalue* build_bool_value(Gen*, Node*); -static int block_counter = 0, loop_counter = 0; +static int block_counter = 0, loop_counter = 0, while_counter = 0; + +static bool +build_while_statement(Gen* gen, Node* node) +{ + gcc_jit_location* loc = loc_from_node(gen, node); + + Node* cond = node->data.while_statement.cond; + Node* body = node->data.while_statement.body; + + while_counter++; + char label_cond[64], label_body[64], label_end[64]; + snprintf(label_cond, sizeof label_cond, "while.cond%d", while_counter); + snprintf(label_body, sizeof label_body, "while.body%d", while_counter); + snprintf(label_end, sizeof label_end, "while.end%d", while_counter); + + gcc_jit_block* cond_block = gcc_jit_function_new_block(gen->curr_func, label_cond); + gcc_jit_block* body_block = gcc_jit_function_new_block(gen->curr_func, label_body); + gcc_jit_block* end_block = gcc_jit_function_new_block(gen->curr_func, label_end); + + /* jump from current block into the loop */ + gcc_jit_block_end_with_jump(gen->curr_block, loc, cond_block); + + gen->curr_block = cond_block; + gcc_jit_rvalue* cnd = NULL; + + if (cond != NULL) { + cnd = build_bool_value(gen, cond); + gcc_jit_block_end_with_conditional(cond_block, loc, cnd, body_block, end_block); + } else { + /* while (1) */ + gcc_jit_block_end_with_jump(cond_block, loc, body_block); + } + + gcc_jit_block* continue_target = cond != NULL ? cond_block : body_block; + push_loop(gen, /*break to*/ end_block, /*continue to*/ continue_target); + + gen->curr_block = body_block; + bool body_ended = build_block(gen, body); + if (!body_ended) { gcc_jit_block_end_with_jump(gen->curr_block, loc, continue_target); } + + pop_loop(gen); + + /* resume after loop */ + gen->curr_block = end_block; + return false; +} static bool build_for_statement(Gen* gen, Node* node) @@ -1094,6 +1153,8 @@ build_statement(Gen* gen, Node* node) return build_if_statement(gen, node); case NODE_FOR: return build_for_statement(gen, node); + case NODE_WHILE: + return build_while_statement(gen, node); case NODE_FUNCTION_DECL: // "closure" build_func_decl(gen, node); @@ -1113,6 +1174,22 @@ build_bool_value(Gen* gen, Node* node) gcc_jit_location* loc = loc_from_node(gen, node); switch (node->type) { + case NODE_BOOL_LITERAL: + /* true/false literal */ + return gcc_jit_context_new_rvalue_from_int(gen->ctx, type_boolean, node->data.boolean.value ? 1 : 0); + break; + case NODE_INT_LITERAL: { + /* while (1) style – C truthiness */ + gcc_jit_rvalue* iv = emit_literal_int(gen, node); + return gcc_jit_context_new_cast(gen->ctx, loc, iv, type_boolean); + } + case NODE_STRING_LITERAL: { + /* while("word") should also be truthy if the pointer is non null */ + gcc_jit_rvalue* v = emit_literal_string(gen, node); + gcc_jit_type* ty = gcc_jit_rvalue_get_type(v); + gcc_jit_rvalue* nullv = gcc_jit_context_null(gen->ctx, ty); + return gcc_jit_context_new_comparison(gen->ctx, loc, GCC_JIT_COMPARISON_NE, v, nullv); + } case NODE_BINARY_EXPR: { gcc_jit_rvalue* lvalue = handle_expr(gen, node->data.binary_expr.lhs); gcc_jit_rvalue* rvalue = handle_expr(gen, node->data.binary_expr.rhs); @@ -1140,7 +1217,7 @@ build_bool_value(Gen* gen, Node* node) break; } default: - printf("build_bool_rvalue unhandled, %s\n", node_type_str(node->type)); + printf("/!\\ build_bool_rvalue unhandled, %s\n", node_type_str(node->type)); break; } @@ -1153,6 +1230,7 @@ build_block(Gen* gen, Node* body) { // TODO if there is no block ({ ... }), we presume there is only 1 statement. // This might not always be valid, func declaration do require a block {}, but there might be cases... + // FIXME: a symbol declared in a child block is accessible in the parent or in a sibling block... if (body->data.block.len == 0) { return build_statement(gen, body); } for (size_t i = 0; i < body->data.block.len; i++) { diff --git a/src/lexer.c b/src/lexer.c @@ -94,6 +94,11 @@ make_ident(Lexer* lex, size_t pos, size_t line, size_t col) else if ((lex->pos - pos) == 6 && strncmp(lex->src + pos, "extend", 6) == 0) type = TOKEN_EXTEND; + else if ((lex->pos - pos) == 4 && strncmp(lex->src + pos, "true", 4) == 0) + type = TOKEN_BOOL_TRUE_LITERAL; + else if ((lex->pos - pos) == 5 && strncmp(lex->src + pos, "false", 5) == 0) + type = TOKEN_BOOL_FALSE_LITERAL; + else if ((lex->pos - pos) == 2 && strncmp(lex->src + pos, "fx", 2) == 0) type = TOKEN_FX; else if ((lex->pos - pos) == 2 && strncmp(lex->src + pos, "fn", 2) == 0) @@ -302,6 +307,8 @@ print_token(const Token* t, const char* contents) [TOKEN_INT_LITERAL] = "integer literal", [TOKEN_FLOAT_LITERAL] = "float literal", [TOKEN_STRING_LITERAL] = "string literal", + [TOKEN_BOOL_TRUE_LITERAL] = "bool TRUE literal", + [TOKEN_BOOL_FALSE_LITERAL] = "bool FALSE literal", [TOKEN_SLASH] = "slash", [TOKEN_STAR] = "star", [TOKEN_PLUS] = "plus", @@ -382,6 +389,8 @@ token_type_str(TokenType t) [TOKEN_INT_LITERAL] = "TOKEN_INT_LITERAL", [TOKEN_FLOAT_LITERAL] = "TOKEN_FLOAT_LITERAL", [TOKEN_STRING_LITERAL] = "TOKEN_STRING_LITERAL", + [TOKEN_BOOL_TRUE_LITERAL] = "TOKEN_BOOL_TRUE_LITERAL", + [TOKEN_BOOL_FALSE_LITERAL] = "TOKEN_BOOL_FALSE_LITERAL", [TOKEN_SLASH] = "TOKEN_SLASH", [TOKEN_STAR] = "TOKEN_STAR", [TOKEN_PLUS] = "TOKEN_PLUS", diff --git a/src/parser.h b/src/parser.h @@ -54,6 +54,7 @@ Node* make_ident_node(Span); Node* make_postfix_node(UnaryOp, Node*); Node* make_number_node(Parser*); Node* make_unary_node(UnaryOp, Node*); +Node* make_boolean_node(Parser*, bool); Node* make_string_node(Parser*); Node* make_binary_node(OpType, Node*, Node*); Node* parse_return_statement(Parser*); diff --git a/src/parser/ast.c b/src/parser/ast.c @@ -67,6 +67,18 @@ make_unary_node(UnaryOp op, Node* operand) } Node* +make_boolean_node(Parser* par, bool truthy) +{ + consume(par); + Node* node = (Node*)calloc(1, sizeof(Node)); + if (node == NULL) panic("make_boolean_node: could not alloc"); + node->type = NODE_BOOL_LITERAL; + node->scope = NULL; + node->data.boolean.value = truthy; + return node; +} + +Node* make_string_node(Parser* par) { Token tok = consume(par); diff --git a/src/parser/expr.c b/src/parser/expr.c @@ -187,6 +187,9 @@ Node* parse_primary(Parser* par) { Token tok = peek(par); + if (tok.type == TOKEN_BOOL_TRUE_LITERAL || tok.type == TOKEN_BOOL_FALSE_LITERAL) { + return make_boolean_node(par, tok.type == TOKEN_BOOL_TRUE_LITERAL); + } 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 || tok.type == TOKEN_VARIADIC || tok.type == TOKEN_COMP_TIME) { return parse_ident(par); } diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c @@ -76,7 +76,8 @@ print_node(const char* source, Node* node, int level) const char* name; switch (node->type) { case NODE_FUNCTION_DECL: - name = range_str(source, node->data.function_decl.name.start, node->data.function_decl.name.end, (char[IDENTSZ]) { 0 }); + name = range_str( + source, node->data.function_decl.name.start, node->data.function_decl.name.end, (char[IDENTSZ]) { 0 }); printf("%*s FUNC DECL: name='%s'\n", level, "", name); if (node->data.function_decl.return_type) { printf("%*s ↳ return type:\n", level * 2, ""); @@ -157,6 +158,12 @@ print_node(const char* source, Node* node, int level) case NODE_FLOAT_LITERAL: printf("%*s ↳ LITERAL FLOAT NUMBER value=%f\n", level * 2, "", node->data.number.value); break; + case NODE_BOOL_LITERAL: + if (node->data.boolean.value) + printf("%*s LITERAL BOOL TRUE\n", level * 2, ""); + else + printf("%*s LITERAL BOOL FALSE\n", level * 2, ""); + break; case NODE_STRING_LITERAL: { const char* lit = span_str(source, node->data.string.value, (char[IDENTSZ]) { 0 }); printf("%*s ↳ LITERAL STRING value=\"%s\"\n", level * 2, "", lit); 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_BOOL_TRUE_LITERAL, + TOKEN_BOOL_FALSE_LITERAL, TOKEN_COMP_TIME, TOKEN_VARIADIC, TOKEN_SLASH, @@ -85,6 +87,7 @@ typedef enum { NODE_INT_LITERAL, NODE_FLOAT_LITERAL, NODE_STRING_LITERAL, + NODE_BOOL_LITERAL, NODE_IDENT, NODE_TYPE, NODE_BINARY_EXPR, @@ -179,6 +182,7 @@ typedef struct Node { struct { UnaryOp op; struct Node* operand; bool is_postfix; } unary_expr; struct { struct Node* array; struct Node* index; } subscript_expr; struct { double value; } number; + struct { bool value; } boolean; struct { Span value; } string; struct { Span name; } ident; struct { Span name; bool is_comp_time; bool is_variadic; } ident_type; diff --git a/tests/ex-bool.ox b/tests/ex-bool.ox @@ -0,0 +1,7 @@ +void main() { + + print("true is true"); + print(true); + print("false is false"); + print(false); +} diff --git a/tests/ex-for-simple2.ox b/tests/ex-for-simple2.ox @@ -1,6 +1,7 @@ void main() { ~int x; - print("initial value:"); + print("initial value:"); + // do we initialise at 0 or not? print(x); for(int x = 3; x < 5; x++) { //TODO the x defined in the for init is never actually used diff --git a/tests/ex-variadic.ox b/tests/ex-variadic.ox @@ -4,5 +4,4 @@ void main() { print(variadic_num); i64 let_num = 11; - let_num = 12; } diff --git a/tests/ex-while.ox b/tests/ex-while.ox @@ -0,0 +1,22 @@ +void main() { + + print("before"); + int i = 1; + while(true) { + print("while true prints once 1/3"); + break; + } + while(1) { + print("while 1 prints once 2/3"); + break; + } + while(0) { + print("should never happen!"); + break; + } + while("in vino veritas") { + print("while string prints once 3/3"); + break; + } + print("after"); +} diff --git a/tests/ex1.ox b/tests/ex1.ox @@ -1,6 +1,25 @@ // Hello world and comment -void main(int var) { +fx main() { print("hello world"); - //print(param1); +} + +fx main => print "hello world" + +fx main() => print "hello world" + +fx main() => print("hello world") + +fx main() => print("hello world"); + +fx main() { + print("hello world"); +} + +fx main(void) void { + print("hello world"); +} + +fx print(contents: str) void { + print_("%s\n", contents) }