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");
13 tokens: std.ArrayList(Token) = .empty,
18 const keyword: std.StaticStringMap(TokenType) = .initComptime(.{
21 .{ "else", .@"else" },
29 .{ "return", .@"return" },
34 .{ "while", .@"while" },
37 pub fn init(source: []const u8) Scanner {
43 pub fn scanTokens(self: *Scanner, allocator: Allocator) ![]Token {
44 while (!isAtEnd(self)) {
45 // We are at the beginning of the next lexeme.
46 self.start = self.current;
47 try self.scanToken(allocator);
50 try self.tokens.append(allocator, .init(.eof, "", null, self.line));
51 return try self.tokens.toOwnedSlice(allocator);
54 fn scanToken(self: *Scanner, allocator: Allocator) !void {
55 const c = self.advance();
58 '(' => try self.addToken(allocator, .left_paren, null),
59 ')' => try self.addToken(allocator, .right_paren, null),
60 '{' => try self.addToken(allocator, .left_brace, null),
61 '}' => try self.addToken(allocator, .right_brace, null),
62 ',' => try self.addToken(allocator, .comma, null),
63 '.' => try self.addToken(allocator, .dot, null),
64 '-' => try self.addToken(allocator, .minus, null),
65 '+' => try self.addToken(allocator, .plus, null),
66 ';' => try self.addToken(allocator, .semicolon, null),
67 '*' => try self.addToken(allocator, .star, null),
68 '!' => try self.addToken(allocator, if (self.match('=')) .bang_equal else .bang, null),
69 '=' => try self.addToken(allocator, if (self.match('=')) .equal_equal else .equal, null),
70 '<' => try self.addToken(allocator, if (self.match('=')) .less_equal else .less, null),
71 '>' => try self.addToken(allocator, if (self.match('=')) .greater_equal else .greater, null),
73 '/' => if (self.match('/')) {
74 while (self.peek() != '\n' and !self.isAtEnd()) _ = self.advance();
76 try self.addToken(allocator, .slash, null);
79 ' ', '\r', '\t' => {},
80 '\n' => self.line += 1,
81 '"' => try self.string(allocator),
83 else => if (isDigit(c)) {
84 try self.number(allocator);
85 } else if (isAlpha(c)) {
86 try self.identifier(allocator);
88 try lox.@"error"(self.line, "Unexpected character.");
93 fn identifier(self: *Scanner, allocator: Allocator) !void {
94 while (isAlphanumeric(self.peek())) _ = self.advance();
95 const text = self.source[self.start..self.current];
96 const @"type" = keyword.get(text) orelse .identifier;
97 try self.addToken(allocator, @"type", null);
100 fn number(self: *Scanner, allocator: Allocator) !void {
101 while (isDigit(self.peek())) _ = self.advance();
103 // Look for a fractional part.
104 if (self.peek() == '.' and isDigit(self.peekNext())) {
108 while (isDigit(self.peek())) _ = self.advance();
111 try self.addToken(allocator, .number, .{ .number = std.fmt.parseFloat(f64, self.source[self.start..self.current]) catch unreachable });
114 fn string(self: *Scanner, allocator: Allocator) !void {
115 while (self.peek() != '"' and !self.isAtEnd()) {
116 if (self.peek() == '\n') self.line += 1;
120 if (self.isAtEnd()) {
121 try lox.@"error"(self.line, "Unterminated string.");
128 const value = self.source[self.start + 1 .. self.current - 1];
129 try self.addToken(allocator, .string, .{ .string = value });
132 fn match(self: *Scanner, expected: u8) bool {
133 if (self.isAtEnd()) return false;
134 if (self.source[self.current] != expected) return false;
139 fn peek(self: *Scanner) u8 {
140 if (self.isAtEnd()) return 0;
141 return self.source[self.current];
144 fn peekNext(self: *Scanner) u8 {
145 if (self.current + 1 >= self.source.len) return 0;
146 return self.source[self.current + 1];
149 fn isAlpha(c: u8) bool {
150 return isAlphabetic(c) or c == '_';
153 fn isAlphanumeric(c: u8) bool {
154 return isAlpha(c) or isDigit(c);
157 fn isAtEnd(self: *Scanner) bool {
158 return self.current >= self.source.len;
161 fn advance(self: *Scanner) u8 {
162 defer self.current += 1;
163 return self.source[self.current];
166 fn addToken(self: *Scanner, allocator: Allocator, @"type": TokenType, literal: ?Literal) !void {
167 const text = self.source[self.start..self.current];
168 try self.tokens.append(allocator, .init(@"type", text, literal, self.line));