×ばつ callback/generator). Useful for pattern matching with extraction, constraint search, type inference, planners, expert systems, and policy checks. ESM, single runtime dependency (deep6). - Release notes · uhop/yopl Wiki"> ×ばつ callback/generator). Useful for pattern matching with..." /> ×ばつ callback/generator). Useful for pattern matching with..." />×ばつ callback/generator). Useful for pattern matching with..." />
Skip to content

Navigation Menu

Sign in
Sign up

Release notes

Eugene Lazutkin edited this page Jul 13, 2026 · 5 revisions

Release notes

Long-form per-release changelog for yopl. The repo's README.md carries cliff-notes for users skimming GitHub; this page is the canonical history with internal-change context, motivation, and credits.

1.4.0 — 2026年05月10日

Strict-Prolog front-end and IR-level performance dogfood. Largest follow-up since the 1.3.0 compiler MVP: a complete Prolog program parser shares the IR with the per-clause DSL; the IR + lowering + validation surface is now exposed via clean public subpaths; per-clause source positions thread through to validator issues and runtime fns; a parity bench surfaces the codegen opportunity.

Strict-Prolog tagged-template parser

  • **prolog\...`** (yopl/compile/prolog) — multi-clause program parser. Returns the lowered runtime Rulesdict by default with the parsed IR attached underSymbol.for('yopl.ir'); pass {lower: false}via the configurator to get the IR dict directly. Three call shapes (tag form, function form, configurator) lifted fromdollar-shell's bqSpawn` polymorphic-tag pattern.
  • **prologClause\...`** — single-clause variant; trailing .optional. Returns{name, head, body, helpers?}wherename` is the head functor.
  • Body operators. , (conjunction at 1000), ; (disjunction at 1100, mints $or_<N> helper rules), -> (if-then at 1050, mints $ite_<N> helpers), \+ (negation at 900, desugars to Call('not', [G])). Cut inside ; / -> branches scopes opaquely to the helper — transparent cut deferred until a real use case appears.
  • Goal aliases. Symbolic operators in body position auto-alias to yopl runtime predicates: =eq, \=notEq, ==eq, \==notEq, <lt, >gt, =<le, >=ge. Applied at goalize time, not the op table — so foo(X = Y) in arg position emits Compound('=', ...) while top-level X = Y in body emits Call('eq', ...).
  • op/3 and op/4 directives. ISO :- op(P, T, N). (3-arg) and yopl-canonical :- op(P, T, N, Pred). (4-arg with target alias). Per-invocation parser scope — directives mutate a clone of the input table, never the caller's.
  • Bare lowercase atoms in arg position parse as Lit(name) (string-atom literals), matching yopl's runtime convention. The per-clause clause\...`` DSL still rejects bare atoms.
  • All five built-in rule modules ported (system, comp, math, bits, logic) — the existing 535-test suite now exercises both front-ends through the same rules exports.
  • IR-equivalence dogfood (tests/test-prolog-dogfood.js) — for a representative sample of patterns, parses each rule both ways and deep-equals the IR (Js.factory compared by ===). Makes the "iter-2 IR ≡ iter-1 IR" design promise tactile.

prologFile / prologFileAsync

Filesystem-backed convenience loaders at yopl/compile/prolog/file.js:

import {prologFile, prologFileAsync} from 'yopl/compile/prolog/file.js';
const rules = prologFile(new URL('./family.pl', import.meta.url));
const [a, b] = await Promise.all([prologFileAsync(urlA), prologFileAsync(urlB)]);

Both wrap node:fs readFileSync / fs/promises.readFile. Lives in its own subpath so browser bundles without filesystem access don't pull in node:fs. Works in Node, Bun, and Deno (Deno via its Node-compat layer; same module loads unmodified in all three).

When sourceMap: true is passed, file defaults to the URL/path.

Public IR exposure

Three explicit subpath exports replace the older yopl/compile/<file>.js deep imports (which still resolve via the wildcard fallback):

Subpath Surface
yopl/compile IR constructors + lowerRule / lowerRules + validate / validateOrThrow + the IR symbol + open / soft / _ / any from deep6.
yopl/compile/clause.js Per-clause DSL: rule, clause.
yopl/compile/prolog Strict-Prolog tags: prolog, prologClause.
yopl/compile/prolog/file.js prologFile, prologFileAsync.

Full TypeScript typings for each. Backfilled the IR symbol export in yopl/compile/ir.d.ts (was emitted at runtime but missing from the typings).

Source-map support (opt-in)

Clause IR carries an optional source: {file?, line, col} field. The lexer always tracks positions (transient memory, GC'd after parse); attaching the metadata to clauses is opt-in via the prolog / prologClause configurator's sourceMap: true flag — defaults to false so production deployments skip the per-clause cost (~50 bytes / clause permanent memory; ~280 KB on a 5000-clause program).

When on:

  • Both front-ends populate Clause.source from the lexer's position tracker.
  • The file configurator option (silently ignored when sourceMap is off) populates source.file. prologFile / prologFileAsync default file to the URL.
  • lowerClause attaches source to the runtime fn as a non-enumerable property — error reporters can introspect it without disturbing JSON serialization of the rules dict.
  • validate includes source on per-clause Issue objects and appends a [file:line:col] suffix to the issue's message.

The per-clause clause\...`` DSL keeps source always-on (each tag is one clause; cost is negligible).

Arithmetic predicates

  • is/2 (math.js). evalExpr(term, env) recursively walks the RHS term tree, dispatching compound nodes to ARITH_BINARY / ARITH_UNARY lookup tables. Throws on insufficiently-instantiated subterms (matches ISO Prolog's instantiation_error). Op-table additions: is (700 xfx), mod (400 yfx), // (400 yfx). Ops covered: binary +, -, *, /, //, mod, min, max; unary +, -, abs, sqrt, floor, ceiling, round, sign.
  • =:= / =\= (math.js). Both sides evaluated as arithmetic expressions, then compared with ===. Distinct from unification (= / eq) — X =:= 3 + 4 evaluates RHS arithmetically; X = 3 + 4 unifies X with the compound term 3 + 4. Removed the previous '=:=''eq' and '=\\=''notEq' GOAL_ALIASES entries (which silently failed for compound expressions).

Wild() → fresh variable() lowering

lower.js now lowers Wild() to variable() per occurrence (a fresh deep6 Variable each time), matching standard Prolog's fresh-per-occurrence _ semantics. Bench-neutral on all three existing benches. The previous "yopl _ ≠ Prolog _" gap is closed — encodings can use _ as a fresh anonymous variable rather than naming every slot. See tests/test-zebra.js for the simplification this unlocked (50+ named workarounds → bare _).

Solver bug — backtracking lost variable bindings

Two-commit fix (fcdd545 v1, 235022c v2 — "bugfix of bugfix"). A pre-existing bug in prove: goals.terms[goals.index++] permanently advanced index; on backtracking-then-rematch the outer goals walked up via .next to a stale (downstream-advanced) index, silently skipping body goals that should have been re-processed under the new bindings. Surfaced via the zebra-puzzle dogfood; latent because the existing 450-test suite didn't exercise the specific pattern.

Fix v2 stamps restoreParent onto each new sub-goals at match time; the walk-up loop propagates the reset up the .next chain every time it climbs from child to parent. Regression test tests/test-solve-backtrack.js covers all four solvers (8 assertions). Tests grew 450 → 461 with the regression test + test-zebra.js added.

EnvMap end-to-end swap

Bumped deep6 ^1.3.0^1.3.1 (1.3.1 fixed instanceof Env narrowing in assemble / clone / deref that had been silently dropping EnvMap); swapped new Env()new EnvMap() in src/solve.js and the three driver files. Bench results (paired against the pre-swap baseline): ×ばつ speedup across bench-proof-loop.js and bench-drivers.js; ×ばつ on bench-inline-goals.js (after the cycle TDZ fix below).

ESM cycle TDZ fix

bench-inline-goals.js failed to load with Cannot access 'lowerRules' before initialization because its entry point pulled math.js first, triggering the math.js → lower.js → system.js → lower.js cycle from a position that left system.js's body needing lowerRules at module-eval time. Fix: extracted call, cut, fail, halt (the runtime primitives lower.js actually needs from system.js) into a leaf module src/rules/system-runtime.{js,d.ts} that depends only on deep6/env.js. Repointed lower.js's import. system.js re-exports the four primitives so the public API surface is preserved.

Parity bench (bench/bench-parity.js)

Encodes member/2 and append/3 four ways (hand-written, codegen, clause, prolog) and runs the same workloads through each via the same solve driver. Two assertions:

  • _clause_prolog within bench noise — IR equivalence made tactile at the runtime layer (the IR-level dogfood already asserts it at parse time).
  • _codegen_hand for wildcard-free rules (append10). Direct evidence that an IR→JS codegen lowering would close the ~13–28% compiler-overhead gap on member workloads. The residual on member is the wildcard-allocation difference (hand-written omits the value property; the current lower.js always emits a fresh variable()) — recoverable via a "smart codegen" that detects value-position wildcards.

The bench's job is to detect new gaps opening up, not to celebrate the existing compiler-overhead floor.

Classic Prolog test programs

Six new test files spanning the canonical shapes — Hanoi (Lucas 1883, recursion + move list); Wolf/Goat/Cabbage (Alcuin ~800 AD, state-space search); naïve quicksort (Hoare 1961 algorithm); Knight's Tour (al-Adli 9th c. — partial naïve + full Warnsdorff with cut-and-commit, 24 deterministic calls / ~13 ms for full ×ばつ5); N-queens (Bezzel 1848, 4 / 6 / 8 boards); ×ばつ4 mini-Sudoku (unique- and two-solution puzzles). All public-domain provenance documented in test headers; encodings original. Tests grew 461 → 535 (+74 assertions, +7 files).

The Knight's Tour with Warnsdorff demonstrates the heuristic-driven search pattern: inline-JS goal walks the visited cons-list, computes onward-degree per candidate, returns a sorted cons-list of pair compounds; Prolog commits with Sorted = [pair(X2, Y2) | _], !. Generalizable to any best-first search.

Tests

Tests grew 258 → 666 across iter-2 (+408): parse primitives, Pratt, parseClause, parseProgram, polymorphic-tag, body operators, disjunction, if-then-else, IR equivalence, source maps, public-export resolution, prologFile loading, classic-puzzle suite, regression coverage for the solver bug, plus the EnvMap swap stayed green through it all.

1.3.0 — 2026年05月09日

Rule compiler and JS-native bridge library. The largest feature release since the 1.2.0 ESM repackage. Rules can now be written declaratively via a tagged-template DSL; the IR is reusable across front-ends; a new predicate library bridges yopl unification to plain JS objects, arrays, Map, Set, and Date.

  • Rule compiler (src/compile/). Pure-data IR with 5 Term kinds (var, wildcard, literal, cons, compound) and 4 Goal kinds (call, cut, fail, js), plus Clause and Rule. Lowering is the only place that knows the runtime rule shape; front-ends emit IR.
    • Per-clause front-end (compile/clause.js). Tagged-template DSL:
      rule('member', 2)(clause`(X, [X | _])`, clause`(X, [_ | T]) :- member(X, T)`);
      Uppercase identifiers are logic Variables; _ is the wildcard; [X | T] is cons-list sugar; ${...} interpolates IR or auto-wraps primitives / functions.
    • Validator (compile/validate.js) catches arity-mismatch, call-arity-mismatch, undeclared-var, duplicate-rule, and (opt-in) unresolved-rule.
    • All five existing rule modules (system, comp, math, bits, logic) are dogfooded through the compiler.
  • Lit-walker. Lit(value) lowers per activation, descending into plain objects and arrays and substituting nested IR nodes (Var, Wild, Cons, Compound, Lit) with the activation's fresh logic Variables. So Lit({age: Var('A')}) doubles as a pattern matcher and a constructor. Maps, Sets, Dates, and Wrap-wrapped values pass through verbatim. IR detection is gated on the closed IR_KINDS set so user objects with a domain kind field aren't misread.
  • unifyOpts/3 (in system.js). unifyOpts(X, Y, Opts) runs deep6's unification with a per-call options bag ({openObjects, openArrays, openMaps, openSets, circular, loose, ignoreFunctions, signedZero, symbols}). The env's baseline options is restored before the goal returns. From clause source, pass options as ${Lit({openArrays: true})}.
  • yopl/rules/native.js — JS-native bridge predicates split out from system.js so the latter stays focused on generic logic-programming.
    • Type testsisArray, isMap, isSet, isDate. (isArray moved out of system.js to live with its siblings.)
    • ArrayarrayList(A, L) (bidir array ↔ cons list), arrayGet(A, I, X) (forward indexed lookup), arraySet(A, I, X, A2) (immutable single-index override; in-bounds replace or append-at-end), arrayLength(A, N).
    • MapmapEntries(M, Es) (bidir; both-bound case is order-independent), mapGet(M, K, V), mapHas(M, K).
    • SetsetItems(S, Items) (bidir), setHas(S, X).
    • DatedateTimestamp(D, Ms) (bidir; TZ-agnostic), dateComponents(D, C) and dateComponentsUTC(D, C) (bidir; component bag {year, month, day, hour, minute, second, ms}, month 0-based; pairs cleanly with the Lit-walker for partial-bag pattern matching).
  • bitOr reverse-mode identities. bitOr(0, Y, Z) and bitOr(X, 0, Z) now answer Z = Y / Z = X respectively via (0, Y, Y) and (X, 0, X) fact clauses, mirroring the existing bitXor identity-fact pattern. forwardTernary cuts only on the modes it actually resolves (verify-all-3 + forward) so identity facts can fire on under-specified reverse queries without duplicating forward solutions.
  • IR re-exports. yopl/compile/ir.js re-exports open, soft, and _ (also as any) from deep6 for fine-grained per-value match control: Lit(open({tag: 'a'})) locks subset matching regardless of env options; Lit(soft({...})) extends both sides.
  • Var() mints anonymous Variables. Var() (no name argument) returns {kind: 'var', name: Symbol()} — useful for IR fragments built in JS code that need a guaranteed-unique identity. Mirrors deep6's variable() idiom.
  • Bench scaffold (bench/). Top-level bench-{proof-loop,drivers, inline-goals}.js scripts using nano-benchmark as a dev dep. Run via npm run bench (one-shot) or npm run bench:watch.
  • Regex-based clause tokenizer modelled on stream-json's sticky- regex lexing — the V8 RegExp engine eats whole lexemes in native code rather than the JS interpreter walking char-by-char. Functionally equivalent to the previous lexer; faster on the multi-KB inputs the upcoming Prolog front-end will need.
  • Design docs. New dev-docs/compiler-ir.md (IR design, decisions, Lit-walker, unifyOpts/3, practical patterns) and dev-docs/native-objects.md (Array / Map / Set / Date roadmap, what's shipped vs postponed).
  • Junk audit. Removed AUTHORS (single-line duplicate of package.json#author) and CODEBASE.md (redundant with ARCHITECTURE.md).

Test count grew from 152 → 258 across the IR / lower / validate / clause front-end / native bridges / Lit-walker. npm test is green; npm run ts-check clean; npm run lint clean; bench/bench-proof-loop.js runs in the same ballpark as the pre-compiler hand-written rules.

1.2.0 — 2026年04月06日

ESM-only repackage and test/docs overhaul, the largest release since the extraction of deep6 at 1.1.0.

  • CommonJS build removed. yopl ships as pure ESM. CJS consumers can use Node's built-in dynamic import(); a smoke-test demonstrating the interop pattern lives at tests/test-cjs.cjs and runs via node tests/test-cjs.cjs.
  • TypeScript typings added. Hand-written .d.ts sidecars for every source module. npm run ts-check runs tsc --noEmit against the typings plus a dedicated TypeScript surface test (tests/test-types.ts).
  • Tests restructured. Per-driver test files (test-{solve,gen,async,asyncGen}.js) split out of a monolithic suite, plus dedicated test-system.js and test-rules.js for the predicate libraries. Run with npm test.
  • Simplified list creation. Helper exports — head, term, list, listHead, rest — replace the inlined object-literal patterns common in older yopl rule definitions. The wiki's Writing rules page documents the new shapes.
  • Bug fixes and perf improvements in the proof loop and the rule libraries.
  • Expanded docs. ARCHITECTURE.md, llms.txt, llms-full.txt, AGENTS.md, plus per-module wiki pages (solve, solvers-gen, solvers-async, solvers-asyncGen, rules-system, rules-comp, rules-math, rules-bits, rules-logic).

Earlier releases

Cliff-notes preserved verbatim from README.md for releases that predate this long-form page:

  • 1.1.4 — updated dependencies.
  • 1.1.3 — updated dependencies.
  • 1.1.2 — updated dependencies.
  • 1.1.1 — updated dependencies.
  • 1.1.0deep6 was extracted from this package and is now a dependency.
  • 1.0.1 — added the exports statement.
  • 1.0.0 — first 1.0 release.

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /