Some observations / recommendations:
- In the scanner, I don’t think you need to store the allocator since the
ArrayList
already stores it. So your init
could just be:
pub fn init(allocator: mem.Allocator, source: []const u8) Scanner {
return .{
.token_list = std.ArrayList(Token).init(allocator),
.source = source,
};
}
- There are multiple calles where you pass
self
as the parameter. Although this is perfectly fine and allowed in Zig, you can also turn it into a method call form:
isAtEnd(self)
self.isAtEnd()
- Speaking of
isAtEnd
, I think subtracting 1 from the source len is a bug, since it would leave the last byte out.
fn isAtEnd(self: *Scanner) bool {
return self.current >= (self.source.len - 1);
}
- You can use enum literals to shorten your code:
// This
'(' => try addToken(self, tokens.TokenType.LEFT_PAREN, null),
// can be this.
'(' => try self.addToken(.LEFT_PAREN, null),
- You can comma separate multiple cases in a switch prong:
'a'...'z', 'A'...'Z', '_' => try self.identifier(),
- You could use a
StaticStringMap
to handle the keywords. This is how I’m doing it in my take on Lox (I’m working through the book too.):
const keywords = std.StaticStringMap(Token.Tag).initComptime(.{
.{ "and", .kw_and },
.{ "class", .kw_class },
.{ "else", .kw_else },
.{ "false", .kw_false },
.{ "for", .kw_for },
.{ "fun", .kw_fun },
.{ "if", .kw_if },
.{ "nil", .kw_nil },
.{ "or", .kw_or },
.{ "print", .kw_print },
.{ "return", .kw_return },
.{ "super", .kw_super },
.{ "this", .kw_this },
.{ "true", .kw_true },
.{ "var", .kw_var },
.{ "while", .kw_while },
});
fn ident(self: *Scanner) !void {
while (isIdent(self.peek())) _ = self.advance();
const identifier = self.src[self.start..self.current];
const tag = if (keywords.get(identifier)) |tag| tag else .ident;
try self.addToken(tag, .nil);
}
- When dealing with
ArrayList
, it’s recommended to pass in a pointer to a list and modify it in the function versus creating the list in the function and returning it.
fn addStuffToList(list: *ArrayList(Token)) !void {
// modify list...
}
In my case, I just collected the tokens in Scanner.tokens
(an ArrayList
) and then directly geve the list’s items to the parser:
// collect all tokens in scanner.tokens
try scanner.scan();
// pass the slice to parser
try parser.parse(scanner.tokens.items);