diff --git a/examples/28-sdl-graphics.sx b/examples/28-sdl-graphics.sx new file mode 100644 index 0000000..151bbad --- /dev/null +++ b/examples/28-sdl-graphics.sx @@ -0,0 +1,294 @@ +#import "modules/std.sx"; +#import "modules/sdl3.sx"; +#import "modules/opengl.sx"; + +PI :f32: 3.14159265; + +// ---- Matrix math (column-major [16]f32, passed as [*]f32) ---- + +mat4_zero :: (m: [*]f32) { + i := 0; + while i < 16 { m[i] = 0.0; i += 1; } +} + +mat4_identity :: (m: [*]f32) { + mat4_zero(m); + m[0] = 1.0; m[5] = 1.0; m[10] = 1.0; m[15] = 1.0; +} + +mat4_multiply :: (out: [*]f32, a: [*]f32, b: [*]f32) { + tmp : [16]f32 = ---; + j := 0; + while j < 4 { + i := 0; + while i < 4 { + sum : f32 = 0.0; + k := 0; + while k < 4 { + sum = sum + a[k * 4 + i] * b[j * 4 + k]; + k += 1; + } + tmp[j * 4 + i] = sum; + i += 1; + } + j += 1; + } + i := 0; + while i < 16 { out[i] = tmp[i]; i += 1; } +} + +mat4_perspective :: (m: [*]f32, fov: f32, aspect: f32, near: f32, far: f32) { + mat4_zero(m); + half := fov / 2.0; + f := cos(half) / sin(half); + m[0] = f / aspect; + m[5] = f; + m[10] = (far + near) / (near - far); + m[11] = -1.0; + m[14] = (2.0 * far * near) / (near - far); +} + +mat4_rotate_y :: (m: [*]f32, angle: f32) { + mat4_zero(m); + c := cos(angle); + s := sin(angle); + m[0] = c; + m[2] = 0.0 - s; + m[5] = 1.0; + m[8] = s; + m[10] = c; + m[15] = 1.0; +} + +mat4_rotate_x :: (m: [*]f32, angle: f32) { + mat4_zero(m); + c := cos(angle); + s := sin(angle); + m[0] = 1.0; + m[5] = c; + m[6] = s; + m[9] = 0.0 - s; + m[10] = c; + m[15] = 1.0; +} + +mat4_translate :: (m: [*]f32, tx: f32, ty: f32, tz: f32) { + mat4_identity(m); + m[12] = tx; + m[13] = ty; + m[14] = tz; +} + +// ---- Shader helpers ---- + +compile_shader :: (shader_type: u32, source: [:0]u8) -> u32 { + shader : u32 = glCreateShader(shader_type); + src_ptr : *void = xx &source[0]; + glShaderSource(shader, 1, xx &src_ptr, null); + glCompileShader(shader); + + status : s32 = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, xx &status); + if status == GL_FALSE { + log_buf : [512]u8 = ---; + glGetShaderInfoLog(shader, 512, null, xx &log_buf[0]); + print("Shader compile error\n"); + } + shader; +} + +create_program :: (vert_src: [:0]u8, frag_src: [:0]u8) -> u32 { + vs : u32 = compile_shader(GL_VERTEX_SHADER, vert_src); + fs : u32 = compile_shader(GL_FRAGMENT_SHADER, frag_src); + + prog : u32 = glCreateProgram(); + glAttachShader(prog, vs); + glAttachShader(prog, fs); + glLinkProgram(prog); + + status : s32 = 0; + glGetProgramiv(prog, GL_LINK_STATUS, xx &status); + if status == GL_FALSE { + log_buf : [512]u8 = ---; + glGetProgramInfoLog(prog, 512, null, xx &log_buf[0]); + print("Program link error\n"); + } + + glDeleteShader(vs); + glDeleteShader(fs); + prog; +} + +// ---- Main ---- + +main :: () { + SDL_Init(SDL_INIT_VIDEO); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + + window := SDL_CreateWindow("sx GL cube", 800, 600, SDL_WINDOW_OPENGL); + gl_ctx := SDL_GL_CreateContext(window); + SDL_GL_MakeCurrent(window, gl_ctx); + SDL_GL_SetSwapInterval(1); + + load_gl(SDL_GL_GetProcAddress); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + + // Shaders + vert_src : [:0]u8 = "#version 330 core\nlayout (location = 0) in vec3 aPos;\nlayout (location = 1) in vec3 aNormal;\nuniform mat4 uMVP;\nout vec3 vNormal;\nout vec3 vPos;\nvoid main() {\n gl_Position = uMVP * vec4(aPos, 1.0);\n vNormal = aNormal;\n vPos = aPos;\n}\n"; + + frag_src : [:0]u8 = "#version 330 core\nin vec3 vNormal;\nin vec3 vPos;\nout vec4 FragColor;\nuniform vec3 uLightDir;\nuniform float uWire;\nvoid main() {\n if (uWire > 0.5) {\n FragColor = vec4(0.05, 0.05, 0.05, 1.0);\n return;\n }\n vec3 n = normalize(vNormal);\n vec3 l = normalize(uLightDir);\n float diff = max(dot(n, l), 0.15);\n float cx = floor(vPos.x * 2.0 + 0.001);\n float cy = floor(vPos.y * 2.0 + 0.001);\n float cz = floor(vPos.z * 2.0 + 0.001);\n float check = mod(cx + cy + cz, 2.0);\n vec3 col1 = vec3(0.9, 0.5, 0.2);\n vec3 col2 = vec3(0.2, 0.6, 0.9);\n vec3 base = mix(col1, col2, check);\n FragColor = vec4(base * diff, 1.0);\n}\n"; + + program : u32 = create_program(vert_src, frag_src); + glUseProgram(program); + + mvp_loc : s32 = glGetUniformLocation(program, "uMVP"); + light_loc : s32 = glGetUniformLocation(program, "uLightDir"); + wire_loc : s32 = glGetUniformLocation(program, "uWire"); + + // Cube vertices: position(3) + normal(3), 36 vertices = 216 floats + vertices : [216]f32 = .[ + // Front face (z = +0.5) + -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, + 0.5, -0.5, 0.5, 0.0, 0.0, 1.0, + 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, + -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, + 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, + -0.5, 0.5, 0.5, 0.0, 0.0, 1.0, + // Back face (z = -0.5) + 0.5, -0.5, -0.5, 0.0, 0.0, -1.0, + -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, + -0.5, 0.5, -0.5, 0.0, 0.0, -1.0, + 0.5, -0.5, -0.5, 0.0, 0.0, -1.0, + -0.5, 0.5, -0.5, 0.0, 0.0, -1.0, + 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, + // Top face (y = +0.5) + -0.5, 0.5, 0.5, 0.0, 1.0, 0.0, + 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, + 0.5, 0.5, -0.5, 0.0, 1.0, 0.0, + -0.5, 0.5, 0.5, 0.0, 1.0, 0.0, + 0.5, 0.5, -0.5, 0.0, 1.0, 0.0, + -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, + // Bottom face (y = -0.5) + -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, + 0.5, -0.5, -0.5, 0.0, -1.0, 0.0, + 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, + -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, + 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, + -0.5, -0.5, 0.5, 0.0, -1.0, 0.0, + // Right face (x = +0.5) + 0.5, -0.5, 0.5, 1.0, 0.0, 0.0, + 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, + 0.5, 0.5, -0.5, 1.0, 0.0, 0.0, + 0.5, -0.5, 0.5, 1.0, 0.0, 0.0, + 0.5, 0.5, -0.5, 1.0, 0.0, 0.0, + 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, + // Left face (x = -0.5) + -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, + -0.5, -0.5, 0.5, -1.0, 0.0, 0.0, + -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, + -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, + -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, + -0.5, 0.5, -0.5, -1.0, 0.0, 0.0 + ]; + + // Create VAO and VBO + vao : u32 = 0; + vbo : u32 = 0; + glGenVertexArrays(1, xx &vao); + glGenBuffers(1, xx &vbo); + + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, 864, xx &vertices[0], GL_STATIC_DRAW); + + // Position attribute (location 0): 3 floats, stride 24 bytes, offset 0 + glVertexAttribPointer(0, 3, GL_FLOAT, 0, 24, xx 0); + glEnableVertexAttribArray(0); + + // Normal attribute (location 1): 3 floats, stride 24 bytes, offset 12 + glVertexAttribPointer(1, 3, GL_FLOAT, 0, 24, xx 12); + glEnableVertexAttribArray(1); + + // Set light direction + glUniform3f(light_loc, 0.5, 0.7, 1.0); + glUniform1f(wire_loc, 0.0); + + // Render loop + running := true; + event : [128]u8 = ---; + + while running { + while SDL_PollEvent(xx &event[0]) { + etype : u32 = xx event[0]; + if etype == SDL_EVENT_QUIT { + running = false; + } + } + + // Compute rotation angle from time + ticks := SDL_GetTicks(); + ms : f32 = xx ticks; + angle := ms * 0.001; + + // Build matrices + proj : [16]f32 = ---; + mat4_perspective(xx &proj[0], PI / 4.0, 800.0 / 600.0, 0.1, 100.0); + + view : [16]f32 = ---; + mat4_translate(xx &view[0], 0.0, 0.0, -3.0); + + rot_y : [16]f32 = ---; + mat4_rotate_y(xx &rot_y[0], angle); + + rot_x : [16]f32 = ---; + mat4_rotate_x(xx &rot_x[0], angle * 0.7); + + // model = rot_y * rot_x + model : [16]f32 = ---; + mat4_multiply(xx &model[0], xx &rot_y[0], xx &rot_x[0]); + + // view_model = view * model + vm : [16]f32 = ---; + mat4_multiply(xx &vm[0], xx &view[0], xx &model[0]); + + // mvp = proj * view_model + mvp : [16]f32 = ---; + mat4_multiply(xx &mvp[0], xx &proj[0], xx &vm[0]); + + glUniformMatrix4fv(mvp_loc, 1, 0, xx &mvp[0]); + + glClearColor(0.1, 0.1, 0.15, 1.0); + glClear(GL_COLOR_BUFFER_BIT + GL_DEPTH_BUFFER_BIT); + + // Solid pass + glUniform1f(wire_loc, 0.0); + glBindVertexArray(vao); + glDrawArrays(GL_TRIANGLES, 0, 36); + + // Wireframe overlay + glDepthFunc(GL_LEQUAL); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glLineWidth(2.0); + glUniform1f(wire_loc, 1.0); + glDrawArrays(GL_TRIANGLES, 0, 36); + + // Restore + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glDepthFunc(GL_LESS); + glUniform1f(wire_loc, 0.0); + + SDL_GL_SwapWindow(window); + } + + SDL_GL_DestroyContext(gl_ctx); + SDL_DestroyWindow(window); + SDL_Quit(); +} diff --git a/examples/29-fn-pointers.sx b/examples/29-fn-pointers.sx new file mode 100644 index 0000000..d34f202 --- /dev/null +++ b/examples/29-fn-pointers.sx @@ -0,0 +1,28 @@ +#import "modules/std.sx"; + +add :: (a: s32, b: s32) -> s32 { a + b; } +mul :: (a: s32, b: s32) -> s32 { a * b; } + +apply :: (f: (s32, s32) -> s32, x: s32, y: s32) -> s32 { + f(x, y); +} + +main :: () { + // Store function in variable + fp : (s32, s32) -> s32 = add; + print("fp(3,4) = {}\n", fp(3, 4)); + + // Reassign to different function + fp = mul; + print("fp(3,4) = {}\n", fp(3, 4)); + + // Pass function pointer as argument + print("apply(add,5,6) = {}\n", apply(add, 5, 6)); + print("apply(mul,5,6) = {}\n", apply(mul, 5, 6)); +} + +// ** stdout ** +//fp(3,4) = 7 +//fp(3,4) = 12 +//apply(add,5,6) = 11 +//apply(mul,5,6) = 30 diff --git a/examples/modules/opengl.sx b/examples/modules/opengl.sx new file mode 100644 index 0000000..8b80697 --- /dev/null +++ b/examples/modules/opengl.sx @@ -0,0 +1,98 @@ +// OpenGL 3.3 Core — runtime-loaded function pointers +// No #library needed — caller provides a loader (e.g. SDL_GL_GetProcAddress) + +// Constants +GL_FALSE :s32: 0; +GL_TRUE :s32: 1; +GL_DEPTH_TEST :u32: 0x0B71; +GL_CULL_FACE :u32: 0x0B44; +GL_BLEND :u32: 0x0BE2; +GL_TRIANGLES :u32: 4; +GL_LINES :u32: 1; +GL_FLOAT :u32: 0x1406; +GL_UNSIGNED_INT :u32: 0x1405; +GL_VERTEX_SHADER :u32: 0x8B31; +GL_FRAGMENT_SHADER :u32: 0x8B30; +GL_COMPILE_STATUS :u32: 0x8B81; +GL_LINK_STATUS :u32: 0x8B82; +GL_ARRAY_BUFFER :u32: 0x8892; +GL_ELEMENT_ARRAY_BUFFER :u32: 0x8893; +GL_STATIC_DRAW :u32: 0x88E4; +GL_COLOR_BUFFER_BIT :u32: 0x4000; +GL_DEPTH_BUFFER_BIT :u32: 0x0100; +GL_FRONT_AND_BACK :u32: 0x0408; +GL_LINE :u32: 0x1B01; +GL_FILL :u32: 0x1B02; + +// Function pointer variables (mutable, loaded at runtime) +glClearColor : (f32, f32, f32, f32) -> void = ---; +glClear : (u32) -> void = ---; +glEnable : (u32) -> void = ---; +glDisable : (u32) -> void = ---; +glViewport : (s32, s32, s32, s32) -> void = ---; +glDrawArrays : (u32, s32, s32) -> void = ---; +glPolygonMode : (u32, u32) -> void = ---; +glLineWidth : (f32) -> void = ---; +glCreateShader : (u32) -> u32 = ---; +glShaderSource : (u32, s32, *void, *void) -> void = ---; +glCompileShader : (u32) -> void = ---; +glGetShaderiv : (u32, u32, *void) -> void = ---; +glGetShaderInfoLog : (u32, s32, *void, *void) -> void = ---; +glCreateProgram : () -> u32 = ---; +glAttachShader : (u32, u32) -> void = ---; +glLinkProgram : (u32) -> void = ---; +glGetProgramiv : (u32, u32, *void) -> void = ---; +glGetProgramInfoLog : (u32, s32, *void, *void) -> void = ---; +glUseProgram : (u32) -> void = ---; +glDeleteShader : (u32) -> void = ---; +glGenVertexArrays : (s32, *void) -> void = ---; +glGenBuffers : (s32, *void) -> void = ---; +glBindVertexArray : (u32) -> void = ---; +glBindBuffer : (u32, u32) -> void = ---; +glBufferData : (u32, s64, *void, u32) -> void = ---; +glVertexAttribPointer : (u32, s32, u32, u8, s32, *void) -> void = ---; +glEnableVertexAttribArray : (u32) -> void = ---; +glGetUniformLocation : (u32, [:0]u8) -> s32 = ---; +glUniformMatrix4fv : (s32, s32, u8, *void) -> void = ---; +glUniform3f : (s32, f32, f32, f32) -> void = ---; +glDepthFunc : (u32) -> void = ---; +glUniform1f : (s32, f32) -> void = ---; +GL_LESS :u32: 0x0201; +GL_LEQUAL :u32: 0x0203; + +// Loader: call once after creating GL context +// Pass in a proc loader (e.g. SDL_GL_GetProcAddress) +load_gl :: (get_proc: ([:0]u8) -> *void) { + glClearColor = xx get_proc("glClearColor"); + glClear = xx get_proc("glClear"); + glEnable = xx get_proc("glEnable"); + glDisable = xx get_proc("glDisable"); + glViewport = xx get_proc("glViewport"); + glDrawArrays = xx get_proc("glDrawArrays"); + glPolygonMode = xx get_proc("glPolygonMode"); + glLineWidth = xx get_proc("glLineWidth"); + glCreateShader = xx get_proc("glCreateShader"); + glShaderSource = xx get_proc("glShaderSource"); + glCompileShader = xx get_proc("glCompileShader"); + glGetShaderiv = xx get_proc("glGetShaderiv"); + glGetShaderInfoLog = xx get_proc("glGetShaderInfoLog"); + glCreateProgram = xx get_proc("glCreateProgram"); + glAttachShader = xx get_proc("glAttachShader"); + glLinkProgram = xx get_proc("glLinkProgram"); + glGetProgramiv = xx get_proc("glGetProgramiv"); + glGetProgramInfoLog = xx get_proc("glGetProgramInfoLog"); + glUseProgram = xx get_proc("glUseProgram"); + glDeleteShader = xx get_proc("glDeleteShader"); + glGenVertexArrays = xx get_proc("glGenVertexArrays"); + glGenBuffers = xx get_proc("glGenBuffers"); + glBindVertexArray = xx get_proc("glBindVertexArray"); + glBindBuffer = xx get_proc("glBindBuffer"); + glBufferData = xx get_proc("glBufferData"); + glVertexAttribPointer = xx get_proc("glVertexAttribPointer"); + glEnableVertexAttribArray = xx get_proc("glEnableVertexAttribArray"); + glGetUniformLocation = xx get_proc("glGetUniformLocation"); + glUniformMatrix4fv = xx get_proc("glUniformMatrix4fv"); + glUniform3f = xx get_proc("glUniform3f"); + glDepthFunc = xx get_proc("glDepthFunc"); + glUniform1f = xx get_proc("glUniform1f"); +} diff --git a/examples/modules/sdl3.sx b/examples/modules/sdl3.sx new file mode 100644 index 0000000..4b58003 --- /dev/null +++ b/examples/modules/sdl3.sx @@ -0,0 +1,41 @@ +#library "SDL3"; + +// SDL_InitFlags +SDL_INIT_VIDEO :u32: 0x20; + +// SDL_WindowFlags +SDL_WINDOW_OPENGL :u64: 0x2; + +// SDL_GLAttr (enum starting at 0) +SDL_GL_DOUBLEBUFFER :s32: 5; +SDL_GL_DEPTH_SIZE :s32: 6; +SDL_GL_CONTEXT_MAJOR_VERSION :s32: 17; +SDL_GL_CONTEXT_MINOR_VERSION :s32: 18; +SDL_GL_CONTEXT_FLAGS :s32: 19; +SDL_GL_CONTEXT_PROFILE_MASK :s32: 20; + +// SDL_GLProfile +SDL_GL_CONTEXT_PROFILE_CORE :s32: 0x1; + +// SDL_GLContextFlag +SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG :s32: 0x2; + +// SDL_EventType +SDL_EVENT_QUIT :u32: 0x100; +SDL_EVENT_KEY_DOWN :u32: 0x300; + +// Functions +SDL_Init :: (flags: u32) -> bool #foreign; +SDL_Quit :: () -> void #foreign; +SDL_CreateWindow :: (title: [:0]u8, w: s32, h: s32, flags: u64) -> *void #foreign; +SDL_DestroyWindow :: (window: *void) -> void #foreign; +SDL_GL_SetAttribute :: (attr: s32, value: s32) -> bool #foreign; +SDL_GL_CreateContext :: (window: *void) -> *void #foreign; +SDL_GL_DestroyContext :: (context: *void) -> bool #foreign; +SDL_GL_MakeCurrent :: (window: *void, context: *void) -> bool #foreign; +SDL_GL_SwapWindow :: (window: *void) -> bool #foreign; +SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign; +SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign; +SDL_PollEvent :: (event: *void) -> bool #foreign; +SDL_GetTicks :: () -> u64 #foreign; +SDL_Delay :: (ms: u32) -> void #foreign; diff --git a/examples/modules/std.sx b/examples/modules/std.sx index a1c2883..7b64f2d 100644 --- a/examples/modules/std.sx +++ b/examples/modules/std.sx @@ -1,6 +1,8 @@ Vector :: ($N: int, $T: Type) -> Type #builtin; write :: (str: string) -> void #builtin; sqrt :: (x: $T) -> T #builtin; +sin :: (x: $T) -> T #builtin; +cos :: (x: $T) -> T #builtin; size_of :: ($T: Type) -> s64 #builtin; alloc :: (size: s64) -> string #builtin; malloc :: (size: s64) -> *void #builtin; diff --git a/src/ast.zig b/src/ast.zig index 3f068bf..d79c0f2 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -63,6 +63,7 @@ pub const Node = struct { builtin_expr: void, foreign_expr: void, library_decl: LibraryDecl, + function_type_expr: FunctionTypeExpr, pub fn declName(self: Data) ?[]const u8 { return switch (self) { @@ -359,3 +360,8 @@ pub const NamespaceDecl = struct { pub const LibraryDecl = struct { lib_name: []const u8, }; + +pub const FunctionTypeExpr = struct { + param_types: []const *Node, + return_type: ?*Node, // null = void return +}; diff --git a/src/codegen.zig b/src/codegen.zig index 7a55985..65b48f8 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -177,6 +177,10 @@ pub const CodeGen = struct { foreign_libraries: std.ArrayList([]const u8), // Set of foreign function names (for ABI lowering at call sites) foreign_fns: std.StringHashMap(void), + // Global mutable variables (from top-level var_decl, e.g. function pointers loaded at runtime) + global_mutable_vars: std.StringHashMap(NamedValue), + // Declared return types for non-generic functions (preserves signedness lost by LLVM round-trip) + function_return_types: std.StringHashMap(Type), // Target configuration (triple, cpu, opt level, lib paths, linker) target_config: TargetConfig = .{}, @@ -293,6 +297,8 @@ pub const CodeGen = struct { .deferred_fn_bodies = std.ArrayList(DeferredFn).empty, .foreign_libraries = std.ArrayList([]const u8).empty, .foreign_fns = std.StringHashMap(void).init(allocator), + .global_mutable_vars = std.StringHashMap(NamedValue).init(allocator), + .function_return_types = std.StringHashMap(Type).init(allocator), .target_config = target_config, }; } @@ -366,7 +372,7 @@ pub const CodeGen = struct { const elem_ty = Type.fromName(info.element_name) orelse unreachable; return c.LLVMVectorType(self.typeToLLVM(elem_ty), info.length); }, - .pointer_type, .many_pointer_type => c.LLVMPointerTypeInContext(self.context, 0), + .pointer_type, .many_pointer_type, .function_type => c.LLVMPointerTypeInContext(self.context, 0), .any_type => self.getAnyStructType(), .meta_type => c.LLVMPointerTypeInContext(self.context, 0), }; @@ -741,6 +747,9 @@ pub const CodeGen = struct { .namespace_decl => |ns| { try self.registerNamespace(ns); }, + .var_decl => |vd| { + try self.registerGlobalVar(vd); + }, else => {}, } } @@ -937,6 +946,21 @@ pub const CodeGen = struct { const elem_name = elem_type.displayName(self.allocator) catch unreachable; return .{ .many_pointer_type = .{ .element_name = elem_name } }; } + // Function pointer type: (ParamTypes) -> ReturnType + if (tn.data == .function_type_expr) { + const fte = tn.data.function_type_expr; + var param_types = std.ArrayList(Type).empty; + for (fte.param_types) |pt| { + param_types.append(self.allocator, self.resolveType(pt)) catch return .void_type; + } + const ret_ty = if (fte.return_type) |rt| self.resolveType(rt) else Type.void_type; + const ret_ptr = self.allocator.create(Type) catch return .void_type; + ret_ptr.* = ret_ty; + return .{ .function_type = .{ + .param_types = param_types.toOwnedSlice(self.allocator) catch return .void_type, + .return_type = ret_ptr, + } }; + } // Parameterized type: Vector(N, T) or generic struct instantiation if (tn.data == .parameterized_type_expr) { const pte = tn.data.parameterized_type_expr; @@ -1482,6 +1506,9 @@ pub const CodeGen = struct { } } try self.fn_param_types.put(llvm_name, try param_types.toOwnedSlice(self.allocator)); + // Track declared return type (preserves signedness lost by LLVM round-trip) + const ret_ty = if (fd.return_type) |rt| self.resolveType(rt) else Type.void_type; + try self.function_return_types.put(llvm_name, ret_ty); // Track variadic function info for call site packing for (fd.params, 0..) |param, i| { if (param.is_variadic) { @@ -1553,6 +1580,9 @@ pub const CodeGen = struct { .library_decl => |ld| { try self.foreign_libraries.append(self.allocator, ld.lib_name); }, + .var_decl => |vd| { + try self.registerGlobalVar(vd); + }, else => {}, } } @@ -1703,6 +1733,22 @@ pub const CodeGen = struct { }); } + fn registerGlobalVar(self: *CodeGen, vd: ast.VarDecl) !void { + const ta = vd.type_annotation orelse return; + const sx_ty = self.resolveType(ta); + if (sx_ty == .void_type) return; + + const llvm_ty = self.typeToLLVM(sx_ty); + const name_z = try self.allocator.dupeZ(u8, vd.name); + const global = c.LLVMAddGlobal(self.module, llvm_ty, name_z.ptr); + // Initialize with undef (will be set at runtime, e.g. by load_gl) + c.LLVMSetInitializer(global, c.LLVMGetUndef(llvm_ty)); + // NOT constant — this is a mutable global + c.LLVMSetGlobalConstant(global, 0); + + try self.global_mutable_vars.put(vd.name, .{ .ptr = global, .ty = sx_ty }); + } + fn bindParam(self: *CodeGen, function: c.LLVMValueRef, name: []const u8, sx_ty: Type, param_idx: u32) !void { const llvm_ty = self.typeToLLVM(sx_ty); const param_name_z = try self.allocator.dupeZ(u8, name); @@ -2175,6 +2221,34 @@ pub const CodeGen = struct { return null; } + // Function pointer typed variable + if (sx_ty.isFunctionType()) { + const llvm_ty = c.LLVMPointerTypeInContext(self.context, 0); + const name_z = try self.allocator.dupeZ(u8, vd.name); + const alloca = self.buildEntryBlockAlloca(llvm_ty, name_z.ptr); + + if (vd.value == null) { + _ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(llvm_ty), alloca); + } else if (vd.value.?.data == .undef_literal) { + _ = c.LLVMBuildStore(self.builder, c.LLVMGetUndef(llvm_ty), alloca); + } else if (vd.value.?.data == .unary_op and vd.value.?.data.unary_op.op == .xx) { + // xx cast: e.g. xx SDL_GL_GetProcAddress("glClear") + const inner = vd.value.?.data.unary_op.operand; + const val = try self.genExpr(inner); + const src_ty = self.inferType(inner); + const converted = self.convertValue(val, src_ty, sx_ty); + _ = c.LLVMBuildStore(self.builder, converted, alloca); + } else { + // Direct assignment: identifier (function name) or other expression + const val = try self.genExpr(vd.value.?); + _ = c.LLVMBuildStore(self.builder, val, alloca); + } + + try self.saveShadowed(vd.name); + try self.named_values.put(vd.name, .{ .ptr = alloca, .ty = sx_ty }); + return null; + } + // Guard: void type cannot be allocated (would crash LLVM) if (sx_ty == .void_type) { return self.emitErrorFmt("cannot declare variable '{s}' with void type", .{vd.name}); @@ -2261,6 +2335,26 @@ pub const CodeGen = struct { return null; } + // Function pointer typed constant + if (sx_ty.isFunctionType()) { + const llvm_ty = c.LLVMPointerTypeInContext(self.context, 0); + const name_z = try self.allocator.dupeZ(u8, cd.name); + const alloca = self.buildEntryBlockAlloca(llvm_ty, name_z.ptr); + if (cd.value.data == .unary_op and cd.value.data.unary_op.op == .xx) { + const inner = cd.value.data.unary_op.operand; + const val = try self.genExpr(inner); + const src_inner_ty = self.inferType(inner); + const converted = self.convertValue(val, src_inner_ty, sx_ty); + _ = c.LLVMBuildStore(self.builder, converted, alloca); + } else { + const val = try self.genExpr(cd.value); + _ = c.LLVMBuildStore(self.builder, val, alloca); + } + try self.saveShadowed(cd.name); + try self.named_values.put(cd.name, .{ .ptr = alloca, .ty = sx_ty }); + return null; + } + const enum_name: ?[]const u8 = if (sx_ty.isEnum()) sx_ty.enum_type else null; const init_val = if (cd.value.data == .enum_literal and enum_name != null) self.genEnumLiteral(cd.value.data.enum_literal.name, enum_name.?) @@ -2313,7 +2407,8 @@ pub const CodeGen = struct { // Target must be an identifier if (asgn.target.data != .identifier) return self.emitError("assignment target must be a variable"); const name = asgn.target.data.identifier.name; - const entry = self.named_values.get(name) orelse { + const entry = self.named_values.get(name) orelse + self.global_mutable_vars.get(name) orelse { return self.emitErrorFmt("undefined variable '{s}'", .{name}); }; @@ -2339,6 +2434,21 @@ pub const CodeGen = struct { return self.emitErrorFmt("cannot assign non-type value to Type variable '{s}'", .{name}); } + // Function pointer reassignment + if (entry.ty.isFunctionType() and asgn.op == .assign) { + if (asgn.value.data == .unary_op and asgn.value.data.unary_op.op == .xx) { + const inner = asgn.value.data.unary_op.operand; + const val = try self.genExpr(inner); + const src_ty = self.inferType(inner); + const converted = self.convertValue(val, src_ty, entry.ty); + _ = c.LLVMBuildStore(self.builder, converted, entry.ptr); + } else { + const val = try self.genExpr(asgn.value); + _ = c.LLVMBuildStore(self.builder, val, entry.ptr); + } + return null; + } + // Union reassignment: s = .circle(3.14) or s = .none if (entry.ty.isUnion() and asgn.op == .assign) { const new_alloca = try self.genExprAsType(asgn.value, entry.ty); @@ -2556,6 +2666,25 @@ pub const CodeGen = struct { const llvm_ty = self.typeToLLVM(ct.ty); return c.LLVMBuildLoad2(self.builder, llvm_ty, ct.global, "ct_load"); } + // Fall back to global mutable variables (e.g. function pointers from opengl.sx) + if (self.global_mutable_vars.get(ident.name)) |entry| { + const llvm_ty = self.typeToLLVM(entry.ty); + return c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "global_load"); + } + // Fall back to function name → function pointer value + { + const name_z = try self.allocator.dupeZ(u8, ident.name); + var fn_val = c.LLVMGetNamedFunction(self.module, name_z.ptr); + if (fn_val == null) { + // Try qualified name with current namespace + if (self.current_namespace) |ns| { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, ident.name }); + const q_z = try self.allocator.dupeZ(u8, qualified); + fn_val = c.LLVMGetNamedFunction(self.module, q_z.ptr); + } + } + if (fn_val != null) return fn_val.?; + } return self.emitErrorFmt("undefined identifier '{s}'", .{ident.name}); }, .binary_op => |binop| { @@ -3115,6 +3244,11 @@ pub const CodeGen = struct { return self.convertValue(val, src_ty, target_ty); } + // Function pointer target: bypass narrowing check, just produce the pointer value + if (target_ty.isFunctionType()) { + return try self.genExpr(node); + } + // String literal → pointer context: produce raw pointer directly (no {ptr, len} wrapping) if (node.data == .string_literal and target_ty.isPointer()) { const unescaped = try unescapeString(self.allocator, node.data.string_literal.raw); @@ -3431,6 +3565,19 @@ pub const CodeGen = struct { return val; } + // Int → pointer/function_type: IntToPtr (for xx cast from integer to pointer) + if (src_ty.isInt() and (target_ty.isPointer() or target_ty.isManyPointer() or target_ty.isFunctionType())) { + return c.LLVMBuildIntToPtr(self.builder, val, c.LLVMPointerTypeInContext(self.context, 0), "inttoptr"); + } + + // Pointer → function_type or function_type → pointer: both are opaque pointers, no-op + if ((src_ty.isPointer() or src_ty.isManyPointer()) and target_ty.isFunctionType()) { + return val; + } + if (src_ty.isFunctionType() and (target_ty.isPointer() or target_ty.isManyPointer())) { + return val; + } + return val; } @@ -3482,6 +3629,50 @@ pub const CodeGen = struct { ); } + fn genSin(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { + if (call_node.args.len != 1) return self.emitError("sin expects exactly 1 argument"); + const arg_val = try self.genExpr(call_node.args[0]); + const arg_ty = self.inferType(call_node.args[0]); + + const intrinsic_name: [*c]const u8 = if (std.meta.eql(arg_ty, Type.f64)) "llvm.sin.f64" else "llvm.sin.f32"; + const llvm_float_ty = if (std.meta.eql(arg_ty, Type.f64)) + c.LLVMDoubleTypeInContext(self.context) + else + c.LLVMFloatTypeInContext(self.context); + + var intrinsic_fn = c.LLVMGetNamedFunction(self.module, intrinsic_name); + if (intrinsic_fn == null) { + var param_types = [_]c.LLVMTypeRef{llvm_float_ty}; + const fn_type = c.LLVMFunctionType(llvm_float_ty, ¶m_types, 1, 0); + intrinsic_fn = c.LLVMAddFunction(self.module, intrinsic_name, fn_type); + } + + var args = [_]c.LLVMValueRef{arg_val}; + return c.LLVMBuildCall2(self.builder, c.LLVMGlobalGetValueType(intrinsic_fn.?), intrinsic_fn.?, &args, 1, "sin"); + } + + fn genCos(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { + if (call_node.args.len != 1) return self.emitError("cos expects exactly 1 argument"); + const arg_val = try self.genExpr(call_node.args[0]); + const arg_ty = self.inferType(call_node.args[0]); + + const intrinsic_name: [*c]const u8 = if (std.meta.eql(arg_ty, Type.f64)) "llvm.cos.f64" else "llvm.cos.f32"; + const llvm_float_ty = if (std.meta.eql(arg_ty, Type.f64)) + c.LLVMDoubleTypeInContext(self.context) + else + c.LLVMFloatTypeInContext(self.context); + + var intrinsic_fn = c.LLVMGetNamedFunction(self.module, intrinsic_name); + if (intrinsic_fn == null) { + var param_types = [_]c.LLVMTypeRef{llvm_float_ty}; + const fn_type = c.LLVMFunctionType(llvm_float_ty, ¶m_types, 1, 0); + intrinsic_fn = c.LLVMAddFunction(self.module, intrinsic_name, fn_type); + } + + var args = [_]c.LLVMValueRef{arg_val}; + return c.LLVMBuildCall2(self.builder, c.LLVMGlobalGetValueType(intrinsic_fn.?), intrinsic_fn.?, &args, 1, "cos"); + } + fn genSizeOf(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { if (call_node.args.len != 1) return self.emitError("size_of expects exactly 1 argument"); const ty = self.resolveType(call_node.args[0]); @@ -4318,6 +4509,12 @@ pub const CodeGen = struct { if (std.mem.eql(u8, callee_name, "sqrt")) { return self.genSqrt(call_node); } + if (std.mem.eql(u8, callee_name, "sin")) { + return self.genSin(call_node); + } + if (std.mem.eql(u8, callee_name, "cos")) { + return self.genCos(call_node); + } if (std.mem.eql(u8, callee_name, "cast")) { return self.genCast(call_node); } @@ -4349,7 +4546,17 @@ pub const CodeGen = struct { callee_fn = c.LLVMGetNamedFunction(self.module, qualified_z.ptr); } } - if (callee_fn == null) return self.emitErrorFmt("undefined function '{s}'", .{callee_name}); + // Function pointer indirect call: callee is a variable with function_type + if (callee_fn == null) { + const fp_entry = if (self.named_values.get(callee_name)) |e| e + else self.global_mutable_vars.get(callee_name); + if (fp_entry) |entry| { + if (entry.ty.isFunctionType()) { + return self.genIndirectCall(entry, call_node); + } + } + return self.emitErrorFmt("undefined function '{s}'", .{callee_name}); + } // Get function type (opaque pointers: use LLVMGlobalGetValueType) const fn_type = c.LLVMGlobalGetValueType(callee_fn.?); @@ -4511,6 +4718,48 @@ pub const CodeGen = struct { ); } + fn genIndirectCall(self: *CodeGen, entry: NamedValue, call_node: ast.Call) !c.LLVMValueRef { + const fti = entry.ty.function_type; + + // Load the function pointer from the alloca + const ptr_ty = c.LLVMPointerTypeInContext(self.context, 0); + const fn_ptr = c.LLVMBuildLoad2(self.builder, ptr_ty, entry.ptr, "fn_ptr"); + + // Build LLVM function type from FunctionTypeInfo + var param_llvm_types: [64]c.LLVMTypeRef = undefined; + for (fti.param_types, 0..) |pt, i| { + param_llvm_types[i] = self.typeToLLVM(pt); + } + const ret_llvm = self.typeToLLVM(fti.return_type.*); + const fn_type = c.LLVMFunctionType( + ret_llvm, + if (fti.param_types.len > 0) ¶m_llvm_types else null, + @intCast(fti.param_types.len), + 0, + ); + + // Generate arguments with type conversion + var arg_vals = std.ArrayList(c.LLVMValueRef).empty; + for (call_node.args, 0..) |arg, i| { + if (i < fti.param_types.len) { + try arg_vals.append(self.allocator, try self.genExprAsType(arg, fti.param_types[i])); + } else { + try arg_vals.append(self.allocator, try self.genExpr(arg)); + } + } + const args_slice = try arg_vals.toOwnedSlice(self.allocator); + + const call_name: [*c]const u8 = if (ret_llvm == c.LLVMVoidTypeInContext(self.context)) "" else "calltmp"; + return c.LLVMBuildCall2( + self.builder, + fn_type, + fn_ptr, + if (args_slice.len > 0) args_slice.ptr else null, + @intCast(args_slice.len), + call_name, + ); + } + fn genGenericCall(self: *CodeGen, qualified_name: []const u8, template: GenericTemplate, call_node: ast.Call) !c.LLVMValueRef { const fd = template.fd; @@ -5690,6 +5939,8 @@ pub const CodeGen = struct { const base = if (std.mem.lastIndexOfScalar(u8, name, '.')) |idx| name[idx + 1 ..] else name; if (std.mem.eql(u8, base, "write")) return self.genWriteCall(call_node.args); if (std.mem.eql(u8, base, "sqrt")) return self.genSqrt(call_node); + if (std.mem.eql(u8, base, "sin")) return self.genSin(call_node); + if (std.mem.eql(u8, base, "cos")) return self.genCos(call_node); if (std.mem.eql(u8, base, "size_of")) return self.genSizeOf(call_node); if (std.mem.eql(u8, base, "cast")) return self.genCast(call_node); if (std.mem.eql(u8, base, "alloc")) return self.genAlloc(call_node.args); @@ -5871,6 +6122,7 @@ pub const CodeGen = struct { .identifier => |ident| { if (self.named_values.get(ident.name)) |entry| return entry.ty; if (self.comptime_globals.get(ident.name)) |ct| return ct.ty; + if (self.global_mutable_vars.get(ident.name)) |entry| return entry.ty; return Type.s(64); }, .if_expr => |ie| { @@ -5915,8 +6167,11 @@ pub const CodeGen = struct { } const callee_name = self.resolveCalleeName(call_node) orelse return Type.s(64); const base_name = if (std.mem.lastIndexOfScalar(u8, callee_name, '.')) |idx| callee_name[idx + 1 ..] else callee_name; - // Built-in: sqrt returns same type as argument - if (std.mem.eql(u8, base_name, "sqrt")) { + // Built-in: sqrt/sin/cos returns same type as argument + if (std.mem.eql(u8, base_name, "sqrt") or + std.mem.eql(u8, base_name, "sin") or + std.mem.eql(u8, base_name, "cos")) + { if (call_node.args.len > 0) return self.inferType(call_node.args[0]); return .f32; } @@ -5985,13 +6240,19 @@ pub const CodeGen = struct { } return Type.s(64); } - // Check non-generic LLVM functions + // Check declared return types (preserves signedness) + if (self.function_return_types.get(callee_name)) |ret_ty| return ret_ty; + if (self.current_namespace) |ns| { + const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }) catch return Type.s(64); + if (self.function_return_types.get(qualified)) |ret_ty| return ret_ty; + } + // Fallback: check non-generic LLVM functions const callee_name_z = self.allocator.dupeZ(u8, callee_name) catch return Type.s(64); var callee_fn_opt = c.LLVMGetNamedFunction(self.module, callee_name_z.ptr); // Intra-namespace fallback if (callee_fn_opt == null) { - if (self.current_namespace) |ns| { - const q = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }) catch return Type.s(64); + if (self.current_namespace) |ns2| { + const q = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns2, callee_name }) catch return Type.s(64); const qz = self.allocator.dupeZ(u8, q) catch return Type.s(64); callee_fn_opt = c.LLVMGetNamedFunction(self.module, qz.ptr); } @@ -6001,6 +6262,16 @@ pub const CodeGen = struct { const ret_llvm = c.LLVMGetReturnType(fn_type); return self.llvmTypeToSxType(ret_llvm); } + // Check if callee is a variable with function pointer type + { + const fp_entry = if (self.named_values.get(callee_name)) |e| e + else self.global_mutable_vars.get(callee_name); + if (fp_entry) |entry| { + if (entry.ty.isFunctionType()) { + return entry.ty.function_type.return_type.*; + } + } + } return Type.s(64); }, .unary_op => |unop| { diff --git a/src/parser.zig b/src/parser.zig index ebed879..5e9b572 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -292,6 +292,28 @@ pub const Parser = struct { self.advance(); return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = true } }); } + // Function pointer type: (ParamTypes) -> ReturnType + if (self.current.tag == .l_paren) { + self.advance(); // skip '(' + var param_types = std.ArrayList(*Node).empty; + while (self.current.tag != .r_paren and self.current.tag != .eof) { + if (param_types.items.len > 0) { + try self.expect(.comma); + } + try param_types.append(self.allocator, try self.parseTypeExpr()); + } + try self.expect(.r_paren); + var return_type: ?*Node = null; + if (self.current.tag == .arrow) { + self.advance(); // skip '->' + return_type = try self.parseTypeExpr(); + } + return try self.createNode(start, .{ .function_type_expr = .{ + .param_types = try param_types.toOwnedSlice(self.allocator), + .return_type = return_type, + } }); + } + if (self.current.tag.isTypeKeyword() or self.current.tag == .identifier) { var name = self.tokenSlice(self.current); self.advance(); diff --git a/src/sema.zig b/src/sema.zig index f4da109..f40f152 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -245,6 +245,21 @@ pub const Analyzer = struct { const elem_name = elem_type.displayName(self.allocator) catch return .void_type; return .{ .many_pointer_type = .{ .element_name = elem_name } }; } + // Function pointer type: (ParamTypes) -> ReturnType + if (tn.data == .function_type_expr) { + const fte = tn.data.function_type_expr; + var param_types = std.ArrayList(Type).empty; + for (fte.param_types) |pt| { + param_types.append(self.allocator, self.resolveTypeNode(pt)) catch return .void_type; + } + const ret_ty = if (fte.return_type) |rt| self.resolveTypeNode(rt) else Type.void_type; + const ret_ptr = self.allocator.create(Type) catch return .void_type; + ret_ptr.* = ret_ty; + return .{ .function_type = .{ + .param_types = param_types.toOwnedSlice(self.allocator) catch return .void_type, + .return_type = ret_ptr, + } }; + } // Sema does not resolve generics; codegen handles instantiation if (tn.data == .parameterized_type_expr) { return .void_type; @@ -320,9 +335,12 @@ pub const Analyzer = struct { if (self.fn_signatures.get(callee_name)) |sig| { return sig.return_type; } - // Built-in: sqrt returns same type as argument + // Built-in: sqrt/sin/cos returns same type as argument const base = if (std.mem.lastIndexOfScalar(u8, callee_name, '.')) |idx| callee_name[idx + 1 ..] else callee_name; - if (std.mem.eql(u8, base, "sqrt")) { + if (std.mem.eql(u8, base, "sqrt") or + std.mem.eql(u8, base, "sin") or + std.mem.eql(u8, base, "cos")) + { if (call_node.args.len > 0) return self.inferExprType(call_node.args[0]); return .f32; } @@ -673,6 +691,7 @@ pub const Analyzer = struct { .builtin_expr, .foreign_expr, .library_decl, + .function_type_expr, .import_decl, .array_type_expr, .slice_type_expr, @@ -929,6 +948,7 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node { .builtin_expr, .foreign_expr, .library_decl, + .function_type_expr, .enum_decl, .struct_decl, .union_decl, diff --git a/src/types.zig b/src/types.zig index 2700546..48f6a9d 100644 --- a/src/types.zig +++ b/src/types.zig @@ -21,6 +21,7 @@ pub const Type = union(enum) { pointer_type: PointerTypeInfo, many_pointer_type: ManyPointerTypeInfo, vector_type: VectorTypeInfo, + function_type: FunctionTypeInfo, any_type, meta_type: MetaTypeInfo, @@ -36,6 +37,11 @@ pub const Type = union(enum) { element_name: []const u8, }; + pub const FunctionTypeInfo = struct { + param_types: []const Type, + return_type: *const Type, + }; + pub const ArrayTypeInfo = struct { element_name: []const u8, length: u32, @@ -182,6 +188,13 @@ pub const Type = union(enum) { }; } + pub fn isFunctionType(self: Type) bool { + return switch (self) { + .function_type => true, + else => false, + }; + } + pub fn isArray(self: Type) bool { return switch (self) { .array_type => true, @@ -235,6 +248,7 @@ pub const Type = union(enum) { .f32 => 32, .f64 => 64, .boolean => 1, + .pointer_type, .many_pointer_type, .function_type => 64, else => 0, }; } @@ -382,6 +396,20 @@ pub const Type = union(enum) { try buf.append(allocator, ')'); return try buf.toOwnedSlice(allocator); }, + .function_type => |info| { + var buf = std.ArrayList(u8).empty; + try buf.append(allocator, '('); + for (info.param_types, 0..) |pt, i| { + if (i > 0) try buf.appendSlice(allocator, ", "); + try buf.appendSlice(allocator, try pt.displayName(allocator)); + } + try buf.append(allocator, ')'); + if (!std.meta.eql(info.return_type.*, Type.void_type)) { + try buf.appendSlice(allocator, " -> "); + try buf.appendSlice(allocator, try info.return_type.displayName(allocator)); + } + return try buf.toOwnedSlice(allocator); + }, .meta_type => |info| info.name, }; }