Hi!
I’m continuing my zig learning by creating a bytecode VM.
Last week, I worked on the development of the semantic analyzer, and I’d really appreciate your feedback.
I’ve started to work on the bytecode emitter and VM too, if you’re interested to look at the code.
One problem I encountered was the large number of comparisons I had to make between union(enum)
for type-checking and symbol resolution.
I tried 2 differents ways for now.
- Doing nested switches like this, I find it quite dirty and I guess it’s possible to do better:
fn compareTypes(left: Type, right: Type) !void {
switch (left) {
.built_in => {
return switch (right) {
.built_in => {
if (left.built_in != right.built_in) {
return SemanticError.TypeMismatch;
}
},
else => SemanticError.TypeMismatch,
};
},
.function => {},
.id => {
return switch (right) {
.id => {
if (!std.mem.eql(u8, left.id, right.id)) {
return SemanticError.TypeMismatch;
}
},
else => SemanticError.TypeMismatch,
};
},
}
}
- I’ve been tempted to use the
@tagName
builtin function, but is it really relevant to do so? By the way,std.mem.eql
is considered an efficient way to compare 2 string?
pub fn isInContext(self: *Stack, tag: [:0]const u8) bool {
for (self.contexts.items) |ctx| {
if (std.mem.eql(u8, @tagName(ctx), tag)) return true;
}
return false;
}
pub fn currentFunctionContext(self: *Stack) ?Item.FunctionContext {
const expectedTag = "Function";
for (self.contexts.items) |ctx| {
if (std.mem.eql(u8, @tagName(ctx), expectedTag)) return ctx.Function;
}
return null;
}
.break_ => |_| {
if (!contextStack.isInContext("Loop")) {
return Error.BreakOutsideLoop;
}
return CheckResult{ .type_ = Type{ .built_in = .Void }, .flow = .Unreachable };
}