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