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 (
42nin JS,42nin Python) are currently NOT represented asLiteralNode— they require a dedicatedBigIntLiteralNodewith astd::stringpayload (the canonical decimal representation). This is a planned addition for v4. For now, BigInt literals emit anUnsupportedNodewithreason = "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 anargumentsvector orstatementslist.
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 justxin 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
nodes/access.md—MemberAccessNode(compound identifier access)nodes/operators.md— operators that consume literalstypes.md—UIRTypeused inLiteralNode::type../standard-library/modules.md— module-list / function names that interact with literal-typed arguments