const std = @import("std"); const fail = @import("fail.zig"); pub const Position = struct { line: usize = 0, column: usize = 0, }; pub const Token = struct { pos: Position, tok: union(enum) { identifier: []const u8, scalar: usize, lbrace: void, rbrace: void, lparen: void, rparen: void, lbracket: void, rbracket: void, colon: void, comma: void, bang: void, eof: void, }, pub fn describe(self: Token) []const u8 { return switch (self.tok) { .identifier => "identifier", .scalar => "scalar", .lbrace => "left brace", .rbrace => "right brace", .lparen => "left parenthesis", .rparen => "right parenthesis", .lbracket => "left bracket", .rbracket => "right bracket", .bang => "exclamation point", .colon => "colon", .eof => "end of file", }; } }; fn is_ident_start(c: u8) bool { return std.ascii.isAlphabetic(c) or (c == '_'); } fn is_ident_continue(c: u8) bool { return std.ascii.isAlphanumeric(c) or (c == '_'); } pub fn tokenize(buf: []const u8, alloc: std.mem.Allocator) !std.ArrayList(Token) { var res = std.ArrayList(Token).empty; var current_pos: Position = .{ .line = 1, .column = 1 }; var current_idx: usize = 0; while (current_idx < buf.len) { if (is_ident_start(buf[current_idx])) { const start = current_idx; while (current_idx < buf.len and is_ident_continue(buf[current_idx])) { current_idx += 1; } try res.append(alloc, .{ .tok = .{ .identifier = buf[start..current_idx], }, .pos = current_pos, }); current_pos.column += current_idx - start; } else if (std.ascii.isDigit(buf[current_idx])) { const start = current_idx; while (current_idx < buf.len and std.ascii.isDigit(buf[current_idx])) { current_idx += 1; } try res.append(alloc, .{ .tok = .{ .scalar = try std.fmt.parseInt(usize, buf[start..current_idx], 10), }, .pos = current_pos, }); current_pos.column += current_idx - start; } else if (buf[current_idx] == '\n') { current_idx += 1; current_pos.line += 1; current_pos.column = 1; } else if (std.ascii.isWhitespace(buf[current_idx])) { current_idx += 1; current_pos.column += 1; } else if (buf[current_idx] == '#') { while (current_idx < buf.len and buf[current_idx] != '\n') { current_idx += 1; current_pos.column += 1; } } else { switch (buf[current_idx]) { '{' => try res.append(alloc, .{ .tok = .lbrace, .pos = current_pos }), '}' => try res.append(alloc, .{ .tok = .rbrace, .pos = current_pos }), '(' => try res.append(alloc, .{ .tok = .lparen, .pos = current_pos }), ')' => try res.append(alloc, .{ .tok = .rparen, .pos = current_pos }), '[' => try res.append(alloc, .{ .tok = .lbracket, .pos = current_pos }), ']' => try res.append(alloc, .{ .tok = .rbracket, .pos = current_pos }), ':' => try res.append(alloc, .{ .tok = .colon, .pos = current_pos }), ',' => try res.append(alloc, .{ .tok = .comma, .pos = current_pos }), '!' => try res.append(alloc, .{ .tok = .bang, .pos = current_pos }), else => fail.fail(current_pos, "invalid token"), } current_idx += 1; current_pos.column += 1; } } try res.append(alloc, .{ .tok = .eof, .pos = current_pos }); return res; }