@bitcode/expr-js is a source-parity TypeScript port of the Go library
expr-lang/expr.
expr-js v1.17.8 tracks expr-lang/expr v1.17.8
- Goal: semantic / functional parity — the same expression evaluates to the same value, and the same programs are accepted/rejected as in Go.
- Non-goals: bytecode parity (identical opcodes/disassembly) and performance parity. These do not affect correctness for the intended use (cross-target expression evaluation, e.g. Mitosis-generated UI components).
Correctness is proven by a parity harness: a Go generator runs each input
through the real expr-lang/expr (the source of truth) and emits fixtures; the
TypeScript port runs the same inputs and the results are compared.
npm run parity:gen # regenerate fixtures from Go (requires Go toolchain)
npm run test:parity # run the TS port against Go fixtures| Area | Status | Notes |
|---|---|---|
| Lexer / Parser | ✅ | full grammar, same tokens |
| AST + visitor | ✅ | identical node names |
| Compiler | ✅ | generic (non-specialized) opcodes |
| Bytecode VM | ✅ | stack machine, same opcodes |
| Optimizer | ✅ | all 12 passes ported |
| Checker | ✅ | opt-in, descriptor-based (see below) |
| Types API | ✅ | types.Map/Array/TypeOf/... |
| Patcher | ✅ | Operator / WithContext / WithTimezone |
| Builtins (64) | ✅ | string/math/array/map/bit/json/base64 |
| Date / Duration | ✅ | Go reference-time layouts, time.Time methods |
| Public API options | ✅ | Env/As*/Optimize/Patch/Operator/Timezone/... |
Following the principle "every upstream test/example is classified":
| Source | Status | Notes |
|---|---|---|
testdata/examples.md |
✅ pass | real upstream example programs |
parser/lexer behavior |
✅ pass | via synthetic + extracted fixtures |
test/pipes/pipes_test.go |
✅ pass | extracted expressions |
expr_test.go (portable subset) |
✅ pass | scalar/nested/array/collection |
| Checker behavior | ✅ pass | 155 accept/reject fixtures vs Go |
expr_test.go TestExpr (method-call corpus) |
✅ pass | via mock-env adapter (fields + methods) |
reflect / fmt.Stringer-specific tests |
N/A | Go-specific, not portable to JS |
docgen/ |
N/A | non-core tooling, does not affect eval |
| benchmarks | N/A | performance, not a parity goal |
| Suite | Result |
|---|---|
| Eval parity (runtime results) | 371 / 371 |
| Checker error-parity (accept/reject) | 155 / 155 |
| Mock-env parity (TestExpr corpus) | 55 / 55 |
| Ported unit tests | 23 / 23 |
These numbers reflect the currently extracted corpus. The pending
TestExpr method-call corpus is the remaining parity work that could surface
real divergences; everything classified N/A is intentionally out of scope.
- Numeric model:
bigint= Goint/int64,number=float64. Safe integers are normalized tonumberat the publicrun()boundary; values beyond 2^53 staybigintto preserve int64 precision. %(modulo) on env-provided numbers: JS env numbers carry no int/float tag, so integer-valued operands are treated as integers (keepsfilter(nums, # % 2 == 0)working); genuinely fractional floats are rejected.- Map key/value iteration order: Go map iteration is non-deterministic;
order-dependent
keys()/values()assertions are not part of parity. - Static checker: opt-in and descriptor-based (JS has no Go reflection). Runtime results are unaffected whether or not the checker is enabled.
When a new upstream version is released:
git diff v<old>..v<new>in theexpr-lang/exprrepo.- Apply the same changes to the matching files/functions here (names mirror upstream — see the README versioning policy).
- Run
npm run parity:gen && npm run test:parityto confirm parity holds. - Update the version and this document.