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