Sign In

yopl

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

yopl - npm Package Compare versions

Comparing version
1.1.4
to
1.2.0
+108
AGENTS.md
# AGENTS.md — yopl
> `yopl` is an ES6 mini-library implementing a Prolog-style logic solver. It supports rule-based
> logic programming with both callback and generator drivers. Its only runtime dependency is
> [`deep6`](https://www.npmjs.com/package/deep6), itself a zero-dependency library that provides
> the unification engine.
## AI Documentation
- **Architecture:** [ARCHITECTURE.md](./ARCHITECTURE.md) — module map, dependency graph, algorithm details
- **Quick API:** [llms.txt](./llms.txt) — concise API reference for LLMs
- **Full API:** [llms-full.txt](./llms-full.txt) — complete API reference with examples
- **Codebase Quick Ref:** [CODEBASE.md](./CODEBASE.md) — one-liner, entry points, key patterns
- **Usage:** [README.md](./README.md) — installation and examples
For detailed usage docs see the [wiki](https://github.com/uhop/yopl/wiki).
## Setup
```bash
git clone git@github.com:uhop/yopl.git
cd yopl
npm install
```
## Commands
- **Install:** `npm install`
- **Test:** `npm test`
- **Debug:** `npm run debug` — run tests with Node inspector
- **Type check:** `npm run ts-check` — TypeScript type checking
- **Lint:** `npm run lint` — Prettier check
- **Lint fix:** `npm run lint:fix` — Prettier write
## Project structure
```
yopl/
├── package.json # Package config
├── src/ # ES6 source code
│ ├── solve.js # Solver core / main entry
│ ├── solvers/ # Alternative driver styles
│ │ ├── gen.js # Synchronous generator-based solver
│ │ ├── async.js # Async callback-based solver
│ │ └── asyncGen.js # Async generator-based solver
│ └── rules/ # Built-in rule library
│ ├── logic.js # Logical connectives
│ ├── comp.js # Comparison rules
│ ├── math.js # Arithmetic rules
│ ├── bits.js # Bitwise rules
│ └── system.js # System / utility rules
├── tests/ # Test files (grouped test-*.js, dispatched by tests.js)
└── .github/ # CI workflows, funding, dependabot
```
## Code style
- **ES6 modules** (`"type": "module"` in package.json).
- **Single runtime dependency.** Only `deep6` is allowed in `dependencies`.
- **Prettier** for formatting (see `.prettierrc`): 2-space indent, single quotes, semicolons required, no trailing commas.
## Critical rules
- **ES6 modules.** Use `import`/`export` syntax in source.
- **Single runtime dependency.** Do not add packages to `dependencies` other than `deep6`.
- **Do not modify or delete test expectations** without understanding why they changed.
- **Do not add or remove comments** unless explicitly asked.
- **Keep `src/` in sync.** Run `npm test` and `npm run lint:fix` after changes.
## Architecture
- **Solver core** (`src/solve.js`) — the synchronous callback-style solver and the main public
entry. Drives unification (via `deep6`) over a rule database using an explicit goal stack.
- **Solver drivers** (`src/solvers/`) — alternative execution strategies sharing the same proof
loop:
- `gen.js` — synchronous generator yielding one `Env` per solution.
- `async.js` — async callback-based driver for `await`-bearing predicates.
- `asyncGen.js` — async generator combining the two.
- **Rule library** (`src/rules/`) — built-in predicates:
- `system.js` — helpers (`head`, `term`, `list`, `listHead`, `rest`) and control predicates
(`call`, `cut`, `fail`, `halt`, `isBound`, `not`, `true`, `eq`, `once`, `map`, `filter`,
`foldl`, `foldr`, …).
- `comp.js` — comparisons (`lt`/`le`/`gt`/`ge`, `nz`).
- `math.js` — arithmetic (`add`/`sub`/`mul`/`div`/`neg`).
- `bits.js` — bitwise (`bitAnd`/`bitOr`/`bitXor`/`bitNot`).
- `logic.js` — boolean logic (`logicalAnd`/`logicalOr`/`logicalXor`/`logicalNot`).
Per-module documentation lives in the [wiki](https://github.com/uhop/yopl/wiki).
## Writing tests
- Tests live in `tests/test-*.js`, dispatched via `tests/tests.js`.
- The test harness (`tests/harness.js`) is the same lightweight one used by `deep6`. Each test
module exports a default array of named test functions; the dispatcher concatenates them and
runs them with `runAllTests`.
- Use the `submit` / `TEST` helpers from `harness.js` to record assertions:
`eval(TEST('unify(result, [1, 2, 3])'));`.
- Shared list/timeout helpers live in `tests/helpers.js`.
- Run with `npm test`. The CommonJS interop smoke test lives at `tests/test-cjs.cjs` and is run
manually with `node tests/test-cjs.cjs`.
- TypeScript typings are exercised separately by `tests/test-types.ts` via `npm run ts-check`.
## When reading the codebase
- Start with this file ([AGENTS.md](./AGENTS.md)) for rules and constraints.
- Consult [ARCHITECTURE.md](./ARCHITECTURE.md) for module relationships.
- `src/solve.js` is the core — read it first.
- Run `npm test` and `npm run lint:fix` after any changes.
# Architecture
`yopl` is an ES6 mini-library implementing a Prolog-style logic solver. It supports rule-based
logic programming with multiple driver styles. Unification is delegated to
[`deep6`](https://www.npmjs.com/package/deep6) — yopl's only runtime dependency, itself a
zero-dependency library.
## Project layout
```
package.json # Package config
src/ # ES6 source code
├── solve.js # Solver core / main entry
├── solvers/ # Alternative driver styles
│ ├── gen.js # Synchronous generator-based solver
│ ├── async.js # Async callback-based solver
│ └── asyncGen.js # Async generator-based solver
└── rules/ # Built-in rule library
├── logic.js # Logical connectives
├── comp.js # Comparison rules
├── math.js # Arithmetic rules
├── bits.js # Bitwise rules
└── system.js # System / utility rules
tests/ # Tests grouped in test-*.js, dispatched by tests.js
.github/ # CI workflows, funding, dependabot
```
## Core concepts
### Goals, terms, rules
A **goal** is one of:
- a string — the name of a rule with no arguments;
- a structured term `{name, args}` — invoke `name` with the given argument vector;
- a JavaScript function `(env, goals, stack) => boolean | GoalFrame` — an inline predicate
evaluated directly by the solver.
A **rule** is a function (or array of functions for a disjunction) that receives a fresh batch
of logical variables and returns an array of terms. The first element is the rule's _head_
(`{args: [...]}`); the rest are body goals evaluated in order.
A **rule database** is a `{name: rule}` object. Spread the built-in rule libraries into your own
database to compose them: `{...systemRules, ...mathRules, myRule: …}`.
### Solver core
`src/solve.js` is the heart of yopl. It evaluates goals against a rule database, delegating
unification to `deep6`. The proof loop is non-recursive — it maintains an explicit stack of
frames so deep proofs do not blow the JS call stack — and tracks alternatives via choice-point
frames so that backtracking is just popping the stack and reverting the environment.
The synchronous callback-style `solve` is the main entry point. The `src/solvers/` modules wrap
the same proof loop in alternative drivers that yield (or `await`) per solution.
### Drivers
The `src/solvers/` modules wrap the core in alternative execution styles:
- **`gen.js`** — synchronous generator yielding solutions on demand.
- **`async.js`** — async callback-based driver for I/O-bound rules.
- **`asyncGen.js`** — async generator that combines both.
### Built-in rules
`src/rules/` provides a small standard library of predicates:
- **`logic.js`** — logical connectives (and/or/not, if-then-else, etc.).
- **`comp.js`** — comparison and ordering predicates.
- **`math.js`** — arithmetic predicates.
- **`bits.js`** — bitwise predicates.
- **`system.js`** — system/utility predicates.
## Module dependency graph
```
src/solve.js ── deep6/unify.js (unify, Env, variable)
src/solvers/gen.js ── deep6/unify.js
src/solvers/async.js ── deep6/unify.js
src/solvers/asyncGen.js ── deep6/unify.js
src/rules/system.js ── deep6/env.js (_, isVariable)
src/rules/comp.js ── deep6/env.js (_), src/rules/system.js
src/rules/math.js ── deep6/env.js (_), src/rules/system.js
src/rules/bits.js ── deep6/env.js (_), src/rules/system.js
src/rules/logic.js ── deep6/env.js (_), src/rules/system.js
```
The solver drivers are independent — none of them imports the others, and none of them depends
on the rule library. The rule modules depend only on `deep6` and on `system.js` for shared
helpers.
## Import paths
```js
// Main solver
import solve from 'yopl';
// Drivers
import solveGen from 'yopl/solvers/gen.js';
import solveAsync from 'yopl/solvers/async.js';
import solveAsyncGen from 'yopl/solvers/asyncGen.js';
// Rules
import logic from 'yopl/rules/logic.js';
import comp from 'yopl/rules/comp.js';
import math from 'yopl/rules/math.js';
import bits from 'yopl/rules/bits.js';
import system from 'yopl/rules/system.js';
```
## Testing
- **Run all:** `npm test`
- **Single dispatcher:** `node tests/tests.js`
- **Debug:** `npm run debug` (Node inspector)
# yopl — Full API Reference
`yopl` is an ES6 mini-library implementing a Prolog-style logic solver in JavaScript. It
provides rule-based logic programming with multiple driver styles and a built-in rule library.
Unification is delegated to [`deep6`](https://www.npmjs.com/package/deep6) — yopl's only
runtime dependency, itself a zero-dependency library.
## Concepts
- **Goal** — one of: a string (rule name with no args), a structured term `{name, args}`, or
an inline JS function `(env, goals, stack) => boolean | GoalFrame`.
- **Rule** — a function (or array of functions for a disjunction) that receives a fresh batch
of logical variables and returns an array of terms. The first element is the rule head
(`{args: [...]}`); the rest are body goals.
- **Rule database** — a `{name: rule}` object. Spread the built-in libraries to compose them.
## Solvers
### `yopl` — `src/solve.js`
```js
import solve from 'yopl';
solve(rules, name, args, env => { /* per solution */ });
```
Synchronous callback-style solver. Main public entry point.
### `yopl/solvers/gen.js`
```js
import gen from 'yopl/solvers/gen.js';
for (const env of gen(rules, name, args)) { /* … */ }
```
Synchronous generator. Supports lazy enumeration — break out of the loop to stop after the
first match.
### `yopl/solvers/async.js`
```js
import asyncSolve from 'yopl/solvers/async.js';
await asyncSolve(rules, name, args, async env => { /* … */ });
```
Async callback solver — for predicates that need to `await`.
### `yopl/solvers/asyncGen.js`
```js
import asyncGen from 'yopl/solvers/asyncGen.js';
for await (const env of asyncGen(rules, name, args)) { /* … */ }
```
Async generator solver.
## Rule libraries
### `yopl/rules/system.js`
Helpers and control predicates.
**Term builders:**
- `head(...args)` → `{args}`. Build a rule head.
- `term(name, ...args)` → `{name, args}`. Build a structured goal term.
- `list(...args)` → cons-list. Wrap the last arg with `rest()` to splice an explicit tail.
- `listHead(...args)` → cons-list whose last argument is the tail directly. Requires ≥ 2 args.
- `rest(value)` → `Tail` marker.
**Inline goal builders:**
- `fail` — always fails.
- `halt` — aborts the proof search by clearing the driver stack.
- `cut(sys)` — Prolog-style cut. Pass the trailing `...sys` rest-args from the rule definition.
- `call(X)` — meta-call. `X` may be a string, a structured term, or a bound variable.
- `isBound(...vars)` — succeeds when every supplied variable is bound.
**`rules` object** — spreadable rule database:
- *Type tests:* `isVar`, `isNonVar`, `isNumber`, `isString`, `isNull`, `isUndefined`, `isArray`.
- *Equality:* `eq` (alias `unify`), `notEq` (alias `notUnifiable`).
- *Control:* `call`, `not`, `isUnifiable`, `conjunction`, `disjunction`, `true`, `once`.
- *Extended logic:* `counterExample`, `implies`.
- *Higher-order:* `map`, `filter`, `foldl`, `foldr`, `compose`, `converse`.
### `yopl/rules/comp.js`
Comparisons. `lt`, `le`, `gt`, `ge` operate on bound, comparable values (number or string)
of the same type. `nz` succeeds when its argument is non-zero.
### `yopl/rules/math.js`
Arithmetic, all reversible: `add`, `sub`, `mul`, `div`, `neg`. Each rule defines a relation
between its arguments, and any one missing operand can be solved for.
### `yopl/rules/bits.js`
Bitwise: `bitAnd`, `bitOr` (forward only), `bitXor`, `bitNot` (reversible).
### `yopl/rules/logic.js`
Boolean logic: `logicalAnd`, `logicalOr`, `logicalXor`, `logicalNot`. Arguments are coerced
with `!!`.
## Examples
### List membership
```js
import {variable} from 'deep6/env.js';
import assemble from 'deep6/traverse/assemble.js';
import solve from 'yopl';
const rules = {
member: [
(V, X) => [{args: [{value: V, next: X}, V]}],
(V, X) => [{args: [{next: X}, V]}, {name: 'member', args: [X, V]}]
]
};
const list = {value: 1, next: {value: 2, next: {value: 3, next: null}}};
const X = variable('X');
solve(rules, 'member', [list, X], env => console.log(assemble(X, env)));
// 1, 2, 3
```
### Append
```js
import gen from 'yopl/solvers/gen.js';
import {variable} from 'deep6/env.js';
import assemble from 'deep6/traverse/assemble.js';
const rules = {
append: [
Y => [{args: [null, Y, Y]}],
(X, Y, Z, V) => [
{args: [{value: V, next: X}, Y, {value: V, next: Z}]},
{name: 'append', args: [X, Y, Z]}
]
]
};
const X = variable('X'), Y = variable('Y');
const target = {value: 1, next: {value: 2, next: {value: 3, next: null}}};
for (const env of gen(rules, 'append', [X, Y, target])) {
console.log(assemble(X, env), '++', assemble(Y, env));
}
```
## Conventions
- ES6 modules; `"type": "module"`.
- Single runtime dependency: `deep6`.
- 2-space indent, single quotes, semicolons (Prettier).
- Pure ESM source. CommonJS consumers can use Node's built-in dynamic `import()` —
see `tests/test-cjs.cjs` for a smoke test.
# yopl — Quick API Reference
`yopl` is an ES6 mini-library implementing a Prolog-style logic solver. Unification is delegated
to `deep6`, yopl's only runtime dependency (a zero-dependency library).
## Entry points
- `yopl` (`src/solve.js`) — synchronous callback-style solver. `solve(rules, name, args, callback)`.
- `yopl/solvers/gen.js` — synchronous generator solver. `gen(rules, name, args)` → `Generator<Env>`.
- `yopl/solvers/async.js` — async callback solver. `asyncSolve(rules, name, args, async callback)`.
- `yopl/solvers/asyncGen.js` — async generator solver. `asyncGen(rules, name, args)` → `AsyncGenerator<Env>`.
## Rule libraries
All rule modules export a spreadable `rules` object:
- `yopl/rules/system.js` — helpers (`head`, `term`, `list`, `listHead`, `rest`) and control predicates (`call`, `cut`, `fail`, `halt`, `isBound`). The `rules` export adds `eq`/`unify`, `notEq`/`notUnifiable`, `not`, `true`, `once`, `conjunction`, `disjunction`, `isVar`/`isNonVar`/`isNumber`/`isString`/`isNull`/`isUndefined`/`isArray`, `counterExample`, `implies`, and the higher-order predicates `map`, `filter`, `foldl`, `foldr`, `compose`, `converse`.
- `yopl/rules/comp.js` — `lt`, `le`, `gt`, `ge`, `nz`.
- `yopl/rules/math.js` — `add`, `sub`, `mul`, `div`, `neg` (each reversible).
- `yopl/rules/bits.js` — `bitAnd`, `bitOr`, `bitXor`, `bitNot`.
- `yopl/rules/logic.js` — `logicalAnd`, `logicalOr`, `logicalXor`, `logicalNot`.
## Conventions
- ES6 modules; `"type": "module"`.
- Single runtime dependency: `deep6`.
- Use `assemble(variable, env)` from `deep6/traverse/assemble.js` to extract solution values.
- A rule body returns an array of terms whose first element is the head and whose remaining
elements are body goals. A goal is a string, a `{name, args}` term, or an inline
`(env, goals, stack) => boolean | GoalFrame` function.
---
name: use-yopl
description: Use the yopl Prolog-style logic engine in a JavaScript/TypeScript project. Use when adding rule-based search, pattern matching with extraction, constraint solving, type inference, planning, or expert-system style logic to a project that depends on `yopl`.
---
# Use yopl
`yopl` is an embeddable Prolog-style logic engine for JavaScript. It gives you
declarative rules, unification (via `deep6`), backtracking search, and four
solver drivers (sync/async × callback/generator). It is ES-modules only and has
a single runtime dependency (`deep6`).
When in doubt, the canonical references are
`node_modules/yopl/llms.txt` (concise) and `node_modules/yopl/llms-full.txt`
(complete with examples). Read them before guessing.
## When to reach for yopl
Pick yopl when the problem is naturally expressed as _rules + queries_:
- Pattern matching where you also need to **extract** parts of the input.
- Multi-solution search with backtracking (planners, constraint puzzles, layout).
- Type inference, dataflow analysis, simple theorem proving.
- Policy / authorization rules.
- Anything you'd otherwise hand-roll as nested `if`/`for` over a small relational model.
Do **not** reach for yopl for plain validation, simple lookups, or anything a
single SQL query / array filter handles cleanly.
## Installation
```bash
npm install yopl
```
`yopl` requires Node 18+ (or modern Deno / Bun) and `"type": "module"` (or
`.mjs` files).
## Imports
```js
// Default sync callback solver
import solve from 'yopl';
// Alternative drivers
import gen from 'yopl/solvers/gen.js'; // sync generator → yields Env per solution
import asyncSolve from 'yopl/solvers/async.js'; // async callback (await-able predicates)
import asyncGen from 'yopl/solvers/asyncGen.js'; // async generator
// Built-in rule libraries (each exports a spreadable `rules` object)
import {rules as systemRules} from 'yopl/rules/system.js';
import {rules as compRules} from 'yopl/rules/comp.js';
import {rules as mathRules} from 'yopl/rules/math.js';
import {rules as bitsRules} from 'yopl/rules/bits.js';
import {rules as logicRules} from 'yopl/rules/logic.js';
// Variables and value extraction come from deep6
import {variable as v} from 'deep6/unify.js';
import assemble from 'deep6/traverse/assemble.js';
```
## Defining rules
A _rule database_ is a plain object keyed by `'name/arity'`. Each entry is one
clause function (or an array of clause functions, tried in order):
```js
const rules = {
// member(X, [X | _]).
// member(X, [_ | T]) :- member(X, T).
'member/2': [(V, X) => [{args: [{value: V, next: X}, V]}], (V, X, T) => [{args: [{value: V, next: X}, T]}, {name: 'member/2', args: [X, T]}]]
};
```
A clause is a function whose parameters become **fresh logical variables** for
that invocation. It returns an array of _terms_:
- The **first** term is the rule head: `{args: [...]}`.
- The remaining terms are body goals: either `{name: 'foo/N', args: [...]}` to
call another rule, or an inline guard `env => boolean` for native checks.
## Composing the rule database
Combine your own rules with the built-ins via spread:
```js
import {rules as systemRules} from 'yopl/rules/system.js';
import {rules as compRules} from 'yopl/rules/comp.js';
import {rules as mathRules} from 'yopl/rules/math.js';
const rules = {
...systemRules,
...compRules,
...mathRules,
'positive/1': X => [{args: [X]}, {name: 'gt/2', args: [X, 0]}],
'square/2': (X, Y) => [{args: [X, Y]}, {name: 'mul/3', args: [X, X, Y]}]
};
```
## Running queries
### Sync, callback (default `solve`)
```js
import solve from 'yopl';
import {variable as v} from 'deep6/unify.js';
import assemble from 'deep6/traverse/assemble.js';
const X = v('X');
const results = [];
solve(rules, 'square/2', [5, X], env => {
results.push(assemble(X, env));
});
// results === [25]
```
The callback is invoked **once per solution**. Return `false` from it to stop
the search early.
### Sync generator
```js
import gen from 'yopl/solvers/gen.js';
const X = v('X');
for (const env of gen(rules, 'member/2', [X, list])) {
console.log(assemble(X, env));
}
```
### Async (callback or generator)
Use `yopl/solvers/async.js` or `yopl/solvers/asyncGen.js` when any rule body
needs `await` (database lookups, HTTP, fs, etc.).
```js
import asyncGen from 'yopl/solvers/asyncGen.js';
for await (const env of asyncGen(rules, 'lookup/2', [key, X])) {
// ...
}
```
## Extracting values
Variables are not "results" by themselves — they're placeholders. After a
successful solve, call `assemble(variable, env)` from
`deep6/traverse/assemble.js` to walk the bindings and produce a plain value
(deeply, including nested terms and lists).
## Common patterns
- **List membership / search:** use `member/2` (built-in via your own clauses
or via `system.js` helpers like `list`, `listHead`, `rest`).
- **Higher-order:** `system.js` provides `map`, `filter`, `foldl`, `foldr`,
`compose`, `converse` — all expressed as logic rules.
- **Negation as failure:** `not`, `counterExample`.
- **Reversible arithmetic:** `add`, `sub`, `mul`, `div`, `neg` from `math.js`
work in any direction as long as enough arguments are bound.
- **Cut / control:** `cut`, `once`, `fail`, `halt`, `call` from `system.js`.
- **Type tests:** `isVar`, `isNonVar`, `isNumber`, `isString`, `isArray`, etc.
## Pitfalls
- **Arity matters.** `'foo/2'` is _only_ called when the goal arity is 2.
Mismatched arity silently produces no solutions.
- **Guards on unbound variables.** An inline guard `env => X.get(env) > 0`
will throw or behave wrongly if `X` isn't bound yet. Gate it:
`env => X.isBound(env) && X.get(env) > 0`.
- **Recursion needs a base case.** Otherwise the solver backtracks forever or
blows the stack.
- **Don't share captured variables across clauses.** Each clause function gets
its own fresh variables via its parameters.
- **Async drivers require async consumption.** Don't call `asyncSolve`
fire-and-forget — `await` it (or iterate `asyncGen` with `for await`).
## Picking a driver
| Need | Driver |
| ----------------------------------------- | -------------------------- |
| Simple, sync, one callback per solution | `yopl` (default `solve`) |
| Sync, want to pull solutions lazily | `yopl/solvers/gen.js` |
| Any rule body needs `await` | `yopl/solvers/async.js` |
| Async + want lazy `for await` consumption | `yopl/solvers/asyncGen.js` |
## Where to look next
- `node_modules/yopl/llms.txt` — concise API reference.
- `node_modules/yopl/llms-full.txt` — full API reference with examples.
- `node_modules/yopl/AGENTS.md` — rules, conventions, architecture quick ref.
- [Wiki](https://github.com/uhop/yopl/wiki) — per-module documentation, including each built-in rule.
// Type definitions for yopl — bitwise rules.
import type {Rules} from '../solve.js';
/**
* Bitwise rule library: `bitAnd`, `bitOr`, `bitXor`, `bitNot`.
*
* `bitXor` and `bitNot` are reversible (any one missing operand can be
* solved for). `bitAnd` and `bitOr` are forward-only.
*/
export declare const rules: Rules;
// Type definitions for yopl — comparison rules.
import type {Rules} from '../solve.js';
/**
* Comparison rule library: `lt`, `le`, `gt`, `ge`, and `nz`.
*
* Each comparison succeeds when both operands are bound, of the same
* comparable type (number or string), and stand in the requested
* relation. `nz` succeeds for any non-zero value.
*/
export declare const rules: Rules;
// Type definitions for yopl — boolean logic rules.
import type {Rules} from '../solve.js';
/**
* Boolean logic rule library: `logicalAnd`, `logicalOr`, `logicalXor`,
* `logicalNot`. Each defines a relation among its boolean arguments
* that may be solved in any direction with sufficient bindings.
*/
export declare const rules: Rules;
// Type definitions for yopl — arithmetic rules.
import type {Rules} from '../solve.js';
/**
* Arithmetic rule library: `add`, `sub`, `mul`, `div`, `neg`.
*
* Each rule defines a relation X⊕Y=Z and is reversible: any two of
* the three arguments may be bound, and the rule will solve for the
* third.
*/
export declare const rules: Rules;
// Type definitions for yopl — system rules and helpers.
import type {Variable} from 'deep6/env.js';
import type {GoalFn, Rules, TermObject} from '../solve.js';
/** Goal that always fails (used to force backtracking). */
export declare const fail: GoalFn;
/** Goal that aborts the entire proof search. */
export declare const halt: GoalFn;
/**
* Build a Prolog-style cut. Pass the rest of the rule's variables
* (`...sys`) so cut can locate the choice point to commit to.
*
* @param sys The trailing rest-args bound by the rule definition.
*/
export declare const cut: (sys: ReadonlyArray<Variable>) => GoalFn;
/**
* Meta-call: evaluate `X` as a goal at proof time.
*
* @param X A goal name (string), a structured term, or a variable
* bound to either of the above.
*/
export declare const call: (X: string | TermObject | Variable) => GoalFn;
/**
* Goal that succeeds when every supplied variable is bound in the
* current environment.
*/
export declare const isBound: (...args: Variable[]) => GoalFn;
/**
* Build a rule head: `head(a, b, c)` → `{args: [a, b, c]}`.
*/
export declare const head: (...args: unknown[]) => TermObject;
/**
* Build a structured goal term: `term('foo', 1, 2)` →
* `{name: 'foo', args: [1, 2]}`.
*/
export declare const term: (name: string, ...args: unknown[]) => TermObject;
/** Tail wrapper used by `list` to mark the rest position. */
export declare class Tail {
value: unknown;
constructor(value: unknown);
}
/** Mark `list`'s final argument as the explicit list tail. */
export declare const rest: (list: unknown) => Tail;
/**
* Build a yopl cons-list from positional arguments. The last argument
* may be wrapped in `rest()` to supply an explicit tail; otherwise the
* tail defaults to `null`.
*
* @throws Error if `rest()` is used in a non-final position.
*/
export declare const list: (...args: unknown[]) => unknown;
/**
* Like `list` but the *last* argument is treated as the tail directly
* (no `rest()` wrapper). Requires at least 2 arguments.
*
* @throws Error if called with fewer than 2 arguments.
*/
export declare const listHead: (...args: unknown[]) => unknown;
/** The system rule library. */
export declare const rules: Rules;
// Type definitions for yopl — solver core.
// Generated by hand from src/solve.js.
import type {Env, Variable} from 'deep6/env.js';
/**
* A goal: either a structured term, a bare goal name, or an inline JS
* function executed during proof search.
*/
export type Goal = string | TermObject | GoalFn;
/**
* Structured goal term. `name` is the rule name (omitted for the rule
* head, where only `args` matters). `args` is the argument vector that
* unifies against the head of a rule.
*/
export interface TermObject {
name?: string;
args?: readonly unknown[];
}
/**
* Inline goal function executed by the solver.
*
* Receives the current environment, the current goal frame, and the
* driver stack. Return values:
* - `true` — succeed and advance to the next goal
* - `false` — fail and trigger backtracking
* - a goal frame — replace the current continuation
*/
export type GoalFn = (env: Env, goals: GoalFrame, stack: ReadonlyArray<unknown>) => boolean | GoalFrame | void;
/** Linked list of goal frames maintained by the prover. */
export interface GoalFrame {
terms: ReadonlyArray<Goal>;
index: number;
next: GoalFrame | null;
}
/**
* A rule body factory. Receives a fresh batch of logical variables and
* returns the rule's terms; the first element is the head and the rest
* are body goals.
*/
export type RuleBody = (...vars: Variable[]) => ReadonlyArray<Goal>;
/** A single rule body or a disjunction (an array of bodies). */
export type Rule = RuleBody | ReadonlyArray<RuleBody>;
/** Rule database — maps a rule name to its definition. */
export type Rules = Record<string, Rule>;
/** Callback invoked once per solution discovered by the solver. */
export type SolveCallback = (env: Env) => void;
/**
* Run the callback-style solver against `rules`, attempting to satisfy
* the goal named `name` with the supplied `args`. The callback fires
* once for every solution found.
*
* @param rules Rule database.
* @param name Initial goal name.
* @param args Argument vector for the initial goal.
* @param callback Invoked for every solution.
*/
declare function solve(rules: Rules, name: string, args: ReadonlyArray<unknown>, callback: SolveCallback): void;
export default solve;
// Type definitions for yopl — asynchronous callback-based solver.
import type {Env} from 'deep6/env.js';
import type {Rules} from '../solve.js';
/** Async callback invoked once per solution. */
export type AsyncSolveCallback = (env: Env) => void | Promise<void>;
/**
* Async callback-style solver. Identical in shape to the synchronous
* `solve`, except the per-solution callback may return a promise that
* the solver awaits before backtracking.
*
* @param rules Rule database.
* @param name Initial goal name.
* @param args Argument vector for the initial goal.
* @param callback Async callback invoked for every solution.
*/
declare function solve(rules: Rules, name: string, args: ReadonlyArray<unknown>, callback: AsyncSolveCallback): Promise<void>;
export default solve;
// Type definitions for yopl — asynchronous generator-based solver.
import type {Env} from 'deep6/env.js';
import type {Rules} from '../solve.js';
/**
* Async generator solver. Yields the live `Env` for each solution.
* Useful when goals contain `await`-bearing inline functions.
*
* @param rules Rule database.
* @param name Initial goal name.
* @param args Argument vector for the initial goal.
*/
declare function generate(rules: Rules, name: string, args: ReadonlyArray<unknown>): AsyncGenerator<Env, void, void>;
export default generate;
// Type definitions for yopl — synchronous generator-based solver.
import type {Env} from 'deep6/env.js';
import type {Rules} from '../solve.js';
/**
* Synchronous generator solver. Yields the live `Env` for each solution
* found; consumers typically call `assemble(variable, env)` from deep6
* to extract bindings before requesting the next solution.
*
* @param rules Rule database.
* @param name Initial goal name.
* @param args Argument vector for the initial goal.
*/
declare function generate(rules: Rules, name: string, args: ReadonlyArray<unknown>): Generator<Env, void, void>;
export default generate;
+1
-1
The "New" BSD License:
**********************
Copyright (c) 2005-2020, Eugene Lazutkin
Copyright (c) 2005-2026, Eugene Lazutkin
All rights reserved.

@@ -6,0 +6,0 @@

{
"name": "yopl",
"version": "1.1.4",
"description": "No dependency mini-library: unification, deep equivalence, deep cloning, logical solver.",
"version": "1.2.0",
"description": "Embeddable Prolog-style logic engine for JavaScript: declarative rules, unification, backtracking search, and four solver drivers (sync/async × callback/generator). Useful for pattern matching with extraction, constraint search, type inference, planners, expert systems, and policy checks. ESM, single runtime dependency (deep6).",
"type": "module",
"module": "src/solve.js",
"main": "cjs/solve.js",
"main": "src/solve.js",
"types": "src/solve.d.ts",
"exports": {
".": {
"require": "./cjs/solve.js",
"default": "./src/solve.js"
},
"./*": {
"require": "./cjs/*",
"default": "./src/*"
},
"./solve.js": {
"require": "./cjs/solve.js",
"default": "./src/solve.js"
},
"./solvers/async.js": {
"require": "./cjs/solvers/async.js",
"default": "./src/solvers/async.js"
},
"./solvers/asyncGen.js": {
"require": "./cjs/solvers/asyncGen.js",
"default": "./src/solvers/asyncGen.js"
},
"./solvers/gen.js": {
"require": "./cjs/solvers/gen.js",
"default": "./src/solvers/gen.js"
},
"./cjs": "./cjs/solve.js",
"./cjs/*": "./cjs/*",
"./cjs/solve.js": "./cjs/solve.js",
"./cjs/solvers/async.js": "./cjs/solvers/async.js",
"./cjs/solvers/asyncGen.js": "./cjs/solvers/asyncGen.js",
"./cjs/solvers/gen.js": "./cjs/solvers/gen.js"
".": "./src/solve.js",
"./*": "./src/*"
},
"scripts": {
"test": "node tests/tests.js",
"test:bun": "bun run tests/tests.js",
"test:deno": "deno run --allow-read tests/tests.js",
"debug": "node --inspect-brk tests/tests.js",
"prepareDist": "node scripts/prepareDist.js",
"babel": "babel src --out-dir cjs",
"build": "npm run prepareDist && npm run babel",
"prepublishOnly": "npm run build"
"ts-check": "tsc --noEmit",
"lint": "prettier --check .",
"lint:fix": "prettier --write ."
},

@@ -53,32 +26,47 @@ "repository": {

"keywords": [
"logic programming",
"solver",
"logic solver"
"prolog",
"logic-programming",
"logic-solver",
"unification",
"unify",
"backtracking",
"rule-engine",
"rules",
"declarative",
"relational",
"constraint-solver",
"pattern-matching",
"inference",
"type-inference",
"expert-system",
"planner",
"embeddable",
"esm",
"typescript",
"deep6"
],
"author": "Eugene Lazutkin <eugene.lazutkin@gmail.com> (https://www.lazutkin.com/)",
"funding": "https://github.com/sponsors/uhop",
"license": "BSD-3-Clause",
"devDependencies": {
"@babel/cli": "^7.24.7",
"@babel/core": "^7.24.7",
"@babel/preset-env": "^7.24.7"
"bugs": {
"url": "https://github.com/uhop/yopl/issues"
},
"homepage": "https://github.com/uhop/yopl#readme",
"llms": "https://raw.githubusercontent.com/uhop/yopl/master/llms.txt",
"llmsFull": "https://raw.githubusercontent.com/uhop/yopl/master/llms-full.txt",
"files": [
"/src",
"/cjs"
"src",
"skills",
"llms.txt",
"llms-full.txt",
"AGENTS.md",
"ARCHITECTURE.md"
],
"babel": {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
"dependencies": {
"deep6": "^1.2.1"
},
"dependencies": {
"deep6": "^1.1.4"
"devDependencies": {
"prettier": "^3.8.1",
"typescript": "^6.0.2"
}
}
+86
-17
# yopl [![NPM version][npm-image]][npm-url]
[npm-image]: https://img.shields.io/npm/v/yopl.svg
[npm-url]: https://npmjs.org/package/yopl
[npm-image]: https://img.shields.io/npm/v/yopl.svg
[npm-url]: https://npmjs.org/package/yopl
`yopl`: an ES6 mini-library that provides:
`yopl` is an ES6 mini-library that implements a Prolog-style logic solver in JavaScript. It provides:
* Logic solvers.
* Rule-based, logic programming style.
* Both callback and generator styles are supported.
- A small core solver with multiple driver styles: callback, generator, async callback, async generator.
- A built-in rule library: helpers and control predicates, comparisons, arithmetic, bitwise, and boolean logic.
It has only one dependency: [deep6](https://www.npmjs.com/package/deep6), which is a no-dependency library itself.
Its only runtime dependency is [`deep6`](https://www.npmjs.com/package/deep6), itself a zero-dependency library that provides the unification engine.
# Introduction
## What it does and when to use it
TBD
`yopl` lets you describe a problem as a set of _rules_ over JavaScript values and ask the solver to find values that satisfy them. You write declarative rules; the engine handles search, unification, and backtracking. You stay inside JavaScript — there is no embedded DSL to parse, no separate Prolog runtime, and rules can call back into plain JS (sync or async) whenever a piece of logic is easier to express that way.
It is useful when a problem is awkward to express as straight-line code but natural to express as constraints or relations:
- Pattern matching and shape validation against deeply nested data, where you also want to _extract_ values during the match.
- Searching configurations, dependency graphs, or rule sets for combinations that satisfy several conditions at once.
- Type-inference-like or tag-propagation passes over an AST or IR.
- Small expert systems, planners, permission/policy checks, and "find me an X such that Y" queries embedded inside a larger JS app.
- Test fixtures and property-style checks that need to enumerate all values matching a spec.
If you only need single-direction pattern matching, a regex or a destructuring assignment is simpler. Reach for `yopl` when you need _bidirectional_ matching (unification), backtracking across alternative rules, or enumeration of all solutions — and you want all of that without leaving your JavaScript codebase.
## Installation

@@ -24,10 +33,70 @@

## Release History
## Quick start
- 1.1.4 *updated dependencies.*
- 1.1.3 *updated dependencies.*
- 1.1.2 *updated dependencies.*
- 1.1.1 *updated dependencies.*
- 1.1.0 *[deep6](https://npmjs.org/package/deep6) was extracted from this package, and now used as a dependency.*
- 1.0.1 *added the exports statement.*
- 1.0.0 *the first 1.0 release.*
```js
import {variable} from 'deep6/env.js';
import assemble from 'deep6/traverse/assemble.js';
import solve from 'yopl';
const rules = {
member: [(V, X) => [{args: [{value: V, next: X}, V]}], (V, X) => [{args: [{next: X}, V]}, {name: 'member', args: [X, V]}]]
};
const list = {value: 1, next: {value: 2, next: {value: 3, next: null}}};
const X = variable('X');
solve(rules, 'member', [list, X], env => {
console.log('X =', assemble(X, env));
});
// X = 1
// X = 2
// X = 3
```
## Modules
| Module | Purpose |
| -------------------------- | ------------------------------------------------------------------------------- |
| `yopl` (`src/solve.js`) | Synchronous callback solver — main entry point. |
| `yopl/solvers/gen.js` | Synchronous generator solver. |
| `yopl/solvers/async.js` | Async callback solver. |
| `yopl/solvers/asyncGen.js` | Async generator solver. |
| `yopl/rules/system.js` | Helpers + control predicates (`head`, `term`, `list`, `cut`, `call`, `not`, …). |
| `yopl/rules/comp.js` | Comparisons: `lt`, `le`, `gt`, `ge`, `nz`. |
| `yopl/rules/math.js` | Arithmetic: `add`, `sub`, `mul`, `div`, `neg`. |
| `yopl/rules/bits.js` | Bitwise: `bitAnd`, `bitOr`, `bitXor`, `bitNot`. |
| `yopl/rules/logic.js` | Boolean logic: `logicalAnd`, `logicalOr`, `logicalXor`, `logicalNot`. |
Per-module documentation lives in the [wiki](https://github.com/uhop/yopl/wiki).
## CommonJS
`yopl` ships as ESM only. CommonJS consumers can use Node's built-in dynamic `import()`:
```js
const {default: solve} = await import('yopl');
```
A full CJS interop demo lives in `tests/test-cjs.cjs` (run it with `node tests/test-cjs.cjs`).
## Development
```bash
git clone git@github.com:uhop/yopl.git
cd yopl
npm install
npm test
```
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the development workflow and [AGENTS.md](./AGENTS.md) for AI-agent rules.
## Release history
- 1.2.0 — removed CJS build, restructured tests, added TypeScript typings, simplified list creation, bug fixes and performance improvements, expanded docs and wiki.
- 1.1.4 — updated dependencies.
- 1.1.3 — updated dependencies.
- 1.1.2 — updated dependencies.
- 1.1.1 — updated dependencies.
- 1.1.0 — [deep6](https://npmjs.org/package/deep6) was extracted from this package and is now a dependency.
- 1.0.1 — added the `exports` statement.
- 1.0.0 — first 1.0 release.

@@ -1,2 +0,2 @@

import {_} from 'deep6/env';
import {_} from 'deep6/env.js';
import {head, cut} from './system.js';

@@ -19,3 +19,3 @@

if (y !== _ && typeof y != 'number') return false;
const z = X.get(env);
const z = Z.get(env);
if (z !== _ && typeof z != 'number') return false;

@@ -52,3 +52,3 @@ if (x === _ || y === _ || z === _) return true;

if (y !== _ && typeof y != 'number') return false;
const z = X.get(env);
const z = Z.get(env);
if (z !== _ && typeof z != 'number') return false;

@@ -75,3 +75,3 @@ if (x === _ || y === _ || z === _) return true;

head(X, Y, Z),
(env, stack) => {
(env, goals, stack) => {
const isX = X.isBound(env),

@@ -82,3 +82,3 @@ isY = Y.isBound(env),

if (count < 2) return false;
cut(sys)(env, stack);
cut(sys)(env, goals, stack);
if (count == 3) {

@@ -89,3 +89,3 @@ const x = X.get(env);

if (y !== _ && typeof y != 'number') return false;
const z = X.get(env);
const z = Z.get(env);
if (z !== _ && typeof z != 'number') return false;

@@ -124,8 +124,8 @@ if (x === _ || y === _ || z === _) return true;

head(X, Y),
(env, stack) => {
(env, goals, stack) => {
const isX = X.isBound(env),
isY = Y.isBound(env);
count = (isX ? 1 : 0) + (isY ? 1 : 0);
isY = Y.isBound(env),
count = (isX ? 1 : 0) + (isY ? 1 : 0);
if (count < 1) return false;
cut(sys)(env, stack);
cut(sys)(env, goals, stack);
if (count == 2) {

@@ -136,3 +136,2 @@ const x = X.get(env);

if (y !== _ && typeof y != 'number') return false;
const z = X.get(env);
if (x === _ || y === _) return true;

@@ -139,0 +138,0 @@ return x === ~y;

@@ -1,2 +0,2 @@

import {_} from 'deep6/env';
import {_} from 'deep6/env.js';
import {head, cut, fail, isBound} from './system.js';

@@ -3,0 +3,0 @@

@@ -1,2 +0,2 @@

import {_} from 'deep6/env';
import {_} from 'deep6/env.js';
import {head} from './system.js';

@@ -17,3 +17,3 @@

y = Y.get(env),
z = X.get(env);
z = Z.get(env);
if (x === _ || y === _ || z === _) return true;

@@ -46,3 +46,3 @@ return !(x && y) === !z;

y = Y.get(env),
z = X.get(env);
z = Z.get(env);
if (x === _ || y === _ || z === _) return true;

@@ -75,3 +75,3 @@ return !(x || y) === !z;

y = Y.get(env),
z = X.get(env);
z = Z.get(env);
if (x === _ || y === _ || z === _) return true;

@@ -104,4 +104,4 @@ return (!!x ^ !!y) === !!z;

const isX = X.isBound(env),
isY = Y.isBound(env);
count = (isX ? 1 : 0) + (isY ? 1 : 0);
isY = Y.isBound(env),
count = (isX ? 1 : 0) + (isY ? 1 : 0);
if (count < 1) return false;

@@ -108,0 +108,0 @@ if (count == 2) {

@@ -1,2 +0,2 @@

import {_} from 'deep6/env';
import {_} from 'deep6/env.js';
import {head, cut} from './system.js';

@@ -9,3 +9,3 @@

head(X, Y, Z),
(env, stack) => {
(env, goals, stack) => {
const isX = X.isBound(env),

@@ -16,3 +16,3 @@ isY = Y.isBound(env),

if (count < 2) return false;
cut(sys)(env, stack);
cut(sys)(env, goals, stack);
if (count == 3) {

@@ -23,3 +23,3 @@ const x = X.get(env);

if (y !== _ && typeof y != 'number') return false;
const z = X.get(env);
const z = Z.get(env);
if (z !== _ && typeof z != 'number') return false;

@@ -57,3 +57,3 @@ if (x === _ || y === _ || z === _) return true;

head(X, Y, Z),
(env, stack) => {
(env, goals, stack) => {
const isX = X.isBound(env),

@@ -64,3 +64,3 @@ isY = Y.isBound(env),

if (count < 2) return false;
cut(sys)(env, stack);
cut(sys)(env, goals, stack);
if (count == 3) {

@@ -71,3 +71,3 @@ const x = X.get(env);

if (y !== _ && typeof y != 'number') return false;
const z = X.get(env);
const z = Z.get(env);
if (z !== _ && typeof z != 'number') return false;

@@ -105,3 +105,3 @@ if (x === _ || y === _ || z === _) return true;

head(X, Y, Z),
(env, stack) => {
(env, goals, stack) => {
const isX = X.isBound(env),

@@ -112,3 +112,3 @@ isY = Y.isBound(env),

if (count < 2) return false;
cut(sys)(env, stack);
cut(sys)(env, goals, stack);
if (count == 3) {

@@ -119,3 +119,3 @@ const x = X.get(env);

if (y !== _ && typeof y != 'number') return false;
const z = X.get(env);
const z = Z.get(env);
if (z !== _ && typeof z != 'number') return false;

@@ -155,3 +155,3 @@ if (x === _ || y === _ || z === _) return true;

head(X, Y, Z),
(env, stack) => {
(env, goals, stack) => {
const isX = X.isBound(env),

@@ -162,3 +162,3 @@ isY = Y.isBound(env),

if (count < 2) return false;
cut(sys)(env, stack);
cut(sys)(env, goals, stack);
if (count == 3) {

@@ -169,3 +169,3 @@ const x = X.get(env);

if (y !== _ && typeof y != 'number') return false;
const z = X.get(env);
const z = Z.get(env);
if (z !== _ && typeof z != 'number') return false;

@@ -204,8 +204,8 @@ if (x === _ || y === _ || z === _) return true;

head(X, Y),
(env, stack) => {
(env, goals, stack) => {
const isX = X.isBound(env),
isY = Y.isBound(env);
count = (isX ? 1 : 0) + (isY ? 1 : 0);
isY = Y.isBound(env),
count = (isX ? 1 : 0) + (isY ? 1 : 0);
if (count < 1) return false;
cut(sys)(env, stack);
cut(sys)(env, goals, stack);
if (count == 2) {

@@ -216,3 +216,2 @@ const x = X.get(env);

if (y !== _ && typeof y != 'number') return false;
const z = X.get(env);
if (x === _ || y === _) return true;

@@ -219,0 +218,0 @@ return x === -y;

@@ -22,3 +22,3 @@ import {_, isVariable} from 'deep6/env.js';

args;
// TODO: add processing of arrays of goals
// TODO: add processing of arrays of goals
if (isVariable(X)) {

@@ -46,3 +46,6 @@ if (!X.isBound(env)) return false;

export const isBound = (...args) => env => args.every(V => isVariable(V) && V.isBound(env));
export const isBound =
(...args) =>
env =>
args.every(V => isVariable(V) && V.isBound(env));

@@ -98,4 +101,4 @@ export const head = (...args) => ({args});

// equality
eq: X => head(X, X),
notEq: [(X, ...sys) => [head(X, X), cut(sys), fail], [_]],
eq: X => [head(X, X)],
notEq: [(X, ...sys) => [head(X, X), cut(sys), fail], () => [head(_, _)]],
// unify is eq

@@ -105,3 +108,3 @@

call: X => [head(X), call(X)],
not: [(X, ...sys) => [head(X), call(X), cut(sys), fail], () => [head()]],
not: [(X, ...sys) => [head(X), call(X), cut(sys), fail], () => [head(_)]],
isUnifiable: (X, Y) => [head(X, Y), term('not', term('not', term('eq', [X, Y])))],

@@ -129,3 +132,3 @@ // notUnifiable is notEq

],
foldl: [A => [head(_, A, null, A)], (F, A, X, Xt, O, B) => [head(F, A, listHead(X, Xt), Yt), call(term(F, A, X, B)), term('foldl', F, B, Xt, O)]],
foldl: [A => [head(_, A, null, A)], (F, A, X, Xt, O, B) => [head(F, A, listHead(X, Xt), O), call(term(F, A, X, B)), term('foldl', F, B, Xt, O)]],
foldr: [A => [head(_, A, null, A)], (F, A, X, Xt, O, T) => [head(F, A, listHead(X, Xt), O), term('foldr', F, A, Xt, T), call(term(F, X, T, O))]],

@@ -132,0 +135,0 @@ compose: (F, G, X, O, T) => [head(F, G, X, O), call(term(G, X, T)), call(term(F, T, O))],

@@ -5,7 +5,10 @@ import unify, {Env, variable} from 'deep6/unify.js';

const generateVariables = count => {
const t = [];
for (let i = 0; i < count; ++i) t.push(counter++);
return t.map(name => variable(Symbol(name)));
const vars = new Array(count);
for (let i = 0; i < count; ++i) vars[i] = variable(Symbol(counter++));
return vars;
};
const POP = {command: 1};
const NO_ARGS = Object.freeze([]);
const prove = (rules, goals, env) => {

@@ -25,5 +28,5 @@ const stack = [{goals}];

env.push();
if (unify(terms[0].args || [], frame.args, env)) {
if (unify(terms[0].args || NO_ARGS, frame.args, env)) {
const newGoals = {terms, index: 1, next: frame.goals};
stack.push(frame, {command: 1}, {goals: newGoals});
stack.push(frame, POP, {goals: newGoals});
env.bindVal(vars[vars.length - 1].name, frame);

@@ -46,4 +49,4 @@ continue main;

if (newGoals || newGoals === null) {
(newGoals && !newGoals.terms) && (newGoals = goals);
stack.push({command: 1}, {goals: newGoals});
newGoals && !newGoals.terms && (newGoals = goals);
stack.push(POP, {goals: newGoals});
continue main;

@@ -59,4 +62,5 @@ }

let ruleList = rules[goal.name];
!Array.isArray(ruleList) && (ruleList = [ruleList]);
stack.push({command: 2, ruleList, index: 0, goals, args: goal.args || []});
if (ruleList == null) continue main;
if (!Array.isArray(ruleList)) ruleList = [ruleList];
stack.push({command: 2, ruleList, index: 0, goals, args: goal.args || NO_ARGS});
}

@@ -63,0 +67,0 @@ };

@@ -5,7 +5,10 @@ import unify, {Env, variable} from 'deep6/unify.js';

const generateVariables = count => {
const t = [];
for (let i = 0; i < count; ++i) t.push(counter++);
return t.map(name => variable(Symbol(name)));
const vars = new Array(count);
for (let i = 0; i < count; ++i) vars[i] = variable(Symbol(counter++));
return vars;
};
const POP = {command: 1};
const NO_ARGS = Object.freeze([]);
const prove = async (rules, goals, env) => {

@@ -25,5 +28,5 @@ const stack = [{goals}];

env.push();
if (unify(terms[0].args || [], frame.args, env)) {
if (unify(terms[0].args || NO_ARGS, frame.args, env)) {
const newGoals = {terms, index: 1, next: frame.goals};
stack.push(frame, {command: 1}, {goals: newGoals});
stack.push(frame, POP, {goals: newGoals});
env.bindVal(vars[vars.length - 1].name, frame);

@@ -46,4 +49,4 @@ continue main;

if (newGoals || newGoals === null) {
(newGoals && !newGoals.terms) && (newGoals = goals);
stack.push({command: 1}, {goals: newGoals});
newGoals && !newGoals.terms && (newGoals = goals);
stack.push(POP, {goals: newGoals});
continue main;

@@ -59,4 +62,5 @@ }

let ruleList = rules[goal.name];
!Array.isArray(ruleList) && (ruleList = [ruleList]);
stack.push({command: 2, ruleList, index: 0, goals, args: goal.args || []});
if (ruleList == null) continue main;
if (!Array.isArray(ruleList)) ruleList = [ruleList];
stack.push({command: 2, ruleList, index: 0, goals, args: goal.args || NO_ARGS});
}

@@ -69,5 +73,5 @@ };

const goals = {terms: [{name, args}, async env => (await callback(env), false)], index: 0, next: null};
prove(rules, goals, env);
await prove(rules, goals, env);
};
export default solve;

@@ -5,7 +5,10 @@ import unify, {Env, variable} from 'deep6/unify.js';

const generateVariables = count => {
const t = [];
for (let i = 0; i < count; ++i) t.push(counter++);
return t.map(name => variable(Symbol(name)));
const vars = new Array(count);
for (let i = 0; i < count; ++i) vars[i] = variable(Symbol(counter++));
return vars;
};
const POP = {command: 1};
const NO_ARGS = Object.freeze([]);
async function* prove(rules, goals, env) {

@@ -25,5 +28,5 @@ const stack = [{goals}];

env.push();
if (unify(terms[0].args || [], frame.args, env)) {
if (unify(terms[0].args || NO_ARGS, frame.args, env)) {
const newGoals = {terms, index: 1, next: frame.goals};
stack.push(frame, {command: 1}, {goals: newGoals});
stack.push(frame, POP, {goals: newGoals});
env.bindVal(vars[vars.length - 1].name, frame);

@@ -40,3 +43,3 @@ continue main;

}
if (!goals){
if (!goals) {
yield env;

@@ -50,4 +53,4 @@ continue main;

if (newGoals || newGoals === null) {
(newGoals && !newGoals.terms) && (newGoals = goals);
stack.push({command: 1}, {goals: newGoals});
newGoals && !newGoals.terms && (newGoals = goals);
stack.push(POP, {goals: newGoals});
continue main;

@@ -63,6 +66,7 @@ }

let ruleList = rules[goal.name];
!Array.isArray(ruleList) && (ruleList = [ruleList]);
stack.push({command: 2, ruleList, index: 0, goals, args: goal.args || []});
if (ruleList == null) continue main;
if (!Array.isArray(ruleList)) ruleList = [ruleList];
stack.push({command: 2, ruleList, index: 0, goals, args: goal.args || NO_ARGS});
}
};
}

@@ -74,4 +78,4 @@ async function* generate(rules, name, args) {

yield* prove(rules, goals, env);
};
}
export default generate;

@@ -5,7 +5,10 @@ import unify, {Env, variable} from 'deep6/unify.js';

const generateVariables = count => {
const t = [];
for (let i = 0; i < count; ++i) t.push(counter++);
return t.map(name => variable(Symbol(name)));
const vars = new Array(count);
for (let i = 0; i < count; ++i) vars[i] = variable(Symbol(counter++));
return vars;
};
const POP = {command: 1};
const NO_ARGS = Object.freeze([]);
function* prove(rules, goals, env) {

@@ -25,5 +28,5 @@ const stack = [{goals}];

env.push();
if (unify(terms[0].args || [], frame.args, env)) {
if (unify(terms[0].args || NO_ARGS, frame.args, env)) {
const newGoals = {terms, index: 1, next: frame.goals};
stack.push(frame, {command: 1}, {goals: newGoals});
stack.push(frame, POP, {goals: newGoals});
env.bindVal(vars[vars.length - 1].name, frame);

@@ -40,3 +43,3 @@ continue main;

}
if (!goals){
if (!goals) {
yield env;

@@ -50,4 +53,4 @@ continue main;

if (newGoals || newGoals === null) {
(newGoals && !newGoals.terms) && (newGoals = goals);
stack.push({command: 1}, {goals: newGoals});
newGoals && !newGoals.terms && (newGoals = goals);
stack.push(POP, {goals: newGoals});
continue main;

@@ -63,6 +66,7 @@ }

let ruleList = rules[goal.name];
!Array.isArray(ruleList) && (ruleList = [ruleList]);
stack.push({command: 2, ruleList, index: 0, goals, args: goal.args || []});
if (ruleList == null) continue main;
if (!Array.isArray(ruleList)) ruleList = [ruleList];
stack.push({command: 2, ruleList, index: 0, goals, args: goal.args || NO_ARGS});
}
};
}

@@ -74,4 +78,4 @@ function* generate(rules, name, args) {

yield* prove(rules, goals, env);
};
}
export default generate;
{"type":"commonjs"}
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.rules = void 0;
var _env = require("deep6/env");
var _system = require("./system.js");
const rules = exports.rules = {
// bitwise operations
bitAnd: (X, Y, Z) => [(0, _system.head)(X, Y, Z), env => {
const isX = X.isBound(env),
isY = Y.isBound(env),
isZ = Z.isBound(env),
count = (isX ? 1 : 0) + (isY ? 1 : 0) + (isZ ? 1 : 0);
if (count < 2) return false;
if (count == 3) {
const x = X.get(env);
if (x !== _env._ && typeof x != 'number') return false;
const y = Y.get(env);
if (y !== _env._ && typeof y != 'number') return false;
const z = X.get(env);
if (z !== _env._ && typeof z != 'number') return false;
if (x === _env._ || y === _env._ || z === _env._) return true;
return (x & y) === z;
}
if (isX) {
const x = X.get(env);
if (typeof x != 'number') return false;
if (isY) {
const y = Y.get(env);
if (typeof y != 'number') return false;
env.bindVal(Z.name, x & y);
return true;
}
return false;
}
return false;
}],
bitOr: (X, Y, Z) => [(0, _system.head)(X, Y, Z), env => {
const isX = X.isBound(env),
isY = Y.isBound(env),
isZ = Z.isBound(env),
count = (isX ? 1 : 0) + (isY ? 1 : 0) + (isZ ? 1 : 0);
if (count < 2) return false;
if (count == 3) {
const x = X.get(env);
if (x !== _env._ && typeof x != 'number') return false;
const y = Y.get(env);
if (y !== _env._ && typeof y != 'number') return false;
const z = X.get(env);
if (z !== _env._ && typeof z != 'number') return false;
if (x === _env._ || y === _env._ || z === _env._) return true;
return (x | y) === z;
}
if (isX) {
const x = X.get(env);
if (typeof x != 'number') return false;
if (isY) {
const y = Y.get(env);
if (typeof y != 'number') return false;
env.bindVal(Z.name, x | y);
return true;
}
return false;
}
return false;
}],
bitXor: [(X, Y, Z, ...sys) => [(0, _system.head)(X, Y, Z), (env, stack) => {
const isX = X.isBound(env),
isY = Y.isBound(env),
isZ = Z.isBound(env),
count = (isX ? 1 : 0) + (isY ? 1 : 0) + (isZ ? 1 : 0);
if (count < 2) return false;
(0, _system.cut)(sys)(env, stack);
if (count == 3) {
const x = X.get(env);
if (x !== _env._ && typeof x != 'number') return false;
const y = Y.get(env);
if (y !== _env._ && typeof y != 'number') return false;
const z = X.get(env);
if (z !== _env._ && typeof z != 'number') return false;
if (x === _env._ || y === _env._ || z === _env._) return true;
return (x ^ y) === z;
}
if (isX) {
const x = X.get(env);
if (typeof x != 'number') return false;
if (isY) {
const y = Y.get(env);
if (typeof y != 'number') return false;
env.bindVal(Z.name, x ^ y);
return true;
}
const z = Z.get(env);
if (typeof z != 'number') return false;
env.bindVal(Y.name, x ^ z);
return true;
}
const y = Y.get(env);
if (typeof y != 'number') return false;
const z = Z.get(env);
if (typeof z != 'number') return false;
env.bindVal(X.name, y ^ z);
return true;
}], Y => [(0, _system.head)(0, Y, Y)], X => [(0, _system.head)(X, 0, X)], X => [(0, _system.head)(X, X, 0)]],
bitNot: [(X, Y, ...sys) => [(0, _system.head)(X, Y), (env, stack) => {
const isX = X.isBound(env),
isY = Y.isBound(env);
count = (isX ? 1 : 0) + (isY ? 1 : 0);
if (count < 1) return false;
(0, _system.cut)(sys)(env, stack);
if (count == 2) {
const x = X.get(env);
if (x !== _env._ && typeof x != 'number') return false;
const y = Y.get(env);
if (y !== _env._ && typeof y != 'number') return false;
const z = X.get(env);
if (x === _env._ || y === _env._) return true;
return x === ~y;
}
if (isX) {
const x = X.get(env);
if (typeof x != 'number') return false;
env.bindVal(Y.name, ~x);
return true;
}
const y = Y.get(env);
if (typeof y != 'number') return false;
env.bindVal(X.name, ~y);
return true;
}], () => [(0, _system.head)(0, 0)]]
};
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.rules = void 0;
var _env = require("deep6/env");
var _system = require("./system.js");
const comparable = {
string: 1,
number: 1
};
const rules = exports.rules = {
// comparisons
lt: (X, Y) => [(0, _system.head)(X, Y), (0, _system.isBound)(X, Y), env => {
const x = X.get(env),
y = Y.get(env);
if (x === _env._) return y === _env._ || comparable[typeof y] === 1;
if (y === _env._) return comparable[typeof x] === 1;
return typeof x == typeof y && comparable[typeof x] === 1 && x < y;
}],
le: (X, Y) => [(0, _system.head)(X, Y), (0, _system.isBound)(X, Y), env => {
const x = X.get(env),
y = Y.get(env);
if (x === _env._) return y === _env._ || comparable[typeof y] === 1;
if (y === _env._) return comparable[typeof x] === 1;
return typeof x == typeof y && comparable[typeof x] === 1 && x <= y;
}],
gt: (X, Y) => [(0, _system.head)(X, Y), (0, _system.isBound)(X, Y), env => {
const x = X.get(env),
y = Y.get(env);
if (x === _env._) return y === _env._ || comparable[typeof y] === 1;
if (y === _env._) return comparable[typeof x] === 1;
return typeof x == typeof y && comparable[typeof x] === 1 && x > y;
}],
ge: (X, Y) => [(0, _system.head)(X, Y), (0, _system.isBound)(X, Y), env => {
const x = X.get(env),
y = Y.get(env);
if (x === _env._) return y === _env._ || comparable[typeof y] === 1;
if (y === _env._) return comparable[typeof x] === 1;
return typeof x == typeof y && comparable[typeof x] === 1 && x >= y;
}],
// miscellaneous
nz: [(...sys) => [(0, _system.head)(0), (0, _system.cut)(sys), _system.fail], (...sys) => [(0, _system.head)(_env._), (0, _system.cut)(sys)]]
};
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.rules = void 0;
var _env = require("deep6/env");
var _system = require("./system.js");
const rules = exports.rules = {
// logical operations
logicalAnd: (X, Y, Z) => [(0, _system.head)(X, Y, Z), env => {
const isX = X.isBound(env),
isY = Y.isBound(env),
isZ = Z.isBound(env),
count = (isX ? 1 : 0) + (isY ? 1 : 0) + (isZ ? 1 : 0);
if (count < 2) return false;
if (count == 3) {
const x = X.get(env),
y = Y.get(env),
z = X.get(env);
if (x === _env._ || y === _env._ || z === _env._) return true;
return !(x && y) === !z;
}
if (isX) {
const x = X.get(env);
if (isY) {
const y = Y.get(env);
if (x === _env._ || y === _env._) return true;
env.bindVal(Z.name, !!(x && y));
return true;
}
return false;
}
return false;
}],
logicalOr: (X, Y, Z) => [(0, _system.head)(X, Y, Z), env => {
const isX = X.isBound(env),
isY = Y.isBound(env),
isZ = Z.isBound(env),
count = (isX ? 1 : 0) + (isY ? 1 : 0) + (isZ ? 1 : 0);
if (count < 2) return false;
if (count == 3) {
const x = X.get(env),
y = Y.get(env),
z = X.get(env);
if (x === _env._ || y === _env._ || z === _env._) return true;
return !(x || y) === !z;
}
if (isX) {
const x = X.get(env);
if (isY) {
const y = Y.get(env);
if (x === _env._ || y === _env._) return true;
env.bindVal(Z.name, !!(x || y));
return true;
}
return false;
}
return false;
}],
logicalXor: (X, Y, Z) => [(0, _system.head)(X, Y, Z), env => {
const isX = X.isBound(env),
isY = Y.isBound(env),
isZ = Z.isBound(env),
count = (isX ? 1 : 0) + (isY ? 1 : 0) + (isZ ? 1 : 0);
if (count < 2) return false;
if (count == 3) {
const x = X.get(env),
y = Y.get(env),
z = X.get(env);
if (x === _env._ || y === _env._ || z === _env._) return true;
return (!!x ^ !!y) === !!z;
}
if (isX) {
const x = X.get(env);
if (isY) {
const y = Y.get(env);
if (x === _env._ || y === _env._) return true;
env.bindVal(Z.name, !!(!!x ^ !!y));
return true;
}
const z = Z.get(env);
if (x === _env._ || z === _env._) return true;
env.bindVal(Y.name, !!(!!x ^ !!z));
return true;
}
const y = Y.get(env),
z = Z.get(env);
if (y === _env._ || z === _env._) return true;
env.bindVal(X.name, !!(!!y ^ !!z));
return true;
}],
logicalNot: (X, Y) => [(0, _system.head)(X, Y), env => {
const isX = X.isBound(env),
isY = Y.isBound(env);
count = (isX ? 1 : 0) + (isY ? 1 : 0);
if (count < 1) return false;
if (count == 2) {
const x = X.get(env),
y = Y.get(env);
if (x === _env._ || y === _env._) return true;
return !x === !!y;
}
if (isX) {
const x = X.get(env);
if (x === _env._) return true;
env.bindVal(Y.name, !x);
return true;
}
const y = Y.get(env);
if (y === _env._) return true;
env.bindVal(X.name, !y);
return true;
}]
};
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.rules = void 0;
var _env = require("deep6/env");
var _system = require("./system.js");
const rules = exports.rules = {
// arithmetics
add: [(X, Y, Z, ...sys) => [(0, _system.head)(X, Y, Z), (env, stack) => {
const isX = X.isBound(env),
isY = Y.isBound(env),
isZ = Z.isBound(env),
count = (isX ? 1 : 0) + (isY ? 1 : 0) + (isZ ? 1 : 0);
if (count < 2) return false;
(0, _system.cut)(sys)(env, stack);
if (count == 3) {
const x = X.get(env);
if (x !== _env._ && typeof x != 'number') return false;
const y = Y.get(env);
if (y !== _env._ && typeof y != 'number') return false;
const z = X.get(env);
if (z !== _env._ && typeof z != 'number') return false;
if (x === _env._ || y === _env._ || z === _env._) return true;
return x + y === z;
}
if (isX) {
const x = X.get(env);
if (typeof x != 'number') return false;
if (isY) {
const y = Y.get(env);
if (typeof y != 'number') return false;
env.bindVal(Z.name, x + y);
return true;
}
const z = Z.get(env);
if (typeof z != 'number') return false;
env.bindVal(Y.name, z - x);
return true;
}
const y = Y.get(env);
if (typeof y != 'number') return false;
const z = Z.get(env);
if (typeof z != 'number') return false;
env.bindVal(X.name, z - y);
return true;
}], Y => [(0, _system.head)(0, Y, Y)], X => [(0, _system.head)(X, 0, X)]],
sub: [(X, Y, Z, ...sys) => [(0, _system.head)(X, Y, Z), (env, stack) => {
const isX = X.isBound(env),
isY = Y.isBound(env),
isZ = Z.isBound(env),
count = (isX ? 1 : 0) + (isY ? 1 : 0) + (isZ ? 1 : 0);
if (count < 2) return false;
(0, _system.cut)(sys)(env, stack);
if (count == 3) {
const x = X.get(env);
if (x !== _env._ && typeof x != 'number') return false;
const y = Y.get(env);
if (y !== _env._ && typeof y != 'number') return false;
const z = X.get(env);
if (z !== _env._ && typeof z != 'number') return false;
if (x === _env._ || y === _env._ || z === _env._) return true;
return x - y === z;
}
if (isX) {
const x = X.get(env);
if (typeof x != 'number') return false;
if (isY) {
const y = Y.get(env);
if (typeof y != 'number') return false;
env.bindVal(Z.name, x - y);
return true;
}
const z = Z.get(env);
if (typeof z != 'number') return false;
env.bindVal(Y.name, x - z);
return true;
}
const y = Y.get(env);
if (typeof y != 'number') return false;
const z = Z.get(env);
if (typeof z != 'number') return false;
env.bindVal(X.name, y + z);
return true;
}], X => [(0, _system.head)(X, 0, X)], X => [(0, _system.head)(X, X, 0)]],
mul: [(X, Y, Z, ...sys) => [(0, _system.head)(X, Y, Z), (env, stack) => {
const isX = X.isBound(env),
isY = Y.isBound(env),
isZ = Z.isBound(env),
count = (isX ? 1 : 0) + (isY ? 1 : 0) + (isZ ? 1 : 0);
if (count < 2) return false;
(0, _system.cut)(sys)(env, stack);
if (count == 3) {
const x = X.get(env);
if (x !== _env._ && typeof x != 'number') return false;
const y = Y.get(env);
if (y !== _env._ && typeof y != 'number') return false;
const z = X.get(env);
if (z !== _env._ && typeof z != 'number') return false;
if (x === _env._ || y === _env._ || z === _env._) return true;
return x * y === z;
}
if (isX) {
const x = X.get(env);
if (typeof x != 'number') return false;
if (isY) {
const y = Y.get(env);
if (typeof y != 'number') return false;
env.bindVal(Z.name, x * y);
return true;
}
const z = Z.get(env);
if (typeof z != 'number') return false;
env.bindVal(Y.name, z / x);
return true;
}
const y = Y.get(env);
if (typeof y != 'number') return false;
const z = Z.get(env);
if (typeof z != 'number') return false;
env.bindVal(X.name, z / y);
return true;
}], () => [(0, _system.head)(0, _env._, 0)], () => [(0, _system.head)(_env._, 0, 0)], X => [(0, _system.head)(1, X, X)], X => [(0, _system.head)(X, 1, X)]],
div: [(X, Y, Z, ...sys) => [(0, _system.head)(X, Y, Z), (env, stack) => {
const isX = X.isBound(env),
isY = Y.isBound(env),
isZ = Z.isBound(env),
count = (isX ? 1 : 0) + (isY ? 1 : 0) + (isZ ? 1 : 0);
if (count < 2) return false;
(0, _system.cut)(sys)(env, stack);
if (count == 3) {
const x = X.get(env);
if (x !== _env._ && typeof x != 'number') return false;
const y = Y.get(env);
if (y !== _env._ && typeof y != 'number') return false;
const z = X.get(env);
if (z !== _env._ && typeof z != 'number') return false;
if (x === _env._ || y === _env._ || z === _env._) return true;
return x / y === z;
}
if (isX) {
const x = X.get(env);
if (typeof x != 'number') return false;
if (isY) {
const y = Y.get(env);
if (typeof y != 'number') return false;
env.bindVal(Z.name, x / y);
return true;
}
const z = Z.get(env);
if (typeof z != 'number') return false;
env.bindVal(Y.name, x / z);
return true;
}
const y = Y.get(env);
if (typeof y != 'number') return false;
const z = Z.get(env);
if (typeof z != 'number') return false;
env.bindVal(X.name, y * z);
return true;
}], () => [(0, _system.head)(0, _env._, 0)], X => [(0, _system.head)(X, X, 1)], X => [(0, _system.head)(X, 1, X)]],
neg: [(X, Y, ...sys) => [(0, _system.head)(X, Y), (env, stack) => {
const isX = X.isBound(env),
isY = Y.isBound(env);
count = (isX ? 1 : 0) + (isY ? 1 : 0);
if (count < 1) return false;
(0, _system.cut)(sys)(env, stack);
if (count == 2) {
const x = X.get(env);
if (x !== _env._ && typeof x != 'number') return false;
const y = Y.get(env);
if (y !== _env._ && typeof y != 'number') return false;
const z = X.get(env);
if (x === _env._ || y === _env._) return true;
return x === -y;
}
if (isX) {
const x = X.get(env);
if (typeof x != 'number') return false;
env.bindVal(Y.name, -x);
return true;
}
const y = Y.get(env);
if (typeof y != 'number') return false;
env.bindVal(X.name, -y);
return true;
}], () => [(0, _system.head)(0, 0)]]
};
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.term = exports.rules = exports.rest = exports.listHead = exports.list = exports.isBound = exports.head = exports.halt = exports.fail = exports.cut = exports.call = void 0;
var _env = require("deep6/env.js");
// utilities
const fail = () => false;
exports.fail = fail;
const halt = (env, goals, stack) => (stack.splice(0), false);
exports.halt = halt;
const cut = sys => (env, goals, stack) => {
const lastFrame = sys[0].get(env);
for (let i = stack.length - 1; i >= 0; --i) {
const frame = stack[i];
if (frame.command === 2) {
frame.index = Infinity;
}
if (frame === lastFrame) break;
}
return true;
};
exports.cut = cut;
const call = X => (env, goals) => {
let term = X,
name,
args;
// TODO: add processing of arrays of goals
if ((0, _env.isVariable)(X)) {
if (!X.isBound(env)) return false;
term = X.get(env);
}
if (typeof term == 'string') {
name = term;
} else {
if (!term || typeof term != 'object') return false;
name = term.name;
args = term.args;
if ((0, _env.isVariable)(name)) {
name = name.get(env);
}
if (typeof name != 'string') return false;
if ((0, _env.isVariable)(args)) {
args = args.get(env);
}
if (args && !Array.isArray(args)) return false;
}
return {
terms: [{
name,
args
}],
index: 0,
next: goals
};
};
exports.call = call;
const isBound = (...args) => env => args.every(V => (0, _env.isVariable)(V) && V.isBound(env));
exports.isBound = isBound;
const head = (...args) => ({
args
});
exports.head = head;
const term = (name, ...args) => ({
name,
args
});
exports.term = term;
class Tail {
constructor(value) {
this.value = value;
}
}
const rest = list => new Tail(list);
exports.rest = rest;
const list = (...args) => {
if (!args.length) return null;
let list = null,
startFrom = args.length - 1;
const last = args[startFrom];
if (last instanceof Tail) {
--startFrom;
list = last.value;
}
for (let i = startFrom; i >= 0; --i) {
const value = args[i];
if (value instanceof Tail) throw new Error('list cannot contain a tail argument in the middle');
list = {
value,
next: list
};
}
return list;
};
exports.list = list;
const listHead = (...args) => {
if (args.length < 2) throw new Error('list constructor cannot have less then 2 elements');
let startFrom = args.length - 1,
list = args[startFrom];
for (let i = startFrom - 1; i >= 0; --i) {
list = {
value: args[i],
next: list
};
}
return list;
};
// rules
exports.listHead = listHead;
const rules = exports.rules = {
// types
isVar: X => [head(X), env => !X.isBound(env)],
isNonVar: X => [head(X), env => X.isBound(env)],
isNumber: X => [head(X), env => X.isBound(env) && typeof X.get(env) == 'number'],
isString: X => [head(X), env => X.isBound(env) && typeof X.get(env) == 'string'],
isNull: X => [head(X), env => X.isBound(env) && X.get(env) === null],
isUndefined: X => [head(X), env => X.isBound(env) && X.get(env) === undefined],
isArray: X => [head(X), env => X.isBound(env) && Array.isArray(X.get(env))],
// equality
eq: X => head(X, X),
notEq: [(X, ...sys) => [head(X, X), cut(sys), fail], [_env._]],
// unify is eq
// control predicates
call: X => [head(X), call(X)],
not: [(X, ...sys) => [head(X), call(X), cut(sys), fail], () => [head()]],
isUnifiable: (X, Y) => [head(X, Y), term('not', term('not', term('eq', [X, Y])))],
// notUnifiable is notEq
conjunction: [() => [head(null)], (X, Xt) => [head(listHead(X, Xt)), call(X), term('conjunction', Xt)]],
disjunction: [X => [head(listHead(X, _env._)), call(X)], Xt => [head(listHead(_env._, Xt)), term('disjunction', Xt)]],
true: () => [head()],
once: (X, ...sys) => [head(X), call(X), cut(sys)],
// meta predicates
// apply, applyp
// extended logic
counterExample: (A, B) => [head(A, B), call(A), term('not', B)],
implies: (A, B) => [head(A, B), term('not', term('counterExample', A, B))],
// second-order logic
// map, filter, foldl, foldr, compose, converse
map: [() => [head(_env._, null, null)], (F, X, Xt, Y, Yt) => [head(F, listHead(X, Xt), listHead(Y, Yt)), call(term(F, X, Y)), term('map', Xt, Yt)]],
filter: [() => [head(_env._, null, null)], (P, X, Xt, Yt) => [head(P, listHead(X, Xt), listHead(X, Yt)), term(P, X), term('filter', Xt, Yt)], (P, X, Xt, Yt) => [head(P, listHead(X, Xt), listHead(X, Yt)), term('not', term(P, X)), term('filter', Xt, Yt)]],
foldl: [A => [head(_env._, A, null, A)], (F, A, X, Xt, O, B) => [head(F, A, listHead(X, Xt), Yt), call(term(F, A, X, B)), term('foldl', F, B, Xt, O)]],
foldr: [A => [head(_env._, A, null, A)], (F, A, X, Xt, O, T) => [head(F, A, listHead(X, Xt), O), term('foldr', F, A, Xt, T), call(term(F, X, T, O))]],
compose: (F, G, X, O, T) => [head(F, G, X, O), call(term(G, X, T)), call(term(F, T, O))],
converse: (F, X, Y, O) => [head(F, X, Y, O), call(term(F, Y, X, O))]
};
rules.unify = rules.eq;
rules.notUnifiable = rules.notEq;
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _unify = _interopRequireWildcard(require("deep6/unify.js"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
let counter = 0;
const generateVariables = count => {
const t = [];
for (let i = 0; i < count; ++i) t.push(counter++);
return t.map(name => (0, _unify.variable)(Symbol(name)));
};
const prove = (rules, goals, env) => {
const stack = [{
goals
}];
main: while (stack.length) {
const frame = stack.pop();
if (frame.command) {
if (frame.command === 1) {
env.pop();
continue main;
}
while (frame.index < frame.ruleList.length) {
const rule = frame.ruleList[frame.index++],
vars = generateVariables(rule.length + 1),
terms = (typeof rule == 'function' ? rule : rule.goals)(...vars);
env.push();
if ((0, _unify.default)(terms[0].args || [], frame.args, env)) {
const newGoals = {
terms,
index: 1,
next: frame.goals
};
stack.push(frame, {
command: 1
}, {
goals: newGoals
});
env.bindVal(vars[vars.length - 1].name, frame);
continue main;
}
env.pop();
}
continue main;
}
let goals = frame.goals;
while (goals && goals.index >= goals.terms.length) {
goals = goals.next;
}
if (!goals) continue main;
let goal = goals.terms[goals.index++];
if (typeof goal == 'function') {
env.push();
let newGoals = goal(env, goals, stack);
if (newGoals || newGoals === null) {
newGoals && !newGoals.terms && (newGoals = goals);
stack.push({
command: 1
}, {
goals: newGoals
});
continue main;
}
--goals.index;
env.pop();
continue main;
}
if (typeof goal == 'string') {
goal = {
name: goal
};
}
let ruleList = rules[goal.name];
!Array.isArray(ruleList) && (ruleList = [ruleList]);
stack.push({
command: 2,
ruleList,
index: 0,
goals,
args: goal.args || []
});
}
};
const solve = (rules, name, args, callback) => {
const env = new _unify.Env();
env.openObjects = true;
const goals = {
terms: [{
name,
args
}, env => (callback(env), false)],
index: 0,
next: null
};
prove(rules, goals, env);
};
var _default = exports.default = solve;
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _unify = _interopRequireWildcard(require("deep6/unify.js"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
let counter = 0;
const generateVariables = count => {
const t = [];
for (let i = 0; i < count; ++i) t.push(counter++);
return t.map(name => (0, _unify.variable)(Symbol(name)));
};
const prove = async (rules, goals, env) => {
const stack = [{
goals
}];
main: while (stack.length) {
const frame = stack.pop();
if (frame.command) {
if (frame.command === 1) {
env.pop();
continue main;
}
while (frame.index < frame.ruleList.length) {
const rule = frame.ruleList[frame.index++],
vars = generateVariables(rule.length + 1),
terms = (typeof rule == 'function' ? rule : rule.goals)(...vars);
env.push();
if ((0, _unify.default)(terms[0].args || [], frame.args, env)) {
const newGoals = {
terms,
index: 1,
next: frame.goals
};
stack.push(frame, {
command: 1
}, {
goals: newGoals
});
env.bindVal(vars[vars.length - 1].name, frame);
continue main;
}
env.pop();
}
continue main;
}
let goals = frame.goals;
while (goals && goals.index >= goals.terms.length) {
goals = goals.next;
}
if (!goals) continue main;
let goal = goals.terms[goals.index++];
if (typeof goal == 'function') {
env.push();
let newGoals = await goal(env, goals, stack);
if (newGoals || newGoals === null) {
newGoals && !newGoals.terms && (newGoals = goals);
stack.push({
command: 1
}, {
goals: newGoals
});
continue main;
}
--goals.index;
env.pop();
continue main;
}
if (typeof goal == 'string') {
goal = {
name: goal
};
}
let ruleList = rules[goal.name];
!Array.isArray(ruleList) && (ruleList = [ruleList]);
stack.push({
command: 2,
ruleList,
index: 0,
goals,
args: goal.args || []
});
}
};
const solve = async (rules, name, args, callback) => {
const env = new _unify.Env();
env.openObjects = true;
const goals = {
terms: [{
name,
args
}, async env => (await callback(env), false)],
index: 0,
next: null
};
prove(rules, goals, env);
};
var _default = exports.default = solve;
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _unify = _interopRequireWildcard(require("deep6/unify.js"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
let counter = 0;
const generateVariables = count => {
const t = [];
for (let i = 0; i < count; ++i) t.push(counter++);
return t.map(name => (0, _unify.variable)(Symbol(name)));
};
async function* prove(rules, goals, env) {
const stack = [{
goals
}];
main: while (stack.length) {
const frame = stack.pop();
if (frame.command) {
if (frame.command === 1) {
env.pop();
continue main;
}
while (frame.index < frame.ruleList.length) {
const rule = frame.ruleList[frame.index++],
vars = generateVariables(rule.length + 1),
terms = (typeof rule == 'function' ? rule : rule.goals)(...vars);
env.push();
if ((0, _unify.default)(terms[0].args || [], frame.args, env)) {
const newGoals = {
terms,
index: 1,
next: frame.goals
};
stack.push(frame, {
command: 1
}, {
goals: newGoals
});
env.bindVal(vars[vars.length - 1].name, frame);
continue main;
}
env.pop();
}
continue main;
}
let goals = frame.goals;
while (goals && goals.index >= goals.terms.length) {
goals = goals.next;
}
if (!goals) {
yield env;
continue main;
}
let goal = goals.terms[goals.index++];
if (typeof goal == 'function') {
env.push();
let newGoals = await goal(env, goals, stack);
if (newGoals || newGoals === null) {
newGoals && !newGoals.terms && (newGoals = goals);
stack.push({
command: 1
}, {
goals: newGoals
});
continue main;
}
--goals.index;
env.pop();
continue main;
}
if (typeof goal == 'string') {
goal = {
name: goal
};
}
let ruleList = rules[goal.name];
!Array.isArray(ruleList) && (ruleList = [ruleList]);
stack.push({
command: 2,
ruleList,
index: 0,
goals,
args: goal.args || []
});
}
}
;
async function* generate(rules, name, args) {
const env = new _unify.Env();
env.openObjects = true;
const goals = {
terms: [{
name,
args
}],
index: 0,
next: null
};
yield* prove(rules, goals, env);
}
;
var _default = exports.default = generate;
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _unify = _interopRequireWildcard(require("deep6/unify.js"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
let counter = 0;
const generateVariables = count => {
const t = [];
for (let i = 0; i < count; ++i) t.push(counter++);
return t.map(name => (0, _unify.variable)(Symbol(name)));
};
function* prove(rules, goals, env) {
const stack = [{
goals
}];
main: while (stack.length) {
const frame = stack.pop();
if (frame.command) {
if (frame.command === 1) {
env.pop();
continue main;
}
while (frame.index < frame.ruleList.length) {
const rule = frame.ruleList[frame.index++],
vars = generateVariables(rule.length + 1),
terms = (typeof rule == 'function' ? rule : rule.goals)(...vars);
env.push();
if ((0, _unify.default)(terms[0].args || [], frame.args, env)) {
const newGoals = {
terms,
index: 1,
next: frame.goals
};
stack.push(frame, {
command: 1
}, {
goals: newGoals
});
env.bindVal(vars[vars.length - 1].name, frame);
continue main;
}
env.pop();
}
continue main;
}
let goals = frame.goals;
while (goals && goals.index >= goals.terms.length) {
goals = goals.next;
}
if (!goals) {
yield env;
continue main;
}
let goal = goals.terms[goals.index++];
if (typeof goal == 'function') {
env.push();
let newGoals = goal(env, goals, stack);
if (newGoals || newGoals === null) {
newGoals && !newGoals.terms && (newGoals = goals);
stack.push({
command: 1
}, {
goals: newGoals
});
continue main;
}
--goals.index;
env.pop();
continue main;
}
if (typeof goal == 'string') {
goal = {
name: goal
};
}
let ruleList = rules[goal.name];
!Array.isArray(ruleList) && (ruleList = [ruleList]);
stack.push({
command: 2,
ruleList,
index: 0,
goals,
args: goal.args || []
});
}
}
;
function* generate(rules, name, args) {
const env = new _unify.Env();
env.openObjects = true;
const goals = {
terms: [{
name,
args
}],
index: 0,
next: null
};
yield* prove(rules, goals, env);
}
;
var _default = exports.default = generate;