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.
@@ -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 };