eyelang 1.4.0 → 1.5.0
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 +22 -43
- package/SPEC.md +16 -16
- package/bin/eyelang +0 -0
- package/conformance/README.md +4 -5
- package/conformance/cases/core/001_fact_output.pl +4 -0
- package/conformance/cases/core/002_rule_recursion.pl +4 -2
- package/conformance/cases/core/003_terms_and_readback.pl +15 -13
- package/conformance/cases/core/004_conjunction_and_parentheses.pl +1 -0
- package/conformance/cases/core/005_list_deconstruction.pl +1 -0
- package/conformance/cases/core/006_comma_formula_data.pl +1 -0
- package/conformance/cases/core/007_anonymous_variables.pl +1 -0
- package/conformance/cases/core/008_graphic_atoms.pl +5 -3
- package/conformance/cases/core/009_comments_and_whitespace.pl +1 -0
- package/conformance/cases/core/010_variable_scope_and_reuse.pl +1 -0
- package/conformance/cases/core/011_predicate_arity.pl +1 -0
- package/conformance/cases/core/012_nested_compound_unification.pl +1 -0
- package/conformance/cases/core/013_multiple_clauses_order.pl +1 -0
- package/conformance/cases/core/014_failure_filters_answers.pl +2 -0
- package/conformance/cases/core/015_improper_list_unification.pl +1 -0
- package/conformance/cases/core/016_zero_arity_compound.pl +1 -0
- package/conformance/cases/core/017_three_step_recursion.pl +1 -0
- package/conformance/cases/core/018_quoted_atom_readback.pl +1 -0
- package/conformance/cases/core/019_parenthesized_three_conjuncts.pl +1 -0
- package/conformance/cases/core/020_nested_list_terms.pl +1 -0
- package/conformance/cases/extension/001_default_derived_output.pl +1 -1
- package/conformance/cases/extension/002_materialize_focus.pl +1 -1
- package/conformance/cases/extension/003_arithmetic_and_comparison.pl +1 -0
- package/conformance/cases/extension/004_strings_and_atoms.pl +1 -0
- package/conformance/cases/extension/005_lists_aggregation_ordering.pl +1 -0
- package/conformance/cases/extension/006_formula_terms.pl +1 -0
- package/conformance/cases/extension/007_negation_once_generators.pl +1 -0
- package/conformance/cases/extension/008_equality_and_inequality.pl +1 -0
- package/conformance/cases/extension/009_list_relations.pl +1 -0
- package/conformance/cases/extension/010_append_splits.pl +1 -0
- package/conformance/cases/extension/011_matching_and_comparison.pl +1 -0
- package/conformance/cases/extension/012_memoize_declaration.pl +5 -3
- package/conformance/cases/extension/013_numeric_functions.pl +1 -0
- package/conformance/cases/extension/014_between_enumeration.pl +1 -0
- package/conformance/cases/extension/015_smallest_divisor.pl +1 -0
- package/conformance/cases/extension/016_negation_filter.pl +1 -0
- package/conformance/cases/extension/017_once_user_predicate.pl +1 -0
- package/conformance/cases/extension/018_findall_user_goal.pl +1 -0
- package/conformance/cases/extension/019_sort_deduplicates_atoms.pl +1 -0
- package/conformance/cases/extension/020_append_bound_prefix_suffix.pl +1 -0
- package/conformance/cases/extension/021_nth0_index_generation.pl +1 -0
- package/conformance/cases/extension/022_set_nth0_edges.pl +1 -0
- package/conformance/cases/extension/023_select_duplicate_occurrences.pl +1 -0
- package/conformance/cases/extension/024_not_member_filter.pl +1 -0
- package/conformance/cases/extension/025_is_list_filter.pl +1 -0
- package/conformance/cases/extension/026_nested_formula_terms.pl +1 -0
- package/conformance/cases/extension/027_materialize_excludes_source_fact.pl +1 -1
- package/conformance/cases/extension/028_numeric_and_lexical_comparison.pl +1 -0
- package/conformance/cases/extension/029_string_matching_filters.pl +1 -0
- package/conformance/cases/extension/030_string_and_atom_concat.pl +1 -0
- package/conformance/cases/extension/031_countall_empty_and_nonempty.pl +2 -0
- package/conformance/cases/extension/032_sumall_numeric_template.pl +2 -0
- package/conformance/cases/extension/033_aggregate_min_template.pl +2 -0
- package/conformance/cases/extension/034_aggregate_max_compound_key.pl +2 -0
- package/conformance/expected/extension/031_countall_empty_and_nonempty.out +1 -1
- package/conformance/expected/extension/032_sumall_numeric_template.out +1 -1
- package/conformance/expected/extension/033_aggregate_min_template.out +1 -1
- package/conformance/expected/extension/034_aggregate_max_compound_key.out +1 -1
- package/examples/basic-monadic.pl +1 -1
- package/examples/monkey-bananas.pl +1 -1
- package/examples/path-discovery.pl +3 -3
- package/examples/peano-arithmetic.pl +1 -1
- package/package.json +1 -1
- package/playground-worker.mjs +2 -15
- package/playground.html +5 -25
- package/src/builtins/aggregation.js +5 -5
- package/src/builtins/control.js +3 -3
- package/src/builtins/registry.js +7 -0
- package/src/cli.js +36 -38
- package/src/index.js +21 -28
- package/src/parser.js +3 -3
- package/src/solver.js +2 -2
- package/test/run-conformance.js +20 -45
- package/test/run-examples.js +42 -50
- package/test/run-regression.js +38 -59
- package/conformance/cases/core/001_fact_query.pl +0 -2
- /package/conformance/expected/core/{001_fact_query.out → 001_fact_output.out} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
(
|
|
1
|
+
answer(counts, counts(2, 0)).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
answer(sum, 7).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
answer(min, result(3, item(beta, 3))).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
answer(max, result([7, beta], beta)).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
% Basic Monadic Benchmark port from EYE reasoning/basic-monadic.
|
|
2
2
|
%
|
|
3
3
|
% This example uses the ten Turtle inputs 1tt1.ttl ... 1tt10.ttl
|
|
4
|
-
% from EYE and the EYE
|
|
4
|
+
% from EYE and the EYE selected-goal shape:
|
|
5
5
|
% D0 R D1, D1 R D2, ..., D9 R D0 -> R cycle (D0 ... D9 D0).
|
|
6
6
|
%
|
|
7
7
|
% The expected output contains 1518 distinct cycle relations, matching
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
% input/monkey-bananas.pl.
|
|
3
3
|
%
|
|
4
4
|
% A state is [bananas_location, monkey_location, box_location, on_box,
|
|
5
|
-
% has_bananas]. The
|
|
5
|
+
% has_bananas]. The selected output searches bounded move lists and derives successful
|
|
6
6
|
% plans.
|
|
7
7
|
|
|
8
8
|
materialize(plan, 2).
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
% Generic path discovery over the air-routes graph.
|
|
2
|
-
% Change or add
|
|
2
|
+
% Change or add route_request(FromLabel, ToLabel, MaxStopOvers) to answer other routes.
|
|
3
3
|
materialize(airroute, 2).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
route_request("Ostend-Bruges International Airport", "Václav Havel Airport Prague", 2).
|
|
6
6
|
|
|
7
7
|
airroute(discovered, RouteText) :-
|
|
8
|
-
|
|
8
|
+
route_request(From, To, MaxStopOvers),
|
|
9
9
|
airport(Source, From),
|
|
10
10
|
airport(Destination, To),
|
|
11
11
|
add(MaxStopOvers, 1, MaxLegs),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
% Peano arithmetic port from EYE reasoning/peano.
|
|
2
2
|
%
|
|
3
3
|
% The EYE example defines add, multiply and factorial over Peano numerals.
|
|
4
|
-
% Its
|
|
4
|
+
% Its selected output computes (1 * 2 + 3)! and emits the factorial of 5.
|
|
5
5
|
|
|
6
6
|
materialize(factorial, 2).
|
|
7
7
|
|
package/package.json
CHANGED
package/playground-worker.mjs
CHANGED
|
@@ -46,7 +46,7 @@ async function initialize(requestId) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
async function runEyelang(request) {
|
|
49
|
-
const { id, program,
|
|
49
|
+
const { id, program, stats, proof = false } = request;
|
|
50
50
|
if (active) {
|
|
51
51
|
self.postMessage({
|
|
52
52
|
type: 'result',
|
|
@@ -72,7 +72,6 @@ async function runEyelang(request) {
|
|
|
72
72
|
Program,
|
|
73
73
|
Solver,
|
|
74
74
|
copyResolved,
|
|
75
|
-
parseQueryGoal,
|
|
76
75
|
termIsGround,
|
|
77
76
|
termToString,
|
|
78
77
|
whyNoProof,
|
|
@@ -82,19 +81,7 @@ async function runEyelang(request) {
|
|
|
82
81
|
phase = 'parsing input';
|
|
83
82
|
const parsed = Program.parse(program || '', { filename: '<playground>', sourceMetadata: proof, markRecursive: proof });
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
phase = 'parsing query';
|
|
87
|
-
const goal = parseQueryGoal(query.trim());
|
|
88
|
-
phase = 'solving query';
|
|
89
|
-
const solver = new Solver(parsed);
|
|
90
|
-
const out = [];
|
|
91
|
-
for (const env of solver.solve([goal], new Env(), 0)) {
|
|
92
|
-
out.push(`${termToString(goal, env, true)}.\n`);
|
|
93
|
-
if (proof) appendExplanation(out, parsed, copyResolved(goal, env), whyProof, whyNoProof);
|
|
94
|
-
}
|
|
95
|
-
stdout = out.join('');
|
|
96
|
-
if (stats) stderr = formatStats(solver.stats);
|
|
97
|
-
} else {
|
|
84
|
+
{
|
|
98
85
|
phase = 'materializing output';
|
|
99
86
|
const goals = parsed.materializationGoals();
|
|
100
87
|
const materializedKeys = new Set(goals.map((goal) => `${goal.name}/${goal.arity}`));
|
package/playground.html
CHANGED
|
@@ -172,13 +172,6 @@
|
|
|
172
172
|
button.primary { border-color: var(--accent); background: var(--accent); color: white; }
|
|
173
173
|
button.primary:hover { background: var(--accent-strong); }
|
|
174
174
|
button:disabled { opacity: 0.55; cursor: not-allowed; }
|
|
175
|
-
.query-row {
|
|
176
|
-
display: grid;
|
|
177
|
-
grid-template-columns: minmax(0, 1fr);
|
|
178
|
-
gap: 8px;
|
|
179
|
-
margin-top: 10px;
|
|
180
|
-
}
|
|
181
|
-
.query-row input { width: 100%; }
|
|
182
175
|
.run-options {
|
|
183
176
|
margin-top: 10px;
|
|
184
177
|
align-items: flex-start;
|
|
@@ -399,10 +392,6 @@
|
|
|
399
392
|
<textarea id="programInput" class="program-textarea" spellcheck="false" autocapitalize="off" autocomplete="off" wrap="soft" aria-label="Editable eyelang program"></textarea>
|
|
400
393
|
</div>
|
|
401
394
|
</div>
|
|
402
|
-
<div class="query-row">
|
|
403
|
-
<label for="queryInput">Optional <code>--query</code> goal</label>
|
|
404
|
-
<input id="queryInput" type="text" placeholder="ancestor(pat, X)" spellcheck="false">
|
|
405
|
-
</div>
|
|
406
395
|
<div class="toolbar run-options">
|
|
407
396
|
<label class="row"><input id="statsToggle" type="checkbox"> include <code>--stats</code> counters</label>
|
|
408
397
|
<label class="row"><input id="proofToggle" type="checkbox"> include <code>--proof</code> explanations</label>
|
|
@@ -415,7 +404,7 @@
|
|
|
415
404
|
<button id="clearSavedButton" type="button">Clear autosave</button>
|
|
416
405
|
<span id="status" class="status" role="status">Idle.</span>
|
|
417
406
|
</div>
|
|
418
|
-
<p class="small">The playground concatenates loaded background programs before the editor contents. Use
|
|
407
|
+
<p class="small">The playground concatenates loaded background programs before the editor contents. Use <code>materialize/2</code> declarations to focus output and the <code>--stats</code> toggle for counters. Output is ordinary eyelang syntax: answer facts by default, with <code>why/2</code> explanations added when <code>--proof</code> is selected.</p>
|
|
419
408
|
</div>
|
|
420
409
|
</section>
|
|
421
410
|
|
|
@@ -566,13 +555,12 @@
|
|
|
566
555
|
"zebra"
|
|
567
556
|
];
|
|
568
557
|
const DEFAULT_PROGRAM = "% Socrates is mortal.\n\nmaterialize(type, 2).\nmaterialize(is, 2).\n\ntype(socrates, man).\n\ntype(X, mortal) :-\n type(X, man).\n\nis(test, true) :-\n type(socrates, mortal).";
|
|
569
|
-
const STORAGE_KEY = 'eyelang.playground.
|
|
558
|
+
const STORAGE_KEY = 'eyelang.playground.v7';
|
|
570
559
|
|
|
571
560
|
const programInput = document.getElementById('programInput');
|
|
572
561
|
const programSyntax = document.getElementById('programSyntax');
|
|
573
562
|
const programGutter = document.getElementById('programGutter');
|
|
574
563
|
const programErrorBand = document.getElementById('programErrorBand');
|
|
575
|
-
const queryInput = document.getElementById('queryInput');
|
|
576
564
|
const statsToggle = document.getElementById('statsToggle');
|
|
577
565
|
const proofToggle = document.getElementById('proofToggle');
|
|
578
566
|
const commandPreview = document.getElementById('commandPreview');
|
|
@@ -943,7 +931,6 @@
|
|
|
943
931
|
saveTimer = setTimeout(() => {
|
|
944
932
|
localStorage.setItem(STORAGE_KEY, JSON.stringify({
|
|
945
933
|
program: programInput.value,
|
|
946
|
-
query: queryInput.value,
|
|
947
934
|
stats: statsToggle.checked,
|
|
948
935
|
proof: proofToggle.checked,
|
|
949
936
|
backgroundPrograms,
|
|
@@ -979,7 +966,6 @@
|
|
|
979
966
|
const args = ['eyelang'];
|
|
980
967
|
if (statsToggle.checked) args.push('--stats');
|
|
981
968
|
if (proofToggle.checked) args.push('--proof');
|
|
982
|
-
if (queryInput.value.trim()) args.push('--query', shellQuote(queryInput.value.trim()));
|
|
983
969
|
args.push(currentProgramStillMatchesUrl() ? currentProgramUrl : 'input.pl');
|
|
984
970
|
commandPreview.textContent = args.join(' ');
|
|
985
971
|
}
|
|
@@ -989,7 +975,6 @@
|
|
|
989
975
|
if (searchParams.has('url')) {
|
|
990
976
|
const sourceUrl = searchParams.get('url');
|
|
991
977
|
urlInput.value = sourceUrl;
|
|
992
|
-
queryInput.value = searchParams.get('query') || searchParams.get('q') || '';
|
|
993
978
|
statsToggle.checked = parseFlag(searchParams.get('stats') || searchParams.get('s'));
|
|
994
979
|
proofToggle.checked = parseFlag(searchParams.get('proof') || searchParams.get('pr'));
|
|
995
980
|
updateCommandPreview();
|
|
@@ -1006,7 +991,6 @@
|
|
|
1006
991
|
if (params.has('p')) {
|
|
1007
992
|
try {
|
|
1008
993
|
setProgramValue(decodePayload(params.get('p')));
|
|
1009
|
-
queryInput.value = params.has('q') ? decodePayload(params.get('q')) : '';
|
|
1010
994
|
statsToggle.checked = parseFlag(params.get('s'));
|
|
1011
995
|
proofToggle.checked = parseFlag(params.get('r'));
|
|
1012
996
|
currentProgramUrl = null;
|
|
@@ -1018,11 +1002,10 @@
|
|
|
1018
1002
|
}
|
|
1019
1003
|
}
|
|
1020
1004
|
try {
|
|
1021
|
-
const raw = localStorage.getItem(STORAGE_KEY) || localStorage.getItem('eyelang.playground.v4') || localStorage.getItem('eyelang.playground.v3') || localStorage.getItem('eyelang.playground.v2') || localStorage.getItem('eyelang.playground.v1');
|
|
1005
|
+
const raw = localStorage.getItem(STORAGE_KEY) || localStorage.getItem('eyelang.playground.v6') || localStorage.getItem('eyelang.playground.v5') || localStorage.getItem('eyelang.playground.v4') || localStorage.getItem('eyelang.playground.v3') || localStorage.getItem('eyelang.playground.v2') || localStorage.getItem('eyelang.playground.v1');
|
|
1022
1006
|
if (raw) {
|
|
1023
1007
|
const payload = JSON.parse(raw);
|
|
1024
1008
|
setProgramValue(payload.program || DEFAULT_PROGRAM);
|
|
1025
|
-
queryInput.value = payload.query || '';
|
|
1026
1009
|
statsToggle.checked = Boolean(payload.stats);
|
|
1027
1010
|
proofToggle.checked = Boolean(payload.proof);
|
|
1028
1011
|
backgroundPrograms = Array.isArray(payload.backgroundPrograms) ? payload.backgroundPrograms : [];
|
|
@@ -1248,7 +1231,6 @@
|
|
|
1248
1231
|
type: 'run',
|
|
1249
1232
|
id: currentRunId,
|
|
1250
1233
|
program: combinedProgram(),
|
|
1251
|
-
query: queryInput.value,
|
|
1252
1234
|
stats: statsToggle.checked,
|
|
1253
1235
|
proof: proofToggle.checked,
|
|
1254
1236
|
});
|
|
@@ -1304,13 +1286,11 @@
|
|
|
1304
1286
|
if (currentProgramStillMatchesUrl()) {
|
|
1305
1287
|
url.search = '';
|
|
1306
1288
|
url.searchParams.set('url', currentProgramUrl);
|
|
1307
|
-
if (queryInput.value.trim()) url.searchParams.set('query', queryInput.value.trim());
|
|
1308
1289
|
if (statsToggle.checked) url.searchParams.set('stats', '1');
|
|
1309
1290
|
if (proofToggle.checked) url.searchParams.set('proof', '1');
|
|
1310
1291
|
} else {
|
|
1311
1292
|
const params = new URLSearchParams();
|
|
1312
1293
|
params.set('p', encodePayload(programInput.value));
|
|
1313
|
-
if (queryInput.value.trim()) params.set('q', encodePayload(queryInput.value));
|
|
1314
1294
|
if (statsToggle.checked) params.set('s', '1');
|
|
1315
1295
|
if (proofToggle.checked) params.set('r', '1');
|
|
1316
1296
|
url.search = '';
|
|
@@ -1324,7 +1304,6 @@
|
|
|
1324
1304
|
loadExampleButton.addEventListener('click', () => loadIntoEditor(`examples/${exampleSelect.value}.pl`, false).catch(error => setStatus(error.message, 'error')));
|
|
1325
1305
|
resetButton.addEventListener('click', () => {
|
|
1326
1306
|
setProgramValue(DEFAULT_PROGRAM);
|
|
1327
|
-
queryInput.value = '';
|
|
1328
1307
|
statsToggle.checked = false;
|
|
1329
1308
|
proofToggle.checked = false;
|
|
1330
1309
|
backgroundPrograms = [];
|
|
@@ -1357,6 +1336,8 @@
|
|
|
1357
1336
|
copyLinkButton.addEventListener('click', () => copyShareLink().catch(error => setStatus(`Could not copy link: ${error.message}`, 'error')));
|
|
1358
1337
|
clearSavedButton.addEventListener('click', () => {
|
|
1359
1338
|
localStorage.removeItem(STORAGE_KEY);
|
|
1339
|
+
localStorage.removeItem('eyelang.playground.v6');
|
|
1340
|
+
localStorage.removeItem('eyelang.playground.v5');
|
|
1360
1341
|
localStorage.removeItem('eyelang.playground.v4');
|
|
1361
1342
|
localStorage.removeItem('eyelang.playground.v3');
|
|
1362
1343
|
localStorage.removeItem('eyelang.playground.v2');
|
|
@@ -1366,7 +1347,6 @@
|
|
|
1366
1347
|
programInput.addEventListener('input', () => { clearErrorLine(); updateProgramSyntax(); markProgramEdited(); });
|
|
1367
1348
|
programInput.addEventListener('scroll', updateProgramHighlight);
|
|
1368
1349
|
combinedOutput.addEventListener('scroll', () => { outputGutter.scrollTop = combinedOutput.scrollTop; });
|
|
1369
|
-
queryInput.addEventListener('input', saveState);
|
|
1370
1350
|
statsToggle.addEventListener('change', saveState);
|
|
1371
1351
|
proofToggle.addEventListener('change', saveState);
|
|
1372
1352
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Aggregation builtins that run a
|
|
1
|
+
// Aggregation builtins that run a inner goal and collect, count, sum, or select the best answers.
|
|
2
2
|
// Each handler clones the solver so the inner goal can enumerate independently of the outer goal.
|
|
3
3
|
import { compareTerms, copyResolved, isDecimalInteger, lexicalValue, listFromItems, numberTerm, numberTextFromDouble, parseFiniteNumber, unify } from '../term.js';
|
|
4
4
|
|
|
@@ -14,7 +14,7 @@ export const aggregationBuiltins = {
|
|
|
14
14
|
|
|
15
15
|
function* findall({ solver, goal, env }) {
|
|
16
16
|
const [template, innerGoal, bag] = goal.args;
|
|
17
|
-
const collector = solver.
|
|
17
|
+
const collector = solver.cloneForInnerGoal(10000000);
|
|
18
18
|
const collected = [];
|
|
19
19
|
for (const answerEnv of collector.solve([innerGoal], env.clone(), 0)) collected.push(copyResolved(template, answerEnv));
|
|
20
20
|
const next = env.clone();
|
|
@@ -23,7 +23,7 @@ function* findall({ solver, goal, env }) {
|
|
|
23
23
|
|
|
24
24
|
function* countall({ solver, goal, env }) {
|
|
25
25
|
const [innerGoal, count] = goal.args;
|
|
26
|
-
const collector = solver.
|
|
26
|
+
const collector = solver.cloneForInnerGoal(10000000);
|
|
27
27
|
let n = 0;
|
|
28
28
|
for (const _ of collector.solve([innerGoal], env.clone(), 0)) n++;
|
|
29
29
|
const next = env.clone();
|
|
@@ -32,7 +32,7 @@ function* countall({ solver, goal, env }) {
|
|
|
32
32
|
|
|
33
33
|
function* sumall({ solver, goal, env }) {
|
|
34
34
|
const [template, innerGoal, sum] = goal.args;
|
|
35
|
-
const collector = solver.
|
|
35
|
+
const collector = solver.cloneForInnerGoal(10000000);
|
|
36
36
|
let intSum = 0n;
|
|
37
37
|
let floatMode = false;
|
|
38
38
|
let floatSum = 0;
|
|
@@ -55,7 +55,7 @@ function* sumall({ solver, goal, env }) {
|
|
|
55
55
|
function aggregateBest(wantMin) {
|
|
56
56
|
return function* ({ solver, goal, env }) {
|
|
57
57
|
const [keyTemplate, valueTemplate, innerGoal, bestKey, bestValue] = goal.args;
|
|
58
|
-
const collector = solver.
|
|
58
|
+
const collector = solver.cloneForInnerGoal(10000000);
|
|
59
59
|
let has = false;
|
|
60
60
|
let key = null;
|
|
61
61
|
let value = null;
|
package/src/builtins/control.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Control builtins. These intentionally use bounded
|
|
1
|
+
// Control builtins. These intentionally use bounded nested solvers so not/1 and once/1 only ask for the answers they need.
|
|
2
2
|
export const controlBuiltins = {
|
|
3
3
|
register(registry) {
|
|
4
4
|
registry.add('not', 1, notBuiltin);
|
|
@@ -7,14 +7,14 @@ export const controlBuiltins = {
|
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
function* notBuiltin({ solver, goal, env }) {
|
|
10
|
-
const limited = solver.
|
|
10
|
+
const limited = solver.cloneForInnerGoal(1);
|
|
11
11
|
let found = false;
|
|
12
12
|
for (const _ of limited.solve([goal.args[0]], env.clone(), 0)) { found = true; break; }
|
|
13
13
|
if (!found) yield env;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function* onceBuiltin({ solver, goal, env }) {
|
|
17
|
-
const limited = solver.
|
|
17
|
+
const limited = solver.cloneForInnerGoal(1);
|
|
18
18
|
for (const answerEnv of limited.solve([goal.args[0]], env.clone(), 0)) {
|
|
19
19
|
yield answerEnv;
|
|
20
20
|
break;
|
package/src/builtins/registry.js
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
// Command-line interface for eyelang.
|
|
2
|
-
// It loads programs from files, URLs, or stdin, then
|
|
2
|
+
// It loads programs from files, URLs, or stdin, then materializes derived output predicates.
|
|
3
3
|
import fs from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import process from 'node:process';
|
|
6
|
-
import { Env, copyResolved, termIsGround, termToString } from './term.js';
|
|
7
|
-
import { Program } from './program.js';
|
|
8
|
-
import { Solver } from './solver.js';
|
|
9
|
-
import { parseQueryGoal } from './parser.js';
|
|
10
|
-
import { whyNoProof, whyProof } from './explain.js';
|
|
11
6
|
|
|
12
|
-
|
|
7
|
+
let engineModule = null;
|
|
8
|
+
let explanationModule = null;
|
|
13
9
|
|
|
14
10
|
export async function main(argv) {
|
|
15
11
|
if (argv.length === 0) {
|
|
16
|
-
usage(process.stdout);
|
|
12
|
+
await usage(process.stdout);
|
|
17
13
|
return;
|
|
18
14
|
}
|
|
19
15
|
|
|
20
16
|
const options = {
|
|
21
17
|
files: [],
|
|
22
18
|
proof: false,
|
|
23
|
-
query: null,
|
|
24
19
|
stats: false,
|
|
25
20
|
version: false,
|
|
26
21
|
};
|
|
@@ -35,13 +30,10 @@ export async function main(argv) {
|
|
|
35
30
|
} else if (!endOptions && (arg === '--version' || arg === '-v')) {
|
|
36
31
|
options.version = true;
|
|
37
32
|
} else if (!endOptions && (arg === '--help' || arg === '-h')) {
|
|
38
|
-
usage(process.stdout);
|
|
33
|
+
await usage(process.stdout);
|
|
39
34
|
return;
|
|
40
35
|
} else if (!endOptions && (arg === '--proof' || arg === '-p')) {
|
|
41
36
|
options.proof = true;
|
|
42
|
-
} else if (!endOptions && arg === '--query') {
|
|
43
|
-
if (i + 1 >= argv.length) throw new Error('--query requires an argument');
|
|
44
|
-
options.query = argv[++i];
|
|
45
37
|
} else if (!endOptions && arg === '--stats') {
|
|
46
38
|
options.stats = true;
|
|
47
39
|
} else if (!endOptions && arg.startsWith('-') && arg !== '-') {
|
|
@@ -52,7 +44,7 @@ export async function main(argv) {
|
|
|
52
44
|
}
|
|
53
45
|
|
|
54
46
|
if (options.version) {
|
|
55
|
-
process.stdout.write(`eyelang ${
|
|
47
|
+
process.stdout.write(`eyelang ${await packageVersion()}\n`);
|
|
56
48
|
return;
|
|
57
49
|
}
|
|
58
50
|
|
|
@@ -77,45 +69,52 @@ export async function main(argv) {
|
|
|
77
69
|
}
|
|
78
70
|
}
|
|
79
71
|
|
|
80
|
-
const
|
|
72
|
+
const engine = await loadEngine();
|
|
73
|
+
const program = engine.Program.parseSources(sourceParts, { sourceMetadata: options.proof, markRecursive: options.proof });
|
|
81
74
|
|
|
82
|
-
|
|
83
|
-
else runDefault(program, options);
|
|
75
|
+
await runDefault(engine, program, options);
|
|
84
76
|
}
|
|
85
77
|
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
78
|
+
async function loadEngine() {
|
|
79
|
+
if (engineModule == null) {
|
|
80
|
+
const [term, program, solver, registry] = await Promise.all([
|
|
81
|
+
import('./term.js'),
|
|
82
|
+
import('./program.js'),
|
|
83
|
+
import('./solver.js'),
|
|
84
|
+
import('./builtins/registry.js'),
|
|
85
|
+
]);
|
|
86
|
+
engineModule = { ...term, ...program, ...solver, ...registry };
|
|
94
87
|
}
|
|
88
|
+
return engineModule;
|
|
89
|
+
}
|
|
95
90
|
|
|
96
|
-
|
|
91
|
+
async function loadExplanation() {
|
|
92
|
+
if (explanationModule == null) explanationModule = await import('./explain.js');
|
|
93
|
+
return explanationModule;
|
|
97
94
|
}
|
|
98
95
|
|
|
99
|
-
function runDefault(program, options) {
|
|
96
|
+
async function runDefault(engine, program, options) {
|
|
100
97
|
const goals = program.materializationGoals();
|
|
101
98
|
const materializedKeys = new Set(goals.map((goal) => `${goal.name}/${goal.arity}`));
|
|
102
99
|
const facts = program.sourceFactLines(materializedKeys);
|
|
103
100
|
const lines = new Set();
|
|
104
101
|
let lastStats = null;
|
|
102
|
+
const registry = engine.getDefaultRegistry();
|
|
103
|
+
const explanation = options.proof ? await loadExplanation() : null;
|
|
105
104
|
|
|
106
105
|
for (const goal of goals) {
|
|
107
|
-
const solver = new Solver(program);
|
|
106
|
+
const solver = new engine.Solver(program, { registry });
|
|
108
107
|
|
|
109
|
-
for (const env of solver.solve([goal], new Env(), 0)) {
|
|
110
|
-
if (!termIsGround(goal, env)) continue;
|
|
108
|
+
for (const env of solver.solve([goal], new engine.Env(), 0)) {
|
|
109
|
+
if (!engine.termIsGround(goal, env)) continue;
|
|
111
110
|
|
|
112
|
-
const line = `${termToString(goal, env, true)}.\n`;
|
|
111
|
+
const line = `${engine.termToString(goal, env, true)}.\n`;
|
|
113
112
|
if (facts.has(line) || lines.has(line)) continue;
|
|
114
113
|
|
|
115
114
|
lines.add(line);
|
|
116
115
|
|
|
117
116
|
process.stdout.write(line);
|
|
118
|
-
if (options.proof) writeExplanation(program, copyResolved(goal, env));
|
|
117
|
+
if (options.proof) writeExplanation(explanation, program, engine.copyResolved(goal, env), registry);
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
lastStats = solver.stats;
|
|
@@ -124,14 +123,14 @@ function runDefault(program, options) {
|
|
|
124
123
|
if (options.stats && lastStats) printStats(lastStats);
|
|
125
124
|
}
|
|
126
125
|
|
|
127
|
-
function writeExplanation(program, resolved) {
|
|
128
|
-
const proof = whyProof(program, resolved);
|
|
126
|
+
function writeExplanation(explanation, program, resolved, registry) {
|
|
127
|
+
const proof = explanation.whyProof(program, resolved, { registry });
|
|
129
128
|
process.stdout.write(proof.text);
|
|
130
|
-
if (!proof.ok) process.stdout.write(whyNoProof(resolved));
|
|
129
|
+
if (!proof.ok) process.stdout.write(explanation.whyNoProof(resolved));
|
|
131
130
|
}
|
|
132
131
|
|
|
133
|
-
function usage(stream) {
|
|
134
|
-
stream.write(`eyelang ${
|
|
132
|
+
async function usage(stream) {
|
|
133
|
+
stream.write(`eyelang ${await packageVersion()}
|
|
135
134
|
|
|
136
135
|
Usage:
|
|
137
136
|
eyelang [options] [file-or-url.pl|- ...]
|
|
@@ -143,7 +142,6 @@ Input:
|
|
|
143
142
|
Options:
|
|
144
143
|
-h, --help Show this help text and exit.
|
|
145
144
|
-p, --proof Enable proof explanations.
|
|
146
|
-
--query GOAL Run GOAL as a query instead of materializing output predicates.
|
|
147
145
|
--stats Print solver statistics to stderr after execution.
|
|
148
146
|
-v, --version Show the package version and exit.
|
|
149
147
|
-- Stop option parsing; following arguments are treated as files.
|