1 const std = @import("std");
2 const Allocator = std.mem.Allocator;
3 const ascii = std.ascii;
4 const isDigit = ascii.isDigit;
5 const isAlphabetic = ascii.isAlphabetic;
6 const Token = @import("Token.zig");
7 const Literal = Token.Literal;
8 const Scanner = @This();
9 const TokenType = @import("token_type.zig").TokenType;
10 const lox = @import("main.zig");
14 tokens: std.ArrayList(Token) = .empty,
19 const keyword: std.StaticStringMap(TokenType) = .initComptime(.{
22 .{ "else", .@"else" },
30 .{ "return", .@"return" },
35 .{ "while", .@"while" },
38 pub fn init(allocator: Allocator, source: []const u8) Scanner {
40 .allocator = allocator,
45 pub fn scanTokens(self: *Scanner) ![]Token {
46 while (!isAtEnd(self)) {
47 // We are at the beginning of the next lexeme.
48 self.start = self.current;
52 try self.tokens.append(self.allocator, .init(.eof, "", null, self.line));
53 return self.tokens.toOwnedSlice(self.allocator);
56 fn scanToken(self: *Scanner) !void {
57 const c = self.advance();
60 '(' => try self.addToken(.left_paren, null),
61 ')' => try self.addToken(.right_paren, null),
62 '{' => try self.addToken(.left_brace, null),
63 '}' => try self.addToken(.right_brace, null),
64 ',' => try self.addToken(.comma, null),
65 '.' => try self.addToken(.dot, null),
66 '-' => try self.addToken(.minus, null),
67 '+' => try self.addToken(.plus, null),
68 ';' => try self.addToken(.semicolon, null),
69 '*' => try self.addToken(.star, null),
70 '!' => try self.addToken(if (self.match('=')) .bang_equal else .bang, null),
71 '=' => try self.addToken(if (self.match('=')) .equal_equal else .equal, null),
72 '<' => try self.addToken(if (self.match('=')) .less_equal else .less, null),
73 '>' => try self.addToken(if (self.match('=')) .greater_equal else .greater, null),
75 '/' => if (self.match('/')) {
76 while (self.peek() != '\n' and !self.isAtEnd()) _ = self.advance();
78 try self.addToken(.slash, null);
81 ' ', '\r', '\t' => {},
82 '\n' => self.line += 1,
83 '"' => try self.string(),
85 else => if (isDigit(c)) {
87 } else if (isAlpha(c)) {
88 try self.identifier();
90 try lox.scanError(self.line, "Unexpected character.");
95 fn identifier(self: *Scanner) !void {
96 while (isAlphanumeric(self.peek())) _ = self.advance();
97 const text = self.source[self.start..self.current];
98 const @"type" = keyword.get(text) orelse .identifier;
99 try self.addToken(@"type", null);
102 fn number(self: *Scanner) !void {
103 while (isDigit(self.peek())) _ = self.advance();
105 // Look for a fractional part.
106 if (self.peek() == '.' and isDigit(self.peekNext())) {
110 while (isDigit(self.peek())) _ = self.advance();
113 try self.addToken(.number, .{ .number = std.fmt.parseFloat(f64, self.source[self.start..self.current]) catch unreachable });
116 fn string(self: *Scanner) !void {
117 while (self.peek() != '"' and !self.isAtEnd()) {
118 if (self.peek() == '\n') self.line += 1;
122 if (self.isAtEnd()) {
123 try lox.scanError(self.line, "Unterminated string.");
130 const value = self.source[self.start + 1 .. self.current - 1];
131 try self.addToken(.string, .{ .string = value });
134 fn match(self: *Scanner, expected: u8) bool {
135 if (self.isAtEnd()) return false;
136 if (self.source[self.current] != expected) return false;
141 fn peek(self: *Scanner) u8 {
142 if (self.isAtEnd()) return 0;
143 return self.source[self.current];
146 fn peekNext(self: *Scanner) u8 {
147 if (self.current + 1 >= self.source.len) return 0;
148 return self.source[self.current + 1];
151 fn isAlpha(c: u8) bool {
152 return isAlphabetic(c) or c == '_';
155 fn isAlphanumeric(c: u8) bool {
156 return isAlpha(c) or isDigit(c);
159 fn isAtEnd(self: *Scanner) bool {
160 return self.current >= self.source.len;
163 fn advance(self: *Scanner) u8 {
164 defer self.current += 1;
165 return self.source[self.current];
168 fn addToken(self: *Scanner, @"type": TokenType, literal: ?Literal) !void {
169 const text = self.source[self.start..self.current];
170 try self.tokens.append(self.allocator, .init(@"type", text, literal, self.line));