eyeprolog 1.5.8 → 1.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +18 -11
- package/src/arithmetic-host.js +116 -0
- package/src/{atts.js → atts-host.js} +3 -1
- package/src/charsio-host.js +380 -0
- package/src/crypto-host.js +628 -0
- package/src/dcgs-host.js +37 -0
- package/src/dif-host.js +8 -0
- package/src/eyelet-host.js +71 -0
- package/src/files-host.js +174 -0
- package/src/format-host.js +52 -0
- package/src/host-utils.js +67 -0
- package/src/iso.js +5 -20
- package/src/iso_ext-host.js +180 -0
- package/src/lib/arithmetic.pl +32 -0
- package/src/lib/charsio.pl +60 -1
- package/src/lib/clpb.pl +1 -1
- package/src/lib/crypto.pl +91 -0
- package/src/lib/dcgs.pl +32 -3
- package/src/lib/debug.pl +2 -9
- package/src/lib/files.pl +98 -6
- package/src/lib/gensym.pl +2 -2
- package/src/lib/iso_ext.pl +54 -8
- package/src/lib/lists.pl +8 -0
- package/src/lib/pio.pl +9 -2
- package/src/lib/random.pl +2 -2
- package/src/lib/time.pl +8 -2
- package/src/library-autoload-index.js +49 -5
- package/src/lists-host.js +29 -0
- package/src/os-host.js +93 -0
- package/src/pio-host.js +30 -0
- package/src/random-host.js +29 -0
- package/src/standard-library.js +63 -27
- package/src/tabling-host.js +22 -0
- package/src/time-host.js +36 -0
- package/test/run-architecture.mjs +71 -0
- package/test/run-regression.mjs +151 -12
- package/test/scryer-library-exports.json +391 -0
- package/the-art-of-eyeprolog.md +54 -33
- package/why-eyeprolog.md +16 -7
- package/src/library-host.js +0 -576
- package/src/scryer-compat.js +0 -339
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ Normal mode executes top-level `Conclusion :+ Premise` rules through the bundled
|
|
|
61
61
|
|
|
62
62
|
Bundled `src/lib/` predicates autoload in files, CLI/API goals, and the REPL; use `--no-autoload` to require explicit imports, and explicitly import libraries that introduce operators before using their syntax. See [*The Art of EyeProlog*](the-art-of-eyeprolog.md) for the full semantics.
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
All 32 bundled modules that overlap Scryer's current `src/lib/` surface now cover Scryer's exported predicates, while the stricter Trealla/Scryer intersection remains a separate portability profile. Runtime-dependent library primitives follow one ownership rule: `src/lib/<module>.pl` calls private adapters from `src/<module>-host.js`; pure Prolog libraries need no host file. This includes the full Scryer `library(files)` surface, Scryer character-list paths in `library(pio)` with atom-path compatibility, chars/UTF-8/term/Base64 helpers, the completed arithmetic/time/`iso_ext` surfaces, and the full Scryer `library(crypto)`. Filesystem and OS side effects require Node; crypto uses Node's backend where needed, with Web Crypto also used for secure random bytes when available. Non-integral rational conversions remain structural `rdiv(Numerator,Denominator)` terms until rational numbers become processor numeric values.
|
|
65
65
|
|
|
66
66
|
## Links
|
|
67
67
|
|
package/package.json
CHANGED
package/src/ARCHITECTURE.md
CHANGED
|
@@ -15,8 +15,9 @@ higher-level frontends.
|
|
|
15
15
|
`cleanup.js` owns lifecycle-aware disposal of protected builtin
|
|
16
16
|
iterators and registers the normal-profile cleanup controls without making
|
|
17
17
|
`solver.js` depend back on the language registry.
|
|
18
|
-
4. **Language services** — `iso.js`, `iso-arithmetic.js`, `dcg.js`, `atts.js`,
|
|
19
|
-
`
|
|
18
|
+
4. **Language services** — `iso.js`, `iso-arithmetic.js`, `dcg.js`, `atts-host.js`,
|
|
19
|
+
`standard-library.js`, the module-owned `src/<module>-host.js` adapters, and
|
|
20
|
+
the public Prolog modules under `src/lib/`.
|
|
20
21
|
5. **Frontends/tools** — `execute.js`, `repl.js`, `cli.js`, `quads.js`,
|
|
21
22
|
`explain.js`, and the playground worker.
|
|
22
23
|
|
|
@@ -40,7 +41,7 @@ annotations. Descriptors can optionally define logical subsumption so the
|
|
|
40
41
|
store retains only its strongest pending constraints. `dif/2` is the first
|
|
41
42
|
descriptor-based user of this mechanism.
|
|
42
43
|
|
|
43
|
-
`atts.js` layers Prolog-visible attributed variables over that same persistent
|
|
44
|
+
`atts-host.js` layers Prolog-visible attributed variables over that same persistent
|
|
44
45
|
environment. Per-module attributes follow the current variable representative.
|
|
45
46
|
When an attributed variable is about to be bound, the solver runs that module's
|
|
46
47
|
`verify_attributes/3` against the still-unbound representative; only a successful
|
|
@@ -51,9 +52,9 @@ protocol directly for its domains and propagators.
|
|
|
51
52
|
`Env` also owns the small generic backtrackable blackboard used by Scryer-style
|
|
52
53
|
libraries. Blackboard maps are shared by clones and copied only on write, so
|
|
53
54
|
`bb_b_put/2` follows ordinary Prolog backtracking without any CLP(Z)-specific
|
|
54
|
-
state in the VM.
|
|
55
|
-
`library(
|
|
56
|
-
|
|
55
|
+
state in the VM. Its private runtime bridge is owned by `iso_ext-host.js`, matching
|
|
56
|
+
Scryer's canonical `library(iso_ext)` ownership; `library(debug)` may re-export
|
|
57
|
+
that interface for EyeProlog compatibility without owning another implementation.
|
|
57
58
|
|
|
58
59
|
`source-expansion.js` is the explicit compile-time execution boundary. When an
|
|
59
60
|
already-loaded `term_expansion/2` or `goal_expansion/2` hook exists, program
|
|
@@ -65,11 +66,17 @@ import graph acyclic while avoiding a second meta-interpreter solely for source
|
|
|
65
66
|
expansion. `expansion-builtins.js` provides the lower-level `expand_term/2` DCG
|
|
66
67
|
service without depending on `Solver`.
|
|
67
68
|
|
|
68
|
-
The JavaScript runtime stays flat directly under `src/`;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
The JavaScript runtime stays flat directly under `src/`; `src/lib/` contains the
|
|
70
|
+
public Prolog library sources. Runtime-dependent library primitives follow a
|
|
71
|
+
one-module/one-host convention: a private `eyeprolog__*` adapter referenced by
|
|
72
|
+
`src/lib/foo.pl` must be registered from `src/foo-host.js`. Pure Prolog modules,
|
|
73
|
+
such as `library(freeze)`, intentionally have no host file. `standard-library.js`
|
|
74
|
+
registers these module hosts without owning their semantics. The architecture
|
|
75
|
+
test rejects JavaScript import cycles, verifies private adapter ownership, freezes
|
|
76
|
+
the current Scryer export surface for all 32 overlapping bundled modules, and
|
|
77
|
+
rejects the retired `library-host.js` and `scryer-compat.js` grab bags. Cleanup
|
|
78
|
+
lifecycle hooks are installed from the public API and CLI entry paths, so the
|
|
79
|
+
execution layer remains acyclic.
|
|
73
80
|
|
|
74
81
|
## Performance rule
|
|
75
82
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Runtime arithmetic conversions used by library(arithmetic).
|
|
2
|
+
// Pure arithmetic relations stay in src/lib/arithmetic.pl.
|
|
3
|
+
|
|
4
|
+
import { COMPOUND, NUMBER, VAR, compound, copyResolved, deref, numberTerm, unify } from './term.js';
|
|
5
|
+
import { PrologError } from './errors.js';
|
|
6
|
+
|
|
7
|
+
function gcdBigInt(a, b) {
|
|
8
|
+
a = a < 0n ? -a : a;
|
|
9
|
+
b = b < 0n ? -b : b;
|
|
10
|
+
while (b !== 0n) [a, b] = [b, a % b];
|
|
11
|
+
return a;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function approximateRational(value, tolerance = 1e-6) {
|
|
15
|
+
if (!Number.isFinite(value)) throw new PrologError('type_error(number)', numberTerm(String(value)));
|
|
16
|
+
if (!Number.isFinite(tolerance) || tolerance < 0) throw new PrologError('domain_error(not_less_than_zero)', numberTerm(String(tolerance)));
|
|
17
|
+
if (Number.isInteger(value) && Number.isSafeInteger(value)) return [BigInt(value), 1n];
|
|
18
|
+
const sign = value < 0 ? -1n : 1n;
|
|
19
|
+
const target = Math.abs(value);
|
|
20
|
+
let x = target;
|
|
21
|
+
let hPrev2 = 0, hPrev1 = 1, kPrev2 = 1, kPrev1 = 0;
|
|
22
|
+
for (let i = 0; i < 96; i++) {
|
|
23
|
+
const a = Math.floor(x);
|
|
24
|
+
const h = a * hPrev1 + hPrev2;
|
|
25
|
+
const k = a * kPrev1 + kPrev2;
|
|
26
|
+
if (!Number.isSafeInteger(h) || !Number.isSafeInteger(k) || k === 0) break;
|
|
27
|
+
if (Math.abs(target - h / k) <= tolerance) {
|
|
28
|
+
const n = sign * BigInt(h), d = BigInt(k), g = gcdBigInt(n, d);
|
|
29
|
+
return [n / g, d / g];
|
|
30
|
+
}
|
|
31
|
+
const fraction = x - a;
|
|
32
|
+
if (fraction === 0) {
|
|
33
|
+
const n = sign * BigInt(h), d = BigInt(k), g = gcdBigInt(n, d);
|
|
34
|
+
return [n / g, d / g];
|
|
35
|
+
}
|
|
36
|
+
[hPrev2, hPrev1] = [hPrev1, h];
|
|
37
|
+
[kPrev2, kPrev1] = [kPrev1, k];
|
|
38
|
+
x = 1 / fraction;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Exact decimal-token fallback for continued fractions that exceed safe
|
|
42
|
+
// JavaScript integer intermediates.
|
|
43
|
+
const text = target.toString();
|
|
44
|
+
const match = /^(\d+)(?:\.(\d*))?(?:e([+-]?\d+))?$/i.exec(text);
|
|
45
|
+
if (!match) throw new PrologError('representation_error(rational)');
|
|
46
|
+
const fractionDigits = match[2] ?? '';
|
|
47
|
+
const exponent = Number(match[3] ?? 0);
|
|
48
|
+
let numerator = BigInt(match[1] + fractionDigits);
|
|
49
|
+
let denominator = 10n ** BigInt(fractionDigits.length);
|
|
50
|
+
if (exponent > 0) numerator *= 10n ** BigInt(exponent);
|
|
51
|
+
else if (exponent < 0) denominator *= 10n ** BigInt(-exponent);
|
|
52
|
+
numerator *= sign;
|
|
53
|
+
const g = gcdBigInt(numerator, denominator);
|
|
54
|
+
return [numerator / g, denominator / g];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function rationalParts(term, env, tolerance = 1e-6) {
|
|
58
|
+
const value = deref(term, env);
|
|
59
|
+
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
60
|
+
if (value.type === NUMBER) {
|
|
61
|
+
if (/^-?\d+$/.test(value.name)) return [BigInt(value.name), 1n];
|
|
62
|
+
return approximateRational(Number(value.name), tolerance);
|
|
63
|
+
}
|
|
64
|
+
if (value.type === COMPOUND && value.name === 'rdiv' && value.arity === 2) {
|
|
65
|
+
const numerator = deref(value.args[0], env), denominator = deref(value.args[1], env);
|
|
66
|
+
if (numerator.type !== NUMBER || denominator.type !== NUMBER ||
|
|
67
|
+
!/^-?\d+$/.test(numerator.name) || !/^-?\d+$/.test(denominator.name)) {
|
|
68
|
+
throw new PrologError('type_error(rational)', copyResolved(value, env));
|
|
69
|
+
}
|
|
70
|
+
let n = BigInt(numerator.name), d = BigInt(denominator.name);
|
|
71
|
+
if (d === 0n) throw new PrologError('evaluation_error(zero_divisor)');
|
|
72
|
+
if (d < 0n) { n = -n; d = -d; }
|
|
73
|
+
const g = gcdBigInt(n, d);
|
|
74
|
+
return [n / g, d / g];
|
|
75
|
+
}
|
|
76
|
+
throw new PrologError('type_error(number)', copyResolved(value, env));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function rationalTerm(numerator, denominator) {
|
|
80
|
+
return denominator === 1n
|
|
81
|
+
? numberTerm(numerator.toString())
|
|
82
|
+
: compound('rdiv', [numberTerm(numerator.toString()), numberTerm(denominator.toString())]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function* numberToRationalBuiltin({ goal, env }) {
|
|
86
|
+
const [numerator, denominator] = rationalParts(goal.args[0], env);
|
|
87
|
+
const next = env.clone();
|
|
88
|
+
if (unify(goal.args[1], rationalTerm(numerator, denominator), next)) yield next;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function* numberToRationalToleranceBuiltin({ goal, env }) {
|
|
92
|
+
const epsilonTerm = deref(goal.args[0], env);
|
|
93
|
+
if (epsilonTerm.type === VAR) throw new PrologError('instantiation_error');
|
|
94
|
+
if (epsilonTerm.type !== NUMBER) throw new PrologError('type_error(number)', copyResolved(epsilonTerm, env));
|
|
95
|
+
const epsilon = Number(epsilonTerm.name);
|
|
96
|
+
if (!Number.isFinite(epsilon)) throw new PrologError('type_error(number)', copyResolved(epsilonTerm, env));
|
|
97
|
+
if (epsilon < 0) throw new PrologError('domain_error(not_less_than_zero)', copyResolved(epsilonTerm, env));
|
|
98
|
+
const [numerator, denominator] = rationalParts(goal.args[1], env, epsilon);
|
|
99
|
+
const next = env.clone();
|
|
100
|
+
if (unify(goal.args[2], rationalTerm(numerator, denominator), next)) yield next;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function* rationalPartsBuiltin({ goal, env }) {
|
|
104
|
+
const [numerator, denominator] = rationalParts(goal.args[0], env);
|
|
105
|
+
const next = env.clone();
|
|
106
|
+
if (unify(goal.args[1], numberTerm(numerator.toString()), next) &&
|
|
107
|
+
unify(goal.args[2], numberTerm(denominator.toString()), next)) yield next;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const arithmeticHostBuiltins = {
|
|
111
|
+
register(registry) {
|
|
112
|
+
registry.add('eyeprolog__number_to_rational', 2, numberToRationalBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
113
|
+
registry.add('eyeprolog__number_to_rational', 3, numberToRationalToleranceBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
114
|
+
registry.add('eyeprolog__rational_numerator_denominator', 3, rationalPartsBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
115
|
+
},
|
|
116
|
+
};
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
properListItems, unify, variable,
|
|
12
12
|
} from './term.js';
|
|
13
13
|
import { PrologError } from './errors.js';
|
|
14
|
+
import { callResidueVarsBuiltin } from './iso.js';
|
|
14
15
|
|
|
15
16
|
function callerModule(goal) {
|
|
16
17
|
return goal.module ?? 'user';
|
|
@@ -217,8 +218,9 @@ function* delFromAttrListBuiltin({ goal, env }) {
|
|
|
217
218
|
yield next;
|
|
218
219
|
}
|
|
219
220
|
|
|
220
|
-
export const
|
|
221
|
+
export const attsHostBuiltins = {
|
|
221
222
|
register(registry) {
|
|
223
|
+
registry.add('eyeprolog__call_residue_vars', 2, callResidueVarsBuiltin, { eyePrologLibrary: true });
|
|
222
224
|
registry.add('put_atts', 2, putAttsBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
223
225
|
registry.add('get_atts', 2, getAttsBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
224
226
|
// Scryer's CLP(Z) sources use these through goal expansion. EyeProlog
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
// Runtime character parsing/writing and character classification for
|
|
2
|
+
// library(charsio). Portable UTF-8 relations remain in src/lib/charsio.pl.
|
|
3
|
+
|
|
4
|
+
import { createParserOperatorState, parseTermText } from './parser.js';
|
|
5
|
+
import { BufferCtor } from './platform.js';
|
|
6
|
+
import {
|
|
7
|
+
ATOM, COMPOUND, NUMBER, VAR, atom, compound, copyResolved, deref,
|
|
8
|
+
listFromItems, numberTerm, properListItems, unify, variable,
|
|
9
|
+
} from './term.js';
|
|
10
|
+
import { PrologError } from './errors.js';
|
|
11
|
+
import { formatTermForWrite } from './write.js';
|
|
12
|
+
import { characterListText, chars } from './host-utils.js';
|
|
13
|
+
|
|
14
|
+
let readFresh = 0;
|
|
15
|
+
function activeCharConverter(solver) {
|
|
16
|
+
if (solver.prologFlags.get('char_conversion')?.value?.name !== 'on' || solver.charConversions.size === 0) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return (character) => solver.charConversions.get(character) ?? character;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function convertTextCharacters(text, solver) {
|
|
23
|
+
const convert = activeCharConverter(solver);
|
|
24
|
+
if (convert == null) return text;
|
|
25
|
+
let result = '', quote = null;
|
|
26
|
+
for (let i = 0; i < text.length; i++) {
|
|
27
|
+
const ch = text[i], next = text[i + 1];
|
|
28
|
+
if (quote != null) {
|
|
29
|
+
result += ch;
|
|
30
|
+
if (ch === '\\') {
|
|
31
|
+
if (i + 1 < text.length) result += text[++i];
|
|
32
|
+
} else if (ch === quote && next === quote) {
|
|
33
|
+
result += text[++i];
|
|
34
|
+
} else if (ch === quote) {
|
|
35
|
+
quote = null;
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (ch === "'" || ch === '"') {
|
|
40
|
+
quote = ch;
|
|
41
|
+
result += ch;
|
|
42
|
+
} else {
|
|
43
|
+
result += convert(ch);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function scopeReadTerm(term) {
|
|
50
|
+
const scope = ++readFresh;
|
|
51
|
+
const bySourceName = new Map();
|
|
52
|
+
const variables = [];
|
|
53
|
+
const copy = (item) => {
|
|
54
|
+
if (item.type === VAR) {
|
|
55
|
+
let record = bySourceName.get(item.name);
|
|
56
|
+
if (record == null) {
|
|
57
|
+
const scoped = variable(`\u0000chars:${scope}:${variables.length}`);
|
|
58
|
+
scoped.displayName = item.name;
|
|
59
|
+
record = {
|
|
60
|
+
sourceName: item.name,
|
|
61
|
+
term: scoped,
|
|
62
|
+
count: 0,
|
|
63
|
+
anonymous: item.name.startsWith('__anon'),
|
|
64
|
+
};
|
|
65
|
+
bySourceName.set(item.name, record);
|
|
66
|
+
variables.push(record);
|
|
67
|
+
}
|
|
68
|
+
record.count++;
|
|
69
|
+
return record.term;
|
|
70
|
+
}
|
|
71
|
+
if (item.type !== COMPOUND) return item;
|
|
72
|
+
return compound(item.name, item.args.map(copy));
|
|
73
|
+
};
|
|
74
|
+
return { term: copy(term), variables };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function readTextTerm(text, solver) {
|
|
78
|
+
const converted = convertTextCharacters(text, solver);
|
|
79
|
+
try {
|
|
80
|
+
const operatorState = createParserOperatorState(solver.program.operators.values(), false);
|
|
81
|
+
const parsed = parseTermText(converted, {
|
|
82
|
+
operatorState,
|
|
83
|
+
isoStrict: solver.isoStrict,
|
|
84
|
+
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
85
|
+
readTermEnd: converted.endsWith('.') ? converted.length - 1 : undefined,
|
|
86
|
+
});
|
|
87
|
+
return scopeReadTerm(parsed);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (error instanceof PrologError) throw error;
|
|
90
|
+
throw new PrologError('syntax_error(read_term)');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function requireOptionList(term, env, kind) {
|
|
95
|
+
const value = deref(term, env);
|
|
96
|
+
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
97
|
+
const options = properListItems(value, env);
|
|
98
|
+
if (options == null) throw new PrologError('type_error(list)', copyResolved(value, env));
|
|
99
|
+
for (const optionTerm of options) {
|
|
100
|
+
if (deref(optionTerm, env).type === VAR) throw new PrologError('instantiation_error');
|
|
101
|
+
}
|
|
102
|
+
return options.map((option) => deref(option, env));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function* readFromCharsBuiltin({ solver, goal, env }) {
|
|
106
|
+
const text = characterListText(goal.args[0], env);
|
|
107
|
+
const { term } = readTextTerm(text, solver);
|
|
108
|
+
const next = env.clone();
|
|
109
|
+
if (unify(goal.args[1], term, next)) yield next;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function* readTermFromCharsBuiltin({ solver, goal, env }) {
|
|
113
|
+
const text = characterListText(goal.args[0], env);
|
|
114
|
+
const options = requireOptionList(goal.args[2], env, 'read');
|
|
115
|
+
for (const option of options) {
|
|
116
|
+
if (option.type !== COMPOUND || option.arity !== 1 ||
|
|
117
|
+
!['variables', 'variable_names', 'singletons'].includes(option.name)) {
|
|
118
|
+
throw new PrologError('domain_error(read_option)', copyResolved(option, env));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const { term, variables } = readTextTerm(text, solver);
|
|
122
|
+
const next = env.clone();
|
|
123
|
+
if (!unify(goal.args[1], term, next)) return;
|
|
124
|
+
for (const option of options) {
|
|
125
|
+
const value = option.name === 'variables'
|
|
126
|
+
? listFromItems(variables.map((item) => item.term))
|
|
127
|
+
: option.name === 'variable_names'
|
|
128
|
+
? listFromItems(variables
|
|
129
|
+
.filter((item) => !item.anonymous)
|
|
130
|
+
.map((item) => compound('=', [atom(item.sourceName), item.term])))
|
|
131
|
+
: listFromItems(variables
|
|
132
|
+
.filter((item) => !item.anonymous && item.count === 1)
|
|
133
|
+
.map((item) => compound('=', [atom(item.sourceName), item.term])));
|
|
134
|
+
if (!unify(option.args[0], value, next)) return;
|
|
135
|
+
}
|
|
136
|
+
yield next;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function optionBoolean(term, env, option) {
|
|
140
|
+
const value = deref(term, env);
|
|
141
|
+
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
142
|
+
if (value.type !== ATOM || !['true', 'false'].includes(value.name)) {
|
|
143
|
+
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
144
|
+
}
|
|
145
|
+
return value.name === 'true';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function variableNamesOption(term, env, option) {
|
|
149
|
+
const value = deref(term, env);
|
|
150
|
+
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
151
|
+
const items = properListItems(value, env);
|
|
152
|
+
if (items == null) throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
153
|
+
const names = new Map();
|
|
154
|
+
for (const itemTerm of items) {
|
|
155
|
+
const item = deref(itemTerm, env);
|
|
156
|
+
if (item.type !== COMPOUND || item.name !== '=' || item.arity !== 2) {
|
|
157
|
+
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
158
|
+
}
|
|
159
|
+
const name = deref(item.args[0], env);
|
|
160
|
+
const target = deref(item.args[1], env);
|
|
161
|
+
if (name.type === VAR) throw new PrologError('instantiation_error');
|
|
162
|
+
if (name.type !== ATOM) throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
163
|
+
if (target.type === VAR && !names.has(target.name)) names.set(target.name, name.name);
|
|
164
|
+
}
|
|
165
|
+
return names;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function writeOptions(term, env, solver) {
|
|
169
|
+
const options = requireOptionList(term, env, 'write');
|
|
170
|
+
const result = {
|
|
171
|
+
quoted: false,
|
|
172
|
+
ignoreOps: false,
|
|
173
|
+
numbervars: false,
|
|
174
|
+
variableNames: new Map(),
|
|
175
|
+
compact: true,
|
|
176
|
+
minimalOperatorSpacing: true,
|
|
177
|
+
operatorAtomsAsArgs: true,
|
|
178
|
+
doubleQuotes: null,
|
|
179
|
+
};
|
|
180
|
+
for (const option of options) {
|
|
181
|
+
if (option.type !== COMPOUND || option.arity !== 1) {
|
|
182
|
+
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
183
|
+
}
|
|
184
|
+
if (option.name === 'quoted') result.quoted = optionBoolean(option.args[0], env, option);
|
|
185
|
+
else if (option.name === 'ignore_ops') result.ignoreOps = optionBoolean(option.args[0], env, option);
|
|
186
|
+
else if (option.name === 'numbervars') result.numbervars = optionBoolean(option.args[0], env, option);
|
|
187
|
+
else if (option.name === 'variable_names') result.variableNames = variableNamesOption(option.args[0], env, option);
|
|
188
|
+
else if (option.name === 'double_quotes' && !solver.isoStrict) result.doubleQuotes = optionBoolean(option.args[0], env, option);
|
|
189
|
+
else throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function* writeTermToCharsBuiltin({ solver, goal, env }) {
|
|
195
|
+
if (deref(goal.args[2], env).type !== VAR) {
|
|
196
|
+
throw new PrologError('uninstantiation_error', copyResolved(goal.args[2], env));
|
|
197
|
+
}
|
|
198
|
+
const options = writeOptions(goal.args[1], env, solver);
|
|
199
|
+
const text = formatTermForWrite(goal.args[0], env, {
|
|
200
|
+
...options,
|
|
201
|
+
generateVariableNames: true,
|
|
202
|
+
variableNameState: solver.writeVariableState,
|
|
203
|
+
operators: solver.program.operators.values(),
|
|
204
|
+
});
|
|
205
|
+
const next = env.clone();
|
|
206
|
+
if (unify(goal.args[2], chars(text), next)) yield next;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function base64Options(term, env) {
|
|
210
|
+
const options = requireOptionList(term, env, 'base64');
|
|
211
|
+
let padding = true, charset = 'standard';
|
|
212
|
+
for (const option of options) {
|
|
213
|
+
if (option.type !== COMPOUND || option.arity !== 1) {
|
|
214
|
+
throw new PrologError('domain_error(base64_option)', copyResolved(option, env));
|
|
215
|
+
}
|
|
216
|
+
const value = deref(option.args[0], env);
|
|
217
|
+
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
218
|
+
if (option.name === 'padding') {
|
|
219
|
+
if (value.type !== ATOM || !['true', 'false'].includes(value.name)) {
|
|
220
|
+
throw new PrologError('domain_error(boolean)', copyResolved(value, env));
|
|
221
|
+
}
|
|
222
|
+
padding = value.name === 'true';
|
|
223
|
+
} else if (option.name === 'charset') {
|
|
224
|
+
if (value.type !== ATOM || !['standard', 'url'].includes(value.name)) {
|
|
225
|
+
throw new PrologError('domain_error(charset)', copyResolved(value, env));
|
|
226
|
+
}
|
|
227
|
+
charset = value.name;
|
|
228
|
+
} else {
|
|
229
|
+
throw new PrologError('domain_error(base64_option)', copyResolved(option, env));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { padding, charset };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function base64Encode(text, charset, padding) {
|
|
236
|
+
const bytes = [];
|
|
237
|
+
for (const ch of Array.from(text)) {
|
|
238
|
+
const code = ch.codePointAt(0);
|
|
239
|
+
if (code > 255) throw new PrologError('domain_error(octet_character)', numberTerm(code));
|
|
240
|
+
bytes.push(code);
|
|
241
|
+
}
|
|
242
|
+
let encoded;
|
|
243
|
+
if (BufferCtor != null) encoded = BufferCtor.from(bytes).toString('base64');
|
|
244
|
+
else if (typeof btoa === 'function') encoded = btoa(String.fromCharCode(...bytes));
|
|
245
|
+
else throw new PrologError('resource_error(base64)');
|
|
246
|
+
if (charset === 'url') encoded = encoded.replace(/\+/g, '-').replace(/\//g, '_');
|
|
247
|
+
if (!padding) encoded = encoded.replace(/=+$/, '');
|
|
248
|
+
return encoded;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function base64Decode(text, charset) {
|
|
252
|
+
let encoded = text;
|
|
253
|
+
if (charset === 'url') encoded = encoded.replace(/-/g, '+').replace(/_/g, '/');
|
|
254
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(encoded) || encoded.length % 4 === 1) {
|
|
255
|
+
throw new PrologError('domain_error(base64)', chars(text));
|
|
256
|
+
}
|
|
257
|
+
encoded += '='.repeat((4 - encoded.length % 4) % 4);
|
|
258
|
+
let bytes;
|
|
259
|
+
if (BufferCtor != null) bytes = [...BufferCtor.from(encoded, 'base64')];
|
|
260
|
+
else if (typeof atob === 'function') bytes = [...atob(encoded)].map((ch) => ch.charCodeAt(0));
|
|
261
|
+
else throw new PrologError('resource_error(base64)');
|
|
262
|
+
return String.fromCharCode(...bytes);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function* charsBase64Builtin({ goal, env }) {
|
|
266
|
+
const source = deref(goal.args[0], env);
|
|
267
|
+
const encoded = deref(goal.args[1], env);
|
|
268
|
+
if (source.type === VAR && encoded.type === VAR) throw new PrologError('instantiation_error');
|
|
269
|
+
const { padding, charset } = base64Options(goal.args[2], env);
|
|
270
|
+
const next = env.clone();
|
|
271
|
+
if (source.type !== VAR) {
|
|
272
|
+
const text = characterListText(goal.args[0], env);
|
|
273
|
+
if (unify(goal.args[1], chars(base64Encode(text, charset, padding)), next)) yield next;
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const text = characterListText(goal.args[1], env);
|
|
277
|
+
if (unify(goal.args[0], chars(base64Decode(text, charset)), next)) yield next;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function charTypeCandidates(ch) {
|
|
281
|
+
const code = ch.codePointAt(0);
|
|
282
|
+
const letter = /\p{L}/u.test(ch);
|
|
283
|
+
const decimal = /\p{Nd}/u.test(ch);
|
|
284
|
+
const numeric = /\p{N}/u.test(ch);
|
|
285
|
+
const whitespace = /\s/u.test(ch);
|
|
286
|
+
const control = /\p{Cc}/u.test(ch);
|
|
287
|
+
const lower = /\p{Ll}/u.test(ch);
|
|
288
|
+
const upper = /\p{Lu}/u.test(ch);
|
|
289
|
+
const asciiGraphic = code >= 0x21 && code <= 0x7e;
|
|
290
|
+
const out = [];
|
|
291
|
+
const add = (name, yes) => { if (yes) out.push(atom(name)); };
|
|
292
|
+
add('alnum', letter || decimal);
|
|
293
|
+
add('alpha', letter);
|
|
294
|
+
add('alphabetic', letter);
|
|
295
|
+
add('alphanumeric', letter || decimal);
|
|
296
|
+
add('ascii', code <= 0x7f);
|
|
297
|
+
add('ascii_graphic', asciiGraphic);
|
|
298
|
+
add('ascii_punctuation', asciiGraphic && !letter && !decimal);
|
|
299
|
+
add('binary_digit', ch === '0' || ch === '1');
|
|
300
|
+
add('control', control);
|
|
301
|
+
add('decimal_digit', decimal);
|
|
302
|
+
add('exponent', ch === 'e' || ch === 'E');
|
|
303
|
+
add('graphic', !whitespace && !control);
|
|
304
|
+
add('graphic_token', '#$&*+-./:<=>?@\\^~'.includes(ch));
|
|
305
|
+
add('hexadecimal_digit', /^[0-9A-Fa-f]$/.test(ch));
|
|
306
|
+
add('layout', whitespace);
|
|
307
|
+
add('lower', lower);
|
|
308
|
+
add('meta', "\\'\"`".includes(ch));
|
|
309
|
+
add('numeric', numeric);
|
|
310
|
+
add('octal_digit', /^[0-7]$/.test(ch));
|
|
311
|
+
add('octet', code <= 0xff);
|
|
312
|
+
add('prolog', true);
|
|
313
|
+
add('sign', ch === '+' || ch === '-');
|
|
314
|
+
add('solo', '!,;[]{}()|'.includes(ch));
|
|
315
|
+
add('symbolic_control', '#$&*+-./:<=>?@\\^~'.includes(ch));
|
|
316
|
+
add('symbolic_hexadecimal', /^[A-Fa-f]$/.test(ch));
|
|
317
|
+
add('upper', upper);
|
|
318
|
+
add('whitespace', whitespace);
|
|
319
|
+
out.push(compound('lower', [listFromItems([...ch.toLowerCase()].map(atom))]));
|
|
320
|
+
out.push(compound('upper', [listFromItems([...ch.toUpperCase()].map(atom))]));
|
|
321
|
+
return out;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function validCharType(term) {
|
|
325
|
+
return (term.type === ATOM && charTypeNames.has(term.name)) ||
|
|
326
|
+
(term.type === COMPOUND && ['lower', 'upper'].includes(term.name) && term.arity === 1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function charTypeBuiltin(context) {
|
|
330
|
+
const state = { pending: false };
|
|
331
|
+
const iterator = charTypeSolutions(context, state);
|
|
332
|
+
iterator.hasPendingAlternatives = () => state.pending;
|
|
333
|
+
return iterator;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function* charTypeSolutions({ goal, env }, state) {
|
|
337
|
+
const char = deref(goal.args[0], env);
|
|
338
|
+
const type = deref(goal.args[1], env);
|
|
339
|
+
if (char.type === VAR && type.type === VAR) throw new PrologError('instantiation_error');
|
|
340
|
+
if (char.type !== VAR && (char.type !== ATOM || [...char.name].length !== 1)) {
|
|
341
|
+
throw new PrologError('type_error(character)', char);
|
|
342
|
+
}
|
|
343
|
+
if (type.type !== VAR && !validCharType(type)) throw new PrologError('domain_error(char_type)', type);
|
|
344
|
+
|
|
345
|
+
const firstCode = char.type === VAR ? 0 : char.name.codePointAt(0);
|
|
346
|
+
const lastCode = char.type === VAR ? 0x10ffff : firstCode;
|
|
347
|
+
for (let code = firstCode; code <= lastCode; code++) {
|
|
348
|
+
if (code >= 0xd800 && code <= 0xdfff) continue;
|
|
349
|
+
const ch = String.fromCodePoint(code);
|
|
350
|
+
const candidates = charTypeCandidates(ch).filter((candidate) =>
|
|
351
|
+
type.type === VAR || (candidate.type === type.type && candidate.name === type.name));
|
|
352
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
353
|
+
const candidate = candidates[index];
|
|
354
|
+
const next = env.clone();
|
|
355
|
+
if (unify(goal.args[0], atom(ch), next) && unify(goal.args[1], candidate, next)) {
|
|
356
|
+
state.pending = index + 1 < candidates.length || code < lastCode;
|
|
357
|
+
yield next;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
state.pending = false;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const shortMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
|
365
|
+
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
366
|
+
const longMonths = ['January', 'February', 'March', 'April', 'May', 'June',
|
|
367
|
+
'July', 'August', 'September', 'October', 'November', 'December'];
|
|
368
|
+
const shortWeekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
369
|
+
const longWeekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
export const charsioHostBuiltins = {
|
|
373
|
+
register(registry) {
|
|
374
|
+
registry.add('eyeprolog__char_type', 2, charTypeBuiltin, { eyePrologLibrary: true });
|
|
375
|
+
registry.add('eyeprolog__read_from_chars', 2, readFromCharsBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
376
|
+
registry.add('eyeprolog__read_term_from_chars', 3, readTermFromCharsBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
377
|
+
registry.add('eyeprolog__write_term_to_chars', 3, writeTermToCharsBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
378
|
+
registry.add('eyeprolog__chars_base64', 3, charsBase64Builtin, { deterministic: true, eyePrologLibrary: true });
|
|
379
|
+
},
|
|
380
|
+
};
|