Elvelt

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, with statement 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-789InterfaceDeclNode
type X = … JSFrontend.h:792-800TypeAliasDeclNode
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(), consolempl_get_value(__mpl_globalThis(), "console"), __dirnamempl_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:

  1. Data descriptors (value + writable + enumerable + configurable)
  2. mpl_object_value_slots() — actual mpl_value storage
  3. mpl_callable_slots() — callable methods
  4. mpl_getter_slots() — accessor-only re-exports (critical for TS CJS shims)
  5. 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:

  1. ESM resolution: for import "x", walks up directories looking for node_modules/x/package.json and reads its exports field. Supports conditional exports ("import", "require", "node", "default").
  2. CJS resolution: similar but using "main" and "exports" with "require" condition.
  3. package.json#imports: self-references via #name.
  4. node_modules/undici stub: JSFrontend.h:46-87 detects undici sources and replaces module.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 in backends/. 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.