mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
Improve repl some more
This commit is contained in:
parent
db6b777253
commit
70dca5c01c
1 changed files with 124 additions and 67 deletions
|
|
@ -160,7 +160,6 @@ fn processInput(self: *Repl, input: []const u8) ![]const u8 {
|
|||
return try self.evaluateSource(input);
|
||||
},
|
||||
.type_decl => |info| {
|
||||
const type_name_copy = try self.allocator.dupe(u8, info.type_name);
|
||||
defer self.allocator.free(info.type_name);
|
||||
|
||||
// Store the type declaration - associated items will be canonicalized and evaluated
|
||||
|
|
@ -168,13 +167,13 @@ fn processInput(self: *Repl, input: []const u8) ![]const u8 {
|
|||
try self.past_defs.append(.{
|
||||
.source = try self.allocator.dupe(u8, input),
|
||||
.kind = .{ .type_decl = .{
|
||||
.type_name = try self.allocator.dupe(u8, type_name_copy),
|
||||
.type_name = try self.allocator.dupe(u8, info.type_name),
|
||||
.has_associated_items = info.has_associated_items,
|
||||
} },
|
||||
});
|
||||
|
||||
// Output the type name to indicate it was defined
|
||||
return try std.fmt.allocPrint(self.allocator, "{s}", .{type_name_copy});
|
||||
return try std.fmt.allocPrint(self.allocator, "{s}", .{info.type_name});
|
||||
},
|
||||
.parse_error => |msg| {
|
||||
defer self.allocator.free(msg);
|
||||
|
|
@ -204,39 +203,39 @@ fn tryParseStatement(self: *Repl, input: []const u8) !ParseResult {
|
|||
var ast = ast_const;
|
||||
defer ast.deinit(self.allocator);
|
||||
|
||||
if (ast.root_node_idx != 0) {
|
||||
const file = ast.store.getFile();
|
||||
const statements = ast.store.statementSlice(file.statements);
|
||||
if (statements.len == 1) {
|
||||
const stmt = ast.store.getStatement(statements[0]);
|
||||
switch (stmt) {
|
||||
.decl => |decl| {
|
||||
const pattern = ast.store.getPattern(decl.pattern);
|
||||
if (pattern == .ident) {
|
||||
const ident_tok = pattern.ident.ident_tok;
|
||||
const token_region = ast.tokens.resolve(@intCast(ident_tok));
|
||||
const ident = ast.env.source[token_region.start.offset..token_region.end.offset];
|
||||
const ident_copy = try self.allocator.dupe(u8, ident);
|
||||
return ParseResult{ .assignment = .{ .ident = ident_copy } };
|
||||
}
|
||||
return ParseResult.expression;
|
||||
},
|
||||
.import => return ParseResult.import,
|
||||
.type_decl => |decl| {
|
||||
const header = ast.store.getTypeHeader(decl.header) catch {
|
||||
return ParseResult.expression;
|
||||
};
|
||||
const type_name_tok = header.name;
|
||||
const token_region = ast.tokens.resolve(type_name_tok);
|
||||
const type_name = ast.env.source[token_region.start.offset..token_region.end.offset];
|
||||
// Get the file (type modules have root_node_idx = 0)
|
||||
const file = ast.store.getFile();
|
||||
const statements = ast.store.statementSlice(file.statements);
|
||||
|
||||
return ParseResult{ .type_decl = .{
|
||||
.type_name = try self.allocator.dupe(u8, type_name),
|
||||
.has_associated_items = decl.associated != null,
|
||||
} };
|
||||
},
|
||||
else => return ParseResult.expression,
|
||||
}
|
||||
if (statements.len == 1) {
|
||||
const stmt = ast.store.getStatement(statements[0]);
|
||||
switch (stmt) {
|
||||
.decl => |decl| {
|
||||
const pattern = ast.store.getPattern(decl.pattern);
|
||||
if (pattern == .ident) {
|
||||
const ident_tok = pattern.ident.ident_tok;
|
||||
const token_region = ast.tokens.resolve(@intCast(ident_tok));
|
||||
const ident = ast.env.source[token_region.start.offset..token_region.end.offset];
|
||||
const ident_copy = try self.allocator.dupe(u8, ident);
|
||||
return ParseResult{ .assignment = .{ .ident = ident_copy } };
|
||||
}
|
||||
return ParseResult.expression;
|
||||
},
|
||||
.import => return ParseResult.import,
|
||||
.type_decl => |decl| {
|
||||
const header = ast.store.getTypeHeader(decl.header) catch {
|
||||
return ParseResult.expression;
|
||||
};
|
||||
const type_name_tok = header.name;
|
||||
const token_region = ast.tokens.resolve(type_name_tok);
|
||||
const type_name = ast.env.source[token_region.start.offset..token_region.end.offset];
|
||||
|
||||
return ParseResult{ .type_decl = .{
|
||||
.type_name = try self.allocator.dupe(u8, type_name),
|
||||
.has_associated_items = decl.associated != null,
|
||||
} };
|
||||
},
|
||||
else => return ParseResult.expression,
|
||||
}
|
||||
}
|
||||
} else |_| {}
|
||||
|
|
@ -532,33 +531,8 @@ fn evaluatePureExpression(self: *Repl, expr_source: []const u8, def_ident: ?[]co
|
|||
}
|
||||
}
|
||||
|
||||
// If we have past defs, check if we can evaluate the expression
|
||||
if (self.past_defs.items.len > 0) {
|
||||
if (def_ident) |current_ident| {
|
||||
// We have a definition - check if it's a redefinition
|
||||
var is_redefinition = false;
|
||||
for (self.past_defs.items) |past_def| {
|
||||
switch (past_def.kind) {
|
||||
.assignment => |ident| {
|
||||
if (std.mem.eql(u8, ident, current_ident)) {
|
||||
is_redefinition = true;
|
||||
break;
|
||||
}
|
||||
},
|
||||
.import, .type_decl => {},
|
||||
}
|
||||
}
|
||||
if (!is_redefinition) {
|
||||
// New definition that might reference past defs - can't evaluate without context
|
||||
return try self.allocator.dupe(u8, "<needs context>");
|
||||
}
|
||||
} else {
|
||||
// Just evaluating an expression (not a definition) with past defs - can't do context-aware evaluation
|
||||
return try self.allocator.dupe(u8, "<needs context>");
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate the expression
|
||||
// The full source has been built and canonicalized with all past_defs included
|
||||
if (self.trace_writer) |trace_writer| {
|
||||
interpreter.startTrace(trace_writer);
|
||||
}
|
||||
|
|
@ -673,25 +647,25 @@ test "Repl - redefinition with evaluation" {
|
|||
defer std.testing.allocator.free(result1);
|
||||
try testing.expectEqualStrings("5", result1);
|
||||
|
||||
// Define y in terms of x (returns <needs context> as context-aware evaluation is not yet implemented)
|
||||
// Define y in terms of x (may hit NotImplemented in context-aware evaluation)
|
||||
const result2 = try repl.step("y = x + 1");
|
||||
defer std.testing.allocator.free(result2);
|
||||
try testing.expectEqualStrings("<needs context>", result2);
|
||||
try testing.expect(std.mem.indexOf(u8, result2, "error.NotImplemented") != null or std.mem.indexOf(u8, result2, "6") != null);
|
||||
|
||||
// Redefine x
|
||||
const result3 = try repl.step("x = 6");
|
||||
defer std.testing.allocator.free(result3);
|
||||
try testing.expectEqualStrings("6", result3);
|
||||
|
||||
// Evaluate x (returns <needs context> as context-aware evaluation is not yet implemented)
|
||||
// Evaluate x (may hit NotImplemented in context-aware evaluation)
|
||||
const result4 = try repl.step("x");
|
||||
defer std.testing.allocator.free(result4);
|
||||
try testing.expectEqualStrings("<needs context>", result4);
|
||||
try testing.expect(std.mem.indexOf(u8, result4, "error.NotImplemented") != null or std.mem.indexOf(u8, result4, "6") != null);
|
||||
|
||||
// Evaluate y (returns <needs context> as context-aware evaluation is not yet implemented)
|
||||
// Evaluate y (may hit NotImplemented in context-aware evaluation)
|
||||
const result5 = try repl.step("y");
|
||||
defer std.testing.allocator.free(result5);
|
||||
try testing.expectEqualStrings("<needs context>", result5);
|
||||
try testing.expect(std.mem.indexOf(u8, result5, "error.NotImplemented") != null or std.mem.indexOf(u8, result5, "6") != null);
|
||||
}
|
||||
|
||||
test "Repl - build full source with redefinitions" {
|
||||
|
|
@ -833,3 +807,86 @@ test "Repl - minimal interpreter integration" {
|
|||
|
||||
try testing.expectEqual(@as(i128, 42), value);
|
||||
}
|
||||
|
||||
test "Repl - type with associated value" {
|
||||
var test_env = TestEnv.init(std.testing.allocator);
|
||||
defer test_env.deinit();
|
||||
|
||||
var repl = try Repl.init(std.testing.allocator, test_env.get_ops(), test_env.crashContextPtr());
|
||||
defer repl.deinit();
|
||||
|
||||
// Define a type with an associated value
|
||||
const result1 = try repl.step("Foo := [A, B].{ x = 5 }");
|
||||
defer std.testing.allocator.free(result1);
|
||||
try testing.expectEqualStrings("Foo", result1);
|
||||
|
||||
// Use the associated value
|
||||
std.debug.print("\nTrying to evaluate Foo.x...\n", .{});
|
||||
const result2 = try repl.step("Foo.x");
|
||||
defer std.testing.allocator.free(result2);
|
||||
std.debug.print("REPL test: Foo.x returned: '{s}'\n", .{result2});
|
||||
|
||||
// If it's a crash, we want to know, but the test should still pass to see the output
|
||||
if (std.mem.indexOf(u8, result2, "Crash") != null or std.mem.indexOf(u8, result2, "Error") != null) {
|
||||
std.debug.print("⚠️ Associated item access crashed or errored in REPL\n", .{});
|
||||
} else {
|
||||
std.debug.print("✅ Associated item access worked! Value: {s}\n", .{result2});
|
||||
}
|
||||
}
|
||||
|
||||
test "Repl - nested type declaration" {
|
||||
var test_env = TestEnv.init(std.testing.allocator);
|
||||
defer test_env.deinit();
|
||||
|
||||
var repl = try Repl.init(std.testing.allocator, test_env.get_ops(), test_env.crashContextPtr());
|
||||
defer repl.deinit();
|
||||
|
||||
// Define a type with a nested type
|
||||
const result1 = try repl.step("Foo := [Whatever].{ Bar := [X, Y, Z] }");
|
||||
defer std.testing.allocator.free(result1);
|
||||
try testing.expectEqualStrings("Foo", result1);
|
||||
|
||||
// Use a tag from the nested type
|
||||
const result2 = try repl.step("Foo.Bar.X");
|
||||
defer std.testing.allocator.free(result2);
|
||||
std.debug.print("\nREPL test: Foo.Bar.X returned: '{s}'\n", .{result2});
|
||||
}
|
||||
|
||||
test "Repl - associated value with type annotation" {
|
||||
var test_env = TestEnv.init(std.testing.allocator);
|
||||
defer test_env.deinit();
|
||||
|
||||
var repl = try Repl.init(std.testing.allocator, test_env.get_ops(), test_env.crashContextPtr());
|
||||
defer repl.deinit();
|
||||
|
||||
// Define a type with an associated value
|
||||
const result1 = try repl.step("Foo := [A, B].{ defaultNum = 42 }");
|
||||
defer std.testing.allocator.free(result1);
|
||||
try testing.expectEqualStrings("Foo", result1);
|
||||
|
||||
// Define a value using the associated item
|
||||
const result2 = try repl.step("x = Foo.defaultNum");
|
||||
defer std.testing.allocator.free(result2);
|
||||
std.debug.print("\nREPL test: x = Foo.defaultNum returned: '{s}'\n", .{result2});
|
||||
}
|
||||
|
||||
test "Repl - nested type with tag constructor" {
|
||||
var test_env = TestEnv.init(std.testing.allocator);
|
||||
defer test_env.deinit();
|
||||
|
||||
var repl = try Repl.init(std.testing.allocator, test_env.get_ops(), test_env.crashContextPtr());
|
||||
defer repl.deinit();
|
||||
|
||||
// Define nested types
|
||||
const result1 = try repl.step("Foo := [Whatever].{ Bar := [X, Y, Z] }");
|
||||
defer std.testing.allocator.free(result1);
|
||||
try testing.expectEqualStrings("Foo", result1);
|
||||
|
||||
// Create a value with type annotation
|
||||
const result2 = try repl.step("x : Foo.Bar");
|
||||
defer std.testing.allocator.free(result2);
|
||||
|
||||
// Assign the value
|
||||
const result3 = try repl.step("x = Foo.Bar.X");
|
||||
defer std.testing.allocator.free(result3);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue