EMPL IR
EMPLValidator — IR structural validation
EMPLValidator — IR structural validation
EMPLValidator walks an IR tree and produces a structured ValidationResult of errors and warnings. It is declared in MPL_Compiler/core/ir/EMPLValidator.h (27 lines) and implemented in EMPLValidator.cpp.
// MPL_Compiler/core/ir/EMPLValidator.h:17
class EMPLValidator {
public:
static ValidationResult validate(const std::shared_ptr<ModuleNode>& module);
static ValidationResult validate(const std::shared_ptr<ProgramNode>& program);
private:
static void validateNode(const EMPLNodePtr& node,
ValidationResult& result,
bool strictUnsupported);
};
ValidationResult
// MPL_Compiler/core/ir/EMPLValidator.h:11
struct ValidationResult {
bool ok = true;
std::vector<std::string> errors;
std::vector<std::string> warnings;
};
| Field | Type | Meaning |
|---|---|---|
ok |
bool |
true if errors.empty(). warnings do NOT affect ok. |
errors |
vector<string> |
Human-readable error messages (one per failure). An error means the IR is malformed. |
warnings |
vector<string> |
Human-readable warnings (e.g. legacy PrintCall use). The IR is correct, but the caller may want to upgrade. |
API
| Method | Source | Purpose |
|---|---|---|
validate(module) |
EMPLValidator.h:19 |
Validate a single module. Strict on Unsupported. |
validate(program) |
EMPLValidator.h:20 |
Validate a program. Walks every module. |
validateNode(node, result, strictUnsupported) (private) |
EMPLValidator.h:23 |
Per-node recursive check. |
validateNode is private; users only call the two public overloads. Internally both public methods dispatch to validateNode with strictUnsupported = true.
Usage
#include "core/ir/EMPLValidator.h"
auto result = mpl::ir::EMPLValidator::validate(module);
if (!result.ok) {
for (const auto& err : result.errors) {
std::cerr << "IR error: " << err << "\n";
}
return 1;
}
for (const auto& warn : result.warnings) {
std::cerr << "IR warning: " << warn << "\n";
}
Validation rules
The validator checks the following categories. Each rule produces one or more error messages with the offending node's SourceLocation prefix (<file>:<line>:<col> when available).
Rule V1 — Required child slots
Each node kind has a set of required child slots. Missing required children is an error.
| Node | Required slots |
|---|---|
ModuleNode |
name (non-empty string) |
FunctionDeclNode |
name (non-empty); body may be nullptr only if the function is abstract / extern (no flag for this currently — nullptr body is allowed for forward declarations) |
IfNode |
condition, thenBranch |
ForNode |
body; init / condition / increment MAY be nullptr (empty parts of for (;;)) |
WhileNode |
condition, body |
DoWhileNode |
body, condition |
SwitchNode |
expr, cases[] (default branch may be nullptr) |
TryCatchNode |
tryBlock |
BinaryOpNode |
left, right, op |
UnaryOpNode |
operand, op |
FunctionCallNode |
name, arguments[] |
MethodCallNode |
object, methodName |
MemberAccessNode |
object, memberName |
IndexAccessNode |
object, index |
LiteralNode |
value (always present by construction) |
IdentifierNode |
name |
TernaryNode |
condition, whenTrue, whenFalse |
AwaitNode |
expression |
YieldNode |
expression |
LambdaNode |
parameters[], body or expressionBody (at least one) |
ImportNode |
module (non-empty) |
ExportNode |
either bindings[] non-empty, value != nullptr, or isReexport = true |
CastNode |
targetType (non-empty), expression |
ReturnNode / ThrowNode |
value MAY be nullptr |
Rule V2 — Operator allow-list (BinaryOpNode, UnaryOpNode)
For BinaryOpNode::op, the validator accepts the following strings (others produce an error):
| Category | Allowed op strings |
|---|---|
| Arithmetic | +, -, *, /, %, ** |
| Comparison | ==, !=, ===, !==, <, <=, >, >= |
| Logical | &&, ` |
| Bitwise | &, |, ^, <<, >>, >>> |
| String / collection | in, instanceof |
For UnaryOpNode::op:
| Category | Allowed op strings |
|---|---|
| Logical | ! |
| Arithmetic | +, - |
| Increment / decrement | ++, -- |
| Type queries | typeof, void, delete |
| Bitwise | ~ |
Note.
===/!==are accepted as IR-level operators (their semantics are language-defined; backends MAY lower them to==/!=when the language lacks strict equality).
Rule V3 — Assignment operator allow-list
AssignmentNode::op accepts exactly:
op |
Meaning |
|---|---|
= |
Plain assignment |
+= |
Add-assign |
-= |
Subtract-assign |
*= |
Multiply-assign |
/= |
Divide-assign |
%= |
Modulo-assign |
**= |
Power-assign (where supported) |
<<=, >>=, >>>=, &=, |=, ^= |
Bitwise compound assigns |
&&=, ||=, ??= |
Logical / nullish compound assigns |
Anything else is an error.
Rule V4 — Standard-library module/function names
StandardLibraryCallNode::moduleName and functionName are checked against the canonical MPL_Standard/Specification.json module list. Unknown combinations produce an error. The canonical module set is:
| Module | Functions (canonical) |
|---|---|
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 |
This list is duplicated from MPL_Standard/README.md lines 49-56 and MPL_Standard/CppImpl.h lines 32-332.
Rule V5 — Unsupported nodes (strict mode)
When strictUnsupported is true (the default for both public methods), the validator produces an error for every UnsupportedNode. When false, an UnsupportedNode produces a warning instead.
This is the recommended gate for release builds: run EMPLValidator::validate after the frontend parses and before the canonicalizer runs. If the result has any UnsupportedNode, the build fails.
Rule V6 — Legacy nodes produce warnings
The following legacy NodeKind values always produce a warning (not an error) so that older frontends can keep emitting them while still passing validation:
| Legacy kind | Warning message |
|---|---|
PrintCall |
"PrintCall is deprecated; emit StandardLibraryCall(Console, WriteLine|Write, args) instead" |
FuncDecl |
"FuncDecl is a retired alias; use FunctionDecl" |
MethodNode alias |
(only at the C++ type level — no NodeKind) |
The validator does NOT downgrade these to errors, so legacy frontends continue to work; new code SHOULD upgrade.
Rule V7 — Identifier name validity
IdentifierNode::name must be a non-empty string and must not contain control characters (\0–\x1F) or whitespace.
Rule V8 — Variable declaration invariants
VariableDeclNode invariants:
- If
nameis non-empty ANDpatternis non-null, the validator reports an error: pick one or the other. - If both
nameandpatternare empty, the validator reports an error.
Rule V9 — Class declaration invariants
ClassDeclNode invariants:
namemust be non-empty.methods[]may contain any number ofFunctionDeclNodeentries. A method without a body (abstract) MUST haveisStatic = false(abstract static methods are not modeled).- If
baseClassis non-null, itskindMUST beIdentifierorMemberAccess(a class identifier expression).
Rule V10 — Import / export invariants
ImportNode invariants:
modulemust be non-empty.- If
isSideEffectOnly = true,bindingsMUST be empty. - If
bindingsis non-empty, everyImportBinding::localNamemust be non-empty.
ExportNode invariants:
- Exactly one of:
isDefault = true,isReexport = truewithreexportModulenon-empty, ORbindingsnon-empty ORvalue != nullptr.
Failure modes
If validation finds errors, the compiler is expected to fail fast with a non-zero exit code. There is no auto-correction step; canonicalization does not repair invalid IR.
See also
index.md— IR overviewcanonicalizer.md— normalizes IR after validationprinter.md— print the IR (typically after validation)../standard-library/modules.md— canonical module list