Elvelt

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:

  1. Lift the call to a temporary variable and emit FunctionCallNode with a name, or
  2. Wrap the callee as a MethodCallNode whose object is the callee expression and whose methodName is empty (canonicalizer will rewrite).

A dedicated CallableExpressionNode is 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; falseConsole.Write.

Deprecated by canonicalizer rule C1. Frontends MUST emit StandardLibraryCallNode directly.


See also