Elvelt

EMPL IR

Operator nodes — `BinaryOpNode`, `UnaryOpNode`, `TernaryNode`, `CastNode`

Operator nodes — BinaryOpNode, UnaryOpNode, TernaryNode, CastNode

The IR has three operator nodes: BinaryOpNode, UnaryOpNode, TernaryNode. They cover the vast majority of expressions in source programs. The closely-related CastNode is also documented here.

BinaryOpNode

// MPL_Compiler/core/ir/EMPLNode.h:471
class BinaryOpNode : public EMPLNode {
public:
    std::string op;
    EMPLNodePtr left;
    EMPLNodePtr right;

    BinaryOpNode() : EMPLNode(NodeKind::BinaryOp) {}
    void accept(EMPLVisitor& v) override { v.visit(*this); }
};
Field Type Source Description
kind NodeKind::BinaryOp EMPLNode.h:477 Discriminator.
op std::string EMPLNode.h:473 Operator name (see table below). Required.
left EMPLNodePtr EMPLNode.h:474 Left operand. Required.
right EMPLNodePtr EMPLNode.h:475 Right operand. Required.

Operator allow-list

The validator (rule V2) accepts the following strings in op:

Arithmetic

op Meaning Source examples
+ Addition a + b (all languages)
- Subtraction a - b
* Multiplication a * b
/ Division a / b
% Modulo a % b (JS); a % b (Python); a % b (C, C++, Java)
** Exponentiation a ** b (JS ES2016, Python, Ruby)

Comparison

op Meaning Notes
== Equality (loose) Canonicalized from === if MPL_CANONICALIZE_STRICT=1
!= Inequality (loose) Same
=== Strict equality Preserved in IR; canonicalizer may downgrade (see canonicalizer.md rule C2)
!== Strict inequality Same
< Less than
<= Less than or equal
> Greater than
>= Greater than or equal

Logical

op Meaning
&& Logical AND
|| Logical OR
?? Nullish coalescing (JS, C#)

Bitwise

op Meaning
& Bitwise AND
| Bitwise OR
^ Bitwise XOR
<< Left shift
>> Right shift
>>> Unsigned right shift (JS, Java)

String / collection

op Meaning
in Membership test (x in arr — JS, Python)
instanceof Type test (obj instanceof Class — JS, Java)

Examples

// JS
a + b * c       → BinaryOpNode("+", a, BinaryOpNode("*", b, c))
x === y         → BinaryOpNode("===", x, y)
arr.includes(k) → MethodCallNode("includes", IdentifierNode("arr"), k)  // NOT a binary op
k in obj        → BinaryOpNode("in", k, obj)
# Python
a ** b          → BinaryOpNode("**", a, b)
x is None       → BinaryOpNode("==", x, LiteralNode(null))  // canonicalized from "is"

Source-language mapping

Source syntax op
JS a + b +
JS a === b ===
Python a ** b **
Python a is b == (canonicalized)
Python a is not b != (canonicalized)
Ruby a..b (range) .. (canonicalized to BinaryOpNode("..", a, b)) — kept distinct from comparison <
C# a ?? b ??
PHP $a . $b (concat) + (canonicalized) — backends specializing on string concatenation may read metadata["concat"]
SQL a AND b && (canonicalized)

Backend lowering

Backend Lowered form
C++ Direct operator mapping: ++, **std::pow, === / !==mpl_strict_eq / mpl_strict_ne (see empl.h), ?? → ternary or mpl_coalesce.
Python (planned) Direct mapping; ****, =====.
JavaScript (planned) Direct; === preserved.

UnaryOpNode

// MPL_Compiler/core/ir/EMPLNode.h:482
class UnaryOpNode : public EMPLNode {
public:
    std::string op;
    EMPLNodePtr operand;
    bool isPrefix = true;

    UnaryOpNode() : EMPLNode(NodeKind::UnaryOp) {}
    void accept(EMPLVisitor& v) override { v.visit(*this); }
};
Field Type Source Description
kind NodeKind::UnaryOp EMPLNode.h:488 Discriminator.
op std::string EMPLNode.h:484 Operator name. Required.
operand EMPLNodePtr EMPLNode.h:485 The operand expression. Required.
isPrefix bool EMPLNode.h:486 true for prefix (!x, ++x), false for postfix (x++). Defaults to true.

Operator allow-list

Logical / arithmetic

op isPrefix Meaning
! true Logical NOT
- true Numeric negation
+ true Numeric promotion (unary plus)
~ true Bitwise NOT

Increment / decrement

op isPrefix Meaning
++ true / false Pre/post increment
-- true / false Pre/post decrement

Type queries

op isPrefix Meaning
typeof true Type-of query (JS, TS)
void true void operator (JS) — always returns undefined
delete true Property deletion (JS)

Examples

!x          → UnaryOpNode("!", x, true)
typeof x    → UnaryOpNode("typeof", x, true)
delete o.pUnaryOpNode("delete", MemberAccessNode(o, "p"), true)
i++         → UnaryOpNode("++", i, false)
++i         → UnaryOpNode("++", i, true)
-x          → UnaryOpNode("-", x, true)

Source-language mapping

Source op
JS !x !
JS typeof x typeof
JS void 0 void
C++ *p (dereference) NOT a unary op — represented as a MemberAccessNode with metadata["deref"]="true", OR a dedicated DereferenceNode (planned)
Python -x -
Python not x ! (canonicalized)
Ruby !x !

TernaryNode

// MPL_Compiler/core/ir/EMPLNode.h:610
class TernaryNode : public EMPLNode {
public:
    EMPLNodePtr condition;
    EMPLNodePtr whenTrue;
    EMPLNodePtr whenFalse;

    TernaryNode() : EMPLNode(NodeKind::Ternary) {}
    void accept(EMPLVisitor& v) override { v.visit(*this); }
};
Field Type Source Description
kind NodeKind::Ternary EMPLNode.h:616 Discriminator.
condition EMPLNodePtr EMPLNode.h:612 Test expression. Required.
whenTrue EMPLNodePtr EMPLNode.h:613 Expression evaluated when condition is truthy. Required.
whenFalse EMPLNodePtr EMPLNode.h:614 Expression evaluated when condition is falsy. Required.

Source mapping

Source Field config
c ? a : b (JS / C / Java / etc.) condition=c, whenTrue=a, whenFalse=b
a if c else b (Python) condition=c, whenTrue=a, whenFalse=b (canonicalized)

Backend lowering

Direct C++ ternary: c ? a : b. The backend does NOT desugar ternaries to if-else (it preserves source semantics).


CastNode

// MPL_Compiler/core/ir/EMPLNode.h:600
class CastNode : public EMPLNode {
public:
    std::string targetType;
    EMPLNodePtr expression;

    CastNode() : EMPLNode(NodeKind::Cast) {}
    void accept(EMPLVisitor& v) override { v.visit(*this); }
};
Field Type Source Description
kind NodeKind::Cast EMPLNode.h:605 Discriminator.
targetType std::string EMPLNode.h:602 Source-text type expression (e.g. "int", "Promise<string>", "MyClass"). Required.
expression EMPLNodePtr EMPLNode.h:603 The expression being cast. Required.

Source mapping

Source Field config
(x as number) (TS) targetType="number", expression=x
int(x) (Python) targetType="int", expression=x
(int)x (C / C++ / Java / C#) targetType="int", expression=x
x as MyClass (Rust) targetType="MyClass", expression=x

Backend lowering

Backend Lowered form
C++ static_cast<targetType>(expr) (or reinterpret_cast / const_cast if metadata["cast_kind"] is set)
Python (planned) targetType(expr)
JavaScript (planned) Direct — JS has no real casts

Validation rules

Rule Constraint
V2 BinaryOpNode::op must be in the allow-list above.
V2 UnaryOpNode::op must be in the allow-list above.
V1 All required fields are non-null.

See also