diff --git a/examples/hello.ry b/examples/hello.ry index 34bcb18..4c0dad8 100644 --- a/examples/hello.ry +++ b/examples/hello.ry @@ -7,8 +7,8 @@ fn f() i32 { return 2; } -fn f2() i32 { - return f() + 2; +fn f2() i64 { + return 1301; } enum B { diff --git a/src/types.zig b/src/analysis.zig similarity index 90% rename from src/types.zig rename to src/analysis.zig index c9ac686..c6ef1f8 100644 --- a/src/types.zig +++ b/src/analysis.zig @@ -16,8 +16,13 @@ pub const TypeSolver = struct { err_tok: ?Token = null, hadError: bool = false, - pub fn init(allocator: *std.mem.Allocator) TypeSolver { - return TypeSolver{ .allocator = allocator }; + err_ctx_buffer: []u8, + + pub fn init(allocator: *std.mem.Allocator) !TypeSolver { + return TypeSolver{ + .allocator = allocator, + .err_ctx_buffer = try allocator.alloc(u8, 512), + }; } fn setErrContext(self: *@This(), comptime fmt: ?[]const u8, args: ...) void { @@ -26,9 +31,11 @@ pub const TypeSolver = struct { return; } - // TODO allocate buffer on init() and use it - var buf = self.allocator.alloc(u8, 256) catch unreachable; - self.err_ctx = std.fmt.bufPrint(buf, fmt.?, args) catch unreachable; + self.err_ctx = std.fmt.bufPrint( + self.err_ctx_buffer, + fmt.?, + args, + ) catch unreachable; } fn setErrToken(self: *@This(), tok: ?Token) void { @@ -38,7 +45,7 @@ pub const TypeSolver = struct { fn doError(self: *@This(), comptime fmt: []const u8, args: ...) void { self.hadError = true; - std.debug.warn("type error"); + std.debug.warn("analysis error"); if (self.err_tok) |tok| { std.debug.warn(" at line {}", tok.line); } @@ -53,35 +60,41 @@ pub const TypeSolver = struct { } /// Resolve a type in global scope + /// Properly resolves composite (currently opaque) types to structs/enums. fn resolveGlobalType( self: *@This(), ctx: *comp.CompilationContext, identifier: []const u8, ) ?SymbolUnderlyingType { - // assume the identifier references a builtin + // first, we assume the identifier is for a simple type + // if we fail (and this always returns OpaqueType as a fallback), + // we take it and find something in global scope var typ = ctx.solveType(identifier); switch (typ) { .OpaqueType => |val| { - // solve for opaque so it isnt opaque var sym = ctx.symbol_table.get(val); - if (sym != null) - return switch (sym.?.value) { - .Struct => SymbolUnderlyingType{ .Struct = val }, - .Enum => SymbolUnderlyingType{ .Enum = val }, - else => blk: { - self.doError( - "expected struct or enum for type '{}', got {}", - val, - sym, - ); - break :blk null; - }, - }; + if (sym == null) { + self.doError("Unknown type: '{}'", val); + return null; + } - self.doError("Unknown type: '{}'", val); - return null; + return switch (sym.?.value) { + .Struct => SymbolUnderlyingType{ .Struct = val }, + .Enum => SymbolUnderlyingType{ .Enum = val }, + + // TODO name resolution + + else => blk: { + self.doError( + "expected struct or enum for '{}', got {}", + val, + @tagName(comp.SymbolType(sym.?.value)), + ); + break :blk null; + }, + }; }, else => return typ, @@ -102,6 +115,7 @@ pub const TypeSolver = struct { } } + /// Compare if the given type names are equal. fn compositeIdentifierEqual( self: *@This(), typ_enum: comp.SymbolUnderlyingTypeEnum, @@ -195,7 +209,9 @@ pub const TypeSolver = struct { .Bool => SymbolUnderlyingType{ .Bool = {} }, // TODO determine its i64 depending of parseInt results - .Integer => SymbolUnderlyingType{ .Integer32 = {} }, + .Integer32 => SymbolUnderlyingType{ .Integer32 = {} }, + .Integer64 => SymbolUnderlyingType{ .Integer64 = {} }, + .Float => SymbolUnderlyingType{ .Double = {} }, else => unreachable, }; diff --git a/src/ast.zig b/src/ast.zig index 7a5c4cd..e1e3205 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -83,7 +83,10 @@ pub const UnaryExpr = struct { pub const LiteralExpr = union(enum) { Bool: bool, - Integer: []const u8, + + Integer32: i32, + Integer64: i64, + Float: []const u8, String: []const u8, Array: ExprList, diff --git a/src/ast_printer.zig b/src/ast_printer.zig index 322ccb7..1eb5407 100644 --- a/src/ast_printer.zig +++ b/src/ast_printer.zig @@ -227,7 +227,8 @@ pub fn printExpr(expr: *const Expr) void { .Literal => |literal| { switch (literal) { .Bool => |val| std.debug.warn("{}", val), - .Integer => |val| std.debug.warn("{}", val), + .Integer32 => |val| std.debug.warn("{}", val), + .Integer64 => |val| std.debug.warn("{}", val), .Float => |val| std.debug.warn("{}", val), .String => |val| std.debug.warn("'{}'", val), .Array => |exprs| { @@ -362,6 +363,7 @@ fn prettyType(typ: SymbolUnderlyingType) []const u8 { .Integer32 => "i32", .Integer64 => "i64", .Bool => "bool", + .Double => "double", .OpaqueType => |ident| retWithName("opaque", ident), .Struct => |ident| retWithName("struct", ident), diff --git a/src/codegen.zig b/src/codegen.zig index d481a7f..63af360 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2,7 +2,7 @@ const std = @import("std"); const ast = @import("ast.zig"); const llvm = @import("llvm.zig"); const comp = @import("comp_ctx.zig"); -const types = @import("types.zig"); +// const analysis = @import("analysis.zig"); fn sliceify(non_slice: ?[*]const u8) []const u8 { if (non_slice == null) return ""; @@ -85,10 +85,17 @@ pub const Codegen = struct { .Literal => |literal| blk: { break :blk switch (literal) { // TODO other literals - .Integer => |val| blk2: { - var val_cstr = try std.cstr.addNullByte(self.allocator, val); - break :blk2 llvm.LLVMConstIntOfString(llvm.LLVMInt32Type(), val_cstr.ptr, 10); - }, + .Integer32 => |val| llvm.LLVMConstInt( + llvm.LLVMInt32Type(), + @intCast(c_ulonglong, val), + 10, + ), + .Integer64 => |val| llvm.LLVMConstInt( + llvm.LLVMInt64Type(), + @intCast(c_ulonglong, val), + 10, + ), + .Float => |val| blk2: { var val_cstr = try std.cstr.addNullByte(self.allocator, val); break :blk2 llvm.LLVMConstRealOfString(llvm.LLVMDoubleType(), val_cstr.ptr); diff --git a/src/comp_ctx.zig b/src/comp_ctx.zig index e04b18a..ac1cfa8 100644 --- a/src/comp_ctx.zig +++ b/src/comp_ctx.zig @@ -9,6 +9,7 @@ pub const TypeList = std.ArrayList(SymbolUnderlyingType); pub const SymbolUnderlyingTypeEnum = enum { Integer32, Integer64, + Double, Bool, // opaque unsolved identifier @@ -22,6 +23,7 @@ pub const SymbolUnderlyingTypeEnum = enum { pub const SymbolUnderlyingType = union(SymbolUnderlyingTypeEnum) { Integer32: void, Integer64: void, + Double: void, Bool: void, OpaqueType: []const u8, diff --git a/src/main.zig b/src/main.zig index 517738b..2becea9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,7 +4,7 @@ const scanners = @import("scanners.zig"); const parsers = @import("parsers.zig"); const printer = @import("ast_printer.zig"); const codegen = @import("codegen.zig"); -const types = @import("types.zig"); +const analysis = @import("analysis.zig"); pub const Result = enum { Ok, @@ -50,7 +50,7 @@ pub fn run(allocator: *std.mem.Allocator, slice: []const u8) !Result { std.debug.warn("parse tree\n"); printer.printNode(root, 0); - var solver = types.TypeSolver.init(allocator); + var solver = try analysis.TypeSolver.init(allocator); var ctx = try solver.pass(root); std.debug.warn("symbol table\n"); diff --git a/src/parsers.zig b/src/parsers.zig index 2e278cc..3bb8572 100644 --- a/src/parsers.zig +++ b/src/parsers.zig @@ -14,6 +14,7 @@ const TokenType = tokens.TokenType; pub const ParseError = error{ CompileError, UnknownOperator, + InvalidInteger, }; const Node = ast.Node; @@ -356,11 +357,22 @@ pub const Parser = struct { return expr; } - fn mkInteger(self: *Parser, val: []const u8) !*ast.Expr { + fn mkInt32(self: *Parser, val: i32) !*ast.Expr { var expr = try self.allocator.create(Expr); expr.* = Expr{ .Literal = ast.LiteralExpr{ - .Integer = val, + .Integer32 = val, + }, + }; + + return expr; + } + + fn mkInt64(self: *Parser, val: i64) !*ast.Expr { + var expr = try self.allocator.create(Expr); + expr.* = Expr{ + .Literal = ast.LiteralExpr{ + .Integer64 = val, }, }; @@ -1124,8 +1136,40 @@ pub const Parser = struct { .False => try self.mkBool(false), .True => try self.mkBool(true), - .Integer => try self.mkInteger(lexeme), + .Integer => blk: { + // hacky, but it works. the old approach was doing + // parseInt(i64) on the catch block of parseInt(i32) + var i32_num_opt: ?i32 = std.fmt.parseInt(i32, lexeme, 10) catch null; + var i64_num: i64 = std.fmt.parseInt(i64, lexeme, 10) catch |err| { + return self.doError( + "Invalid integer (not 32bit or 64bit) '{}': {}", + lexeme, + err, + ); + }; + + if (i32_num_opt) |i32_num| { + break :blk try self.mkInt32(i32_num); + } else { + break :blk try self.mkInt64(i64_num); + } + + // old one + + //break :blk try self.mkInt32(std.fmt.parseInt(i32, lexeme, 10) catch |err32| { + // break :blk try self.mkInt64(std.fmt.parseInt(i64, lexeme, 10) catch |err64| { + // return self.doError( + // "Invalid integer (not 32bit or 64bit) '{}': {}", + // lexeme, + // err64, + // ); + // }); + //}); + }, + + // TODO std.fmt.parseFloat .Float => try self.mkFloat(lexeme), + .String => try self.mkString(lexeme), .Identifier => try self.mkVariable(self.peek()),