/** * Portable-scalar expression evaluator — the cross-target-safe core shared by * the body-statement binding contracts (`let`, `assign`, and the `while` * condition). * * The portable scalar domain is the subset of values TS and Python agree on * observably: string, finite number, boolean, null. Expressions are kept * deliberately small — literals, identifiers resolving to portable scalars, * arithmetic over numbers, comparisons over same-typed scalars, boolean / * nullish operators over portable truthiness, and conditional expressions. * Same-type guards (`sameType`) keep the evaluator out of the divergent * corners (Python `bool == int`, mixed-type ordering, etc.); out-of-domain * inputs throw, and callers translate that throw into a precondition failure. * * Extracted from the `let` contract so `assign` and `while` reuse one * evaluator instead of forking subtly different copies. There is intentionally * no shared evaluator for the collection contracts (`for` / `lambda` keep their * own minimal local `evalValue`) — this module is scoped to scalar bindings. */ import type { ValueIR } from '../../value-ir.js'; import type { SemanticEnv } from './index.js'; export type PortableScalar = string | number | boolean | null; export declare const IDENT_RE: RegExp; export declare const RESERVED_NAMES: Set; /** True when `name` is a syntactically valid, non-reserved, non-internal binding name. */ export declare function isPortableBindingName(name: unknown): name is string; export declare function isPortableScalar(value: unknown): value is PortableScalar; /** Brand symbol marking a runtime Decimal value. Symbol-keyed so it can never * collide with a user JSON property and is dropped by structural JSON cloning. */ export declare const DECIMAL_VALUE_TAG: unique symbol; /** The runner's tagged runtime Decimal value: a frozen object carrying the brand * and the canonical rendered STRING. NOT a portable scalar (see above). */ export interface DecimalValue { readonly [DECIMAL_VALUE_TAG]: true; readonly canonical: string; } /** Build a tagged runtime Decimal value from its canonical rendered string. */ export declare function makeDecimalValue(canonical: string): DecimalValue; /** Brand symbol marking a runtime CAUGHT-ERROR value (see `portable-error.ts`). */ export declare const CAUGHT_ERROR_TAG: unique symbol; /** The runner's tagged caught-error value: a frozen object carrying the brand, * the canonical error `kind`, and the evaluated literal `message`. NOT a * portable scalar. */ export interface CaughtErrorValue { readonly [CAUGHT_ERROR_TAG]: true; readonly kind: string; readonly message: string; } /** True iff `value` is a tagged caught-error value. */ export declare function isCaughtErrorValue(value: unknown): value is CaughtErrorValue; /** True iff `value` is a tagged runtime Decimal value produced by * {@link makeDecimalValue}. */ export declare function isDecimalValue(value: unknown): value is DecimalValue; export declare function assertPortableScalar(value: unknown, label: string): PortableScalar; export declare function portableTruthy(value: PortableScalar): boolean; export declare function sameType(a: PortableScalar, b: PortableScalar): boolean; export declare function evalPortableValue(node: ValueIR, env: SemanticEnv): PortableScalar; export declare function coerceToString(val: PortableScalar): string; export declare function evalPortableBinary(node: Extract, env: SemanticEnv): PortableScalar; export declare function evalNumberBinary(op: string, left: PortableScalar, right: PortableScalar): PortableScalar; export declare function evalOrderedComparison(op: string, left: string | number, right: string | number): boolean; /** True iff `node` is a `Decimal.(...)` member-call on the bare `Decimal` * namespace identifier — the EXACT recognition shape the emitters use * (`callee.kind === 'member'`, `callee.object` is `ident 'Decimal'`). A user * binding named `decimal` or a member chain is NOT matched. */ export declare function isDecimalNamespaceCall(node: ValueIR): node is Extract; export declare function decimalNamespaceMethod(node: ValueIR): string | null; /** True iff `error` is the SHARED canonical Decimal-literal scale fail-close (the one * {@link assertPortableDecimalLiteral} throws). The `expression-v1` precondition uses * this to RE-ADMIT that specific failure to effects — so the byte-identical fail-close * message surfaces on the production path — while abstaining on EVERY other throw * (e.g. an unbound / non-Decimal variable operand → "binding is not a Decimal value"). * Matching the exported {@link DECIMAL_SCALE_FAILCLOSE} prefix is precise enough: it is * the only message family carrying that prefix, and slice-1's fail-close regression test * guards against a reword silently flipping re-admit into abstain. */ export declare function isCanonicalDecimalLiteralFailure(error: unknown): boolean; export declare function isRunnerNativeDecimalFailClose(error: unknown): boolean; export declare function evalRunnerNativeDecimalScalarCall(node: Extract, env: SemanticEnv): PortableScalar | undefined; /** True iff `node` is a STRUCTURALLY-EVALUABLE runner-native Decimal expression — * i.e. either a Decimal VALUE producer (`Decimal.of/add/sub/mul/neg/abs/div/mod/pow(...)`) or a Decimal * comparator (`eq/ne/lt/lte/gt/gte/cmp`) whose operand tree is made of * structurally-valid Decimal operands. This is the recursive admission predicate the * runner routes on: it must accept EXACTLY the inputs {@link evalDecimalNode} * can reach without a STRUCTURAL throw (an arity / shape / out-of-slice error). On a * `true` node, {@link evalDecimalExpression} either succeeds, throws the canonical * `Decimal.of` fail-close, OR — once VARIABLE operands exist — throws a binding * resolution error ("binding is not a Decimal value") for an `ident` operand that is * unbound or not a tagged Decimal. The `expression-v1` precondition distinguishes the * two non-success throws: it RE-ADMITS the canonical fail-close (see * {@link isCanonicalDecimalLiteralFailure}) so effects surfaces the byte-identical * message, and ABSTAINS on the binding error — so the over-accept of `ident` operands * is fail-SAFE, never a divergent value. * * It deliberately does NOT check the literal's CANONICAL-ness: a non-canonical * `Decimal.of("1.10")` is structurally valid → `true`, and effects fails closed * with the shared canonical-scale message (mirroring the emitters, which compile * the call but throw at the lowering boundary). * * Examples — `true`: `Decimal.of("1.5")`, `Decimal.of("1.10")`, * `Decimal.add(Decimal.of("1"), Decimal.of("2"))`, arbitrarily nested * producers, `Decimal.eq(d, Decimal.of("1"))`. `false`: `Decimal.add(1, 2)` * (non-Decimal operand), `Decimal.of("1","2")` (arity), `Decimal.of()` (arity), * `String(n)` / `1 + 2` (not a Decimal namespace call). */ export declare function isDecimalExpression(node: ValueIR): boolean; /** True iff the ROOT expression is a Decimal VALUE producer the runner binds as a * tagged Decimal (`Decimal.of/add/mul(...)`), as opposed to a comparator whose * result is already a portable scalar. */ export declare function isDecimalValueExpression(node: ValueIR): boolean; /** Evaluate a `Decimal.(...)` expression through the runner's native * Decimal evaluation and render the result to its KERN-canonical STRING — the * runner's third "leg" of the decimal differential oracle. Computes on a LOCAL * cloned constructor pinned to the canonical context (precision 28, * ROUND_HALF_EVEN, modulo ROUND_DOWN) — NEVER mutating the global decimal.js * constructor — and renders via the kernel's {@link kernDecimalStr}, so the output * is byte-identical to both emitted legs. A non-canonical `Decimal.of` literal * fails closed with the EXACT shared message. */ export declare function evalDecimalExpression(node: ValueIR, env?: SemanticEnv): string;