eyemind 0.1.27 → 0.1.28
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/SPEC.md +6 -3
- package/package.json +1 -1
- package/src/engine.js +25 -13
- package/src/version.js +1 -1
- package/src/writer.js +28 -0
package/SPEC.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
```text
|
|
4
4
|
Title: The eyemind Rule Language and Proof Format
|
|
5
|
-
Version: eyemind 0.1.
|
|
5
|
+
Version: eyemind 0.1.28
|
|
6
6
|
Status: Informational
|
|
7
7
|
Author: Jos De Roo, KNoWS office of IDLab, Ghent University - imec
|
|
8
8
|
Repository: https://github.com/eyereasoner/eyemind
|
|
@@ -22,7 +22,7 @@ command line.
|
|
|
22
22
|
## Status of This Memo
|
|
23
23
|
|
|
24
24
|
This document is not an Internet Standards Track specification. It describes
|
|
25
|
-
the language and formats as implemented by eyemind 0.1.
|
|
25
|
+
the language and formats as implemented by eyemind 0.1.28, for readers who want
|
|
26
26
|
to write programs, produce or consume proofs and reports, or implement a
|
|
27
27
|
compatible reasoner or checker. Where this document and the implementation
|
|
28
28
|
disagree, that is a defect in one of them.
|
|
@@ -340,6 +340,9 @@ only original `implies` and `implied_by` clauses whose heads contain no rule
|
|
|
340
340
|
templates and whose bodies contain no inspection goals, including inside
|
|
341
341
|
controls. Facts, queries, contradictions, template generators and appended
|
|
342
342
|
clauses are excluded. Inspecting rules therefore cannot inspect themselves.
|
|
343
|
+
A rule value used as an inspection pattern is embedded in the inspecting
|
|
344
|
+
statement (Section 4.6), so a stored rule value used as a pattern loses its
|
|
345
|
+
standalone clause and is not in the catalogue.
|
|
343
346
|
|
|
344
347
|
The catalogue keeps source order. A rule's body and forward head conjunctions
|
|
345
348
|
are terms `and(...)`, as normalized goals are, and inspection arguments are
|
|
@@ -1034,7 +1037,7 @@ conclusions and proof documents in the canonical text, and checks every proof
|
|
|
1034
1037
|
it produces as Section 11 describes. A conforming **checker** implements
|
|
1035
1038
|
Sections 9, 11 and 12 and does not depend on a reasoner. For the same
|
|
1036
1039
|
program, a conforming reasoner and checker MUST produce the same
|
|
1037
|
-
conclusions, proof documents and reports as eyemind 0.1.
|
|
1040
|
+
conclusions, proof documents and reports as eyemind 0.1.28, byte for byte,
|
|
1038
1041
|
except for comments and the `Detail` texts of failures (Section 12), the internal names of
|
|
1039
1042
|
variables where Section 3 says so, and where the host's floating-point
|
|
1040
1043
|
library functions (`exp`, `log`, the trigonometric and hyperbolic functions,
|
package/package.json
CHANGED
package/src/engine.js
CHANGED
|
@@ -8,10 +8,10 @@ import { CONTROL_KEYS, Program } from './program.js';
|
|
|
8
8
|
import { isActivation } from './templates.js';
|
|
9
9
|
import {
|
|
10
10
|
Env, EyemindError, Struct, Var, conjunction, copyResolved, deref, flattenConjunction, freshTerm,
|
|
11
|
-
identity, isCallable, isGround, isGroundButAnonymous, isTerm, key, keyArity, listFromItems,
|
|
12
|
-
variables,
|
|
11
|
+
identity, isAnonymous, isCallable, isGround, isGroundButAnonymous, isTerm, key, keyArity, listFromItems,
|
|
12
|
+
unify, variables,
|
|
13
13
|
} from './terms.js';
|
|
14
|
-
import { Lettering, write } from './writer.js';
|
|
14
|
+
import { Lettering, SourceNaming, write } from './writer.js';
|
|
15
15
|
|
|
16
16
|
export const BUILTIN = 'builtin';
|
|
17
17
|
export const CONTROL = 'control';
|
|
@@ -153,6 +153,18 @@ function freshNode(node, suffix) {
|
|
|
153
153
|
return root;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// The error for a negation whose goal still has a variable to bind, naming
|
|
157
|
+
// the variables as the program wrote them rather than as renamed apart.
|
|
158
|
+
function unboundNegation(goal, env) {
|
|
159
|
+
const naming = new SourceNaming();
|
|
160
|
+
const resolved = copyResolved(goal, env);
|
|
161
|
+
const shown = write(resolved, null, naming);
|
|
162
|
+
const unbound = [...new Set([...variables(resolved).values()]
|
|
163
|
+
.filter((variable) => !isAnonymous(variable)).map((variable) => naming.get(variable.name)))];
|
|
164
|
+
return new EyemindError('negation requires a goal whose variables are bound, except for anonymous ones: ' +
|
|
165
|
+
`${unbound.join(', ')} ${unbound.length === 1 ? 'is' : 'are'} unbound in ${shown}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
156
168
|
// Resolve a proof forest with an explicit stack: a derivation chain is as deep
|
|
157
169
|
// as the search that produced it. A derived fact's node is already final and
|
|
158
170
|
// is shared rather than copied, which keeps a long forward chain from being
|
|
@@ -395,10 +407,7 @@ export class Solver {
|
|
|
395
407
|
if (goalKey === 'not/1') {
|
|
396
408
|
// Negation is a test, not a way to bind its variables; its anonymous
|
|
397
409
|
// variables stand for any value.
|
|
398
|
-
if (!isGroundButAnonymous(goal.args[0], env))
|
|
399
|
-
throw new EyemindError('negation requires a goal whose variables are bound, ' +
|
|
400
|
-
`except for anonymous ones: ${text(goal, env)}`);
|
|
401
|
-
}
|
|
410
|
+
if (!isGroundButAnonymous(goal.args[0], env)) throw unboundNegation(goal, env);
|
|
402
411
|
const mark = env.mark();
|
|
403
412
|
const iterator = this.solve([goal.args[0]], env, frame.depth + 1);
|
|
404
413
|
let absent;
|
|
@@ -420,7 +429,7 @@ export class Solver {
|
|
|
420
429
|
this.serial++;
|
|
421
430
|
const names = new Map();
|
|
422
431
|
items.push(freshTerm(copyResolved(goal.args[0], answerEnv), `collection${this.serial}`, names));
|
|
423
|
-
if (this.program.inspectionEnabled) {
|
|
432
|
+
if (this.program.inspectionEnabled && names.size) {
|
|
424
433
|
for (const name of scopedVariables(nodes, answerEnv).keys()) {
|
|
425
434
|
if (names.has(name)) scoped.push(names.get(name));
|
|
426
435
|
}
|
|
@@ -700,10 +709,14 @@ export class Solver {
|
|
|
700
709
|
// between the heads of one conclusion.
|
|
701
710
|
const resolved = copyResolved(head, answerEnv);
|
|
702
711
|
const unresolved = variables(resolved);
|
|
703
|
-
|
|
712
|
+
const captured = clause.captures === null ? null : copyResolved(body[0].args[1], answerEnv);
|
|
713
|
+
const relevant = this.program.inspectionEnabled
|
|
714
|
+
? [...unresolved.keys(), ...(captured === null ? [] : variables(captured).keys())]
|
|
715
|
+
: [];
|
|
716
|
+
// Walking the evidence is linear in its size, so do it only when the
|
|
717
|
+
// conclusion or captures still have variables an inspection could scope.
|
|
718
|
+
if (relevant.length) {
|
|
704
719
|
const scoped = scopedVariables(nodes, answerEnv);
|
|
705
|
-
const captured = clause.captures === null ? null : copyResolved(body[0].args[1], answerEnv);
|
|
706
|
-
const relevant = [...unresolved.keys(), ...(captured === null ? [] : variables(captured).keys())];
|
|
707
720
|
if (relevant.some((name) => scoped.has(name))) {
|
|
708
721
|
const inBody = new Set();
|
|
709
722
|
for (const goal of clause.body) for (const name of variables(goal).keys()) inBody.add(name);
|
|
@@ -716,8 +729,7 @@ export class Solver {
|
|
|
716
729
|
if (unresolved.size) {
|
|
717
730
|
// A generated rule also depends on its captures when its head omits
|
|
718
731
|
// them; otherwise two enabled instances could share a witness.
|
|
719
|
-
const
|
|
720
|
-
const witnesses = skolemTerms(clause, head, unresolved, answerEnv, context);
|
|
732
|
+
const witnesses = skolemTerms(clause, head, unresolved, answerEnv, captured);
|
|
721
733
|
[...unresolved.values()].forEach((value, i) => unify(value, witnesses[i], answerEnv));
|
|
722
734
|
}
|
|
723
735
|
const conclusions = unresolved.size
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// The release this checkout is; ./release keeps it in step with package.json.
|
|
2
|
-
export const VERSION = '0.1.
|
|
2
|
+
export const VERSION = '0.1.28';
|
package/src/writer.js
CHANGED
|
@@ -150,6 +150,34 @@ export class Lettering {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
// Names for write() that spell variables as the program wrote them, for
|
|
154
|
+
// messages about a running goal: the renamed-apart X#12 is X again and each
|
|
155
|
+
// anonymous variable is _. Distinct variables that share a name are numbered,
|
|
156
|
+
// so a message never shows two of them as one.
|
|
157
|
+
export class SourceNaming {
|
|
158
|
+
constructor() {
|
|
159
|
+
this.names = new Map();
|
|
160
|
+
this.taken = new Set();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
get(name) {
|
|
164
|
+
let shown = this.names.get(name);
|
|
165
|
+
if (shown === undefined) {
|
|
166
|
+
const hash = name.indexOf('#');
|
|
167
|
+
const base = hash < 0 ? name : name.slice(0, hash);
|
|
168
|
+
if (/^_[0-9]*$/.test(base)) {
|
|
169
|
+
shown = '_';
|
|
170
|
+
} else {
|
|
171
|
+
shown = variableText(base);
|
|
172
|
+
for (let n = 2; this.taken.has(shown); n++) shown = variableText(`${base}_${n}`);
|
|
173
|
+
this.taken.add(shown);
|
|
174
|
+
}
|
|
175
|
+
this.names.set(name, shown);
|
|
176
|
+
}
|
|
177
|
+
return shown;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
153
181
|
// Names for write() that name nothing but notice a variable.
|
|
154
182
|
class Spy {
|
|
155
183
|
constructor() {
|