fitch-js 0.1.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/LICENSE +7 -0
- package/README.md +401 -0
- package/dist/cjs/index.js +131 -0
- package/dist/cjs/parser.js +211 -0
- package/dist/cjs/proof/index.js +31 -0
- package/dist/cjs/proof/textParser.js +222 -0
- package/dist/cjs/proof/types.js +117 -0
- package/dist/cjs/proofChecker.js +163 -0
- package/dist/cjs/ruleValidators.js +811 -0
- package/dist/cjs/symbols.js +85 -0
- package/dist/cjs/types.js +184 -0
- package/dist/esm/index.js +17 -0
- package/dist/esm/parser.js +204 -0
- package/dist/esm/proof/index.js +7 -0
- package/dist/esm/proof/textParser.js +217 -0
- package/dist/esm/proof/types.js +111 -0
- package/dist/esm/proofChecker.js +128 -0
- package/dist/esm/ruleValidators.js +799 -0
- package/dist/esm/symbols.js +77 -0
- package/dist/esm/types.js +178 -0
- package/package.json +45 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
2
|
+
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
3
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
4
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
5
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
6
|
+
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
7
|
+
/**
|
|
8
|
+
* Text-format proof parser for Fitch-style natural deduction proofs
|
|
9
|
+
* @module proof/textParser
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { parseFormula } from '../parser.js';
|
|
13
|
+
import { convertAsciiToSymbols } from '../symbols.js';
|
|
14
|
+
import { RULE_MAP } from './types.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {import('./types.js').ProofLine} ProofLine
|
|
18
|
+
* @typedef {import('./types.js').ParsedProof} ParsedProof
|
|
19
|
+
* @typedef {import('./types.js').SubproofCitation} SubproofCitation
|
|
20
|
+
* @typedef {import('../types.js').Formula} Formula
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parses a text-format Fitch proof into a structured representation
|
|
25
|
+
*
|
|
26
|
+
* @param {string} text - The proof text to parse
|
|
27
|
+
* @returns {ParsedProof} The parsed proof structure
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const proof = parseProofText(`
|
|
31
|
+
* P → Q :PR
|
|
32
|
+
* P :PR
|
|
33
|
+
* Q :→E 1 2
|
|
34
|
+
* // Goal: Q
|
|
35
|
+
* `);
|
|
36
|
+
*
|
|
37
|
+
* @description
|
|
38
|
+
* Text format specification:
|
|
39
|
+
* - Each line: FORMULA :RULE citations
|
|
40
|
+
* - Subproofs indicated by leading pipes: | or ||
|
|
41
|
+
* - Comments: // text
|
|
42
|
+
* - Goal: // Goal: FORMULA
|
|
43
|
+
* - Line citations: space-separated numbers (e.g., 1 2)
|
|
44
|
+
* - Subproof citations: hyphenated ranges (e.g., 3-5)
|
|
45
|
+
*/
|
|
46
|
+
export function parseProofText(text) {
|
|
47
|
+
/** @type {ProofLine[]} */
|
|
48
|
+
var lines = [];
|
|
49
|
+
var rawLines = text.split('\n');
|
|
50
|
+
var lineNum = 0;
|
|
51
|
+
/** @type {Formula|null} */
|
|
52
|
+
var goal = null;
|
|
53
|
+
/** @type {string|null} */
|
|
54
|
+
var goalText = null;
|
|
55
|
+
/** @type {number[]} */
|
|
56
|
+
var subproofStack = [];
|
|
57
|
+
for (var i = 0; i < rawLines.length; i++) {
|
|
58
|
+
var raw = rawLines[i];
|
|
59
|
+
var trimmed = raw.trim();
|
|
60
|
+
|
|
61
|
+
// Skip empty lines
|
|
62
|
+
if (!trimmed) continue;
|
|
63
|
+
|
|
64
|
+
// Handle comments
|
|
65
|
+
if (trimmed.startsWith('//')) {
|
|
66
|
+
// Check for goal declaration
|
|
67
|
+
var goalMatch = trimmed.match(/\/\/\s*Goal:\s*(.+)/i);
|
|
68
|
+
if (goalMatch) {
|
|
69
|
+
goalText = goalMatch[1].trim();
|
|
70
|
+
try {
|
|
71
|
+
goal = parseFormula(convertAsciiToSymbols(goalText));
|
|
72
|
+
} catch (_unused) {
|
|
73
|
+
// Invalid goal formula - will be caught during validation
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
lineNum++;
|
|
79
|
+
|
|
80
|
+
// Calculate depth from leading pipes
|
|
81
|
+
var depthMatch = raw.match(/^(\s*\|*)/);
|
|
82
|
+
var depth = depthMatch ? (depthMatch[1].match(/\|/g) || []).length : 0;
|
|
83
|
+
|
|
84
|
+
// Update subproof stack based on depth changes
|
|
85
|
+
while (subproofStack.length > depth) {
|
|
86
|
+
subproofStack.pop();
|
|
87
|
+
}
|
|
88
|
+
if (depth > subproofStack.length) {
|
|
89
|
+
subproofStack.push(lineNum);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Parse the line content (strip leading pipes and whitespace)
|
|
93
|
+
var content = trimmed.replace(/^\|+\s*/, '');
|
|
94
|
+
var colonIndex = content.lastIndexOf(':');
|
|
95
|
+
var formulaText = '';
|
|
96
|
+
var ruleText = '';
|
|
97
|
+
/** @type {number[]} */
|
|
98
|
+
var citations = [];
|
|
99
|
+
/** @type {SubproofCitation[]} */
|
|
100
|
+
var subproofCitations = [];
|
|
101
|
+
if (colonIndex !== -1) {
|
|
102
|
+
formulaText = content.substring(0, colonIndex).trim();
|
|
103
|
+
var rulePart = content.substring(colonIndex + 1).trim();
|
|
104
|
+
var ruleParts = rulePart.split(/\s+/);
|
|
105
|
+
ruleText = ruleParts[0] || '';
|
|
106
|
+
|
|
107
|
+
// Parse citations
|
|
108
|
+
for (var j = 1; j < ruleParts.length; j++) {
|
|
109
|
+
var cit = ruleParts[j];
|
|
110
|
+
if (cit.includes('-')) {
|
|
111
|
+
var _cit$split$map = cit.split('-').map(Number),
|
|
112
|
+
_cit$split$map2 = _slicedToArray(_cit$split$map, 2),
|
|
113
|
+
start = _cit$split$map2[0],
|
|
114
|
+
end = _cit$split$map2[1];
|
|
115
|
+
if (!isNaN(start) && !isNaN(end)) {
|
|
116
|
+
subproofCitations.push({
|
|
117
|
+
start: start,
|
|
118
|
+
end: end
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
var num = parseInt(cit, 10);
|
|
123
|
+
if (!isNaN(num)) {
|
|
124
|
+
citations.push(num);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
formulaText = content;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Parse formula
|
|
133
|
+
/** @type {Formula|null} */
|
|
134
|
+
var formula = null;
|
|
135
|
+
/** @type {string[]} */
|
|
136
|
+
var issues = [];
|
|
137
|
+
try {
|
|
138
|
+
if (formulaText) {
|
|
139
|
+
formula = parseFormula(convertAsciiToSymbols(formulaText));
|
|
140
|
+
}
|
|
141
|
+
} catch (e) {
|
|
142
|
+
issues.push("Parse error: ".concat(e instanceof Error ? e.message : 'Invalid formula'));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Normalize rule - try both the raw rule and the converted version
|
|
146
|
+
var convertedRule = convertAsciiToSymbols(ruleText);
|
|
147
|
+
var normalizedRule = RULE_MAP[convertedRule] || RULE_MAP[ruleText] || ruleText;
|
|
148
|
+
|
|
149
|
+
// Validate that rule is known
|
|
150
|
+
if (ruleText && !RULE_MAP[convertedRule] && !RULE_MAP[ruleText]) {
|
|
151
|
+
issues.push("Unknown rule: ".concat(ruleText));
|
|
152
|
+
}
|
|
153
|
+
lines.push({
|
|
154
|
+
lineNum: lineNum,
|
|
155
|
+
rawText: raw,
|
|
156
|
+
formula: formula,
|
|
157
|
+
rule: normalizedRule,
|
|
158
|
+
citations: citations,
|
|
159
|
+
subproofCitations: subproofCitations,
|
|
160
|
+
depth: depth,
|
|
161
|
+
location: [].concat(subproofStack),
|
|
162
|
+
issues: issues
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
lines: lines,
|
|
167
|
+
goal: goal,
|
|
168
|
+
goalText: goalText
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Finds the proof line corresponding to a document position
|
|
174
|
+
*
|
|
175
|
+
* @param {ParsedProof} parsed - The parsed proof
|
|
176
|
+
* @param {string} text - The original document text
|
|
177
|
+
* @param {number} line - The 0-indexed document line number
|
|
178
|
+
* @returns {ProofLine|null} The proof line at that position, or null
|
|
179
|
+
*/
|
|
180
|
+
export function getLineForPosition(parsed, text, line) {
|
|
181
|
+
var textLines = text.split('\n');
|
|
182
|
+
var proofLineCount = 0;
|
|
183
|
+
for (var i = 0; i < textLines.length && i <= line; i++) {
|
|
184
|
+
var trimmed = textLines[i].trim();
|
|
185
|
+
if (trimmed && !trimmed.startsWith('//')) {
|
|
186
|
+
proofLineCount++;
|
|
187
|
+
if (i === line) {
|
|
188
|
+
return parsed.lines.find(function (l) {
|
|
189
|
+
return l.lineNum === proofLineCount;
|
|
190
|
+
}) || null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Finds the document line number for a given proof line number
|
|
199
|
+
*
|
|
200
|
+
* @param {string} text - The original document text
|
|
201
|
+
* @param {number} proofLineNum - The 1-indexed proof line number
|
|
202
|
+
* @returns {number} The 0-indexed document line number
|
|
203
|
+
*/
|
|
204
|
+
export function getDocumentLineForProofLine(text, proofLineNum) {
|
|
205
|
+
var textLines = text.split('\n');
|
|
206
|
+
var proofLineCount = 0;
|
|
207
|
+
for (var i = 0; i < textLines.length; i++) {
|
|
208
|
+
var trimmed = textLines[i].trim();
|
|
209
|
+
if (trimmed && !trimmed.startsWith('//')) {
|
|
210
|
+
proofLineCount++;
|
|
211
|
+
if (proofLineCount === proofLineNum) {
|
|
212
|
+
return i;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for proof text parsing
|
|
3
|
+
* @module proof/types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('../types.js').Formula} Formula
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A parsed line from a text-format proof
|
|
12
|
+
* @typedef {Object} ProofLine
|
|
13
|
+
* @property {number} lineNum - The proof line number (1-indexed, excludes comments)
|
|
14
|
+
* @property {string} rawText - The original text of the line
|
|
15
|
+
* @property {Formula|null} formula - The parsed formula, or null if parsing failed
|
|
16
|
+
* @property {string} rule - The normalized rule name (e.g., '∧I', 'Pr')
|
|
17
|
+
* @property {number[]} citations - Line numbers cited in justification
|
|
18
|
+
* @property {SubproofCitation[]} subproofCitations - Subproof ranges cited
|
|
19
|
+
* @property {number} depth - Subproof nesting depth (0 = main proof)
|
|
20
|
+
* @property {number[]} location - Path through subproof tree
|
|
21
|
+
* @property {string[]} issues - Parse errors for this line
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A subproof citation range
|
|
26
|
+
* @typedef {Object} SubproofCitation
|
|
27
|
+
* @property {number} start - Starting line number
|
|
28
|
+
* @property {number} end - Ending line number
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Result of parsing a proof text file
|
|
33
|
+
* @typedef {Object} ParsedProof
|
|
34
|
+
* @property {ProofLine[]} lines - All parsed proof lines
|
|
35
|
+
* @property {Formula|null} goal - The goal formula (from // Goal: comment)
|
|
36
|
+
* @property {string|null} goalText - The raw goal text
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Rule normalization map - maps various ASCII/Unicode rule names to canonical form
|
|
41
|
+
* @type {Record<string, string>}
|
|
42
|
+
*/
|
|
43
|
+
export var RULE_MAP = {
|
|
44
|
+
// Structural rules
|
|
45
|
+
'PR': 'Pr',
|
|
46
|
+
'Pr': 'Pr',
|
|
47
|
+
'Hyp': 'Hyp',
|
|
48
|
+
'AS': 'Hyp',
|
|
49
|
+
'R': 'R',
|
|
50
|
+
// Conjunction
|
|
51
|
+
'∧I': '∧I',
|
|
52
|
+
'&I': '∧I',
|
|
53
|
+
'/\\I': '∧I',
|
|
54
|
+
'^I': '∧I',
|
|
55
|
+
'∧E': '∧E',
|
|
56
|
+
'&E': '∧E',
|
|
57
|
+
'/\\E': '∧E',
|
|
58
|
+
'^E': '∧E',
|
|
59
|
+
// Disjunction
|
|
60
|
+
'∨I': '∨I',
|
|
61
|
+
'|I': '∨I',
|
|
62
|
+
'\\/I': '∨I',
|
|
63
|
+
'∨E': '∨E',
|
|
64
|
+
'|E': '∨E',
|
|
65
|
+
'\\/E': '∨E',
|
|
66
|
+
// Implication
|
|
67
|
+
'→I': '→I',
|
|
68
|
+
'->I': '→I',
|
|
69
|
+
'→E': '→E',
|
|
70
|
+
'->E': '→E',
|
|
71
|
+
'MP': '→E',
|
|
72
|
+
// Biconditional
|
|
73
|
+
'↔I': '↔I',
|
|
74
|
+
'<->I': '↔I',
|
|
75
|
+
'↔E': '↔E',
|
|
76
|
+
'<->E': '↔E',
|
|
77
|
+
// Negation
|
|
78
|
+
'¬I': '¬I',
|
|
79
|
+
'~I': '¬I',
|
|
80
|
+
'!I': '¬I',
|
|
81
|
+
'¬E': '¬E',
|
|
82
|
+
'~E': '¬E',
|
|
83
|
+
'!E': '¬E',
|
|
84
|
+
'DNE': '¬E',
|
|
85
|
+
// Contradiction
|
|
86
|
+
'⊥I': '⊥I',
|
|
87
|
+
'#I': '⊥I',
|
|
88
|
+
'_|_I': '⊥I',
|
|
89
|
+
'⊥E': '⊥E',
|
|
90
|
+
'#E': '⊥E',
|
|
91
|
+
'_|_E': '⊥E',
|
|
92
|
+
'X': '⊥E',
|
|
93
|
+
// Quantifiers
|
|
94
|
+
'∀I': '∀I',
|
|
95
|
+
'@I': '∀I',
|
|
96
|
+
'∀E': '∀E',
|
|
97
|
+
'@E': '∀E',
|
|
98
|
+
'∃I': '∃I',
|
|
99
|
+
'$I': '∃I',
|
|
100
|
+
'∃E': '∃E',
|
|
101
|
+
'$E': '∃E',
|
|
102
|
+
// Identity
|
|
103
|
+
'=I': '=I',
|
|
104
|
+
'=E': '=E',
|
|
105
|
+
// Meta rules
|
|
106
|
+
'LEM': 'LEM',
|
|
107
|
+
'CQ': 'CQ'
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Export empty object to make this a module (types are in JSDoc)
|
|
111
|
+
export default {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
|
|
2
|
+
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
3
|
+
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
|
|
4
|
+
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
|
|
5
|
+
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
|
6
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
7
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
8
|
+
import { RULES, RULE_REQUIREMENTS, getAllTerms } from './types.js';
|
|
9
|
+
import { RULE_VALIDATORS, formulasEqual, occursFree, substitute, isLineAvailable, isValidCitation } from './ruleValidators.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Gets all available terms from premises and hypotheses at the current scope
|
|
13
|
+
* @param {import('./types').ProofLine[]} previousLines
|
|
14
|
+
* @param {number[]} currentLocation
|
|
15
|
+
* @returns {Set<string>}
|
|
16
|
+
*/
|
|
17
|
+
export function getAvailableTerms(previousLines, currentLocation) {
|
|
18
|
+
var terms = new Set();
|
|
19
|
+
var _iterator = _createForOfIteratorHelper(previousLines),
|
|
20
|
+
_step;
|
|
21
|
+
try {
|
|
22
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
23
|
+
var line = _step.value;
|
|
24
|
+
if ((line.rule === RULES.PREMISE || line.rule === RULES.HYPOTHESIS) && isLineAvailable(currentLocation, line.location)) {
|
|
25
|
+
getAllTerms(line.formula).forEach(function (term) {
|
|
26
|
+
return terms.add(term);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch (err) {
|
|
31
|
+
_iterator.e(err);
|
|
32
|
+
} finally {
|
|
33
|
+
_iterator.f();
|
|
34
|
+
}
|
|
35
|
+
return terms;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validates a single proof line
|
|
40
|
+
* @param {import('./types').ProofLine} line
|
|
41
|
+
* @param {import('./types').ProofLine[]} previousLines
|
|
42
|
+
* @param {number} lineIndex
|
|
43
|
+
* @param {number} numPremises
|
|
44
|
+
* @returns {string[]} Array of issues found
|
|
45
|
+
*/
|
|
46
|
+
function validateLine(line, previousLines, lineIndex, numPremises) {
|
|
47
|
+
var issues = [];
|
|
48
|
+
|
|
49
|
+
// Validate premises
|
|
50
|
+
if (lineIndex < numPremises) {
|
|
51
|
+
if (line.rule !== RULES.PREMISE) {
|
|
52
|
+
issues.push('Line must be marked as a premise');
|
|
53
|
+
}
|
|
54
|
+
return issues;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check rule requirements exist
|
|
58
|
+
var requirements = RULE_REQUIREMENTS[line.rule];
|
|
59
|
+
if (!requirements) {
|
|
60
|
+
issues.push("Unknown rule: ".concat(line.rule));
|
|
61
|
+
return issues;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Validate citation counts
|
|
65
|
+
if (line.citations.length !== requirements.lines) {
|
|
66
|
+
issues.push("Rule ".concat(line.rule, " requires exactly ").concat(requirements.lines, " line citations"));
|
|
67
|
+
}
|
|
68
|
+
if (line.subproofs.length !== requirements.subproofs) {
|
|
69
|
+
issues.push("Rule ".concat(line.rule, " requires exactly ").concat(requirements.subproofs, " subproof citations"));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Skip structural rules that don't need validation
|
|
73
|
+
if (line.rule === RULES.PREMISE || line.rule === RULES.HYPOTHESIS) {
|
|
74
|
+
return issues;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Get the appropriate validator and execute it
|
|
78
|
+
var validator = RULE_VALIDATORS[line.rule];
|
|
79
|
+
if (validator) {
|
|
80
|
+
validator(line, previousLines, lineIndex, issues);
|
|
81
|
+
}
|
|
82
|
+
return issues;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Validates an entire proof
|
|
87
|
+
* @param {import('./types').Proof} proof
|
|
88
|
+
* @returns {import('./types').ValidationResult}
|
|
89
|
+
*/
|
|
90
|
+
export function checkProof(proof) {
|
|
91
|
+
var result = {
|
|
92
|
+
isValid: true,
|
|
93
|
+
issues: [],
|
|
94
|
+
conclusionReached: false
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Validate each line
|
|
98
|
+
var _loop = function _loop(i) {
|
|
99
|
+
var line = proof.lines[i];
|
|
100
|
+
var previousLines = proof.lines.slice(0, i);
|
|
101
|
+
var lineIssues = validateLine(line, previousLines, i, proof.numPremises);
|
|
102
|
+
if (lineIssues.length > 0) {
|
|
103
|
+
var _result$issues;
|
|
104
|
+
result.isValid = false;
|
|
105
|
+
(_result$issues = result.issues).push.apply(_result$issues, _toConsumableArray(lineIssues.map(function (issue) {
|
|
106
|
+
return "Line ".concat(i + 1, ": ").concat(issue);
|
|
107
|
+
})));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check if conclusion is reached
|
|
111
|
+
if (formulasEqual(line.formula, proof.conclusion)) {
|
|
112
|
+
result.conclusionReached = true;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
for (var i = 0; i < proof.lines.length; i++) {
|
|
116
|
+
_loop(i);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Verify conclusion was reached
|
|
120
|
+
if (!result.conclusionReached) {
|
|
121
|
+
result.issues.push('Conclusion not reached in proof');
|
|
122
|
+
result.isValid = false;
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Re-export utilities for external use
|
|
128
|
+
export { formulasEqual, occursFree, substitute, isLineAvailable, isValidCitation };
|