UCHR7QMVW46HZU73JAZAXFA6MXYUPUBOC2GL7WJ5LL54RDJYZ2KQC
fn init_readline() !void {
readline.rl_completion_entry_function = &lox_completion;
// if (readline.rl_bind_key('\t', readline.rl_insert) != 0) {
// return error.ReadlineInitError;
// }
}
fn lox_completion(text: [*c]const u8, state: c_int) callconv(.C) [*c]u8 {
const alloc = std.heap.c_allocator;
const textptr = @ptrCast(?[*:0]const u8, text);
if (textptr) |curr| {
var slice = lox_completion_internal(alloc, std.mem.span(curr), state) catch return null;
if (slice) |comp| {
// Assume that C will properly deallocate our given pointers?
var ret = alloc.allocSentinel(u8, comp.len, 0) catch @panic("OOM");
std.mem.copy(u8, ret, comp);
return ret;
}
return null;
} else {
// Could not cast Readline text to proper pointer, so it's unsafe, so exit.
return null;
}
}
const CompletionDbGoal = enum {
// General
Name,
Value,
// Syntax
Equal,
};
const CompletionDbEntry = struct {
token: Token.Type,
goal: CompletionDbGoal,
};
const completion_db = [_]CompletionDbEntry{
.{ .token = .kw_var, .goal = .Name }, // recent names that have been used
.{ .token = .identifier, .goal = .Equal },
.{ .token = .equal, .goal = .Value }, // recent values that have been used
};
var completion_db_index: usize = 0;
fn lox_completion_internal(allocator: std.mem.Allocator, text: []const u8, state: c_int) !?[]const u8 {
// So we are only given a single
// std.log.debug("{d}", .{state});
if (state == 0) {
completion_db_index = 0;
}
// Lex the text, then check the last token
// Can't init Scanner? No completion.
var scanner = Scanner.init(allocator, text) catch return null;
defer scanner.deinit();
Error.can_output = false;
defer Error.can_output = true;
// Error in scanning tokens? No completion.
const tokens = scanner.scanTokens() catch return null;
defer {
for (tokens) |token| {
allocator.free(token.lexeme);
}
allocator.free(tokens);
}
const lastToken = tokens[tokens.len -| 2]; // Last Token before EOF
var output = std.ArrayList(u8).init(allocator);
defer output.deinit();
const writer = output.writer();
while (completion_db_index < completion_db.len) : (completion_db_index += 1) {
const entry = completion_db[completion_db_index];
if (lastToken.token_type == entry.token) {
completion_db_index += 1; // Bump so that next call doesn't find us.
switch (entry.goal) {
.Name => @panic("TODO get identifiers in scope?"),
.Equal => try writer.print("{s} =", .{text}),
.Value => @panic("TODO get values that have been used"),
}
return try output.toOwnedSlice();
}
}
return null;
}
var line_read: ?[*:0]u8 = null;
fn rl_gets(prompt: []const u8) !?[]const u8 {
if (line_read) |_| {
std.c.free(@ptrCast(?*anyopaque, line_read));
line_read = null;
}
line_read = readline.readline(@ptrCast([*c]const u8, prompt));
if (line_read) |read_line| {
readline.add_history(read_line);
return std.mem.span(read_line);
}
return null;
}
// TODO Make environment static, so that completion can access
const Normalizer = @import("ziglyph").Normalizer;
const Grapheme = @import("ziglyph").Grapheme;
const CodePoint = @import("ziglyph").CodePoint;
const ziglyph = @import("ziglyph");
const Normalizer = ziglyph.Normalizer;
const Grapheme = ziglyph.Grapheme;
const CodePoint = ziglyph.CodePoint;
fn advance(self: *Self) Grapheme {
inline fn peek(self: *Self) !u21 {
if (self.isAtEnd()) {
return 0;
}
return codepointize(self.source[self.index]);
}
fn match(self: *Self, expected: u21) !bool {
if (self.isAtEnd()) return false;
if (try self.peek() != expected) return false;
self.index += 1;
return true;
}
fn advance(self: *Self) !u21 {
fn codepointize(grapheme: Grapheme) u21 {
const bytes = grapheme.bytes;
switch (bytes.len) {
0 => unreachable,
1 => return @as(u21, bytes[0]),
else => @panic("Unsupported grapheme length"),
const KeywordEntry = struct {
kw: Token.Type,
text: []const u8,
};
const keywords = [_]KeywordEntry{
.{ .kw = .kw_var, .text = "var" },
};
fn identOrKeyword(self: *Self, ini: u21) !Token {
var ident = std.ArrayList(u21).init(self.allocator);
defer ident.deinit();
try ident.append(ini);
while (ziglyph.isAlphaNum(try self.peek())) {
try ident.append(try self.advance());
}
var ident_string = std.ArrayList(u8).init(self.allocator);
const writer = ident_string.writer();
defer ident_string.deinit();
for (ident.items) |cp| {
try writer.print("{u}", .{cp});
fn scanToken(self: *Self, list: *std.ArrayList(Token)) !void {
const c = self.advance();
// We do not support combining characters. But we don't have to,
// as we have already merged necessary characters using the Unicode NFKC process, so...
const cp = codepointize(c);
fn identifyToken(self: *Self, cp: u21) !?Token {
'(' => try self.addToken(list, .left_paren, null),
'(' => return try self.createToken(.left_paren, null),
')' => return try self.createToken(.right_paren, null),
'{' => return try self.createToken(.left_brace, null),
'}' => return try self.createToken(.right_brace, null),
',' => return try self.createToken(.comma, null),
'.' => return try self.createToken(.dot, null),
'-' => return try self.createToken(.minus, null),
'+' => return try self.createToken(.plus, null),
';' => return try self.createToken(.semicolon, null),
'*' => return try self.createToken(.star, null),
'!' => if (try self.match('=')) {
return try self.createToken(.bang_equal, null);
} else {
return try self.createToken(.bang, null);
},
'=' => if (try self.match('=')) {
return try self.createToken(.equal_equal, null);
} else {
return try self.createToken(.equal, null);
},
'>' => if (try self.match('=')) {
return try self.createToken(.greater_equal, null);
} else {
return try self.createToken(.greater, null);
},
'<' => if (try self.match('=')) {
return try self.createToken(.less_equal, null);
} else {
return try self.createToken(.less, null);
},
'/' => if (try self.match('/')) {
while (try self.peek() != '\n' and !self.isAtEnd()) {
_ = try self.advance();
}
return try self.createToken(.comment, null);
} else {
return try self.createToken(.slash, null);
},
}
}
fn scanToken(self: *Self, list: *std.ArrayList(Token)) !void {
var c = try self.advance();
var token = try self.identifyToken(c);
var unknown = std.ArrayList(u21).init(self.allocator);
defer unknown.deinit();
while (token == null) {
try unknown.append(c);
self.start = self.index; // Move up the start of the token (the previous start is unknown)
if (!self.isAtEnd()) {
c = try self.advance();
token = try self.identifyToken(c);
} else {
break;
}
}
if (unknown.items.len > 0) {
var unknown_string = std.ArrayList(u8).init(self.allocator);
const string_writer = unknown_string.writer();
defer unknown_string.deinit();
for (unknown.items) |ucp| {
try string_writer.print("{u}", .{ucp});
}
try Error.report(self.line, "lexer", "Unknown codepoints '{}'", .{std.zig.fmtEscapes(unknown_string.items)});
[macos]
run *FLAGS:
zig build run --search-prefix "$(brew --prefix readline)"
[windows]
[linux]
run *FLAGS:
zig build run
var language = "lox";
print "Hello World";
.git
.DS_Store