eyeling 1.24.0 → 1.24.2
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/HANDBOOK.md +98 -0
- package/README.md +4 -4
- package/dist/browser/eyeling.browser.js +103 -0
- package/eyeling.js +103 -0
- package/lib/lexer.js +103 -0
- package/package.json +1 -1
- package/see/README.md +13 -11
- package/see/examples/_see.js +33 -2
- package/see/examples/age.js +37 -11
- package/see/examples/annotation.js +37 -11
- package/see/examples/backward.js +37 -11
- package/see/examples/backward_recursion.js +37 -11
- package/see/examples/bayes_diagnosis.js +37 -11
- package/see/examples/bayes_therapy.js +37 -11
- package/see/examples/bmi.js +37 -11
- package/see/examples/builtin_coverage.js +37 -11
- package/see/examples/collection.js +37 -11
- package/see/examples/complex.js +37 -11
- package/see/examples/complex_matrix_stability.js +37 -11
- package/see/examples/composition_of_injective_functions_is_injective.js +37 -11
- package/see/examples/control_system.js +37 -11
- package/see/examples/crypto_builtins_tests.js +37 -11
- package/see/examples/delfour.js +37 -11
- package/see/examples/digital_product_passport.js +37 -11
- package/see/examples/dijkstra.js +37 -11
- package/see/examples/dijkstra_risk_path.js +37 -11
- package/see/examples/doc/age.md +1 -1
- package/see/examples/doc/annotation.md +1 -1
- package/see/examples/doc/backward.md +1 -1
- package/see/examples/doc/backward_recursion.md +1 -1
- package/see/examples/doc/bayes_diagnosis.md +1 -1
- package/see/examples/doc/bayes_therapy.md +1 -1
- package/see/examples/doc/bmi.md +1 -1
- package/see/examples/doc/builtin_coverage.md +1 -1
- package/see/examples/doc/collection.md +1 -1
- package/see/examples/doc/complex.md +1 -1
- package/see/examples/doc/complex_matrix_stability.md +1 -1
- package/see/examples/doc/composition_of_injective_functions_is_injective.md +1 -1
- package/see/examples/doc/control_system.md +1 -1
- package/see/examples/doc/crypto_builtins_tests.md +1 -1
- package/see/examples/doc/delfour.md +1 -1
- package/see/examples/doc/digital_product_passport.md +1 -1
- package/see/examples/doc/dijkstra.md +1 -1
- package/see/examples/doc/dijkstra_risk_path.md +1 -1
- package/see/examples/doc/dog.md +1 -1
- package/see/examples/doc/eco_route_insight.md +1 -1
- package/see/examples/doc/equals.md +1 -1
- package/see/examples/doc/equivalence_classes_overlap_implies_same_class.md +1 -1
- package/see/examples/doc/euler_identity.md +1 -1
- package/see/examples/doc/ev_roundtrip_planner.md +1 -1
- package/see/examples/doc/existential_rule.md +1 -1
- package/see/examples/doc/expression_eval.md +1 -1
- package/see/examples/doc/family_cousins.md +1 -1
- package/see/examples/doc/fastpow.md +1 -1
- package/see/examples/doc/fibonacci.md +1 -1
- package/see/examples/doc/french_cities.md +1 -1
- package/see/examples/doc/fundamental_theorem_arithmetic.md +1 -1
- package/see/examples/doc/genetic_knapsack_selection.md +1 -1
- package/see/examples/doc/goldbach_1000.md +1 -1
- package/see/examples/doc/good_cobbler.md +1 -1
- package/see/examples/doc/gps.md +1 -1
- package/see/examples/doc/gray_code_counter.md +1 -1
- package/see/examples/doc/greatest_lower_bound_uniqueness.md +1 -1
- package/see/examples/doc/group_inverse_uniqueness.md +1 -1
- package/see/examples/doc/hadamard_approx.md +1 -1
- package/see/examples/doc/hanoi.md +1 -1
- package/see/examples/doc/odrl_dpv_risk_ranked.md +1 -1
- package/see/examples/doc/path_discovery.md +1 -1
- package/see/examples/doc/rc_discharge_envelope.md +1 -1
- package/see/examples/doc/rdf_message_flow.md +1 -1
- package/see/examples/doc/rdf_messages.md +1 -1
- package/see/examples/doc/school_placement_audit.md +1 -1
- package/see/examples/doc/smoke_arithmetic.md +1 -1
- package/see/examples/doc/socrates.md +1 -1
- package/see/examples/doc/triple_terms.md +26 -0
- package/see/examples/doc/wind_turbine.md +1 -1
- package/see/examples/doc/witch.md +1 -1
- package/see/examples/dog.js +37 -11
- package/see/examples/eco_route_insight.js +37 -11
- package/see/examples/equals.js +37 -11
- package/see/examples/equivalence_classes_overlap_implies_same_class.js +37 -11
- package/see/examples/euler_identity.js +37 -11
- package/see/examples/ev_roundtrip_planner.js +37 -11
- package/see/examples/existential_rule.js +37 -11
- package/see/examples/expression_eval.js +37 -11
- package/see/examples/family_cousins.js +37 -11
- package/see/examples/fastpow.js +37 -11
- package/see/examples/fibonacci.js +37 -11
- package/see/examples/french_cities.js +37 -11
- package/see/examples/fundamental_theorem_arithmetic.js +37 -11
- package/see/examples/genetic_knapsack_selection.js +37 -11
- package/see/examples/goldbach_1000.js +37 -11
- package/see/examples/good_cobbler.js +37 -11
- package/see/examples/gps.js +37 -11
- package/see/examples/gray_code_counter.js +37 -11
- package/see/examples/greatest_lower_bound_uniqueness.js +37 -11
- package/see/examples/group_inverse_uniqueness.js +37 -11
- package/see/examples/hadamard_approx.js +37 -11
- package/see/examples/hanoi.js +37 -11
- package/see/examples/input/triple_terms.trig +28 -0
- package/see/examples/n3/triple_terms.n3 +23 -0
- package/see/examples/odrl_dpv_risk_ranked.js +37 -11
- package/see/examples/output/age.md +3 -3
- package/see/examples/output/annotation.md +4 -4
- package/see/examples/output/backward.md +3 -3
- package/see/examples/output/backward_recursion.md +3 -3
- package/see/examples/output/bayes_diagnosis.md +1 -1
- package/see/examples/output/bayes_therapy.md +1 -1
- package/see/examples/output/bmi.md +1 -1
- package/see/examples/output/builtin_coverage.md +3 -3
- package/see/examples/output/collection.md +3 -3
- package/see/examples/output/complex.md +4 -4
- package/see/examples/output/complex_matrix_stability.md +1 -1
- package/see/examples/output/composition_of_injective_functions_is_injective.md +3 -3
- package/see/examples/output/control_system.md +3 -3
- package/see/examples/output/crypto_builtins_tests.md +3 -3
- package/see/examples/output/delfour.md +1 -1
- package/see/examples/output/digital_product_passport.md +1 -1
- package/see/examples/output/dijkstra.md +3 -3
- package/see/examples/output/dijkstra_risk_path.md +1 -1
- package/see/examples/output/dog.md +3 -3
- package/see/examples/output/eco_route_insight.md +1 -1
- package/see/examples/output/equals.md +3 -3
- package/see/examples/output/equivalence_classes_overlap_implies_same_class.md +3 -3
- package/see/examples/output/euler_identity.md +3 -3
- package/see/examples/output/ev_roundtrip_planner.md +1 -1
- package/see/examples/output/existential_rule.md +3 -3
- package/see/examples/output/expression_eval.md +3 -3
- package/see/examples/output/family_cousins.md +3 -3
- package/see/examples/output/fastpow.md +1 -1
- package/see/examples/output/fibonacci.md +1 -1
- package/see/examples/output/french_cities.md +3 -3
- package/see/examples/output/fundamental_theorem_arithmetic.md +1 -1
- package/see/examples/output/genetic_knapsack_selection.md +1 -1
- package/see/examples/output/goldbach_1000.md +1 -1
- package/see/examples/output/good_cobbler.md +4 -4
- package/see/examples/output/gps.md +1 -1
- package/see/examples/output/gray_code_counter.md +1 -1
- package/see/examples/output/greatest_lower_bound_uniqueness.md +3 -3
- package/see/examples/output/group_inverse_uniqueness.md +3 -3
- package/see/examples/output/hadamard_approx.md +3 -3
- package/see/examples/output/hanoi.md +3 -3
- package/see/examples/output/odrl_dpv_risk_ranked.md +3 -3
- package/see/examples/output/path_discovery.md +3 -3
- package/see/examples/output/rc_discharge_envelope.md +1 -1
- package/see/examples/output/rdf_message_flow.md +1 -1
- package/see/examples/output/rdf_messages.md +1 -1
- package/see/examples/output/school_placement_audit.md +1 -1
- package/see/examples/output/smoke_arithmetic.md +1 -1
- package/see/examples/output/socrates.md +3 -3
- package/see/examples/output/triple_terms.md +53 -0
- package/see/examples/output/wind_turbine.md +1 -1
- package/see/examples/output/witch.md +3 -3
- package/see/examples/path_discovery.js +37 -11
- package/see/examples/rc_discharge_envelope.js +37 -11
- package/see/examples/rdf_message_flow.js +37 -11
- package/see/examples/rdf_messages.js +37 -11
- package/see/examples/school_placement_audit.js +37 -11
- package/see/examples/smoke_arithmetic.js +37 -11
- package/see/examples/socrates.js +37 -11
- package/see/examples/triple_terms.js +1442 -0
- package/see/examples/wind_turbine.js +37 -11
- package/see/examples/witch.js +37 -11
- package/see/see.js +455 -94
- package/test/api.test.js +20 -0
|
@@ -8,6 +8,7 @@ const crypto = require('crypto');
|
|
|
8
8
|
|
|
9
9
|
function canonical(term) {
|
|
10
10
|
if (term.kind === 'list') return ['list', term.items.map(canonical)];
|
|
11
|
+
if (term.kind === 'triple') return ['triple', canonical(term.s), canonical(term.p), canonical(term.o)];
|
|
11
12
|
if (term.kind === 'formula') return ['formula', term.atoms.map((a) => [canonical(a.s), canonical(a.p), canonical(a.o)])];
|
|
12
13
|
return [term.kind, term.value];
|
|
13
14
|
}
|
|
@@ -17,6 +18,7 @@ function compoundIndexKey() { return Array.from(arguments).map(termIndexKey).joi
|
|
|
17
18
|
function termIsConcrete(t) {
|
|
18
19
|
if (!t || t.kind === 'var') return false;
|
|
19
20
|
if (t.kind === 'list') return t.items.every(termIsConcrete);
|
|
21
|
+
if (t.kind === 'triple') return termIsConcrete(t.s) && termIsConcrete(t.p) && termIsConcrete(t.o);
|
|
20
22
|
if (t.kind === 'formula') return t.atoms.every((a) => termIsConcrete(a.s) && termIsConcrete(a.p) && termIsConcrete(a.o));
|
|
21
23
|
return true;
|
|
22
24
|
}
|
|
@@ -32,6 +34,7 @@ function primitive(t) {
|
|
|
32
34
|
if (t.kind === 'iri') return t.value.replace(/^:/, '');
|
|
33
35
|
if (t.kind === 'blank') return t.value;
|
|
34
36
|
if (t.kind === 'list') return t.items.map(primitive);
|
|
37
|
+
if (t.kind === 'triple') return termToN3(t);
|
|
35
38
|
if (t.kind === 'formula') return termToN3(t);
|
|
36
39
|
return undefined;
|
|
37
40
|
}
|
|
@@ -52,6 +55,7 @@ function termToN3(t) {
|
|
|
52
55
|
if (t.kind === 'var') return '?' + t.value;
|
|
53
56
|
if (t.kind === 'blank') return t.value.startsWith('_:') ? t.value : '_:' + t.value.replace(/^_+/, '');
|
|
54
57
|
if (t.kind === 'list') return '(' + t.items.map(termToN3).join(' ') + ')';
|
|
58
|
+
if (t.kind === 'triple') return '<<( ' + termToN3(t.s) + ' ' + termToN3(t.p) + ' ' + termToN3(t.o) + ' )>>';
|
|
55
59
|
if (t.kind === 'formula') return '{ ' + t.atoms.map(atomToN3).join(' . ') + ' }';
|
|
56
60
|
return String(t.value ?? t);
|
|
57
61
|
}
|
|
@@ -74,6 +78,7 @@ function resolve(term, env, seen = new Set()) {
|
|
|
74
78
|
return resolve(env[term.value], env, seen);
|
|
75
79
|
}
|
|
76
80
|
if (term.kind === 'list') return list(term.items.map((item) => resolve(item, env, seen)));
|
|
81
|
+
if (term.kind === 'triple') return { kind: 'triple', s: resolve(term.s, env), p: resolve(term.p, env), o: resolve(term.o, env) };
|
|
77
82
|
if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: resolve(a.s, env), p: resolve(a.p, env), o: resolve(a.o, env) })) };
|
|
78
83
|
return term;
|
|
79
84
|
}
|
|
@@ -91,6 +96,14 @@ function unify(a, b, env) {
|
|
|
91
96
|
}
|
|
92
97
|
return out;
|
|
93
98
|
}
|
|
99
|
+
if (a.kind === 'triple' || b.kind === 'triple') {
|
|
100
|
+
if (a.kind !== 'triple' || b.kind !== 'triple') return null;
|
|
101
|
+
let out = unify(a.s, b.s, env);
|
|
102
|
+
if (!out) return null;
|
|
103
|
+
out = unify(a.p, b.p, out);
|
|
104
|
+
if (!out) return null;
|
|
105
|
+
return unify(a.o, b.o, out);
|
|
106
|
+
}
|
|
94
107
|
return deepEqual(a, b) ? env : null;
|
|
95
108
|
}
|
|
96
109
|
function bind(pattern, value, env) { return unify(pattern, value, env); }
|
|
@@ -106,6 +119,7 @@ function termIsGround(t, env) {
|
|
|
106
119
|
const r = resolve(t, env);
|
|
107
120
|
if (r.kind === 'var') return false;
|
|
108
121
|
if (r.kind === 'list') return r.items.every((item) => termIsGround(item, env));
|
|
122
|
+
if (r.kind === 'triple') return termIsGround(r.s, env) && termIsGround(r.p, env) && termIsGround(r.o, env);
|
|
109
123
|
if (r.kind === 'formula') return r.atoms.every((atom) => atomIsGround(atom, env));
|
|
110
124
|
return true;
|
|
111
125
|
}
|
|
@@ -665,6 +679,7 @@ function instantiate(term, env, ruleId) {
|
|
|
665
679
|
}
|
|
666
680
|
if (term.kind === 'blank') return blank('_:r' + ruleId + '_' + envSignature(env) + '_' + term.value.replace(/^_/, ''));
|
|
667
681
|
if (term.kind === 'list') return list(term.items.map((item) => instantiate(item, env, ruleId)));
|
|
682
|
+
if (term.kind === 'triple') return { kind: 'triple', s: instantiate(term.s, env, ruleId), p: instantiate(term.p, env, ruleId), o: instantiate(term.o, env, ruleId) };
|
|
668
683
|
if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: instantiate(a.s, env, ruleId), p: instantiate(a.p, env, ruleId), o: instantiate(a.o, env, ruleId) })) };
|
|
669
684
|
return cloneTerm(term);
|
|
670
685
|
}
|
|
@@ -1040,19 +1055,19 @@ function renderStructuredOutput({ title, graph, queries = [], rules = [], initia
|
|
|
1040
1055
|
const lines = [];
|
|
1041
1056
|
lines.push('# ' + title);
|
|
1042
1057
|
lines.push('');
|
|
1043
|
-
lines.push('##
|
|
1058
|
+
lines.push('## Entailment');
|
|
1044
1059
|
if (mode === 'query') {
|
|
1045
1060
|
lines.push('The compiled query selected ' + selected.length + ' fact(s) after the rule closure was computed.');
|
|
1046
1061
|
} else if (mode === 'formula') {
|
|
1047
|
-
lines.push('The derivation produced ' + selected.length + ' formula-valued
|
|
1062
|
+
lines.push('The derivation produced ' + selected.length + ' formula-valued entailment(s).');
|
|
1048
1063
|
} else {
|
|
1049
1064
|
lines.push('The derivation produced ' + derived.length + ' new fact(s) from ' + initialFacts.length + ' stated fact(s).');
|
|
1050
1065
|
}
|
|
1051
|
-
if (keyFact) lines.push('Main
|
|
1066
|
+
if (keyFact) lines.push('Main entailment: **' + factSentence(keyFact) + '**');
|
|
1052
1067
|
const bullets = selected.slice(-6).reverse();
|
|
1053
1068
|
if (bullets.length) {
|
|
1054
1069
|
lines.push('');
|
|
1055
|
-
lines.push('Selected
|
|
1070
|
+
lines.push('Selected entailments:');
|
|
1056
1071
|
for (const fact of bullets) lines.push('- ' + codeFact(fact));
|
|
1057
1072
|
}
|
|
1058
1073
|
lines.push('');
|
|
@@ -1114,15 +1129,15 @@ function dedupeExplanationHeadings(text) {
|
|
|
1114
1129
|
function normalizePublicReport(markdown, title) {
|
|
1115
1130
|
let text = String(markdown || '').trimEnd();
|
|
1116
1131
|
if (!/^\s*#\s+/m.test(text)) text = '# ' + title + '\n\n' + text;
|
|
1117
|
-
if (!/^##\s+
|
|
1118
|
-
text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n##
|
|
1132
|
+
if (!/^##\s+Entailment\s*$/mi.test(text)) {
|
|
1133
|
+
text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n## Entailment\n');
|
|
1119
1134
|
}
|
|
1120
1135
|
if (!/^##\s+Explanation\s*$/mi.test(text)) {
|
|
1121
1136
|
text += '\n\n## Explanation\nNo additional explanation was provided by the generated output.';
|
|
1122
1137
|
}
|
|
1123
1138
|
text = text.replace(/^##\s+([^\n]+?)\s*$/gm, (line, heading) => {
|
|
1124
1139
|
const normalized = heading.trim().toLowerCase();
|
|
1125
|
-
if (normalized === 'insight' || normalized === 'explanation') return '## ' + (normalized === '
|
|
1140
|
+
if (normalized === 'insight' || normalized === 'conclusion' || normalized === 'entailment' || normalized === 'explanation') return '## ' + (normalized === 'explanation' ? 'Explanation' : 'Entailment');
|
|
1126
1141
|
return '**' + heading.trim() + '**';
|
|
1127
1142
|
});
|
|
1128
1143
|
text = dedupeExplanationHeadings(text);
|
|
@@ -1131,13 +1146,13 @@ function normalizePublicReport(markdown, title) {
|
|
|
1131
1146
|
function markdownize(raw, title) {
|
|
1132
1147
|
let text = String(raw || '');
|
|
1133
1148
|
text = text
|
|
1134
|
-
.replace(/===\s*Answer\s*===/g, '##
|
|
1149
|
+
.replace(/===\s*Answer\s*===/g, '## Entailment')
|
|
1135
1150
|
.replace(/===\s*Reason\s+Why\s*===/gi, '## Explanation')
|
|
1136
1151
|
.replace(/===\s*Explanation\s*===/gi, '## Explanation')
|
|
1137
1152
|
.replace(/===\s*([^=]+?)\s*===/g, (_, h) => '**' + h.trim() + '**');
|
|
1138
1153
|
text = text.replace(/^C(\d+)\s+OK\s*-\s*/gm, 'C$1: ');
|
|
1139
1154
|
text = dedupeExplanationHeadings(text);
|
|
1140
|
-
if (!text.trim()) text = '##
|
|
1155
|
+
if (!text.trim()) text = '## Entailment\nNo log:outputString facts were derived.\n\n## Explanation\nThe compiled derivation did not produce authored report text.';
|
|
1141
1156
|
return normalizePublicReport(text, title);
|
|
1142
1157
|
}
|
|
1143
1158
|
function authoredSupportAppendix(graph, queries, rules, initialFacts, trace) {
|
|
@@ -1793,7 +1808,7 @@ const QUERIES = [
|
|
|
1793
1808
|
]
|
|
1794
1809
|
}
|
|
1795
1810
|
];
|
|
1796
|
-
const DOC_MARKDOWN = "# Control System\n\nGenerated by `see.js` from a Notation3 source file.\n\nCompact SEE version of the EYE reasoning/control-system example.\n\n## Compilation summary\n\n- Example name: `control_system`\n- Input facts emitted: 7\n- Forward rules compiled: 2\n- Backward predicate rules compiled: 2\n- Fuses compiled: 0\n- Predicate count: 15\n\n## Built-ins used\n\n- `math:difference`\n- `math:exponentiation`\n- `math:lessThan`\n- `math:notLessThan`\n- `math:product`\n- `math:quotient`\n- `math:sum`\n\n## Runtime model\n\nThe generated `examples/control_system.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/control_system.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/control-system.trig` or `examples/input/control_system.trig`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning `node examples/control_system.js` produces a SEE-style Markdown report with an **
|
|
1811
|
+
const DOC_MARKDOWN = "# Control System\n\nGenerated by `see.js` from a Notation3 source file.\n\nCompact SEE version of the EYE reasoning/control-system example.\n\n## Compilation summary\n\n- Example name: `control_system`\n- Input facts emitted: 7\n- Forward rules compiled: 2\n- Backward predicate rules compiled: 2\n- Fuses compiled: 0\n- Predicate count: 15\n\n## Built-ins used\n\n- `math:difference`\n- `math:exponentiation`\n- `math:lessThan`\n- `math:notLessThan`\n- `math:product`\n- `math:quotient`\n- `math:sum`\n\n## Runtime model\n\nThe generated `examples/control_system.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/control_system.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/control-system.trig` or `examples/input/control_system.trig`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning `node examples/control_system.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.\n";
|
|
1797
1812
|
function seeMetadata(data) { return (data && data.__see) || {}; }
|
|
1798
1813
|
function trustedDerivation(data) { const meta = seeMetadata(data); const facts = data && Array.isArray(data.facts) ? data.facts : []; const expectedFacts = EXPECTED_INPUT_FACTS || Number(meta.InputFacts || 0); if (meta.SourceSHA256 && meta.SourceSHA256 !== "0950a7098972ffc4883c2cc7410f7d54f5699111ecfc026bc88dba74ef0ffc40") throw new Error('input evidence does not match the N3 source compiled into this example'); const result = saturate(facts, RULES); const rawOutput = renderRawOutput(result.graph, QUERIES, RULES, facts); fail('Compiled N3 derivation failed', { 'input evidence metadata is present and matches compiled source': meta.SourceSHA256 === "0950a7098972ffc4883c2cc7410f7d54f5699111ecfc026bc88dba74ef0ffc40", 'input evidence facts were loaded': expectedFacts > 0 ? facts.length === expectedFacts : facts.length >= 0, 'compiled rules were loaded': RULES.length === 4, 'compiled query directives were loaded': QUERIES.length === 1, 'a derivation fixpoint was reached': result.graph.facts.length >= facts.length, 'query or output facts were produced': rawOutput.length > 0 }); return { ...result, rawOutput, inputFacts: facts }; }
|
|
1799
1814
|
function snapshotMarkdown(markdown) { return markdown.split(/\n/).map((line) => line ? line + ' \n' : '\n').join(''); }
|
|
@@ -1827,6 +1842,16 @@ function formalOutputFacts(graph, queries, rules, initialFacts) {
|
|
|
1827
1842
|
}
|
|
1828
1843
|
return out;
|
|
1829
1844
|
}
|
|
1845
|
+
function termHasTripleTerm(term) {
|
|
1846
|
+
if (!term) return false;
|
|
1847
|
+
if (term.kind === 'triple') return true;
|
|
1848
|
+
if (term.kind === 'list') return term.items.some(termHasTripleTerm);
|
|
1849
|
+
if (term.kind === 'formula') return term.atoms.some(atomHasTripleTerm);
|
|
1850
|
+
return false;
|
|
1851
|
+
}
|
|
1852
|
+
function atomHasTripleTerm(atom) { return termHasTripleTerm(atom.s) || termHasTripleTerm(atom.p) || termHasTripleTerm(atom.o); }
|
|
1853
|
+
function factsHaveTripleTerms(facts) { return (facts || []).some(atomHasTripleTerm); }
|
|
1854
|
+
function trigHasVersion12(trig) { return /^s*(?:@version|VERSION)s+["']1.2["']/mi.test(String(trig || '')); }
|
|
1830
1855
|
function trigGraphBlock(label, atoms) {
|
|
1831
1856
|
const lines = [label + ' {'];
|
|
1832
1857
|
for (const atom of atoms || []) lines.push(' ' + atomToN3(atom) + ' .');
|
|
@@ -1874,7 +1899,8 @@ function formalOutputToTrig(facts, trig) {
|
|
|
1874
1899
|
const prefixes = prefixLinesFromTrig(trig);
|
|
1875
1900
|
if (state.needOutPrefix && !prefixes.some((line) => line.toLowerCase().startsWith('@prefix out:'))) prefixes.push('@prefix out: <https://example.org/see/output#> .');
|
|
1876
1901
|
const nl = String.fromCharCode(10);
|
|
1877
|
-
|
|
1902
|
+
const version = factsHaveTripleTerms(facts) ? 'VERSION "1.2"' + nl + nl : '';
|
|
1903
|
+
return version + prefixes.join(nl) + nl + nl + body.join(nl);
|
|
1878
1904
|
}
|
|
1879
1905
|
function appendFormalTrigOutput(markdown, graph, queries, rules, initialFacts, data) {
|
|
1880
1906
|
const trig = formalOutputToTrig(formalOutputFacts(graph, queries, rules, initialFacts), data && data.trig);
|
|
@@ -8,6 +8,7 @@ const crypto = require('crypto');
|
|
|
8
8
|
|
|
9
9
|
function canonical(term) {
|
|
10
10
|
if (term.kind === 'list') return ['list', term.items.map(canonical)];
|
|
11
|
+
if (term.kind === 'triple') return ['triple', canonical(term.s), canonical(term.p), canonical(term.o)];
|
|
11
12
|
if (term.kind === 'formula') return ['formula', term.atoms.map((a) => [canonical(a.s), canonical(a.p), canonical(a.o)])];
|
|
12
13
|
return [term.kind, term.value];
|
|
13
14
|
}
|
|
@@ -17,6 +18,7 @@ function compoundIndexKey() { return Array.from(arguments).map(termIndexKey).joi
|
|
|
17
18
|
function termIsConcrete(t) {
|
|
18
19
|
if (!t || t.kind === 'var') return false;
|
|
19
20
|
if (t.kind === 'list') return t.items.every(termIsConcrete);
|
|
21
|
+
if (t.kind === 'triple') return termIsConcrete(t.s) && termIsConcrete(t.p) && termIsConcrete(t.o);
|
|
20
22
|
if (t.kind === 'formula') return t.atoms.every((a) => termIsConcrete(a.s) && termIsConcrete(a.p) && termIsConcrete(a.o));
|
|
21
23
|
return true;
|
|
22
24
|
}
|
|
@@ -32,6 +34,7 @@ function primitive(t) {
|
|
|
32
34
|
if (t.kind === 'iri') return t.value.replace(/^:/, '');
|
|
33
35
|
if (t.kind === 'blank') return t.value;
|
|
34
36
|
if (t.kind === 'list') return t.items.map(primitive);
|
|
37
|
+
if (t.kind === 'triple') return termToN3(t);
|
|
35
38
|
if (t.kind === 'formula') return termToN3(t);
|
|
36
39
|
return undefined;
|
|
37
40
|
}
|
|
@@ -52,6 +55,7 @@ function termToN3(t) {
|
|
|
52
55
|
if (t.kind === 'var') return '?' + t.value;
|
|
53
56
|
if (t.kind === 'blank') return t.value.startsWith('_:') ? t.value : '_:' + t.value.replace(/^_+/, '');
|
|
54
57
|
if (t.kind === 'list') return '(' + t.items.map(termToN3).join(' ') + ')';
|
|
58
|
+
if (t.kind === 'triple') return '<<( ' + termToN3(t.s) + ' ' + termToN3(t.p) + ' ' + termToN3(t.o) + ' )>>';
|
|
55
59
|
if (t.kind === 'formula') return '{ ' + t.atoms.map(atomToN3).join(' . ') + ' }';
|
|
56
60
|
return String(t.value ?? t);
|
|
57
61
|
}
|
|
@@ -74,6 +78,7 @@ function resolve(term, env, seen = new Set()) {
|
|
|
74
78
|
return resolve(env[term.value], env, seen);
|
|
75
79
|
}
|
|
76
80
|
if (term.kind === 'list') return list(term.items.map((item) => resolve(item, env, seen)));
|
|
81
|
+
if (term.kind === 'triple') return { kind: 'triple', s: resolve(term.s, env), p: resolve(term.p, env), o: resolve(term.o, env) };
|
|
77
82
|
if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: resolve(a.s, env), p: resolve(a.p, env), o: resolve(a.o, env) })) };
|
|
78
83
|
return term;
|
|
79
84
|
}
|
|
@@ -91,6 +96,14 @@ function unify(a, b, env) {
|
|
|
91
96
|
}
|
|
92
97
|
return out;
|
|
93
98
|
}
|
|
99
|
+
if (a.kind === 'triple' || b.kind === 'triple') {
|
|
100
|
+
if (a.kind !== 'triple' || b.kind !== 'triple') return null;
|
|
101
|
+
let out = unify(a.s, b.s, env);
|
|
102
|
+
if (!out) return null;
|
|
103
|
+
out = unify(a.p, b.p, out);
|
|
104
|
+
if (!out) return null;
|
|
105
|
+
return unify(a.o, b.o, out);
|
|
106
|
+
}
|
|
94
107
|
return deepEqual(a, b) ? env : null;
|
|
95
108
|
}
|
|
96
109
|
function bind(pattern, value, env) { return unify(pattern, value, env); }
|
|
@@ -106,6 +119,7 @@ function termIsGround(t, env) {
|
|
|
106
119
|
const r = resolve(t, env);
|
|
107
120
|
if (r.kind === 'var') return false;
|
|
108
121
|
if (r.kind === 'list') return r.items.every((item) => termIsGround(item, env));
|
|
122
|
+
if (r.kind === 'triple') return termIsGround(r.s, env) && termIsGround(r.p, env) && termIsGround(r.o, env);
|
|
109
123
|
if (r.kind === 'formula') return r.atoms.every((atom) => atomIsGround(atom, env));
|
|
110
124
|
return true;
|
|
111
125
|
}
|
|
@@ -665,6 +679,7 @@ function instantiate(term, env, ruleId) {
|
|
|
665
679
|
}
|
|
666
680
|
if (term.kind === 'blank') return blank('_:r' + ruleId + '_' + envSignature(env) + '_' + term.value.replace(/^_/, ''));
|
|
667
681
|
if (term.kind === 'list') return list(term.items.map((item) => instantiate(item, env, ruleId)));
|
|
682
|
+
if (term.kind === 'triple') return { kind: 'triple', s: instantiate(term.s, env, ruleId), p: instantiate(term.p, env, ruleId), o: instantiate(term.o, env, ruleId) };
|
|
668
683
|
if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: instantiate(a.s, env, ruleId), p: instantiate(a.p, env, ruleId), o: instantiate(a.o, env, ruleId) })) };
|
|
669
684
|
return cloneTerm(term);
|
|
670
685
|
}
|
|
@@ -1040,19 +1055,19 @@ function renderStructuredOutput({ title, graph, queries = [], rules = [], initia
|
|
|
1040
1055
|
const lines = [];
|
|
1041
1056
|
lines.push('# ' + title);
|
|
1042
1057
|
lines.push('');
|
|
1043
|
-
lines.push('##
|
|
1058
|
+
lines.push('## Entailment');
|
|
1044
1059
|
if (mode === 'query') {
|
|
1045
1060
|
lines.push('The compiled query selected ' + selected.length + ' fact(s) after the rule closure was computed.');
|
|
1046
1061
|
} else if (mode === 'formula') {
|
|
1047
|
-
lines.push('The derivation produced ' + selected.length + ' formula-valued
|
|
1062
|
+
lines.push('The derivation produced ' + selected.length + ' formula-valued entailment(s).');
|
|
1048
1063
|
} else {
|
|
1049
1064
|
lines.push('The derivation produced ' + derived.length + ' new fact(s) from ' + initialFacts.length + ' stated fact(s).');
|
|
1050
1065
|
}
|
|
1051
|
-
if (keyFact) lines.push('Main
|
|
1066
|
+
if (keyFact) lines.push('Main entailment: **' + factSentence(keyFact) + '**');
|
|
1052
1067
|
const bullets = selected.slice(-6).reverse();
|
|
1053
1068
|
if (bullets.length) {
|
|
1054
1069
|
lines.push('');
|
|
1055
|
-
lines.push('Selected
|
|
1070
|
+
lines.push('Selected entailments:');
|
|
1056
1071
|
for (const fact of bullets) lines.push('- ' + codeFact(fact));
|
|
1057
1072
|
}
|
|
1058
1073
|
lines.push('');
|
|
@@ -1114,15 +1129,15 @@ function dedupeExplanationHeadings(text) {
|
|
|
1114
1129
|
function normalizePublicReport(markdown, title) {
|
|
1115
1130
|
let text = String(markdown || '').trimEnd();
|
|
1116
1131
|
if (!/^\s*#\s+/m.test(text)) text = '# ' + title + '\n\n' + text;
|
|
1117
|
-
if (!/^##\s+
|
|
1118
|
-
text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n##
|
|
1132
|
+
if (!/^##\s+Entailment\s*$/mi.test(text)) {
|
|
1133
|
+
text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n## Entailment\n');
|
|
1119
1134
|
}
|
|
1120
1135
|
if (!/^##\s+Explanation\s*$/mi.test(text)) {
|
|
1121
1136
|
text += '\n\n## Explanation\nNo additional explanation was provided by the generated output.';
|
|
1122
1137
|
}
|
|
1123
1138
|
text = text.replace(/^##\s+([^\n]+?)\s*$/gm, (line, heading) => {
|
|
1124
1139
|
const normalized = heading.trim().toLowerCase();
|
|
1125
|
-
if (normalized === 'insight' || normalized === 'explanation') return '## ' + (normalized === '
|
|
1140
|
+
if (normalized === 'insight' || normalized === 'conclusion' || normalized === 'entailment' || normalized === 'explanation') return '## ' + (normalized === 'explanation' ? 'Explanation' : 'Entailment');
|
|
1126
1141
|
return '**' + heading.trim() + '**';
|
|
1127
1142
|
});
|
|
1128
1143
|
text = dedupeExplanationHeadings(text);
|
|
@@ -1131,13 +1146,13 @@ function normalizePublicReport(markdown, title) {
|
|
|
1131
1146
|
function markdownize(raw, title) {
|
|
1132
1147
|
let text = String(raw || '');
|
|
1133
1148
|
text = text
|
|
1134
|
-
.replace(/===\s*Answer\s*===/g, '##
|
|
1149
|
+
.replace(/===\s*Answer\s*===/g, '## Entailment')
|
|
1135
1150
|
.replace(/===\s*Reason\s+Why\s*===/gi, '## Explanation')
|
|
1136
1151
|
.replace(/===\s*Explanation\s*===/gi, '## Explanation')
|
|
1137
1152
|
.replace(/===\s*([^=]+?)\s*===/g, (_, h) => '**' + h.trim() + '**');
|
|
1138
1153
|
text = text.replace(/^C(\d+)\s+OK\s*-\s*/gm, 'C$1: ');
|
|
1139
1154
|
text = dedupeExplanationHeadings(text);
|
|
1140
|
-
if (!text.trim()) text = '##
|
|
1155
|
+
if (!text.trim()) text = '## Entailment\nNo log:outputString facts were derived.\n\n## Explanation\nThe compiled derivation did not produce authored report text.';
|
|
1141
1156
|
return normalizePublicReport(text, title);
|
|
1142
1157
|
}
|
|
1143
1158
|
function authoredSupportAppendix(graph, queries, rules, initialFacts, trace) {
|
|
@@ -1364,7 +1379,7 @@ const RULES = [
|
|
|
1364
1379
|
}
|
|
1365
1380
|
];
|
|
1366
1381
|
const QUERIES = [];
|
|
1367
|
-
const DOC_MARKDOWN = "# crypto builtins tests\n\nGenerated by `see.js` from a Notation3 source file.\n\n## Compilation summary\n\n- Example name: `crypto_builtins_tests`\n- Input facts emitted: 0\n- Forward rules compiled: 4\n- Backward predicate rules compiled: 0\n- Fuses compiled: 0\n- Predicate count: 5\n\n## Built-ins used\n\n- `crypto:md5`\n- `crypto:sha`\n- `crypto:sha256`\n- `crypto:sha512`\n\n## Runtime model\n\nThe generated `examples/crypto_builtins_tests.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/crypto_builtins_tests.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/crypto-builtins-tests.trig` or `examples/input/crypto_builtins_tests.trig`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning `node examples/crypto_builtins_tests.js` produces a SEE-style Markdown report with an **
|
|
1382
|
+
const DOC_MARKDOWN = "# crypto builtins tests\n\nGenerated by `see.js` from a Notation3 source file.\n\n## Compilation summary\n\n- Example name: `crypto_builtins_tests`\n- Input facts emitted: 0\n- Forward rules compiled: 4\n- Backward predicate rules compiled: 0\n- Fuses compiled: 0\n- Predicate count: 5\n\n## Built-ins used\n\n- `crypto:md5`\n- `crypto:sha`\n- `crypto:sha256`\n- `crypto:sha512`\n\n## Runtime model\n\nThe generated `examples/crypto_builtins_tests.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/crypto_builtins_tests.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/crypto-builtins-tests.trig` or `examples/input/crypto_builtins_tests.trig`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning `node examples/crypto_builtins_tests.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.\n";
|
|
1368
1383
|
function seeMetadata(data) { return (data && data.__see) || {}; }
|
|
1369
1384
|
function trustedDerivation(data) { const meta = seeMetadata(data); const facts = data && Array.isArray(data.facts) ? data.facts : []; const expectedFacts = EXPECTED_INPUT_FACTS || Number(meta.InputFacts || 0); if (meta.SourceSHA256 && meta.SourceSHA256 !== "36867ada425da37071cbc96e74a10311adbfe437adf3d8e56b4a1d95a23763b5") throw new Error('input evidence does not match the N3 source compiled into this example'); const result = saturate(facts, RULES); const rawOutput = renderRawOutput(result.graph, QUERIES, RULES, facts); fail('Compiled N3 derivation failed', { 'input evidence metadata is present and matches compiled source': meta.SourceSHA256 === "36867ada425da37071cbc96e74a10311adbfe437adf3d8e56b4a1d95a23763b5", 'input evidence facts were loaded': expectedFacts > 0 ? facts.length === expectedFacts : facts.length >= 0, 'compiled rules were loaded': RULES.length === 4, 'compiled query directives were loaded': QUERIES.length === 0, 'a derivation fixpoint was reached': result.graph.facts.length >= facts.length, 'query or output facts were produced': rawOutput.length > 0 }); return { ...result, rawOutput, inputFacts: facts }; }
|
|
1370
1385
|
function snapshotMarkdown(markdown) { return markdown.split(/\n/).map((line) => line ? line + ' \n' : '\n').join(''); }
|
|
@@ -1398,6 +1413,16 @@ function formalOutputFacts(graph, queries, rules, initialFacts) {
|
|
|
1398
1413
|
}
|
|
1399
1414
|
return out;
|
|
1400
1415
|
}
|
|
1416
|
+
function termHasTripleTerm(term) {
|
|
1417
|
+
if (!term) return false;
|
|
1418
|
+
if (term.kind === 'triple') return true;
|
|
1419
|
+
if (term.kind === 'list') return term.items.some(termHasTripleTerm);
|
|
1420
|
+
if (term.kind === 'formula') return term.atoms.some(atomHasTripleTerm);
|
|
1421
|
+
return false;
|
|
1422
|
+
}
|
|
1423
|
+
function atomHasTripleTerm(atom) { return termHasTripleTerm(atom.s) || termHasTripleTerm(atom.p) || termHasTripleTerm(atom.o); }
|
|
1424
|
+
function factsHaveTripleTerms(facts) { return (facts || []).some(atomHasTripleTerm); }
|
|
1425
|
+
function trigHasVersion12(trig) { return /^s*(?:@version|VERSION)s+["']1.2["']/mi.test(String(trig || '')); }
|
|
1401
1426
|
function trigGraphBlock(label, atoms) {
|
|
1402
1427
|
const lines = [label + ' {'];
|
|
1403
1428
|
for (const atom of atoms || []) lines.push(' ' + atomToN3(atom) + ' .');
|
|
@@ -1445,7 +1470,8 @@ function formalOutputToTrig(facts, trig) {
|
|
|
1445
1470
|
const prefixes = prefixLinesFromTrig(trig);
|
|
1446
1471
|
if (state.needOutPrefix && !prefixes.some((line) => line.toLowerCase().startsWith('@prefix out:'))) prefixes.push('@prefix out: <https://example.org/see/output#> .');
|
|
1447
1472
|
const nl = String.fromCharCode(10);
|
|
1448
|
-
|
|
1473
|
+
const version = factsHaveTripleTerms(facts) ? 'VERSION "1.2"' + nl + nl : '';
|
|
1474
|
+
return version + prefixes.join(nl) + nl + nl + body.join(nl);
|
|
1449
1475
|
}
|
|
1450
1476
|
function appendFormalTrigOutput(markdown, graph, queries, rules, initialFacts, data) {
|
|
1451
1477
|
const trig = formalOutputToTrig(formalOutputFacts(graph, queries, rules, initialFacts), data && data.trig);
|
package/see/examples/delfour.js
CHANGED
|
@@ -8,6 +8,7 @@ const crypto = require('crypto');
|
|
|
8
8
|
|
|
9
9
|
function canonical(term) {
|
|
10
10
|
if (term.kind === 'list') return ['list', term.items.map(canonical)];
|
|
11
|
+
if (term.kind === 'triple') return ['triple', canonical(term.s), canonical(term.p), canonical(term.o)];
|
|
11
12
|
if (term.kind === 'formula') return ['formula', term.atoms.map((a) => [canonical(a.s), canonical(a.p), canonical(a.o)])];
|
|
12
13
|
return [term.kind, term.value];
|
|
13
14
|
}
|
|
@@ -17,6 +18,7 @@ function compoundIndexKey() { return Array.from(arguments).map(termIndexKey).joi
|
|
|
17
18
|
function termIsConcrete(t) {
|
|
18
19
|
if (!t || t.kind === 'var') return false;
|
|
19
20
|
if (t.kind === 'list') return t.items.every(termIsConcrete);
|
|
21
|
+
if (t.kind === 'triple') return termIsConcrete(t.s) && termIsConcrete(t.p) && termIsConcrete(t.o);
|
|
20
22
|
if (t.kind === 'formula') return t.atoms.every((a) => termIsConcrete(a.s) && termIsConcrete(a.p) && termIsConcrete(a.o));
|
|
21
23
|
return true;
|
|
22
24
|
}
|
|
@@ -32,6 +34,7 @@ function primitive(t) {
|
|
|
32
34
|
if (t.kind === 'iri') return t.value.replace(/^:/, '');
|
|
33
35
|
if (t.kind === 'blank') return t.value;
|
|
34
36
|
if (t.kind === 'list') return t.items.map(primitive);
|
|
37
|
+
if (t.kind === 'triple') return termToN3(t);
|
|
35
38
|
if (t.kind === 'formula') return termToN3(t);
|
|
36
39
|
return undefined;
|
|
37
40
|
}
|
|
@@ -52,6 +55,7 @@ function termToN3(t) {
|
|
|
52
55
|
if (t.kind === 'var') return '?' + t.value;
|
|
53
56
|
if (t.kind === 'blank') return t.value.startsWith('_:') ? t.value : '_:' + t.value.replace(/^_+/, '');
|
|
54
57
|
if (t.kind === 'list') return '(' + t.items.map(termToN3).join(' ') + ')';
|
|
58
|
+
if (t.kind === 'triple') return '<<( ' + termToN3(t.s) + ' ' + termToN3(t.p) + ' ' + termToN3(t.o) + ' )>>';
|
|
55
59
|
if (t.kind === 'formula') return '{ ' + t.atoms.map(atomToN3).join(' . ') + ' }';
|
|
56
60
|
return String(t.value ?? t);
|
|
57
61
|
}
|
|
@@ -74,6 +78,7 @@ function resolve(term, env, seen = new Set()) {
|
|
|
74
78
|
return resolve(env[term.value], env, seen);
|
|
75
79
|
}
|
|
76
80
|
if (term.kind === 'list') return list(term.items.map((item) => resolve(item, env, seen)));
|
|
81
|
+
if (term.kind === 'triple') return { kind: 'triple', s: resolve(term.s, env), p: resolve(term.p, env), o: resolve(term.o, env) };
|
|
77
82
|
if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: resolve(a.s, env), p: resolve(a.p, env), o: resolve(a.o, env) })) };
|
|
78
83
|
return term;
|
|
79
84
|
}
|
|
@@ -91,6 +96,14 @@ function unify(a, b, env) {
|
|
|
91
96
|
}
|
|
92
97
|
return out;
|
|
93
98
|
}
|
|
99
|
+
if (a.kind === 'triple' || b.kind === 'triple') {
|
|
100
|
+
if (a.kind !== 'triple' || b.kind !== 'triple') return null;
|
|
101
|
+
let out = unify(a.s, b.s, env);
|
|
102
|
+
if (!out) return null;
|
|
103
|
+
out = unify(a.p, b.p, out);
|
|
104
|
+
if (!out) return null;
|
|
105
|
+
return unify(a.o, b.o, out);
|
|
106
|
+
}
|
|
94
107
|
return deepEqual(a, b) ? env : null;
|
|
95
108
|
}
|
|
96
109
|
function bind(pattern, value, env) { return unify(pattern, value, env); }
|
|
@@ -106,6 +119,7 @@ function termIsGround(t, env) {
|
|
|
106
119
|
const r = resolve(t, env);
|
|
107
120
|
if (r.kind === 'var') return false;
|
|
108
121
|
if (r.kind === 'list') return r.items.every((item) => termIsGround(item, env));
|
|
122
|
+
if (r.kind === 'triple') return termIsGround(r.s, env) && termIsGround(r.p, env) && termIsGround(r.o, env);
|
|
109
123
|
if (r.kind === 'formula') return r.atoms.every((atom) => atomIsGround(atom, env));
|
|
110
124
|
return true;
|
|
111
125
|
}
|
|
@@ -665,6 +679,7 @@ function instantiate(term, env, ruleId) {
|
|
|
665
679
|
}
|
|
666
680
|
if (term.kind === 'blank') return blank('_:r' + ruleId + '_' + envSignature(env) + '_' + term.value.replace(/^_/, ''));
|
|
667
681
|
if (term.kind === 'list') return list(term.items.map((item) => instantiate(item, env, ruleId)));
|
|
682
|
+
if (term.kind === 'triple') return { kind: 'triple', s: instantiate(term.s, env, ruleId), p: instantiate(term.p, env, ruleId), o: instantiate(term.o, env, ruleId) };
|
|
668
683
|
if (term.kind === 'formula') return { kind: 'formula', atoms: term.atoms.map((a) => ({ s: instantiate(a.s, env, ruleId), p: instantiate(a.p, env, ruleId), o: instantiate(a.o, env, ruleId) })) };
|
|
669
684
|
return cloneTerm(term);
|
|
670
685
|
}
|
|
@@ -1040,19 +1055,19 @@ function renderStructuredOutput({ title, graph, queries = [], rules = [], initia
|
|
|
1040
1055
|
const lines = [];
|
|
1041
1056
|
lines.push('# ' + title);
|
|
1042
1057
|
lines.push('');
|
|
1043
|
-
lines.push('##
|
|
1058
|
+
lines.push('## Entailment');
|
|
1044
1059
|
if (mode === 'query') {
|
|
1045
1060
|
lines.push('The compiled query selected ' + selected.length + ' fact(s) after the rule closure was computed.');
|
|
1046
1061
|
} else if (mode === 'formula') {
|
|
1047
|
-
lines.push('The derivation produced ' + selected.length + ' formula-valued
|
|
1062
|
+
lines.push('The derivation produced ' + selected.length + ' formula-valued entailment(s).');
|
|
1048
1063
|
} else {
|
|
1049
1064
|
lines.push('The derivation produced ' + derived.length + ' new fact(s) from ' + initialFacts.length + ' stated fact(s).');
|
|
1050
1065
|
}
|
|
1051
|
-
if (keyFact) lines.push('Main
|
|
1066
|
+
if (keyFact) lines.push('Main entailment: **' + factSentence(keyFact) + '**');
|
|
1052
1067
|
const bullets = selected.slice(-6).reverse();
|
|
1053
1068
|
if (bullets.length) {
|
|
1054
1069
|
lines.push('');
|
|
1055
|
-
lines.push('Selected
|
|
1070
|
+
lines.push('Selected entailments:');
|
|
1056
1071
|
for (const fact of bullets) lines.push('- ' + codeFact(fact));
|
|
1057
1072
|
}
|
|
1058
1073
|
lines.push('');
|
|
@@ -1114,15 +1129,15 @@ function dedupeExplanationHeadings(text) {
|
|
|
1114
1129
|
function normalizePublicReport(markdown, title) {
|
|
1115
1130
|
let text = String(markdown || '').trimEnd();
|
|
1116
1131
|
if (!/^\s*#\s+/m.test(text)) text = '# ' + title + '\n\n' + text;
|
|
1117
|
-
if (!/^##\s+
|
|
1118
|
-
text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n##
|
|
1132
|
+
if (!/^##\s+Entailment\s*$/mi.test(text)) {
|
|
1133
|
+
text = text.replace(/^(#\s+[^\n]+\n*)/, '$1\n## Entailment\n');
|
|
1119
1134
|
}
|
|
1120
1135
|
if (!/^##\s+Explanation\s*$/mi.test(text)) {
|
|
1121
1136
|
text += '\n\n## Explanation\nNo additional explanation was provided by the generated output.';
|
|
1122
1137
|
}
|
|
1123
1138
|
text = text.replace(/^##\s+([^\n]+?)\s*$/gm, (line, heading) => {
|
|
1124
1139
|
const normalized = heading.trim().toLowerCase();
|
|
1125
|
-
if (normalized === 'insight' || normalized === 'explanation') return '## ' + (normalized === '
|
|
1140
|
+
if (normalized === 'insight' || normalized === 'conclusion' || normalized === 'entailment' || normalized === 'explanation') return '## ' + (normalized === 'explanation' ? 'Explanation' : 'Entailment');
|
|
1126
1141
|
return '**' + heading.trim() + '**';
|
|
1127
1142
|
});
|
|
1128
1143
|
text = dedupeExplanationHeadings(text);
|
|
@@ -1131,13 +1146,13 @@ function normalizePublicReport(markdown, title) {
|
|
|
1131
1146
|
function markdownize(raw, title) {
|
|
1132
1147
|
let text = String(raw || '');
|
|
1133
1148
|
text = text
|
|
1134
|
-
.replace(/===\s*Answer\s*===/g, '##
|
|
1149
|
+
.replace(/===\s*Answer\s*===/g, '## Entailment')
|
|
1135
1150
|
.replace(/===\s*Reason\s+Why\s*===/gi, '## Explanation')
|
|
1136
1151
|
.replace(/===\s*Explanation\s*===/gi, '## Explanation')
|
|
1137
1152
|
.replace(/===\s*([^=]+?)\s*===/g, (_, h) => '**' + h.trim() + '**');
|
|
1138
1153
|
text = text.replace(/^C(\d+)\s+OK\s*-\s*/gm, 'C$1: ');
|
|
1139
1154
|
text = dedupeExplanationHeadings(text);
|
|
1140
|
-
if (!text.trim()) text = '##
|
|
1155
|
+
if (!text.trim()) text = '## Entailment\nNo log:outputString facts were derived.\n\n## Explanation\nThe compiled derivation did not produce authored report text.';
|
|
1141
1156
|
return normalizePublicReport(text, title);
|
|
1142
1157
|
}
|
|
1143
1158
|
function authoredSupportAppendix(graph, queries, rules, initialFacts, trace) {
|
|
@@ -3049,7 +3064,7 @@ const QUERIES = [
|
|
|
3049
3064
|
]
|
|
3050
3065
|
}
|
|
3051
3066
|
];
|
|
3052
|
-
const DOC_MARKDOWN = "# Delfour\n\nGenerated by `see.js` from a Notation3 source file.\n\nN3-compiled version of the Delfour insight-economy example. A private phone\ncondition is desensitized into a scoped low-sugar insight; the scanner may use\nit for shopping assistance, but not for marketing.\n\n## Compilation summary\n\n- Example name: `delfour`\n- Input facts emitted: 61\n- Forward rules compiled: 16\n- Backward predicate rules compiled: 0\n- Fuses compiled: 3\n- Predicate count: 62\n\n## Built-ins used\n\n- `crypto:sha256`\n- `log:notEqualTo`\n- `log:notIncludes`\n- `log:outputString`\n- `math:greaterThan`\n- `math:notEqualTo`\n- `math:notGreaterThan`\n- `math:notLessThan`\n- `string:format`\n- `string:notMatches`\n\n## Runtime model\n\nThe generated `examples/delfour.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/delfour.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/delfour.trig` or `examples/input/delfour.trig`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning `node examples/delfour.js` produces a SEE-style Markdown report with an **
|
|
3067
|
+
const DOC_MARKDOWN = "# Delfour\n\nGenerated by `see.js` from a Notation3 source file.\n\nN3-compiled version of the Delfour insight-economy example. A private phone\ncondition is desensitized into a scoped low-sugar insight; the scanner may use\nit for shopping assistance, but not for marketing.\n\n## Compilation summary\n\n- Example name: `delfour`\n- Input facts emitted: 61\n- Forward rules compiled: 16\n- Backward predicate rules compiled: 0\n- Fuses compiled: 3\n- Predicate count: 62\n\n## Built-ins used\n\n- `crypto:sha256`\n- `log:notEqualTo`\n- `log:notIncludes`\n- `log:outputString`\n- `math:greaterThan`\n- `math:notEqualTo`\n- `math:notGreaterThan`\n- `math:notLessThan`\n- `string:format`\n- `string:notMatches`\n\n## Runtime model\n\nThe generated `examples/delfour.js` is a specialized JavaScript derivation program. For ordinary sources, `see.js` emits the source facts as `examples/input/delfour.trig`. For rules-only sources, generation can reuse an existing external evidence file such as `examples/input/delfour.trig` or `examples/input/delfour.trig`. The runner reads that TriG evidence directly and performs a local fixpoint derivation; it does not parse the program source or call an external reasoner.\n\n## Output model\n\nRunning `node examples/delfour.js` produces a SEE-style Markdown report with an **Entailment** section, an **Explanation** section, and a **Formal TriG Output** section containing the selected derived/query facts.\n";
|
|
3053
3068
|
function seeMetadata(data) { return (data && data.__see) || {}; }
|
|
3054
3069
|
function trustedDerivation(data) { const meta = seeMetadata(data); const facts = data && Array.isArray(data.facts) ? data.facts : []; const expectedFacts = EXPECTED_INPUT_FACTS || Number(meta.InputFacts || 0); if (meta.SourceSHA256 && meta.SourceSHA256 !== "250631cb3de8addff7037b54789b63b2ea034eb6b579c61a9feca542efd584c1") throw new Error('input evidence does not match the N3 source compiled into this example'); const result = saturate(facts, RULES); const rawOutput = renderRawOutput(result.graph, QUERIES, RULES, facts); fail('Compiled N3 derivation failed', { 'input evidence metadata is present and matches compiled source': meta.SourceSHA256 === "250631cb3de8addff7037b54789b63b2ea034eb6b579c61a9feca542efd584c1", 'input evidence facts were loaded': expectedFacts > 0 ? facts.length === expectedFacts : facts.length >= 0, 'compiled rules were loaded': RULES.length === 19, 'compiled query directives were loaded': QUERIES.length === 1, 'a derivation fixpoint was reached': result.graph.facts.length >= facts.length, 'query or output facts were produced': rawOutput.length > 0 }); return { ...result, rawOutput, inputFacts: facts }; }
|
|
3055
3070
|
function snapshotMarkdown(markdown) { return markdown.split(/\n/).map((line) => line ? line + ' \n' : '\n').join(''); }
|
|
@@ -3083,6 +3098,16 @@ function formalOutputFacts(graph, queries, rules, initialFacts) {
|
|
|
3083
3098
|
}
|
|
3084
3099
|
return out;
|
|
3085
3100
|
}
|
|
3101
|
+
function termHasTripleTerm(term) {
|
|
3102
|
+
if (!term) return false;
|
|
3103
|
+
if (term.kind === 'triple') return true;
|
|
3104
|
+
if (term.kind === 'list') return term.items.some(termHasTripleTerm);
|
|
3105
|
+
if (term.kind === 'formula') return term.atoms.some(atomHasTripleTerm);
|
|
3106
|
+
return false;
|
|
3107
|
+
}
|
|
3108
|
+
function atomHasTripleTerm(atom) { return termHasTripleTerm(atom.s) || termHasTripleTerm(atom.p) || termHasTripleTerm(atom.o); }
|
|
3109
|
+
function factsHaveTripleTerms(facts) { return (facts || []).some(atomHasTripleTerm); }
|
|
3110
|
+
function trigHasVersion12(trig) { return /^s*(?:@version|VERSION)s+["']1.2["']/mi.test(String(trig || '')); }
|
|
3086
3111
|
function trigGraphBlock(label, atoms) {
|
|
3087
3112
|
const lines = [label + ' {'];
|
|
3088
3113
|
for (const atom of atoms || []) lines.push(' ' + atomToN3(atom) + ' .');
|
|
@@ -3130,7 +3155,8 @@ function formalOutputToTrig(facts, trig) {
|
|
|
3130
3155
|
const prefixes = prefixLinesFromTrig(trig);
|
|
3131
3156
|
if (state.needOutPrefix && !prefixes.some((line) => line.toLowerCase().startsWith('@prefix out:'))) prefixes.push('@prefix out: <https://example.org/see/output#> .');
|
|
3132
3157
|
const nl = String.fromCharCode(10);
|
|
3133
|
-
|
|
3158
|
+
const version = factsHaveTripleTerms(facts) ? 'VERSION "1.2"' + nl + nl : '';
|
|
3159
|
+
return version + prefixes.join(nl) + nl + nl + body.join(nl);
|
|
3134
3160
|
}
|
|
3135
3161
|
function appendFormalTrigOutput(markdown, graph, queries, rules, initialFacts, data) {
|
|
3136
3162
|
const trig = formalOutputToTrig(formalOutputFacts(graph, queries, rules, initialFacts), data && data.trig);
|