const std = @import("std"); pub const Token = struct { loc: Location, type: Type, pub const Type = enum { left_paren, right_paren, integer_literal, plus, minus, invalid, eof, }; }; pub const Location = struct { start: usize, end: usize, pub fn combine(a: Location, b: Location) Location { std.debug.assert(a.end <= b.start); return .{ .start = @min(a.start, b.start), .end = @max(a.end, b.end) }; } /// Assumes that the location comes directly from an `integer_literal` token. pub fn getInt(self: Location, file_source: []const u8) u64 { var value: u64 = 0; for (file_source[self.start..self.end]) |c| { std.debug.assert('0' <= c and c <= '9'); value = value * 10 + (c - '0'); } return value; } }; source: []const u8, start: usize = 0, pos: usize = 0, pub fn next(self: *Self) ?Token { self.start = self.pos; return s: switch (self.eat() orelse return self.create(.eof)) { '(' => self.create(.left_paren), ')' => self.create(.right_paren), '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => self.integerLiteral(), '+' => self.create(.plus), '-' => self.create(.minus), ' ' => { self.start = self.pos; continue :s (self.eat() orelse return self.create(.eof)); }, else => self.create(.invalid), }; } fn integerLiteral(self: *Self) Token { var value: ?u64 = self.source[self.start] - '0'; while (digitValue(self.peek())) |v| { var nxt: ?u64 = null; if (value) |val| if (std.math.mul(u64, val, 10) catch null) |p| if (std.math.add(u64, p, v) catch null) |s| { nxt = s; }; value = nxt; _ = self.eat(); } return if (value != null) self.create(.integer_literal) else self.create(.invalid); } fn create(self: *Self, tajp: Token.Type) Token { const start = self.start; return .{ .loc = .{ .start = start, .end = self.pos }, .type = tajp }; } fn eat(self: *Self) ?u8 { const token = self.peek(); if (token != null) self.pos += 1; return token; } fn peek(self: *Self) ?u8 { return if (self.pos < self.source.len) self.source[self.pos] else null; } const Self = @This(); fn digitValue(c: ?u8) ?u8 { return switch (c orelse return null) { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => c.? - '0', else => null, }; }