EMPL IR
Lambda, Await, Yield
Lambda, Await, Yield
Three node kinds covering closures, asynchronous suspension, and generator yields. They are documented together because they share semantic overlap (lambdas may be async; generators yield values; await suspends async functions).
LambdaNode
// MPL_Compiler/core/ir/EMPLNode.h:565
class LambdaNode : public EMPLNode {
public:
std::vector<std::pair<std::string, std::string>> parameters;
std::shared_ptr<BlockNode> body;
EMPLNodePtr expressionBody;
bool isAsync = false;
bool capturesLexicalThis = false; // arrow functions / Python lambdas
bool isConstructible = true; // false for arrow functions
bool hasOwnArguments = true; // false for arrow functions
LambdaNode() : EMPLNode(NodeKind::Lambda) {}
void accept(EMPLVisitor& v) override { v.visit(*this); }
};
Field reference
| Field | Type | Source | Description |
|---|---|---|---|
kind |
NodeKind::Lambda |
EMPLNode.h:576 |
Discriminator. |
parameters |
vector<pair<string,string>> |
EMPLNode.h:567 |
Un-typed parameter list. Each entry is (parameterName, sourceText). MAY be empty. |
body |
shared_ptr<BlockNode> |
EMPLNode.h:568 |
Block body. Exactly one of body or expressionBody MUST be set. |
expressionBody |
EMPLNodePtr |
EMPLNode.h:569 |
Expression body (for (x) => x + 1). Exactly one of body or expressionBody MUST be set. |
isAsync |
bool |
EMPLNode.h:570 |
true for async lambdas (async (x) => x). |
capturesLexicalThis |
bool |
EMPLNode.h:572 |
true if the lambda captures this lexically (arrow functions in JS, lambdas in Python). Defaults to false. |
isConstructible |
bool |
EMPLNode.h:573 |
false for arrow functions (which cannot be new-ed). Defaults to true. |
hasOwnArguments |
bool |
EMPLNode.h:574 |
false for arrow functions (which inherit arguments from the enclosing function). Defaults to true. |
Constructors
| Constructor | Source | Notes |
|---|---|---|
LambdaNode() (default) |
EMPLNode.h:576 |
All fields default-initialized. |
There is no parameterized constructor. Use default construction + field assignment.
Methods
| Method | Source | Description |
|---|---|---|
accept(EMPLVisitor& v) override |
EMPLNode.h:578 |
Standard visitor dispatch. |
Source mapping
| Source | parameters |
body / expressionBody |
isAsync |
capturesLexicalThis |
isConstructible |
hasOwnArguments |
|---|---|---|---|---|---|---|
(x) => x + 1 (JS arrow) |
[("x","x")] |
expressionBody = BinaryOp("+", x, 1) |
false |
true |
false |
false |
() => 42 |
[] |
expressionBody = Literal(42) |
false |
true |
false |
false |
async (x) => x (TS) |
[("x","x")] |
expressionBody = Identifier("x") |
true |
true |
false |
false |
function(x) { return x; } (JS traditional) |
[("x","x")] |
body = BlockNode{ReturnNode(Identifier("x"))} |
false |
false |
true |
true |
lambda x: x + 1 (Python) |
[("x","x")] |
expressionBody = BinaryOp("+", x, 1) |
false |
true |
false |
false |
def f(x): return x + 1 (Python, but treated as expression) |
[("x","x")] |
body = BlockNode{…} |
false |
true |
false |
false |
|x| x + 1 (Ruby block) |
[("x","x")] |
expressionBody = BinaryOp("+", x, 1) |
false |
false |
true |
true |
func(x) { x + 1 } (Go func literal) |
[("x","x")] |
body = BlockNode{…} |
false |
false |
true |
true |
|x| x + 1 (Rust closure) |
[("x","x")] |
expressionBody = BinaryOp("+", x, 1) |
false |
true (depends on usage) |
true |
true |
(x) => { return x; } (TS expression block) |
[("x","x")] |
body = BlockNode{…} |
false |
true |
false |
false |
Validation rules
| Rule | Constraint |
|---|---|
| V1 | body or expressionBody MUST be set (exactly one). |
| V1 | parameters MAY be empty. |
Structural-attribute rationale
The three booleans (capturesLexicalThis, isConstructible, hasOwnArguments) were introduced in v3 to replace the legacy metadata["js.arrow"] = "true" flag. They are structural — they describe properties of the lambda itself, not the source language — so backends do not need to know which language produced the lambda.
| Source-language feature | capturesLexicalThis |
isConstructible |
hasOwnArguments |
|---|---|---|---|
| JS arrow function | true |
false |
false |
| JS traditional function | false |
true |
true |
Python lambda |
true |
false |
false |
Python def (used as closure) |
true (lexical) |
true |
false (no arguments) |
| Ruby block | false |
true |
true |
| Go func literal | false |
true |
true |
| Rust closure (Fn / FnMut / FnOnce) | true (Fn / FnMut) or false (FnOnce) |
true |
true |
Backend lowering (C++)
For capturesLexicalThis = false and isConstructible = true (traditional function):
auto lambda = [params...](args...) -> RetType {
// body
};
For arrow-style (capturesLexicalThis = true, isConstructible = false, hasOwnArguments = false):
auto lambda = [&env, params...](args...) -> RetType {
// body
};
// where env is the closure-binding struct produced by EMPLCanonicalizer::extractClosureBindings
For async (isAsync = true):
auto lambda = [params...](args...) -> mpl::mpl_future<RetType> {
co_return /* body */;
};
The closure-binding struct is a mpl::mpl_object containing one slot per captured variable.
Closure binding extraction
EMPLCanonicalizer::extractClosureBindings(body, parameters, typedParameters) (declared at EMPLCanonicalizer.h:21) returns:
struct ClosureBindings {
std::vector<std::string> capturedVars; // sorted
bool capturesThis = false;
};
The backend uses this to allocate the env struct referenced in the lambda capture list. See ../canonicalizer.md for the analysis rules.
AwaitNode
// MPL_Compiler/core/ir/EMPLNode.h:581
class AwaitNode : public EMPLNode {
public:
EMPLNodePtr expression;
AwaitNode() : EMPLNode(NodeKind::Await) {}
void accept(EMPLVisitor& v) override { v.visit(*this); }
};
| Field | Type | Description |
|---|---|---|
expression |
EMPLNodePtr |
The expression whose value to await. Required. |
Source mapping
| Source | Field config |
|---|---|
await promise (JS) |
expression=Identifier("promise") |
await fetch(url) |
expression=FunctionCallNode("fetch", url) |
await asyncio.sleep(1) (Python) |
expression=FunctionCallNode("asyncio.sleep", Literal(1)) |
co_await fut (C++) |
expression=Identifier("fut") (canonicalized from co_await) |
Validation rules
| Rule | Constraint |
|---|---|
| V1 | expression MUST be non-null. |
| V1 | AwaitNode MUST appear inside a FunctionDeclNode or LambdaNode with isAsync = true. The validator warns (rule V1 strict mode) if used outside an async context. |
Backend lowering (C++)
For mpl_promise<T>: mpl_await(expr).
For stdlib futures: expr.wait() (if void return) or expr.get() (if value).
For raw values: expr (the await is a no-op).
YieldNode
// MPL_Compiler/core/ir/EMPLNode.h:590
class YieldNode : public EMPLNode {
public:
EMPLNodePtr expression;
bool delegate = false;
YieldNode() : EMPLNode(NodeKind::Yield) {}
void accept(EMPLVisitor& v) override { v.visit(*this); }
};
| Field | Type | Description |
|---|---|---|
expression |
EMPLNodePtr |
The yielded value. MAY be nullptr (bare yield). |
delegate |
bool |
true for yield* (JS) — delegates iteration to an inner iterable. Defaults to false. |
Source mapping
| Source | Field config |
|---|---|
yield 1 (JS generator) |
expression=Literal(1), delegate=false |
yield (no value) |
expression=null, delegate=false |
yield* other (JS) |
expression=Identifier("other"), delegate=true |
yield x (Python) |
expression=Identifier("x"), delegate=false |
Validation rules
| Rule | Constraint |
|---|---|
| V1 | YieldNode MUST appear inside a FunctionDeclNode or LambdaNode with isGenerator = true. Validator warns otherwise. |
Backend lowering (C++)
mpl_yield(generator, value) — mpl_yield is a template defined in empl.h:365 (non-Promise case) and empl.h:376 (Promise case).
For delegate = true: mpl_yield_delegate(generator, inner) (defined in empl.h adapter region).
IR construction example
// JS: const f = (x) => x * 2;
auto lambda = std::make_shared<mpl::ir::LambdaNode>();
lambda->parameters = { {"x", "x"} };
lambda->expressionBody = std::make_shared<mpl::ir::BinaryOpNode>();
auto binOp = std::static_pointer_cast<mpl::ir::BinaryOpNode>(lambda->expressionBody);
binOp->op = "*";
binOp->left = std::make_shared<mpl::ir::IdentifierNode>("x");
binOp->right = mpl::ir::LiteralNode::makeInt(2);
lambda->isAsync = false;
lambda->capturesLexicalThis = true;
lambda->isConstructible = false;
lambda->hasOwnArguments = false;
See also
nodes/function-decl.md—FunctionDeclNode(isAsync,isGeneratorflags)nodes/block.md—BlockNode(used asbody)../canonicalizer.md—extractClosureBindingsnodes/operators.md—BinaryOpNode(typicalexpressionBody)