Something went wrong. Try again.
A general purpose .. programming language written in zig ijadux2.tngl.sh/Rinnescript
zig language
Something went wrong. Try again.
12 kB · 298 lines
Zig
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299const std = @import("std");const parser_mod = @import("parser.zig");const Program = parser_mod.Program;const FnDecl = parser_mod.FnDecl;const Stmt = parser_mod.Stmt;const Expr = parser_mod.Expr;const DataType = parser_mod.DataType;
pub const TypeError = error{ TypeMismatch, UndefinedVariable, RedeclaredVariable, InvalidFieldAccess, InvalidFunctionCall, InvalidOptionalUnwrap, OutOfMemory,};
pub const Symbol = struct { name: []const u8, data_type: DataType, is_const: bool,};
pub const Scope = struct { parent: ?*Scope, symbols: std.StringHashMap(Symbol),
pub fn init(allocator: std.mem.Allocator, parent: ?*Scope) Scope { return .{ .parent = parent, .symbols = std.StringHashMap(Symbol).init(allocator) }; }
pub fn deinit(self: *Scope) void { self.symbols.deinit(); }
pub fn put(self: *Scope, symbol: Symbol) TypeError!void { if (self.symbols.contains(symbol.name)) { return TypeError.RedeclaredVariable; } self.symbols.put(symbol.name, symbol) catch return TypeError.OutOfMemory; }
pub fn get(self: *Scope, name: []const u8) ?Symbol { if (self.symbols.get(name)) |sym| return sym; if (self.parent) |p| return p.get(name); return null; }};
/// Static scope/type validator. NOTE: since the pivot to a general-purpose/// language (see PIVOT.md), `rinne run` uses the bytecode compiler directly;/// the checker remains available for tooling and future phases.pub const TypeChecker = struct { allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) TypeChecker { return .{ .allocator = allocator }; }
pub fn checkProgram(self: *TypeChecker, program: *const Program) TypeError!void { var global_scope = Scope.init(self.allocator, null); defer global_scope.deinit();
for (program.fn_decls) |fn_decl| { try global_scope.put(.{ .name = fn_decl.name, .data_type = .{ .primitive = .void_type }, .is_const = true }); }
for (program.global_stmts.stmts) |*stmt| { try self.checkStmt(&global_scope, stmt); }
for (program.fn_decls) |*fn_decl| { try self.checkFnDecl(&global_scope, fn_decl); } }
fn checkFnDecl(self: *TypeChecker, parent_scope: *Scope, fn_decl: *const FnDecl) TypeError!void { var fn_scope = Scope.init(self.allocator, parent_scope); defer fn_scope.deinit();
for (fn_decl.params) |param| { try fn_scope.put(.{ .name = param.name, .data_type = param.param_type, .is_const = true }); }
for (fn_decl.body.stmts) |*stmt| { try self.checkStmt(&fn_scope, stmt); } }
fn checkStmt(self: *TypeChecker, scope: *Scope, stmt: *const Stmt) TypeError!void { switch (stmt.*) { .var_decl => |decl| { const init_type = try self.checkExpr(scope, &decl.initializer); var final_type = init_type; if (decl.type_ann) |ann| { if (!self.areTypesCompatible(ann, init_type)) { return TypeError.TypeMismatch; } final_type = ann; } try scope.put(.{ .name = decl.name, .data_type = final_type, .is_const = decl.is_const }); }, .assignment => |assign| { const val_type = try self.checkExpr(scope, &assign.value); if (assign.target == .identifier) { if (scope.get(assign.target.identifier)) |sym| { if (sym.is_const) return TypeError.TypeMismatch; if (!self.areTypesCompatible(sym.data_type, val_type)) { return TypeError.TypeMismatch; } } else { return TypeError.UndefinedVariable; } } else { _ = try self.checkExpr(scope, &assign.target); } }, .expr_stmt => |*expr| { _ = try self.checkExpr(scope, expr); }, .if_stmt => |if_s| { const cond_type = try self.checkExpr(scope, &if_s.condition); if (cond_type != .primitive or cond_type.primitive != .bool) { return TypeError.TypeMismatch; } var then_scope = Scope.init(self.allocator, scope); defer then_scope.deinit(); for (if_s.then_branch.stmts) |*s| { try self.checkStmt(&then_scope, s); } if (if_s.else_branch) |eb| { var else_scope = Scope.init(self.allocator, scope); defer else_scope.deinit(); for (eb.stmts) |*s| { try self.checkStmt(&else_scope, s); } } }, .while_stmt => |w| { const cond_type = try self.checkExpr(scope, &w.condition); if (cond_type != .primitive or cond_type.primitive != .bool) { return TypeError.TypeMismatch; } var body_scope = Scope.init(self.allocator, scope); defer body_scope.deinit(); for (w.body.stmts) |*s| { try self.checkStmt(&body_scope, s); } }, .break_stmt, .continue_stmt => {}, .for_stmt => |f| { _ = try self.checkExpr(scope, &f.iter); var loop_scope = Scope.init(self.allocator, scope); defer loop_scope.deinit(); for (f.body.stmts) |*s| { try self.checkStmt(&loop_scope, s); } }, .range_for => |r| { _ = try self.checkExpr(scope, &r.lo); _ = try self.checkExpr(scope, &r.hi); var loop_scope = Scope.init(self.allocator, scope); defer loop_scope.deinit(); for (r.body.stmts) |*s| { try self.checkStmt(&loop_scope, s); } }, .return_stmt => |ret_expr| { if (ret_expr) |*e| { _ = try self.checkExpr(scope, e); } }, .defer_stmt => |*e| { _ = try self.checkExpr(scope, e); }, .fn_decl => |*fd| { try scope.put(.{ .name = fd.name, .data_type = .{ .primitive = .void_type }, .is_const = true }); try self.checkFnDecl(scope, fd); }, } }
pub fn checkExpr(self: *TypeChecker, scope: *Scope, expr: *const Expr) TypeError!DataType { switch (expr.*) { .int_lit => return .{ .primitive = .int }, .float_lit => return .{ .primitive = .float }, .string_lit => return .{ .primitive = .str }, .bool_lit => return .{ .primitive = .bool }, .null_lit => return .{ .primitive = .inferred }, .identifier => |name| { if (scope.get(name)) |sym| { return sym.data_type; } return TypeError.UndefinedVariable; }, .array_lit => |items| { if (items.len == 0) { const elem_box = try self.allocator.create(DataType); elem_box.* = .{ .primitive = .inferred }; return .{ .slice = elem_box }; } const first_type = try self.checkExpr(scope, &items[0]); for (items[1..]) |*item| { const it = try self.checkExpr(scope, item); if (!self.areTypesCompatible(first_type, it)) { return TypeError.TypeMismatch; } } const elem_box = try self.allocator.create(DataType); elem_box.* = first_type; return .{ .slice = elem_box }; }, .struct_lit => return .{ .primitive = .inferred }, .binary => |bin| { const lhs_t = try self.checkExpr(scope, bin.lhs); const rhs_t = try self.checkExpr(scope, bin.rhs); if (!self.areTypesCompatible(lhs_t, rhs_t)) { return TypeError.TypeMismatch; } switch (bin.op) { .equal_equal, .not_equal, .less, .less_equal, .greater, .greater_equal, .kw_and, .kw_or => return .{ .primitive = .bool }, else => return lhs_t, } }, .unary => |un| { if (un.op == .kw_not) return .{ .primitive = .bool }; return try self.checkExpr(scope, un.operand); }, .field_access => |fa| { _ = try self.checkExpr(scope, fa.object); return .{ .primitive = .inferred }; }, .index_access => |ia| { const obj_type = try self.checkExpr(scope, ia.object); _ = try self.checkExpr(scope, ia.index); if (obj_type == .slice) { return obj_type.slice.*; } return .{ .primitive = .inferred }; }, .call => |c| { _ = try self.checkExpr(scope, c.callee); for (c.args) |*arg| { _ = try self.checkExpr(scope, arg); } return .{ .primitive = .inferred }; }, .call_directive => |cd| { // Directive intrinsics are backend-resolved (@stdout, @env, // @tensor, ...). Arguments are still validated. for (cd.args) |*arg| { _ = try self.checkExpr(scope, arg); } return .{ .primitive = .inferred }; }, .enum_lit => return .{ .primitive = .inferred }, .fn_lit => return .{ .primitive = .inferred }, } }
fn areTypesCompatible(self: *TypeChecker, expected: DataType, actual: DataType) bool { if (expected == .primitive and expected.primitive == .inferred) return true; if (actual == .primitive and actual.primitive == .inferred) return true;
switch (expected) { .primitive => |p1| { if (actual == .primitive) { return p1 == actual.primitive; } return false; }, .optional => |opt1| { if (actual == .optional) { return self.areTypesCompatible(opt1.*, actual.optional.*); } return self.areTypesCompatible(opt1.*, actual); }, .slice => |sl1| { if (actual == .slice) { return self.areTypesCompatible(sl1.*, actual.slice.*); } return false; }, .custom => return true, } }};
test "TypeChecker validates variable typing" { var tc = TypeChecker.init(std.testing.allocator); var scope = Scope.init(std.testing.allocator, null); defer scope.deinit();
const expr = Expr{ .int_lit = 42 }; const t = try tc.checkExpr(&scope, &expr); try std.testing.expectEqual(DataType.Primitive.int, t.primitive);}