EMPL IR
Call nodes — `FunctionCallNode`, `MethodCallNode`, `StandardLibraryCallNode`
Call nodes — FunctionCallNode, MethodCallNode, StandardLibraryCallNode
The IR has three call node kinds covering all forms of "invoke something with arguments":
FunctionCallNode — f(arg1, arg2, …)
MethodCallNode — obj.method(arg1, arg2, …)
StandardLibraryCallNode — language-neutral stdlib call (Console.WriteLine, Math.Sqrt, …)
The legacy PrintCallNode is deprecated (see canonicalizer.md rule C1).
FunctionCallNode
// MPL_Compiler/core/ir/EMPLNode.h:493
class FunctionCallNode : public EMPLNode {
public:
std::string name;
std::vector<EMPLNodePtr> arguments;
FunctionCallNode() : EMPLNode(NodeKind::FunctionCall) {}
void accept(EMPLVisitor& v) override { v.visit(*this); }
};
| Field | Type | Source | Description |
|---|---|---|---|
kind |
NodeKind::FunctionCall |
EMPLNode.h:498 |
Discriminator. |
name |
std::string |
EMPLNode.h:495 |
The function identifier. Required. |
arguments |
vector<EMPLNodePtr> |
EMPLNode.h:496 |
Positional arguments. MAY be empty. |
Source mapping
| Source | Field config |
|---|---|
f() |
name="f", arguments=[] |
f(a, b) |
name="f", arguments=[a, b] |
f(...args) (JS spread) |
arguments=[SpreadNode(args)] — SpreadNode is currently FunctionCallNode with metadata["spread"]="true" on the argument; planned: dedicated SpreadNode |
print("hi", x) (Python) |
name="print", arguments=[Literal("hi"), x] — print is also valid as a StandardLibraryCallNode for Console.WriteLine; canonicalizer prefers the latter when safe |
print(x) (Ruby) |
name="print", arguments=[x] — same canonicalization as Python |
Note. The IR does NOT model the callee as a separate
EMPLNodePtr. For dynamic-dispatch calls ((expr)()), frontends should either:
- Lift the call to a temporary variable and emit
FunctionCallNodewith a name, or- Wrap the callee as a
MethodCallNodewhoseobjectis the callee expression and whosemethodNameis empty (canonicalizer will rewrite).A dedicated
CallableExpressionNodeis planned for v4.
MethodCallNode
// MPL_Compiler/core/ir/EMPLNode.h:352
class MethodCallNode : public EMPLNode {
public:
EMPLNodePtr object;
std::string methodName;
std::vector<EMPLNodePtr> arguments;
MethodCallNode() : EMPLNode(NodeKind::MethodCall) {}
void accept(EMPLVisitor& v) override { v.visit(*this); }
};
| Field | Type | Source | Description |
|---|---|---|---|
kind |
NodeKind::MethodCall |
EMPLNode.h:358 |
Discriminator. |
object |
EMPLNodePtr |
EMPLNode.h:354 |
The receiver expression (obj in obj.m(...)). Required. |
methodName |
std::string |
EMPLNode.h:355 |
The method name. Required. |
arguments |
vector<EMPLNodePtr> |
EMPLNode.h:356 |
Positional arguments. MAY be empty. |
Source mapping
| Source | Field config |
|---|---|
obj.m() |
object=Identifier("obj"), methodName="m", arguments=[] |
arr.push(x) |
object=Identifier("arr"), methodName="push", arguments=[x] |
s.substring(0, 3) |
object=Identifier("s"), methodName="substring", arguments=[Literal(0), Literal(3)] |
await fetch(url).then(r => r.json()) |
nested MethodCallNode with arrow function (Lambda) argument |
list.append(x) (Python) |
object=Identifier("list"), methodName="append", arguments=[x] |
m.tryBind(self) (Ruby) |
object=Identifier("m"), methodName="tryBind", arguments=[Identifier("self")] |
Backend lowering (C++)
// obj.m(a, b) → obj.m(a, b) // normal method
// → (obj).m(a, b) // if `obj` is a smart-pointer dereference
// → mpl_call_method(obj, "m", a, b) // if dynamic dispatch
The C++ backend prefers native member access where the receiver has a known static type. For dynamic dispatch (or generic boxed values), the backend emits mpl_call_method(obj, name, args...) (defined in empl.h:680).
StandardLibraryCallNode
The language-neutral standard-library call node. This is the canonical representation of any call to a function in MPL_Standard/Specification.json. Frontends map language-specific built-ins (console.log, print, Math.sqrt) to this node; backends lower it to the target language's equivalent.
// MPL_Compiler/core/ir/EMPLNode.h:517
class StandardLibraryCallNode : public EMPLNode {
public:
std::string moduleName; // e.g. "Console", "Math", "IO"
std::string functionName; // e.g. "WriteLine", "Sqrt", "ReadFileText"
std::vector<EMPLNodePtr> arguments;
std::vector<std::string> argumentNames; // for named arguments (optional)
StandardLibraryCallNode() : EMPLNode(NodeKind::StandardLibraryCall) {}
void accept(EMPLVisitor& v) override { v.visit(*this); }
};
| Field | Type | Source | Description |
|---|---|---|---|
kind |
NodeKind::StandardLibraryCall |
EMPLNode.h:524 |
Discriminator. |
moduleName |
std::string |
EMPLNode.h:519 |
Module name. Must be one of: Console, Environment, IO, Path, Process, Math, String, DateTime (validator V4). |
functionName |
std::string |
EMPLNode.h:520 |
Function name within the module. See the canonical module list below. |
arguments |
vector<EMPLNodePtr> |
EMPLNode.h:521 |
Positional arguments. MAY be empty. |
argumentNames |
vector<string> |
EMPLNode.h:522 |
Parallel array to arguments. Entry i is the named-argument label for arguments[i] (empty string if positional). Filled by canonicalizer rule C8 if left blank. |
Canonical modules
The following table is duplicated from validator.md (rule V4). See ../../standard-library/modules.md for the exhaustive per-module function list with signatures.
| Module | Functions |
|---|---|
Console |
WriteLine, Write, ErrorWriteLine, ErrorWrite, ReadLine |
Environment |
GetVariable, SetVariable, GetCurrentDirectory, SetCurrentDirectory, GetPlatform, GetNewLine |
IO |
ReadFileText, WriteFileText, FileExists, DeleteFile, CreateDirectory, DeleteDirectory, GetDirectoryEntries |
Path |
Join, GetDirectoryName, GetFileName, GetExtension, GetFullPath |
Process |
Exit, GetCommandLineArgs, GetRuntimeVersion |
Math |
Abs, Floor, Ceil, Round, Sqrt, Pow, Max, Min, Pi, E |
String |
Trim, ToLower, ToUpper, StartsWith, EndsWith, Split, Join |
DateTime |
NowUnixMs, NowUnixSeconds |
Source-language mapping
| Source | Lowered to |
|---|---|
console.log("Hi", 42) (JS) |
StandardLibraryCallNode("Console", "WriteLine", ["Hi", 42]) |
print("Hi", 42) (Python) |
StandardLibraryCallNode("Console", "WriteLine", ["Hi", 42]) |
puts "Hi" (Ruby) |
StandardLibraryCallNode("Console", "WriteLine", ["Hi"]) |
print "Hi" (Ruby, no newline) |
StandardLibraryCallNode("Console", "Write", ["Hi"]) |
console.warn("oops") (JS) |
StandardLibraryCallNode("Console", "ErrorWriteLine", ["oops"]) |
console.error("oops") (JS) |
StandardLibraryCallNode("Console", "ErrorWriteLine", ["oops"]) |
Math.sqrt(x) (JS) |
StandardLibraryCallNode("Math", "Sqrt", [x]) |
math.sqrt(x) (Python) |
StandardLibraryCallNode("Math", "Sqrt", [x]) |
Math.sqrt(x) (Java) |
StandardLibraryCallNode("Math", "Sqrt", [x]) |
Mathf.Sqrt(x) (C#) |
StandardLibraryCallNode("Math", "Sqrt", [x]) |
os.environ["PATH"] (Python) |
StandardLibraryCallNode("Environment", "GetVariable", ["PATH"]) (frontends may also emit MemberAccessNode for the indexing part) |
File.ReadAllText(path) (C#) |
StandardLibraryCallNode("IO", "ReadFileText", [path]) |
open(path).read() (Python) |
StandardLibraryCallNode("IO", "ReadFileText", [path]) (Python's open() is a richer API, but the textual-read simplification is valid) |
Date.now() (JS) |
StandardLibraryCallNode("DateTime", "NowUnixMs", []) |
time.time() (Python) |
StandardLibraryCallNode("DateTime", "NowUnixSeconds", []) |
Backend lowering (C++)
The C++ backend emits calls to the canonical implementations in MPL_Standard/CppImpl.h:
// StandardLibraryCallNode("Console", "WriteLine", [a, b])
mpl::System::Console::WriteLine(mpl::mpl_to_string(a) + mpl::mpl_to_string(b));
// StandardLibraryCallNode("Math", "Sqrt", [x])
mpl::System::Math::Sqrt(x);
// StandardLibraryCallNode("IO", "ReadFileText", [path])
mpl::System::IO::ReadFileText(path);
The backend stringifies each argument via mpl::mpl_to_string and concatenates for variadic-style write operations (matching JS console.log semantics). See ../../standard-library/cpp-impl.md for the C++ implementation reference.
Validation rules
| Rule | Constraint |
|---|---|
| V4 | moduleName must be a known stdlib module. |
| V4 | functionName must be a known function within that module. |
| V1 | arguments MAY be empty. |
| V1 | argumentNames MAY be empty (positional). If non-empty, must have same length as arguments. |
Legacy PrintCallNode
// MPL_Compiler/core/ir/EMPLNode.h:503
class PrintCallNode : public EMPLNode {
public:
std::string name;
std::vector<EMPLNodePtr> arguments;
bool newline = false;
PrintCallNode() : EMPLNode(NodeKind::PrintCall) {}
void accept(EMPLVisitor& v) override { v.visit(*this); }
};
| Field | Type | Description |
|---|---|---|
name |
std::string |
Print function name (typically "print"). |
arguments |
vector<EMPLNodePtr> |
Arguments. |
newline |
bool |
true → canonicalizes to Console.WriteLine; false → Console.Write. |
Deprecated by canonicalizer rule C1. Frontends MUST emit StandardLibraryCallNode directly.
See also
nodes/access.md—MemberAccessNode(used to construct the receiver of aMethodCallNode)../validator.md— full rule set../canonicalizer.md— rule C1 (print-call rewrite), rule C8 (named-argument fill)../../standard-library/modules.md— canonical module list