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,799 @@
|
|
|
1
|
+
var _RULE_VALIDATORS;
|
|
2
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
3
|
+
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; } } }; }
|
|
4
|
+
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
5
|
+
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."); }
|
|
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
|
+
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; } }
|
|
9
|
+
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
10
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
11
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
12
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
13
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
14
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
15
|
+
import { RULES, getAllTerms } from './types.js';
|
|
16
|
+
import { OPERATORS } from './parser.js';
|
|
17
|
+
|
|
18
|
+
// Standard variable names used in first-order logic quantifiers
|
|
19
|
+
var STANDARD_VARIABLES = new Set(['x', 'y', 'z', 'w', 'v', 'u']);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Determines if a term is a variable (as opposed to a constant)
|
|
23
|
+
* @param {string} term
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
export function isVariable(term) {
|
|
27
|
+
if (term.length === 1) {
|
|
28
|
+
return STANDARD_VARIABLES.has(term);
|
|
29
|
+
}
|
|
30
|
+
if (term.length >= 2 && STANDARD_VARIABLES.has(term[0]) && /^\d+$/.test(term.slice(1))) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Checks if two formulas are structurally identical
|
|
38
|
+
* @param {import('./types').Formula} f1
|
|
39
|
+
* @param {import('./types').Formula} f2
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
export function formulasEqual(f1, f2) {
|
|
43
|
+
if (!f1 || !f2) return f1 === f2;
|
|
44
|
+
if (f1.type !== f2.type) return false;
|
|
45
|
+
switch (f1.type) {
|
|
46
|
+
case 'atomic':
|
|
47
|
+
if (f1.predicate !== f2.predicate) return false;
|
|
48
|
+
if (!f1.terms || !f2.terms) return !f1.terms && !f2.terms;
|
|
49
|
+
if (f1.terms.length !== f2.terms.length) return false;
|
|
50
|
+
return f1.terms.every(function (term, i) {
|
|
51
|
+
return term === f2.terms[i];
|
|
52
|
+
});
|
|
53
|
+
case 'compound':
|
|
54
|
+
if (f1.operator !== f2.operator) return false;
|
|
55
|
+
if (f1.operator === OPERATORS.NOT) {
|
|
56
|
+
return formulasEqual(f1.right, f2.right);
|
|
57
|
+
}
|
|
58
|
+
return formulasEqual(f1.left, f2.left) && formulasEqual(f1.right, f2.right);
|
|
59
|
+
case 'quantified':
|
|
60
|
+
return f1.operator === f2.operator && f1.variable === f2.variable && formulasEqual(f1.scope, f2.scope);
|
|
61
|
+
case 'identity':
|
|
62
|
+
return f1.terms[0] === f2.terms[0] && f1.terms[1] === f2.terms[1];
|
|
63
|
+
case 'splat':
|
|
64
|
+
return true;
|
|
65
|
+
default:
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Checks if a variable occurs free in a formula
|
|
72
|
+
* @param {string} variable
|
|
73
|
+
* @param {import('./types').Formula} formula
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
76
|
+
export function occursFree(variable, formula) {
|
|
77
|
+
var _formula$terms$includ, _formula$terms;
|
|
78
|
+
if (!variable || !formula) return false;
|
|
79
|
+
switch (formula.type) {
|
|
80
|
+
case 'atomic':
|
|
81
|
+
return (_formula$terms$includ = (_formula$terms = formula.terms) === null || _formula$terms === void 0 ? void 0 : _formula$terms.includes(variable)) !== null && _formula$terms$includ !== void 0 ? _formula$terms$includ : false;
|
|
82
|
+
case 'compound':
|
|
83
|
+
if (formula.operator === OPERATORS.NOT) {
|
|
84
|
+
return occursFree(variable, formula.right);
|
|
85
|
+
}
|
|
86
|
+
return occursFree(variable, formula.left) || occursFree(variable, formula.right);
|
|
87
|
+
case 'quantified':
|
|
88
|
+
if (formula.variable === variable) return false;
|
|
89
|
+
return occursFree(variable, formula.scope);
|
|
90
|
+
case 'identity':
|
|
91
|
+
return formula.terms.includes(variable);
|
|
92
|
+
default:
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Substitutes a term for a variable in a formula
|
|
99
|
+
* @param {import('./types').Formula} formula
|
|
100
|
+
* @param {string} term
|
|
101
|
+
* @param {string} variable
|
|
102
|
+
* @returns {import('./types').Formula}
|
|
103
|
+
*/
|
|
104
|
+
export function substitute(formula, term, variable) {
|
|
105
|
+
var _formula$terms$map, _formula$terms2;
|
|
106
|
+
if (!formula) return formula;
|
|
107
|
+
switch (formula.type) {
|
|
108
|
+
case 'atomic':
|
|
109
|
+
return _objectSpread(_objectSpread({}, formula), {}, {
|
|
110
|
+
terms: (_formula$terms$map = (_formula$terms2 = formula.terms) === null || _formula$terms2 === void 0 ? void 0 : _formula$terms2.map(function (t) {
|
|
111
|
+
return t === variable ? term : t;
|
|
112
|
+
})) !== null && _formula$terms$map !== void 0 ? _formula$terms$map : []
|
|
113
|
+
});
|
|
114
|
+
case 'compound':
|
|
115
|
+
if (formula.operator === OPERATORS.NOT) {
|
|
116
|
+
return _objectSpread(_objectSpread({}, formula), {}, {
|
|
117
|
+
right: substitute(formula.right, term, variable)
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return _objectSpread(_objectSpread({}, formula), {}, {
|
|
121
|
+
left: substitute(formula.left, term, variable),
|
|
122
|
+
right: substitute(formula.right, term, variable)
|
|
123
|
+
});
|
|
124
|
+
case 'quantified':
|
|
125
|
+
if (formula.variable === variable) return formula;
|
|
126
|
+
return _objectSpread(_objectSpread({}, formula), {}, {
|
|
127
|
+
scope: substitute(formula.scope, term, variable)
|
|
128
|
+
});
|
|
129
|
+
case 'identity':
|
|
130
|
+
return _objectSpread(_objectSpread({}, formula), {}, {
|
|
131
|
+
terms: formula.terms.map(function (t) {
|
|
132
|
+
return t === variable ? term : t;
|
|
133
|
+
})
|
|
134
|
+
});
|
|
135
|
+
default:
|
|
136
|
+
return formula;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Checks if a line is available at the current scope
|
|
142
|
+
* @param {number[]} currentLocation
|
|
143
|
+
* @param {number[]} targetLocation
|
|
144
|
+
* @returns {boolean}
|
|
145
|
+
*/
|
|
146
|
+
export function isLineAvailable(currentLocation, targetLocation) {
|
|
147
|
+
if (!currentLocation || !targetLocation) return true;
|
|
148
|
+
if (targetLocation.length > currentLocation.length) return false;
|
|
149
|
+
for (var i = 0; i < targetLocation.length - 1; i++) {
|
|
150
|
+
if (targetLocation[i] !== currentLocation[i]) return false;
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Checks if a citation is valid within current scope
|
|
157
|
+
* @param {import('./types').ProofLine} citedLine
|
|
158
|
+
* @param {import('./types').ProofLine} currentLine
|
|
159
|
+
* @returns {boolean}
|
|
160
|
+
*/
|
|
161
|
+
export function isValidCitation(citedLine, currentLine) {
|
|
162
|
+
return isLineAvailable(currentLine.location, citedLine.location);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Validates a subproof range
|
|
167
|
+
* @param {{start: number, end: number}} subproof
|
|
168
|
+
* @param {number} lineIndex
|
|
169
|
+
* @param {number} totalLines
|
|
170
|
+
* @returns {{valid: boolean, startIdx: number, endIdx: number}}
|
|
171
|
+
*/
|
|
172
|
+
export function validateSubproofRange(subproof, lineIndex, totalLines) {
|
|
173
|
+
var startIdx = subproof.start - 1;
|
|
174
|
+
var endIdx = subproof.end - 1;
|
|
175
|
+
var valid = startIdx <= endIdx && startIdx >= 0 && endIdx < totalLines && endIdx < lineIndex;
|
|
176
|
+
return {
|
|
177
|
+
valid: valid,
|
|
178
|
+
startIdx: startIdx,
|
|
179
|
+
endIdx: endIdx
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// PROPOSITIONAL LOGIC RULE VALIDATORS
|
|
185
|
+
// ============================================================================
|
|
186
|
+
|
|
187
|
+
function validateConjIntro(line, previousLines, lineIndex, issues) {
|
|
188
|
+
if (line.citations.length !== 2) return;
|
|
189
|
+
var _line$citations = _slicedToArray(line.citations, 2),
|
|
190
|
+
cite1 = _line$citations[0],
|
|
191
|
+
cite2 = _line$citations[1];
|
|
192
|
+
var idx1 = cite1 - 1;
|
|
193
|
+
var idx2 = cite2 - 1;
|
|
194
|
+
if (idx1 < 0 || idx1 >= previousLines.length || idx2 < 0 || idx2 >= previousLines.length) {
|
|
195
|
+
issues.push('Invalid line citations');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
var formula1 = previousLines[idx1].formula;
|
|
199
|
+
var formula2 = previousLines[idx2].formula;
|
|
200
|
+
if (line.formula.type !== 'compound' || line.formula.operator !== OPERATORS.AND) {
|
|
201
|
+
issues.push('Conclusion must be a conjunction');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
var matchesOrder1 = formulasEqual(line.formula.left, formula1) && formulasEqual(line.formula.right, formula2);
|
|
205
|
+
var matchesOrder2 = formulasEqual(line.formula.left, formula2) && formulasEqual(line.formula.right, formula1);
|
|
206
|
+
if (!matchesOrder1 && !matchesOrder2) {
|
|
207
|
+
issues.push('Cited formulas must match the left and right sides of the conjunction');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function validateConjElim(line, previousLines, lineIndex, issues) {
|
|
211
|
+
if (line.citations.length !== 1) return;
|
|
212
|
+
var citeIdx = line.citations[0] - 1;
|
|
213
|
+
if (citeIdx < 0 || citeIdx >= previousLines.length) {
|
|
214
|
+
issues.push('Invalid line citation');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
var formula = previousLines[citeIdx].formula;
|
|
218
|
+
if (formula.type !== 'compound' || formula.operator !== OPERATORS.AND) {
|
|
219
|
+
issues.push('Cited formula must be a conjunction');
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (!formulasEqual(line.formula, formula.left) && !formulasEqual(line.formula, formula.right)) {
|
|
223
|
+
issues.push('Conclusion must match either the left or right side of the conjunction');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function validateDisjIntro(line, previousLines, lineIndex, issues) {
|
|
227
|
+
if (line.citations.length !== 1) return;
|
|
228
|
+
var citeIdx = line.citations[0] - 1;
|
|
229
|
+
if (citeIdx < 0 || citeIdx >= previousLines.length) {
|
|
230
|
+
issues.push('Invalid line citation');
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
var formula = previousLines[citeIdx].formula;
|
|
234
|
+
if (line.formula.type !== 'compound' || line.formula.operator !== OPERATORS.OR) {
|
|
235
|
+
issues.push('Conclusion must be a disjunction');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (!formulasEqual(formula, line.formula.left) && !formulasEqual(formula, line.formula.right)) {
|
|
239
|
+
issues.push('Cited formula must match either the left or right side of the disjunction');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function validateDisjElim(line, previousLines, lineIndex, issues) {
|
|
243
|
+
if (line.citations.length !== 1) {
|
|
244
|
+
issues.push('Disjunction elimination requires exactly one citation');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
var citeIdx = line.citations[0] - 1;
|
|
248
|
+
if (citeIdx < 0 || citeIdx >= previousLines.length) {
|
|
249
|
+
issues.push('Invalid line citation');
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
var disjFormula = previousLines[citeIdx].formula;
|
|
253
|
+
if (disjFormula.type !== 'compound' || disjFormula.operator !== OPERATORS.OR) {
|
|
254
|
+
issues.push('First cited formula must be a disjunction');
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (line.subproofs.length !== 2) {
|
|
258
|
+
issues.push('Disjunction elimination requires exactly two subproofs');
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
var _line$subproofs = _slicedToArray(line.subproofs, 2),
|
|
262
|
+
sp1 = _line$subproofs[0],
|
|
263
|
+
sp2 = _line$subproofs[1];
|
|
264
|
+
var sp1Range = validateSubproofRange(sp1, lineIndex, previousLines.length);
|
|
265
|
+
var sp2Range = validateSubproofRange(sp2, lineIndex, previousLines.length);
|
|
266
|
+
if (!sp1Range.valid || !sp2Range.valid) {
|
|
267
|
+
issues.push('Invalid subproof ranges');
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
var sp1Assumption = previousLines[sp1Range.startIdx].formula;
|
|
271
|
+
var sp2Assumption = previousLines[sp2Range.startIdx].formula;
|
|
272
|
+
var sp1Conclusion = previousLines[sp1Range.endIdx].formula;
|
|
273
|
+
var sp2Conclusion = previousLines[sp2Range.endIdx].formula;
|
|
274
|
+
if (!formulasEqual(sp1Assumption, disjFormula.left)) {
|
|
275
|
+
issues.push('First subproof must start with the left disjunct');
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (!formulasEqual(sp2Assumption, disjFormula.right)) {
|
|
279
|
+
issues.push('Second subproof must start with the right disjunct');
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (!formulasEqual(sp1Conclusion, sp2Conclusion)) {
|
|
283
|
+
issues.push('Both subproofs must reach the same conclusion');
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (!formulasEqual(sp1Conclusion, line.formula)) {
|
|
287
|
+
issues.push('Subproof conclusions must match the final conclusion');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function validateCondIntro(line, previousLines, lineIndex, issues) {
|
|
291
|
+
if (line.citations.length !== 0 || line.subproofs.length !== 1) {
|
|
292
|
+
issues.push('Conditional introduction requires exactly one subproof and no citations');
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
var spRange = validateSubproofRange(line.subproofs[0], lineIndex, previousLines.length);
|
|
296
|
+
if (!spRange.valid) {
|
|
297
|
+
issues.push('Invalid subproof range');
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (line.formula.type !== 'compound' || line.formula.operator !== OPERATORS.IMPLIES) {
|
|
301
|
+
issues.push('Conclusion must be a conditional');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
var assumption = previousLines[spRange.startIdx].formula;
|
|
305
|
+
var conclusion = previousLines[spRange.endIdx].formula;
|
|
306
|
+
if (!formulasEqual(assumption, line.formula.left)) {
|
|
307
|
+
issues.push('Subproof assumption must match antecedent of conditional');
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (!formulasEqual(conclusion, line.formula.right)) {
|
|
311
|
+
issues.push('Subproof conclusion must match consequent of conditional');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function validateCondElim(line, previousLines, lineIndex, issues) {
|
|
315
|
+
if (line.citations.length !== 2) {
|
|
316
|
+
issues.push('Conditional elimination requires exactly two citations');
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
var _line$citations2 = _slicedToArray(line.citations, 2),
|
|
320
|
+
cite1 = _line$citations2[0],
|
|
321
|
+
cite2 = _line$citations2[1];
|
|
322
|
+
var idx1 = cite1 - 1;
|
|
323
|
+
var idx2 = cite2 - 1;
|
|
324
|
+
if (idx1 < 0 || idx1 >= previousLines.length || idx2 < 0 || idx2 >= previousLines.length) {
|
|
325
|
+
issues.push('Invalid line citations');
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
var formula1 = previousLines[idx1].formula;
|
|
329
|
+
var formula2 = previousLines[idx2].formula;
|
|
330
|
+
var valid1 = formula1.type === 'compound' && formula1.operator === OPERATORS.IMPLIES && formulasEqual(formula1.left, formula2) && formulasEqual(formula1.right, line.formula);
|
|
331
|
+
var valid2 = formula2.type === 'compound' && formula2.operator === OPERATORS.IMPLIES && formulasEqual(formula2.left, formula1) && formulasEqual(formula2.right, line.formula);
|
|
332
|
+
if (!valid1 && !valid2) {
|
|
333
|
+
issues.push('One citation must be a conditional and the other must match its antecedent');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function validateBicondIntro(line, previousLines, lineIndex, issues) {
|
|
337
|
+
if (line.citations.length !== 0 || line.subproofs.length !== 2) {
|
|
338
|
+
issues.push('Biconditional introduction requires exactly two subproofs and no citations');
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (line.formula.type !== 'compound' || line.formula.operator !== OPERATORS.IFF) {
|
|
342
|
+
issues.push('Conclusion must be a biconditional');
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
var _line$subproofs2 = _slicedToArray(line.subproofs, 2),
|
|
346
|
+
sp1 = _line$subproofs2[0],
|
|
347
|
+
sp2 = _line$subproofs2[1];
|
|
348
|
+
var sp1Range = validateSubproofRange(sp1, lineIndex, previousLines.length);
|
|
349
|
+
var sp2Range = validateSubproofRange(sp2, lineIndex, previousLines.length);
|
|
350
|
+
if (!sp1Range.valid || !sp2Range.valid) {
|
|
351
|
+
issues.push('Invalid subproof ranges');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
var sp1Assumption = previousLines[sp1Range.startIdx];
|
|
355
|
+
var sp1Conclusion = previousLines[sp1Range.endIdx];
|
|
356
|
+
var sp2Assumption = previousLines[sp2Range.startIdx];
|
|
357
|
+
var sp2Conclusion = previousLines[sp2Range.endIdx];
|
|
358
|
+
if (sp1Assumption.rule !== RULES.HYPOTHESIS || sp2Assumption.rule !== RULES.HYPOTHESIS) {
|
|
359
|
+
issues.push('Subproofs must begin with hypotheses');
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
var validOrder1 = formulasEqual(sp1Assumption.formula, line.formula.left) && formulasEqual(sp1Conclusion.formula, line.formula.right) && formulasEqual(sp2Assumption.formula, line.formula.right) && formulasEqual(sp2Conclusion.formula, line.formula.left);
|
|
363
|
+
var validOrder2 = formulasEqual(sp1Assumption.formula, line.formula.right) && formulasEqual(sp1Conclusion.formula, line.formula.left) && formulasEqual(sp2Assumption.formula, line.formula.left) && formulasEqual(sp2Conclusion.formula, line.formula.right);
|
|
364
|
+
if (!validOrder1 && !validOrder2) {
|
|
365
|
+
issues.push('Invalid biconditional introduction - subproofs must prove P→Q and Q→P');
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function validateBicondElim(line, previousLines, lineIndex, issues) {
|
|
369
|
+
if (line.citations.length !== 2) {
|
|
370
|
+
issues.push('Biconditional elimination requires exactly two citations');
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
var _line$citations3 = _slicedToArray(line.citations, 2),
|
|
374
|
+
cite1 = _line$citations3[0],
|
|
375
|
+
cite2 = _line$citations3[1];
|
|
376
|
+
if (cite1 < 1 || cite1 > lineIndex || cite2 < 1 || cite2 > lineIndex) {
|
|
377
|
+
issues.push('Invalid line citations');
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
var citedLine1 = previousLines[cite1 - 1];
|
|
381
|
+
var citedLine2 = previousLines[cite2 - 1];
|
|
382
|
+
if (!isValidCitation(citedLine1, line) || !isValidCitation(citedLine2, line)) {
|
|
383
|
+
issues.push('Cited lines must be available in the current scope');
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
var valid = false;
|
|
387
|
+
if (citedLine1.formula.type === 'compound' && citedLine1.formula.operator === OPERATORS.IFF) {
|
|
388
|
+
valid = formulasEqual(citedLine1.formula.left, citedLine2.formula) && formulasEqual(citedLine1.formula.right, line.formula) || formulasEqual(citedLine1.formula.right, citedLine2.formula) && formulasEqual(citedLine1.formula.left, line.formula);
|
|
389
|
+
}
|
|
390
|
+
if (!valid && citedLine2.formula.type === 'compound' && citedLine2.formula.operator === OPERATORS.IFF) {
|
|
391
|
+
valid = formulasEqual(citedLine2.formula.left, citedLine1.formula) && formulasEqual(citedLine2.formula.right, line.formula) || formulasEqual(citedLine2.formula.right, citedLine1.formula) && formulasEqual(citedLine2.formula.left, line.formula);
|
|
392
|
+
}
|
|
393
|
+
if (!valid) {
|
|
394
|
+
issues.push('Invalid biconditional elimination - one citation must be a biconditional and the other must match one of its sides');
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function validateNegIntro(line, previousLines, lineIndex, issues) {
|
|
398
|
+
if (line.citations.length !== 0 || line.subproofs.length !== 1) {
|
|
399
|
+
issues.push('Negation introduction requires exactly one subproof and no citations');
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (line.formula.type !== 'compound' || line.formula.operator !== OPERATORS.NOT) {
|
|
403
|
+
issues.push('Conclusion must be a negation');
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
var spRange = validateSubproofRange(line.subproofs[0], lineIndex, previousLines.length);
|
|
407
|
+
if (!spRange.valid) {
|
|
408
|
+
issues.push('Invalid subproof range');
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
var assumption = previousLines[spRange.startIdx];
|
|
412
|
+
var conclusion = previousLines[spRange.endIdx];
|
|
413
|
+
if (!formulasEqual(assumption.formula, line.formula.right)) {
|
|
414
|
+
issues.push('Subproof assumption must match what is being negated');
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (conclusion.formula.type !== 'splat') {
|
|
418
|
+
issues.push('Subproof must end in a contradiction');
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function validateNegElim(line, previousLines, lineIndex, issues) {
|
|
422
|
+
if (line.citations.length !== 2 || line.subproofs.length !== 0) {
|
|
423
|
+
issues.push('Negation elimination requires exactly two citations and no subproofs');
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (line.formula.type !== 'splat') {
|
|
427
|
+
issues.push('Conclusion must be a contradiction');
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
var _line$citations4 = _slicedToArray(line.citations, 2),
|
|
431
|
+
cite1 = _line$citations4[0],
|
|
432
|
+
cite2 = _line$citations4[1];
|
|
433
|
+
if (cite1 < 1 || cite1 > lineIndex || cite2 < 1 || cite2 > lineIndex) {
|
|
434
|
+
issues.push('Invalid line citations');
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
var citedLine1 = previousLines[cite1 - 1];
|
|
438
|
+
var citedLine2 = previousLines[cite2 - 1];
|
|
439
|
+
if (!isValidCitation(citedLine1, line) || !isValidCitation(citedLine2, line)) {
|
|
440
|
+
issues.push('Cited lines must be available in the current scope');
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
var valid = citedLine1.formula.type === 'compound' && citedLine1.formula.operator === OPERATORS.NOT && formulasEqual(citedLine1.formula.right, citedLine2.formula) || citedLine2.formula.type === 'compound' && citedLine2.formula.operator === OPERATORS.NOT && formulasEqual(citedLine2.formula.right, citedLine1.formula);
|
|
444
|
+
if (!valid) {
|
|
445
|
+
issues.push('Invalid negation elimination - one line must be the negation of the other');
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function validateContraIntro(line, previousLines, lineIndex, issues) {
|
|
449
|
+
if (line.citations.length !== 2 || line.subproofs.length !== 0) {
|
|
450
|
+
issues.push('Contradiction introduction requires exactly two citations and no subproofs');
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (line.formula.type !== 'splat') {
|
|
454
|
+
issues.push('Conclusion must be a contradiction');
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
var _line$citations5 = _slicedToArray(line.citations, 2),
|
|
458
|
+
cite1 = _line$citations5[0],
|
|
459
|
+
cite2 = _line$citations5[1];
|
|
460
|
+
if (cite1 < 1 || cite1 > lineIndex || cite2 < 1 || cite2 > lineIndex) {
|
|
461
|
+
issues.push('Invalid line citations');
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
var citedLine1 = previousLines[cite1 - 1];
|
|
465
|
+
var citedLine2 = previousLines[cite2 - 1];
|
|
466
|
+
if (!isValidCitation(citedLine1, line) || !isValidCitation(citedLine2, line)) {
|
|
467
|
+
issues.push('Cited lines must be available in the current scope');
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
var valid = citedLine1.formula.type === 'compound' && citedLine1.formula.operator === OPERATORS.NOT && formulasEqual(citedLine1.formula.right, citedLine2.formula) || citedLine2.formula.type === 'compound' && citedLine2.formula.operator === OPERATORS.NOT && formulasEqual(citedLine2.formula.right, citedLine1.formula);
|
|
471
|
+
if (!valid) {
|
|
472
|
+
issues.push('Invalid contradiction introduction - one line must be the negation of the other');
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function validateContraElim(line, previousLines, lineIndex, issues) {
|
|
476
|
+
if (line.citations.length !== 1 || line.subproofs.length !== 0) {
|
|
477
|
+
issues.push('Contradiction elimination requires exactly one citation and no subproofs');
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
var citeIdx = line.citations[0] - 1;
|
|
481
|
+
if (citeIdx < 0 || citeIdx >= previousLines.length) {
|
|
482
|
+
issues.push('Invalid line citation');
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
var citedLine = previousLines[citeIdx];
|
|
486
|
+
if (!isValidCitation(citedLine, line)) {
|
|
487
|
+
issues.push('Cited line must be available in the current scope');
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (citedLine.formula.type !== 'splat') {
|
|
491
|
+
issues.push('Contradiction elimination requires a contradiction');
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
function validateLEM(line, previousLines, lineIndex, issues) {
|
|
495
|
+
if (line.citations.length !== 0 || line.subproofs.length !== 2) {
|
|
496
|
+
issues.push('Law of excluded middle requires exactly two subproofs and no citations');
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
var _line$subproofs3 = _slicedToArray(line.subproofs, 2),
|
|
500
|
+
sp1 = _line$subproofs3[0],
|
|
501
|
+
sp2 = _line$subproofs3[1];
|
|
502
|
+
var sp1Range = validateSubproofRange(sp1, lineIndex, previousLines.length);
|
|
503
|
+
var sp2Range = validateSubproofRange(sp2, lineIndex, previousLines.length);
|
|
504
|
+
if (!sp1Range.valid || !sp2Range.valid) {
|
|
505
|
+
issues.push('Invalid subproof ranges');
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
var sp1Assumption = previousLines[sp1Range.startIdx];
|
|
509
|
+
var sp1Conclusion = previousLines[sp1Range.endIdx];
|
|
510
|
+
var sp2Assumption = previousLines[sp2Range.startIdx];
|
|
511
|
+
var sp2Conclusion = previousLines[sp2Range.endIdx];
|
|
512
|
+
if (sp1Assumption.rule !== RULES.HYPOTHESIS || sp2Assumption.rule !== RULES.HYPOTHESIS) {
|
|
513
|
+
issues.push('Subproofs must begin with hypotheses');
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
var valid = sp2Assumption.formula.type === 'compound' && sp2Assumption.formula.operator === OPERATORS.NOT && formulasEqual(sp2Assumption.formula.right, sp1Assumption.formula) || sp1Assumption.formula.type === 'compound' && sp1Assumption.formula.operator === OPERATORS.NOT && formulasEqual(sp1Assumption.formula.right, sp2Assumption.formula);
|
|
517
|
+
if (!valid) {
|
|
518
|
+
issues.push('Invalid law of excluded middle - subproofs must assume P and ¬P');
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (!formulasEqual(sp1Conclusion.formula, sp2Conclusion.formula) || !formulasEqual(line.formula, sp1Conclusion.formula)) {
|
|
522
|
+
issues.push('Invalid law of excluded middle - both subproofs must reach the same conclusion');
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ============================================================================
|
|
527
|
+
// FIRST-ORDER LOGIC RULE VALIDATORS
|
|
528
|
+
// ============================================================================
|
|
529
|
+
|
|
530
|
+
function validateUnivElim(line, previousLines, lineIndex, issues) {
|
|
531
|
+
if (line.citations.length !== 1) return;
|
|
532
|
+
var citeIdx = line.citations[0] - 1;
|
|
533
|
+
if (citeIdx < 0 || citeIdx >= previousLines.length) {
|
|
534
|
+
issues.push('Invalid line citation');
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
var cited = previousLines[citeIdx];
|
|
538
|
+
if (cited.formula.type !== 'quantified' || cited.formula.operator !== OPERATORS.FORALL) {
|
|
539
|
+
issues.push('Universal elimination requires a universal formula');
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
var conclusionTerms = getAllTerms(line.formula);
|
|
543
|
+
var foundValidSubstitution = false;
|
|
544
|
+
var _iterator = _createForOfIteratorHelper(conclusionTerms),
|
|
545
|
+
_step;
|
|
546
|
+
try {
|
|
547
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
548
|
+
var term = _step.value;
|
|
549
|
+
var substituted = substitute(cited.formula.scope, term, cited.formula.variable);
|
|
550
|
+
if (formulasEqual(substituted, line.formula)) {
|
|
551
|
+
foundValidSubstitution = true;
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
} catch (err) {
|
|
556
|
+
_iterator.e(err);
|
|
557
|
+
} finally {
|
|
558
|
+
_iterator.f();
|
|
559
|
+
}
|
|
560
|
+
if (!foundValidSubstitution) {
|
|
561
|
+
var withOriginal = substitute(cited.formula.scope, cited.formula.variable, cited.formula.variable);
|
|
562
|
+
foundValidSubstitution = formulasEqual(withOriginal, line.formula);
|
|
563
|
+
}
|
|
564
|
+
if (!foundValidSubstitution) {
|
|
565
|
+
issues.push('Formula does not follow by universal elimination');
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function validateUnivIntro(line, previousLines, lineIndex, issues) {
|
|
569
|
+
if (line.citations.length !== 1) {
|
|
570
|
+
issues.push('Universal introduction requires exactly one citation');
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
var citeIdx = line.citations[0] - 1;
|
|
574
|
+
if (citeIdx < 0 || citeIdx >= previousLines.length) {
|
|
575
|
+
issues.push('Invalid line citation');
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (line.formula.type !== 'quantified' || line.formula.operator !== OPERATORS.FORALL) {
|
|
579
|
+
issues.push('Universal introduction must conclude with a universal formula');
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
var cited = previousLines[citeIdx];
|
|
583
|
+
var variable = line.formula.variable;
|
|
584
|
+
var citedTerms = getAllTerms(cited.formula);
|
|
585
|
+
var foundValidSubstitution = false;
|
|
586
|
+
var _iterator2 = _createForOfIteratorHelper(citedTerms),
|
|
587
|
+
_step2;
|
|
588
|
+
try {
|
|
589
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
590
|
+
var term = _step2.value;
|
|
591
|
+
var substituted = substitute(line.formula.scope, term, variable);
|
|
592
|
+
if (formulasEqual(substituted, cited.formula)) {
|
|
593
|
+
foundValidSubstitution = true;
|
|
594
|
+
if (line.location && !isVariable(term)) {
|
|
595
|
+
for (var j = 0; j < lineIndex; j++) {
|
|
596
|
+
var prevLine = previousLines[j];
|
|
597
|
+
if (prevLine.rule === RULES.PREMISE && getAllTerms(prevLine.formula).includes(term)) {
|
|
598
|
+
issues.push("Eigenvariable condition violated: '".concat(term, "' appears in premise"));
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
} catch (err) {
|
|
607
|
+
_iterator2.e(err);
|
|
608
|
+
} finally {
|
|
609
|
+
_iterator2.f();
|
|
610
|
+
}
|
|
611
|
+
if (!foundValidSubstitution) {
|
|
612
|
+
var withVariable = substitute(line.formula.scope, variable, variable);
|
|
613
|
+
foundValidSubstitution = formulasEqual(withVariable, cited.formula);
|
|
614
|
+
}
|
|
615
|
+
if (!foundValidSubstitution) {
|
|
616
|
+
issues.push('Formula does not follow by universal introduction');
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function validateExistIntro(line, previousLines, lineIndex, issues) {
|
|
620
|
+
if (line.citations.length !== 1) return;
|
|
621
|
+
var citeIdx = line.citations[0] - 1;
|
|
622
|
+
if (citeIdx < 0 || citeIdx >= previousLines.length) {
|
|
623
|
+
issues.push('Invalid line citation');
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
var cited = previousLines[citeIdx];
|
|
627
|
+
if (line.formula.type !== 'quantified' || line.formula.operator !== OPERATORS.EXISTS) {
|
|
628
|
+
issues.push('Existential introduction must introduce an existential formula');
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
var citedTerms = getAllTerms(cited.formula);
|
|
632
|
+
var foundValidSubstitution = false;
|
|
633
|
+
var _iterator3 = _createForOfIteratorHelper(citedTerms),
|
|
634
|
+
_step3;
|
|
635
|
+
try {
|
|
636
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
637
|
+
var term = _step3.value;
|
|
638
|
+
var substituted = substitute(line.formula.scope, term, line.formula.variable);
|
|
639
|
+
if (formulasEqual(substituted, cited.formula)) {
|
|
640
|
+
foundValidSubstitution = true;
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
} catch (err) {
|
|
645
|
+
_iterator3.e(err);
|
|
646
|
+
} finally {
|
|
647
|
+
_iterator3.f();
|
|
648
|
+
}
|
|
649
|
+
if (!foundValidSubstitution) {
|
|
650
|
+
issues.push('Formula does not follow by existential introduction');
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function validateExistElim(line, previousLines, lineIndex, issues) {
|
|
654
|
+
if (line.citations.length !== 1 || line.subproofs.length !== 1) {
|
|
655
|
+
issues.push('Existential elimination requires exactly one citation and one subproof');
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
var citeIdx = line.citations[0] - 1;
|
|
659
|
+
if (citeIdx < 0 || citeIdx >= previousLines.length) {
|
|
660
|
+
issues.push('Invalid line citation');
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
var cited = previousLines[citeIdx];
|
|
664
|
+
var subproof = line.subproofs[0];
|
|
665
|
+
if (cited.formula.type !== 'quantified' || cited.formula.operator !== OPERATORS.EXISTS) {
|
|
666
|
+
issues.push('Existential elimination requires an existential formula');
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
var spRange = validateSubproofRange(subproof, lineIndex, previousLines.length);
|
|
670
|
+
if (!spRange.valid) {
|
|
671
|
+
issues.push('Invalid subproof range');
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
var subproofLines = previousLines.slice(spRange.startIdx, spRange.endIdx + 1);
|
|
675
|
+
if (subproofLines.length < 2) {
|
|
676
|
+
issues.push('Subproof must contain at least an assumption and a conclusion');
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
var assumption = subproofLines[0];
|
|
680
|
+
if (assumption.rule !== RULES.HYPOTHESIS) {
|
|
681
|
+
issues.push('First line of existential elimination subproof must be a hypothesis');
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
var witnessTerms = getAllTerms(assumption.formula);
|
|
685
|
+
var validWitness = false;
|
|
686
|
+
var _iterator4 = _createForOfIteratorHelper(witnessTerms),
|
|
687
|
+
_step4;
|
|
688
|
+
try {
|
|
689
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
690
|
+
var witness = _step4.value;
|
|
691
|
+
if (isVariable(witness)) continue;
|
|
692
|
+
var substituted = substitute(cited.formula.scope, witness, cited.formula.variable);
|
|
693
|
+
if (!formulasEqual(assumption.formula, substituted)) continue;
|
|
694
|
+
if (getAllTerms(line.formula).includes(witness)) continue;
|
|
695
|
+
if (getAllTerms(cited.formula).includes(witness)) continue;
|
|
696
|
+
var foundInPremises = false;
|
|
697
|
+
for (var j = 0; j < spRange.startIdx; j++) {
|
|
698
|
+
var prevLine = previousLines[j];
|
|
699
|
+
if ((prevLine.rule === RULES.PREMISE || prevLine.rule === RULES.HYPOTHESIS) && prevLine.location && line.location && prevLine.location.length <= line.location.length && prevLine.location.every(function (loc, i) {
|
|
700
|
+
return line.location[i] === loc;
|
|
701
|
+
})) {
|
|
702
|
+
if (getAllTerms(prevLine.formula).includes(witness)) {
|
|
703
|
+
foundInPremises = true;
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
if (!foundInPremises) {
|
|
709
|
+
validWitness = true;
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
} catch (err) {
|
|
714
|
+
_iterator4.e(err);
|
|
715
|
+
} finally {
|
|
716
|
+
_iterator4.f();
|
|
717
|
+
}
|
|
718
|
+
if (!validWitness) {
|
|
719
|
+
issues.push('Witness term must not appear in any available premises or hypotheses');
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function validateIdIntro(line, previousLines, lineIndex, issues) {
|
|
723
|
+
if (line.formula.type !== 'identity') {
|
|
724
|
+
issues.push('Identity introduction must conclude with an identity formula');
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
if (line.formula.terms[0] !== line.formula.terms[1]) {
|
|
728
|
+
issues.push('Identity introduction requires t = t (reflexivity)');
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function validateIdElim(line, previousLines, lineIndex, issues) {
|
|
732
|
+
if (line.citations.length !== 2) {
|
|
733
|
+
issues.push('Identity elimination requires exactly two citations');
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
var _line$citations6 = _slicedToArray(line.citations, 2),
|
|
737
|
+
cite1 = _line$citations6[0],
|
|
738
|
+
cite2 = _line$citations6[1];
|
|
739
|
+
if (cite1 < 1 || cite1 > lineIndex || cite2 < 1 || cite2 > lineIndex) {
|
|
740
|
+
issues.push('Invalid line citations');
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
var citedLine1 = previousLines[cite1 - 1];
|
|
744
|
+
var citedLine2 = previousLines[cite2 - 1];
|
|
745
|
+
if (!isValidCitation(citedLine1, line) || !isValidCitation(citedLine2, line)) {
|
|
746
|
+
issues.push('Cited lines must be available in the current scope');
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
var valid = false;
|
|
750
|
+
if (citedLine1.formula.type === 'identity') {
|
|
751
|
+
var _citedLine1$formula$t = _slicedToArray(citedLine1.formula.terms, 2),
|
|
752
|
+
a = _citedLine1$formula$t[0],
|
|
753
|
+
b = _citedLine1$formula$t[1];
|
|
754
|
+
var subAtoB = substitute(citedLine2.formula, b, a);
|
|
755
|
+
var subBtoA = substitute(citedLine2.formula, a, b);
|
|
756
|
+
valid = formulasEqual(subAtoB, line.formula) || formulasEqual(subBtoA, line.formula);
|
|
757
|
+
}
|
|
758
|
+
if (!valid && citedLine2.formula.type === 'identity') {
|
|
759
|
+
var _citedLine2$formula$t = _slicedToArray(citedLine2.formula.terms, 2),
|
|
760
|
+
_a = _citedLine2$formula$t[0],
|
|
761
|
+
_b = _citedLine2$formula$t[1];
|
|
762
|
+
var _subAtoB = substitute(citedLine1.formula, _b, _a);
|
|
763
|
+
var _subBtoA = substitute(citedLine1.formula, _a, _b);
|
|
764
|
+
valid = formulasEqual(_subAtoB, line.formula) || formulasEqual(_subBtoA, line.formula);
|
|
765
|
+
}
|
|
766
|
+
if (!valid) {
|
|
767
|
+
issues.push('Identity elimination requires an identity and a formula to substitute into');
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// ============================================================================
|
|
772
|
+
// STRUCTURAL RULE VALIDATORS
|
|
773
|
+
// ============================================================================
|
|
774
|
+
|
|
775
|
+
function validateReit(line, previousLines, lineIndex, issues) {
|
|
776
|
+
if (line.citations.length !== 1) {
|
|
777
|
+
issues.push('Reiteration requires exactly one citation');
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
var citedIndex = line.citations[0] - 1;
|
|
781
|
+
if (citedIndex < 0 || citedIndex >= previousLines.length) {
|
|
782
|
+
issues.push('Invalid line citation');
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
var cited = previousLines[citedIndex];
|
|
786
|
+
if (!isValidCitation(cited, line)) {
|
|
787
|
+
issues.push('Cited line is not available in current scope');
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
if (!formulasEqual(line.formula, cited.formula)) {
|
|
791
|
+
issues.push('Formula does not match cited line');
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// ============================================================================
|
|
796
|
+
// VALIDATOR REGISTRY
|
|
797
|
+
// ============================================================================
|
|
798
|
+
|
|
799
|
+
export var RULE_VALIDATORS = (_RULE_VALIDATORS = {}, _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_RULE_VALIDATORS, RULES.CONJ_INTRO, validateConjIntro), RULES.CONJ_ELIM, validateConjElim), RULES.DISJ_INTRO, validateDisjIntro), RULES.DISJ_ELIM, validateDisjElim), RULES.COND_INTRO, validateCondIntro), RULES.COND_ELIM, validateCondElim), RULES.BICOND_INTRO, validateBicondIntro), RULES.BICOND_ELIM, validateBicondElim), RULES.NEG_INTRO, validateNegIntro), RULES.NEG_ELIM, validateNegElim), _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_RULE_VALIDATORS, RULES.CONTRA_INTRO, validateContraIntro), RULES.CONTRA_ELIM, validateContraElim), RULES.LEM, validateLEM), RULES.UNIV_INTRO, validateUnivIntro), RULES.UNIV_ELIM, validateUnivElim), RULES.EXIST_INTRO, validateExistIntro), RULES.EXIST_ELIM, validateExistElim), RULES.ID_INTRO, validateIdIntro), RULES.ID_ELIM, validateIdElim), RULES.REIT, validateReit));
|