Skip to content

Latest commit

 

History

History
211 lines (171 loc) · 9.91 KB

File metadata and controls

211 lines (171 loc) · 9.91 KB

PARITY.md — expr-js vs expr-lang/expr v1.17.8

This document tracks the parity of expr-js (TypeScript) against its source of truth, expr-lang/expr v1.17.8 (Go).

Goal: expr-js is a maintainable TypeScript port of expr-lang/expr v1.17.8. Future upgrades are applied by diffing upstream Go and porting equivalent changes with minimal translation effort.

Version

Project Version
expr-lang/expr (Go, upstream) v1.17.8
expr-js (this port) 1.17.8

Folder parity

Every upstream package is mirrored 1:1 under src/ (file-per-file where the TypeScript type system allows):

Upstream Go expr-js TS Status
file/ src/file/ Ported (location, source, error)
ast/ src/ast/ Ported (node, print, visitor, find, dump)
parser/lexer/ src/parser/lexer/ Ported (lexer, state, token, utils)
parser/operator/ src/parser/operator/ Ported
parser/utils/ src/parser/utils/ Ported
parser/ src/parser/ Ported (parser.go -> parser.ts)
conf/ src/conf/ Ported (config)
builtin/ src/builtin/ Ported (builtin, lib, utils, validation, function)
checker/nature/ src/checker/nature/ Ported (nature, type, kind)
checker/ src/checker/ Ported (checker, info)
compiler/ src/compiler/ Ported (compiler.go -> compiler.ts)
vm/ src/vm/ Ported (vm, program, opcodes, utils)
vm/runtime/ src/vm/runtime/ Ported (runtime, helpers, sort, gotime)
optimizer/ src/optimizer/ Ported (all 15 files)
patcher/ src/patcher/ Ported (operator_override, with_context, with_timezone)
types/ src/types/ Ported
internal/ring/ src/internal/ring/ Ported
expr.go src/expr.ts Ported (public API)
docgen/ src/docgen/ Ported (docgen, markdown, index)
repl/ src/repl/ Ported (Node readline REPL)
debug/ src/debug/ Ported (headless; TUI = FORCED_DIVERGENCE)

Public API parity

Go-style names are preserved (source parity, Rule 3). camelCase aliases are added for JS ergonomics. Both styles work.

Go expr-js (Go-style) expr-js (alias)
Compile Compile compile
Run Run run
Eval Eval evaluate
parser.Parse Parse parse

Options ported: Env, AllowUndefinedVariables, Operator, ConstExpr, AsAny, AsKind, AsBool, AsInt, AsInt64, AsFloat64, DisableIfOperator, WarnOnAny, Optimize, DisableShortCircuit, Patch, Function, DisableAllBuiltins, DisableBuiltin, EnableBuiltin, WithContext, Timezone, MaxNodes.

Verification (this build)

Gate Command Result
Typecheck tsc -p tsconfig.json --noEmit PASS (0 errors)
Dual build npm run build (ESM + CJS + .d.ts) PASS
Unit tests tsx --test tests/unit/core.test.ts 24/24 PASS
Smoke tests tsx tests/smoke.ts 21/21 PASS
Parity tests tsx --test tests/parity.test.ts 118/118 PASS
Expr corpus tsx --test tests/go-parity/expr/expr.parity.test.ts 139/160 (21 N/A)
Checker corpus tsx --test tests/go-parity/checker/checker.parity.test.ts 15/45 (30 N/A)
Builtin corpus tsx --test tests/go-parity/builtin/builtin.parity.test.ts 137/157 (20 N/A)
Upstream literal tsx --test tests/upstream/**/*_test.ts 112/140 (28 N/A, 35 files)
Total 665 tests, 566 pass, 99 skip, 0 fail

Parity harness

Go is the source of truth. parity/gen (Go) evaluates expressions with the upstream engine and emits tagged JSON fixtures into parity/fixtures. The TS runner tests/parity.test.ts replays each fixture against expr-js and asserts equivalent output with int/float fidelity.

  • Regenerate fixtures: cd parity/gen && go run .
  • Run parity: tsx --test tests/parity.test.ts

Test classification

Category Bucket Count Notes
basics PASS 30 arithmetic, comparison, logic, ternary, range, in
numeric PASS 20 int/float semantics, modulo, exponent, casts
builtins PASS 20 len/max/min/sum/type/keys/values/sort/etc.
strings PASS 14 concat, contains/startsWith/endsWith, slicing, split
collections PASS 10 arrays, maps, indexing, concat
predicates PASS 14 filter/map/all/any/reduce/groupBy/sortBy
advanced PASS 10 let, pipes, #index, nested ternary
Total 118 All replayed against Go truth

Upstream Go-only tests classified NOT_APPLICABLE:

  • internal/testify, internal/spew, internal/difflib, internal/deref — vendored Go test infrastructure, not part of the expr language surface.
  • *_bench_test.go — performance benchmarks (out of scope; goal is semantic, not performance, parity).
  • test/deref, reflect/Stringer-specific tests — depend on Go pointer/interface deref and reflection with no JS analog.
  • test/fuzz (Go native fuzzing) — replaced conceptually by the fixture harness.

PASS_WITH_ADAPTER (host-env binding): tests that bind Go struct methods / interfaces require a JS object/method shim before the expression is portable. The engine supports this (methods on env objects are bound and callable); such cases are exercised via the env-based unit tests.

Documented divergences (Go -> TypeScript)

All divergences are forced by language differences; behavior is preserved.

1. Numeric model (bigint + number)

Go has int/int64/float64 as distinct types. TypeScript has one number (IEEE-754) plus bigint. expr-js maps Go int/int64 -> bigint, Go float64 -> number. This preserves Go semantics exactly:

  • integer ops stay integer: 2 + 3 == 5n
  • / always yields float64: 1 / 2 == 0.5
  • % is integer-only and truncates toward zero
  • ** yields float64
  • int64 precision is preserved (no float rounding for large ints)
  • int64 overflow wraps (emulated in runtime/helpers.ts)

2. Reflection -> TypeDescriptor

Go's checker uses reflect.Type/reflect.Value. TypeScript has no reflection. expr-js implements a TypeDescriptor system (src/checker/nature/type.ts) reproducing the subset of reflect.Type the checker needs: Kind, Elem, Key, struct fields, func in/out, identity. The VM operates on native JS values (Array, Map, object, string, function) instead of reflect.Value.

3. Typed/fast func dispatch tables

Go specializes calls via vm.FuncTypes (generated). expr-js has no typed dispatch tables: checker.TypedFuncIndex always returns [0,false] and IsFastFunc returns false, so all calls route through generic OpCall/OpCallN. Results are identical; only the internal opcode differs.

4. Errors

Go returns (value, error). expr-js throws (FileError) and returns the value directly. Error messages mirror Go strings where feasible.

5. time.Time / time.Duration

Modeled by GoTime/GoDuration (src/vm/runtime/gotime.ts) wrapping epoch-ms and bigint nanoseconds. time.Location is an opaque marker.

6. Generated arithmetic helpers

Go generates ~3700 lines of per-type-pair arithmetic (vm/runtime/helpers[generated].go). expr-js collapses this to compact bigint/number dispatch in src/vm/runtime/helpers.ts — same behavior, far less code, because the JS numeric domain has two members instead of 13.

7. Reserved-name renames (minimal)

TypeScript reserved/global shadowing is tolerated where the Go name must be preserved for diff-based maintenance (e.g. unescape, Function, Map, Array, String in types). These are lint advisories only; tsc accepts them. No semantic rename was made.

Non-core packages (ported)

Package TS files Status
docgen/ src/docgen/docgen.ts, markdown.ts, index.ts Ported. Full functional parity. See NON_CORE_PARITY.md §1.
repl/ src/repl/repl.ts + src/test/fuzz/fuzz_env.ts Ported. Node readline REPL. History persistence = FORCED_DIVERGENCE. See NON_CORE_PARITY.md §2.
debug/ src/debug/debugger.ts Ported (headless). Data side (disassembly + execution + stack) fully ported. Interactive TUI (tview/tcell) = FORCED_DIVERGENCE. See NON_CORE_PARITY.md §3.

PARITY STATUS (audit, 2026-06-08) — HONEST CLASSIFICATION

This project is high functional parity + ~complete source-level file parity, with all divergences classified. It is NOT claimed as byte-for-byte bytecode parity (see DESIGN_DECISION B1/B2).

Source-level file parity

  • Core language + runtime: every non-test, non-generated Go file has a TS counterpart. See tests/go-parity/FILE_MAPPING.md.
  • Closed this audit: conf/env.go, checker/nature/utils.go, patcher/value/value.go, internal/deref/deref.go (previously gaps).
  • Split (3) / collapsed (1) / not-ported(func_types): all classified FORCED_DIVERGENCE or roadmapped — see tests/go-parity/DIVERGENCES.md.

Upstream test corpus (Go = source of truth)

Corpus Source Total Pass Pass w/ adapter N/A Result
expr expr_test.go TestExpr 160 88 51 21 139/160 evaluated pass
checker checker_test.go TestCheck_error 45 0 15 30 15/45 evaluated pass
builtin builtin_test.go TestBuiltin 157 99 38 20 137/157 evaluated pass
eval (curated) parity/gen 118 118 0 0 118/118
unit hand-written 24 24 24/24

Machine-readable: tests/go-parity/CLASSIFICATION.json.

Engine bugs found & fixed by the corpus

  1. in over constant array (Set membership missing in runtime.In)
  2. env function rejected "doesn't return value" (typeOfValue 0-output func)
  3. duration * float returned duration instead of float64

See tests/go-parity/ENGINE_BUGS.md.

Honest label

High functional parity, near-complete source-level file parity, every divergence classified (FORCED_DIVERGENCE / DESIGN_DECISION / NOT_IMPLEMENTED_YET). Not byte-for-byte bytecode parity (typed dispatch + generated helpers are documented design divergences).