aboutsummaryrefslogtreecommitdiff
path: root/src/z/parser/js/lexical_grammar.zig
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2026-07-30 09:36:34 +0200
committerNathan Reiner <nathan@nathanreiner.xyz>2026-07-30 09:36:34 +0200
commitaca38f5c96709f56db75a476051eb644de644d3d (patch)
tree1ce90ccffb80cff8d527be6efbd073031fccd3a1 /src/z/parser/js/lexical_grammar.zig
parent006674d7e08146a9bd980e2138ce9444d2e40e6f (diff)
restructure project
Diffstat (limited to 'src/z/parser/js/lexical_grammar.zig')
-rw-r--r--src/z/parser/js/lexical_grammar.zig1137
1 files changed, 1137 insertions, 0 deletions
diff --git a/src/z/parser/js/lexical_grammar.zig b/src/z/parser/js/lexical_grammar.zig
new file mode 100644
index 0000000..2211fbe
--- /dev/null
+++ b/src/z/parser/js/lexical_grammar.zig
@@ -0,0 +1,1137 @@
+const std = @import("std");
+
+const ast = @import("ast.zig");
+pub const Loc = ast.Loc;
+
+pub const TokenType = enum {
+ // Keywords
+ await,
+ @"break",
+ case,
+ @"catch",
+ class,
+ @"const",
+ @"continue",
+ debugger,
+ default,
+ delete,
+ do,
+ @"else",
+ @"enum",
+ @"export",
+ extends,
+ false,
+ finally,
+ @"for",
+ function,
+ @"if",
+ import,
+ in,
+ instanceof,
+ let,
+ new,
+ null,
+ @"return",
+ super,
+ @"switch",
+ this,
+ throw,
+ true,
+ @"try",
+ typeof,
+ @"var",
+ void,
+ @"while",
+ with,
+ yield,
+ using,
+ async,
+ static,
+ get,
+ set,
+ of,
+
+ // Identifiers and literals
+ identifier,
+ private_identifier,
+ number,
+ string,
+ bigint,
+ regex,
+ template_head,
+ template_middle,
+ template_tail,
+ no_sub_template,
+
+ // Punctuators
+ lbrace,
+ rbrace,
+ lparen,
+ rparen,
+ lbracket,
+ rbracket,
+ dot,
+ semicolon,
+ comma,
+ @"...",
+ @"<",
+ @">",
+ @"<=",
+ @">=",
+ @"==",
+ @"!=",
+ @"===",
+ @"!==",
+ @"+",
+ @"-",
+ @"*",
+ @"%",
+ @"**",
+ @"++",
+ @"--",
+ @"<<",
+ @">>",
+ @">>>",
+ @"&",
+ @"|",
+ @"^",
+ @"!",
+ @"~",
+ @"&&",
+ @"||",
+ @"??",
+ @"?",
+ @":",
+ @"=",
+ @"+=",
+ @"-=",
+ @"*=",
+ @"%=",
+ @"**=",
+ @"<<=",
+ @">>=",
+ @">>>=",
+ @"&=",
+ @"|=",
+ @"^=",
+ @"&&=",
+ @"||=",
+ @"??=",
+ @"=>",
+ @"/",
+ @"/=",
+ @"?.",
+ @".",
+ @"#",
+
+ // Special
+ from,
+ eof,
+ unknown,
+
+ pub fn isKeyword(self: TokenType) bool {
+ return switch (self) {
+ .await,
+ .@"break",
+ .case,
+ .@"catch",
+ .class,
+ .@"const",
+ .@"continue",
+ .debugger,
+ .default,
+ .delete,
+ .do,
+ .@"else",
+ .@"enum",
+ .@"export",
+ .extends,
+ .false,
+ .finally,
+ .@"for",
+ .function,
+ .@"if",
+ .import,
+ .in,
+ .instanceof,
+ .let,
+ .new,
+ .null,
+ .@"return",
+ .super,
+ .@"switch",
+ .this,
+ .throw,
+ .true,
+ .@"try",
+ .typeof,
+ .@"var",
+ .void,
+ .@"while",
+ .with,
+ .yield,
+ .using,
+ .async,
+ .static,
+ .get,
+ .set,
+ .of,
+ .from,
+ => true,
+ else => false,
+ };
+ }
+
+ pub fn isIdentifier(self: TokenType) bool {
+ return self == .identifier or self.isKeyword();
+ }
+
+ pub fn isKeywordButNotLet(self: TokenType) bool {
+ return self.isKeyword() and self != .let;
+ }
+};
+
+pub const Token = struct {
+ kind: TokenType,
+ loc: Loc,
+ slice: []const u8,
+};
+
+pub const Lexer = struct {
+ source: []const u8,
+ pos: usize,
+
+ const Self = @This();
+
+ pub fn init(source: []const u8) Self {
+ return .{ .source = source, .pos = 0 };
+ }
+
+ pub fn tokenize(source: []const u8) []const Token {
+ var buf: [4096]Token = undefined;
+ return tokenizeBuf(source, &buf);
+ }
+
+ pub fn tokenizeBuf(source: []const u8, tokens: *[4096]Token) []const Token {
+ @setEvalBranchQuota(100000);
+ var lexer = Self.init(source);
+ var count: usize = 0;
+
+ while (true) {
+ const tok = lexer.next();
+ tokens[count] = tok;
+ count += 1;
+ if (tok.kind == .eof) break;
+ if (count >= tokens.len - 1) {
+ tokens[count] = .{ .kind = .eof, .loc = .{ .start = lexer.pos, .end = lexer.pos }, .slice = "" };
+ break;
+ }
+ }
+
+ return tokens[0..count];
+ }
+
+ fn ch(self: Self) ?u8 {
+ if (self.pos >= self.source.len) return null;
+ return self.source[self.pos];
+ }
+
+ fn chAt(self: Self, offset: usize) ?u8 {
+ const idx = self.pos + offset;
+ if (idx >= self.source.len) return null;
+ return self.source[idx];
+ }
+
+ fn advance(self: *Self) void {
+ if (self.pos < self.source.len) self.pos += 1;
+ }
+
+ fn isWhiteSpace(c: u8) bool {
+ return switch (c) {
+ ' ', '\t', 0x0B, 0x0C => true,
+ else => false,
+ };
+ }
+
+ fn isLineTerminator(c: u8) bool {
+ return switch (c) {
+ '\n', '\r' => true,
+ else => false,
+ };
+ }
+
+ fn isDigit(c: u8) bool {
+ return c >= '0' and c <= '9';
+ }
+
+ fn isHexDigit(c: u8) bool {
+ return switch (c) {
+ '0'...'9', 'a'...'f', 'A'...'F' => true,
+ else => false,
+ };
+ }
+
+ fn isOctalDigit(c: u8) bool {
+ return c >= '0' and c <= '7';
+ }
+
+ fn isIdStart(c: u8) bool {
+ return switch (c) {
+ 'a'...'z', 'A'...'Z', '_', '$' => true,
+ else => c > 0x7F,
+ };
+ }
+
+ fn isIdContinue(c: u8) bool {
+ return isIdStart(c) or isDigit(c) or c == 0x200C or c == 0x200D;
+ }
+
+ fn skipWhiteSpace(self: *Self) void {
+ while (self.ch()) |c| {
+ if (isWhiteSpace(c) or isLineTerminator(c)) {
+ self.advance();
+ } else {
+ break;
+ }
+ }
+ }
+
+ fn skipSingleLineComment(self: *Self) void {
+ self.advance();
+ self.advance();
+ while (self.ch()) |c| {
+ if (isLineTerminator(c)) break;
+ self.advance();
+ }
+ }
+
+ fn skipMultiLineComment(self: *Self) void {
+ self.advance();
+ self.advance();
+ while (self.ch()) |c| {
+ if (c == '*') {
+ if (self.chAt(1)) |n| {
+ if (n == '/') {
+ self.advance();
+ self.advance();
+ return;
+ }
+ }
+ }
+ self.advance();
+ }
+ }
+
+ fn skipHashbang(self: *Self) void {
+ if (self.ch()) |c| {
+ if (c == '#') {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '!') {
+ self.advance();
+ while (self.ch()) |c2| {
+ if (isLineTerminator(c2)) break;
+ self.advance();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fn scanString(self: *Self, quote: u8) Token {
+ const start = self.pos;
+ self.advance();
+
+ while (self.ch()) |c| {
+ if (c == quote) {
+ self.advance();
+ return .{
+ .kind = .string,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start + 1 .. self.pos - 1],
+ };
+ }
+ if (c == '\\') {
+ self.advance();
+ if (self.ch()) |_| self.advance();
+ } else if (isLineTerminator(c) and c != 0x2028 and c != 0x2029) {
+ break;
+ } else {
+ self.advance();
+ }
+ }
+
+ return .{
+ .kind = .string,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start + 1 .. self.pos],
+ };
+ }
+
+ fn scanTemplate(self: *Self, start_kind: TokenType) Token {
+ const start = self.pos;
+ if (start_kind == .template_middle or start_kind == .template_head) {
+ self.advance();
+ }
+ return self.scanTemplateRest(start);
+ }
+
+ fn scanTemplateRest(self: *Self, start: usize) Token {
+ while (self.ch()) |c| {
+ if (c == '`') {
+ self.advance();
+ return .{
+ .kind = .no_sub_template,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ if (c == '$') {
+ if (self.chAt(1)) |n| {
+ if (n == '{') {
+ self.advance();
+ self.advance();
+ return .{
+ .kind = .template_head,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 2],
+ };
+ }
+ }
+ }
+ if (c == '\\') {
+ self.advance();
+ if (self.ch()) |_| self.advance();
+ } else {
+ self.advance();
+ }
+ }
+
+ return .{
+ .kind = .no_sub_template,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanTemplateMiddleOrTail(self: *Self) Token {
+ const start = self.pos;
+ self.advance();
+
+ while (self.ch()) |c| {
+ if (c == '`') {
+ self.advance();
+ return .{
+ .kind = .template_tail,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ if (c == '$') {
+ if (self.chAt(1)) |n| {
+ if (n == '{') {
+ self.advance();
+ self.advance();
+ return .{
+ .kind = .template_middle,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 2],
+ };
+ }
+ }
+ }
+ if (c == '\\') {
+ self.advance();
+ if (self.ch()) |_| self.advance();
+ } else {
+ self.advance();
+ }
+ }
+
+ return .{
+ .kind = .template_tail,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanNumber(self: *Self) Token {
+ const start = self.pos;
+
+ if (self.ch()) |c| {
+ if (c == '0') {
+ self.advance();
+ if (self.ch()) |n| {
+ switch (n) {
+ 'x', 'X' => return self.scanHex(start),
+ 'b', 'B' => return self.scanBinary(start),
+ 'o', 'O' => return self.scanOctal(start),
+ 'n' => {
+ self.advance();
+ return .{
+ .kind = .bigint,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ },
+ 'e', 'E' => return self.scanDecimalAfterInt(start),
+ '.', '0'...'9' => return self.scanDecimalAfterInt(start),
+ else => {},
+ }
+ }
+ }
+ }
+
+ return self.scanDecimal(start);
+ }
+
+ fn scanDecimal(self: *Self, start: usize) Token {
+ while (self.ch()) |c| {
+ if (!isDigit(c) and c != '_') break;
+ self.advance();
+ }
+
+ if (self.ch()) |c| {
+ if (c == '.') {
+ self.advance();
+ while (self.ch()) |c2| {
+ if (!isDigit(c2) and c2 != '_') break;
+ self.advance();
+ }
+ }
+ if (self.ch()) |c2| {
+ if (c2 == 'e' or c2 == 'E') {
+ return self.scanExponent(start);
+ }
+ }
+ if (self.ch()) |c2| {
+ if (c2 == 'n') {
+ self.advance();
+ return .{
+ .kind = .bigint,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ }
+ }
+
+ return .{
+ .kind = .number,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanDecimalAfterInt(self: *Self, start: usize) Token {
+ while (self.ch()) |c| {
+ if (!isDigit(c) and c != '_') break;
+ self.advance();
+ }
+ if (self.ch()) |c| {
+ if (c == '.') {
+ self.advance();
+ while (self.ch()) |c2| {
+ if (!isDigit(c2) and c2 != '_') break;
+ self.advance();
+ }
+ }
+ if (self.ch()) |c2| {
+ if (c2 == 'e' or c2 == 'E') {
+ return self.scanExponent(start);
+ }
+ }
+ if (self.ch()) |c2| {
+ if (c2 == 'n') {
+ self.advance();
+ return .{
+ .kind = .bigint,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ }
+ }
+ return .{
+ .kind = .number,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanExponent(self: *Self, start: usize) Token {
+ self.advance();
+ if (self.ch()) |c| {
+ if (c == '+' or c == '-') self.advance();
+ }
+ while (self.ch()) |c| {
+ if (!isDigit(c) and c != '_') break;
+ self.advance();
+ }
+ return .{
+ .kind = .number,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanHex(self: *Self, start: usize) Token {
+ self.advance();
+ while (self.ch()) |c| {
+ if (!isHexDigit(c) and c != '_') break;
+ self.advance();
+ }
+ if (self.ch()) |c| {
+ if (c == 'n') {
+ self.advance();
+ return .{
+ .kind = .bigint,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ }
+ return .{
+ .kind = .number,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanBinary(self: *Self, start: usize) Token {
+ self.advance();
+ while (self.ch()) |c| {
+ if (c != '0' and c != '1' and c != '_') break;
+ self.advance();
+ }
+ if (self.ch()) |c| {
+ if (c == 'n') {
+ self.advance();
+ return .{
+ .kind = .bigint,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ }
+ return .{
+ .kind = .number,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanOctal(self: *Self, start: usize) Token {
+ self.advance();
+ while (self.ch()) |c| {
+ if (!isOctalDigit(c) and c != '_') break;
+ self.advance();
+ }
+ if (self.ch()) |c| {
+ if (c == 'n') {
+ self.advance();
+ return .{
+ .kind = .bigint,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start .. self.pos - 1],
+ };
+ }
+ }
+ return .{
+ .kind = .number,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanIdentifierOrKeyword(self: *Self) Token {
+ const start = self.pos;
+ while (self.ch()) |c| {
+ if (!isIdContinue(c)) break;
+ self.advance();
+ }
+
+ const slice = self.source[start..self.pos];
+ const kind = keywordFromString(slice);
+
+ return .{
+ .kind = kind,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = slice,
+ };
+ }
+
+ fn scanPrivateIdentifier(self: *Self) Token {
+ const start = self.pos;
+ self.advance();
+ while (self.ch()) |c| {
+ if (!isIdContinue(c)) break;
+ self.advance();
+ }
+ return .{
+ .kind = .private_identifier,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanRegex(self: *Self) Token {
+ const start = self.pos;
+ self.advance();
+
+ var in_class = false;
+ while (self.ch()) |c| {
+ if (c == '\\') {
+ self.advance();
+ if (self.ch()) |_| self.advance();
+ } else if (c == '[') {
+ in_class = true;
+ self.advance();
+ } else if (c == ']') {
+ in_class = false;
+ self.advance();
+ } else if (c == '/' and !in_class) {
+ self.advance();
+ break;
+ } else if (isLineTerminator(c)) {
+ break;
+ } else {
+ self.advance();
+ }
+ }
+
+ while (self.ch()) |c| {
+ if (!isIdContinue(c)) break;
+ self.advance();
+ }
+
+ return .{
+ .kind = .regex,
+ .loc = .{ .start = start, .end = self.pos },
+ .slice = self.source[start..self.pos],
+ };
+ }
+
+ fn scanPunctuator(self: *Self) Token {
+ const start = self.pos;
+ const c = self.ch().?;
+
+ switch (c) {
+ '{' => {
+ self.advance();
+ return simple(.lbrace, start);
+ },
+ '}' => {
+ self.advance();
+ return simple(.rbrace, start);
+ },
+ '(' => {
+ self.advance();
+ return simple(.lparen, start);
+ },
+ ')' => {
+ self.advance();
+ return simple(.rparen, start);
+ },
+ '[' => {
+ self.advance();
+ return simple(.lbracket, start);
+ },
+ ']' => {
+ self.advance();
+ return simple(.rbracket, start);
+ },
+ ';' => {
+ self.advance();
+ return simple(.semicolon, start);
+ },
+ ',' => {
+ self.advance();
+ return simple(.comma, start);
+ },
+ ':' => {
+ self.advance();
+ return simple(.@":", start);
+ },
+ '?' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '?') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"??=", start);
+ }
+ }
+ return simple(.@"??", start);
+ }
+ if (n == '.') {
+ if (self.chAt(1)) |n2| {
+ if (!isDigit(n2)) {
+ self.advance();
+ return simple(.@"?.", start);
+ }
+ } else {
+ self.advance();
+ return simple(.@"?.", start);
+ }
+ }
+ }
+ return simple(.@"?", start);
+ },
+ '~' => {
+ self.advance();
+ return simple(.@"~", start);
+ },
+ '.' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '.') {
+ if (self.chAt(1)) |n2| {
+ if (n2 == '.') {
+ self.advance();
+ self.advance();
+ return simple(.@"...", start);
+ }
+ }
+ }
+ }
+ return simple(.@".", start);
+ },
+ '#' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (isIdStart(n)) {
+ return self.scanPrivateIdentifier();
+ }
+ }
+ return simple(.@"#", start);
+ },
+ '+' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '+') {
+ self.advance();
+ return simple(.@"++", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"+=", start);
+ }
+ }
+ return simple(.@"+", start);
+ },
+ '-' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '-') {
+ self.advance();
+ return simple(.@"--", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"-=", start);
+ }
+ }
+ return simple(.@"-", start);
+ },
+ '*' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '*') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"**=", start);
+ }
+ }
+ return simple(.@"**", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"*=", start);
+ }
+ }
+ return simple(.@"*", start);
+ },
+ '%' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '=') {
+ self.advance();
+ return simple(.@"%=", start);
+ }
+ }
+ return simple(.@"%", start);
+ },
+ '=' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '=') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"===", start);
+ }
+ }
+ return simple(.@"==", start);
+ }
+ if (n == '>') {
+ self.advance();
+ return simple(.@"=>", start);
+ }
+ }
+ return simple(.@"=", start);
+ },
+ '!' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '=') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"!==", start);
+ }
+ }
+ return simple(.@"!=", start);
+ }
+ }
+ return simple(.@"!", start);
+ },
+ '<' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '<') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"<<=", start);
+ }
+ }
+ return simple(.@"<<", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"<=", start);
+ }
+ }
+ return simple(.@"<", start);
+ },
+ '>' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '>') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '>') {
+ self.advance();
+ if (self.ch()) |n3| {
+ if (n3 == '=') {
+ self.advance();
+ return simple(.@">>>=", start);
+ }
+ }
+ return simple(.@">>>", start);
+ }
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@">>=", start);
+ }
+ }
+ return simple(.@">>", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@">=", start);
+ }
+ }
+ return simple(.@">", start);
+ },
+ '&' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '&') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"&&=", start);
+ }
+ }
+ return simple(.@"&&", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"&=", start);
+ }
+ }
+ return simple(.@"&", start);
+ },
+ '|' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '|') {
+ self.advance();
+ if (self.ch()) |n2| {
+ if (n2 == '=') {
+ self.advance();
+ return simple(.@"||=", start);
+ }
+ }
+ return simple(.@"||", start);
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"|=", start);
+ }
+ }
+ return simple(.@"|", start);
+ },
+ '^' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '=') {
+ self.advance();
+ return simple(.@"^=", start);
+ }
+ }
+ return simple(.@"^", start);
+ },
+ '`' => {
+ self.advance();
+ return self.scanTemplateRest(start);
+ },
+ '\'' => return self.scanString('\''),
+ '"' => return self.scanString('"'),
+ '/' => {
+ self.advance();
+ if (self.ch()) |n| {
+ if (n == '/') {
+ self.skipSingleLineComment();
+ return self.next();
+ }
+ if (n == '*') {
+ self.skipMultiLineComment();
+ return self.next();
+ }
+ if (n == '=') {
+ self.advance();
+ return simple(.@"/=", start);
+ }
+ }
+ return simple(.@"/", start);
+ },
+ else => {
+ if (isLineTerminator(c) or isWhiteSpace(c)) {
+ self.skipWhiteSpace();
+ return self.next();
+ }
+ if (isDigit(c)) return self.scanNumber();
+ if (isIdStart(c)) return self.scanIdentifierOrKeyword();
+ if (c == '\\') {
+ self.advance();
+ return self.scanIdentifierOrKeyword();
+ }
+ self.advance();
+ return simple(.unknown, start);
+ },
+ }
+ }
+
+ fn simple(kind: TokenType, start: usize) Token {
+ return .{ .kind = kind, .loc = .{ .start = start, .end = start }, .slice = "" };
+ }
+
+ fn simpleWithSlice(comptime kind: TokenType, start: usize, slice: []const u8) Token {
+ return .{ .kind = kind, .loc = .{ .start = start, .end = start }, .slice = slice };
+ }
+
+ pub fn next(self: *Self) Token {
+ if (self.ch()) |c| {
+ _ = c;
+ // Handle hashbang at beginning
+ if (self.pos == 0) {
+ if (self.ch()) |c2| {
+ if (c2 == '#') {
+ self.skipHashbang();
+ return self.next();
+ }
+ }
+ }
+ return self.scanPunctuator();
+ }
+ return simple(.eof, self.pos);
+ }
+
+ fn keywordFromString(s: []const u8) TokenType {
+ const keywords = comptime keywords: {
+ const arr = struct {
+ const data = [_]struct { key: []const u8, val: TokenType }{
+ .{ .key = "await", .val = .await },
+ .{ .key = "break", .val = .@"break" },
+ .{ .key = "case", .val = .case },
+ .{ .key = "catch", .val = .@"catch" },
+ .{ .key = "class", .val = .class },
+ .{ .key = "const", .val = .@"const" },
+ .{ .key = "continue", .val = .@"continue" },
+ .{ .key = "debugger", .val = .debugger },
+ .{ .key = "default", .val = .default },
+ .{ .key = "delete", .val = .delete },
+ .{ .key = "do", .val = .do },
+ .{ .key = "else", .val = .@"else" },
+ .{ .key = "enum", .val = .@"enum" },
+ .{ .key = "export", .val = .@"export" },
+ .{ .key = "extends", .val = .extends },
+ .{ .key = "false", .val = .false },
+ .{ .key = "finally", .val = .finally },
+ .{ .key = "for", .val = .@"for" },
+ .{ .key = "function", .val = .function },
+ .{ .key = "if", .val = .@"if" },
+ .{ .key = "import", .val = .import },
+ .{ .key = "in", .val = .in },
+ .{ .key = "instanceof", .val = .instanceof },
+ .{ .key = "let", .val = .let },
+ .{ .key = "new", .val = .new },
+ .{ .key = "null", .val = .null },
+ .{ .key = "return", .val = .@"return" },
+ .{ .key = "super", .val = .super },
+ .{ .key = "switch", .val = .@"switch" },
+ .{ .key = "this", .val = .this },
+ .{ .key = "throw", .val = .throw },
+ .{ .key = "true", .val = .true },
+ .{ .key = "try", .val = .@"try" },
+ .{ .key = "typeof", .val = .typeof },
+ .{ .key = "var", .val = .@"var" },
+ .{ .key = "void", .val = .void },
+ .{ .key = "while", .val = .@"while" },
+ .{ .key = "with", .val = .with },
+ .{ .key = "yield", .val = .yield },
+ .{ .key = "using", .val = .using },
+ .{ .key = "async", .val = .async },
+ .{ .key = "static", .val = .static },
+ .{ .key = "get", .val = .get },
+ .{ .key = "set", .val = .set },
+ .{ .key = "of", .val = .of },
+ .{ .key = "from", .val = .from },
+ };
+ };
+ break :keywords arr.data;
+ };
+ inline for (keywords) |entry| {
+ if (std.mem.eql(u8, s, entry.key)) return entry.val;
+ }
+ return .identifier;
+ }
+
+ pub fn tokenizeComptime(comptime source: []const u8) []const Token {
+ comptime {
+ return tokenize(source);
+ }
+ }
+};