From 2930e2cf6c5f1e8e4d00e76d6dccbfae5a0514c0 Mon Sep 17 00:00:00 2001 From: Sublime Tshimpangila Date: Sun, 12 Jul 2026 17:59:49 -0400 Subject: [PATCH 01/11] add parsing of var in parse_expr --- src/ast.zig | 6 ++++++ src/parser.zig | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/ast.zig b/src/ast.zig index ba11640..de640e5 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -94,6 +94,7 @@ const ExprTag = enum { bool_, fn_call, str, + var_, }; pub const Expr = union(ExprTag) { @@ -103,6 +104,7 @@ pub const Expr = union(ExprTag) { bool_: BoolExpr, fn_call: FnCallExpr, str: []const u8, + var_: []const u8, pub fn create_fn_call(name: []const u8, args: std.ArrayList(Expr)) Expr { return .{ .fn_call = .{ @@ -115,6 +117,10 @@ pub const Expr = union(ExprTag) { return .{ .str = content }; } + pub fn create_var(name: []const u8) Expr { + return .{ .var_ = name }; + } + pub fn print(self: Self) void { switch (self) { .str => |str| std.debug.print("\"{s}\"", .{str}), diff --git a/src/parser.zig b/src/parser.zig index 8fd4c16..6de9487 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -164,6 +164,9 @@ fn parse_expr(l: *Lexer, alloc: Allocator) error{OutOfMemory}!Expr { // fn call return parse_fn_call_expr(l, alloc); } + const name = l.name.as_str(l.content); + l.eat(.id); + return Expr.create_var(name); } else if (l.token == .str) { return parse_str(l); } From 6114e514f105e965fe1fcd64db685e20292db87e Mon Sep 17 00:00:00 2001 From: Sublime Tshimpangila Date: Sun, 12 Jul 2026 18:02:03 -0400 Subject: [PATCH 02/11] add print for var_ expression --- src/ast.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ast.zig b/src/ast.zig index de640e5..58dfdf8 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -125,6 +125,7 @@ pub const Expr = union(ExprTag) { switch (self) { .str => |str| std.debug.print("\"{s}\"", .{str}), .fn_call => |fn_call| fn_call.print(), + .var_ => |var_| std.debug.print("{s}", .{var_}), else => panic("print unimplemented for {}", .{std.meta.activeTag(self)}), } } From 20f6a64ed23386ce75919915d7cb246ee5ce13e7 Mon Sep 17 00:00:00 2001 From: Sublime Tshimpangila Date: Sun, 12 Jul 2026 18:02:17 -0400 Subject: [PATCH 03/11] add example for sema analysis --- examples/sema/use-undeclared-var.sub | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 examples/sema/use-undeclared-var.sub diff --git a/examples/sema/use-undeclared-var.sub b/examples/sema/use-undeclared-var.sub new file mode 100644 index 0000000..bee6332 --- /dev/null +++ b/examples/sema/use-undeclared-var.sub @@ -0,0 +1,4 @@ +fn main() void { + var a: i32 = "bonjour"; + printf(a); +} From a05ce2577079f98484bec2f6eb8dc72538c3e33a Mon Sep 17 00:00:00 2001 From: Sublime Tshimpangila Date: Mon, 13 Jul 2026 14:44:37 -0400 Subject: [PATCH 04/11] add sema for if and expr --- examples/sema/use-undeclared-var.sub | 9 +++- src/ast.zig | 2 +- src/main.zig | 3 ++ src/sema.zig | 74 ++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 src/sema.zig diff --git a/examples/sema/use-undeclared-var.sub b/examples/sema/use-undeclared-var.sub index bee6332..5445876 100644 --- a/examples/sema/use-undeclared-var.sub +++ b/examples/sema/use-undeclared-var.sub @@ -1,4 +1,9 @@ fn main() void { - var a: i32 = "bonjour"; - printf(a); + var b: i32 = "bonjour"; + if b { + var a: i32 = "in if"; + printf(b); + printf(a); + } + printf(a); } diff --git a/src/ast.zig b/src/ast.zig index 58dfdf8..5f65d91 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -51,7 +51,7 @@ pub const ProgramDecl = union(ProgramDeclTag) { } }; -const FnDecl = struct { +pub const FnDecl = struct { const Self = @This(); name: []const u8, diff --git a/src/main.zig b/src/main.zig index ea6b467..84132b1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,10 +2,12 @@ const std = @import("std"); const lexer_pkg = @import("lexer.zig"); const parser_pkg = @import("parser.zig"); +const sema_pkg = @import("sema.zig"); const Lexer = lexer_pkg.Lexer; const parse = parser_pkg.parse; +const sema = sema_pkg.sema; const LIMIT = 1024 * 10; @@ -33,6 +35,7 @@ pub fn main(init: std.process.Init) !void { ); const ast = try parse(&l, alloc); + try sema(alloc, &ast); ast.print(); diff --git a/src/sema.zig b/src/sema.zig new file mode 100644 index 0000000..771abb2 --- /dev/null +++ b/src/sema.zig @@ -0,0 +1,74 @@ +const std = @import("std"); + +const Allocator = std.mem.Allocator; + +const ast_pkg = @import("ast.zig"); + +const Ast = ast_pkg.Ast; +const FnDecl = ast_pkg.FnDecl; +const Arg = ast_pkg.Arg; +const Expr = ast_pkg.Expr; +const BlockStmt = ast_pkg.BlockStmt; + +const panic = std.debug.panic; + +pub fn sema(alloc: Allocator, ast: *const Ast) !void { + // undeclared var + + for (ast.decls.items) |decl| { + switch(decl) { + .fn_decl => |fn_decl| try sema_fn_decl(alloc, fn_decl), + } + } +} + +fn sema_fn_decl(alloc: Allocator, fn_decl: FnDecl) !void { + var decl_vars: std.ArrayList(Arg) = .empty; + defer decl_vars.deinit(alloc); + + try decl_vars.appendSlice(alloc, fn_decl.args.items); + + try sema_block(alloc, fn_decl.body, &decl_vars); +} + +fn sema_block(alloc: Allocator, block: BlockStmt, decl_vars: *std.ArrayList(Arg)) !void { + const length = decl_vars.items.len; + // remove added elements on out + defer decl_vars.items.len = length; + for (block.stmts.items) |stmt| { + switch (stmt) { + .assign => |assign| { + const arg: Arg = .{ .name = assign.var_, .type_ = assign.type_ ,}; + try decl_vars.append(alloc, arg); + }, + .no_assign => |no_assign| sema_expr(no_assign.value, decl_vars), + .if_ => |if_| { + sema_expr(if_.if_eval, decl_vars); + try sema_block(alloc, if_.if_body, decl_vars); + }, + } + } +} + +fn sema_expr(expr: Expr, decl_vars: *std.ArrayList(Arg)) void { + switch(expr) { + .fn_call => |fn_call| { + for (fn_call.args.items) |arg| { + sema_expr(arg, decl_vars); + } + }, + .var_ => |var_| { + if (!contains(decl_vars.items, var_)) { + panic("use of undeclared var: {s}", .{var_}); + } + }, + .arith, .bool_, .str => unreachable, + } +} + +fn contains(args: []const Arg, needle: []const u8) bool { + for (args) |arg| { + if (std.mem.eql(u8, arg.name, needle)) return true; + } + return false; +} From cfd7d652243ee4c7ba4a2944080db18efdd0dce1 Mon Sep 17 00:00:00 2001 From: Sublime Tshimpangila Date: Mon, 13 Jul 2026 20:40:00 -0400 Subject: [PATCH 05/11] add filepath and cursor to expr --- src/ast.zig | 71 ++++++++++++++++++++++++++++++++++++++------------ src/lexer.zig | 21 ++++++++------- src/parser.zig | 14 +++++++--- src/sema.zig | 29 +++++++++++++++++---- 4 files changed, 101 insertions(+), 34 deletions(-) diff --git a/src/ast.zig b/src/ast.zig index 5f65d91..e29b10d 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -1,5 +1,9 @@ const std = @import("std"); +const lexer_pkg = @import("lexer.zig"); + +const Cursor = lexer_pkg.Cursor; + const panic = std.debug.panic; const assert = std.debug.assert; @@ -97,40 +101,73 @@ const ExprTag = enum { var_, }; -pub const Expr = union(ExprTag) { +pub const Expr = struct { const Self = @This(); - arith: ArithExpr, - bool_: BoolExpr, - fn_call: FnCallExpr, - str: []const u8, - var_: []const u8, + cursor: Cursor, + filepath: []const u8, + as: ExprAs, - pub fn create_fn_call(name: []const u8, args: std.ArrayList(Expr)) Expr { - return .{ .fn_call = .{ - .name = name, - .args = args, - } }; + pub fn create_fn_call( + name: []const u8, + args: std.ArrayList(Expr), + filepath: []const u8, + cursor: Cursor, + ) Expr { + return .{ + .as = .{ .fn_call = .{ + .name = name, + .args = args, + } }, + .filepath = filepath, + .cursor = cursor, + }; } - pub fn create_str(content: []const u8) Expr { - return .{ .str = content }; + pub fn create_str( + content: []const u8, + filepath: []const u8, + cursor: Cursor, + ) Expr { + return .{ + .as = .{ .str = content }, + .filepath = filepath, + .cursor = cursor, + }; } - pub fn create_var(name: []const u8) Expr { - return .{ .var_ = name }; + pub fn create_var( + name: []const u8, + filepath: []const u8, + cursor: Cursor, + ) Expr { + return .{ + .as = .{ .var_ = name }, + .filepath = filepath, + .cursor = cursor, + }; } pub fn print(self: Self) void { - switch (self) { + switch (self.as) { .str => |str| std.debug.print("\"{s}\"", .{str}), .fn_call => |fn_call| fn_call.print(), .var_ => |var_| std.debug.print("{s}", .{var_}), - else => panic("print unimplemented for {}", .{std.meta.activeTag(self)}), + else => panic("print unimplemented for {}", .{std.meta.activeTag(self.as)}), } } }; +pub const ExprAs = union(ExprTag) { + const Self = @This(); + + arith: ArithExpr, + bool_: BoolExpr, + fn_call: FnCallExpr, + str: []const u8, + var_: []const u8, +}; + const ArithExpr = struct { value: i32, }; diff --git a/src/lexer.zig b/src/lexer.zig index 30be49b..1992940 100644 --- a/src/lexer.zig +++ b/src/lexer.zig @@ -69,7 +69,7 @@ pub const Lexer = struct { l.nexti(); } - fn current_line(l: *Lexer) []const u8 { + pub fn current_line(l: *Lexer) []const u8 { var begin: usize = l.cursor.pos; var end: usize = l.cursor.pos; @@ -85,14 +85,17 @@ pub const Lexer = struct { const END_TAG = "\x1b[0m"; if (l.token != expected) { const panic_line = l.current_line(); - std.debug.print("\n" ++ GREEN_TAG ++ "{s}:{}:{}" ++ END_TAG ++ " expected this `{s}`, found: `{s}` in line\n{s}\n", .{ - l.file_path, - l.cursor.row + 1, - l.cursor.col, - expected.get_str(), - l.name.as_str(l.content), - panic_line, - }); + std.debug.print( + "\n" ++ GREEN_TAG ++ "{s}:{}:{}" ++ END_TAG ++ " expected this `{s}`, found: `{s}` in line\n{s}\n", + .{ + l.file_path, + l.cursor.row + 1, + l.cursor.col, + expected.get_str(), + l.name.as_str(l.content), + panic_line, + }, + ); for (0..l.cursor.col - 1) |_| { std.debug.print(" ", .{}); diff --git a/src/parser.zig b/src/parser.zig index 6de9487..8a3caa0 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -166,7 +166,11 @@ fn parse_expr(l: *Lexer, alloc: Allocator) error{OutOfMemory}!Expr { } const name = l.name.as_str(l.content); l.eat(.id); - return Expr.create_var(name); + return Expr.create_var( + name, + l.file_path, + l.cursor, + ); } else if (l.token == .str) { return parse_str(l); } @@ -176,7 +180,11 @@ fn parse_expr(l: *Lexer, alloc: Allocator) error{OutOfMemory}!Expr { fn parse_str(l: *Lexer) !Expr { const raw_str = l.name.as_str(l.content); l.eat(.str); - return Expr.create_str(raw_str); + return Expr.create_str( + raw_str, + l.file_path, + l.cursor, + ); } fn parse_fn_call_expr(l: *Lexer, alloc: Allocator) !Expr { @@ -197,5 +205,5 @@ fn parse_fn_call_expr(l: *Lexer, alloc: Allocator) !Expr { } l.eat(.cparen); - return Expr.create_fn_call(fn_name, args); + return Expr.create_fn_call(fn_name, args, l.file_path, l.cursor); } diff --git a/src/sema.zig b/src/sema.zig index 771abb2..18450fb 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const Allocator = std.mem.Allocator; @@ -16,7 +17,7 @@ pub fn sema(alloc: Allocator, ast: *const Ast) !void { // undeclared var for (ast.decls.items) |decl| { - switch(decl) { + switch (decl) { .fn_decl => |fn_decl| try sema_fn_decl(alloc, fn_decl), } } @@ -38,7 +39,10 @@ fn sema_block(alloc: Allocator, block: BlockStmt, decl_vars: *std.ArrayList(Arg) for (block.stmts.items) |stmt| { switch (stmt) { .assign => |assign| { - const arg: Arg = .{ .name = assign.var_, .type_ = assign.type_ ,}; + const arg: Arg = .{ + .name = assign.var_, + .type_ = assign.type_, + }; try decl_vars.append(alloc, arg); }, .no_assign => |no_assign| sema_expr(no_assign.value, decl_vars), @@ -51,7 +55,7 @@ fn sema_block(alloc: Allocator, block: BlockStmt, decl_vars: *std.ArrayList(Arg) } fn sema_expr(expr: Expr, decl_vars: *std.ArrayList(Arg)) void { - switch(expr) { + switch (expr.as) { .fn_call => |fn_call| { for (fn_call.args.items) |arg| { sema_expr(arg, decl_vars); @@ -59,10 +63,25 @@ fn sema_expr(expr: Expr, decl_vars: *std.ArrayList(Arg)) void { }, .var_ => |var_| { if (!contains(decl_vars.items, var_)) { - panic("use of undeclared var: {s}", .{var_}); + if (builtin.mode == .Debug and false) { + panic("{s}:{}:{} use of undeclared var: {s}", .{ + expr.filepath, + expr.cursor.row, + expr.cursor.col, + var_, + }); + } else { + std.debug.print("{s}:{}:{} use of undeclared var: {s}\n", .{ + expr.filepath, + expr.cursor.row, + expr.cursor.col, + var_, + }); + std.process.exit(1); + } } }, - .arith, .bool_, .str => unreachable, + .arith, .bool_, .str => {}, } } From 5b6af9af769d2a03a44f65d39cea459c0f3429b6 Mon Sep 17 00:00:00 2001 From: Sublime Tshimpangila Date: Mon, 13 Jul 2026 21:27:18 -0400 Subject: [PATCH 06/11] add declaration for undeclared var error --- src/ast.zig | 21 ++++++++++++++------- src/errors.zig | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/parser.zig | 8 +++++--- src/sema.zig | 28 +++++++++++++--------------- 4 files changed, 77 insertions(+), 25 deletions(-) create mode 100644 src/errors.zig diff --git a/src/ast.zig b/src/ast.zig index e29b10d..bf04a51 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -105,46 +105,53 @@ pub const Expr = struct { const Self = @This(); cursor: Cursor, - filepath: []const u8, + file_path: []const u8, + file_content: []const u8, as: ExprAs, pub fn create_fn_call( name: []const u8, args: std.ArrayList(Expr), - filepath: []const u8, + file_path: []const u8, cursor: Cursor, + file_content: []const u8, ) Expr { return .{ .as = .{ .fn_call = .{ .name = name, .args = args, } }, - .filepath = filepath, + .file_path = file_path, .cursor = cursor, + .file_content = file_content, }; } pub fn create_str( content: []const u8, - filepath: []const u8, + file_path: []const u8, cursor: Cursor, + file_content: []const u8, ) Expr { return .{ .as = .{ .str = content }, - .filepath = filepath, + .file_path = file_path, .cursor = cursor, + .file_content = file_content, }; } pub fn create_var( name: []const u8, - filepath: []const u8, + file_path: []const u8, cursor: Cursor, + file_content: []const u8, ) Expr { return .{ .as = .{ .var_ = name }, - .filepath = filepath, + .file_path = file_path, .cursor = cursor, + .file_content = file_content, }; } diff --git a/src/errors.zig b/src/errors.zig new file mode 100644 index 0000000..4399cbe --- /dev/null +++ b/src/errors.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +const lexer_pkg = @import("lexer.zig"); + +const Cursor = lexer_pkg.Cursor; + +const panic = std.debug.panic; + +pub fn print_error_line(file_path: []const u8, content: []const u8, cursor: Cursor) void { + var begin: usize = cursor.pos; + var end: usize = cursor.pos; + + var nb_line: usize = 3; + while (true) { + if (begin == 0) break; + if (content[begin] == '\n' and nb_line == 0) break; + if (content[begin] == '\n') nb_line -= 1; + begin -= 1; + } + while (begin > 0 and content[begin] != '\n') begin -= 1; + + while (end < content.len and content[end] != '\n') end += 1; + + const content_error = content[begin..end]; + // std.debug.print("{s}\n", .{content_error}); + + const GREEN_TAG = "\x1b[32m"; + const RED_TAG = "\x1b[31m"; + const END_TAG = "\x1b[0m"; + std.debug.print( + "\n" ++ GREEN_TAG ++ "{s}:{}:{}" ++ END_TAG ++ " use of undeclared variable in line\n{s}\n", + .{ + file_path, + cursor.row + 1, + cursor.col, + content_error, + }, + ); + + for (0..cursor.col - 1) |_| { + std.debug.print(" ", .{}); + } + std.debug.print(RED_TAG ++ "^" ++ END_TAG ++ "\n", .{}); +} diff --git a/src/parser.zig b/src/parser.zig index 8a3caa0..0478c99 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -169,7 +169,8 @@ fn parse_expr(l: *Lexer, alloc: Allocator) error{OutOfMemory}!Expr { return Expr.create_var( name, l.file_path, - l.cursor, + l.previous_cursor, + l.content, ); } else if (l.token == .str) { return parse_str(l); @@ -183,7 +184,8 @@ fn parse_str(l: *Lexer) !Expr { return Expr.create_str( raw_str, l.file_path, - l.cursor, + l.previous_cursor, + l.content, ); } @@ -205,5 +207,5 @@ fn parse_fn_call_expr(l: *Lexer, alloc: Allocator) !Expr { } l.eat(.cparen); - return Expr.create_fn_call(fn_name, args, l.file_path, l.cursor); + return Expr.create_fn_call(fn_name, args, l.file_path, l.previous_cursor, l.content); } diff --git a/src/sema.zig b/src/sema.zig index 18450fb..fa9cb08 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -1,9 +1,10 @@ const std = @import("std"); const builtin = @import("builtin"); -const Allocator = std.mem.Allocator; - const ast_pkg = @import("ast.zig"); +const errors_pkg = @import("errors.zig"); + +const Allocator = std.mem.Allocator; const Ast = ast_pkg.Ast; const FnDecl = ast_pkg.FnDecl; @@ -12,6 +13,7 @@ const Expr = ast_pkg.Expr; const BlockStmt = ast_pkg.BlockStmt; const panic = std.debug.panic; +const print_error_line = errors_pkg.print_error_line; pub fn sema(alloc: Allocator, ast: *const Ast) !void { // undeclared var @@ -63,20 +65,16 @@ fn sema_expr(expr: Expr, decl_vars: *std.ArrayList(Arg)) void { }, .var_ => |var_| { if (!contains(decl_vars.items, var_)) { - if (builtin.mode == .Debug and false) { - panic("{s}:{}:{} use of undeclared var: {s}", .{ - expr.filepath, - expr.cursor.row, - expr.cursor.col, - var_, - }); + std.debug.print("{s}:{}:{} use of undeclared var: {s}", .{ + expr.file_path, + expr.cursor.row, + expr.cursor.col, + var_, + }); + print_error_line(expr.file_path, expr.file_content, expr.cursor); + if (builtin.mode == .Debug) { + panic("", .{}); } else { - std.debug.print("{s}:{}:{} use of undeclared var: {s}\n", .{ - expr.filepath, - expr.cursor.row, - expr.cursor.col, - var_, - }); std.process.exit(1); } } From ca6afb7736593c4fb7aa2012d17a688ab2f248ee Mon Sep 17 00:00:00 2001 From: Sublime Tshimpangila Date: Mon, 20 Jul 2026 11:56:58 -0400 Subject: [PATCH 07/11] add error call to undefined function --- examples/sema/use-undeclared-var.sub | 4 ++ src/errors.zig | 14 +++-- src/sema.zig | 79 ++++++++++++++++++++++------ 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/examples/sema/use-undeclared-var.sub b/examples/sema/use-undeclared-var.sub index 5445876..fe35181 100644 --- a/examples/sema/use-undeclared-var.sub +++ b/examples/sema/use-undeclared-var.sub @@ -7,3 +7,7 @@ fn main() void { } printf(a); } + +fn printf() void { + +} diff --git a/src/errors.zig b/src/errors.zig index 4399cbe..ee41905 100644 --- a/src/errors.zig +++ b/src/errors.zig @@ -7,7 +7,13 @@ const Cursor = lexer_pkg.Cursor; const panic = std.debug.panic; -pub fn print_error_line(file_path: []const u8, content: []const u8, cursor: Cursor) void { +pub fn print_error_line( + comptime fmt: []const u8, + args: anytype, + file_path: []const u8, + content: []const u8, + cursor: Cursor, +) void { var begin: usize = cursor.pos; var end: usize = cursor.pos; @@ -29,15 +35,17 @@ pub fn print_error_line(file_path: []const u8, content: []const u8, cursor: Curs const RED_TAG = "\x1b[31m"; const END_TAG = "\x1b[0m"; std.debug.print( - "\n" ++ GREEN_TAG ++ "{s}:{}:{}" ++ END_TAG ++ " use of undeclared variable in line\n{s}\n", + "\n" ++ GREEN_TAG ++ "{s}:{}:{}" ++ END_TAG ++ " ", .{ file_path, cursor.row + 1, cursor.col, - content_error, }, ); + std.debug.print(fmt ++ "\n", args); + std.debug.print("{s}\n", .{ content_error}); + for (0..cursor.col - 1) |_| { std.debug.print(" ", .{}); } diff --git a/src/sema.zig b/src/sema.zig index fa9cb08..f90a857 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -16,25 +16,43 @@ const panic = std.debug.panic; const print_error_line = errors_pkg.print_error_line; pub fn sema(alloc: Allocator, ast: *const Ast) !void { - // undeclared var + var fn_names: std.ArrayList([]const u8) = .empty; + defer fn_names.deinit(alloc); + // register fn names + for (ast.decls.items) |decl| { + switch (decl) { + .fn_decl => |fn_decl| { + try fn_names.append(alloc, fn_decl.name); + }, + } + } for (ast.decls.items) |decl| { switch (decl) { - .fn_decl => |fn_decl| try sema_fn_decl(alloc, fn_decl), + .fn_decl => |fn_decl| try sema_fn_decl(alloc, fn_decl, fn_names), } } } -fn sema_fn_decl(alloc: Allocator, fn_decl: FnDecl) !void { +fn sema_fn_decl( + alloc: Allocator, + fn_decl: FnDecl, + fn_names: std.ArrayList([]const u8), +) !void { var decl_vars: std.ArrayList(Arg) = .empty; defer decl_vars.deinit(alloc); try decl_vars.appendSlice(alloc, fn_decl.args.items); - try sema_block(alloc, fn_decl.body, &decl_vars); + try sema_block(alloc, fn_decl.body, &decl_vars, fn_names); } -fn sema_block(alloc: Allocator, block: BlockStmt, decl_vars: *std.ArrayList(Arg)) !void { +fn sema_block( + alloc: Allocator, + block: BlockStmt, + decl_vars: *std.ArrayList(Arg), + fn_names: std.ArrayList([]const u8), +) !void { const length = decl_vars.items.len; // remove added elements on out defer decl_vars.items.len = length; @@ -47,31 +65,53 @@ fn sema_block(alloc: Allocator, block: BlockStmt, decl_vars: *std.ArrayList(Arg) }; try decl_vars.append(alloc, arg); }, - .no_assign => |no_assign| sema_expr(no_assign.value, decl_vars), + .no_assign => |no_assign| sema_expr(no_assign.value, decl_vars, fn_names), .if_ => |if_| { - sema_expr(if_.if_eval, decl_vars); - try sema_block(alloc, if_.if_body, decl_vars); + sema_expr(if_.if_eval, decl_vars, fn_names); + try sema_block(alloc, if_.if_body, decl_vars, fn_names); }, } } } -fn sema_expr(expr: Expr, decl_vars: *std.ArrayList(Arg)) void { +fn sema_expr( + expr: Expr, + decl_vars: *std.ArrayList(Arg), + funs: std.ArrayList([]const u8), +) void { switch (expr.as) { .fn_call => |fn_call| { + if (!contains_str(funs.items, fn_call.name)) { + print_error_line( + "call to undefined function: {s}", + .{ + fn_call.name, + }, + expr.file_path, + expr.file_content, + expr.cursor, + ); + if (builtin.mode == .Debug) { + panic("", .{}); + } else { + std.process.exit(1); + } + } for (fn_call.args.items) |arg| { - sema_expr(arg, decl_vars); + sema_expr(arg, decl_vars, funs); } }, .var_ => |var_| { if (!contains(decl_vars.items, var_)) { - std.debug.print("{s}:{}:{} use of undeclared var: {s}", .{ + print_error_line( + "use of undeclared var: {s}", + .{ + var_, + }, expr.file_path, - expr.cursor.row, - expr.cursor.col, - var_, - }); - print_error_line(expr.file_path, expr.file_content, expr.cursor); + expr.file_content, + expr.cursor, + ); if (builtin.mode == .Debug) { panic("", .{}); } else { @@ -83,6 +123,13 @@ fn sema_expr(expr: Expr, decl_vars: *std.ArrayList(Arg)) void { } } +fn contains_str(args: [][]const u8, needle: []const u8) bool { + for (args) |arg| { + if (std.mem.eql(u8, arg, needle)) return true; + } + return false; +} + fn contains(args: []const Arg, needle: []const u8) bool { for (args) |arg| { if (std.mem.eql(u8, arg.name, needle)) return true; From 50136011f8c8d979ae59d9aa4548335c22165930 Mon Sep 17 00:00:00 2001 From: Sublime Tshimpangila Date: Mon, 20 Jul 2026 15:30:16 -0400 Subject: [PATCH 08/11] add a simple assembly program --- increment.s | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 increment.s diff --git a/increment.s b/increment.s new file mode 100644 index 0000000..1aa87ce --- /dev/null +++ b/increment.s @@ -0,0 +1,28 @@ +.intel_syntax noprefix +.global _start + +.text +_start: + push rbp + mov rbp, rsp + sub rsp, 16 + + mov byte ptr [rbp - 1], 'a' + mov byte ptr [rbp - 3], 5 + + movzx eax, byte ptr [rbp - 1] + movzx ecx, byte ptr [rbp - 3] + + add eax, ecx + + mov byte ptr [rbp - 1], al + + mov eax, 1 + mov edi, 1 + lea rsi, [rbp - 1] + mov edx, 1 + syscall + + mov eax, 60 + xor edi, edi + syscall From 4cbd3ab0ec67139a5be7331abe2100415abf536e Mon Sep 17 00:00:00 2001 From: Sublime Tshimpangila Date: Mon, 20 Jul 2026 15:32:39 -0400 Subject: [PATCH 09/11] zig fmt . --- src/errors.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/errors.zig b/src/errors.zig index ee41905..e457241 100644 --- a/src/errors.zig +++ b/src/errors.zig @@ -44,7 +44,7 @@ pub fn print_error_line( ); std.debug.print(fmt ++ "\n", args); - std.debug.print("{s}\n", .{ content_error}); + std.debug.print("{s}\n", .{content_error}); for (0..cursor.col - 1) |_| { std.debug.print(" ", .{}); From c5c0c9393b68840e65b538c36270673a1db5d3b3 Mon Sep 17 00:00:00 2001 From: Sublime Tshimpangila Date: Mon, 20 Jul 2026 15:52:52 -0400 Subject: [PATCH 10/11] return an sema error instead of panic for better error message --- src/main.zig | 10 +++++++++- src/sema.zig | 34 +++++++++++++++++++--------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/main.zig b/src/main.zig index 84132b1..e121041 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5,6 +5,7 @@ const parser_pkg = @import("parser.zig"); const sema_pkg = @import("sema.zig"); const Lexer = lexer_pkg.Lexer; +const SemaErr = sema_pkg.SemaErr; const parse = parser_pkg.parse; const sema = sema_pkg.sema; @@ -35,7 +36,14 @@ pub fn main(init: std.process.Init) !void { ); const ast = try parse(&l, alloc); - try sema(alloc, &ast); + sema(alloc, &ast) catch |err| { + switch (err) { + SemaErr.UndeclaredVar => std.debug.print("Call to undeclared var", .{}), + SemaErr.CallUnknownFunction => std.debug.print("unknowns function", .{}), + SemaErr.OutOfMemory => return SemaErr.OutOfMemory, + } + return; + }; ast.print(); diff --git a/src/sema.zig b/src/sema.zig index f90a857..688ef29 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -15,7 +15,13 @@ const BlockStmt = ast_pkg.BlockStmt; const panic = std.debug.panic; const print_error_line = errors_pkg.print_error_line; -pub fn sema(alloc: Allocator, ast: *const Ast) !void { +pub const SemaErr = error { + OutOfMemory, + CallUnknownFunction, + UndeclaredVar, +}; + +pub fn sema(alloc: Allocator, ast: *const Ast) SemaErr!void { var fn_names: std.ArrayList([]const u8) = .empty; defer fn_names.deinit(alloc); // register fn names @@ -65,9 +71,10 @@ fn sema_block( }; try decl_vars.append(alloc, arg); }, - .no_assign => |no_assign| sema_expr(no_assign.value, decl_vars, fn_names), + .no_assign => |no_assign| try sema_expr(no_assign.value, decl_vars, fn_names), + .if_ => |if_| { - sema_expr(if_.if_eval, decl_vars, fn_names); + try sema_expr(if_.if_eval, decl_vars, fn_names); try sema_block(alloc, if_.if_body, decl_vars, fn_names); }, } @@ -78,7 +85,7 @@ fn sema_expr( expr: Expr, decl_vars: *std.ArrayList(Arg), funs: std.ArrayList([]const u8), -) void { +) SemaErr!void { switch (expr.as) { .fn_call => |fn_call| { if (!contains_str(funs.items, fn_call.name)) { @@ -91,14 +98,15 @@ fn sema_expr( expr.file_content, expr.cursor, ); - if (builtin.mode == .Debug) { - panic("", .{}); - } else { - std.process.exit(1); - } + return SemaErr.CallUnknownFunction; + // if (builtin.mode == .Debug) { + // panic("", .{}); + // } else { + // std.process.exit(1); + // } } for (fn_call.args.items) |arg| { - sema_expr(arg, decl_vars, funs); + try sema_expr(arg, decl_vars, funs); } }, .var_ => |var_| { @@ -112,11 +120,7 @@ fn sema_expr( expr.file_content, expr.cursor, ); - if (builtin.mode == .Debug) { - panic("", .{}); - } else { - std.process.exit(1); - } + return SemaErr.UndeclaredVar; } }, .arith, .bool_, .str => {}, From 9e7f00ced5cc42f39a274e6521a898e47a56812e Mon Sep 17 00:00:00 2001 From: Sublime Tshimpangila Date: Mon, 20 Jul 2026 15:55:19 -0400 Subject: [PATCH 11/11] remove dead comments --- src/errors.zig | 1 - src/main.zig | 2 +- src/sema.zig | 7 +------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/errors.zig b/src/errors.zig index e457241..9c99844 100644 --- a/src/errors.zig +++ b/src/errors.zig @@ -29,7 +29,6 @@ pub fn print_error_line( while (end < content.len and content[end] != '\n') end += 1; const content_error = content[begin..end]; - // std.debug.print("{s}\n", .{content_error}); const GREEN_TAG = "\x1b[32m"; const RED_TAG = "\x1b[31m"; diff --git a/src/main.zig b/src/main.zig index e121041..e37fe20 100644 --- a/src/main.zig +++ b/src/main.zig @@ -40,7 +40,7 @@ pub fn main(init: std.process.Init) !void { switch (err) { SemaErr.UndeclaredVar => std.debug.print("Call to undeclared var", .{}), SemaErr.CallUnknownFunction => std.debug.print("unknowns function", .{}), - SemaErr.OutOfMemory => return SemaErr.OutOfMemory, + SemaErr.OutOfMemory => return SemaErr.OutOfMemory, } return; }; diff --git a/src/sema.zig b/src/sema.zig index 688ef29..3c58e55 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -15,7 +15,7 @@ const BlockStmt = ast_pkg.BlockStmt; const panic = std.debug.panic; const print_error_line = errors_pkg.print_error_line; -pub const SemaErr = error { +pub const SemaErr = error{ OutOfMemory, CallUnknownFunction, UndeclaredVar, @@ -99,11 +99,6 @@ fn sema_expr( expr.cursor, ); return SemaErr.CallUnknownFunction; - // if (builtin.mode == .Debug) { - // panic("", .{}); - // } else { - // std.process.exit(1); - // } } for (fn_call.args.items) |arg| { try sema_expr(arg, decl_vars, funs);