const std = @import("std"); const token = @import("token.zig"); const main = @import("main.zig"); const TokenList = std.ArrayList(token.Token); pub const Scanner = struct { source: []u8, tokens: TokenList, start: usize = 0, current: usize = 0, line: usize = 1, pub fn init(allocator: *std.mem.Allocator, data: []u8) Scanner { return Scanner{ .source = data, .tokens = TokenList.init(allocator), }; } fn isAtEnd(self: *Scanner) bool { return self.current >= self.source.len; } fn advance(self: *Scanner) u8 { self.current += 1; return self.source[self.current - 1]; } fn currentLexeme(self: *Scanner) []u8 { return self.source[self.start..self.current]; } fn addSimpleToken(self: *Scanner, ttype: token.TokenType) !void { try self.addToken(token.Token{ .Simple = token.SimpleToken.init( ttype, self.currentLexeme(), self.line, {}, ), }); } fn addSliceToken(self: *Scanner, ttype: token.TokenType, slice: []u8) !void { try self.addToken(token.Token{ .Slice = token.SliceToken.init( ttype, self.currentLexeme(), self.line, slice, ), }); } fn addToken( self: *Scanner, tok: token.Token, ) !void { try self.tokens.append(tok); } /// Check if the next character matches what is expected. fn match(self: *Scanner, expected: u8) bool { if (self.isAtEnd()) return false; if (self.source[self.current] != expected) return false; self.current += 1; return true; } /// Add a SimpleToken of type_match if the next character is /// `expected`. Adds a SimpleToken of type_nomatch when it is not. fn addMatchToken( self: *Scanner, expected: u8, type_match: token.TokenType, type_nomatch: token.TokenType, ) !void { if (self.match(expected)) { try self.addSimpleToken(type_match); } else { try self.addSimpleToken(type_nomatch); } } fn peek(self: *Scanner) u8 { if (self.isAtEnd()) return 0; return self.source[self.current]; } fn doString(self: *Scanner) !void { // consume entire string while (self.peek() != '"' and !self.isAtEnd()) { if (self.peek() == '\n') self.line += 1; _ = self.advance(); } // unterminated string. if (self.isAtEnd()) { try main.doError(self.line, "Unterminated string."); return; } // the closing ". _ = self.advance(); // trim the surrounding quotes. try self.addSliceToken( .STRING, self.source[self.start + 1 .. self.current - 1], ); } /// Scan through our tokens and add them to the Scanner's token list. fn scanToken(self: *Scanner) !void { var c = self.advance(); switch (c) { '(' => try self.addSimpleToken(.LEFT_PAREN), ')' => try self.addSimpleToken(.RIGHT_PAREN), '{' => try self.addSimpleToken(.LEFT_BRACE), '}' => try self.addSimpleToken(.RIGHT_BRACE), ',' => try self.addSimpleToken(.COMMA), '.' => try self.addSimpleToken(.DOT), '-' => try self.addSimpleToken(.MINUS), '+' => try self.addSimpleToken(.PLUS), ';' => try self.addSimpleToken(.SEMICOLON), '*' => try self.addSimpleToken(.STAR), '!' => try self.addMatchToken('=', .BANG_EQUAL, .BANG), '=' => try self.addMatchToken('=', .EQUAL_EQUAL, .EQUAL), '<' => try self.addMatchToken('=', .LESS_EQUAL, .LESS), '>' => try self.addMatchToken('=', .GREATER_EQUAL, .GREATER), '/' => blk: { // consume comments if (self.match('/')) { while (self.peek() != '\n' and !self.isAtEnd()) { _ = self.advance(); } } else { try self.addSimpleToken(.SLASH); } }, ' ', '\r', '\t' => blk: {}, '\n' => blk: { self.line += 1; }, '"' => try self.doString(), else => { try main.doError(self.line, "Unexpected character"); }, } } pub fn scanTokens(self: *Scanner) !TokenList { // while we aren't at the end, we're still consuming // tokens. while (!self.isAtEnd()) { self.start = self.current; try self.scanToken(); } try self.addToken(token.Token{ .Simple = token.SimpleToken.init( .EOF, "", self.line, {}, ), }); return self.tokens; } };