Frontends
JavaScript / TypeScript Frontend
JavaScript / TypeScript Frontend
The JavaScript frontend is by far the largest and most feature-complete
frontend in MPL_Compiler. It supports ECMAScript 2025, Node.js v26,
TypeScript 6.0, JSX, and the CommonJS + ES Module module
systems (Node.js resolution algorithm with node_modules and
package.json#exports/#imports).
| File | Lines | Purpose |
|---|---|---|
frontends/javascript/JSFrontend.h |
1,002 | Frontend class — the entry point that subclasses BraceFrontendBase |
frontends/javascript/JSGlobals.h |
290 | Three maps: globalIdentMap (variable lookup), globalCtorMap (new X()), globalCallMap (X(...)) |
frontends/javascript/JsStdLib.h |
63 | Public API declaration for findStdLibMapping |
frontends/javascript/JsStdLib.cpp |
56 | The actual StdLib mapping table |
frontends/javascript/Lexer.h / .cpp |
49 / 680 | Hand-written lexer (tokens for =>, ?., ??, **, etc.) |
frontends/javascript/Parser.h / .cpp |
203 / 1,586 | Pratt-style parser, plus a TypeScript sub-parser for interface, type, namespace, decorators, type assertions |
frontends/javascript/runtime/js_runtime_umbrella.h |
54 | Roadmap for migrating JS-specific runtime out of empl.h |
frontends/javascript/runtime/js_export_star.h |
105 | __mpl_exportStar for TS CJS interop |
frontends/javascript/runtime/js_node.h |
57 | CommonJS wrappers (__require__, __dirname__, __filename__, __mpl_require_resolve__, __mpl_require_cache__, __mpl_require_main__) |
frontends/javascript/runtime/js_semantics.h |
29,454 | JS language semantics (this, Symbol, decorators, async/await, generators, ES2025 Iterator Helpers, WHATWG Streams, Web Crypto, PQC) |
frontends/javascript/runtime/js_es2026.h |
2,287 | ES2025/ES2026 extras: Math.sumPrecise, Error.isError, Promise.try, Array.fromAsync, Temporal API, RegExp.escape, Uint8Array base64, Intl, structuredClone |
Language registration
// JSFrontend.h:14-15
std::string languageName() const override { return "javascript"; }
std::vector<std::string> fileExtensions() const override {
return {".js", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts", ".jsx"};
}
Eight file extensions are recognised: vanilla JS, ESM, CommonJS,
TypeScript (.ts), React TSX, ESM TS, CommonJS TS, and JSX.
Module systems
ESM (import / export)
JSFrontend::tryParseLanguageSpecificStatement (JSFrontend.h:651-706)
handles every import shape:
| Source form | IR produced |
|---|---|
import 'side-effect-only' |
FunctionCallNode("__import__", ["side-effect-only", "side_effect"]) |
import x from 'm' |
VariableDeclNode(name="x", decl_origin="import") with FunctionCallNode("__import__", ["m", "default"]) |
import { a, b as c } from 'm' |
One VariableDeclNode per binding with FunctionCallNode("__import__", ["m", "<importedName>"]) |
import * as ns from 'm' |
VariableDeclNode(name="ns") with FunctionCallNode("__import_namespace__", ["m"]) |
import x = require('m') (TS) |
VariableDeclNode(decl_origin="import_eq") with FunctionCallNode("__require__", ["m"]) |
import type { T } from 'm' |
Same as above with metadata["type_only"] = "true" |
CommonJS (require / module.exports)
| Source form | IR produced |
|---|---|
const x = require('m') |
VariableDeclNode(decl_origin="require") with FunctionCallNode("__require__", ["m"]) |
var a = require('x'), b = require('y') |
Two VariableDeclNodes, each with __require__ |
module.exports = value |
FunctionCallNode("__mpl_module_exports__", [value]) |
exports.foo = value |
ExportNode wrapping AssignmentNode(target=MemberAccessNode("exports","foo"), value=value, op="=") |
export * from 'm' |
FunctionCallNode("__reexport_all__", ["m"]) |
export * as ns from 'm' |
FunctionCallNode("__export_binding__", ["ns", __import_namespace__("m")]) |
The parser.parseRequireCall / parser.parseCommonJSExport /
parser.parseImportDeclaration / parser.parseExportDeclaration helpers
live in Parser.h:151-185.
Strict mode
JS is the only frontend with supportsStrictMode() = true
(JSFrontend.h:372).
detectUseStrictDirective (BraceFrontendBase.h:105-192) looks for
"use strict"; (or 'use strict';) as the first non-comment, non-shebang,
non-string-literal directive in the source. When found:
mod->metadata["strict_mode"] = "true"strictMode_instance flag is set- The C++ backend then enforces: duplicate lexical bindings, octal escape
rejection,
withstatement rejection, reserved-word binding rejection, duplicate parameter rejection (function & arrow), etc.
The CLI exposes --js-source-type module|script|auto to control
IModuleAwareFrontend::setForceModuleStrictMode. ESM is always strict.
Modern JS / TS features (file:line)
ES2025 features
| Feature | Where | Notes |
|---|---|---|
Iterator Helpers (map, filter, take, drop, toArray, forEach, …) |
js_semantics.h:10785 |
Full Stage 3 implementation |
Map.getOrInsert / getOrInsertComputed |
js_es2026.h |
Set + Map extensions |
Set.prototype.union/intersection/difference/symmetricDifference/isSubsetOf/isSupersetOf/isDisjointFrom |
js_es2026.h |
New Set methods |
Math.sumPrecise |
js_es2026.h:75 |
long double accumulation |
Error.isError |
js_es2026.h:92 |
Brand check (__Error_brand__) |
Promise.try |
js_es2026.h:113 |
|
Array.fromAsync |
js_es2026.h:127 |
|
RegExp.escape |
js_es2026.h:944 |
|
Object.hasOwnProperty |
js_es2026.h:936 |
|
Uint8Array base64 / hex |
js_es2026.h:151 |
ES2025 / Node v26 |
path.matchesGlob |
js_es2026.h:679 |
Node v22+ |
node:sqlite |
js_es2026.h:702 |
Node v22+ stable, v26 default |
Temporal API |
js_es2026.h:188 |
PlainDate/PlainTime/PlainDateTime/ZonedDateTime/Duration/Instant/TimeZone/Calendar |
Intl namespace |
js_es2026.h:960 |
ICU-backed |
structuredClone |
js_es2026.h:2105 |
W3C/HTML spec |
TypeScript 6.0 features
| Feature | Where |
|---|---|
interface IFoo { … } |
JSFrontend.h:781-789 → InterfaceDeclNode |
type X = … |
JSFrontend.h:792-800 → TypeAliasDeclNode |
namespace NAME { … } |
JSFrontend.h:101-252 (rewrite) → const NAME = { members } |
import type { … } from '…' |
JSFrontend.h:803-852 (type-only metadata) |
import x = require('…') |
JSFrontend.h:874-887 (CommonJS interop) |
export = value |
JSFrontend.h:890-899 (__mpl_module_exports__) |
declare statement |
JSFrontend.h:865-871 (type-erased UnsupportedNode) |
Decorators @dec |
Stage 3 — supported via js_semantics.h |
as Type / <Type>expr type assertions |
Parser.h:167-168 |
JSX / TSX
JSFrontend.h:907-930 detects JSX elements (looks for < followed by
identifier/_/$, then a closing > after a </). Emits a
FunctionCallNode(name="jsx") with metadata jsx.placeholder="true" and
the raw line as a single string argument. The C++ backend renders JSX as
a passthrough placeholder (real JSX → C++ codegen is not yet shipped).
Class fields
| Form | Where handled |
|---|---|
class Foo { x = 1; } |
BraceFrontendBase::parseClassDeclaration |
class Foo { #x = 1; } (private field) |
BraceFrontendBase |
class Foo { static #x = 1; } |
BraceFrontendBase |
class Foo { #method() { … } } (private method) |
BraceFrontendBase |
class Foo { #x; } (ES2025 private brand check) |
js_semantics.h |
using / await using
Detected in JSFrontend.h:497-501:
using file = openFile("…");
await using resource = acquireAsync();
VariableDeclNode is emitted with metadata["using_decl"]="true" (and
using_await="true" for the awaitable form).
for await (… of …) / await … for
Handled by BraceFrontendBase via the AwaitNode wrapping the for-of
loop iterable.
BigInt
BigInt is in JSGlobals.h:36 (mpl_get_value(__mpl_globalThis(), "BigInt")).
123n literals flow through LiteralNode::makeBigInt. All BigInt64 /
BigUint64 typed arrays are mapped (lines 68-69).
Symbol
Symbol is at JSGlobals.h:37. The full implementation lives in
js_semantics.h. Symbol.for, Symbol.keyFor, Symbol.iterator, and the
well-known symbols are supported.
Global identifier resolution (JSGlobals.h)
Three lookup tables, queried by the C++ backend via the
js_runtime_sym/js_runtime_ctor/js_runtime_call metadata keys:
globalIdentMap (JSGlobals.h:21-115)
Maps every free-standing JS global to a C++ expression that materialises it.
For example Object → __mpl_Object(), console →
mpl_get_value(__mpl_globalThis(), "console"), __dirname →
mpl_box_value(__mpl_path_dirname(__import_meta__.url)).
| Category | Identifiers |
|---|---|
| Object constructors | Object, Array, String, Number, Boolean, Error, TypeError, RangeError, ReferenceError, SyntaxError, EvalError, URIError, AggregateError, BigInt, Symbol, Reflect, Promise, Iterator, AsyncIterator |
| URL / Streams | URL, ReadableStream, WritableStream, TransformStream, CountQueuingStrategy, ByteLengthQueuingStrategy |
| Weak refs | WeakRef, FinalizationRegistry, SuppressedError |
| Typed arrays | TypedArray, Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, BigInt64Array, BigUint64Array, Buffer, ArrayBuffer, SharedArrayBuffer, DataView |
| Networking | Headers, Request, Response, FormData, fetch, WebSocket |
| Encoding | TextEncoder, TextDecoder |
| CJS interop | require, __dirname, __filename, module, process, exports, performance |
| Encoding helpers | parseInt, parseFloat, encodeURI, encodeURIComponent, decodeURI, decodeURIComponent |
| Timers | setTimeout, clearTimeout, setInterval, clearInterval, setImmediate, clearImmediate, queueMicrotask |
| Special values | NaN, Infinity, undefined, globalThis, global, self |
| Other | eval, isNaN, isFinite, define, escape, unescape, RegExp, Function |
globalCtorMap (JSGlobals.h:117-177)
Subset of globalIdentMap for new X(…) constructor sites. Includes
additions like TextEncoderStream, TextDecoderStream, CompressionStream,
DecompressionStream.
globalCallMap (JSGlobals.h:179-255)
Subset of globalIdentMap for X(…) call sites.
isJsGlobal (JSGlobals.h:275-286)
The true test for "this is a JS runtime identifier". Includes
Math, JSON, Date, Atomics, Intl, WebAssembly, Proxy,
Date, plus all identifiers already in globalIdentMap.
Extra globals set (JSGlobals.h:279-285)
{"this", "super", "undefined", "exports", "module", "globalThis", "global", "self",
"Date", "Math", "JSON", "Atomics", "Intl", "WebAssembly", "Proxy",
"__dirname", "__filename", "__mpl_undefined__", "__omit_keys__",
"__magic__", "typeof", "NaN", "Infinity"}
Standard library mapping (JsStdLib.cpp)
The complete table is in frontends/javascript/JsStdLib.cpp:7-38. Each
mapping is a 4-tuple (objectName, methodName, mplModule, mplFunction).
| JS source | MPL module.function |
|---|---|
console.log(x, y, …) |
Console.WriteLine(x, y, …) |
console.info(x, y, …) |
Console.WriteLine(x, y, …) |
console.warn(x, y, …) |
Console.ErrorWriteLine(x, y, …) |
console.error(x, y, …) |
Console.ErrorWriteLine(x, y, …) |
console.debug(x, y, …) |
Console.WriteLine(x, y, …) |
Math.abs(x) |
Math.Abs(x) |
Math.floor(x) |
Math.Floor(x) |
Math.ceil(x) |
Math.Ceil(x) |
Math.round(x) |
Math.Round(x) |
Math.sqrt(x) |
Math.Sqrt(x) |
Math.pow(x, y) |
Math.Pow(x, y) |
Math.max(a, b, …) |
Math.Max(a, b, …) |
Math.min(a, b, …) |
Math.Min(a, b, …) |
Math.random() |
Math.Random() |
Math.trunc(x) |
Math.Trunc(x) |
Math.sign(x) |
Math.Sign(x) |
Math.log(x) |
Math.Log(x) |
Math.log2(x) |
Math.Log2(x) |
Math.log10(x) |
Math.Log10(x) |
Math.sin(x) |
Math.Sin(x) |
Math.cos(x) |
Math.Cos(x) |
Math.tan(x) |
Math.Tan(x) |
Math.atan2(y, x) |
Math.Atan2(y, x) |
process.exit(code) |
Process.Exit(code) |
process.cwd() |
Environment.GetCurrentDirectory() |
JSON.stringify(v) |
Json.Stringify(v) |
JSON.parse(s) |
Json.Parse(s) |
The tryConvertToStandardLibraryCall method (JSFrontend.h:17-40) calls
findStdLibMapping and explicitly excludes network modules
(Http, Https, Net, Tls, Stream, Worker — those still flow
through ordinary IR because they need richer control flow than
StandardLibraryCallNode can carry).
JavaScript runtime (frontends/javascript/runtime/)
js_runtime_umbrella.h (roadmap)
The architectural plan for extracting JS-specific symbols from
MPL_Standard/System/empl.h Section 3, 4, and 6 into per-concern headers.
Section 5 (process / OS / crypto / ISO-8601 helpers) was already migrated
to the language-agnostic empl_runtime.h.
js_export_star.h (Phase 2 — done)
__mpl_exportStar(target, source) implements TypeScript's
export * from "module" semantics when the source is a CJS module.
Copies five property slots to preserve all descriptor types:
- Data descriptors (
value + writable + enumerable + configurable) mpl_object_value_slots()— actualmpl_valuestoragempl_callable_slots()— callable methodsmpl_getter_slots()— accessor-only re-exports (critical for TS CJS shims)mpl_setter_slots()— paired with getters
Skips default and __esModule per the TS __exportStar semantics
(ESM/CJS interop).
js_node.h (Section 4 partial)
CommonJS wrappers and globalThis.fetch:
inline mpl_value __require__(mpl_value v) { return __mpl_require__(mpl_to_string(v)); }
inline std::string __dirname__() { return "/"; }
inline std::string __filename__() { return __import_meta__.url; }
inline std::string __mpl_require_resolve__(const std::string& s) { return s; }
inline mpl_value __mpl_require_cache__() { return mpl_box_value(std::make_shared<mpl_object>()); }
inline mpl_value __mpl_require_main__() { return mpl_box_value(std::make_shared<mpl_object>()); }
inline std::shared_ptr<mpl_object>& __mpl_fetch_module() {
// Registers a callable "fetch" property that delegates to
// __mpl_http_fetch_value(url, init).
}
js_semantics.h (Section 3, 29,454 lines)
The bulk of JS language semantics. Sections (per awk extracted headers):
| Section | Approximate start | Topic |
|---|---|---|
| Forward shims | line 81 | Forward declarations |
| SECTION 3 | line 87 | JS language adapter (this, decorators, generators, async/await, JS Globals) |
| SECTION 4 stub | line 47 | Now in js_node.h |
Object lazy singleton |
line 7406 | __mpl_Object() |
Array lazy singleton |
line 7739 | __mpl_Array() |
| Free-standing globals | line 7791 | __mpl_globalThis, __mpl_undefined__, etc. |
| Iterator helpers (ES2025) | line 10785 | .map, .filter, .take, .drop, .toArray, … |
| WHATWG Streams | line 12395 | WritableStream, QueuingStrategy |
| Web Crypto | line 20027 | CryptoKey, SubtleCrypto |
| Post-Quantum Crypto | line 20806 | ML-KEM / ML-DSA / SLH-DSA (FIPS 203/204/205) |
| CommonJS compat | line 5218 | module, require, exports |
| Wrapper lambdas | line 8181 | auto lambdas usable as variables in generated code |
The PQC implementation (line 20806) uses OpenSSL 3.5+ for ML-KEM, ML-DSA, SLH-DSA, plus classical X448 / Ed448 (line 20902). Parameter tables for FIPS 203/204/205 are at line 20851.
js_es2026.h (Section 6, 2,287 lines)
ES2025 / ES2026 / Node v26 / TS 6.0 extras. Forward declarations for functions used before their definitions (lines 17-43), then the implementation:
| Subsystem | Lines |
|---|---|
Math.sumPrecise (ES2025) |
75 |
Error.isError (ES2025) |
92 |
Promise.try (ES2025) |
113 |
Array.fromAsync (ES2023) |
127 |
Uint8Array base64 / hex |
151 |
Temporal API (PlainDate, PlainTime, …) |
188 |
path.matchesGlob (Node v22+) |
679 |
node:sqlite (Node v22+) |
702 |
__exportStar forwarder |
927 |
Object.hasOwnProperty (ES2025) |
936 |
RegExp.escape (ES2025) |
944 |
Intl namespace (ECMA-402, ICU) |
960 |
structuredClone |
2105 |
| JSON.parse reviver source-text access | 2140 |
Main registration: __mpl_register_es2026_extras() |
2152 |
IR examples
Example 1 — async function with destructuring
import { readFile } from 'node:fs/promises';
export async function readFirst(path, { encoding = 'utf8' } = {}) {
const buf = await readFile(path, encoding);
return buf.length;
}
Generates:
ModuleNode {
name: "example.js"
metadata: { }
imports: []
statements: [
VariableDeclNode(name="readFile", isConst=true,
decl_origin="import",
initializer=FunctionCallNode("__import__", ["node:fs/promises", "readFile"])),
ExportNode(
value=FunctionDeclNode(name="readFirst",
metadata={ async="true", param_src="path, { encoding = 'utf8' } = {}",
originalText=... },
parameters=[("auto","path"), ("auto","{ encoding = 'utf8' } = {}")]),
isDefault=false,
bindings=[("readFirst","readFirst")])
]
}
Example 2 — class with private fields and decorators
@logged
class Counter {
#count = 0;
static instances = 0;
@trace
inc() { this.#count++; return this; }
static create() { return new Counter(); }
}
The class body is lowered to:
ClassDeclNode(name="Counter", metadata={ has_decorator="true" })
decorators: [ IdentifierNode("logged") ]
members:
VariableDeclNode(name="#count", class_field="true", initializer=LiteralNode(0))
VariableDeclNode(name="instances", class_field="true", static="true", initializer=LiteralNode(0))
methods:
FunctionDeclNode(name="inc", metadata={ decorator="trace", private="false" }, body=...)
FunctionDeclNode(name="create", metadata={ static="true" }, body=...)
Example 3 — using declaration
using logFile = openFile("/tmp/log.txt");
await using db = await connect("postgres://…");
tryParseVarDecl (JSFrontend.h:486-506) recognises using and emits:
VariableDeclNode(name="logFile",
metadata={ using_decl="true" })
VariableDeclNode(name="db",
metadata={ using_decl="true", using_await="true" })
Module resolution (Node.js algorithm)
MPL_Compiler.cpp (the CLI driver, not the frontend itself) implements
the Node.js module resolution algorithm:
- ESM resolution: for
import "x", walks up directories looking fornode_modules/x/package.jsonand reads itsexportsfield. Supports conditional exports ("import","require","node","default"). - CJS resolution: similar but using
"main"and"exports"with"require"condition. package.json#imports: self-references via#name.node_modules/undicistub:JSFrontend.h:46-87detects undici sources and replacesmodule.exports = class { … }with a stub function so that complex bundled HTTP machinery doesn't break parsing.
The WebSocket lowering
JSFrontend.h:254-369 adds a special-case IR rewriter for WebSocket:
| Source | Rewritten to |
|---|---|
new WebSocket(url) |
FunctionCallNode("mpl_websocket_create", [url]) |
new WebSocket(url, protocols) |
FunctionCallNode("mpl_websocket_create", [url]) (protocols ignored) |
ws.send(data) |
FunctionCallNode("mpl_websocket_send", [ws, data]) |
ws.close() |
FunctionCallNode("mpl_websocket_close", [ws]) |
This bypasses StandardLibraryCallNode because the constructor path
cannot be lowered through that node's expression-only interface.
NPM package support
MPL_Compiler.cpp:582-636 (copyRuntimeHeadersToOutDir) copies every
frontends/javascript/runtime/*.h into the build output. The CLI can
therefore compile any real npm package that doesn't use platform-specific
Node.js APIs (or whose Node.js APIs have stubs in js_node.h).
Tested workloads include complex packages that depend on fs/promises,
node:url, node:stream, node:path, Buffer, process, util,
events, etc.
Strict-mode violations (WILL_FAIL tests)
The following tests are expected to fail (i.e. the compiler correctly rejects the input):
| Test | Reason |
|---|---|
js_strict_mode_violation_impl_test |
Octal escape or duplicate binding in strict mode |
js_module_duplicate_lexical_impl_test |
Same lexical binding twice in ESM |
js_module_lexical_var_conflict_impl_test |
Lexical/var conflict in ESM |
js_module_duplicate_params_impl_test |
Duplicate params in ESM |
js_module_implicit_strict_violation_impl_test |
with or octal escape in implicit-strict ESM |
js_strict_binding_violation_impl_test |
eval / arguments reassignment in strict |
js_strict_reserved_binding_violation_impl_test |
Reserved-word binding in strict |
js_strict_duplicate_params_impl_test |
Duplicate params in strict function |
js_strict_arrow_duplicate_params_impl_test |
Duplicate params in strict arrow |
js_class_method_strict_binding_impl_test |
Strict-binding violation inside class method |
js_throw_newline_violation_impl_test |
throw\n (no semicolon — illegal in strict) |
js_with_module_class_nonfunc_impl_test |
with in module class non-function |
js_bare_specifier_exports_null_block_impl_test |
Bare specifier with null block |
js_package_imports_null_block_impl_test |
#imports block with null |
Architecture compliance
JSGlobals.h:1-12 is the canonical statement of JS-frontend architectural
compliance:
Architecture compliance: ALL JS-specific mappings live in
frontends/, NOT inbackends/. The frontend encodes runtime symbols as IR node metadata (metadata["js_runtime_sym"],metadata["js_runtime_ctor"],metadata["js_runtime_call"]). The backend reads only metadata, never queries frontend maps.
This is the contract that lets the C++ backend stay language-agnostic.