Skip to content

Commit

Permalink
Additional compiler checks
Browse files Browse the repository at this point in the history
  • Loading branch information
bgk- committed May 31, 2024
1 parent 3c7b34a commit 3a8d7ee
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 50 deletions.
3 changes: 2 additions & 1 deletion src/compiler-error.zig
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ pub const CompilerErrors = struct {
var lineStart: usize = 0;
if (lineNumber == 1) lineStart = if (std.mem.startsWith(u8, l, "\xEF\xBB\xBF")) 3 else @as(usize, 0);
try writer.print("{s}\n", .{l[lineStart..]});
try writer.writeByteNTimes(' ', @max(column - 1, 0));
const offset_col: u8 = if (lineNumber == 1) 1 else 2;
try writer.writeByteNTimes(' ', @max(column - offset_col, 0));
try writer.print("{s}", .{color_prefix});
try writer.writeByteNTimes('~', end - start);
try writer.writeAll("\n\x1b[0m");
Expand Down
66 changes: 36 additions & 30 deletions src/compiler.zig
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ pub const initial_constants = [_]Value{
.{ .visit = 0 },
};

fn arrayOfTypeContains(comptime T: type, haystack: []const []const T, needle: []const T) bool {
for (haystack) |element| {
if (std.mem.eql(T, element, needle)) return true;
}
return false;
}
pub const Compiler = struct {
allocator: std.mem.Allocator,
builtins: *Scope,
Expand All @@ -53,7 +59,7 @@ pub const Compiler = struct {
divert_log: std.ArrayList(JumpTree.Entry),
visit_tree: VisitTree,

types: std.ArrayList(*const ast.Statement),
types: std.StringHashMap(*const ast.Statement),

pub const Chunk = struct {
instructions: std.ArrayList(u8),
Expand Down Expand Up @@ -106,7 +112,7 @@ pub const Compiler = struct {
.jump_tree = try JumpTree.init(allocator),
.visit_tree = try VisitTree.init(allocator),
.divert_log = std.ArrayList(JumpTree.Entry).init(allocator),
.types = std.ArrayList(*const ast.Statement).init(allocator),
.types = std.StringHashMap(*const ast.Statement).init(allocator),
.err = undefined,
.module = undefined,
};
Expand Down Expand Up @@ -441,7 +447,7 @@ pub const Compiler = struct {
try self.setSymbol(symbol, token, true);
},
.class => |c| {
try self.types.append(&stmt);
try self.types.put(c.name, &stmt);
try self.enterScope(.local);
for (c.fields, 0..) |field, i| {
try self.compileExpression(&field);
Expand All @@ -458,7 +464,7 @@ pub const Compiler = struct {
try self.setSymbol(symbol, token, true);
},
.@"enum" => |e| {
try self.types.append(&stmt);
try self.types.put(e.name, &stmt);
var names = std.ArrayList([]const u8).init(self.allocator);
defer names.deinit();
const obj = try self.allocator.create(Value.Obj);
Expand Down Expand Up @@ -753,7 +759,11 @@ pub const Compiler = struct {
try self.loadSymbol(symbol, id, token);
return;
},
.indexer => {
.indexer => |idx| {
if (idx.target.type == .identifier) {
if (self.types.get(idx.target.type.identifier)) |stmt|
return self.fail("Cannot assign value to {s}", token, .{@tagName(stmt.type)});
}
try self.compileExpression(bin.right);
try self.compileExpression(bin.left);
try self.removeLast(.index);
Expand Down Expand Up @@ -799,7 +809,11 @@ pub const Compiler = struct {
try self.loadSymbol(symbol, id, token);
return;
},
.indexer => {
.indexer => |idx| {
if (idx.target.type == .identifier) {
if (self.types.get(idx.target.type.identifier)) |stmt|
return self.fail("Cannot assign value to {s}", token, .{@tagName(stmt.type)});
}
try self.compileExpression(bin.left);
try self.removeLast(.index);
try self.writeOp(.set_property, token);
Expand Down Expand Up @@ -875,16 +889,17 @@ pub const Compiler = struct {
try self.compileExpression(idx.target);
if (token.token_type == .dot) {
if (idx.target.type == .identifier) {
for (self.types.items) |stmt| {
if (stmt.type != .@"enum") continue;
if (std.mem.eql(u8, idx.target.type.identifier, stmt.type.@"enum".name)) {
var found = false;
for (stmt.type.@"enum".values) |value| {
if (!std.mem.eql(u8, idx.index.type.identifier, value)) continue;
found = true;
break;
}
if (!found) return self.fail("Enum {s} does not contain a value '{s}'", idx.index.token, .{ idx.target.type.identifier, idx.index.type.identifier });
if (self.types.get(idx.target.type.identifier)) |stmt| {
switch (stmt.type) {
.@"enum" => |e| {
if (!arrayOfTypeContains(u8, e.values, idx.index.type.identifier))
return self.fail("Enum {s} does not contain a value '{s}'", idx.index.token, .{ idx.target.type.identifier, idx.index.type.identifier });
},
.class => |c| {
if (!arrayOfTypeContains(u8, c.field_names, idx.index.type.identifier))
return self.fail("Class {s} does not contain a field '{s}'", idx.index.token, .{ idx.target.type.identifier, idx.index.type.identifier });
},
else => {},
}
}
}
Expand Down Expand Up @@ -974,22 +989,13 @@ pub const Compiler = struct {
},
.instance => |ins| {
var cls: ?*const ast.Statement = null;
for (self.types.items) |stmt| {
if (stmt.type == .class and std.mem.eql(u8, ins.name, stmt.type.class.name)) {
cls = stmt;
break;
}
if (self.types.get(ins.name)) |stmt| {
cls = stmt;
}
if (cls == null) return self.fail("Unknown class {s}", token, .{ins.name});
if (cls == null or cls.?.type != .class) return self.fail("Unknown class {s}", token, .{ins.name});
for (ins.fields, 0..) |field, i| {
var found = false;
for (cls.?.type.class.field_names) |field_name| {
if (!std.mem.eql(u8, field_name, ins.field_names[i])) continue;
found = true;
break;
}
if (!found) return self.fail("Class {s} does not contain a field named '{s}'", token, .{ ins.name, ins.field_names[i] });

if (!arrayOfTypeContains(u8, cls.?.type.class.field_names, ins.field_names[i]))
return self.fail("Class {s} does not contain a field named '{s}'", token, .{ ins.name, ins.field_names[i] });
try self.compileExpression(&field);
try self.getOrSetIdentifierConstant(ins.field_names[i], token);
}
Expand Down
85 changes: 66 additions & 19 deletions src/vm.test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,7 @@ test "Classes" {
\\ class Test = {
\\ value = 0
\\ }
\\ assert(Test.value == 0, "Test.value == 0")
;
var mod = Module.create(allocator);
defer mod.deinit();
Expand All @@ -757,26 +758,55 @@ test "Classes" {
defer vm.deinit();
defer vm.bytecode.free(testing.allocator);
try vm.interpret();
const value = vm.stack.previous();
try testing.expect(value.obj.data == .class);
try testing.expectEqualStrings("Test", value.obj.data.class.name);
}

test "Class Error" {
const input =
test "Class Runtime Error" {
const tests = .{
\\ class Test = {
\\ value = 0
\\ }
\\ var test = new Test{}
\\ test.val = 55
,
};
inline for (tests) |input| {
var mod = Module.create(allocator);
defer mod.deinit();
defer mod.entry.source_loaded = false;
var vm = try initTestVm(input, &mod, false);
defer vm.deinit();
defer vm.bytecode.free(testing.allocator);
const err = vm.interpret();
try std.testing.expectError(error.RuntimeError, err);
}
}

test "Class Compile Error" {
const tests = .{
\\ class Test = {
\\ value = 0
\\ }
\\ var test = new Test{
\\ val = 2
\\ }
\\
;
var mod = Module.create(allocator);
defer mod.deinit();
defer mod.entry.source_loaded = false;
const err = initTestVm(input, &mod, false);
try testing.expectError(Vm.Error.CompilerError, err);
,
\\ class Test = {
\\ value = 0
\\ }
\\ Test.value = 55
,
\\ class Test = {
\\ value = 0
\\ }
\\ Test = 55
};
inline for (tests) |input| {
var mod = Module.create(allocator);
defer mod.deinit();
defer mod.entry.source_loaded = false;
const err = initTestVm(input, &mod, false);
try testing.expectError(Vm.Error.CompilerError, err);
}
}

test "Instance" {
Expand Down Expand Up @@ -867,7 +897,7 @@ test "Enums" {
}

test "Enum Error" {
const input =
const tests = .{
\\ enum TimeOfDay = {
\\ Morning,
\\ Afternoon,
Expand All @@ -876,13 +906,30 @@ test "Enum Error" {
\\ }
\\
\\ var time = TimeOfDay.morn
;
,
\\ enum TimeOfDay = {
\\ Morning,
\\ Afternoon,
\\ Evening,
\\ Night
\\ }
\\
\\ TimeOfDay.Morning = 5
,
\\ enum TimeOfDay = {
\\ Morning,
\\ }
\\
\\ TimeOfDay = 5
};

var mod = Module.create(allocator);
defer mod.deinit();
defer mod.entry.source_loaded = false;
const err = initTestVm(input, &mod, false);
try testing.expectError(Vm.Error.CompilerError, err);
inline for (tests) |input| {
var mod = Module.create(allocator);
defer mod.deinit();
defer mod.entry.source_loaded = false;
const err = initTestVm(input, &mod, false);
try testing.expectError(Vm.Error.CompilerError, err);
}
}

test "Boughs" {
Expand Down
14 changes: 14 additions & 0 deletions src/vm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,20 @@ pub const Vm = struct {
}
if (!found) return self.fail("Unknown value \"{s}\" on enum {s}", .{ index.obj.data.string, e.name });
},
.class => |c| {
if (index != .obj)
return self.fail("Can only query instance fields by string name, not {s}", .{@tagName(index)});
if (index.obj.data != .string)
return self.fail("Can only query instance fields by string name, not {s}", .{@tagName(index.obj.data)});
var found = false;
for (c.fields) |field| {
if (std.mem.eql(u8, field.name, index.obj.data.string)) {
try self.push(field.value);
found = true;
}
}
if (!found) return self.fail("Unknown value \"{s}\" on Class {s}", .{ index.obj.data.string, c.name });
},
else => return self.fail("Unknown target type {s} to index. Only lists, maps, sets, or instances can be indexed.", .{@tagName(target)}),
},
.map_pair => |mp| {
Expand Down

0 comments on commit 3a8d7ee

Please sign in to comment.