From 3bfa0679aed1c266db9102ad4d3c91ae61e45e24 Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Thu, 26 Jun 2025 13:43:00 -0600 Subject: [PATCH] basic procedures --- bytes/buffer.jai | 45 ++++++++ bytes/module.jai | 9 ++ jc.jai | 1 + thirdparty/luajit/x/module.jai | 190 +++++++++++++++++++++++++++++++++ vm/interp.jai | 64 ++++++++--- vm/module.jai | 9 +- 6 files changed, 294 insertions(+), 24 deletions(-) create mode 100644 bytes/buffer.jai create mode 100644 bytes/module.jai create mode 120000 jc.jai create mode 100644 thirdparty/luajit/x/module.jai diff --git a/bytes/buffer.jai b/bytes/buffer.jai new file mode 100644 index 0000000..a18c672 --- /dev/null +++ b/bytes/buffer.jai @@ -0,0 +1,45 @@ +Buffer :: struct { + allocator: Allocator; + + data: [..]u8; + count: int; +} + +append :: inline (buf: *Buffer, ptr: *void, count: int) { + free_space := ensure_buffer_has_room(buf, count); + memcpy(free_space, ptr, count); + buf.count += count; +} + +append :: inline (buf: *Buffer, data: []u8) { + append(buf, data.data, data.count); +} + +append :: inline (buf: *Buffer, ptr: *$T) { + append(buf, ptr, size_of(T)); +} + +reset :: inline (buf: *Buffer) { + buf.count = 0; +} + +#scope_file; + +array :: #import "jc/array"; + +ensure_buffer_has_room :: (buf: *Buffer, count: int) -> *u8 { + ptr := buf.data.data + buf.count; + if buf.count + count >= buf.data.allocated { + array.resize(*buf.data, buf.data.allocated * 2); + ptr = buf.data.data + buf.count; + buf.data.count = buf.data.allocated; + } + + return ptr; +} + +#if RUN_TESTS #run { + test.run("basic operations", t => { + buf: Buffer; + }); +} diff --git a/bytes/module.jai b/bytes/module.jai new file mode 100644 index 0000000..4c3db65 --- /dev/null +++ b/bytes/module.jai @@ -0,0 +1,9 @@ +#module_parameters(RUN_TESTS := false); + +#load "buffer.jai"; + +#scope_module; + +#if RUN_TESTS { + test :: #import "jc/test"; +} diff --git a/jc.jai b/jc.jai new file mode 120000 index 0000000..501958c --- /dev/null +++ b/jc.jai @@ -0,0 +1 @@ +jc.jai/ \ No newline at end of file diff --git a/thirdparty/luajit/x/module.jai b/thirdparty/luajit/x/module.jai new file mode 100644 index 0000000..1eb5cda --- /dev/null +++ b/thirdparty/luajit/x/module.jai @@ -0,0 +1,190 @@ +#module_parameters(STATIC := true); + +c_context: #Context; + +#scope_export; + +using #import,file "../module.jai"(STATIC = STATIC); + +expose :: (L: *State, proc: $T, $caller_code := #caller_code, $loc := #caller_location) +#modify { + return T.(*Type_Info).type == .PROCEDURE, "expose must take a procedure"; +} #expand { + #insert -> string { + info := T.(*Type_Info_Procedure); + code := compiler_get_nodes(caller_code); + call := code.(*Code_Procedure_Call); + + proc := call.arguments_sorted[1].(*Code_Ident); + basic.assert(proc.kind == .IDENT, "must be an identifier", loc = loc); + + body_builder: basic.String_Builder; + args_builder: basic.String_Builder; + call_builder: basic.String_Builder; + rets_builder: basic.String_Builder; + + if info.return_types.count != 0 { + basic.append(*call_builder, "\t\t\t\t"); + + for info.return_types { + basic.print_to_builder(*call_builder, "r%", it_index); + if it_index < info.return_types.count -1 { + basic.append(*call_builder, ", "); + } + } + + basic.append(*call_builder, " := "); + } + + basic.append(*call_builder, "proc("); + + for < info.argument_types { + index := -(it_index + 1); + basic.print_to_builder(*args_builder, "\t\t\ta% := %;\n", it_index, gen_stack_pull(it, index)); + + basic.print_to_builder(*call_builder, "a%", it_index); + if it_index > 0 { + basic.append(*call_builder, ", "); + } + } + + basic.append(*call_builder, ");"); + + for info.return_types { + basic.print_to_builder(*rets_builder, "\t\t\t\t%;", gen_stack_push(it, it_index)); + if it_index < info.return_types.count - 1 { + basic.append(*rets_builder, "\n"); + } + } + + return basic.sprint(#string END + lua_name :: "%1"; + lua_wrapper :: (L: *State) -> s32 #c_call { +%2 + push_context c_context { +%3 +%4 + } + + return %5; + } + END, + proc.name, + basic.builder_to_string(*args_builder), + basic.builder_to_string(*call_builder), + basic.builder_to_string(*rets_builder), + info.return_types.count, + ); + } + + info := T.(*Type_Info_Procedure); + pushcclosure(L, lua_wrapper, info.argument_types.count.(s32)); + setfield(L, LUA_GLOBALSINDEX, lua_name); +} + + +#scope_file; + +gen_stack_pull :: (info: *Type_Info, index: int) -> string { + if info.type == { + case .BOOL; + return basic.tprint("get_lua_bool(L, %)", index); + case .INTEGER; + return basic.tprint("tointeger(L, %)", index); + case .FLOAT; + return basic.tprint("tonumber(L, %)", index); + case .STRING; + return basic.tprint("get_lua_string(L, %)", index); + case .STRUCT; + return basic.tprint("get_lua_table(L, %, %)", info.(*Type_Info_Struct).name, index); + + case; + basic.assert(false, "% (%)", info.type, index); + } + + return ""; +} + +gen_stack_push :: (info: *Type_Info, index: int) -> string { + if info.type == { + case .BOOL; + return basic.tprint("pushboolean(L, r%.(s32))", index); + case .INTEGER; + return basic.tprint("pushinteger(L, r%)", index); + case; + basic.assert(false, "% (%)", info.type, index); + } + + return ""; +} + +get_lua_string :: inline (L: *State, index: s32) -> string #c_call { + len: u64; + ptr := tolstring(L, index, *len); + return string.{ data = ptr, count = xx len }; +} + +get_lua_bool :: inline (L: *State, index: s32) -> bool #c_call { + return toboolean(L, index) == 1; +} + +get_lua_table :: (L: *State, $T: Type, index: s32) -> T #c_call { + push_context c_context { + return get_lua_table(L, T.(*Type_Info_Struct), index).(*T).*; + } +} + +temp_storage: [4096]u8; +temp_offset: int; + +get_lua_table :: (L: *State, info: *Type_Info_Struct, index: s32) -> *void { + res := temp_storage.data + temp_offset; + memset(res, 0, info.runtime_size); + + temp_offset = (temp_offset + info.runtime_size) % temp_storage.count; + + for info.members { + defer settop(L, -2); + pushlstring(L, it.name.data, xx it.name.count); + rawget(L, -2); + + vp: *void; + if it.type.type == { + case .BOOL; + v := get_lua_bool(L, -1); + vp = *v; + case .INTEGER; + v := tointeger(L, -1); + if it.type.runtime_size == { + case 1; vp = *(v.(u8, no_check)); + case 2; vp = *(v.(u16, no_check)); + case 4; vp = *(v.(u32, no_check)); + case 8; vp = *(v.(u64, no_check)); + } + case .FLOAT; + v := tonumber(L, -1); + if it.type.runtime_size == { + case 4; vp = *(v.(float32, no_check)); + case 8; vp = *(v.(float64, no_check)); + } + case .STRING; + v := get_lua_string(L, -1); + vp = *v; + case .STRUCT; + v := get_lua_table(L, it.type.(*Type_Info_Struct), -1); + vp = *v; + + case; + basic.assert(false, "% (%)", info.type, index); + } + + memcpy(res + it.offset_in_bytes, vp, it.type.runtime_size); + } + + return res; +} + + +basic :: #import "Basic"; // @future + +#import "Compiler"; diff --git a/vm/interp.jai b/vm/interp.jai index 0c9d27c..cbc9a12 100644 --- a/vm/interp.jai +++ b/vm/interp.jai @@ -1,8 +1,13 @@ Interp :: struct { allocator: Allocator; - symbols: kv.Kv(string, *Interp_Value); toplevel: []*Node; + global: *Interp_Scope; +} + +Interp_Scope :: struct { + parent: *Interp_Scope; + bindings: kv.Kv(string, *Interp_Value); } Interp_Value :: struct { @@ -37,35 +42,39 @@ init :: (i: *Interp, allocator: Allocator) { value_false = make_interp_value(i, .bool); value_false.b = false; + + i.global = make_scope(null,, allocator = allocator); } interp_program :: (i: *Interp) { + scope := i.global; + for i.toplevel if it.kind == { case .variable; var := it.(*Node_Var); sym := var.symbol; - basic.assert(!kv.exists(*i.symbols, sym.str), "redeclaring symbol '%'", sym.str); // @errors + basic.assert(!kv.exists(*scope.bindings, sym.str), "redeclaring symbol '%'", sym.str); // @errors value := value_nil; if var.value_expr != null { - value = interp_expr(i, var.value_expr); + value = interp_expr(i, var.value_expr, i.global); basic.assert(value != null); // @errors } - kv.set(*i.symbols, sym.str, value); + kv.set(*scope.bindings, sym.str, value); case .procedure; proc := it.(*Node_Procedure); sym := proc.symbol; - basic.assert(!kv.exists(*i.symbols, sym.str), "redeclaring procedure '%'", sym.str); + basic.assert(!kv.exists(*scope.bindings, sym.str), "redeclaring procedure '%'", sym.str); value := make_interp_value(i, .procedure); value.proc = proc; - kv.set(*i.symbols, sym.str, value); + kv.set(*scope.bindings, sym.str, value); case .print; print := it.(*Node_Print); - expr := interp_expr(i, print.expr); + expr := interp_expr(i, print.expr, i.global); if expr == null continue; if expr.kind == { @@ -85,7 +94,7 @@ interp_program :: (i: *Interp) { } } -interp_expr :: (i: *Interp, expr: *Node) -> *Interp_Value { +interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value { if expr.kind == { case .procedure_call; call := expr.(*Node_Procedure_Call); @@ -95,19 +104,24 @@ interp_expr :: (i: *Interp, expr: *Node) -> *Interp_Value { sym := call.call_expr.(*Node_Symbol); basic.assert(sym.kind == .symbol); - value, ok := kv.get(*i.symbols, sym.str); - basic.assert(ok, "procedure didn't exists '%'", sym.str); + value := find_symbol(scope, sym.str); + basic.assert(value != null, "procedure didn't exists '%'", sym.str); basic.assert(value.kind == .procedure, "attempt to call non procedure '%'", sym.str); result := value_nil; proc := value.proc; + basic.assert(proc.arguments.count == args.count, "argument mismatch. expected %, given %", proc.arguments.count, args.count); // @errors + + proc_scope := make_scope(scope); + for proc.arguments { + kv.set(*proc_scope.bindings, it.symbol.str, interp_expr(i, args[it_index], scope)); + } - // @todo(judah): check arity, create scope, map args to locals, exec for expr: proc.block.body { if expr.kind == .return_ { ret := expr.(*Node_Return); if ret.values.count != 0 { - result = interp_expr(i, ret.values[0]); + result = interp_expr(i, ret.values[0], proc_scope); } break; @@ -131,7 +145,7 @@ interp_expr :: (i: *Interp, expr: *Node) -> *Interp_Value { } un := expr.(*Node_Unary); - rhs := interp_expr(i, un.right); + rhs := interp_expr(i, un.right, scope); res := make_interp_value(i, rhs.kind); if un.op.kind == { case .plus; do_unop(#code ifx right < 0 then -right else right); @@ -143,8 +157,8 @@ interp_expr :: (i: *Interp, expr: *Node) -> *Interp_Value { case .binary; bin := expr.(*Node_Binary); - lhs := interp_expr(i, bin.left); - rhs := interp_expr(i, bin.right); + lhs := interp_expr(i, bin.left, scope); + rhs := interp_expr(i, bin.right, scope); basic.assert(lhs.kind == rhs.kind, "type mismatch % vs. %", lhs.kind, rhs.kind); // @errors do_binop :: (code: Code) #expand { @@ -182,8 +196,8 @@ interp_expr :: (i: *Interp, expr: *Node) -> *Interp_Value { case .symbol; sym := expr.(*Node_Symbol); - value, ok := kv.get(*i.symbols, sym.str); - basic.assert(ok, "use of undeclared symbol '%'", sym.str); // @errors + value := find_symbol(scope, sym.str); + basic.assert(value != null, "use of undeclared symbol '%'", sym.str); // @errors return value; case .literal; @@ -214,6 +228,22 @@ value_nil: *Interp_Value; value_true: *Interp_Value; value_false: *Interp_Value; +find_symbol :: (scope: *Interp_Scope, symbol: string, all_the_way_up := true) -> *Interp_Value { + value, ok := kv.get(*scope.bindings, symbol); + if !ok && (all_the_way_up && scope.parent != null) { + return find_symbol(scope.parent, symbol, true); + } + + return value; +} + +make_scope :: (parent: *Interp_Scope) -> *Interp_Scope { + scope := mem.request_memory(Interp_Scope); + scope.parent = parent; + kv.init(*scope.bindings, context.allocator); + return scope; +} + make_interp_value :: (i: *Interp, kind: Interp_Value.Kind) -> *Interp_Value { value := mem.request_memory(Interp_Value,, allocator = i.allocator); value.kind = kind; diff --git a/vm/module.jai b/vm/module.jai index ee9e815..21f78f9 100644 --- a/vm/module.jai +++ b/vm/module.jai @@ -26,14 +26,9 @@ strings :: #import "String"; // @future var x = 11.0 var y = 22.0 - var z = x + y * 2.0 / 3.0 - var w = add(x, y) - print x // 10 - print y // 20 - print z // 23.3 - print w // 30 - // print z + print add(x, y) + print add(x+1.0, y) END); interp: Interp;