×ばつ callback/generator). Useful for pattern matching with extraction, constraint search, type inference, planners, expert systems, and policy checks. ESM, single runtime dependency (deep6). - compile prolog · 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

compile prolog

Eugene Lazutkin edited this page May 11, 2026 · 2 revisions

compile-prolog

The strict-Prolog tagged-template front-end parses standard Prolog source into the same IR the per-clause compile-overview DSL produces. Two tags, one whole-program parser, one single-clause parser; both share the lexer + Pratt machinery in src/compile/parse/ and lower through the same lower.js.

The internal design (Pratt parser, goalize, helper-rule minting for ;/->, polymorphic-tag scaffolding) is in dev-docs/prolog-parser.md. This page is the user-facing reference.

Quick start

import solve from 'yopl';
import {variable as v} from 'deep6/env.js';
import assemble from 'deep6/traverse/assemble.js';
import {prolog} from 'yopl/compile/prolog';
import {rules as systemRules} from 'yopl/rules/system.js';
const rules = {
 ...systemRules,
 ...prolog`
 member(X, [X | _]).
 member(X, [_ | T]) :- member(X, T).
 `
};
const X = v('X');
const list = {value: 1, next: {value: 2, next: {value: 3, next: null}}};
solve(rules, 'member', [X, list], env => console.log(assemble(X, env)));
// 1, 2, 3

The tag returns the lowered runtime Rules dict ready to spread into the rule database. The parsed IR is also attached under Symbol.for('yopl.ir') (importable as IR from yopl/compile) for inspection / cross-validation / codegen.

Tags

prolog\...source...``

Parse a multi-clause program. Returns the lowered Rules dict by default; pass {lower: false} (via the configurator form below) to get the IR Rules dict instead.

import {prolog} from 'yopl/compile/prolog';
const rules = prolog`
 parent(tom, bob).
 parent(tom, liz).
 parent(bob, ann).

 ancestor(X, Y) :- parent(X, Y).
 ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
`;

prologClause\...clause...``

Parse a single clause. Useful for incrementally building rule libraries or for IR inspection. The trailing . is auto-appended if absent. Returns {name, head, body, helpers?}name is the head functor; helpers is present when the body produced $or_N / $ite_N helper rules (see Body operators below).

import {prologClause} from 'yopl/compile/prolog';
prologClause`green`; // → {name: 'green', head: [], body: []}
prologClause`member(X, [X | _])`;
// → {name: 'member', head: [Var('X'), Cons(Var('X'), Wild())], body: []}

Three call shapes

Both tags are polymorphic — the same identifier handles tag form, function form, and configurator form (lifted from dollar-shell's bqSpawn).

Tag form

prolog`source`; // tagged template — parses the literal

Function form (prolog(source [, options]))

Parses a string. Useful for source already in a variable, e.g. file content. (For loading files from disk, prefer the dedicated prologFile loader below.)

const src = `
 parent(tom, bob).
 parent(bob, ann).
`;
const rules = prolog(src);
const ir = prolog(src, {lower: false});

Configurator form (prolog(options) / prolog.with(options))

Returns a fresh tag closing over merged options. Per-invocation scope — options never leak to the next call.

const irOnly = prolog.with({lower: false});
const ir = irOnly`foo(X) :- bar(X).`;
// Equivalent:
const ir2 = prolog({lower: false})`foo(X) :- bar(X).`;

Options

Configurator (prolog(opts) / prolog.with(opts)) accepts:

Option Type Default Effect
lower boolean true When false, the tag returns the IR Rules dict instead of the lowered runtime fns dict.
operators PrologOpDecl[] Extra operators added on top of the default body op table (per-invocation scope).
sourceMap boolean false When true, attach source: {file?, line, col} metadata to each parsed Clause IR. See Source maps.
file string Source file path / URL to attach to each clause's source.file. Only takes effect when sourceMap is true.

prologFile / prologFileAsync

Filesystem-backed convenience loaders for .pl files. Live in their own subpath so browser bundles without filesystem access don't pull in node:fs.

import {prologFile, prologFileAsync} from 'yopl/compile/prolog/file';
const rules = prologFile(new URL('./family.pl', import.meta.url));
// Async — right default for parallel loading via Promise.all:
const [a, b] = await Promise.all([prologFileAsync(urlA), prologFileAsync(urlB)]);

Both wrap node:fs readFileSync / fs/promises.readFile and forward to prolog(source, options). UTF-8 is assumed. Works in Node, Bun, and Deno (Deno via its Node-compat layer).

When sourceMap: true is passed, file defaults to url.href (or the string path) so source positions carry the URL through to validator issues and runtime error reports. Override by setting options.file explicitly.

Body operators

Beyond what the per-clause clause\...`` DSL supports, body context recognizes operator-precedence syntax:

Op Priority Type Meaning
, 1000 xfy Conjunction (sequencing).
; 1100 xfy Disjunction — mints a fresh $or_<N> helper rule per disjunction site.
-> 1050 xfy If-then — mints $ite_<N> helper. With ; for else: Cond -> Then ; Else.
\+ 900 fy Negation as failure — desugars to Call('not', [G]) (target alias to not/1).

Disjunction example:

prolog`
 greet(X) :- (X = alice ; X = bob), write(X).
`;
// transparent disjunction; the parser mints a $or_<N> helper rule and
// rewrites the body to call it

Cut inside ; / -> branches scopes opaquely to the helper — transparent cut requires a dedicated Disjunction IR kind and is deferred until a real use case appears.

Goal aliases

Symbolic operators in body position auto-alias to yopl runtime predicates (applied at goalize time, not in the op table — so foo(X = Y) in arg position emits Compound('=', ...) while top-level X = Y in body emits Call('eq', ...)):

Source op Body call
= eq
\= notEq
== eq
\== notEq
< lt
> gt
=< le
>= ge

Other body-position operators (e.g. is, =:=, =\= from rules-math) are regular rules and resolve through the normal call mechanism.

Operators

Define custom operators per-invocation via op/3 (ISO) or op/4 (yopl-canonical aliasing) directives:

prolog`
 :- op(700, xfx, =>). % ISO form — emits Compound('=>', [A, B])
 :- op(700, xfx, ===, eq). % op/4 form — body-position rewrites to Call('eq', [A, B])

 triple(X, Y, Z) :- X => Y, Y === Z.
`;

Or pre-load them via the configurator's operators option:

const tag = prolog.with({
 operators: [
 {priority: 700, type: 'xfx', name: '=>'},
 {priority: 700, type: 'xfx', name: '===', target: 'eq'}
 ]
});
const rules = tag`
 triple(X, Y, Z) :- X => Y, Y === Z.
`;

PrologOpDecl shape: {name: string, priority: number, type: 'xfx' | 'xfy' | 'yfx' | 'fx' | 'fy', target?: string}.

Per-invocation scope: directives mutate a clone of the input op table; the caller's table is never touched, so each prolog\...`` evaluation starts from a clean slate.

Bare lowercase atoms

In arg position, bare lowercase identifiers parse as Lit(name) (string-atom literals) — matches yopl's runtime convention where atoms are JS strings:

prolog`color(red).`; // → {color: Rule with head [Lit('red')]}

The per-clause clause\...`DSL rejects bare atoms (must quote as"red"or useLit('red')` interpolation); the strict-Prolog parser accepts them per ISO conventions.

Source maps

When sourceMap: true is set on the configurator, each parsed Clause IR carries an optional source: {file?, line, col} annotation pointing at the first token of the clause head. The lowered runtime fn carries source as a non-enumerable property; validator issues for per-clause errors carry it on the Issue object and tag the human-readable message with [file:line:col].

import {prolog} from 'yopl/compile/prolog';
import {validate} from 'yopl/compile';
const ir = prolog.with({lower: false, sourceMap: true, file: 'rules.pl'})`
 member(X, [X | _]).
 member(X, [_ | T]) :- member(X, T).
`;
ir.member.clauses[0].source;
// → {file: 'rules.pl', line: 2, col: 3}

Default false to skip the per-clause memory cost (~50 bytes per clause) in production. Flip on under a debug gate during development.

The per-clause clause\...`` DSL always carries source (negligible cost per single-clause tag).

IR equivalence with the clause DSL

For the shared subset, prolog\...`and the per-clauseclause`...`DSL emit byte-identical IR. Thetests/test-prolog-dogfood.jssuite asserts this across representative patterns (simple facts, multi-clause rules with cut + fail, list patterns, dynamic dispatch, inline JS factories). Thebench/bench-parity.js` workload confirms behavioral parity at the runtime level.

The prolog form additionally handles body operators, op/3 / op/4 directives, goal aliases, bare lowercase atoms in arg positions, and file-name source-mapping — features the per-clause DSL doesn't expose because they only make sense at the program level.

See also

Clone this wiki locally

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