// ABI entry point and the stripping machine. See docs/architecture.md. // // Shape: one forward pass over UTF-8 bytes. A frame stack in linear memory // replaces recursion so the parse can stop at any token boundary when the // input runs out and resume on the next push. Each step is "scan first, // then commit": lookahead (including the type recognizer) is read-only, so a // step that runs out of input is abandoned with nothing to undo. A committed // step consumes one token at its end, or only tokens the lookahead saw, so // the lexer failing on the token after them leaves every frame consistent. import { Memory } from 'zena:memory'; import { tokenIn, setInput, scan, lexReset, seek, reseek, clearMore, more, pos, tb, ts, te, tt, tk, tnl, inEnd, rescanRegex, rescanTemplate, rescanGreater, rescanLess, lexChild, rescanJsxName, rescanJsxString, isBinop, isAssign, precOf, T_JSX_TEXT, T_JSX_CLOSE, T_EOF, T_NAME, T_NUM, T_STR, T_TEMPLATE, T_TEMPLATE_HEAD, T_TEMPLATE_TAIL, T_REGEX, T_PRIVATE, T_LBRACE, T_RBRACE, T_LPAREN, T_RPAREN, T_LBRACK, T_RBRACK, T_DOT, T_ELLIPSIS, T_SEMI, T_COMMA, T_COLON, T_QUESTION, T_QDOT, T_ARROW, T_AT, T_BANG, T_TILDE, T_INC, T_DEC, T_QQ, T_BARBAR, T_AMPAMP, T_LT, T_GT, T_SHL, T_PLUS, T_MINUS, T_STAR, T_SLASH, T_SLASH_EQ, T_STARSTAR, T_EQ, } from './lex.zena'; import { scanType, scanTypeArgs, scanTypeParams, scanTypeList, scanObjectType, scanReturnType, typeEnd } from './types.zena'; import { K_BREAK, K_CASE, K_CATCH, K_CLASS, K_CONST, K_CONTINUE, K_DEBUGGER, K_DEFAULT, K_DELETE, K_DO, K_ELSE, K_ENUM, K_EXPORT, K_EXTENDS, K_FINALLY, K_FOR, K_FUNCTION, K_IF, K_IMPORT, K_IN, K_INSTANCEOF, K_NEW, K_RETURN, K_SWITCH, K_THIS, K_THROW, K_TRY, K_TYPEOF, K_VAR, K_VOID, K_WHILE, K_WITH, K_LET, K_STATIC, K_YIELD, K_AWAIT, K_ASYNC, K_OF, K_GET, K_SET, K_AS, K_SATISFIES, K_TYPE, K_INTERFACE, K_DECLARE, K_NAMESPACE, K_MODULE, K_ABSTRACT, K_READONLY, K_PUBLIC, K_PRIVATE, K_PROTECTED, K_OVERRIDE, K_ACCESSOR, K_IMPLEMENTS, K_GLOBAL, K_FROM, K_USING, K_CONSTRUCTOR, K_REQUIRE, K_ASSERT, K_DEFER, K_FIRST_CONTEXTUAL, KS_HEAD, KS_STMT_MOD, KS_MEMBER_MOD, KS_REMOVED, keywordIn, DATA_END, KS_EXPR_START, KS_DECL, TS_EXPR_START, TS_PROPERTY_NAME, TS_CAN_FOLLOW_MOD, TS_LITERAL, TS_PREFIX_OP, TS_CLOSE_OR_SEP, TS_OPERAND_END, TS_OPEN, TS_CLOSE, TS_BINDING_START, TS_PARAM_START, TS_OPTIONAL_FOLLOW, KS_BINARY_WORD, KS_PAREN_HEAD, KS_PARAM_PROP, } from './keywords.zena'; // engine/hooks.zena is a build-time copy of engine/hooks-erase.zena or engine/hooks-downlevel.zena // (scripts/build.sh), so the erase build never imports engine/downlevel.zena at all. import { init as dlInit, enumStart as dlEnumStart, enumStep as dlEnumStep, namespaceStart as dlNamespaceStart, namespaceEnd as dlNamespaceEnd, exportModifier as dlExportModifier, dropPending as dlDropPending, hookSp as dlHookSp, blockHook as dlBlockHook, functionHead as dlFunctionHead, className as dlClassName, varFlags as dlVarFlags, declNames as dlDeclNames, importEquals as dlImportEquals, exportAssign as dlExportAssign, classOpen as dlClassOpen, classEnd as dlClassEnd, paramProp as dlParamProp, ctorBody as dlCtorBody, castErase as dlCastErase, ambientVar as dlAmbientVar, moduleEsmSeen as dlModuleEsmSeen, moduleCjsSeen as dlModuleCjsSeen, jsxOn as dlJsxOn, jsxPragma as dlJsxPragma, jsxPragmas as dlJsxPragmas, jsxOpen as dlJsxOpen, jsxQuoted as dlJsxQuoted, jsxNs as dlJsxNs, jsxPut as dlJsxPut, jsxText as dlJsxText, jsxString as dlJsxString, } from './hooks.zena'; // --------------------------------------------------------------------------- // Regions: STACK | ANCH | ERR | OUT | IN, contiguous and individually growable. var stackCap: i32 = 0; var anchBase: i32 = 0; var anchCap: i32 = 0; var errBase: i32 = 0; var errCap: i32 = 0; var outBase: i32 = 0; var outCap: i32 = 0; var inBase: i32 = 0; var inCap: i32 = 0; var fp: i32 = 0; // the top frame's address; the stack is empty at DATA_END - 16 var anchLen: i32 = 0; var errLen: i32 = 0; var outStart: i32 = 0; // stream offset of outBase var outLen: i32 = 0; // stream offset of output produced so far var flushed: i32 = 0; // stream offset the host has received var inStart: i32 = 0; // stream offset of inBase var lexFinal: i32 = 0; var outPtr: i32 = 0; var emitPos: i32 = 0; // input consumed into the output so far var blankDepth: i32 = 0; var semiPending: i32 = 0; // the next blanked byte becomes `;` var verbatim: i32 = 0; // > 0 inside constructs ts-blank-space leaves untouched var nest: i32 = 0; // bracket nesting, for statement-end detection var stmtNest: i32 = 0; var uqPos: i32 = 0; // UTF-16 unit cursor var uqUnits: i32 = 0; var done: i32 = 0; var failed: i32 = 0; var mode: i32 = 0; var jsx: i32 = 0; // init's jsx bit: TypeScript's .tsx grammar var childLex: i32 = 0; // the current token came from lexChild, so a retry must re-lex it that way var jsxPragmaScan: i32 = 0; // downlevel mode's JSX comment pragmas are read at the first token var started: i32 = 0; var lastValues: i32 = 0; var stmtUnits: i32 = 0; // UTF-16 position of the current statement's first token // ts-blank-space's semicolonNeeded: the last kept token was not `;`. Leading `;`s are only written when set, so // outputs that do not parse (kept legacy assertions) recover the same way as the oracle's. var semiNeeded: i32 = 0; function ensureMemory(end: i32): void { let have = Memory.default.size() << 16; if (have >= end) return; Memory.default.grow(((end - have) + 65535) >> 16); } // memory.copy (engine/rt.wat). @external("rt", "move") declare function memMove(dst: i32, src: i32, len: i32): void; // Blanks the leading run of the n input bytes at src that are ASCII and not line terminators to dst, 16 at a // time, and returns the run's length (engine/rt.wat). @external("rt", "blank16") declare function blank16(src: i32, dst: i32, n: i32): i32; // Moves len bytes from src to dst; the ranges may overlap. function moveBytes(dst: i32, src: i32, len: i32): void { if (dst == src || len <= 0) return; memMove(dst, src, len); } function layout(): void { anchBase = DATA_END + stackCap; errBase = anchBase + anchCap; outBase = errBase + errCap; inBase = outBase + outCap; ensureMemory(inBase + inCap + 16); } // Grows region `which` (0 stack, 1 anchors, 2 errors, 3 out) to hold `need` bytes, shifting the regions above it. function growRegion(which: i32, need: i32): void { var cap = stackCap; if (which == 1) cap = anchCap; if (which == 2) cap = errCap; if (which == 3) cap = outCap; var ncap = cap * 2; if (ncap < need) ncap = need; let delta = ncap - cap; var from = anchBase; if (which == 1) from = errBase; if (which == 2) from = outBase; if (which == 3) from = inBase; let liveEnd = inBase + (inEnd - inStart); ensureMemory(liveEnd + delta + 16); moveBytes(from + delta, from, liveEnd - from); if (which == 0) stackCap = ncap; if (which == 1) anchCap = ncap; if (which == 2) errCap = ncap; if (which == 3) outCap = ncap; layout(); setInput(inBase - inStart, inEnd, lexFinal); } // --------------------------------------------------------------------------- // Frames: 16 bytes each, [state, a, b, c]. function fr(): i32 { return fp; } // The number of frames on the stack. function sp(): i32 { return ((fp - DATA_END) >> 4) + 1; } function st(): i32 { return Memory.default.getI32(fr()); } export function fa(): i32 { return Memory.default.getI32(fr() + 4); } export function fb(): i32 { return Memory.default.getI32(fr() + 8); } export function fc(): i32 { return Memory.default.getI32(fr() + 12); } export function setSt(v: i32): void { Memory.default.setI32(fr(), v); } export function setA(v: i32): void { Memory.default.setI32(fr() + 4, v); } export function setB(v: i32): void { Memory.default.setI32(fr() + 8, v); } export function setC(v: i32): void { Memory.default.setI32(fr() + 12, v); } export function pushFrame(s: i32, a: i32, b: i32, c: i32): void { let need = fp + 32 - DATA_END; if (need > stackCap) growRegion(0, need); fp = fp + 16; let f = fp; Memory.default.setI32(f, s); Memory.default.setI32(f + 4, a); Memory.default.setI32(f + 8, b); Memory.default.setI32(f + 12, c); } export function pop(): void { fp = fp - 16; } export function belowSt(k: i32): i32 { return Memory.default.getI32(fp - k * 16); } export function belowA(k: i32): i32 { return Memory.default.getI32(fp - k * 16 + 4); } export function belowB(k: i32): i32 { return Memory.default.getI32(fp - k * 16 + 8); } export function setBelowA(k: i32, v: i32): void { Memory.default.setI32(fp - k * 16 + 4, v); } function setBelowB(k: i32, v: i32): void { Memory.default.setI32(fp - k * 16 + 8, v); } // --------------------------------------------------------------------------- // Output. function outAddr(o: i32): i32 { return outBase + (o - outStart); } export function ensureOut(n: i32): void { let need = (outLen - outStart) + n; if (need > outCap) growRegion(3, need); } // Copies input [a, b) to the output, or blanks it: one space per UTF-16 unit, line terminators kept. function emitRange(a: i32, b: i32, blank: boolean): void { if (b <= a) return; let len = b - a; ensureOut(len); let src = a + (inBase - inStart); var dst = outAddr(outLen); if (!blank) { moveBytes(dst, src, len); outLen = outLen + len; return; } var i = 0; while (i < len) { let c = Memory.default.getU8(src + i); // Only where a run starts and a whole chunk remains, so every call makes progress. if (semiPending == 0 && len - i >= 16 && c < 0x80 && c != 10 && c != 13) { let k = blank16(src + i, dst, len - i); i = i + k; dst = dst + k; continue; } if (c < 0x80) { if (c == 10 || c == 13) { Memory.default.setU8(dst, c); } else if (semiPending == 1) { Memory.default.setU8(dst, 59); semiPending = 0; } else { Memory.default.setU8(dst, 32); } dst = dst + 1; } else if (c >= 0xc0) { Memory.default.setU8(dst, 32); dst = dst + 1; if (c >= 0xf0) { Memory.default.setU8(dst, 32); dst = dst + 1; } semiPending = 0; } i = i + 1; } outLen = outStart + (dst - outBase); } export function emitTo(p: i32): void { if (p > emitPos) { emitRange(emitPos, p, blankDepth > 0); emitPos = p; } } // Blanks input [a, b) as an edit (copied instead when inside a verbatim construct). export function blankRange(a: i32, b: i32, semi: boolean): void { emitTo(a); if (b <= a) return; if (verbatim > 0 && blankDepth == 0) { emitRange(a, b, false); } else { if (semi && blankDepth == 0) semiPending = 1; emitRange(a, b, true); semiPending = 0; } emitPos = b; } // Re-blanks output [o, outLen) in place, compacting multi-byte sequences. Reads a whole lead byte's sequence // before writing it: writing 2 bytes for a 1-byte read (an astral lead) would otherwise let `w` overtake `r`, // so the next read would see bytes this same call already wrote. function reblankFrom(o: i32, semi: boolean): void { var r = outAddr(o); var w = r; let end = outAddr(outLen); var first = semi; while (r < end) { let c = Memory.default.getU8(r); if (c < 0x80) { if (c == 10 || c == 13) { Memory.default.setU8(w, c); } else { if (first) Memory.default.setU8(w, 59); else Memory.default.setU8(w, 32); first = false; } w = w + 1; r = r + 1; } else if (c >= 0xc0) { Memory.default.setU8(w, 32); w = w + 1; if (c >= 0xf0) { Memory.default.setU8(w, 32); w = w + 1; r = r + 4; } else if (c >= 0xe0) { r = r + 3; } else { r = r + 2; } first = false; } else { r = r + 1; } } outLen = outStart + (w - outBase); } // Replaces output from o with the untouched input [inPos, emitPos). export function restoreFrom(o: i32, inPos: i32): void { let len = emitPos - inPos; outLen = o; ensureOut(len); moveBytes(outAddr(o), inPos + (inBase - inStart), len); outLen = o + len; } function setOutByte(o: i32, c: i32): void { Memory.default.setU8(outAddr(o), c); } // Consumes the current token per the current mode and lexes the next. export function adv(): void { emitTo(te); if (blankDepth == 0) { semiNeeded = 1; if (tt == T_SEMI) semiNeeded = 0; } scan(); } // Consumes a kept token that ts-blank-space does not visit, so it leaves semiNeeded alone. export function advQuiet(): void { emitTo(te); scan(); } // Consumes the current token as blank. export function advBlank(): void { blankRange(ts, te, false); scan(); } // Blanks from the current token through input offset end, then continues after it. function blankThrough(end: i32, semi: boolean): void { blankRange(ts, end, semi); seek(end); scan(); } // Consumes input through end per the current mode. export function copyThrough(end: i32): void { emitTo(end); seek(end); scan(); } // --------------------------------------------------------------------------- // Anchors: output positions a later step may rewrite. Each is [out, in, errLen, flags]. let ANCH_IN: i32 = 1; // the input must be retained from `in` let ANCH_SEMI: i32 = 2; // a `;` may be written at `out` let ANCH_NEEDED: i32 = 4; // semiNeeded at the anchor, restored when the anchored output is blanked export function anchorPush(flags: i32): void { anchorPushAt(emitPos, flags); } // An anchor at input offset start, which may be before emitPos when the statement's first tokens are already out. export function anchorPushAt(start: i32, flags: i32): void { if ((anchLen + 1) * 16 > anchCap) growRegion(1, (anchLen + 1) * 16); var o = outLen; var s = start; if (start >= emitPos) { emitTo(start); o = outLen; } else if (blankDepth == 0) { // Everything since start was copied one to one. o = outLen - (emitPos - start); } else { s = emitPos; } let p = anchBase + anchLen * 16; Memory.default.setI32(p, o); Memory.default.setI32(p + 4, s); Memory.default.setI32(p + 8, errLen); var f = flags; if (blankDepth > 0 || semiNeeded == 0) f = f & -3; if (semiNeeded == 1) f = f | ANCH_NEEDED; Memory.default.setI32(p + 12, f); anchLen = anchLen + 1; } export function anchorOut(): i32 { return Memory.default.getI32(anchBase + (anchLen - 1) * 16); } export function anchorIn(): i32 { return Memory.default.getI32(anchBase + (anchLen - 1) * 16 + 4); } export function anchorErr(): i32 { return Memory.default.getI32(anchBase + (anchLen - 1) * 16 + 8); } function anchorFlags(): i32 { return Memory.default.getI32(anchBase + (anchLen - 1) * 16 + 12); } export function anchorPop(): void { anchLen = anchLen - 1; } // Blanks everything emitted since the innermost anchor, with a leading `;` where one is allowed. export function anchorBlank(): void { if (verbatim == 0) { reblankFrom(anchorOut(), (anchorFlags() & ANCH_SEMI) != 0); semiNeeded = (anchorFlags() & ANCH_NEEDED) >> 2; } anchorPop(); } function watermark(): i32 { if (anchLen == 0) return outLen; return Memory.default.getI32(anchBase); } function inKeep(): i32 { // An abandoned step may have emitted trivia past the lexer's resume position. var keep = emitPos; if (pos < keep) keep = pos; var i = 0; while (i < anchLen) { let p = anchBase + i * 16; if ((Memory.default.getI32(p + 12) & ANCH_IN) != 0) { let v = Memory.default.getI32(p + 4); if (v < keep) return v; return keep; } i = i + 1; } return keep; } // --------------------------------------------------------------------------- // Errors, in UTF-16 units. function unitsAt(p: i32): i32 { if (p <= uqPos) return uqUnits; let d = inBase - inStart; var q = uqPos; var u = uqUnits; while (q + 4 <= p) { let w = Memory.default.getI32(q + d); if ((w & 0x80808080) == 0) { u = u + 4; q = q + 4; } else { break; } } while (q < p) { let c = Memory.default.getU8(q + d); if (c < 0x80 || c >= 0xc0) { u = u + 1; if (c >= 0xf0) u = u + 1; } q = q + 1; } uqPos = q; uqUnits = u; return u; } // Per-error-entry tag: 1 when that entry is a namespace/module fallback's own collapsed error (see // errorNamespaceFallback below), 0 for an ordinary one. Indexed the same as the errBase region, grown // in lockstep with it (errorUnits below clears each slot it writes, so a stale 1 from an earlier // stream reusing the same slot after init() resets errLen to 0 can never leak into a // later run) — a plain side array rather than a stolen bit in ua/ub, so a caller that reads // errorsPtr()'s raw positions never sees a tag bit baked into a position. var fallbackFlag: FixedArray = new FixedArray(64, 0); function growFlag(need: i32): void { if (need <= fallbackFlag.length) return; var cap = fallbackFlag.length * 2; while (cap < need) cap = cap * 2; let next = new FixedArray(cap, 0); var i = 0; while (i < fallbackFlag.length) { next[i] = fallbackFlag[i]; i = i + 1; } fallbackFlag = next; } function errorUnits(ua: i32, ub: i32): void { if ((errLen + 1) * 8 > errCap) growRegion(2, (errLen + 1) * 8); let p = errBase + errLen * 8; Memory.default.setI32(p, ua); Memory.default.setI32(p + 4, ub); growFlag(errLen + 1); fallbackFlag[errLen] = 0; errLen = errLen + 1; } // Like errorUnits, but tags the pushed entry as a namespace/module statement's own "I can't resolve // this, fall back to reporting one error at my own start and leaving my input untouched" error (the // kind kind==0/values>0 below pushes, and downlevel mode's hNamespace/dlNamespaceEnd equivalent for a plain // instantiated namespace also pushes once it can't fast-path a nested statement). This is the error // whose own *position* is architecturally unreliable once nested: each enclosing level that also // falls back to this same resolution collapses whatever its children already collapsed to, discarding // them, and reports its own single error at its own start instead — so a nested occurrence's reported // position would never match what an enclosing level that itself resolves cleanly (downlevel mode's own // fast path) would have reported for that same nesting. Tagging it lets a down-leveled namespace that *did* // fast-path itself selectively drop just these from its own body's errors (pruneNamespaceFallbacks), // instead of the old setErrLen(anchorErr()) reset, which dropped every error raised in the body, // including ones with no such positional ambiguity (an ordinary unhandled construct's own error, e.g. // a legacy cast whose own erasure guard declined to erase it, reported at exactly the same position // regardless of namespace nesting). export function errorNamespaceFallback(ua: i32, ub: i32): void { errorUnits(ua, ub); fallbackFlag[errLen - 1] = 1; } // Removes just the namespace/module fallback-collapse errors (errorNamespaceFallback) at or after // index `from`, keeping every other error already queued (in order) and compacting the error list in // place. Unlike setErrLen(from), this doesn't discard errors that happen to have been raised while an // outer instantiated namespace's body was still open but aren't about that collapse at all. export function pruneNamespaceFallbacks(from: i32): void { var dst = from; var i = from; while (i < errLen) { if (fallbackFlag[i] == 0) { if (dst != i) { let srcP = errBase + i * 8; let dstP = errBase + dst * 8; Memory.default.setI32(dstP, Memory.default.getI32(srcP)); Memory.default.setI32(dstP + 4, Memory.default.getI32(srcP + 4)); fallbackFlag[dst] = 0; } dst = dst + 1; } i = i + 1; } errLen = dst; } // An error on the current token. function error(a: i32, b: i32): void { if (blankDepth > 0 || verbatim > 0) return; let ua = unitsAt(a); let ub = unitsAt(b); errorUnits(ua, ub); } // An error at the current statement's start. export function errorAtStmt(): void { if (blankDepth > 0 || verbatim > 0) return; errorUnits(stmtUnits, stmtUnits + 1); } // The current statement's own start, in UTF-16 units — for downlevel mode's own namespace declines // (engine/downlevel.zena's declineModuleKeyword/namespaceEnd) to report an error the same way `errorAtStmt` // does for erase mode's own (never fast-pathed) namespace handling. export function getStmtUnits(): i32 { return stmtUnits; } var retryAt: i32 = 0; var failSite: i32 = 0; var lastFlushed: i32 = 0; export function failAt(site: i32): void { failSite = site; failed = 1; done = 1; } // --------------------------------------------------------------------------- // States. let S_TOP: i32 = 1; let S_BLOCK: i32 = 2; let S_STMT: i32 = 3; let S_EXPRSTMT: i32 = 4; let S_VARDECL: i32 = 5; let S_PARENSTMT: i32 = 6; let S_SWITCHBODY: i32 = 7; let S_DO: i32 = 8; let S_FOR: i32 = 9; let S_TRY: i32 = 10; let S_RETURN: i32 = 11; let S_SEQ: i32 = 12; let S_CLOSE: i32 = 13; let S_EXPR: i32 = 14; let S_POSTFIX: i32 = 15; let S_BINARY: i32 = 16; let S_PAREN: i32 = 17; let S_ARGS: i32 = 18; let S_ARRAY: i32 = 19; let S_OBJECT: i32 = 20; let S_TEMPLATE: i32 = 21; let S_COND: i32 = 22; let S_ARROW: i32 = 23; let S_FUNC: i32 = 24; let S_CLASS: i32 = 25; let S_CLASSBODY: i32 = 26; let S_MEMBER: i32 = 27; let S_ENDBLANK: i32 = 28; let S_ENDVERB: i32 = 29; let S_NAMESPACE: i32 = 30; let S_DECORATORS: i32 = 31; let S_STMTEND: i32 = 32; let S_IMPORTBODY: i32 = 33; let S_JSX: i32 = 34; // Expression flags (frame field a). let F_NOIN: i32 = 1; let F_COVER: i32 = 2; let F_WHENTRUE: i32 = 4; let F_NEW: i32 = 8; let F_HERITAGE: i32 = 16; let F_DECORATOR: i32 = 32; let F_HYEXP: i32 = 64; // S_VARDECL: exported from a namespace in downlevel mode (engine/downlevel.zena varFlags) // S_EXPR: the current unary slot is a restricted production's own expression (return/throw/yield), where a // downlevel-mode legacy cast directly at this position must not be erased if doing so would expose a line // terminator between the keyword and the cast's operand (genericArrow below). let F_RESTRICT: i32 = 128; // S_EXPR: the current unary slot is an arrow's concise body. Erasing a cast there must not expose a bare // `{` as the body's own first character: ConciseBody's grammar excludes a body starting with `{` (it would // parse as a block, not an object literal), same ambiguity `() => ({...})` exists to route around. let F_ARROWBODY: i32 = 256; // S_EXPR: the current unary slot is an expression statement's own first token. Erasing a cast there must // not expose a bare `{`/`function`/`class` as the statement's own first character: each would parse as a // block/function/class declaration instead of the expression it actually is. let F_STMTSTART: i32 = 512; // Paren flags. let P_ASYNC: i32 = 1; let P_ARROW: i32 = 2; let P_PARAMS: i32 = 4; let P_WHENTRUE: i32 = 8; let P_TYPED: i32 = 16; let P_HASELEM: i32 = 32; let P_CTOR: i32 = 64; let P_PROP: i32 = 128; // Member flags (frame field a). let M_DECORATED: i32 = 1; let M_NOANCHOR: i32 = 2; let M_BLANKED: i32 = 4; let M_SEMINAME: i32 = 8; let M_CTOR: i32 = 16; // Function flags. let FN_ANCHOR: i32 = 1; let FN_CTOR: i32 = 2; // Block kinds. let B_BLOCK: i32 = 1; let B_NAMESPACE: i32 = 2; // JSX frame flags (a), phases (b), and engine/jsx.zena's jsxPut pieces; see "JSX" below. let J_EXPR: i32 = 1; // an expression of its own, so the token after it is lexed normally; else a child let J_XF: i32 = 2; // written as `factory(tag, props, ...children)` let J_PROPS: i32 = 4; // the props object is open let JP_OPEN: i32 = 0; // at `<` let JP_NAME: i32 = 1; // at the tag name let JP_ATTRS: i32 = 2; // at an attribute, `>` or `/` let JP_ATTRVAL: i32 = 3; // after an attribute container's expression, at `}` let JP_KIDS: i32 = 4; // at a child token let JP_KIDVAL: i32 = 5; // after a child container's expression, at `}` let JP_END: i32 = 6; // a written element's next token // engine/jsx.zena's jsxPut pieces. let JX_COMMA: i32 = 0; // `,` let JX_PROPS: i32 = 1; // `,{` let JX_RBRACE: i32 = 2; // `}` let JX_NULL: i32 = 3; // `,null` let JX_RPAREN: i32 = 4; // `)` let JX_COLON: i32 = 5; // `:` let JX_TRUE: i32 = 6; // `:true` let JX_ARG: i32 = 7; // `,(` let JX_LPAREN: i32 = 8; // `(` let JX_SEMI: i32 = 9; // `;` // A statement frame: a = start (refreshed from the first token when c == 1), b = nest at start. function pushStmt(): void { pushFrame(S_STMT, ts, nest, 1); } // An expression frame whose start position is filled in by hExpr from its first token. export function pushExpr(flags: i32, prec: i32): void { pushFrame(S_EXPR, flags, -1, prec); } function kw(k: i32): boolean { return tt == T_NAME && tk == k; } function isReserved(): boolean { return tt == T_NAME && tk != 0 && tk < K_FIRST_CONTEXTUAL; } // TypeScript's isStartOfExpression. function isStartOfExpression(): boolean { if (tt == T_NAME) { if (tk == 0 || tk >= K_FIRST_CONTEXTUAL) return true; return keywordIn(tk, KS_EXPR_START); } return tokenIn(TS_EXPR_START) || tt == T_SLASH_EQ; } function isBinaryOperatorToken(): boolean { if (isBinop(tt)) return true; return tt == T_NAME && keywordIn(tk, KS_BINARY_WORD); } function isPropertyNameStart(): boolean { return tokenIn(TS_PROPERTY_NAME); } function canFollowModifier(): boolean { return tt == T_LBRACK || tt == T_LBRACE || tt == T_STAR || tt == T_ELLIPSIS || isPropertyNameStart(); } function isModifierKw(k: i32): boolean { return keywordIn(k, KS_MEMBER_MOD); } function isRemovedModifier(k: i32): boolean { return keywordIn(k, KS_REMOVED); } // --------------------------------------------------------------------------- // Statement heads: TypeScript's isDeclaration() (unless the statement is already committed to a declaration), // then parseModifiers(), as one lookahead from the statement's first token. hdMods modifier tokens precede the // declaration token hdKind (a K_* id, HD_AT for decorators after modifiers, HD_NONE for anything else). var hdMods: i32 = 0; var hdKind: i32 = 0; var hdFlags: i32 = 0; var hdFirst: i32 = 0; // hdFlags at the statement's first modifier, for handlers reached after them let HD_NONE: i32 = 0; let HD_AT: i32 = 1000; let HD_DECLARE: i32 = 1; let HD_EXPORT: i32 = 2; function isIdentTok(): boolean { return tt == T_NAME && !isReserved(); } // The statement-start tokens TypeScript's parseStatement() sends through isStartOfDeclaration(). function isHeadKw(k: i32): boolean { return keywordIn(k, KS_HEAD); } function isStmtModifier(k: i32): boolean { return keywordIn(k, KS_STMT_MOD); } // TypeScript's canFollowModifier: `[`, `{`, `*`, `...`, or a literal property name. function canFollowMod(): boolean { return tokenIn(TS_CAN_FOLLOW_MOD); } function canFollowExport(): boolean { return tt == T_AT || (tt != T_STAR && !kw(K_AS) && tt != T_LBRACE && canFollowMod()); } // TypeScript's nextTokenCanFollowDefaultKeyword, from the token after `default`. function followsDefault(): i32 { if (kw(K_CLASS) || kw(K_FUNCTION) || kw(K_INTERFACE) || tt == T_AT) return 1; if (kw(K_ABSTRACT) || kw(K_ASYNC)) { var want = K_CLASS; if (kw(K_ASYNC)) want = K_FUNCTION; scan(); if (more == 1) return -1; if (kw(want) && tnl == 0) return 1; } return 0; } // `using` (current token) starts a declaration: a binding name or `{` follows on the same line. function usingAhead(): i32 { scan(); if (more == 1) return -1; if (tnl == 0 && (isIdentTok() || tt == T_LBRACE)) return 1; return 0; } // TypeScript's isDeclaration() loop: 1 when the statement is a declaration, 0 an expression, -1 out of input. function isDeclAhead(): i32 { while (true) { if (tt != T_NAME) return 0; let k = tk; if (keywordIn(k, KS_DECL)) return 1; if (k == K_USING) return usingAhead(); if (!isHeadKw(k)) return 0; scan(); if (more == 1) return -1; if (k == K_AWAIT) { if (!kw(K_USING)) return 0; return usingAhead(); } if (k == K_INTERFACE || k == K_TYPE) { if (tnl == 0 && isIdentTok()) return 1; return 0; } if (k == K_MODULE || k == K_NAMESPACE) { if (tnl == 0 && (isIdentTok() || tt == T_STR)) return 1; return 0; } if (k == K_GLOBAL) { if (tt == T_LBRACE || (tt == T_NAME && tk == 0) || kw(K_EXPORT)) return 1; return 0; } if (k == K_IMPORT) { if (tt == T_STR || tt == T_STAR || tt == T_LBRACE || tt == T_NAME) return 1; return 0; } if (k == K_EXPORT) { var t = tt; var tkk = tk; if (kw(K_TYPE)) { let m = tb; scan(); if (more == 1) return -1; t = tt; tkk = tk; reseek(m); } if (t == T_EQ || t == T_STAR || t == T_LBRACE || t == T_AT || (t == T_NAME && (tkk == K_DEFAULT || tkk == K_AS))) { return 1; } continue; } if (k == K_STATIC) continue; // abstract, accessor, async, declare, private, protected, public, readonly: ASI applies after them. if (tnl == 1) return 0; if (k == K_DECLARE && kw(K_TYPE)) return 1; } } // TypeScript's parseModifiers() at a statement, from the current token. Sets hdMods/hdKind/hdFlags; -1 when input // ran out. function modsAhead(): i32 { hdMods = 0; hdFlags = 0; while (tt == T_NAME && isStmtModifier(tk)) { let k = tk; let m = tb; scan(); if (more == 1) return -1; // Each check reads the token after the modifier, the next candidate; only the ones that read further // return to it. let next = tb; var ok = 0; if (k == K_CONST) { if (kw(K_ENUM)) ok = 1; } else if (k == K_DEFAULT || (k == K_EXPORT && kw(K_DEFAULT))) { if (k == K_EXPORT) { scan(); if (more == 1) return -1; } ok = followsDefault(); if (ok < 0) return -1; } else if (k == K_EXPORT) { if (kw(K_TYPE)) { scan(); if (more == 1) return -1; } if (canFollowExport()) ok = 1; } else if (canFollowMod() && (tnl == 0 || k == K_STATIC)) { ok = 1; } if (ok == 0) { reseek(m); break; } if (tb != next) reseek(next); hdMods = hdMods + 1; if (k == K_DECLARE) hdFlags = hdFlags | HD_DECLARE; if (k == K_EXPORT) hdFlags = hdFlags | HD_EXPORT; } hdKind = HD_NONE; if (tt == T_NAME) hdKind = tk; if (tt == T_AT && hdMods > 0) hdKind = HD_AT; return 1; } // 1 when the statement at the current token is a declaration (hd* describe it), 0 when it is an expression // statement, -1 when input ran out. The caller restores the token with back(). function lookHead(committed: boolean): i32 { if (!committed) { let m = tb; let r = isDeclAhead(); if (r != 1) return r; if (tb != m) reseek(m); } return modsAhead(); } // Lookahead helpers. Every lookahead starts with `let m = tb` and ends with back(m), which returns false when // the step must be abandoned for lack of input. export function back(m: i32): boolean { if (more == 1) { seek(m); return false; } reseek(m); return true; } // Scans the type that starts at the token after the current one; returns its end or -1. export function lookType(): i32 { scan(); if (more == 1) return -1; if (!scanType()) return -1; return typeEnd; } // Balanced skip from the current opening token through its matching close; returns the end offset or -1. export function skipBalanced(): i32 { let open = tt; var close = T_RPAREN; if (open == T_LBRACK) close = T_RBRACK; if (open == T_LBRACE) close = T_RBRACE; scan(); if (more == 1) return -1; while (true) { if (tt == T_EOF) return -1; if (tt == close) { let e = te; scan(); if (more == 1) return -1; return e; } if (tokenIn(TS_OPEN)) { if (skipBalanced() < 0) return -1; continue; } if (tt == T_TEMPLATE_HEAD) { if (skipTemplate() < 0) return -1; continue; } if (tokenIn(TS_CLOSE)) return -1; scan(); if (more == 1) return -1; } } export function skipTemplate(): i32 { while (true) { scan(); if (more == 1) return -1; while (tt != T_RBRACE) { if (tt == T_EOF) return -1; if (tokenIn(TS_OPEN)) { if (skipBalanced() < 0) return -1; continue; } if (tt == T_TEMPLATE_HEAD) { if (skipTemplate() < 0) return -1; continue; } scan(); if (more == 1) return -1; } rescanTemplate(); if (more == 1) return -1; if (tt == T_TEMPLATE_TAIL) { let e = te; scan(); if (more == 1) return -1; return e; } } } // Marks the enclosing namespace body as holding a value. Wrapper frames a statement morphed into are skipped. export function markValue(): void { var k = 1; while (sp() > k) { let s = belowSt(k); if (s == S_ENDBLANK || s == S_STMTEND || s == S_ENDVERB) { k = k + 1; continue; } if (s == S_BLOCK && belowA(k) == B_NAMESPACE) setBelowB(k, belowB(k) + 1); return; } } // Enters blank mode for the current statement (whose frame is on top), with a leading `;`. export function enterBlank(semi: boolean): void { if (verbatim > 0) { setSt(S_STMTEND); return; } if (blankDepth == 0) { emitTo(ts); blankDepth = 1; if (semi && verbatim == 0) semiPending = semiNeeded; setSt(S_ENDBLANK); } else { setSt(S_STMTEND); } } // Finishes a one-step statement, leaving blank mode if this statement entered it. export function endStmt(): void { if (st() == S_ENDBLANK) { blankDepth = blankDepth - 1; semiPending = 0; } pop(); } // --------------------------------------------------------------------------- // Statement lists. function hTop(): void { if (jsxPragmaScan == 1) { jsxPragmaScan = 0; dlJsxPragmas(ts); } if (tt == T_EOF) { emitTo(te); done = 1; return; } pushStmt(); } function hBlock(): void { if (sp() == dlHookSp() && dlBlockHook()) return; if (tt == T_RBRACE) { if (fa() == B_NAMESPACE) lastValues = fb(); nest = nest - 1; stmtNest = fc(); adv(); pop(); return; } if (tt == T_EOF) { failAt(1); return; } pushStmt(); } // Enters a `{` block body; the current token is `{`. The enclosing statement's nest is restored on close. export function openBlock(kind: i32): void { nest = nest + 1; adv(); pushFrame(S_BLOCK, kind, 0, stmtNest); } // --------------------------------------------------------------------------- // Statements. The frame morphs into the specific statement frame. c = 0, or 1 to refresh a from the first token, // 2 after statement-level decorators (their anchor is held), 3 a declaration committed to by a blanked prefix // (refresh a), 4 a declaration whose first modifiers are consumed, 5 after statement-level decorators whose anchor // stmtDecl has released. function hStmt(): void { let c = fc(); if (c == 1 || c == 3) { setA(ts); stmtUnits = unitsAt(ts); } stmtNest = fb(); if (tt == T_LBRACE) { markValue(); pop(); openBlock(B_BLOCK); return; } if (tt == T_SEMI) { markValue(); adv(); pop(); return; } if (tt == T_AT) { anchorPushAt(ts, ANCH_SEMI); setSt(S_DECORATORS); setA(1); setC(0); return; } if (tt == T_NAME) { let k = tk; if (k == K_IMPORT && c < 2) { hdFirst = 0; stmtImport(); return; } if (c >= 2 || isHeadKw(k)) { let m = tb; let r = lookHead(c >= 2); if (!back(m)) return; if (r == 1) { stmtDecl(); return; } } else if (stmtKeyword(k)) { return; } if (!isReserved() || k == K_YIELD || k == K_AWAIT) { let m = tb; scan(); let isLabel = more == 0 && tt == T_COLON; if (!back(m)) return; if (isLabel) { markValue(); setC(1); adv(); adv(); return; } } } markValue(); setSt(S_EXPRSTMT); setA(0); setC(0); } // A declaration (hd* from lookHead). Blanked statements blank from their first modifier or decorator; a kept one // consumes its modifiers here or in its own handler, which blanks the ones ts-blank-space removes. function stmtDecl(): void { let n = hdMods; let d = hdKind; let declare = (hdFlags & HD_DECLARE) != 0; let decorated = fc() == 2; if (fc() != 4) hdFirst = hdFlags; if (d == K_FUNCTION) { if (mode == 1 && kw(K_EXPORT)) dlExportModifier(); stmtFunction(fa(), decorated, n, declare); return; } let isNs = d == K_NAMESPACE || d == K_MODULE || d == K_GLOBAL; var ambientVarR = 0; if (mode == 1 && declare && !isNs && (d == K_VAR || d == K_LET || d == K_CONST)) ambientVarR = dlAmbientVar(n); if (ambientVarR != 0) return; let blankAll = d == K_INTERFACE || d == K_TYPE || (declare && d != K_IMPORT && d != K_EXPORT && !isNs); // The handlers below may run out of input and retry this step, so the frame records that the decorators' // anchor is gone; popping it again would release the enclosing construct's anchor. A namespace anchors at the // decorators' start instead, so it keeps their anchor until it commits: released here, a push in between // could flush the decorators it then blanks. if (decorated && !blankAll && !isNs) { anchorPop(); setC(5); } if (isNs) { stmtNamespace(n, declare); return; } if (blankAll && (n > 0 || decorated)) { if (decorated) { anchorBlank(); enterBlank(false); } else { enterBlank(true); } var i = 0; while (i < n) { adv(); i = i + 1; } pushFrame(S_STMT, ts, nest, 3); return; } if (d == K_IMPORT && n == 1 && kw(K_EXPORT)) { // An exported import-equals is handled from `export`, which downlevel mode rewrites with it. let m = tb; scan(); let isEquals = importKind() == 2; if (!back(m)) return; if (isEquals) { markValue(); importEquals(); return; } } if (n > 0 && kw(K_EXPORT)) { // `export` as a plain modifier (`export class`/`function`/`enum`/`var`/`let`/`const`): a // value-level ESM export declaration like every other one stmtExport's own exit points flag // (blankAll above already routed every ambient/interface/type export away from this branch). // moduleEsmSeen() itself (engine/downlevel.zena) excludes a namespace member export (`namespace N { // export function f() {} }`, reached from here too) — see its own comment for why. if (mode == 1 && blankDepth == 0 && verbatim == 0 && dlModuleEsmSeen()) errorAtStmt(); // `export` as a plain modifier: the statement continues with this frame, start kept at `export`. setC(4); if (mode == 1) { let r = dlExportModifier(); if (r == 2) return; if (r == 1) { advBlank(); return; } } advQuiet(); return; } if (d == K_CLASS) { // hClass consumes the modifiers. markValue(); setSt(S_CLASS); setC(0); return; } if (d == K_ENUM) { if (kw(K_CONST) || kw(K_ENUM)) { stmtEnum(); return; } setC(4); advQuiet(); return; } if (n > 0) { setC(4); var i = 0; while (i < n) { advQuiet(); i = i + 1; } return; } if (d == K_VAR || d == K_LET || d == K_CONST || d == K_USING || d == K_AWAIT) { if (!stmtVarLike(d)) failAt(14); return; } if (d == K_IMPORT) { stmtImport(); return; } if (d == K_EXPORT) { stmtExport(); return; } if (d == K_TYPE || d == K_INTERFACE) { var end = -1; if (d == K_TYPE) end = lookTypeAlias(); else end = lookInterface(); if (end == -1) return; blankThrough(end, semiNeeded == 1); endStmt(); return; } failAt(15); } // Statement keywords that are not declaration heads. Returns false when the statement is an expression. function stmtKeyword(k: i32): boolean { if (k == K_VAR || k == K_LET) return stmtVarLike(k); if (k == K_FUNCTION) { stmtFunction(fa(), false, 0, false); return true; } if (k == K_CLASS) { markValue(); setSt(S_CLASS); setC(0); return true; } if (keywordIn(k, KS_PAREN_HEAD)) { markValue(); var kind = 0; if (k == K_WHILE) kind = 1; if (k == K_WITH) kind = 2; if (k == K_SWITCH) kind = 3; setSt(S_PARENSTMT); setB(kind); setC(0); adv(); return true; } var s = 0; if (k == K_DO) s = S_DO; if (k == K_FOR) s = S_FOR; if (k == K_TRY) s = S_TRY; if (k == K_RETURN || k == K_THROW) s = S_RETURN; if (s != 0) { markValue(); setSt(s); setC(0); adv(); return true; } if (k == K_BREAK || k == K_CONTINUE || k == K_DEBUGGER) { markValue(); stmtSimple(k != K_DEBUGGER); return true; } return false; } // Expression statement: c = 0 before the expression, 1 after one. function hExprStmt(): void { if (fc() == 0) { // a = 1 (set by stmtExport for `export default`) reuses this same state machine for a position // that's never actually ambiguous; a = 0 is a genuine expression-statement start. let stmtStart = fa() == 0; setC(1); // Also set for a comma-continuation's own next operand (`a, {...}`): the sequence operator is // never actually ambiguous there, so this is conservative, not incorrect, and keeps this one call // site simple. var pf = 0; if (stmtStart) pf = F_STMTSTART; pushExpr(pf, 0); return; } if (tt == T_COMMA) { adv(); setC(0); return; } if (tt == T_SEMI) adv(); pop(); } // `var`/`let`/`const`/`using`/`await using` declarations. Returns false when `let` is an identifier here. function stmtVarLike(k: i32): boolean { if (k == K_LET) { let m = tb; scan(); let ok = more == 0 && (tokenIn(TS_BINDING_START)); if (!back(m)) return true; if (!ok) return false; } markValue(); setSt(S_VARDECL); setA(0); if (mode == 1) setA(dlVarFlags(k)); setC(0); if (k == K_AWAIT) adv(); adv(); return true; } // a = flags, c = 0 before a declarator, 1 after one. function hVarDecl(): void { if (fc() == 0) { var empty = tt != T_NAME && tt != T_LBRACE && tt != T_LBRACK; if (kw(K_OF) && (fa() & F_NOIN) != 0) { // `for (var of x)`: TypeScript's canFollowContextualOfKeyword. let m = tb; scan(); if (more == 0 && isIdentTok()) { scan(); empty = more == 0 && tt == T_RPAREN; } if (!back(m)) return; } if (empty) { // An empty declaration list (`var;`, `var a,;`), which TypeScript parses as a grammar error. setC(1); return; } if (fa() >= F_HYEXP && dlDeclNames(fa())) return; setC(1); pushExpr(F_COVER | (fa() & F_NOIN), 2); return; } if (tt == T_COMMA) { adv(); setC(0); return; } if (tt == T_SEMI && (fa() & F_NOIN) == 0) adv(); pop(); } // if / while / with / switch: b = kind, c = sub-state. function hParenStmt(): void { let s = fc(); let kind = fb(); if (s == 0) { if (tt != T_LPAREN) { failAt(2); return; } nest = nest + 1; setC(1); adv(); pushFrame(S_SEQ, 0, 0, 0); return; } if (s == 1) { if (tt != T_RPAREN) { failAt(3); return; } nest = nest - 1; setC(2); adv(); return; } if (s == 2) { if (kind == 3) { if (tt != T_LBRACE) { failAt(4); return; } nest = nest + 1; setSt(S_SWITCHBODY); setC(0); adv(); return; } setC(3); pushStmt(); return; } if (s == 3) { if (kind == 0 && kw(K_ELSE)) { setC(4); adv(); return; } pop(); return; } if (s == 4) { setC(5); pushStmt(); return; } pop(); } // c = 1 right after `case expr` / `default`, expecting `:`. function hSwitchBody(): void { if (fc() == 1) { setC(0); if (tt == T_COLON) adv(); return; } if (tt == T_RBRACE) { nest = nest - 1; adv(); pop(); return; } if (kw(K_CASE)) { setC(1); adv(); pushFrame(S_SEQ, 0, 0, 0); return; } if (kw(K_DEFAULT)) { setC(1); adv(); return; } if (tt == T_EOF) { failAt(5); return; } pushStmt(); } function hDo(): void { let s = fc(); if (s == 0) { setC(1); pushStmt(); return; } if (s == 1) { if (!kw(K_WHILE)) { failAt(6); return; } setC(2); adv(); return; } if (s == 2) { if (tt != T_LPAREN) { failAt(7); return; } nest = nest + 1; setC(3); adv(); pushFrame(S_SEQ, 0, 0, 0); return; } if (s == 3) { if (tt == T_RPAREN) { nest = nest - 1; setC(4); adv(); return; } failAt(8); return; } if (tt == T_SEMI) adv(); pop(); } function hFor(): void { let s = fc(); if (s == 0) { if (kw(K_AWAIT)) { adv(); return; } if (tt != T_LPAREN) { failAt(9); return; } nest = nest + 1; setC(1); adv(); return; } if (s == 1) { if (tt == T_SEMI) { setC(3); adv(); return; } if (kw(K_VAR) || kw(K_CONST) || kw(K_LET) || kw(K_USING) || kw(K_AWAIT)) { var isDecl = true; let aw = kw(K_AWAIT); if (!kw(K_VAR) && !kw(K_CONST)) { let isLet = kw(K_LET); let m = tb; scan(); if (isLet) { isDecl = more == 0 && (tokenIn(TS_BINDING_START)) && !kw(K_IN); } else { // TypeScript's for-head `using`/`await using`: a binding name or `{` on the same line, and a bare // `using of` only when `=`, `;` or `:` follows. if (aw) { isDecl = more == 0 && kw(K_USING); if (isDecl) scan(); } isDecl = isDecl && more == 0 && tnl == 0 && (isIdentTok() || tt == T_LBRACE); if (isDecl && !aw && kw(K_OF)) { scan(); isDecl = more == 0 && (tt == T_EQ || tt == T_SEMI || tt == T_COLON); } } if (!back(m)) return; } if (isDecl) { setC(2); pushFrame(S_VARDECL, F_NOIN, 0, 0); if (aw) adv(); adv(); return; } } setC(2); pushFrame(S_SEQ, F_NOIN, 0, 0); return; } if (s == 2) { if (kw(K_IN) || kw(K_OF)) { setC(6); adv(); pushFrame(S_SEQ, 0, 0, 0); return; } setC(3); if (tt == T_SEMI) adv(); return; } if (s == 3) { if (tt == T_SEMI) { setC(5); adv(); return; } setC(4); pushFrame(S_SEQ, 0, 0, 0); return; } if (s == 4) { setC(5); if (tt == T_SEMI) adv(); return; } if (s == 5) { if (tt == T_RPAREN) { nest = nest - 1; setC(7); adv(); return; } setC(6); pushFrame(S_SEQ, 0, 0, 0); return; } if (s == 6) { if (tt != T_RPAREN) { failAt(10); return; } nest = nest - 1; setC(7); adv(); return; } if (s == 7) { setC(8); pushStmt(); return; } pop(); } function hTry(): void { let s = fc(); if (s == 0) { if (tt != T_LBRACE) { failAt(11); return; } setC(1); openBlock(B_BLOCK); return; } if (s == 1) { if (kw(K_CATCH)) { setC(2); adv(); return; } if (kw(K_FINALLY)) { setC(6); adv(); return; } pop(); return; } if (s == 2) { if (tt == T_LPAREN) { nest = nest + 1; setC(3); adv(); return; } setC(5); return; } if (s == 3) { setC(4); pushExpr(F_COVER, 2); return; } if (s == 4) { if (tt != T_RPAREN) { failAt(12); return; } nest = nest - 1; setC(5); adv(); return; } if (s == 5) { if (tt != T_LBRACE) { failAt(13); return; } setC(1); openBlock(B_BLOCK); return; } if (s == 6) { if (tt != T_LBRACE) { failAt(14); return; } setC(7); openBlock(B_BLOCK); return; } pop(); } function hReturn(): void { if (fc() == 1) { if (tt == T_SEMI) adv(); pop(); return; } if (tt == T_SEMI) { adv(); pop(); return; } if (tnl == 1 || tt == T_RBRACE || tt == T_EOF) { pop(); return; } setC(1); // Threaded onto the pushed S_SEQ's own flags, which hSeq forwards unchanged into its S_EXPR: marks the // first unary slot of this return/throw's expression as restricted (see F_RESTRICT, genericArrow). pushFrame(S_SEQ, F_RESTRICT, 0, 0); } // break / continue / debugger, one step. function stmtSimple(label: boolean): void { let m = tb; scan(); var hasLabel = more == 0 && label && tt == T_NAME && tnl == 0; if (hasLabel) scan(); let hasSemi = more == 0 && tt == T_SEMI; if (!back(m)) return; adv(); if (hasLabel) adv(); if (hasSemi) adv(); pop(); } function hSeq(): void { if (fc() == 0) { setC(1); pushExpr(fa(), 0); return; } if (tt == T_COMMA) { setC(0); adv(); return; } pop(); } function hClose(): void { if (tt == fa()) { nest = nest - 1; adv(); } else { failAt(15); } pop(); } // Decorators, then the class they decorate. c = 2 right after `@`, 1 right after a decorator expression. function hDecorators(): void { let s = fc(); if (s == 2) { setC(1); pushExpr(F_DECORATOR, 0); return; } if (s == 1) setC(0); if (tt == T_AT) { setC(2); adv(); return; } if (fa() == 1) { // Decorators before a statement: it starts at the first `@`, and hStmt (c = 2) parses its modifiers, then // keeps the anchor for a function (an overload is blanked from there) or a blanked statement. setA(anchorIn()); setSt(S_STMT); setC(2); return; } // Decorators on a class expression: the expression frame below continues after this pops. if (kw(K_CLASS)) { setSt(S_CLASS); setC(0); return; } pop(); } // From `type`: the end of a type alias statement, -1 when input ran out. function lookTypeAlias(): i32 { let m = tb; scan(); var end = -1; if (more == 0) { scan(); if (more == 0 && tt == T_LT) scanTypeParams(); if (more == 0 && tt == T_EQ) { scan(); if (more == 0 && scanType()) { end = typeEnd; if (tt == T_SEMI) end = te; } } } if (!back(m)) return -1; if (end < 0) { failAt(16); return -1; } return end; } // From `interface`: the end of the declaration, -1 when input ran out. function lookInterface(): i32 { let m = tb; scan(); var end = -1; if (more == 0) { scan(); if (more == 0 && tt == T_LT) scanTypeParams(); while (more == 0 && (kw(K_EXTENDS) || kw(K_IMPLEMENTS))) { scan(); if (more == 0 && !scanTypeList()) break; } if (more == 0 && tt == T_LBRACE && scanObjectType()) { end = typeEnd; if (tt == T_SEMI) end = te; } } if (!back(m)) return -1; if (end < 0) { failAt(17); return -1; } return end; } function hEndBlank(): void { blankDepth = blankDepth - 1; semiPending = 0; pop(); } function hEndVerb(): void { verbatim = verbatim - 1; pop(); } function hStmtEnd(): void { pop(); } // `[const] enum E { ... }`: an error (unless already blank), and the body is left untouched. function stmtEnum(): void { if (mode == 1 && blankDepth == 0 && verbatim == 0) { dlEnumStart(); return; } let m = tb; if (kw(K_CONST)) scan(); if (more == 0) scan(); if (more == 0 && tt == T_NAME) scan(); var end = -1; if (more == 0 && tt == T_LBRACE) end = skipBalanced(); if (!back(m)) return; if (end < 0) { failAt(18); return; } markValue(); errorAtStmt(); copyThrough(end); if (blankDepth == 0) semiNeeded = 1; endStmt(); } // Pops the anchor of the decorators before a namespace (frame c = 2) once the namespace commits. export function releaseDecorators(): void { if (fc() == 2) anchorPop(); } // `namespace A.B { }`, `module A { }`, `module 'x' { }`, `module 'x';`, `global { }`, after `lead` modifier tokens. // The frame's a is the statement start. function stmtNamespace(lead: i32, declare: boolean): void { let start = fa(); let leadExport = kw(K_EXPORT); let m = tb; var i = 0; while (more == 0 && i < lead) { scan(); i = i + 1; } let isModule = more == 0 && kw(K_MODULE); let isGlobal = more == 0 && kw(K_GLOBAL); // Verify the header through `{` or `;`. var n = 1 + lead; if (more == 0) scan(); let isString = more == 0 && !isGlobal && tt == T_STR; if (more == 0 && !isGlobal) { scan(); n = n + 1; } while (more == 0 && !isString && !isGlobal && tt == T_DOT) { scan(); if (more == 0) scan(); n = n + 2; } let endsSemi = more == 0 && tt == T_SEMI; let endsBrace = more == 0 && tt == T_LBRACE; let endsShort = more == 0 && isString && !endsBrace && (tnl == 1 || tt == T_EOF || tt == T_RBRACE); if (!back(m)) return; if (!endsSemi && !endsBrace && !endsShort) { failAt(19); return; } if (mode == 1 && blankDepth == 0 && verbatim == 0 && endsBrace && !isString && !isGlobal && !declare && (lead == 0 || (lead == 1 && leadExport))) { // `isModule` (the `module` keyword, as opposed to `namespace`) is deliberately not excluded here: that // would cost erase mode a few bytes for a boolean it never uses (the condition already starts // `mode == 1 &&`, always false for strip, but the check itself still has to exist either way — `mode` // is a genuine runtime parameter to `init`, never provably constant, which is exactly why this // project gets downlevel.zena's bulk out of build/erase.wasm with the hooks-file swap instead of relying // on dead-code elimination to prove `mode == 1` unreachable). `namespaceStart` (engine/downlevel.zena) does // its own, equally cheap check of the very same leading token and declines a `module`-keyword // statement itself instead, matching erase mode's own unconditional "module N {} is always an error" // rule below (ts-blank-space's own rule, confirmed in its `visitModule`) without this condition ever // needing to know about it. dlNamespaceStart(start, lead); return; } releaseDecorators(); // Only a `namespace` is resolved by its body: to ts-blank-space every other module declaration is a value. if (isModule || isGlobal) markValue(); if (isModule && !isString) { // `module N {}` is always an error and left untouched, even with `declare`. errorAtStmt(); verbatim = verbatim + 1; setSt(S_ENDVERB); var j = 0; while (j < n) { adv(); j = j + 1; } pushFrame(S_NAMESPACE, start, 1, 0); openBlock(B_NAMESPACE); return; } if (blankDepth > 0 || isString || isGlobal || declare) { enterBlank(true); var j = 0; while (j < n) { adv(); j = j + 1; } if (endsSemi) adv(); if (endsSemi || endsShort) { endStmt(); return; } pushFrame(S_NAMESPACE, start, 2, 0); openBlock(B_NAMESPACE); return; } if (!endsBrace) { failAt(20); return; } anchorPushAt(start, ANCH_IN | ANCH_SEMI); setSt(S_NAMESPACE); setB(0); setC(stmtUnits); var j = 0; while (j < n) { adv(); j = j + 1; } openBlock(B_NAMESPACE); } // After the namespace body: a = statement start, b = 0 resolve / 1 verbatim / 2 blank, c = start in UTF-16 units. function hNamespace(): void { let values = lastValues; let kind = fb(); if (kind == 3) { dlNamespaceEnd(values); return; } if (kind == 0) { if (values > 0) { restoreFrom(anchorOut(), anchorIn()); errLen = anchorErr(); anchorPop(); markValue(); errorNamespaceFallback(fc(), fc() + 1); } else { errLen = anchorErr(); anchorBlank(); } } else if (values > 0) { markValue(); } pop(); } // --------------------------------------------------------------------------- // import / export. // The first token of an import clause, set by importKind(). var impClause: i32 = 0; // From `import`: TypeScript's split of parseImportDeclarationOrImportEqualsDeclaration: 0 an import declaration, 1 a // type-only one, 2 an import-equals, 3 not a declaration, -1 out of input. impClause is where the clause starts. function importKind(): i32 { scan(); if (more == 1) return -1; // TypeScript's isDeclaration for `import`: `import(`, `import.meta`, `import` are expressions (3). if (tt != T_STR && tt != T_STAR && tt != T_LBRACE && tt != T_NAME) return 3; impClause = tb; if (!isIdentTok()) return 0; let isType = kw(K_TYPE); let isDefer = kw(K_DEFER); scan(); if (more == 1) return -1; var phase = 0; if (isType) { var ok = !kw(K_FROM); if (!ok && isIdentTok()) { let m = tb; scan(); if (more == 1) return -1; ok = kw(K_FROM) || tt == T_EQ; reseek(m); } if (ok && (isIdentTok() || tt == T_STAR || tt == T_LBRACE)) phase = 1; } else if (isDefer) { if (kw(K_FROM)) { let m = tb; scan(); if (more == 1) return -1; if (tt != T_STR) phase = 2; reseek(m); } else if (tt != T_COMMA && tt != T_EQ) { phase = 2; } } var ident = true; if (phase != 0) { impClause = tb; ident = isIdentTok(); if (ident) { scan(); if (more == 1) return -1; } } if (ident && phase != 2 && tt != T_COMMA && !kw(K_FROM)) return 2; if (phase == 1) return 1; return 0; } // From a module specifier: the end of the declaration through its attributes and `;` (-1 when input ran out or it // is malformed). An export's attributes must start on the specifier's line. function specEnd(exp: boolean): i32 { var end = te; if (tt == T_STR) { scan(); } else { // A non-string specifier (a grammar error) is an expression; take it through the line. while (more == 0 && tt != T_SEMI && tt != T_EOF && tt != T_RBRACE && (tnl == 0 || end == te)) { if (tokenIn(TS_OPEN)) { let e = skipBalanced(); if (e < 0) return -1; end = e; } else { end = te; scan(); } } } if (more == 1) return -1; if ((kw(K_WITH) && (tnl == 0 || !exp)) || (kw(K_ASSERT) && tnl == 0)) { scan(); if (more == 0 && tt == T_LBRACE) { end = skipBalanced(); if (end < 0) return -1; } } if (more == 1) return -1; if (tt == T_SEMI) end = te; return end; } // Whether the last declEnd() saw `type` inside braces, a specifier that may need blanking. var declTyped = false; // From an import clause (impClause) or the token after `export [type]`: the end of the declaration, -1 when input // ran out or it is malformed. function declEnd(exp: boolean): i32 { declTyped = false; if (!exp && tt == T_STR) return specEnd(false); if (!exp && tt == T_NAME) { scan(); if (more == 0 && tt == T_COMMA) scan(); } if (more == 1) return -1; var named = false; if (tt == T_LBRACE) { var e = -1; while (e < 0) { scan(); if (more == 1 || tt == T_EOF || tt == T_SEMI) return -1; if (kw(K_TYPE)) declTyped = true; if (tt == T_RBRACE) e = te; } scan(); if (more == 1) return -1; named = true; if (exp && !kw(K_FROM) && !(tt == T_STR && tnl == 0)) { if (tt == T_SEMI) return te; return e; } } else if (tt == T_STAR) { scan(); if (more == 0 && kw(K_AS)) { scan(); if (more == 0) scan(); } } if (more == 1) return -1; if (kw(K_FROM)) { scan(); if (more == 1) return -1; } else if (!named || !exp) { return -1; } return specEnd(exp); } function stmtImport(): void { let m = tb; let k = importKind(); var end = -1; if (k == 0 || k == 1) { reseek(impClause); end = declEnd(false); } if (!back(m)) return; if (k == 3) { markValue(); setSt(S_EXPRSTMT); setA(0); setC(0); return; } if (k == 2) { // ts-blank-space counts an exported import-equals as a namespace value. if ((hdFirst & HD_EXPORT) != 0) markValue(); importEquals(); return; } if (end < 0) { failAt(12); return; } if (k == 1) { enterBlank(true); copyThrough(end); endStmt(); return; } // A value-level ESM import (k == 0): at least one specifier survives erasure. Flags this file // as ESM-bearing for the mixed-module-style check below, unless a CommonJS-only `import x =`/ // `export =` was already seen, in which case this later import is itself the reported construct // (engine/downlevel.zena's moduleEsmSeen, "Module-kind tracking"). if (mode == 1 && blankDepth == 0 && verbatim == 0 && dlModuleEsmSeen()) errorAtStmt(); markValue(); importBody(end); } // `import x = require('y')` / `import x = A.B`: an error in erase mode (always unsupported there), left // untouched through the statement end. Downlevel mode's own equivalent fallback (an `import x = // require(...)` that conflicts with a value-level ESM import/export declaration already seen in this // file) lives inside engine/downlevel.zena's importEquals() itself: unlike `export =` below, only the // `require(...)` form of `import x =` is a CommonJS-only construct (`import x = A.B`, a plain namespace // alias, is neither added to nor gated by module-kind tracking), and downlevel.zena's importEquals() already // has to look past `=` to tell those two forms apart before deciding how to rewrite either. function importEquals(): void { if (mode == 1 && blankDepth == 0 && verbatim == 0) { dlImportEquals(); return; } let m = tb; let end = importEqualsEnd(); if (!back(m)) return; if (end < 0) { failAt(13); return; } errorAtStmt(); copyThrough(end); pop(); } // From the first token of an import-equals declaration: its end through `;` (TypeScript's parseModuleReference). // -1 when input ran out or it is malformed. export function importEqualsEnd(): i32 { while (tt == T_NAME) { scan(); if (more == 1) return -1; } if (tt != T_EQ) return -1; scan(); if (more == 1 || tt != T_NAME) return -1; var end = -1; if (kw(K_REQUIRE)) { let m = tb; scan(); if (more == 1) return -1; if (tt == T_LPAREN) { end = skipBalanced(); if (end < 0) return -1; } else { reseek(m); } } if (end < 0) { end = te; scan(); while (more == 0 && tt == T_DOT) { scan(); if (more == 0) { end = te; scan(); } } } if (more == 1) return -1; if (tt == T_SEMI) end = te; return end; } // The rest of an import or export declaration from its keyword, through `end`: copied at once when no // specifier may be type-only. function importBody(end: i32): void { if (!declTyped) { let semi = Memory.default.getU8(end - 1 + (inBase - inStart)) == 59; copyThrough(end); if (blankDepth == 0) { semiNeeded = 1; if (semi) semiNeeded = 0; } pop(); return; } setSt(S_IMPORTBODY); setB(end); setC(0); adv(); } // Import/export clauses, one token per step through b = the declaration's end; `type` specifiers are blanked and // attributes copied. c = 0 outside braces, 1 inside. function hImportBody(): void { let end = fb(); if (ts >= end) { pop(); return; } if (fc() == 1) { if (tt == T_RBRACE) setC(0); else if (kw(K_TYPE)) { typeSpecifier(); return; } adv(); return; } if (tt == T_LBRACE) { setC(1); adv(); return; } if (kw(K_WITH) || kw(K_ASSERT)) { copyThrough(end); semiNeeded = 1; if (Memory.default.getU8(end - 1 + (inBase - inStart)) == 59) semiNeeded = 0; pop(); return; } adv(); } // At `type` inside an import/export clause: blanks a type-only specifier and its trailing comma. function typeSpecifier(): void { let m = tb; scan(); var isTypeOnly = false; // TypeScript's parseImportOrExportSpecifier: `type x`, `type as`, `type as as x` are type-only; `type`, // `type as x`, `type as as` are not. if (more == 0) { if (tt == T_STR || (tt == T_NAME && tk != K_AS)) { isTypeOnly = true; } else if (kw(K_AS)) { scan(); if (more == 0 && kw(K_AS)) { scan(); isTypeOnly = more == 0 && (tt == T_NAME || tt == T_STR); } else { isTypeOnly = more == 0 && tt != T_NAME && tt != T_STR; } } } var end = -1; if (isTypeOnly && more == 0) { reseek(m); if (more == 0) { end = te; scan(); while (more == 0 && (tt == T_NAME || tt == T_STR)) { end = te; scan(); } if (more == 0 && tt == T_COMMA) end = te; } } if (!back(m)) return; if (isTypeOnly) blankThrough(end, false); else adv(); } // Left unconverted as an ordinary (verbatim) expression statement, reported at the statement start: erase // mode's own fallback for every `export =`, and downlevel mode's fallback for one that conflicts with a // value-level ESM import/export declaration already seen in this file (see engine/downlevel.zena's // moduleEsmSeen/moduleCjsSeen). The current token is still `export`'s own first token. function exportAssignUnconverted(): void { markValue(); errorAtStmt(); verbatim = verbatim + 1; setSt(S_ENDVERB); pushFrame(S_EXPRSTMT, 0, 0, 0); adv(); adv(); } // `export` that is not a modifier (see lookHead): `export =`, `export type {`/`*`, `export as namespace`, // `export {`/`*`, `export default `. function stmtExport(): void { let m = tb; scan(); if (more == 1) { seek(m); return; } if (tt == T_EQ) { if (!back(m)) return; if (mode == 1 && blankDepth == 0 && verbatim == 0) { if (dlModuleCjsSeen()) { exportAssignUnconverted(); return; } dlExportAssign(); return; } exportAssignUnconverted(); return; } let isType = kw(K_TYPE); let isDefault = kw(K_DEFAULT); var end = -1; if (kw(K_AS)) { // `export as namespace X;` scan(); if (more == 0) scan(); if (more == 0) { end = te; scan(); if (more == 0 && tt == T_SEMI) end = te; } } else if (!isDefault) { if (isType) scan(); if (more == 0) end = declEnd(true); } if (!back(m)) return; // `export default` and every branch below but `export type`/`as namespace` are value-level ESM // export declarations (none are ambient or all-type-only): flag the file as ESM-bearing, unless a // CommonJS-only `import x =`/`export =` was already seen, in which case this later export is itself // the reported construct (engine/downlevel.zena's moduleEsmSeen, "Module-kind tracking"). if (isDefault) { if (mode == 1 && blankDepth == 0 && verbatim == 0 && dlModuleEsmSeen()) errorAtStmt(); markValue(); setSt(S_EXPRSTMT); // Reuses S_EXPRSTMT's state machine, but `export default` is never ambiguous the way a bare // expression statement is (`export default {}`/`function`/`class` are all already unambiguously // an expression/anonymous declaration): a = 1 tells hExprStmt to skip F_STMTSTART. setA(1); setC(0); adv(); adv(); return; } if (end < 0) { failAt(21); return; } if (isType) { enterBlank(true); copyThrough(end); endStmt(); return; } if (mode == 1 && blankDepth == 0 && verbatim == 0 && dlModuleEsmSeen()) errorAtStmt(); markValue(); importBody(end); } // --------------------------------------------------------------------------- // Functions. // Verifies a function head from the current token through `(` or `<`; returns the token count before it or -1. function lookFunctionHead(): i32 { var n = 0; while (true) { if (tt == T_LPAREN || tt == T_LT) return n; if (tt != T_NAME && tt != T_STAR) return -1; scan(); if (more == 1) return -1; n = n + 1; } } // `[modifiers] function [*] [name]` as a statement, with `mods` modifier tokens. The anchor from the statement start // covers the signature so an overload (no body) can be blanked; with a body only the modifiers ts-blank-space // removes are blanked. function stmtFunction(start: i32, anchored: boolean, mods: i32, declare: boolean): void { let m = tb; let n = lookFunctionHead(); if (!back(m)) return; if (n < 0) { failAt(24); return; } markValue(); // ts-blank-space blanks a function overload statement without a leading `;`, a declared one with it. if (anchored) { if (!declare) Memory.default.setI32(anchBase + (anchLen - 1) * 16 + 12, anchorFlags() & -3); } else if (declare) { anchorPushAt(start, ANCH_SEMI); } else { anchorPushAt(start, 0); } setSt(S_FUNC); setA(FN_ANCHOR); setB(0); setC(0); if (mode == 1) { dlFunctionHead(n); return; } var i = 0; while (i < n) { if (i < mods && isRemovedModifier(tk)) advBlank(); else adv(); i = i + 1; } } // Function signature and body. a = flags, c = sub-state: 0 type params and `(`, 1 return type, 2 body. function hFunc(): void { let s = fc(); let flags = fa(); if (s == 0) { if (tt == T_LT) { let m = tb; let ltStart = ts; let ok = scanTypeParams(); let end = typeEnd; let parenNl = verbatim == 0 && tt == T_LPAREN && spansLines(ltStart, te); if (!back(m)) return; if (!ok) { failAt(25); return; } emitTo(ts); let ltOut = outLen; blankThrough(end, false); if (parenNl) { // `(` moves up to where `<` was, so the parameter list stays on the name's line. setOutByte(ltOut, 40); setC(1); nest = nest + 1; advBlank(); pushFrame(S_PAREN, P_PARAMS, 0, 0); } return; } if (tt != T_LPAREN) { failAt(26); return; } setC(1); nest = nest + 1; adv(); var pf = P_PARAMS; if ((flags & FN_CTOR) != 0) pf = pf | P_CTOR; pushFrame(S_PAREN, pf, outLen - 1, 0); return; } if (s == 1) { setC(2); if (tt == T_COLON) { let m = tb; scan(); var end = -1; if (more == 0 && scanReturnType()) end = typeEnd; if (!back(m)) { setC(1); return; } if (end < 0) { failAt(27); return; } blankThrough(end, false); } return; } if (s == 2) { if (tt == T_LBRACE) { if ((flags & FN_ANCHOR) != 0) anchorPop(); setC(3); openBlock(B_BLOCK); semiNeeded = 0; if ((flags & FN_CTOR) != 0) dlCtorBody(); return; } // No body: an overload or abstract signature, which ts-blank-space blanks without visiting. if ((flags & FN_ANCHOR) != 0) { if (mode == 1) dlDropPending(); errLen = anchorErr(); anchorBlank(); if (tt == T_SEMI) advBlank(); } pop(); return; } pop(); } // --------------------------------------------------------------------------- // Classes. c: 0 modifiers and `class`, 1 name, 2 type params, 3 heritage clauses, 4 an `extends` element, 5 after // one, 6 body `{`, 7 done. function hClass(): void { let s = fc(); if (s == 0) { // Modifiers lookHead verified. if (tt != T_NAME) { failAt(28); return; } if (!kw(K_CLASS)) { if (isRemovedModifier(tk)) advBlank(); else adv(); return; } setC(1); advQuiet(); return; } if (s == 1) { // TypeScript's isImplementsClause: `class implements {}` is a class named implements. var isName = tt == T_NAME && !kw(K_EXTENDS); if (kw(K_IMPLEMENTS)) { let m = tb; scan(); isName = more == 0 && tt != T_NAME; if (!back(m)) return; } setC(2); if (isName) { if (mode == 1) dlClassName(); advQuiet(); } return; } if (s == 2) { setC(3); if (mode == 1) setB(0); if (tt == T_LT) { let m = tb; let ok = scanTypeParams(); let end = typeEnd; if (!back(m)) { setC(2); return; } if (!ok) { failAt(29); return; } blankThrough(end, false); } return; } if (s == 3) { // TypeScript's parseHeritageClauses: any number of `extends` and `implements` clauses, in any order. if (kw(K_EXTENDS)) { if (mode == 1) setB(1); setC(4); adv(); return; } if (kw(K_IMPLEMENTS)) { let m = tb; var end = te; scan(); if (more == 0 && !kw(K_EXTENDS) && !kw(K_IMPLEMENTS) && tt != T_LBRACE) { end = -1; if (scanTypeList()) end = typeEnd; } if (!back(m)) return; if (end < 0) { failAt(30); return; } blankThrough(end, false); return; } setC(6); return; } if (s == 4) { // A heritage element, unless the list is empty or ends with a comma (both errors): TypeScript's isListElement. let m = tb; var elem = tt != T_LBRACE && (tt != T_NAME || (!kw(K_EXTENDS) && !kw(K_IMPLEMENTS))) && isStartOfExpression(); if (tt == T_LBRACE) { scan(); elem = true; if (more == 0 && tt == T_RBRACE) { scan(); elem = more == 0 && (tt == T_COMMA || tt == T_LBRACE || kw(K_EXTENDS) || kw(K_IMPLEMENTS)); } } if (!back(m)) return; if (!elem) { setC(3); return; } setC(5); pushExpr(F_HERITAGE, 0); return; } if (s == 5) { if (tt == T_COMMA) { setC(4); adv(); return; } setC(3); return; } if (s == 6) { if (tt != T_LBRACE) { failAt(31); return; } nest = nest + 1; setC(7); advQuiet(); if (mode == 1) dlClassOpen(fb()); else pushFrame(S_CLASSBODY, 0, 0, stmtNest); return; } pop(); } function hClassBody(): void { if (tt == T_RBRACE) { nest = nest - 1; stmtNest = fc(); adv(); if (fa() != 0) dlClassEnd(); pop(); return; } if (tt == T_SEMI) { adv(); return; } if (tt == T_EOF) { failAt(32); return; } stmtNest = nest; emitTo(ts); anchorPush(ANCH_SEMI); pushFrame(S_MEMBER, 0, 0, 0); } // Class member. a = flags, c = 0 header, 1 after a decorator, 2 after a computed key, 3 after a property, // 4 after a method, 5 right after `@`, 6 after the name, 7 at `=`, 8 after `=`. function hMember(): void { let s = fc(); if (s == 5) { setC(1); pushExpr(F_DECORATOR, 0); return; } if (s == 1) { setC(0); return; } if (s == 3 || s == 4) { if (tt == T_SEMI) adv(); if ((fa() & M_BLANKED) != 0) { blankDepth = blankDepth - 1; semiPending = 0; } pop(); return; } if (s == 2 || s == 6) { memberAfterName(); return; } if (s == 7) { setC(8); adv(); return; } if (s == 8) { setC(3); pushExpr(0, 2); return; } if (tt == T_AT) { setA(fa() | M_DECORATED); setC(5); adv(); return; } memberHeader(); } // Scans modifiers, decides the member kind, and commits the header through the name. function memberHeader(): void { let m = tb; var blanked = false; var staticBlock = false; var nMods = 0; while (tt == T_NAME && isModifierKw(tk)) { let k = tk; scan(); if (more == 1) break; var follows = canFollowModifier(); if (k != K_STATIC && k != K_GET && k != K_SET && tnl == 1) follows = false; if (k == K_GET || k == K_SET) follows = isPropertyNameStart(); if (k == K_STATIC && tt == T_LBRACE) { staticBlock = true; break; } if (!follows) break; if (k == K_ABSTRACT || k == K_DECLARE) blanked = true; nMods = nMods + 1; } let semiFirst = more == 0 && (tt == T_LBRACK || tt == T_STAR); var indexSig = false; var indexEnd = -1; if (more == 0 && !staticBlock && tt == T_LBRACK) { // TypeScript's isUnambiguouslyIndexSignature, error forms included: `[...`, `[]`, `[public x`, `[x:`, `[x,`, // `[x?:`, `[x?,`, `[x?]`. scan(); if (more == 0 && (tt == T_ELLIPSIS || tt == T_RBRACK)) indexSig = true; if (more == 0 && tt == T_NAME && (isModifierKw(tk) || tk == K_CONST || tk == K_DEFAULT)) { scan(); if (more == 0 && isIdentTok()) indexSig = true; } else if (more == 0 && isIdentTok()) { scan(); if (more == 0 && tt == T_QUESTION) { scan(); if (more == 0 && tt == T_RBRACK) indexSig = true; } if (more == 0 && (tt == T_COLON || tt == T_COMMA)) indexSig = true; } } if (indexSig && more == 0) { reseek(m); var i = 0; while (more == 0 && i < nMods) { scan(); i = i + 1; } if (more == 0) { indexEnd = skipBalanced(); if (indexEnd >= 0 && tt == T_COLON) { scan(); if (more == 0 && scanType()) indexEnd = typeEnd; } if (indexEnd >= 0 && tt == T_SEMI) indexEnd = te; } } if (!back(m)) return; var isMethod = false; if ((blanked || semiFirst) && !indexSig) { // `abstract`/`declare` blank a property whole, but only themselves on a method with a body. var j = 0; while (more == 0 && j < nMods) { scan(); j = j + 1; } if (more == 0 && tt == T_STAR) isMethod = true; if (more == 0 && tt == T_LBRACK) skipBalanced(); else if (more == 0 && isPropertyNameStart()) scan(); if (more == 0 && (tt == T_QUESTION || tt == T_BANG)) scan(); if (more == 0 && (tt == T_LPAREN || tt == T_LT)) isMethod = true; if (!back(m)) return; if (isMethod) blanked = false; } // ts-blank-space's `;` before a computed property is unconditional, before a method only when needed. let semiMod = semiFirst && (fa() & M_DECORATED) == 0 && (!isMethod || semiNeeded == 1); if (indexSig) { if (indexEnd < 0) { failAt(33); return; } if ((fa() & M_NOANCHOR) == 0) anchorBlank(); blankRange(ts, indexEnd, false); seek(indexEnd); scan(); pop(); return; } if (blanked && verbatim == 0) { if ((fa() & M_NOANCHOR) == 0) anchorBlank(); if (blankDepth == 0) { blankDepth = 1; if (verbatim == 0) semiPending = semiNeeded; setA(fa() | M_BLANKED | M_NOANCHOR); } else { setA(fa() | M_NOANCHOR); } } var i = 0; while (i < nMods) { // A static block's modifiers (errors) are not removed: ts-blank-space does not visit the block's node. if (isRemovedModifier(tk) && !staticBlock) { blankRange(ts, te, i == 0 && semiMod); scan(); } else { adv(); } i = i + 1; } if (staticBlock) { if ((fa() & M_NOANCHOR) == 0) anchorPop(); setA(fa() | M_NOANCHOR); setC(4); adv(); openBlock(B_BLOCK); return; } if (tt == T_STAR) { adv(); return; } if (tt == T_LBRACK) { nest = nest + 1; setC(2); adv(); pushFrame(S_CLOSE, T_RBRACK, 0, 0); pushFrame(S_SEQ, 0, 0, 0); return; } if (!isPropertyNameStart()) { failAt(34); return; } if (tt == T_NAME && (tk == K_GET || tk == K_SET || tk == K_STATIC)) setA(fa() | M_SEMINAME); if (mode == 1 && kw(K_CONSTRUCTOR)) setA(fa() | M_CTOR); setC(6); adv(); } // After the member name: `?`/`!`, then a method signature or a property's type and initializer. function memberAfterName(): void { let m = tb; let hasQ = tt == T_QUESTION || tt == T_BANG; if (hasQ) scan(); let isMethod = more == 0 && (tt == T_LPAREN || tt == T_LT); var typeEndPos = -1; var hasInit = false; if (more == 0 && !isMethod && tt == T_COLON) { scan(); if (more == 0 && scanType()) typeEndPos = typeEnd; } if (more == 0 && !isMethod) hasInit = tt == T_EQ; if (!back(m)) return; if (isMethod) { setC(4); var ff = FN_ANCHOR; if ((fa() & M_NOANCHOR) != 0) ff = 0; if ((fa() & M_CTOR) != 0) ff = ff | FN_CTOR; setA(fa() | M_NOANCHOR); pushFrame(S_FUNC, ff, 0, 0); if (hasQ) advBlank(); return; } if ((fa() & M_NOANCHOR) == 0) anchorPop(); setA(fa() | M_NOANCHOR); var semi = (fa() & M_SEMINAME) != 0 && !hasInit; if (hasInit) setC(7); else setC(3); if (hasQ) { blankRange(ts, te, semi); semi = false; scan(); } if (typeEndPos >= 0) blankThrough(typeEndPos, semi); } // --------------------------------------------------------------------------- // Expressions. Frame: [state, flags, startPos, minPrec | leftOp << 8 | parentOp << 16]. function minPrec(): i32 { return fc() & 0xff; } function leftOp(): i32 { return (fc() >> 8) & 0xff; } function parentOp(): i32 { return (fc() >> 16) & 0xff; } function setLeftOp(p: i32): void { setC((fc() & -65281) | (p << 8)); } // Pushes the right operand of a binary operator with precedence p (token op); rp is its minimum precedence. function pushRhs(rp: i32, p: i32, op: i32): void { setLeftOp(p); pushFrame(S_EXPR, fa() & F_NOIN, -1, rp | (op << 16)); } function hExpr(): void { let flags = fa(); if (fb() < 0) setB(unitsAt(ts)); if (tt == T_NAME) { let k = tk; if (k == K_NEW) { let m = tb; scan(); var isTarget = more == 0 && tt == T_DOT; if (isTarget) { scan(); if (more == 1) isTarget = false; } if (!back(m)) return; if (isTarget) { setSt(S_POSTFIX); adv(); adv(); adv(); return; } setA(flags | F_NEW); adv(); return; } // `await` is an operator when an operand follows on its line, else (outside an async body) an identifier. var awaitOp = false; if (k == K_AWAIT) { let m = tb; scan(); awaitOp = more == 0 && tnl == 0 && isStartOfExpression() && !(tt == T_LBRACE && (flags & F_HERITAGE) != 0); if (!back(m)) return; } if (k == K_TYPEOF || k == K_VOID || k == K_DELETE || awaitOp) { adv(); return; } if (k == K_YIELD) { let m = tb; scan(); let star = more == 0 && tt == T_STAR; let bare = more == 0 && (tnl == 1 || !(star || isStartOfExpression())); if (!back(m)) return; if (bare) setSt(S_BINARY); // No `*`: the next token is itself the expression's own first token, the position `yield`'s own // restricted production checks (see F_RESTRICT). With `*`, that check already passed at `*` itself, // which erasure never touches, so nothing past it needs the flag. else if (!star) setA(flags | F_RESTRICT); adv(); if (star) adv(); return; } if (k == K_FUNCTION) { let m = tb; let n = lookFunctionHead(); if (!back(m)) return; if (n < 0) { failAt(35); return; } setSt(S_POSTFIX); pushFrame(S_FUNC, 0, 0, 0); var i = 0; while (i < n) { adv(); i = i + 1; } return; } if (k == K_CLASS) { setSt(S_POSTFIX); pushFrame(S_CLASS, 0, 0, 0); return; } if (k == K_ASYNC) { let m = tb; scan(); var kind = 0; var n = 0; if (more == 0 && tnl == 0) { if (kw(K_FUNCTION)) { kind = 1; n = lookFunctionHead() + 1; } else if (tt == T_LPAREN) { kind = 2; } else if (tt == T_LT) { // `async (...) =>`, else a call of `async` with type arguments. if (scanTypeParams() && more == 0 && tt == T_LPAREN && skipBalanced() >= 0 && (tt == T_ARROW || tt == T_COLON)) { kind = 3; } } else if (tt == T_ARROW) { kind = 5; } else if (tt == T_NAME) { scan(); if (more == 0 && tt == T_ARROW) kind = 4; } } if (!back(m)) return; if (kind == 1) { if (n < 1) { failAt(36); return; } setSt(S_POSTFIX); pushFrame(S_FUNC, 0, 0, 0); var i = 0; while (i < n) { adv(); i = i + 1; } return; } if (kind == 2) { adv(); openParen(flags, P_ASYNC); return; } if (kind == 3) { adv(); genericArrow(0); return; } if (kind == 4 || kind == 5) { setSt(S_ARROW); setC(0); adv(); adv(); if (kind == 4) adv(); return; } setSt(S_POSTFIX); adv(); return; } if (k == K_IMPORT) { let m = tb; scan(); var isMeta = more == 0 && tt == T_DOT; if (isMeta) { scan(); if (more == 1) isMeta = false; } if (!back(m)) return; setSt(S_POSTFIX); adv(); if (isMeta) { adv(); adv(); } return; } let m = tb; scan(); let isArrow = more == 0 && tt == T_ARROW; if (!back(m)) return; if (isArrow) { setSt(S_ARROW); setC(0); adv(); adv(); return; } setSt(S_POSTFIX); adv(); return; } if (tt == T_LPAREN) { openParen(flags, 0); return; } if (tt == T_LBRACK) { nest = nest + 1; setSt(S_POSTFIX); adv(); pushFrame(S_ARRAY, 0, 0, 0); return; } if (tt == T_LBRACE) { nest = nest + 1; setSt(S_POSTFIX); adv(); pushFrame(S_OBJECT, 0, 0, 0); return; } if (tokenIn(TS_LITERAL)) { setSt(S_POSTFIX); adv(); return; } if (tt == T_TEMPLATE_HEAD) { nest = nest + 1; setSt(S_POSTFIX); adv(); pushFrame(S_TEMPLATE, 0, 0, 0); pushFrame(S_SEQ, 0, 0, 0); return; } if (tt == T_SLASH || tt == T_SLASH_EQ) { rescanRegex(); if (more == 1) return; setSt(S_POSTFIX); adv(); return; } if (tokenIn(TS_PREFIX_OP)) { adv(); return; } if (tt == T_AT) { setSt(S_POSTFIX); pushFrame(S_DECORATORS, 0, 0, 0); return; } if (tt == T_LT) { if (jsx == 1) { let arrow = jsxArrowAhead(); if (arrow < 0) return; if (arrow == 0) { // Nothing postfix follows an element: TypeScript parses it as an update expression. setSt(S_BINARY); jsxEnter(J_EXPR); return; } } genericArrow(flags & (F_RESTRICT | F_ARROWBODY | F_STMTSTART)); return; } failAt(37); } // The current token is `(` starting a cover (arrow parameters or parenthesized expression). function openParen(flags: i32, pflags: i32): void { nest = nest + 1; setSt(S_POSTFIX); var pf = pflags; if ((flags & F_WHENTRUE) != 0) pf = pf | P_WHENTRUE; adv(); pushFrame(S_PAREN, pf, outLen - 1, 0); } // `(...) => body` or a legacy `expr` assertion. The current token is `<`. `ctxFlags` is `flags` masked // to F_RESTRICT | F_ARROWBODY | F_STMTSTART (0 from the arrow-function-already-detected call site below, // where none of the three can apply): downlevel mode's own erase decision needs to know which, if any, of // these this `<` sits at. F_RESTRICT marks the token a return/throw/yield directly checks for its own // no-line-terminator-here restriction: downlevel mode must not erase such a cast if a line terminator anywhere // through its own text or the gap before its operand would, once the cast's text is blanked away, land // between the keyword and what erasure exposes as the next token — that would newly trigger ASI on the // keyword and drop the operand. F_ARROWBODY/F_STMTSTART mark an arrow's concise body's own first token, or // an expression statement's own first token: erasing the cast must not expose a bare `{` (either position) // or `function`/`class` (statement start only) as that position's own first character, which would parse as // a block/function/class instead of the expression it actually is. A chained cast (`{...}`) needs no // special handling for this: only the *innermost* one's own next token is ever `{`/`function`/`class`, and // ctxFlags persists across the chain on the same frame the same way F_RESTRICT does, so each level decides // independently from its own lookahead. function genericArrow(ctxFlags: i32): void { let m = tb; var ok = scanTypeParams(); var end = typeEnd; var isArrow = false; if (ok && more == 0 && tt == T_LPAREN) { if (skipBalanced() >= 0) isArrow = tt == T_ARROW || tt == T_COLON; } if (!ok && more == 0) { // Not type parameters: a legacy assertion's type arguments, `>x`. reseek(m); if (more == 0) { ok = scanTypeArgs(); end = typeEnd; } } if (!back(m)) return; if (!ok) { failAt(38); return; } if (isArrow) { emitTo(ts); let ltOut = outLen; let ltStart = ts; blankThrough(end, false); nest = nest + 1; setSt(S_POSTFIX); if (verbatim == 0 && spansLines(ltStart, te)) { // `(` moves up to where `<` was, so the parameter list stays on the line the arrow started on. setOutByte(ltOut, 40); advBlank(); } else { adv(); } pushFrame(S_PAREN, P_ARROW, outLen - 1, 0); return; } // Erase mode never erases a legacy assertion (docs/modes.md): always report it, exactly as before. // The erase/report/abort decision (including its own lookahead past `end`) lives behind this hook so the // erase build, whose hooks-erase.zena stub always answers "report", never pays for it: see // docs/modes.md and engine/downlevel.zena's castErase for the real logic. if (mode == 1) { let decision = dlCastErase(m, end, ctxFlags); if (decision == 2) return; if (decision == 1) { blankThrough(end, false); return; } } error(ts, te); copyThrough(end); } export function spansLines(a: i32, b: i32): boolean { let d = inBase - inStart; var q = a; while (q < b) { if (Memory.default.getU8(q + d) == 10) return true; q = q + 1; } return false; } // Arrow body holder: c = 0 before the body, 1 after it. function hArrow(): void { if (fc() == 1) { pop(); return; } setC(1); if (tt == T_LBRACE) { openBlock(B_BLOCK); semiNeeded = 0; return; } pushExpr((fa() & (F_NOIN | F_WHENTRUE)) | F_ARROWBODY, 2); } function hPostfix(): void { let flags = fa(); if (tt == T_DOT || tt == T_QDOT) { let m = tb; scan(); let isName = more == 0 && (tt == T_NAME || tt == T_PRIVATE); if (!back(m)) return; adv(); if (isName) adv(); return; } if (tt == T_LBRACK && (flags & F_DECORATOR) == 0) { nest = nest + 1; adv(); pushFrame(S_CLOSE, T_RBRACK, 0, 0); pushFrame(S_SEQ, 0, 0, 0); return; } if (tt == T_LPAREN) { if ((flags & F_NEW) != 0) setA(flags & -9); nest = nest + 1; adv(); pushFrame(S_ARGS, 0, 0, 0); return; } if (tt == T_TEMPLATE) { adv(); return; } if (tt == T_TEMPLATE_HEAD) { nest = nest + 1; adv(); pushFrame(S_TEMPLATE, 0, 0, 0); pushFrame(S_SEQ, 0, 0, 0); return; } if (tt == T_BANG && tnl == 0) { advBlank(); return; } if (tt == T_LT || tt == T_SHL) { typeArgsOrLess(flags); return; } if ((tt == T_INC || tt == T_DEC) && tnl == 0) { setSt(S_BINARY); adv(); return; } if ((flags & F_NEW) != 0) setA(flags & -9); setSt(S_BINARY); } // `<` after an operand: type arguments when they parse and the follow token allows, else the less-than operator. function typeArgsOrLess(flags: i32): void { let m = tb; if (tt == T_SHL) { rescanLess(); if (more == 1) return; } var ok = scanTypeArgs(); let end = typeEnd; // TypeScript rescans the closing `>`: `>=` or `>>` there means these were not type arguments. if (ok) { let after = Memory.default.getU8(end + (inBase - inStart)); if (after == 62 || after == 61) ok = false; } var isArgs = false; if (ok && more == 0) { if ((flags & F_HERITAGE) != 0) isArgs = true; else if (tt == T_LPAREN || tt == T_TEMPLATE || tt == T_TEMPLATE_HEAD) isArgs = true; else if (tt == T_LT || tt == T_GT || tt == T_PLUS || tt == T_MINUS) isArgs = false; else isArgs = tnl == 1 || isBinaryOperatorToken() || !isStartOfExpression(); } if (!back(m)) return; if (isArgs) { blankThrough(end, false); return; } setSt(S_BINARY); } function hBinary(): void { let flags = fa(); let mp = minPrec(); if ((flags & (F_HERITAGE | F_DECORATOR)) != 0) { pop(); return; } if (tt == T_GT) { rescanGreater(); if (more == 1) return; } if (isBinop(tt)) { let p = precOf(tt); if (p < mp || (p == mp && tt != T_STARSTAR)) { pop(); return; } let op = tt; var rp = p + 1; if (op == T_STARSTAR) rp = p; pushRhs(rp, p, op); adv(); return; } if (tt == T_NAME) { if ((tk == K_IN && (flags & F_NOIN) == 0) || tk == K_INSTANCEOF) { if (11 < mp) { pop(); return; } pushRhs(12, 11, 0); adv(); return; } if ((tk == K_AS || tk == K_SATISFIES) && tnl == 0) { if (11 < mp) { pop(); return; } asExpression(); return; } pop(); return; } if (isAssign(tt)) { if (mp > 2) { pop(); return; } setLeftOp(0); pushFrame(S_EXPR, flags & F_NOIN, -1, 2); adv(); return; } if (tt == T_QUESTION) { if ((flags & F_COVER) != 0 && mp <= 2) { let m = tb; scan(); let optional = more == 0 && (tokenIn(TS_OPTIONAL_FOLLOW)); if (!back(m)) return; if (optional) { markTyped(); advBlank(); return; } } if (mp > 3) { pop(); return; } setLeftOp(0); // The false branch keeps the conditional's own context: TypeScript passes allowReturnTypeInArrowFunction on. pushFrame(S_COND, flags & (F_NOIN | F_WHENTRUE), 0, 0); pushFrame(S_EXPR, (flags & F_NOIN) | F_WHENTRUE, -1, 2); adv(); return; } if (tt == T_COLON && (flags & F_COVER) != 0 && mp <= 2) { let m = tb; let end = lookType(); if (!back(m)) return; if (end < 0) { failAt(39); return; } markTyped(); blankThrough(end, false); return; } pop(); } // A cover element turned out to be a parameter: tell the paren frame below. function markTyped(): void { if (sp() >= 2 && belowSt(1) == S_PAREN) setBelowA(1, belowA(1) | P_TYPED); } // `as T` / `satisfies T` with the current token on the keyword. function asExpression(): void { let m = tb; var end = lookType(); var lastStart = emitPos; while (end >= 0 && (kw(K_AS) || kw(K_SATISFIES)) && tnl == 0) { lastStart = end; end = lookType(); } let nextTt = tt; var nextPrec = 0; var continues = false; if (more == 0) { if (isBinop(tt)) nextPrec = precOf(tt); if (tt == T_NAME && (tk == K_IN || tk == K_INSTANCEOF)) nextPrec = 11; continues = isBinaryOperatorToken() || isAssign(tt) || tt == T_QUESTION || tt == T_COLON || tt == T_COMMA || tt == T_SEMI; } if (!back(m)) return; if (end < 0) { failAt(40); return; } let base = leftOp(); let po = parentOp(); var err = false; if (po == T_QQ && (nextTt == T_AMPAMP || nextTt == T_BARBAR)) err = true; if ((po == T_AMPAMP || po == T_BARBAR) && nextTt == T_QQ) err = true; if (!err && base != 0 && nextPrec != 0) { if (nextPrec > base) err = true; else if (nextPrec == base && (base == 15 || nextTt == T_STARSTAR)) err = true; } if (err) { if (blankDepth == 0 && verbatim == 0) errorUnits(fb(), fb() + 1); copyThrough(end); return; } // The oracle's `;` goes where the outermost assertion of a chain starts. blankRange(emitPos, lastStart, false); blankRange(lastStart, end, !continues && nest == stmtNest); seek(end); scan(); } function hCond(): void { if (fc() == 0) { if (tt != T_COLON) { failAt(41); return; } setC(1); pushFrame(S_EXPR, fa(), -1, 2); adv(); return; } pop(); } // Parenthesized cover grammar. a = flags, b = out position of `(`, c = 0 element start / 1 after element / // 2 right after a parameter decorator's `@` / 3 after the decorator expression. function hParen(): void { let flags = fa(); var s = fc(); if (s == 2) { setC(3); verbatim = verbatim + 1; pushExpr(F_DECORATOR, 0); return; } if (s == 3) { verbatim = verbatim - 1; setC(0); s = 0; } if (s == 1) { if (tt == T_COMMA) { setC(0); adv(); return; } if (tt != T_RPAREN) { failAt(42); return; } closeParen(flags); return; } if (tt == T_RPAREN) { closeParen(flags); return; } setA(flags | P_HASELEM); if (tt == T_ELLIPSIS) { setA(flags | P_HASELEM | P_TYPED); adv(); return; } if (tt == T_AT) { setC(2); adv(); return; } if (tt == T_NAME && (isModifierKw(tk) || tk == K_CONST || tk == K_DEFAULT)) { let m = tb; scan(); // TypeScript's arrow detection: `(readonly as T)` is an assertion, not a parameter. let isMod = more == 0 && tnl == 0 && (tokenIn(TS_PARAM_START)) && ((flags & P_PARAMS) != 0 || !kw(K_AS)); if (!back(m)) return; if (isMod) { let prop = keywordIn(tk, KS_PARAM_PROP); if ((prop || tk == K_OVERRIDE) && mode == 1 && (flags & P_CTOR) != 0) { setA(fa() | P_PROP); advBlank(); return; } // Parameter property modifiers are errors but stay in place; the others are grammar errors. if (prop) error(ts, te); adv(); return; } } if (kw(K_THIS) && (flags & P_PARAMS) != 0) { let m = tb; var end = te; scan(); if (more == 0 && tt == T_COLON) end = lookType(); if (more == 0 && end >= 0 && tt == T_COMMA) end = te; if (!back(m)) return; if (end < 0) { failAt(43); return; } setA(flags | P_HASELEM | P_TYPED); blankThrough(end, false); return; } if ((fa() & P_PROP) != 0) { setA(fa() & -129); dlParamProp(); } setC(1); pushExpr(F_COVER, 2); } // At `)`: decide between arrow (`=>` or `: T =>`) and a plain parenthesized expression or call arguments. function closeParen(flags: i32): void { if ((flags & P_PARAMS) != 0) { nest = nest - 1; adv(); pop(); return; } let m = tb; let parenPos = ts; scan(); var isArrow = more == 0 && tt == T_ARROW; var retEnd = -1; let typed = (flags & P_TYPED) != 0 || (flags & P_HASELEM) == 0 || (flags & P_ARROW) != 0; if (more == 0 && tt == T_COLON) { scan(); if (more == 0 && scanReturnType()) { if (tt == T_ARROW) { // Inside a conditional's true branch an ambiguous `(x): T => body` is an arrow only if a `:` follows the body. if ((flags & P_WHENTRUE) == 0 || typed || colonFollowsArrowBody()) { isArrow = true; retEnd = typeEnd; } } } } if (!back(m)) return; nest = nest - 1; pop(); if (!isArrow) { adv(); return; } // The arrow replaces the operand frame below: nothing postfix or binary continues after its body. setSt(S_ARROW); setC(0); if (retEnd >= 0) { // A line break between the last parameter (emitPos) and the end of the return type moves `)` to just // before `=>`. if (verbatim == 0 && spansLines(emitPos, retEnd)) { blankRange(parenPos, retEnd, false); setOutByte(outLen - 1, 41); seek(retEnd); scan(); } else { adv(); blankThrough(retEnd, false); } } else { adv(); } adv(); } // At `=>`: skips the arrow body and reports whether a conditional's `:` follows it. function colonFollowsArrowBody(): boolean { scan(); if (more == 1) return false; if (tt == T_LBRACE) { if (skipBalanced() < 0) return false; return tt == T_COLON; } var pending = 0; var ends = false; while (true) { if (tt == T_EOF) return false; // The body ends where ASI would end its statement: an operand on a new line after one that is complete. if (ends && tnl == 1 && (tt == T_NUM || tt == T_STR || tt == T_PRIVATE || tt == T_INC || tt == T_DEC || (tt == T_NAME && !isBinaryOperatorToken()))) { return false; } ends = true; if (tokenIn(TS_OPEN)) { if (skipBalanced() < 0) return false; continue; } if (tt == T_TEMPLATE_HEAD) { if (skipTemplate() < 0) return false; continue; } if (tokenIn(TS_CLOSE_OR_SEP)) return false; if (tt == T_QUESTION) pending = pending + 1; if (tt == T_COLON) { if (pending == 0) return true; pending = pending - 1; } ends = tokenIn(TS_OPERAND_END); scan(); if (more == 1) return false; } } function hArgs(): void { if (tt == T_RPAREN) { nest = nest - 1; adv(); pop(); return; } if (tt == T_COMMA || tt == T_ELLIPSIS) { adv(); return; } pushExpr(0, 2); } function hArray(): void { if (tt == T_RBRACK) { nest = nest - 1; adv(); pop(); return; } if (tt == T_COMMA || tt == T_ELLIPSIS) { adv(); return; } pushExpr(0, 2); } // Object literal. c: 0 member start, 1 after a computed key, 2 after a value or method, 3 after a key, // 4 right after `...`. function hObject(): void { let s = fc(); if (s == 4) { setC(2); pushExpr(0, 2); return; } if (s == 2) { if (tt == T_COLON) { // A binding pattern's `...a: b` (an error TypeScript parses). setC(4); adv(); return; } setC(0); // TypeScript also takes `;` between members (an error). if (tt == T_COMMA || tt == T_SEMI) { adv(); return; } if (tt == T_RBRACE) { nest = nest - 1; adv(); pop(); return; } return; } if (s == 1 || s == 3) { objectAfterKey(); return; } if (s == 0) { if (tt == T_RBRACE) { nest = nest - 1; adv(); pop(); return; } if (tt == T_COMMA) { adv(); return; } if (tt == T_ELLIPSIS) { setC(4); adv(); return; } // A member that may be a method without a body (an error), which ts-blank-space blanks whole: anchored // until it turns out to be a property or reaches its body. emitTo(ts); anchorPush(ANCH_SEMI); setC(5); } if (tt == T_STAR) { adv(); return; } if (tt == T_NAME && (tk == K_GET || tk == K_SET || tk == K_ASYNC || isRemovedModifier(tk))) { let k = tk; let m = tb; scan(); var follows = more == 0 && canFollowModifier(); if (k != K_GET && k != K_SET && tnl == 1) follows = false; var semi = false; if (follows && isRemovedModifier(k)) { // ts-blank-space removes modifiers from object literal methods only, the first with a `;` before a // computed or generator name when one is needed. while (more == 0 && tt == T_NAME && isModifierKw(tk)) scan(); semi = semiNeeded == 1 && (tt == T_LBRACK || tt == T_STAR); if (more == 0 && tt == T_LBRACK) skipBalanced(); else if (more == 0 && tt != T_STAR && tt != T_LPAREN) scan(); follows = more == 0 && (tt == T_LPAREN || tt == T_LT || tt == T_STAR); } if (!back(m)) return; if (follows) { if (isRemovedModifier(k)) { blankRange(ts, te, semi && ts == anchorIn()); scan(); } else { adv(); } return; } } if (tt == T_LBRACK) { nest = nest + 1; setC(1); adv(); pushFrame(S_CLOSE, T_RBRACK, 0, 0); pushFrame(S_SEQ, 0, 0, 0); return; } if (!isPropertyNameStart()) { failAt(44); return; } setC(3); adv(); } function objectAfterKey(): void { if (tt == T_COLON || tt == T_EQ) { anchorPop(); setC(2); pushFrame(S_EXPR, 0, -1, 2); adv(); return; } if (tt == T_BANG) { // A shorthand's `a!` (a grammar error), which ts-blank-space keeps. adv(); return; } if (tt == T_QUESTION) { // ts-blank-space removes a method's `?` only; a property's (a grammar error) stays. let m = tb; scan(); let isMethod = more == 0 && (tt == T_LPAREN || tt == T_LT); if (!back(m)) return; if (isMethod) advBlank(); else adv(); return; } setC(2); if (tt == T_LPAREN || tt == T_LT) pushFrame(S_FUNC, FN_ANCHOR, 0, 0); else anchorPop(); } function hTemplate(): void { if (tt != T_RBRACE) { failAt(45); return; } rescanTemplate(); if (more == 1) return; if (tt == T_TEMPLATE_TAIL) { nest = nest - 1; adv(); pop(); return; } adv(); pushFrame(S_SEQ, 0, 0, 0); } // --------------------------------------------------------------------------- // JSX (init's jsx bit). One S_JSX frame per element or fragment: [S_JSX, flags, phase, 0]. Children are lexed by // lexChild, where whitespace and comment-like text are text; tags and `{...}` containers use the ordinary lexer. // Downlevel mode's classic-runtime output comes from the jsx* hooks (engine/jsx.zena). // At `<` in operand position: TypeScript's .tsx rule for a generic arrow, `` or `/`), each optionally after `const`. 1 arrow, 0 JSX, -1 out of input. function jsxArrowAhead(): i32 { let m = tb; scan(); var arrow = false; if (more == 0 && tt == T_NAME && (tk == 0 || tk >= K_FIRST_CONTEXTUAL || tk == K_CONST)) { if (tk == K_CONST) scan(); if (more == 0) scan(); if (more == 0) { if (kw(K_EXTENDS)) { scan(); arrow = more == 0 && tt != T_EQ && tt != T_GT && tt != T_SLASH && tt != T_SLASH_EQ; } else { arrow = tt == T_COMMA || tt == T_EQ; } } } if (!back(m)) return -1; if (arrow) return 1; return 0; } // The current token is an element's `<`, lexed normally. function jsxEnter(flags: i32): void { nest = nest + 1; var f = flags; if (mode == 1 && blankDepth == 0 && verbatim == 0 && dlJsxOn()) f = f | J_XF; pushFrame(S_JSX, f, JP_OPEN, 0); } function hJsx(): void { let ph = fb(); if (ph == JP_KIDS) { jsxKid(); return; } if (ph == JP_ATTRS) { jsxAttr(); return; } if (ph == JP_NAME) { jsxName(); return; } if (ph == JP_OPEN) { jsxOpen(); return; } if (ph == JP_END) { jsxEnd(); return; } jsxContainerEnd(); } function jsxXf(): boolean { return (fa() & J_XF) != 0; } // Consumes the current token with no output, keeping the trivia before it. function jsxDrop(): void { emitTo(ts); emitPos = te; } function jsxOpen(): void { let m = tb; scan(); let frag = more == 0 && tt == T_GT; if (!back(m)) return; if (jsxXf()) { jsxDrop(); dlJsxOpen(frag); scan(); } else { adv(); } if (frag) jsxKids(); else setB(JP_NAME); } // The current token is the `>` that opens the children: consumes it and lexes the first child. function jsxKids(): void { if (jsxXf()) jsxDrop(); else emitTo(te); setB(JP_KIDS); childLex = 1; seek(te); lexChild(); } // Whether input [a, b) holds a `-`. function hasDash(a: i32, b: i32): boolean { let d = inBase - inStart; var q = a; while (q < b) { if (Memory.default.getU8(q + d) == 45) return true; q = q + 1; } return false; } // A tag name: `a-b`, `ns:name`, `this`, or a member chain `A.b.c`, then any type arguments, which are blanked // (ts-blank-space keeps them, which no JSX tool accepts). An intrinsic name (a plain one starting with a lowercase // letter or holding a `-`) and a namespaced one are strings to the classic runtime. function jsxName(): void { let m = tb; let start = ts; let first = Memory.default.getU8(start + (inBase - inStart)); var str = tk != K_THIS && first >= 97 && first <= 122; rescanJsxName(); let end1 = te; var end = te; if (hasDash(start, end1)) str = true; var nsStart = -1; if (more == 0) scan(); if (more == 0 && tt == T_COLON) { scan(); if (more == 0) { nsStart = ts; rescanJsxName(); end = te; if (more == 0) scan(); } } else { while (more == 0 && tt == T_DOT) { str = false; scan(); end = te; if (more == 0) scan(); } } var targs = -1; var targsEnd = -1; if (more == 0 && tt == T_LT) { targs = ts; if (scanTypeArgs()) targsEnd = typeEnd; } if (!back(m)) return; if (targs >= 0 && targsEnd < 0) { failAt(60); return; } if (jsxXf() && nsStart >= 0) { emitTo(start); dlJsxNs(start, end1, nsStart, end); emitPos = end; } else if (jsxXf() && str) { emitTo(start); dlJsxQuoted(start, end); emitPos = end; } else { emitTo(end); } var resume = end; if (targs >= 0) { blankRange(targs, targsEnd, false); resume = targsEnd; } seek(resume); scan(); setB(JP_ATTRS); } // Opens the props object before the first attribute, or separates the next one. function jsxSep(): void { if ((fa() & J_PROPS) == 0) { setA(fa() | J_PROPS); dlJsxPut(JX_PROPS); } else { dlJsxPut(JX_COMMA); } } // Closes the props object, or writes `null` when there was none. function jsxEndProps(): void { if ((fa() & J_PROPS) != 0) dlJsxPut(JX_RBRACE); else dlJsxPut(JX_NULL); } function jsxAttr(): void { let xf = jsxXf(); if (tt == T_GT) { if (xf) { emitTo(ts); jsxEndProps(); } jsxKids(); return; } if (tt == T_SLASH) { let m = tb; scan(); let e = te; let ok = more == 0 && tt == T_GT; if (!back(m)) return; if (!ok) { failAt(61); return; } if (xf) { emitTo(ts); jsxEndProps(); dlJsxPut(JX_RPAREN); emitPos = e; } else { emitTo(e); } jsxDone(e); return; } if (tt == T_LBRACE) { // `{...expr}`. let m = tb; scan(); let ok = more == 0 && tt == T_ELLIPSIS; if (!back(m)) return; if (!ok) { failAt(62); return; } if (xf) { jsxDrop(); jsxSep(); scan(); adv(); dlJsxPut(JX_LPAREN); } else { adv(); adv(); } setB(JP_ATTRVAL); pushFrame(S_SEQ, 0, 0, 0); return; } if (tt != T_NAME) { failAt(63); return; } // `name`, `name-x` or `ns:name`, then `="..."`, `={expr}`, `=`, or no value (true). let m = tb; let start = ts; rescanJsxName(); let end1 = te; var end = te; var nsStart = -1; if (more == 0) scan(); if (more == 0 && tt == T_COLON) { scan(); if (more == 0) { nsStart = ts; rescanJsxName(); end = te; if (more == 0) scan(); } } var eq = -1; var val = -1; var valEnd = -1; var kind = 0; // 1 string, 2 container, 3 element, 4 empty container (`true`, a checker error) if (more == 0 && tt == T_EQ) { eq = ts; scan(); if (more == 0) { val = ts; valEnd = te; let q = Memory.default.getU8(ts + (inBase - inStart)); if (q == 34 || q == 39) { rescanJsxString(); valEnd = te; kind = 1; } else if (tt == T_LBRACE) { kind = 2; scan(); if (more == 0 && tt == T_RBRACE) { kind = 4; valEnd = te; } } else if (tt == T_LT) { kind = 3; } } } if (!back(m)) return; if (eq >= 0 && kind == 0) { failAt(64); return; } if (xf) { emitTo(start); jsxSep(); if (nsStart >= 0) dlJsxNs(start, end1, nsStart, end); else if (hasDash(start, end)) dlJsxQuoted(start, end); else emitTo(end); emitPos = end; } else { emitTo(end); } if (eq < 0) { if (xf) dlJsxPut(JX_TRUE); seek(end); scan(); return; } if (kind == 4) { if (xf) { emitTo(eq); dlJsxPut(JX_TRUE); blankRange(eq, valEnd, false); } else { emitTo(valEnd); } seek(valEnd); scan(); return; } if (xf) { emitTo(eq); dlJsxPut(JX_COLON); emitPos = eq + 1; } else { emitTo(eq + 1); } if (kind == 3) { seek(eq + 1); scan(); jsxEnter(J_EXPR); return; } if (xf) { emitTo(val); if (kind == 1) dlJsxString(val, valEnd); else dlJsxPut(JX_LPAREN); emitPos = valEnd; } else { emitTo(valEnd); } seek(valEnd); scan(); if (kind == 2) { setB(JP_ATTRVAL); pushFrame(S_SEQ, 0, 0, 0); } } // The `}` after a container's expression. function jsxContainerEnd(): void { if (tt != T_RBRACE) { failAt(65); return; } if (jsxXf()) { jsxDrop(); dlJsxPut(JX_RPAREN); } else { emitTo(te); } if (fb() == JP_ATTRVAL) { setB(JP_ATTRS); scan(); return; } setB(JP_KIDS); childLex = 1; seek(te); lexChild(); } // A child: text, `{expr}`, `{...expr}`, `{}` (or only a comment), an element, or the closing tag. function jsxKid(): void { let xf = jsxXf(); if (tt == T_JSX_TEXT) { if (xf) { dlJsxText(ts, te); emitPos = te; } else { emitTo(te); } lexChild(); return; } if (tt == T_LT) { childLex = 0; if (xf) { emitTo(ts); dlJsxPut(JX_COMMA); } jsxEnter(0); return; } // Lookahead past a child token restores it with lexChild, never back(): that would re-lex it normally. let m = tb; if (tt == T_LBRACE) { let e = te; scan(); if (more == 1) { seek(m); return; } childLex = 0; if (tt == T_RBRACE) { if (xf) { blankRange(m, e, false); emitTo(ts); blankRange(ts, te, false); } else { emitTo(te); } childLex = 1; seek(te); lexChild(); return; } if (xf) { emitTo(m); emitPos = e; if (tt == T_ELLIPSIS) { dlJsxPut(JX_COMMA); adv(); dlJsxPut(JX_LPAREN); } else { dlJsxPut(JX_ARG); } } else { emitTo(e); if (tt == T_ELLIPSIS) adv(); } setB(JP_KIDVAL); pushFrame(S_SEQ, 0, 0, 0); return; } if (tt == T_JSX_CLOSE) { seek(te); scan(); while (more == 0 && tt != T_GT && tt != T_EOF) scan(); let e = te; if (more == 1) { seek(m); return; } if (tt != T_GT) { failAt(66); return; } childLex = 0; if (xf) { emitTo(m); dlJsxPut(JX_RPAREN); blankRange(m, e, false); } else { emitTo(e); } jsxDone(e); return; } failAt(67); } // The element ends at input e, written through it: back to the expression below, or the parent's next child. function jsxDone(e: i32): void { nest = nest - 1; semiNeeded = 1; seek(e); if ((fa() & J_EXPR) == 0) { pop(); childLex = 1; lexChild(); return; } childLex = 0; scan(); if ((fa() & J_XF) != 0) setB(JP_END); else pop(); } // A written element's next token: only ASI lets `(`, `[` or a template follow an element, but after the // factory call's `)` they would continue it. function jsxEnd(): void { if (tt == T_LPAREN || tt == T_LBRACK || tt == T_TEMPLATE || tt == T_TEMPLATE_HEAD) dlJsxPut(JX_SEMI); pop(); } // --------------------------------------------------------------------------- // Dispatch, hottest states first. function step(): void { let s = st(); if (s == S_EXPR) { hExpr(); return; } if (s == S_POSTFIX) { hPostfix(); return; } if (s == S_BINARY) { hBinary(); return; } if (s == S_SEQ) { hSeq(); return; } if (s == S_ARGS) { hArgs(); return; } if (s == S_STMT) { hStmt(); return; } if (s == S_BLOCK) { hBlock(); return; } if (s == S_EXPRSTMT) { hExprStmt(); return; } if (s == S_OBJECT) { hObject(); return; } if (s == S_ARRAY) { hArray(); return; } if (s == S_PAREN) { hParen(); return; } if (s == S_MEMBER) { hMember(); return; } if (s == S_CLASSBODY) { hClassBody(); return; } if (s == S_FUNC) { hFunc(); return; } if (s == S_VARDECL) { hVarDecl(); return; } if (s == S_CLOSE) { hClose(); return; } if (s == S_COND) { hCond(); return; } if (s == S_ARROW) { hArrow(); return; } if (s == S_TEMPLATE) { hTemplate(); return; } if (s == S_TOP) { hTop(); return; } if (s == S_PARENSTMT) { hParenStmt(); return; } if (s == S_RETURN) { hReturn(); return; } if (s == S_FOR) { hFor(); return; } if (s == S_CLASS) { hClass(); return; } if (s == S_SWITCHBODY) { hSwitchBody(); return; } if (s == S_TRY) { hTry(); return; } if (s == S_DO) { hDo(); return; } if (s == S_ENDBLANK) { hEndBlank(); return; } if (s == S_ENDVERB) { hEndVerb(); return; } if (s == S_NAMESPACE) { hNamespace(); return; } if (s == S_DECORATORS) { hDecorators(); return; } if (s == S_STMTEND) { hStmtEnd(); return; } if (s == S_IMPORTBODY) { hImportBody(); return; } if (s == S_JSX) { hJsx(); return; } if (s == 40) { dlEnumStep(); return; } failAt(46); } function run(): void { while (more == 0 && done == 0) { step(); } } // --------------------------------------------------------------------------- // Output primitives for engine/downlevel.zena, which cannot assign this module's state. export function outReserve(n: i32): i32 { ensureOut(n); return outAddr(outLen); } export function outCommit(n: i32): void { outLen = outLen + n; } // Opens n bytes at output offset o, shifting what follows; returns their address. export function outInsert(o: i32, n: i32): i32 { ensureOut(n); let a = outAddr(o); moveBytes(a + n, a, outLen - o); outLen = outLen + n; return a; } // Consumes input through p without output. export function dropTo(p: i32): void { emitPos = p; } export function setNest(v: i32): void { nest = v; } export function setErrLen(v: i32): void { errLen = v; } export function getSp(): i32 { return sp(); } export function getNest(): i32 { return nest; } export function getBlankDepth(): i32 { return blankDepth; } export function getVerbatim(): i32 { return verbatim; } export function getStmtNest(): i32 { return stmtNest; } export function getAnchLen(): i32 { return anchLen; } // --------------------------------------------------------------------------- // The ABI (docs/architecture.md). export function init(m: i32): void { mode = m & 1; jsx = (m >> 1) & 1; childLex = 0; jsxPragmaScan = jsx & mode; if (started == 0) { stackCap = 65536; anchCap = 4096; errCap = 8192; outCap = 65536; inCap = 65536; layout(); started = 1; } fp = DATA_END - 16; anchLen = 0; errLen = 0; outStart = 0; outLen = 0; flushed = 0; inStart = 0; emitPos = 0; blankDepth = 0; semiPending = 0; semiNeeded = 0; retryAt = 0; verbatim = 0; nest = 0; stmtNest = 0; uqPos = 0; uqUnits = 0; done = 0; failed = 0; lexFinal = 0; lastValues = 0; dlInit(); lexReset(); setInput(inBase, 0, 0); seek(0); clearMore(); pushFrame(S_TOP, 0, 0, 0); } export function inbuf(cap: i32): i32 { if (flushed > outStart) { moveBytes(outBase, outAddr(flushed), outLen - flushed); outStart = flushed; } let keepIn = inKeep(); if (keepIn > inStart) { unitsAt(keepIn); moveBytes(inBase, keepIn + (inBase - inStart), inEnd - keepIn); inStart = keepIn; } let live = inEnd - inStart; if (live + cap + 16 > inCap) { var ncap = inCap * 2; if (ncap < live + cap + 16) ncap = live + cap + 16; inCap = ncap; ensureMemory(inBase + inCap + 16); } setInput(inBase - inStart, inEnd, lexFinal); return inBase + live; } export function push(len: i32, fin: i32): i32 { lexFinal = fin; setInput(inBase - inStart, inEnd + len, fin); // A step that ran out of input after scanning L bytes ahead waits for L more before it is retried, so a // lookahead over a construct spanning many chunks costs a geometric series, not one rescan per chunk. if (done == 0 && (fin == 1 || inEnd >= retryAt)) { clearMore(); if (childLex == 1) lexChild(); else scan(); run(); if (more == 1) retryAt = inEnd + (inEnd - pos); } if (failed == 1) return -1; if (done == 1) anchLen = 0; let wm = watermark(); let ready = wm - flushed; lastFlushed = flushed; outPtr = outAddr(flushed); flushed = wm; return ready; } export function outbuf(): i32 { return outPtr; } // Downlevel mode's JSX factories: `a` bytes of pragma, then `b` of pragmaFrag, written at inbuf's pointer // right after init. export function pragma(a: i32, b: i32): void { dlJsxPragma(inBase + (inEnd - inStart), a, b); } // Errors recorded under an anchor may still be dropped (a blanked overload or namespace), so they are held back. export function errorCount(): i32 { if (anchLen > 0) return Memory.default.getI32(anchBase + 8); return errLen; } export function errorsPtr(): i32 { return errBase; } // Debug probe for the harness: 0 top state, 1 top c, 2 top a, 3 sp, 4 lexer pos, 5 token type, 6 emitPos. export function dbgProbe(i: i32): i32 { if (i == 0) return st(); if (i == 1) return fc(); if (i == 2) return fa(); if (i == 3) return sp(); if (i == 4) return ts; if (i == 5) return tt; if (i == 7) return failSite; if (i == 8 && sp() > 1) return belowSt(1); if (i == 9) return anchLen; if (i == 10) return watermark(); if (i == 11) return lastFlushed; if (i == 12) return outLen; if (i == 13 && anchLen > 0) return Memory.default.getI32(anchBase + 12); return emitPos; }