1 const std = @import("std");
2 const Allocator = std.mem.Allocator;
3 const Token = @import("Token.zig");
4 const Literal = Token.Literal;
5 const Scanner = @This();
6 const TokenType = @import("token-type.zig").TokenType;
7 const Lox = @import("Lox.zig");
10 tokens: std.ArrayList(Token) = .empty,
15 const keyword: std.StaticStringMap(TokenType) = .initComptime(.{
18 .{ "else", .@"else" },
26 .{ "return", .@"return" },
31 .{ "while", .@"while" },
34 pub fn init(source: []const u8) Scanner {
40 pub fn scanTokens(self: *Scanner, allocator: Allocator) ![]Token {
41 while (!isAtEnd(self)) {
42 // We are at the beginning of the next lexeme.
43 self.start = self.current;
44 try self.scanToken(allocator);
47 try self.tokens.append(allocator, .init(.eof, "", null, self.line));
48 return try self.tokens.toOwnedSlice(allocator);
51 fn scanToken(self: *Scanner, allocator: Allocator) !void {
52 const c = self.advance();
55 '(' => try self.addToken(allocator, .left_paren, null),
56 ')' => try self.addToken(allocator, .right_paren, null),
57 '{' => try self.addToken(allocator, .left_brace, null),
58 '}' => try self.addToken(allocator, .right_brace, null),
59 ',' => try self.addToken(allocator, .comma, null),
60 '.' => try self.addToken(allocator, .dot, null),
61 '-' => try self.addToken(allocator, .minus, null),
62 '+' => try self.addToken(allocator, .plus, null),
63 ';' => try self.addToken(allocator, .semicolon, null),
64 '*' => try self.addToken(allocator, .star, null),
65 '!' => try self.addToken(allocator, if (self.match('=')) .bang_equal else .bang, null),
66 '=' => try self.addToken(allocator, if (self.match('=')) .equal_equal else .equal, null),
67 '<' => try self.addToken(allocator, if (self.match('=')) .less_equal else .less, null),
68 '>' => try self.addToken(allocator, if (self.match('=')) .greater_equal else .greater, null),
70 '/' => if (self.match('/')) {
71 while (self.peek() != '\n' and !self.isAtEnd()) _ = self.advance();
73 try self.addToken(allocator, .slash, null);
76 ' ', '\r', '\t' => {},
77 '\n' => self.line += 1,
78 '"' => try self.string(allocator),
80 else => if (isDigit(c)) {
81 try self.number(allocator);
82 } else if (isAlpha(c)) {
83 try self.identifier(allocator);
85 try Lox.@"error"(self.line, "Unexpected character.");
90 fn identifier(self: *Scanner, allocator: Allocator) !void {
91 while (isAlphanumeric(self.peek())) _ = self.advance();
92 const text = self.source[self.start..self.current];
93 var @"type" = keyword.get(text);
94 if (@"type" == null) @"type" = .identifier;
95 try self.addToken(allocator, @"type".?, null);
98 fn number(self: *Scanner, allocator: Allocator) !void {
99 while (isDigit(self.peek())) _ = self.advance();
101 // Look for a fractional part.
102 if (self.peek() == '.' and isDigit(self.peekNext())) {
106 while (isDigit(self.peek())) _ = self.advance();
109 try self.addToken(allocator, .number, .{ .number = std.fmt.parseFloat(f64, self.source[self.start..self.current]) catch unreachable });
112 fn string(self: *Scanner, allocator: Allocator) !void {
113 while (self.peek() != '"' and !self.isAtEnd()) {
114 if (self.peek() == '\n') self.line += 1;
118 if (self.isAtEnd()) {
119 try Lox.@"error"(self.line, "Unterminated string.");
126 const value = self.source[self.start + 1 .. self.current - 1];
127 try self.addToken(allocator, .string, .{ .string = value });
130 fn match(self: *Scanner, expected: u8) bool {
131 if (self.isAtEnd()) return false;
132 if (self.source[self.current] != expected) return false;
137 fn peek(self: *Scanner) u8 {
138 if (self.isAtEnd()) return 0;
139 return self.source[self.current];
142 fn peekNext(self: *Scanner) u8 {
143 if (self.current + 1 >= self.source.len) return 0;
144 return self.source[self.current + 1];
147 fn isAlpha(c: u8) bool {
148 return (c >= 'a' and c <= 'z') or
149 (c >= 'A' and c <= 'Z') or
153 fn isAlphanumeric(c: u8) bool {
154 return isAlpha(c) or isDigit(c);
157 fn isDigit(c: u8) bool {
158 return c >= '0' and c <= '9';
161 fn isAtEnd(self: *Scanner) bool {
162 return self.current >= self.source.len;
165 fn advance(self: *Scanner) u8 {
166 defer self.current += 1;
167 return self.source[self.current];
170 fn addToken(self: *Scanner, allocator: Allocator, @"type": TokenType, literal: ?Literal) !void {
171 const text = self.source[self.start..self.current];
172 try self.tokens.append(allocator, .init(@"type", text, literal, self.line));