Skip to content

Commit

Permalink
feat: Handle multiline input in REPL
Browse files Browse the repository at this point in the history
closes #218
  • Loading branch information
giann committed Jul 31, 2024
1 parent 7528467 commit dfbf000
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 38 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ list[?10] == null;
"hello"[?20] == null;
```
- In REPL, user input is syntax highlighted (https://github.com/buzz-language/buzz/issues/217)
- User input is syntax highlighted in REPL (https://github.com/buzz-language/buzz/issues/217)
- REPL handles multilines input (https://github.com/buzz-language/buzz/issues/218)

## Modified
- Enum can now have `rg`, `ud`, `void`, `pat` has value type
Expand Down
16 changes: 8 additions & 8 deletions src/Codegen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ pub inline fn currentCode(self: *Self) usize {

pub fn generate(self: *Self, ast: Ast) Error!?*obj.ObjFunction {
self.ast = ast;
self.reporter.had_error = false;
self.reporter.last_error = null;
self.reporter.panic_mode = false;

// if (BuildOptions.debug) {
Expand All @@ -168,7 +168,7 @@ pub fn generate(self: *Self, ast: Ast) Error!?*obj.ObjFunction {

const function = self.generateNode(self.ast.root.?, null);

return if (self.reporter.had_error) null else function;
return if (self.reporter.last_error != null) null else function;
}

pub fn emit(self: *Self, location: Ast.TokenIndex, code: u32) !void {
Expand Down Expand Up @@ -735,7 +735,7 @@ fn generateBinary(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*o
.Integer => .OP_ADD_I,
.Float => .OP_ADD_F,
else => other: {
std.debug.assert(self.reporter.had_error);
std.debug.assert(self.reporter.last_error != null);

break :other .OP_ADD_I;
},
Expand Down Expand Up @@ -1367,7 +1367,7 @@ fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj
.Map => .OP_MAP_INVOKE,
.Range => .OP_RANGE_INVOKE,
else => unexpected: {
std.debug.assert(self.reporter.had_error);
std.debug.assert(self.reporter.last_error != null);
break :unexpected .OP_INSTANCE_INVOKE;
},
},
Expand All @@ -1380,7 +1380,7 @@ fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj
.Map => obj.ObjMap.members_name.get(member_lexeme).?,
.Range => obj.ObjRange.members_name.get(member_lexeme).?,
else => unexpected: {
std.debug.assert(self.reporter.had_error);
std.debug.assert(self.reporter.last_error != null);
break :unexpected 0;
},
},
Expand Down Expand Up @@ -1659,7 +1659,7 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.
}
},

else => std.debug.assert(self.reporter.had_error),
else => std.debug.assert(self.reporter.last_error != null),
}

try self.patchOptJumps(node);
Expand Down Expand Up @@ -2128,7 +2128,7 @@ fn generateForEach(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*
.String => obj.ObjString.cast(iterable).?.string.len == 0,
.Enum => obj.ObjEnum.cast(iterable).?.cases.len == 0,
.Range => obj.ObjRange.cast(iterable).?.high == obj.ObjRange.cast(iterable).?.low,
else => self.reporter.had_error,
else => self.reporter.last_error != null,
}) {
try self.patchOptJumps(node);
return null;
Expand All @@ -2154,7 +2154,7 @@ fn generateForEach(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*
.Fiber => .OP_FIBER_FOREACH,
.Range => .OP_RANGE_FOREACH,
else => unexpected: {
std.debug.assert(self.reporter.had_error);
std.debug.assert(self.reporter.last_error != null);
break :unexpected .OP_STRING_FOREACH;
},
},
Expand Down
39 changes: 29 additions & 10 deletions src/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,21 @@ pub fn consume(self: *Self, tag: Token.Type, message: []const u8) !void {
return;
}

self.reportErrorAtCurrent(.syntax, message);
// If in repl mode and unclosed brace, we dont print out the error
switch (tag) {
.RightBracket,
.RightParen,
.RightBrace,
=> {
if (self.flavor == .Repl) {
self.reporter.panic_mode = true;
self.reporter.last_error = .unclosed;
} else {
self.reportErrorAtCurrent(.unclosed, message);
}
},
else => self.reportErrorAtCurrent(.syntax, message),
}
}

// Check next token
Expand Down Expand Up @@ -836,7 +850,7 @@ pub fn parse(self: *Self, source: []const u8, file_name: []const u8) !?Ast {

try self.beginFrame(function_type, function_node, null);

self.reporter.had_error = false;
self.reporter.last_error = null;
self.reporter.panic_mode = false;

try self.advancePastEof();
Expand Down Expand Up @@ -945,9 +959,9 @@ pub fn parse(self: *Self, source: []const u8, file_name: []const u8) !?Ast {

self.ast.nodes.items(.components)[function_node].Function.entry = entry;

self.ast.root = if (self.reporter.had_error) null else self.endFrame();
self.ast.root = if (self.reporter.last_error != null) null else self.endFrame();

return if (self.reporter.had_error) null else self.ast;
return if (self.reporter.last_error != null) null else self.ast;
}

fn beginFrame(self: *Self, function_type: obj.ObjFunction.FunctionType, function_node: Ast.Node.Index, this: ?*obj.ObjTypeDef) !void {
Expand Down Expand Up @@ -1201,7 +1215,7 @@ fn block(self: *Self, loop_scope: ?LoopScope) Error!Ast.Node.Index {
}
}

try self.consume(.RightBrace, "Expected `}}` after block.");
try self.consume(.RightBrace, "Expected `}` after block.");

return try self.ast.appendNode(
.{
Expand Down Expand Up @@ -1670,13 +1684,13 @@ pub fn resolveGlobal(self: *Self, prefix: ?[]const u8, name: []const u8) Error!?
return i;
// Is it an import prefix?
} else if (global.prefix != null and std.mem.eql(u8, name, global.prefix.?)) {
const had_error = self.reporter.had_error;
const had_error = self.reporter.last_error != null;

try self.consume(.Dot, "Expected `.` after import prefix.");
try self.consume(.Identifier, "Expected identifier after import prefix.");

// Avoid infinite recursion
if (!had_error and self.reporter.had_error) {
if (!had_error and self.reporter.last_error != null) {
return null;
}

Expand Down Expand Up @@ -3439,7 +3453,12 @@ fn list(self: *Self, _: bool) Error!Ast.Node.Index {
}

if (self.ast.tokens.items(.tag)[self.current_token.? - 1] != .RightBracket) {
self.reportErrorAtCurrent(.syntax, "Expected `]`");
if (self.flavor == .Repl) {
self.reporter.panic_mode = true;
self.reporter.last_error = .unclosed;
} else {
self.reportErrorAtCurrent(.unclosed, "Expected `]`");
}
}

item_type = item_type orelse common_type;
Expand Down Expand Up @@ -3659,7 +3678,7 @@ fn call(self: *Self, _: bool, callee: Ast.Node.Index) Error!Ast.Node.Index {
.callee = callee,
// We do this because the callee type will change in the dot.call usecase
.callee_type_def = self.ast.nodes.items(.type_def)[callee] orelse callee_td: {
std.debug.assert(self.reporter.had_error);
std.debug.assert(self.reporter.last_error != null);

break :callee_td self.gc.type_registry.void_type;
},
Expand Down Expand Up @@ -7766,7 +7785,7 @@ fn importStatement(self: *Self) Error!Ast.Node.Index {

try self.consume(.Semicolon, "Expected `;` after statement.");

const import = if (!self.reporter.had_error)
const import = if (self.reporter.last_error == null)
try self.importScript(
path_token,
file_name,
Expand Down
17 changes: 9 additions & 8 deletions src/Reporter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ const io = @import("io.zig");

const Self = @This();

allocator: std.mem.Allocator,
panic_mode: bool = false,
last_error: ?Error = null,
error_prefix: ?[]const u8 = null,

// Do not reorder without updating documentation, values are explicit so they can be retrieved easily
pub const Error = enum(u8) {
already_conforming_protocol = 0,
Expand Down Expand Up @@ -113,6 +118,7 @@ pub const Error = enum(u8) {
constant_property = 96,
tuple_limit = 97,
mix_tuple = 98,
unclosed = 99,
};

// Inspired by https://github.com/zesterer/ariadne
Expand Down Expand Up @@ -501,11 +507,6 @@ pub const Report = struct {
}
};

allocator: std.mem.Allocator,
panic_mode: bool = false,
had_error: bool = false,
error_prefix: ?[]const u8 = null,

pub fn warn(self: *Self, error_type: Error, token: Token, message: []const u8) void {
var error_report = Report{
.message = message,
Expand All @@ -525,7 +526,7 @@ pub fn warn(self: *Self, error_type: Error, token: Token, message: []const u8) v

pub fn report(self: *Self, error_type: Error, token: Token, message: []const u8) void {
self.panic_mode = true;
self.had_error = true;
self.last_error = error_type;

var error_report = Report{
.message = message,
Expand Down Expand Up @@ -607,7 +608,7 @@ pub fn reportWithOrigin(
};

self.panic_mode = true;
self.had_error = true;
self.last_error = error_type;

decl_report.reportStderr(self) catch @panic("Could not report error");
}
Expand Down Expand Up @@ -682,7 +683,7 @@ pub fn reportTypeCheck(
};

self.panic_mode = true;
self.had_error = true;
self.last_error = error_type;

check_report.reportStderr(self) catch @panic("Could not report error");
}
Expand Down
55 changes: 47 additions & 8 deletions src/repl.zig
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ const CodeGen = @import("Codegen.zig");
const Scanner = @import("Scanner.zig");
const io = @import("io.zig");

pub const PROMPT = "> ";
pub const PROMPT = ">>> ";
pub const MULTILINE_PROMPT = "... ";

pub fn printBanner(out: anytype, full: bool) void {
out.print(
Expand Down Expand Up @@ -163,14 +164,22 @@ pub fn repl(allocator: std.mem.Allocator) !void {
var previous_parser_globals = try parser.globals.clone();
var previous_globals = try vm.globals.clone();
var previous_type_registry = try gc.type_registry.registry.clone();
var previous_input: ?[]u8 = null;

while (true) {
const read_source = ln.linenoise(PROMPT);
const read_source = ln.linenoise(
if (previous_input != null)
MULTILINE_PROMPT
else
PROMPT,
);

if (read_source == null) {
std.process.exit(0);
}

const source = std.mem.span(read_source.?);
var source = std.mem.span(read_source.?);
const original_source = source;

_ = ln.linenoiseHistoryAdd(source);
_ = ln.linenoiseHistorySave(@ptrCast(buzz_history_path.items.ptr));
Expand All @@ -180,14 +189,36 @@ pub fn repl(allocator: std.mem.Allocator) !void {
var source_scanner = Scanner.init(
gc.allocator,
"REPL",
source,
original_source,
);
// Go up one line, erase it
stdout.print("\x1b[1A\r\x1b[2K{s}", .{PROMPT}) catch unreachable;
stdout.print(
"\x1b[1A\r\x1b[2K{s}",
.{
if (previous_input != null)
MULTILINE_PROMPT
else
PROMPT,
},
) catch unreachable;
// Output highlighted user input
source_scanner.highlight(stdout, true_color);
stdout.writeAll("\n") catch unreachable;

if (previous_input) |previous| {
source = std.mem.concatWithSentinel(
gc.allocator,
u8,
&.{
previous,
source,
},
0,
) catch @panic("Out of memory");
gc.allocator.free(previous);
previous_input = null;
}

const expr = runSource(
source,
"REPL",
Expand All @@ -203,7 +234,7 @@ pub fn repl(allocator: std.mem.Allocator) !void {
break :failed null;
};

if (!parser.reporter.had_error and !codegen.reporter.had_error) {
if (parser.reporter.last_error == null and codegen.reporter.last_error == null) {
// FIXME: why can't I deinit those?
// previous_parser_globals.deinit();
previous_parser_globals = try parser.globals.clone();
Expand Down Expand Up @@ -249,11 +280,17 @@ pub fn repl(allocator: std.mem.Allocator) !void {

// gc.type_registry.registry.deinit();
gc.type_registry.registry = previous_type_registry;

// If syntax error was unclosed block, keep previous input
if (parser.reporter.last_error == .unclosed) {
previous_input = gc.allocator.alloc(u8, source.len) catch @panic("Out of memory");
std.mem.copyForwards(u8, previous_input.?, source);
}
}

parser.reporter.had_error = false;
parser.reporter.last_error = null;
parser.reporter.panic_mode = false;
codegen.reporter.had_error = false;
codegen.reporter.last_error = null;
codegen.reporter.panic_mode = false;
}
}
Expand Down Expand Up @@ -316,6 +353,8 @@ fn runSource(
},
);
}
} else if (parser.reporter.last_error == .unclosed) {
return null;
} else {
return CompileError.Recoverable;
}
Expand Down
6 changes: 3 additions & 3 deletions src/wasm_repl.zig
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ pub export fn runLine(ctx: *ReplCtx) void {
break :failed null;
};

if (!ctx.parser.reporter.had_error and !ctx.codegen.reporter.had_error) {
if (ctx.parser.reporter.last_error == null and ctx.codegen.reporter.last_error == null) {
// FIXME: why can't I deinit those?
// previous_parser_globals.deinit();
previous_parser_globals = ctx.parser.globals.clone() catch unreachable;
Expand Down Expand Up @@ -173,9 +173,9 @@ pub export fn runLine(ctx: *ReplCtx) void {
ctx.vm.gc.type_registry.registry = previous_type_registry;
}

ctx.parser.reporter.had_error = false;
ctx.parser.reporter.last_error = null;
ctx.parser.reporter.panic_mode = false;
ctx.codegen.reporter.had_error = false;
ctx.codegen.reporter.last_error = null;
ctx.codegen.reporter.panic_mode = false;
}

Expand Down

0 comments on commit dfbf000

Please sign in to comment.