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,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.convertAsciiToSymbols = convertAsciiToSymbols;
|
|
7
|
+
exports.convertSymbolsToAscii = convertSymbolsToAscii;
|
|
8
|
+
exports.symbolMappings = void 0;
|
|
9
|
+
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
10
|
+
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."); }
|
|
11
|
+
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; } }
|
|
12
|
+
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
13
|
+
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; } } }; }
|
|
14
|
+
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; } }
|
|
15
|
+
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; }
|
|
16
|
+
/**
|
|
17
|
+
* ASCII to Unicode symbol conversion utilities for logical notation
|
|
18
|
+
* @module symbols
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// ASCII to Unicode symbol mappings (sorted by length, longest first for correct replacement order)
|
|
22
|
+
var SYMBOL_MAPPINGS = [['<->', '↔'], ['/\\', '∧'], ['\\/', '∨'], ['_|_', '⊥'], ['->', '→'], ['~', '¬'], ['!', '¬'], ['&', '∧'], ['#', '⊥'], ['@', '∀'], ['$', '∃']];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Converts ASCII logical symbols to their Unicode equivalents
|
|
26
|
+
* @param {string} text - Text containing ASCII symbols
|
|
27
|
+
* @returns {string} Text with Unicode symbols
|
|
28
|
+
* @example
|
|
29
|
+
* convertAsciiToSymbols('P -> Q') // Returns 'P → Q'
|
|
30
|
+
* convertAsciiToSymbols('~P /\\ Q') // Returns '¬P ∧ Q'
|
|
31
|
+
* convertAsciiToSymbols('@x P(x)') // Returns '∀x P(x)'
|
|
32
|
+
*/
|
|
33
|
+
function convertAsciiToSymbols(text) {
|
|
34
|
+
var result = text;
|
|
35
|
+
var _iterator = _createForOfIteratorHelper(SYMBOL_MAPPINGS),
|
|
36
|
+
_step;
|
|
37
|
+
try {
|
|
38
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
39
|
+
var _step$value = _slicedToArray(_step.value, 2),
|
|
40
|
+
ascii = _step$value[0],
|
|
41
|
+
symbol = _step$value[1];
|
|
42
|
+
var regex = new RegExp(ascii.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
|
|
43
|
+
result = result.replace(regex, symbol);
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
_iterator.e(err);
|
|
47
|
+
} finally {
|
|
48
|
+
_iterator.f();
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Converts Unicode logical symbols back to ASCII equivalents
|
|
55
|
+
* @param {string} text - Text containing Unicode symbols
|
|
56
|
+
* @returns {string} Text with ASCII symbols
|
|
57
|
+
* @example
|
|
58
|
+
* convertSymbolsToAscii('P → Q') // Returns 'P -> Q'
|
|
59
|
+
* convertSymbolsToAscii('¬P ∧ Q') // Returns '~P /\\ Q'
|
|
60
|
+
*/
|
|
61
|
+
function convertSymbolsToAscii(text) {
|
|
62
|
+
var result = text;
|
|
63
|
+
var _iterator2 = _createForOfIteratorHelper(SYMBOL_MAPPINGS),
|
|
64
|
+
_step2;
|
|
65
|
+
try {
|
|
66
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
67
|
+
var _step2$value = _slicedToArray(_step2.value, 2),
|
|
68
|
+
ascii = _step2$value[0],
|
|
69
|
+
symbol = _step2$value[1];
|
|
70
|
+
var regex = new RegExp(symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
|
|
71
|
+
result = result.replace(regex, ascii);
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
_iterator2.e(err);
|
|
75
|
+
} finally {
|
|
76
|
+
_iterator2.f();
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The symbol mappings array for external use
|
|
83
|
+
* @type {Array<[string, string]>}
|
|
84
|
+
*/
|
|
85
|
+
var symbolMappings = exports.symbolMappings = SYMBOL_MAPPINGS;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
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); }
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.RULE_REQUIREMENTS = exports.RULES = void 0;
|
|
8
|
+
exports.getAllTerms = getAllTerms;
|
|
9
|
+
var _parser = require("./parser.js");
|
|
10
|
+
var _RULE_REQUIREMENTS;
|
|
11
|
+
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; }
|
|
12
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
13
|
+
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); }
|
|
14
|
+
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
|
|
15
|
+
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."); }
|
|
16
|
+
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; } }
|
|
17
|
+
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
|
|
18
|
+
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
|
|
19
|
+
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; }
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} Formula
|
|
22
|
+
* @property {string} type - The type of formula ('atomic', 'compound', 'quantified', 'identity', 'splat')
|
|
23
|
+
* @property {string} [operator] - The main logical operator (for compound/quantified formulas)
|
|
24
|
+
* @property {string} [predicate] - The predicate symbol (for atomic formulas)
|
|
25
|
+
* @property {string[]} [terms] - The terms in the formula
|
|
26
|
+
* @property {Formula} [left] - The left subformula (for binary operators)
|
|
27
|
+
* @property {Formula} [right] - The right subformula (for binary operators)
|
|
28
|
+
* @property {string} [variable] - The bound variable (for quantified formulas)
|
|
29
|
+
* @property {Formula} [scope] - The scope of the quantifier (for quantified formulas)
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {Object} ProofLine
|
|
34
|
+
* @property {number} lineNum - The line number in the proof
|
|
35
|
+
* @property {Formula} formula - The formula at this line
|
|
36
|
+
* @property {string} rule - The rule applied at this line
|
|
37
|
+
* @property {number[]} citations - Line numbers cited in the justification
|
|
38
|
+
* @property {Array<{start: number, end: number}>} subproofs - Subproof ranges cited
|
|
39
|
+
* @property {string[]} issues - Any issues with this line
|
|
40
|
+
* @property {number[]} location - Array indicating the scope depth
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @typedef {Object} Proof
|
|
45
|
+
* @property {ProofLine[]} lines - The lines in the proof
|
|
46
|
+
* @property {number} numPremises - The number of premises
|
|
47
|
+
* @property {Formula} conclusion - The desired conclusion
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {Object} ValidationResult
|
|
52
|
+
* @property {boolean} isValid - Whether the proof is valid
|
|
53
|
+
* @property {string[]} issues - Any issues with the proof
|
|
54
|
+
* @property {boolean} conclusionReached - Whether the conclusion was reached
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Extracts all terms from a formula recursively
|
|
59
|
+
* @param {Formula} formula
|
|
60
|
+
* @returns {string[]}
|
|
61
|
+
*/
|
|
62
|
+
function getAllTerms(formula) {
|
|
63
|
+
if (!formula) return [];
|
|
64
|
+
switch (formula.type) {
|
|
65
|
+
case 'atomic':
|
|
66
|
+
return formula.terms || [];
|
|
67
|
+
case 'compound':
|
|
68
|
+
if (formula.operator === _parser.OPERATORS.NOT) {
|
|
69
|
+
return formula.right ? getAllTerms(formula.right) : [];
|
|
70
|
+
}
|
|
71
|
+
return [].concat(_toConsumableArray(formula.left ? getAllTerms(formula.left) : []), _toConsumableArray(formula.right ? getAllTerms(formula.right) : []));
|
|
72
|
+
case 'quantified':
|
|
73
|
+
if (!formula.scope) return [];
|
|
74
|
+
return getAllTerms(formula.scope).filter(function (t) {
|
|
75
|
+
return t !== formula.variable;
|
|
76
|
+
});
|
|
77
|
+
case 'identity':
|
|
78
|
+
return formula.terms || [];
|
|
79
|
+
default:
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Rule definitions
|
|
85
|
+
var RULES = exports.RULES = {
|
|
86
|
+
// Propositional Logic Rules
|
|
87
|
+
CONJ_INTRO: '∧I',
|
|
88
|
+
CONJ_ELIM: '∧E',
|
|
89
|
+
DISJ_INTRO: '∨I',
|
|
90
|
+
DISJ_ELIM: '∨E',
|
|
91
|
+
COND_INTRO: '→I',
|
|
92
|
+
COND_ELIM: '→E',
|
|
93
|
+
BICOND_INTRO: '↔I',
|
|
94
|
+
BICOND_ELIM: '↔E',
|
|
95
|
+
NEG_INTRO: '¬I',
|
|
96
|
+
NEG_ELIM: '¬E',
|
|
97
|
+
CONTRA_INTRO: '⊥I',
|
|
98
|
+
CONTRA_ELIM: '⊥E',
|
|
99
|
+
LEM: 'LEM',
|
|
100
|
+
// First-Order Logic Rules
|
|
101
|
+
UNIV_INTRO: '∀I',
|
|
102
|
+
UNIV_ELIM: '∀E',
|
|
103
|
+
EXIST_INTRO: '∃I',
|
|
104
|
+
EXIST_ELIM: '∃E',
|
|
105
|
+
ID_INTRO: '=I',
|
|
106
|
+
ID_ELIM: '=E',
|
|
107
|
+
CQ: 'CQ',
|
|
108
|
+
// Structural Rules
|
|
109
|
+
PREMISE: 'Pr',
|
|
110
|
+
HYPOTHESIS: 'Hyp',
|
|
111
|
+
REIT: 'R'
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Rule citation requirements
|
|
115
|
+
var RULE_REQUIREMENTS = exports.RULE_REQUIREMENTS = (_RULE_REQUIREMENTS = {}, _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_RULE_REQUIREMENTS, RULES.CONJ_INTRO, {
|
|
116
|
+
lines: 2,
|
|
117
|
+
subproofs: 0
|
|
118
|
+
}), RULES.CONJ_ELIM, {
|
|
119
|
+
lines: 1,
|
|
120
|
+
subproofs: 0
|
|
121
|
+
}), RULES.DISJ_INTRO, {
|
|
122
|
+
lines: 1,
|
|
123
|
+
subproofs: 0
|
|
124
|
+
}), RULES.DISJ_ELIM, {
|
|
125
|
+
lines: 1,
|
|
126
|
+
subproofs: 2
|
|
127
|
+
}), RULES.COND_INTRO, {
|
|
128
|
+
lines: 0,
|
|
129
|
+
subproofs: 1
|
|
130
|
+
}), RULES.COND_ELIM, {
|
|
131
|
+
lines: 2,
|
|
132
|
+
subproofs: 0
|
|
133
|
+
}), RULES.BICOND_INTRO, {
|
|
134
|
+
lines: 0,
|
|
135
|
+
subproofs: 2
|
|
136
|
+
}), RULES.BICOND_ELIM, {
|
|
137
|
+
lines: 2,
|
|
138
|
+
subproofs: 0
|
|
139
|
+
}), RULES.NEG_INTRO, {
|
|
140
|
+
lines: 0,
|
|
141
|
+
subproofs: 1
|
|
142
|
+
}), RULES.NEG_ELIM, {
|
|
143
|
+
lines: 2,
|
|
144
|
+
subproofs: 0
|
|
145
|
+
}), _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_RULE_REQUIREMENTS, RULES.CONTRA_INTRO, {
|
|
146
|
+
lines: 2,
|
|
147
|
+
subproofs: 0
|
|
148
|
+
}), RULES.CONTRA_ELIM, {
|
|
149
|
+
lines: 1,
|
|
150
|
+
subproofs: 0
|
|
151
|
+
}), RULES.LEM, {
|
|
152
|
+
lines: 0,
|
|
153
|
+
subproofs: 2
|
|
154
|
+
}), RULES.UNIV_INTRO, {
|
|
155
|
+
lines: 1,
|
|
156
|
+
subproofs: 0
|
|
157
|
+
}), RULES.UNIV_ELIM, {
|
|
158
|
+
lines: 1,
|
|
159
|
+
subproofs: 0
|
|
160
|
+
}), RULES.EXIST_INTRO, {
|
|
161
|
+
lines: 1,
|
|
162
|
+
subproofs: 0
|
|
163
|
+
}), RULES.EXIST_ELIM, {
|
|
164
|
+
lines: 1,
|
|
165
|
+
subproofs: 1
|
|
166
|
+
}), RULES.ID_INTRO, {
|
|
167
|
+
lines: 0,
|
|
168
|
+
subproofs: 0
|
|
169
|
+
}), RULES.ID_ELIM, {
|
|
170
|
+
lines: 2,
|
|
171
|
+
subproofs: 0
|
|
172
|
+
}), RULES.CQ, {
|
|
173
|
+
lines: 1,
|
|
174
|
+
subproofs: 0
|
|
175
|
+
}), _defineProperty(_defineProperty(_defineProperty(_RULE_REQUIREMENTS, RULES.PREMISE, {
|
|
176
|
+
lines: 0,
|
|
177
|
+
subproofs: 0
|
|
178
|
+
}), RULES.HYPOTHESIS, {
|
|
179
|
+
lines: 0,
|
|
180
|
+
subproofs: 0
|
|
181
|
+
}), RULES.REIT, {
|
|
182
|
+
lines: 1,
|
|
183
|
+
subproofs: 0
|
|
184
|
+
}));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Public API exports
|
|
2
|
+
|
|
3
|
+
// Formula parsing
|
|
4
|
+
export { parseFormula, OPERATORS } from './parser.js';
|
|
5
|
+
|
|
6
|
+
// Proof validation
|
|
7
|
+
export { checkProof, formulasEqual, occursFree, substitute, getAvailableTerms } from './proofChecker.js';
|
|
8
|
+
|
|
9
|
+
// Rule definitions and utilities
|
|
10
|
+
export { RULES, RULE_REQUIREMENTS, getAllTerms } from './types.js';
|
|
11
|
+
export { isVariable, isLineAvailable, isValidCitation } from './ruleValidators.js';
|
|
12
|
+
|
|
13
|
+
// Symbol conversion utilities
|
|
14
|
+
export { convertAsciiToSymbols, convertSymbolsToAscii, symbolMappings } from './symbols.js';
|
|
15
|
+
|
|
16
|
+
// Text-format proof parsing
|
|
17
|
+
export { parseProofText, getLineForPosition, getDocumentLineForProofLine, RULE_MAP } from './proof/index.js';
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formula parser for Fitch-style proofs
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export var OPERATORS = {
|
|
6
|
+
AND: '∧',
|
|
7
|
+
OR: '∨',
|
|
8
|
+
NOT: '¬',
|
|
9
|
+
IMPLIES: '→',
|
|
10
|
+
IFF: '↔',
|
|
11
|
+
FORALL: '∀',
|
|
12
|
+
EXISTS: '∃',
|
|
13
|
+
EQUALS: '=',
|
|
14
|
+
CONTRA: '⊥'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Pre-compute operator set for O(1) lookup
|
|
18
|
+
var OPERATOR_SET = new Set(Object.values(OPERATORS));
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Tokenizes a formula string into an array of tokens
|
|
22
|
+
* @param {string} formula - The formula string to tokenize
|
|
23
|
+
* @returns {string[]} Array of tokens
|
|
24
|
+
*/
|
|
25
|
+
function tokenize(formula) {
|
|
26
|
+
var tokens = [];
|
|
27
|
+
var chars = [];
|
|
28
|
+
for (var i = 0; i < formula.length; i++) {
|
|
29
|
+
var _char = formula[i];
|
|
30
|
+
if (_char === ' ') {
|
|
31
|
+
if (chars.length > 0) {
|
|
32
|
+
tokens.push(chars.join(''));
|
|
33
|
+
chars.length = 0;
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if ('(),'.includes(_char)) {
|
|
38
|
+
if (chars.length > 0) {
|
|
39
|
+
tokens.push(chars.join(''));
|
|
40
|
+
chars.length = 0;
|
|
41
|
+
}
|
|
42
|
+
tokens.push(_char);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (OPERATOR_SET.has(_char)) {
|
|
46
|
+
if (chars.length > 0) {
|
|
47
|
+
tokens.push(chars.join(''));
|
|
48
|
+
chars.length = 0;
|
|
49
|
+
}
|
|
50
|
+
tokens.push(_char);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
chars.push(_char);
|
|
54
|
+
}
|
|
55
|
+
if (chars.length > 0) {
|
|
56
|
+
tokens.push(chars.join(''));
|
|
57
|
+
}
|
|
58
|
+
return tokens;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Parses a formula string into a Formula object
|
|
63
|
+
* @param {string} formulaStr - The formula string to parse
|
|
64
|
+
* @returns {import('./types').Formula} The parsed formula
|
|
65
|
+
*/
|
|
66
|
+
export function parseFormula(formulaStr) {
|
|
67
|
+
var tokens = tokenize(formulaStr);
|
|
68
|
+
var pos = 0;
|
|
69
|
+
function parseAtom() {
|
|
70
|
+
var token = tokens[pos];
|
|
71
|
+
pos++;
|
|
72
|
+
|
|
73
|
+
// Check for contradiction
|
|
74
|
+
if (token === OPERATORS.CONTRA) {
|
|
75
|
+
return {
|
|
76
|
+
type: 'splat'
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check if it's followed by terms in parentheses
|
|
81
|
+
if (pos < tokens.length && tokens[pos] === '(') {
|
|
82
|
+
pos++; // Skip opening parenthesis
|
|
83
|
+
var terms = [];
|
|
84
|
+
while (pos < tokens.length && tokens[pos] !== ')') {
|
|
85
|
+
if (tokens[pos] !== ',') {
|
|
86
|
+
terms.push(tokens[pos]);
|
|
87
|
+
}
|
|
88
|
+
pos++;
|
|
89
|
+
}
|
|
90
|
+
if (pos >= tokens.length || tokens[pos] !== ')') {
|
|
91
|
+
throw new Error('Missing closing parenthesis');
|
|
92
|
+
}
|
|
93
|
+
pos++; // Skip closing parenthesis
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
type: 'atomic',
|
|
97
|
+
predicate: token,
|
|
98
|
+
terms: terms
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Single term atomic formula (propositional variable)
|
|
103
|
+
return {
|
|
104
|
+
type: 'atomic',
|
|
105
|
+
predicate: token,
|
|
106
|
+
terms: []
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function parseQuantified() {
|
|
110
|
+
var operator = tokens[pos];
|
|
111
|
+
pos++;
|
|
112
|
+
if (pos >= tokens.length) {
|
|
113
|
+
throw new Error('Incomplete quantified formula');
|
|
114
|
+
}
|
|
115
|
+
var variable = tokens[pos];
|
|
116
|
+
pos++;
|
|
117
|
+
var scope = parseFormula(tokens.slice(pos).join(' '));
|
|
118
|
+
pos = tokens.length;
|
|
119
|
+
return {
|
|
120
|
+
type: 'quantified',
|
|
121
|
+
operator: operator,
|
|
122
|
+
variable: variable,
|
|
123
|
+
scope: scope
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function parseCompound() {
|
|
127
|
+
if (tokens[pos] === '(') {
|
|
128
|
+
pos++; // Skip opening parenthesis
|
|
129
|
+
var formula = parseFormula(tokens.slice(pos).join(' '));
|
|
130
|
+
|
|
131
|
+
// Find matching closing parenthesis
|
|
132
|
+
var parenCount = 1;
|
|
133
|
+
var endPos = pos;
|
|
134
|
+
while (parenCount > 0 && endPos < tokens.length) {
|
|
135
|
+
endPos++;
|
|
136
|
+
if (tokens[endPos] === '(') parenCount++;
|
|
137
|
+
if (tokens[endPos] === ')') parenCount--;
|
|
138
|
+
}
|
|
139
|
+
if (parenCount > 0) {
|
|
140
|
+
throw new Error('Unmatched parentheses');
|
|
141
|
+
}
|
|
142
|
+
pos = endPos + 1;
|
|
143
|
+
|
|
144
|
+
// Check if there's an operator after the parentheses
|
|
145
|
+
if (pos < tokens.length && OPERATOR_SET.has(tokens[pos])) {
|
|
146
|
+
var _operator = tokens[pos];
|
|
147
|
+
pos++;
|
|
148
|
+
var _right = parseFormula(tokens.slice(pos).join(' '));
|
|
149
|
+
pos = tokens.length;
|
|
150
|
+
return {
|
|
151
|
+
type: 'compound',
|
|
152
|
+
operator: _operator,
|
|
153
|
+
left: formula,
|
|
154
|
+
right: _right
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return formula;
|
|
158
|
+
}
|
|
159
|
+
if (tokens[pos] === OPERATORS.NOT) {
|
|
160
|
+
pos++;
|
|
161
|
+
return {
|
|
162
|
+
type: 'compound',
|
|
163
|
+
operator: OPERATORS.NOT,
|
|
164
|
+
right: parseFormula(tokens.slice(pos).join(' '))
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
var left = parseAtom();
|
|
168
|
+
if (pos >= tokens.length) {
|
|
169
|
+
return left;
|
|
170
|
+
}
|
|
171
|
+
var operator = tokens[pos];
|
|
172
|
+
if (!OPERATOR_SET.has(operator)) {
|
|
173
|
+
return left;
|
|
174
|
+
}
|
|
175
|
+
pos++;
|
|
176
|
+
var right = parseFormula(tokens.slice(pos).join(' '));
|
|
177
|
+
pos = tokens.length;
|
|
178
|
+
return {
|
|
179
|
+
type: 'compound',
|
|
180
|
+
operator: operator,
|
|
181
|
+
left: left,
|
|
182
|
+
right: right
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Start parsing
|
|
187
|
+
if (pos >= tokens.length) {
|
|
188
|
+
throw new Error('Empty formula');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check for quantifiers
|
|
192
|
+
if (tokens[pos] === OPERATORS.FORALL || tokens[pos] === OPERATORS.EXISTS) {
|
|
193
|
+
return parseQuantified();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check for identity formulas
|
|
197
|
+
if (tokens.length >= 3 && tokens[1] === OPERATORS.EQUALS) {
|
|
198
|
+
return {
|
|
199
|
+
type: 'identity',
|
|
200
|
+
terms: [tokens[0], tokens[2]]
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
return parseCompound();
|
|
204
|
+
}
|