diff --git a/examples/sema/use-undeclared-var.sub b/examples/sema/use-undeclared-var.sub new file mode 100644 index 0000000..fe35181 --- /dev/null +++ b/examples/sema/use-undeclared-var.sub @@ -0,0 +1,13 @@ +fn main() void { + var b: i32 = "bonjour"; + if b { + var a: i32 = "in if"; + printf(b); + printf(a); + } + printf(a); +} + +fn printf() void { + +} 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 diff --git a/src/ast.zig b/src/ast.zig index ba11640..bf04a51 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; @@ -51,7 +55,7 @@ pub const ProgramDecl = union(ProgramDeclTag) { } }; -const FnDecl = struct { +pub const FnDecl = struct { const Self = @This(); name: []const u8, @@ -94,36 +98,83 @@ const ExprTag = enum { bool_, fn_call, str, + var_, }; -pub const Expr = union(ExprTag) { +pub const Expr = struct { const Self = @This(); - arith: ArithExpr, - bool_: BoolExpr, - fn_call: FnCallExpr, - str: []const u8, + cursor: Cursor, + file_path: []const u8, + file_content: []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), + file_path: []const u8, + cursor: Cursor, + file_content: []const u8, + ) Expr { + return .{ + .as = .{ .fn_call = .{ + .name = name, + .args = args, + } }, + .file_path = file_path, + .cursor = cursor, + .file_content = file_content, + }; } - pub fn create_str(content: []const u8) Expr { - return .{ .str = content }; + pub fn create_str( + content: []const u8, + file_path: []const u8, + cursor: Cursor, + file_content: []const u8, + ) Expr { + return .{ + .as = .{ .str = content }, + .file_path = file_path, + .cursor = cursor, + .file_content = file_content, + }; + } + + pub fn create_var( + name: []const u8, + file_path: []const u8, + cursor: Cursor, + file_content: []const u8, + ) Expr { + return .{ + .as = .{ .var_ = name }, + .file_path = file_path, + .cursor = cursor, + .file_content = file_content, + }; } 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(), - else => panic("print unimplemented for {}", .{std.meta.activeTag(self)}), + .var_ => |var_| std.debug.print("{s}", .{var_}), + 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/errors.zig b/src/errors.zig new file mode 100644 index 0000000..9c99844 --- /dev/null +++ b/src/errors.zig @@ -0,0 +1,52 @@ +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( + 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; + + 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]; + + const GREEN_TAG = "\x1b[32m"; + const RED_TAG = "\x1b[31m"; + const END_TAG = "\x1b[0m"; + std.debug.print( + "\n" ++ GREEN_TAG ++ "{s}:{}:{}" ++ END_TAG ++ " ", + .{ + file_path, + cursor.row + 1, + cursor.col, + }, + ); + + std.debug.print(fmt ++ "\n", args); + std.debug.print("{s}\n", .{content_error}); + + for (0..cursor.col - 1) |_| { + std.debug.print(" ", .{}); + } + std.debug.print(RED_TAG ++ "^" ++ END_TAG ++ "\n", .{}); +} 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/main.zig b/src/main.zig index ea6b467..e37fe20 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,10 +2,13 @@ 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 SemaErr = sema_pkg.SemaErr; const parse = parser_pkg.parse; +const sema = sema_pkg.sema; const LIMIT = 1024 * 10; @@ -33,6 +36,14 @@ pub fn main(init: std.process.Init) !void { ); const ast = try parse(&l, alloc); + 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/parser.zig b/src/parser.zig index 8fd4c16..0478c99 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -164,6 +164,14 @@ 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, + l.file_path, + l.previous_cursor, + l.content, + ); } else if (l.token == .str) { return parse_str(l); } @@ -173,7 +181,12 @@ 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.previous_cursor, + l.content, + ); } fn parse_fn_call_expr(l: *Lexer, alloc: Allocator) !Expr { @@ -194,5 +207,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.previous_cursor, l.content); } diff --git a/src/sema.zig b/src/sema.zig new file mode 100644 index 0000000..3c58e55 --- /dev/null +++ b/src/sema.zig @@ -0,0 +1,137 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +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; +const Arg = ast_pkg.Arg; +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 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 + 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_names), + } + } +} + +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, fn_names); +} + +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; + 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| try sema_expr(no_assign.value, decl_vars, fn_names), + + .if_ => |if_| { + try 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), + funs: std.ArrayList([]const u8), +) SemaErr!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, + ); + return SemaErr.CallUnknownFunction; + } + for (fn_call.args.items) |arg| { + try sema_expr(arg, decl_vars, funs); + } + }, + .var_ => |var_| { + if (!contains(decl_vars.items, var_)) { + print_error_line( + "use of undeclared var: {s}", + .{ + var_, + }, + expr.file_path, + expr.file_content, + expr.cursor, + ); + return SemaErr.UndeclaredVar; + } + }, + .arith, .bool_, .str => {}, + } +} + +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; + } + return false; +}