Elvelt

EMPL IR

Literal nodes — `LiteralNode`, `ArrayLiteralNode`, `ObjectLiteralNode`, `RecordLiteralNode`, `TupleLiteralNode`

Literal nodes — LiteralNode, ArrayLiteralNode, ObjectLiteralNode, RecordLiteralNode, TupleLiteralNode

The IR has five literal node kinds covering scalar, sequence, mapping, and product-type literals:

LiteralNode         — number, string, boolean, null, BigInt
ArrayLiteralNode    — ordered, possibly-heterogeneous list
ObjectLiteralNode   — keyed collection (like JS object / dict)
RecordLiteralNode   — typed keyed collection (struct literal)
TupleLiteralNode    — heterogeneous fixed-shape sequence (struct-like)

Plus the closely-related IdentifierNode (bare name reference) is documented at the end.

LiteralNode

// MPL_Compiler/core/ir/EMPLNode.h:393
class LiteralNode : public EMPLNode {
public:
    std::variant<int64_t, double, std::string, bool, std::nullptr_t> value;
    UIRTypePtr type;

    LiteralNode() : EMPLNode(NodeKind::Literal) {}

    static std::shared_ptr<LiteralNode> makeString(const std::string& v);
    static std::shared_ptr<LiteralNode> makeNumber(double v);
    static std::shared_ptr<LiteralNode> makeInt(int64_t v);
    static std::shared_ptr<LiteralNode> makeBool(bool v);

    void accept(EMPLVisitor& v) override { v.visit(*this); }
};

Field reference

Field Type Source Description
kind NodeKind::Literal EMPLNode.h:398 Discriminator.
value variant<int64, double, string, bool, nullptr> EMPLNode.h:395 The literal value. Required.
type UIRTypePtr EMPLNode.h:396 Optional type annotation. nullptr for un-typed literals.

Variant cases

Active alternative Source syntax Notes
int64_t 42, -1, 0xFF, 0o17, 0b101 All integer literals.
double 3.14, 1e10, NaN, Infinity All floating-point literals. NaN/Infinity map to std::numeric_limits<double>::quiet_NaN() / infinity() in C++.
std::string "hello", 'hello', `template` String literals. Frontends normalize quote styles.
bool true, false
std::nullptr_t null, nil, None, Nothing, undefined (when used as value) Each source language has its own spelling; canonicalized to nullptr_t in the IR.

Note. BigInt literals (42n in JS, 42n in Python) are currently NOT represented as LiteralNode — they require a dedicated BigIntLiteralNode with a std::string payload (the canonical decimal representation). This is a planned addition for v4. For now, BigInt literals emit an UnsupportedNode with reason = "BigInt literal not yet supported".

Factory methods

The factory methods construct a LiteralNode with the appropriate variant alternative:

Method Source Produces
makeString(v) EMPLNode.h:400 LiteralNode { value = std::string(v) }
makeNumber(v) EMPLNode.h:406 LiteralNode { value = double(v) }
makeInt(v) EMPLNode.h:412 LiteralNode { value = int64_t(v) }
makeBool(v) EMPLNode.h:418 LiteralNode { value = bool(v) }

All four return std::shared_ptr<LiteralNode> so they can be dropped directly into an arguments vector or statements list.

Validation rules

Rule Constraint
V1 value is always populated (cannot construct an empty LiteralNode in practice).

Backend lowering

Variant C++ lowered form
int64_t(v) int64_t{v} or static_cast<int64_t>(v) (preferred for explicit narrowing)
double(v) {v} or {v}L for long double context
string(v) std::string{"…"} or u8"…" for UTF-8 explicit
bool(v) true / false
nullptr nullptr or mpl_value(nullptr)

For boxed-target lowering (Python / JS backends), the backend boxes via mpl_box_value.


ArrayLiteralNode

// MPL_Compiler/core/ir/EMPLNode.h:529
class ArrayLiteralNode : public EMPLNode {
public:
    std::vector<EMPLNodePtr> elements;

    ArrayLiteralNode() : EMPLNode(NodeKind::ArrayLiteral) {}
    void accept(EMPLVisitor& v) override { v.visit(*this); }
};
Field Type Description
elements vector<EMPLNodePtr> The list elements, in order. MAY be empty.

Source mapping

Source Field config
[1, 2, 3] elements=[Literal(1), Literal(2), Literal(3)]
[] elements=[]
[1, "x", true] elements=[Literal(1), Literal("x"), Literal(true)] (heterogeneous OK)
[...arr, 5] (JS spread) elements=[SpreadNode(arr), Literal(5)] (spread represented by metadata for now)
Array(5) (JS) NOT a literal — emitted as FunctionCallNode("Array", Literal(5))

Backend lowering (C++)

std::vector<mpl_value>{…} or, if all elements share a type, std::vector<T>{…}.


ObjectLiteralNode

// MPL_Compiler/core/ir/EMPLNode.h:538
class ObjectLiteralNode : public EMPLNode {
public:
    std::vector<std::pair<EMPLNodePtr, EMPLNodePtr>> properties;

    ObjectLiteralNode() : EMPLNode(NodeKind::ObjectLiteral) {}
    void accept(EMPLVisitor& v) override { v.visit(*this); }
};
Field Type Description
properties vector<pair<EMPLNodePtr, EMPLNodePtr>> Each entry is (key, value). Keys are typically LiteralNode(string) or IdentifierNode.

Source mapping

Source Field config
{ a: 1, b: 2 } (JS) properties=[(Literal("a"), Literal(1)), (Literal("b"), Literal(2))]
{ a, b } (JS shorthand) properties=[(Identifier("a"), Identifier("a")), (Identifier("b"), Identifier("b"))]
{ ["k" + i]: v } (computed keys) properties=[(BinaryOp("+", "k", i), v)]
{ "a": 1 } (quoted keys, JS) properties=[(Literal("a"), Literal(1))]
{ a: 1, b: 2 } (Python dict) Same as JS object literal.
{ 'a' => 1 } (Perl / PHP) Same shape; canonicalizer normalizes => to : semantics.

ObjectLiteralNode vs. RecordLiteralNode: ObjectLiteralNode is for untyped object/dict literals (JS objects, Python dicts). RecordLiteralNode (below) is for typed struct literals (TS {x: number, y: number} with a declared shape). The frontend decides which to emit based on whether a type annotation is present.

Backend lowering (C++)

For ObjectLiteralNode: mpl::mpl_make_object({{"a", mpl_box_value(1)}, {"b", mpl_box_value(2)}}). For Python backend (planned): {"a": 1, "b": 2}.


RecordLiteralNode

// MPL_Compiler/core/ir/EMPLNode.h:547
class RecordLiteralNode : public EMPLNode {
public:
    std::vector<std::pair<EMPLNodePtr, EMPLNodePtr>> properties;

    RecordLiteralNode() : EMPLNode(NodeKind::RecordLiteral) {}
    void accept(EMPLVisitor& v) override { v.visit(*this); }
};
Field Type Description
properties vector<pair<EMPLNodePtr, EMPLNodePtr>> The (fieldName, value) pairs, in declaration order.

RecordLiteralNode is structurally identical to ObjectLiteralNode but semantically distinct: the keys MUST be statically known identifiers, and a type annotation MUST be present in the source (typically via metadata["record_type"] or by being the initializer of a VariableDeclNode whose type is Class/Interface).

Source mapping

Source Field config
Point { x: 1, y: 2 } (Rust) properties=[(Identifier("x"), Literal(1)), (Identifier("y"), Literal(2))]
Point { x: 1, y: 2 } (TS with type) Same shape; emitted only if the surrounding VariableDecl::type is a Class / Interface.
new Point { x = 1, y = 2 } (C# object initializer) Same shape.

Backend lowering (C++)

Point{ /* x = */ 1, /* y = */ 2 }

The C++ backend uses designated initializers when supported (C++20), aggregate initialization otherwise.


TupleLiteralNode

// MPL_Compiler/core/ir/EMPLNode.h:556
class TupleLiteralNode : public EMPLNode {
public:
    std::vector<EMPLNodePtr> elements;

    TupleLiteralNode() : EMPLNode(NodeKind::TupleLiteral) {}
    void accept(EMPLVisitor& v) override { v.visit(*this); }
};
Field Type Description
elements vector<EMPLNodePtr> The tuple components, in order. MAY be empty (empty tuple).

A tuple literal represents a fixed-shape, heterogeneous value, distinct from an array (which has uniform element type). Common in Python, TypeScript (when targeting fixed-length arrays), Rust, Swift.

Source mapping

Source Field config
(1, "x") (Python) elements=[Literal(1), Literal("x")]
[1, "x"] as [number, string] (TS tuple annotation) elements=[Literal(1), Literal("x")]
(1, "x") (Rust) elements=[Literal(1), Literal("x")]
(1, "x") (Swift) elements=[Literal(1), Literal("x")]

Note. Parenthesized expressions are NOT tuples — (x) is just x in parentheses. Only multi-element parenthesized expressions with commas are tuples. Frontends must disambiguate based on context (LHS of assignment to a tuple-typed variable, etc.).

Backend lowering (C++)

std::tuple<int64_t, std::string>{1, "x"}

The C++ backend uses std::pair for two-element tuples (lighter weight) and std::tuple for three or more.


IdentifierNode

// MPL_Compiler/core/ir/EMPLNode.h:383
class IdentifierNode : public EMPLNode {
public:
    std::string name;

    explicit IdentifierNode(const std::string& n)
        : EMPLNode(NodeKind::Identifier), name(n) {}

    void accept(EMPLVisitor& v) override { v.visit(*this); }
};
Field Type Description
name std::string The bare identifier. Required.

Source mapping

Source Field config
x (bare reference) name="x"
myVar name="myVar"
this name="this" (canonical — frontends normalize self (Python/Ruby) and Me (VB) to this)
arguments (inside a function body) name="arguments" (canonical)
_ (Go blank) name="_"

Note. IdentifierNode is the only node with a parameterized constructor (EMPLNode.h:387). All other nodes use default construction followed by field assignment.

Validation rules

Rule Constraint
V7 name MUST be non-empty.
V7 name MUST NOT contain control characters or whitespace.

Backend lowering (C++)

Direct — the identifier is emitted as-is. The C++ backend does NOT validate that the identifier is declared (that's the source frontend's job).


See also