mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
Add List.for_each!
This commit is contained in:
parent
99d4758d81
commit
adfa93d77e
15 changed files with 517 additions and 1 deletions
|
|
@ -82,6 +82,11 @@ Builtin :: [].{
|
|||
Try.Err(OutOfBounds)
|
||||
}
|
||||
|
||||
for_each! : List(item), (item => {}) => {}
|
||||
for_each! = |items, cb!| for item in items {
|
||||
cb!(item)
|
||||
}
|
||||
|
||||
map : List(a), (a -> b) -> List(b)
|
||||
map = |list, transform|
|
||||
# Implement using fold + concat for now
|
||||
|
|
|
|||
|
|
@ -5592,6 +5592,86 @@ pub fn canonicalizeExpr(
|
|||
.block => |e| {
|
||||
return try self.canonicalizeBlock(e);
|
||||
},
|
||||
.for_expr => |for_expr| {
|
||||
// Tmp state to capture free vars from both expr & body
|
||||
//
|
||||
// This is stored as a map, so we can avoid adding duplicate captures
|
||||
// if both the expr and the body reference the same var
|
||||
var captures = std.AutoHashMapUnmanaged(Pattern.Idx, void){};
|
||||
defer captures.deinit(self.env.gpa);
|
||||
|
||||
// Canonicalize the list expr
|
||||
// for item in [1,2,3] { ... }
|
||||
// ^^^^^^^
|
||||
const list_expr = blk: {
|
||||
const body_free_vars_start = self.scratch_free_vars.top();
|
||||
defer self.scratch_free_vars.clearFrom(body_free_vars_start);
|
||||
|
||||
const czerd_expr = try self.canonicalizeExprOrMalformed(for_expr.expr);
|
||||
|
||||
// Copy free vars into scratch array
|
||||
const free_vars_slice = self.scratch_free_vars.sliceFromSpan(czerd_expr.free_vars orelse DataSpan.empty());
|
||||
for (free_vars_slice) |fv| {
|
||||
try captures.put(self.env.gpa, fv, {});
|
||||
}
|
||||
|
||||
break :blk czerd_expr;
|
||||
};
|
||||
|
||||
// Canonicalize the pattern
|
||||
// for item in [1,2,3] { ... }
|
||||
// ^^^^
|
||||
const ptrn = try self.canonicalizePatternOrMalformed(for_expr.patt);
|
||||
|
||||
// Collect bound vars from pattern
|
||||
var for_bound_vars = std.AutoHashMapUnmanaged(Pattern.Idx, void){};
|
||||
defer for_bound_vars.deinit(self.env.gpa);
|
||||
try self.collectBoundVars(ptrn, &for_bound_vars);
|
||||
|
||||
// Canonicalize the body
|
||||
// for item in [1,2,3] {
|
||||
// print!(item.toStr()) <<<<
|
||||
// }
|
||||
const body = blk: {
|
||||
const body_free_vars_start = self.scratch_free_vars.top();
|
||||
defer self.scratch_free_vars.clearFrom(body_free_vars_start);
|
||||
|
||||
const body_expr = try self.canonicalizeExprOrMalformed(for_expr.body);
|
||||
|
||||
// Copy free vars into scratch array
|
||||
const body_free_vars_slice = self.scratch_free_vars.sliceFromSpan(body_expr.free_vars orelse DataSpan.empty());
|
||||
for (body_free_vars_slice) |fv| {
|
||||
if (!for_bound_vars.contains(fv)) {
|
||||
try captures.put(self.env.gpa, fv, {});
|
||||
}
|
||||
}
|
||||
|
||||
break :blk body_expr;
|
||||
};
|
||||
|
||||
// Get captures and copy to free_vars for parent
|
||||
const free_vars_start = self.scratch_free_vars.top();
|
||||
var captures_iter = captures.keyIterator();
|
||||
while (captures_iter.next()) |capture| {
|
||||
try self.scratch_free_vars.append(capture.*);
|
||||
}
|
||||
const free_vars = if (self.scratch_free_vars.top() > free_vars_start)
|
||||
self.scratch_free_vars.spanFrom(free_vars_start)
|
||||
else
|
||||
null;
|
||||
|
||||
// Create the for expression
|
||||
const region = self.parse_ir.tokenizedRegionToRegion(for_expr.region);
|
||||
const for_expr_idx = try self.env.addExpr(Expr{
|
||||
.e_for = .{
|
||||
.patt = ptrn,
|
||||
.expr = list_expr.idx,
|
||||
.body = body.idx,
|
||||
},
|
||||
}, region);
|
||||
|
||||
return CanonicalizedExpr{ .idx = for_expr_idx, .free_vars = free_vars };
|
||||
},
|
||||
.malformed => |malformed| {
|
||||
// We won't touch this since it's already a parse error.
|
||||
_ = malformed;
|
||||
|
|
|
|||
|
|
@ -277,6 +277,11 @@ fn collectExprDependencies(
|
|||
try collectExprDependencies(cir, ret.expr, dependencies, allocator);
|
||||
},
|
||||
|
||||
.e_for => |for_expr| {
|
||||
try collectExprDependencies(cir, for_expr.expr, dependencies, allocator);
|
||||
try collectExprDependencies(cir, for_expr.body, dependencies, allocator);
|
||||
},
|
||||
|
||||
.e_runtime_error => {},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -393,6 +393,19 @@ pub const Expr = union(enum) {
|
|||
expr: Expr.Idx,
|
||||
},
|
||||
|
||||
/// For expression that iterates over a list and executes a body for each element.
|
||||
/// The for expression evaluates to the empty record `{}`.
|
||||
/// This is the expression form of a for loop, allowing it to be used in expression contexts.
|
||||
///
|
||||
/// ```roc
|
||||
/// for_each! = |items, cb!| for item in items { cb!(item) }
|
||||
/// ```
|
||||
e_for: struct {
|
||||
patt: CIR.Pattern.Idx,
|
||||
expr: Expr.Idx,
|
||||
body: Expr.Idx,
|
||||
},
|
||||
|
||||
/// A hosted function that will be provided by the platform at runtime.
|
||||
/// This represents a lambda/function whose implementation is provided by the host application
|
||||
/// via the RocOps.hosted_fns array.
|
||||
|
|
@ -1840,6 +1853,24 @@ pub const Expr = union(enum) {
|
|||
// Add inner expression
|
||||
try ir.store.getExpr(ret.expr).pushToSExprTree(ir, tree, ret.expr);
|
||||
|
||||
try tree.endNode(begin, attrs);
|
||||
},
|
||||
.e_for => |for_expr| {
|
||||
const begin = tree.beginNode();
|
||||
try tree.pushStaticAtom("e-for");
|
||||
const region = ir.store.getExprRegion(expr_idx);
|
||||
try ir.appendRegionInfoToSExprTreeFromRegion(tree, region);
|
||||
const attrs = tree.beginNode();
|
||||
|
||||
// Add pattern
|
||||
try ir.store.getPattern(for_expr.patt).pushToSExprTree(ir, tree, for_expr.patt);
|
||||
|
||||
// Add list expression
|
||||
try ir.store.getExpr(for_expr.expr).pushToSExprTree(ir, tree, for_expr.expr);
|
||||
|
||||
// Add body expression
|
||||
try ir.store.getExpr(for_expr.body).pushToSExprTree(ir, tree, for_expr.body);
|
||||
|
||||
try tree.endNode(begin, attrs);
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ pub const Tag = enum {
|
|||
expr_hosted_lambda,
|
||||
expr_low_level,
|
||||
expr_expect,
|
||||
expr_for,
|
||||
expr_record_builder,
|
||||
expr_return,
|
||||
match_branch,
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ pub fn relocate(store: *NodeStore, offset: isize) void {
|
|||
/// Count of the diagnostic nodes in the ModuleEnv
|
||||
pub const MODULEENV_DIAGNOSTIC_NODE_COUNT = 59;
|
||||
/// Count of the expression nodes in the ModuleEnv
|
||||
pub const MODULEENV_EXPR_NODE_COUNT = 38;
|
||||
pub const MODULEENV_EXPR_NODE_COUNT = 39;
|
||||
/// Count of the statement nodes in the ModuleEnv
|
||||
pub const MODULEENV_STATEMENT_NODE_COUNT = 16;
|
||||
/// Count of the type annotation nodes in the ModuleEnv
|
||||
|
|
@ -716,6 +716,13 @@ pub fn getExpr(store: *const NodeStore, expr: CIR.Expr.Idx) CIR.Expr {
|
|||
.body = @enumFromInt(node.data_1),
|
||||
} };
|
||||
},
|
||||
.expr_for => {
|
||||
return CIR.Expr{ .e_for = .{
|
||||
.patt = @enumFromInt(node.data_1),
|
||||
.expr = @enumFromInt(node.data_2),
|
||||
.body = @enumFromInt(node.data_3),
|
||||
} };
|
||||
},
|
||||
.expr_if_then_else => {
|
||||
const extra_start = node.data_1;
|
||||
const extra_data = store.extra_data.items.items[extra_start..];
|
||||
|
|
@ -1795,6 +1802,12 @@ pub fn addExpr(store: *NodeStore, expr: CIR.Expr, region: base.Region) Allocator
|
|||
node.tag = .expr_expect;
|
||||
node.data_1 = @intFromEnum(e.body);
|
||||
},
|
||||
.e_for => |e| {
|
||||
node.tag = .expr_for;
|
||||
node.data_1 = @intFromEnum(e.patt);
|
||||
node.data_2 = @intFromEnum(e.expr);
|
||||
node.data_3 = @intFromEnum(e.body);
|
||||
},
|
||||
}
|
||||
|
||||
const node_idx = try store.nodes.append(store.gpa, node);
|
||||
|
|
|
|||
|
|
@ -403,6 +403,13 @@ test "NodeStore round trip - Expressions" {
|
|||
.expr = rand_idx(CIR.Expr.Idx),
|
||||
},
|
||||
});
|
||||
try expressions.append(gpa, CIR.Expr{
|
||||
.e_for = .{
|
||||
.patt = rand_idx(CIR.Pattern.Idx),
|
||||
.expr = rand_idx(CIR.Expr.Idx),
|
||||
.body = rand_idx(CIR.Expr.Idx),
|
||||
},
|
||||
});
|
||||
|
||||
for (expressions.items, 0..) |expr, i| {
|
||||
const region = from_raw_offsets(@intCast(i * 100), @intCast(i * 100 + 50));
|
||||
|
|
|
|||
|
|
@ -3583,6 +3583,32 @@ fn checkExpr(self: *Self, expr_idx: CIR.Expr.Idx, env: *Env, expected: Expected)
|
|||
does_fx = try self.checkExpr(expect.body, env, expected) or does_fx;
|
||||
try self.unifyWith(expr_var, .{ .structure = .empty_record }, env);
|
||||
},
|
||||
.e_for => |for_expr| {
|
||||
// Check the pattern
|
||||
try self.checkPattern(for_expr.patt, env, .no_expectation);
|
||||
const for_ptrn_var: Var = ModuleEnv.varFrom(for_expr.patt);
|
||||
|
||||
// Check the list expression
|
||||
does_fx = try self.checkExpr(for_expr.expr, env, .no_expectation) or does_fx;
|
||||
const for_expr_region = self.cir.store.getNodeRegion(ModuleEnv.nodeIdxFrom(for_expr.expr));
|
||||
const for_expr_var: Var = ModuleEnv.varFrom(for_expr.expr);
|
||||
|
||||
// Check that the expr is list of the ptrn
|
||||
const list_content = try self.mkListContent(for_ptrn_var, env);
|
||||
const list_var = try self.freshFromContent(list_content, env, for_expr_region);
|
||||
_ = try self.unify(list_var, for_expr_var, env);
|
||||
|
||||
// Check the body
|
||||
does_fx = try self.checkExpr(for_expr.body, env, .no_expectation) or does_fx;
|
||||
const for_body_var: Var = ModuleEnv.varFrom(for_expr.body);
|
||||
|
||||
// Check that the for body evaluates to {}
|
||||
const body_ret = try self.freshFromContent(.{ .structure = .empty_record }, env, for_expr_region);
|
||||
_ = try self.unify(body_ret, for_body_var, env);
|
||||
|
||||
// The for expression itself evaluates to {}
|
||||
try self.unifyWith(expr_var, .{ .structure = .empty_record }, env);
|
||||
},
|
||||
.e_ellipsis => {
|
||||
try self.unifyWith(expr_var, .{ .flex = Flex.init() }, env);
|
||||
},
|
||||
|
|
@ -3968,6 +3994,11 @@ fn unifyEarlyReturns(self: *Self, expr_idx: CIR.Expr.Idx, return_var: Var, env:
|
|||
try self.unifyEarlyReturns(branch.value, return_var, env);
|
||||
}
|
||||
},
|
||||
.e_for => |for_expr| {
|
||||
// Check the list expression and body for returns
|
||||
try self.unifyEarlyReturns(for_expr.expr, return_var, env);
|
||||
try self.unifyEarlyReturns(for_expr.body, return_var, env);
|
||||
},
|
||||
// Lambdas create a new scope for returns - don't recurse into them
|
||||
.e_lambda, .e_closure => {},
|
||||
// All other expressions don't contain statements
|
||||
|
|
|
|||
|
|
@ -722,6 +722,32 @@ test "numeric fold" {
|
|||
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "Sum: 15") != null);
|
||||
}
|
||||
|
||||
test "List.for_each! with effectful callback" {
|
||||
// Tests List.for_each! which iterates over a list and calls an effectful callback
|
||||
const allocator = testing.allocator;
|
||||
|
||||
try ensureRocBinary(allocator);
|
||||
|
||||
const run_result = try std.process.Child.run(.{
|
||||
.allocator = allocator,
|
||||
.argv = &[_][]const u8{
|
||||
"./zig-out/bin/roc",
|
||||
"test/fx/list_for_each.roc",
|
||||
},
|
||||
});
|
||||
defer allocator.free(run_result.stdout);
|
||||
defer allocator.free(run_result.stderr);
|
||||
|
||||
// Verify each item is printed
|
||||
const has_apple = std.mem.indexOf(u8, run_result.stdout, "Item: apple") != null;
|
||||
const has_banana = std.mem.indexOf(u8, run_result.stdout, "Item: banana") != null;
|
||||
const has_cherry = std.mem.indexOf(u8, run_result.stdout, "Item: cherry") != null;
|
||||
|
||||
try testing.expect(has_apple);
|
||||
try testing.expect(has_banana);
|
||||
try testing.expect(has_cherry);
|
||||
}
|
||||
|
||||
test "string literal pattern matching" {
|
||||
// Tests pattern matching on string literals in match expressions.
|
||||
const allocator = testing.allocator;
|
||||
|
|
|
|||
|
|
@ -7618,6 +7618,12 @@ pub const Interpreter = struct {
|
|||
/// For loop - process body result and continue to next iteration.
|
||||
for_loop_body_done: ForLoopBodyDone,
|
||||
|
||||
/// For expression - iterate over list elements after list is evaluated.
|
||||
for_expr_iterate: ForExprIterate,
|
||||
|
||||
/// For expression - process body result and continue to next iteration.
|
||||
for_expr_body_done: ForExprBodyDone,
|
||||
|
||||
/// While loop - check condition and decide whether to continue.
|
||||
while_loop_check: WhileLoopCheck,
|
||||
|
||||
|
|
@ -7996,6 +8002,52 @@ pub const Interpreter = struct {
|
|||
loop_bindings_start: usize,
|
||||
};
|
||||
|
||||
/// For expression - iterate over list elements (simpler than statement version)
|
||||
pub const ForExprIterate = struct {
|
||||
/// The list value being iterated (stored to access elements)
|
||||
list_value: StackValue,
|
||||
/// Current iteration index
|
||||
current_index: usize,
|
||||
/// Total number of elements in the list
|
||||
list_len: usize,
|
||||
/// Element size in bytes
|
||||
elem_size: usize,
|
||||
/// Element layout
|
||||
elem_layout: layout.Layout,
|
||||
/// Pattern to bind each element to
|
||||
pattern: can.CIR.Pattern.Idx,
|
||||
/// Pattern runtime type variable
|
||||
patt_rt_var: types.Var,
|
||||
/// Body expression to evaluate for each element
|
||||
body: can.CIR.Expr.Idx,
|
||||
/// Bindings length at for-expr start (for cleanup after completion)
|
||||
bindings_start: usize,
|
||||
};
|
||||
|
||||
/// For expression - cleanup after body evaluation (simpler than statement version)
|
||||
pub const ForExprBodyDone = struct {
|
||||
/// The list value being iterated
|
||||
list_value: StackValue,
|
||||
/// Current iteration index (just completed)
|
||||
current_index: usize,
|
||||
/// Total number of elements in the list
|
||||
list_len: usize,
|
||||
/// Element size in bytes
|
||||
elem_size: usize,
|
||||
/// Element layout
|
||||
elem_layout: layout.Layout,
|
||||
/// Pattern to bind each element to
|
||||
pattern: can.CIR.Pattern.Idx,
|
||||
/// Pattern runtime type variable
|
||||
patt_rt_var: types.Var,
|
||||
/// Body expression to evaluate for each element
|
||||
body: can.CIR.Expr.Idx,
|
||||
/// Bindings length at for-expr start (for cleanup after completion)
|
||||
bindings_start: usize,
|
||||
/// Bindings length at iteration start (for per-iteration cleanup)
|
||||
loop_bindings_start: usize,
|
||||
};
|
||||
|
||||
/// While loop - check condition
|
||||
pub const WhileLoopCheck = struct {
|
||||
/// Condition expression
|
||||
|
|
@ -8206,6 +8258,14 @@ pub const Interpreter = struct {
|
|||
// Decref the list value
|
||||
fl.list_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
},
|
||||
.for_expr_iterate => |fl| {
|
||||
// Decref the list value
|
||||
fl.list_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
},
|
||||
.for_expr_body_done => |fl| {
|
||||
// Decref the list value
|
||||
fl.list_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
},
|
||||
.sort_compare_result => |sc| {
|
||||
// Decref the list and compare function
|
||||
sc.list_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
|
|
@ -9033,6 +9093,39 @@ pub const Interpreter = struct {
|
|||
} });
|
||||
},
|
||||
|
||||
.e_for => |for_expr| {
|
||||
// For expression: first evaluate the list, then set up iteration
|
||||
const expr_ct_var = can.ModuleEnv.varFrom(for_expr.expr);
|
||||
const expr_rt_var = try self.translateTypeVar(self.env, expr_ct_var);
|
||||
|
||||
// Get the element type for binding
|
||||
const patt_ct_var = can.ModuleEnv.varFrom(for_expr.patt);
|
||||
const patt_rt_var = try self.translateTypeVar(self.env, patt_ct_var);
|
||||
|
||||
// Push for_expr_iterate continuation (will be executed after list is evaluated)
|
||||
try work_stack.push(.{
|
||||
.apply_continuation = .{
|
||||
.for_expr_iterate = .{
|
||||
.list_value = undefined, // Will be set when list is evaluated
|
||||
.current_index = 0,
|
||||
.list_len = 0, // Will be set when list is evaluated
|
||||
.elem_size = 0, // Will be set when list is evaluated
|
||||
.elem_layout = undefined, // Will be set when list is evaluated
|
||||
.pattern = for_expr.patt,
|
||||
.patt_rt_var = patt_rt_var,
|
||||
.body = for_expr.body,
|
||||
.bindings_start = self.bindings.items.len,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Evaluate the list expression
|
||||
try work_stack.push(.{ .eval_expr = .{
|
||||
.expr_idx = for_expr.expr,
|
||||
.expected_rt_var = expr_rt_var,
|
||||
} });
|
||||
},
|
||||
|
||||
// ================================================================
|
||||
// Function calls
|
||||
// ================================================================
|
||||
|
|
@ -12514,6 +12607,159 @@ pub const Interpreter = struct {
|
|||
} });
|
||||
return true;
|
||||
},
|
||||
.for_expr_iterate => |fl_in| {
|
||||
// For expression iteration: list has been evaluated, start iterating
|
||||
const list_value = value_stack.pop() orelse {
|
||||
self.triggerCrash("for_expr_iterate: value_stack empty", false, roc_ops);
|
||||
return error.Crash;
|
||||
};
|
||||
|
||||
// Get the list layout
|
||||
if (list_value.layout.tag != .list) {
|
||||
list_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
return error.TypeMismatch;
|
||||
}
|
||||
const elem_layout_idx = list_value.layout.data.list;
|
||||
const elem_layout = self.runtime_layout_store.getLayout(elem_layout_idx);
|
||||
const elem_size: usize = @intCast(self.runtime_layout_store.layoutSize(elem_layout));
|
||||
|
||||
// Get the RocList header
|
||||
const list_header: *const RocList = @ptrCast(@alignCast(list_value.ptr.?));
|
||||
const list_len = list_header.len();
|
||||
|
||||
// Create the proper for_expr_iterate with list info filled in
|
||||
var fl = fl_in;
|
||||
fl.list_value = list_value;
|
||||
fl.list_len = list_len;
|
||||
fl.elem_size = elem_size;
|
||||
fl.elem_layout = elem_layout;
|
||||
|
||||
// If list is empty, push empty record result and we're done
|
||||
if (list_len == 0) {
|
||||
list_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
// Push empty record {} as result
|
||||
const empty_record_layout_idx = try self.runtime_layout_store.ensureEmptyRecordLayout();
|
||||
const empty_record_layout = self.runtime_layout_store.getLayout(empty_record_layout_idx);
|
||||
const empty_record_value = try self.pushRaw(empty_record_layout, 0);
|
||||
try value_stack.push(empty_record_value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process first element
|
||||
const elem_ptr = if (list_header.bytes) |buffer|
|
||||
buffer
|
||||
else {
|
||||
list_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
return error.TypeMismatch;
|
||||
};
|
||||
|
||||
var elem_value = StackValue{
|
||||
.ptr = elem_ptr,
|
||||
.layout = elem_layout,
|
||||
.is_initialized = true,
|
||||
};
|
||||
elem_value.incref(&self.runtime_layout_store);
|
||||
|
||||
// Bind the pattern
|
||||
const loop_bindings_start = self.bindings.items.len;
|
||||
if (!try self.patternMatchesBind(fl.pattern, elem_value, fl.patt_rt_var, roc_ops, &self.bindings, @enumFromInt(0))) {
|
||||
elem_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
list_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
return error.TypeMismatch;
|
||||
}
|
||||
elem_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
|
||||
// Push body_done continuation
|
||||
try work_stack.push(.{ .apply_continuation = .{ .for_expr_body_done = .{
|
||||
.list_value = fl.list_value,
|
||||
.current_index = 0,
|
||||
.list_len = fl.list_len,
|
||||
.elem_size = fl.elem_size,
|
||||
.elem_layout = fl.elem_layout,
|
||||
.pattern = fl.pattern,
|
||||
.patt_rt_var = fl.patt_rt_var,
|
||||
.body = fl.body,
|
||||
.bindings_start = fl.bindings_start,
|
||||
.loop_bindings_start = loop_bindings_start,
|
||||
} } });
|
||||
|
||||
// Evaluate body
|
||||
try work_stack.push(.{ .eval_expr = .{
|
||||
.expr_idx = fl.body,
|
||||
.expected_rt_var = null,
|
||||
} });
|
||||
return true;
|
||||
},
|
||||
.for_expr_body_done => |fl| {
|
||||
// For expression body completed, clean up and continue to next iteration
|
||||
const body_result = value_stack.pop() orelse {
|
||||
self.triggerCrash("for_expr_body_done: value_stack empty", false, roc_ops);
|
||||
return error.Crash;
|
||||
};
|
||||
body_result.decref(&self.runtime_layout_store, roc_ops);
|
||||
|
||||
// Clean up bindings for this iteration
|
||||
self.trimBindingList(&self.bindings, fl.loop_bindings_start, roc_ops);
|
||||
|
||||
// Move to next element
|
||||
const next_index = fl.current_index + 1;
|
||||
if (next_index >= fl.list_len) {
|
||||
// Loop complete, decref list and push empty record result
|
||||
fl.list_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
// Push empty record {} as result
|
||||
const empty_record_layout_idx = try self.runtime_layout_store.ensureEmptyRecordLayout();
|
||||
const empty_record_layout = self.runtime_layout_store.getLayout(empty_record_layout_idx);
|
||||
const empty_record_value = try self.pushRaw(empty_record_layout, 0);
|
||||
try value_stack.push(empty_record_value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get next element
|
||||
const list_header: *const RocList = @ptrCast(@alignCast(fl.list_value.ptr.?));
|
||||
const elem_ptr = if (list_header.bytes) |buffer|
|
||||
buffer + next_index * fl.elem_size
|
||||
else {
|
||||
fl.list_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
return error.TypeMismatch;
|
||||
};
|
||||
|
||||
var elem_value = StackValue{
|
||||
.ptr = elem_ptr,
|
||||
.layout = fl.elem_layout,
|
||||
.is_initialized = true,
|
||||
};
|
||||
elem_value.incref(&self.runtime_layout_store);
|
||||
|
||||
// Bind the pattern
|
||||
const new_loop_bindings_start = self.bindings.items.len;
|
||||
if (!try self.patternMatchesBind(fl.pattern, elem_value, fl.patt_rt_var, roc_ops, &self.bindings, @enumFromInt(0))) {
|
||||
elem_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
fl.list_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
return error.TypeMismatch;
|
||||
}
|
||||
elem_value.decref(&self.runtime_layout_store, roc_ops);
|
||||
|
||||
// Push body_done continuation for next iteration
|
||||
try work_stack.push(.{ .apply_continuation = .{ .for_expr_body_done = .{
|
||||
.list_value = fl.list_value,
|
||||
.current_index = next_index,
|
||||
.list_len = fl.list_len,
|
||||
.elem_size = fl.elem_size,
|
||||
.elem_layout = fl.elem_layout,
|
||||
.pattern = fl.pattern,
|
||||
.patt_rt_var = fl.patt_rt_var,
|
||||
.body = fl.body,
|
||||
.bindings_start = fl.bindings_start,
|
||||
.loop_bindings_start = new_loop_bindings_start,
|
||||
} } });
|
||||
|
||||
// Evaluate body
|
||||
try work_stack.push(.{ .eval_expr = .{
|
||||
.expr_idx = fl.body,
|
||||
.expected_rt_var = null,
|
||||
} });
|
||||
return true;
|
||||
},
|
||||
.while_loop_check => |wl| {
|
||||
// While loop: condition has been evaluated
|
||||
const cond_value = value_stack.pop() orelse return error.Crash;
|
||||
|
|
|
|||
|
|
@ -2396,6 +2396,12 @@ pub const Expr = union(enum) {
|
|||
region: TokenizedRegion,
|
||||
},
|
||||
block: Block,
|
||||
for_expr: struct {
|
||||
patt: Pattern.Idx,
|
||||
expr: Expr.Idx,
|
||||
body: Expr.Idx,
|
||||
region: TokenizedRegion,
|
||||
},
|
||||
malformed: struct {
|
||||
reason: Diagnostic.Tag,
|
||||
region: TokenizedRegion,
|
||||
|
|
@ -2444,6 +2450,7 @@ pub const Expr = union(enum) {
|
|||
.block => |e| e.region,
|
||||
.record_builder => |e| e.region,
|
||||
.ellipsis => |e| e.region,
|
||||
.for_expr => |e| e.region,
|
||||
.malformed => |e| e.region,
|
||||
.string_part => |e| e.region,
|
||||
.single_quote => |e| e.region,
|
||||
|
|
@ -2774,6 +2781,23 @@ pub const Expr = union(enum) {
|
|||
// Push child expression
|
||||
try ast.store.getExpr(a.expr).pushToSExprTree(gpa, env, ast, tree);
|
||||
|
||||
try tree.endNode(begin, attrs);
|
||||
},
|
||||
.for_expr => |f| {
|
||||
const begin = tree.beginNode();
|
||||
try tree.pushStaticAtom("e-for");
|
||||
try ast.appendRegionInfoToSexprTree(env, tree, f.region);
|
||||
const attrs = tree.beginNode();
|
||||
|
||||
// Push pattern
|
||||
try ast.store.getPattern(f.patt).pushToSExprTree(gpa, env, ast, tree);
|
||||
|
||||
// Push list expression
|
||||
try ast.store.getExpr(f.expr).pushToSExprTree(gpa, env, ast, tree);
|
||||
|
||||
// Push body expression
|
||||
try ast.store.getExpr(f.body).pushToSExprTree(gpa, env, ast, tree);
|
||||
|
||||
try tree.endNode(begin, attrs);
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -434,6 +434,11 @@ pub const Tag = enum {
|
|||
/// * lhs - first statement node
|
||||
/// * rhs - number of statements
|
||||
block,
|
||||
/// A for expression (for loop used as an expression, evaluates to {})
|
||||
/// * main_token - node index for pattern for loop variable
|
||||
/// * lhs - node index for loop initializing expression
|
||||
/// * rhs - node index for loop body expression
|
||||
for_expr,
|
||||
/// DESCRIPTION
|
||||
/// Example: EXAMPLE
|
||||
/// * lhs - LHS DESCRIPTION
|
||||
|
|
|
|||
|
|
@ -739,6 +739,13 @@ pub fn addExpr(store: *NodeStore, expr: AST.Expr) std.mem.Allocator.Error!AST.Ex
|
|||
node.data.lhs = body.statements.span.start;
|
||||
node.data.rhs = body.statements.span.len;
|
||||
},
|
||||
.for_expr => |f| {
|
||||
node.tag = .for_expr;
|
||||
node.region = f.region;
|
||||
node.main_token = @intFromEnum(f.patt);
|
||||
node.data.lhs = @intFromEnum(f.expr);
|
||||
node.data.rhs = @intFromEnum(f.body);
|
||||
},
|
||||
.ellipsis => |e| {
|
||||
node.tag = .ellipsis;
|
||||
node.region = e.region;
|
||||
|
|
@ -1649,6 +1656,14 @@ pub fn getExpr(store: *const NodeStore, expr_idx: AST.Expr.Idx) AST.Expr {
|
|||
.region = node.region,
|
||||
} };
|
||||
},
|
||||
.for_expr => {
|
||||
return .{ .for_expr = .{
|
||||
.patt = @enumFromInt(node.main_token),
|
||||
.expr = @enumFromInt(node.data.lhs),
|
||||
.body = @enumFromInt(node.data.rhs),
|
||||
.region = node.region,
|
||||
} };
|
||||
},
|
||||
.malformed => {
|
||||
return .{ .malformed = .{
|
||||
.reason = @enumFromInt(node.data.lhs),
|
||||
|
|
|
|||
|
|
@ -2153,6 +2153,21 @@ pub fn parseExprWithBp(self: *Parser, min_bp: u8) Error!AST.Expr.Idx {
|
|||
.expr = e,
|
||||
} });
|
||||
},
|
||||
.KwFor => {
|
||||
self.advance();
|
||||
const patt = try self.parsePattern(.alternatives_forbidden);
|
||||
self.expect(.KwIn) catch {
|
||||
return try self.pushMalformed(AST.Expr.Idx, .for_expected_in, self.pos);
|
||||
};
|
||||
const list_expr = try self.parseExpr();
|
||||
const body = try self.parseExpr();
|
||||
expr = try self.store.addExpr(.{ .for_expr = .{
|
||||
.region = .{ .start = start, .end = self.pos },
|
||||
.patt = patt,
|
||||
.expr = list_expr,
|
||||
.body = body,
|
||||
} });
|
||||
},
|
||||
.TripleDot => {
|
||||
expr = try self.store.addExpr(.{ .ellipsis = .{
|
||||
.region = .{ .start = start, .end = self.pos },
|
||||
|
|
|
|||
12
test/fx/list_for_each.roc
Normal file
12
test/fx/list_for_each.roc
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
# Tests List.for_each! with effectful callbacks
|
||||
|
||||
main! = || {
|
||||
items = ["apple", "banana", "cherry"]
|
||||
List.for_each!(items, |item| {
|
||||
Stdout.line!("Item: ${item}")
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue