]> Repositories - zlox.git/blob - src/Scanner.zig
Fix shadowing mistake
[zlox.git] / src / Scanner.zig
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");
11
12 allocator: Allocator,
13 source: []const u8,
14 tokens: std.ArrayList(Token) = .empty,
15 start: u32 = 0,
16 current: u32 = 0,
17 line: u32 = 1,
18
19 const keyword: std.StaticStringMap(TokenType) = .initComptime(.{
20     .{ "and", .@"and" },
21     .{ "class", .class },
22     .{ "else", .@"else" },
23     .{ "false", .false },
24     .{ "for", .@"for" },
25     .{ "fun", .fun },
26     .{ "if", .@"if" },
27     .{ "nil", .nil },
28     .{ "or", .@"or" },
29     .{ "print", .print },
30     .{ "return", .@"return" },
31     .{ "super", .super },
32     .{ "this", .this },
33     .{ "true", .true },
34     .{ "var", .@"var" },
35     .{ "while", .@"while" },
36 });
37
38 pub fn init(allocator: Allocator, source: []const u8) Scanner {
39     return .{
40         .allocator = allocator,
41         .source = source,
42     };
43 }
44
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;
49         try self.scanToken();
50     }
51
52     try self.tokens.append(self.allocator, .init(.eof, "", null, self.line));
53     return try self.tokens.toOwnedSlice(self.allocator);
54 }
55
56 fn scanToken(self: *Scanner) !void {
57     const c = self.advance();
58
59     switch (c) {
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),
74
75         '/' => if (self.match('/')) {
76             while (self.peek() != '\n' and !self.isAtEnd()) _ = self.advance();
77         } else {
78             try self.addToken(.slash, null);
79         },
80
81         ' ', '\r', '\t' => {},
82         '\n' => self.line += 1,
83         '"' => try self.string(),
84
85         else => if (isDigit(c)) {
86             try self.number();
87         } else if (isAlpha(c)) {
88             try self.identifier();
89         } else {
90             try lox.scanError(self.line, "Unexpected character.");
91         },
92     }
93 }
94
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);
100 }
101
102 fn number(self: *Scanner) !void {
103     while (isDigit(self.peek())) _ = self.advance();
104
105     // Look for a fractional part.
106     if (self.peek() == '.' and isDigit(self.peekNext())) {
107         // Consume the "."
108         _ = self.advance();
109
110         while (isDigit(self.peek())) _ = self.advance();
111     }
112
113     try self.addToken(.number, .{ .number = std.fmt.parseFloat(f64, self.source[self.start..self.current]) catch unreachable });
114 }
115
116 fn string(self: *Scanner) !void {
117     while (self.peek() != '"' and !self.isAtEnd()) {
118         if (self.peek() == '\n') self.line += 1;
119         _ = self.advance();
120     }
121
122     if (self.isAtEnd()) {
123         try lox.scanError(self.line, "Unterminated string.");
124         return;
125     }
126
127     // The closing ".
128     _ = self.advance();
129
130     const value = self.source[self.start + 1 .. self.current - 1];
131     try self.addToken(.string, .{ .string = value });
132 }
133
134 fn match(self: *Scanner, expected: u8) bool {
135     if (self.isAtEnd()) return false;
136     if (self.source[self.current] != expected) return false;
137     self.current += 1;
138     return true;
139 }
140
141 fn peek(self: *Scanner) u8 {
142     if (self.isAtEnd()) return 0;
143     return self.source[self.current];
144 }
145
146 fn peekNext(self: *Scanner) u8 {
147     if (self.current + 1 >= self.source.len) return 0;
148     return self.source[self.current + 1];
149 }
150
151 fn isAlpha(c: u8) bool {
152     return isAlphabetic(c) or c == '_';
153 }
154
155 fn isAlphanumeric(c: u8) bool {
156     return isAlpha(c) or isDigit(c);
157 }
158
159 fn isAtEnd(self: *Scanner) bool {
160     return self.current >= self.source.len;
161 }
162
163 fn advance(self: *Scanner) u8 {
164     defer self.current += 1;
165     return self.source[self.current];
166 }
167
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));
171 }