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,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.OPERATORS = void 0;
|
|
7
|
+
exports.parseFormula = parseFormula;
|
|
8
|
+
/**
|
|
9
|
+
* Formula parser for Fitch-style proofs
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
var OPERATORS = exports.OPERATORS = {
|
|
13
|
+
AND: '∧',
|
|
14
|
+
OR: '∨',
|
|
15
|
+
NOT: '¬',
|
|
16
|
+
IMPLIES: '→',
|
|
17
|
+
IFF: '↔',
|
|
18
|
+
FORALL: '∀',
|
|
19
|
+
EXISTS: '∃',
|
|
20
|
+
EQUALS: '=',
|
|
21
|
+
CONTRA: '⊥'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Pre-compute operator set for O(1) lookup
|
|
25
|
+
var OPERATOR_SET = new Set(Object.values(OPERATORS));
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Tokenizes a formula string into an array of tokens
|
|
29
|
+
* @param {string} formula - The formula string to tokenize
|
|
30
|
+
* @returns {string[]} Array of tokens
|
|
31
|
+
*/
|
|
32
|
+
function tokenize(formula) {
|
|
33
|
+
var tokens = [];
|
|
34
|
+
var chars = [];
|
|
35
|
+
for (var i = 0; i < formula.length; i++) {
|
|
36
|
+
var _char = formula[i];
|
|
37
|
+
if (_char === ' ') {
|
|
38
|
+
if (chars.length > 0) {
|
|
39
|
+
tokens.push(chars.join(''));
|
|
40
|
+
chars.length = 0;
|
|
41
|
+
}
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if ('(),'.includes(_char)) {
|
|
45
|
+
if (chars.length > 0) {
|
|
46
|
+
tokens.push(chars.join(''));
|
|
47
|
+
chars.length = 0;
|
|
48
|
+
}
|
|
49
|
+
tokens.push(_char);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (OPERATOR_SET.has(_char)) {
|
|
53
|
+
if (chars.length > 0) {
|
|
54
|
+
tokens.push(chars.join(''));
|
|
55
|
+
chars.length = 0;
|
|
56
|
+
}
|
|
57
|
+
tokens.push(_char);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
chars.push(_char);
|
|
61
|
+
}
|
|
62
|
+
if (chars.length > 0) {
|
|
63
|
+
tokens.push(chars.join(''));
|
|
64
|
+
}
|
|
65
|
+
return tokens;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parses a formula string into a Formula object
|
|
70
|
+
* @param {string} formulaStr - The formula string to parse
|
|
71
|
+
* @returns {import('./types').Formula} The parsed formula
|
|
72
|
+
*/
|
|
73
|
+
function parseFormula(formulaStr) {
|
|
74
|
+
var tokens = tokenize(formulaStr);
|
|
75
|
+
var pos = 0;
|
|
76
|
+
function parseAtom() {
|
|
77
|
+
var token = tokens[pos];
|
|
78
|
+
pos++;
|
|
79
|
+
|
|
80
|
+
// Check for contradiction
|
|
81
|
+
if (token === OPERATORS.CONTRA) {
|
|
82
|
+
return {
|
|
83
|
+
type: 'splat'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check if it's followed by terms in parentheses
|
|
88
|
+
if (pos < tokens.length && tokens[pos] === '(') {
|
|
89
|
+
pos++; // Skip opening parenthesis
|
|
90
|
+
var terms = [];
|
|
91
|
+
while (pos < tokens.length && tokens[pos] !== ')') {
|
|
92
|
+
if (tokens[pos] !== ',') {
|
|
93
|
+
terms.push(tokens[pos]);
|
|
94
|
+
}
|
|
95
|
+
pos++;
|
|
96
|
+
}
|
|
97
|
+
if (pos >= tokens.length || tokens[pos] !== ')') {
|
|
98
|
+
throw new Error('Missing closing parenthesis');
|
|
99
|
+
}
|
|
100
|
+
pos++; // Skip closing parenthesis
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
type: 'atomic',
|
|
104
|
+
predicate: token,
|
|
105
|
+
terms: terms
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Single term atomic formula (propositional variable)
|
|
110
|
+
return {
|
|
111
|
+
type: 'atomic',
|
|
112
|
+
predicate: token,
|
|
113
|
+
terms: []
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function parseQuantified() {
|
|
117
|
+
var operator = tokens[pos];
|
|
118
|
+
pos++;
|
|
119
|
+
if (pos >= tokens.length) {
|
|
120
|
+
throw new Error('Incomplete quantified formula');
|
|
121
|
+
}
|
|
122
|
+
var variable = tokens[pos];
|
|
123
|
+
pos++;
|
|
124
|
+
var scope = parseFormula(tokens.slice(pos).join(' '));
|
|
125
|
+
pos = tokens.length;
|
|
126
|
+
return {
|
|
127
|
+
type: 'quantified',
|
|
128
|
+
operator: operator,
|
|
129
|
+
variable: variable,
|
|
130
|
+
scope: scope
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function parseCompound() {
|
|
134
|
+
if (tokens[pos] === '(') {
|
|
135
|
+
pos++; // Skip opening parenthesis
|
|
136
|
+
var formula = parseFormula(tokens.slice(pos).join(' '));
|
|
137
|
+
|
|
138
|
+
// Find matching closing parenthesis
|
|
139
|
+
var parenCount = 1;
|
|
140
|
+
var endPos = pos;
|
|
141
|
+
while (parenCount > 0 && endPos < tokens.length) {
|
|
142
|
+
endPos++;
|
|
143
|
+
if (tokens[endPos] === '(') parenCount++;
|
|
144
|
+
if (tokens[endPos] === ')') parenCount--;
|
|
145
|
+
}
|
|
146
|
+
if (parenCount > 0) {
|
|
147
|
+
throw new Error('Unmatched parentheses');
|
|
148
|
+
}
|
|
149
|
+
pos = endPos + 1;
|
|
150
|
+
|
|
151
|
+
// Check if there's an operator after the parentheses
|
|
152
|
+
if (pos < tokens.length && OPERATOR_SET.has(tokens[pos])) {
|
|
153
|
+
var _operator = tokens[pos];
|
|
154
|
+
pos++;
|
|
155
|
+
var _right = parseFormula(tokens.slice(pos).join(' '));
|
|
156
|
+
pos = tokens.length;
|
|
157
|
+
return {
|
|
158
|
+
type: 'compound',
|
|
159
|
+
operator: _operator,
|
|
160
|
+
left: formula,
|
|
161
|
+
right: _right
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return formula;
|
|
165
|
+
}
|
|
166
|
+
if (tokens[pos] === OPERATORS.NOT) {
|
|
167
|
+
pos++;
|
|
168
|
+
return {
|
|
169
|
+
type: 'compound',
|
|
170
|
+
operator: OPERATORS.NOT,
|
|
171
|
+
right: parseFormula(tokens.slice(pos).join(' '))
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
var left = parseAtom();
|
|
175
|
+
if (pos >= tokens.length) {
|
|
176
|
+
return left;
|
|
177
|
+
}
|
|
178
|
+
var operator = tokens[pos];
|
|
179
|
+
if (!OPERATOR_SET.has(operator)) {
|
|
180
|
+
return left;
|
|
181
|
+
}
|
|
182
|
+
pos++;
|
|
183
|
+
var right = parseFormula(tokens.slice(pos).join(' '));
|
|
184
|
+
pos = tokens.length;
|
|
185
|
+
return {
|
|
186
|
+
type: 'compound',
|
|
187
|
+
operator: operator,
|
|
188
|
+
left: left,
|
|
189
|
+
right: right
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Start parsing
|
|
194
|
+
if (pos >= tokens.length) {
|
|
195
|
+
throw new Error('Empty formula');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check for quantifiers
|
|
199
|
+
if (tokens[pos] === OPERATORS.FORALL || tokens[pos] === OPERATORS.EXISTS) {
|
|
200
|
+
return parseQuantified();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Check for identity formulas
|
|
204
|
+
if (tokens.length >= 3 && tokens[1] === OPERATORS.EQUALS) {
|
|
205
|
+
return {
|
|
206
|
+
type: 'identity',
|
|
207
|
+
terms: [tokens[0], tokens[2]]
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return parseCompound();
|
|
211
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "RULE_MAP", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function get() {
|
|
9
|
+
return _types.RULE_MAP;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "getDocumentLineForProofLine", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function get() {
|
|
15
|
+
return _textParser.getDocumentLineForProofLine;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "getLineForPosition", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function get() {
|
|
21
|
+
return _textParser.getLineForPosition;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(exports, "parseProofText", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function get() {
|
|
27
|
+
return _textParser.parseProofText;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
var _textParser = require("./textParser.js");
|
|
31
|
+
var _types = require("./types.js");
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getDocumentLineForProofLine = getDocumentLineForProofLine;
|
|
7
|
+
exports.getLineForPosition = getLineForPosition;
|
|
8
|
+
exports.parseProofText = parseProofText;
|
|
9
|
+
var _parser = require("../parser.js");
|
|
10
|
+
var _symbols = require("../symbols.js");
|
|
11
|
+
var _types = require("./types.js");
|
|
12
|
+
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
13
|
+
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."); }
|
|
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
|
+
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; } }
|
|
17
|
+
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } /**
|
|
18
|
+
* Text-format proof parser for Fitch-style natural deduction proofs
|
|
19
|
+
* @module proof/textParser
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {import('./types.js').ProofLine} ProofLine
|
|
23
|
+
* @typedef {import('./types.js').ParsedProof} ParsedProof
|
|
24
|
+
* @typedef {import('./types.js').SubproofCitation} SubproofCitation
|
|
25
|
+
* @typedef {import('../types.js').Formula} Formula
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parses a text-format Fitch proof into a structured representation
|
|
30
|
+
*
|
|
31
|
+
* @param {string} text - The proof text to parse
|
|
32
|
+
* @returns {ParsedProof} The parsed proof structure
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const proof = parseProofText(`
|
|
36
|
+
* P → Q :PR
|
|
37
|
+
* P :PR
|
|
38
|
+
* Q :→E 1 2
|
|
39
|
+
* // Goal: Q
|
|
40
|
+
* `);
|
|
41
|
+
*
|
|
42
|
+
* @description
|
|
43
|
+
* Text format specification:
|
|
44
|
+
* - Each line: FORMULA :RULE citations
|
|
45
|
+
* - Subproofs indicated by leading pipes: | or ||
|
|
46
|
+
* - Comments: // text
|
|
47
|
+
* - Goal: // Goal: FORMULA
|
|
48
|
+
* - Line citations: space-separated numbers (e.g., 1 2)
|
|
49
|
+
* - Subproof citations: hyphenated ranges (e.g., 3-5)
|
|
50
|
+
*/
|
|
51
|
+
function parseProofText(text) {
|
|
52
|
+
/** @type {ProofLine[]} */
|
|
53
|
+
var lines = [];
|
|
54
|
+
var rawLines = text.split('\n');
|
|
55
|
+
var lineNum = 0;
|
|
56
|
+
/** @type {Formula|null} */
|
|
57
|
+
var goal = null;
|
|
58
|
+
/** @type {string|null} */
|
|
59
|
+
var goalText = null;
|
|
60
|
+
/** @type {number[]} */
|
|
61
|
+
var subproofStack = [];
|
|
62
|
+
for (var i = 0; i < rawLines.length; i++) {
|
|
63
|
+
var raw = rawLines[i];
|
|
64
|
+
var trimmed = raw.trim();
|
|
65
|
+
|
|
66
|
+
// Skip empty lines
|
|
67
|
+
if (!trimmed) continue;
|
|
68
|
+
|
|
69
|
+
// Handle comments
|
|
70
|
+
if (trimmed.startsWith('//')) {
|
|
71
|
+
// Check for goal declaration
|
|
72
|
+
var goalMatch = trimmed.match(/\/\/\s*Goal:\s*(.+)/i);
|
|
73
|
+
if (goalMatch) {
|
|
74
|
+
goalText = goalMatch[1].trim();
|
|
75
|
+
try {
|
|
76
|
+
goal = (0, _parser.parseFormula)((0, _symbols.convertAsciiToSymbols)(goalText));
|
|
77
|
+
} catch (_unused) {
|
|
78
|
+
// Invalid goal formula - will be caught during validation
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
lineNum++;
|
|
84
|
+
|
|
85
|
+
// Calculate depth from leading pipes
|
|
86
|
+
var depthMatch = raw.match(/^(\s*\|*)/);
|
|
87
|
+
var depth = depthMatch ? (depthMatch[1].match(/\|/g) || []).length : 0;
|
|
88
|
+
|
|
89
|
+
// Update subproof stack based on depth changes
|
|
90
|
+
while (subproofStack.length > depth) {
|
|
91
|
+
subproofStack.pop();
|
|
92
|
+
}
|
|
93
|
+
if (depth > subproofStack.length) {
|
|
94
|
+
subproofStack.push(lineNum);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Parse the line content (strip leading pipes and whitespace)
|
|
98
|
+
var content = trimmed.replace(/^\|+\s*/, '');
|
|
99
|
+
var colonIndex = content.lastIndexOf(':');
|
|
100
|
+
var formulaText = '';
|
|
101
|
+
var ruleText = '';
|
|
102
|
+
/** @type {number[]} */
|
|
103
|
+
var citations = [];
|
|
104
|
+
/** @type {SubproofCitation[]} */
|
|
105
|
+
var subproofCitations = [];
|
|
106
|
+
if (colonIndex !== -1) {
|
|
107
|
+
formulaText = content.substring(0, colonIndex).trim();
|
|
108
|
+
var rulePart = content.substring(colonIndex + 1).trim();
|
|
109
|
+
var ruleParts = rulePart.split(/\s+/);
|
|
110
|
+
ruleText = ruleParts[0] || '';
|
|
111
|
+
|
|
112
|
+
// Parse citations
|
|
113
|
+
for (var j = 1; j < ruleParts.length; j++) {
|
|
114
|
+
var cit = ruleParts[j];
|
|
115
|
+
if (cit.includes('-')) {
|
|
116
|
+
var _cit$split$map = cit.split('-').map(Number),
|
|
117
|
+
_cit$split$map2 = _slicedToArray(_cit$split$map, 2),
|
|
118
|
+
start = _cit$split$map2[0],
|
|
119
|
+
end = _cit$split$map2[1];
|
|
120
|
+
if (!isNaN(start) && !isNaN(end)) {
|
|
121
|
+
subproofCitations.push({
|
|
122
|
+
start: start,
|
|
123
|
+
end: end
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
var num = parseInt(cit, 10);
|
|
128
|
+
if (!isNaN(num)) {
|
|
129
|
+
citations.push(num);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
formulaText = content;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Parse formula
|
|
138
|
+
/** @type {Formula|null} */
|
|
139
|
+
var formula = null;
|
|
140
|
+
/** @type {string[]} */
|
|
141
|
+
var issues = [];
|
|
142
|
+
try {
|
|
143
|
+
if (formulaText) {
|
|
144
|
+
formula = (0, _parser.parseFormula)((0, _symbols.convertAsciiToSymbols)(formulaText));
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
issues.push("Parse error: ".concat(e instanceof Error ? e.message : 'Invalid formula'));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Normalize rule - try both the raw rule and the converted version
|
|
151
|
+
var convertedRule = (0, _symbols.convertAsciiToSymbols)(ruleText);
|
|
152
|
+
var normalizedRule = _types.RULE_MAP[convertedRule] || _types.RULE_MAP[ruleText] || ruleText;
|
|
153
|
+
|
|
154
|
+
// Validate that rule is known
|
|
155
|
+
if (ruleText && !_types.RULE_MAP[convertedRule] && !_types.RULE_MAP[ruleText]) {
|
|
156
|
+
issues.push("Unknown rule: ".concat(ruleText));
|
|
157
|
+
}
|
|
158
|
+
lines.push({
|
|
159
|
+
lineNum: lineNum,
|
|
160
|
+
rawText: raw,
|
|
161
|
+
formula: formula,
|
|
162
|
+
rule: normalizedRule,
|
|
163
|
+
citations: citations,
|
|
164
|
+
subproofCitations: subproofCitations,
|
|
165
|
+
depth: depth,
|
|
166
|
+
location: [].concat(subproofStack),
|
|
167
|
+
issues: issues
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
lines: lines,
|
|
172
|
+
goal: goal,
|
|
173
|
+
goalText: goalText
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Finds the proof line corresponding to a document position
|
|
179
|
+
*
|
|
180
|
+
* @param {ParsedProof} parsed - The parsed proof
|
|
181
|
+
* @param {string} text - The original document text
|
|
182
|
+
* @param {number} line - The 0-indexed document line number
|
|
183
|
+
* @returns {ProofLine|null} The proof line at that position, or null
|
|
184
|
+
*/
|
|
185
|
+
function getLineForPosition(parsed, text, line) {
|
|
186
|
+
var textLines = text.split('\n');
|
|
187
|
+
var proofLineCount = 0;
|
|
188
|
+
for (var i = 0; i < textLines.length && i <= line; i++) {
|
|
189
|
+
var trimmed = textLines[i].trim();
|
|
190
|
+
if (trimmed && !trimmed.startsWith('//')) {
|
|
191
|
+
proofLineCount++;
|
|
192
|
+
if (i === line) {
|
|
193
|
+
return parsed.lines.find(function (l) {
|
|
194
|
+
return l.lineNum === proofLineCount;
|
|
195
|
+
}) || null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Finds the document line number for a given proof line number
|
|
204
|
+
*
|
|
205
|
+
* @param {string} text - The original document text
|
|
206
|
+
* @param {number} proofLineNum - The 1-indexed proof line number
|
|
207
|
+
* @returns {number} The 0-indexed document line number
|
|
208
|
+
*/
|
|
209
|
+
function getDocumentLineForProofLine(text, proofLineNum) {
|
|
210
|
+
var textLines = text.split('\n');
|
|
211
|
+
var proofLineCount = 0;
|
|
212
|
+
for (var i = 0; i < textLines.length; i++) {
|
|
213
|
+
var trimmed = textLines[i].trim();
|
|
214
|
+
if (trimmed && !trimmed.startsWith('//')) {
|
|
215
|
+
proofLineCount++;
|
|
216
|
+
if (proofLineCount === proofLineNum) {
|
|
217
|
+
return i;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports["default"] = exports.RULE_MAP = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Type definitions for proof text parsing
|
|
9
|
+
* @module proof/types
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {import('../types.js').Formula} Formula
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A parsed line from a text-format proof
|
|
18
|
+
* @typedef {Object} ProofLine
|
|
19
|
+
* @property {number} lineNum - The proof line number (1-indexed, excludes comments)
|
|
20
|
+
* @property {string} rawText - The original text of the line
|
|
21
|
+
* @property {Formula|null} formula - The parsed formula, or null if parsing failed
|
|
22
|
+
* @property {string} rule - The normalized rule name (e.g., '∧I', 'Pr')
|
|
23
|
+
* @property {number[]} citations - Line numbers cited in justification
|
|
24
|
+
* @property {SubproofCitation[]} subproofCitations - Subproof ranges cited
|
|
25
|
+
* @property {number} depth - Subproof nesting depth (0 = main proof)
|
|
26
|
+
* @property {number[]} location - Path through subproof tree
|
|
27
|
+
* @property {string[]} issues - Parse errors for this line
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A subproof citation range
|
|
32
|
+
* @typedef {Object} SubproofCitation
|
|
33
|
+
* @property {number} start - Starting line number
|
|
34
|
+
* @property {number} end - Ending line number
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Result of parsing a proof text file
|
|
39
|
+
* @typedef {Object} ParsedProof
|
|
40
|
+
* @property {ProofLine[]} lines - All parsed proof lines
|
|
41
|
+
* @property {Formula|null} goal - The goal formula (from // Goal: comment)
|
|
42
|
+
* @property {string|null} goalText - The raw goal text
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Rule normalization map - maps various ASCII/Unicode rule names to canonical form
|
|
47
|
+
* @type {Record<string, string>}
|
|
48
|
+
*/
|
|
49
|
+
var RULE_MAP = exports.RULE_MAP = {
|
|
50
|
+
// Structural rules
|
|
51
|
+
'PR': 'Pr',
|
|
52
|
+
'Pr': 'Pr',
|
|
53
|
+
'Hyp': 'Hyp',
|
|
54
|
+
'AS': 'Hyp',
|
|
55
|
+
'R': 'R',
|
|
56
|
+
// Conjunction
|
|
57
|
+
'∧I': '∧I',
|
|
58
|
+
'&I': '∧I',
|
|
59
|
+
'/\\I': '∧I',
|
|
60
|
+
'^I': '∧I',
|
|
61
|
+
'∧E': '∧E',
|
|
62
|
+
'&E': '∧E',
|
|
63
|
+
'/\\E': '∧E',
|
|
64
|
+
'^E': '∧E',
|
|
65
|
+
// Disjunction
|
|
66
|
+
'∨I': '∨I',
|
|
67
|
+
'|I': '∨I',
|
|
68
|
+
'\\/I': '∨I',
|
|
69
|
+
'∨E': '∨E',
|
|
70
|
+
'|E': '∨E',
|
|
71
|
+
'\\/E': '∨E',
|
|
72
|
+
// Implication
|
|
73
|
+
'→I': '→I',
|
|
74
|
+
'->I': '→I',
|
|
75
|
+
'→E': '→E',
|
|
76
|
+
'->E': '→E',
|
|
77
|
+
'MP': '→E',
|
|
78
|
+
// Biconditional
|
|
79
|
+
'↔I': '↔I',
|
|
80
|
+
'<->I': '↔I',
|
|
81
|
+
'↔E': '↔E',
|
|
82
|
+
'<->E': '↔E',
|
|
83
|
+
// Negation
|
|
84
|
+
'¬I': '¬I',
|
|
85
|
+
'~I': '¬I',
|
|
86
|
+
'!I': '¬I',
|
|
87
|
+
'¬E': '¬E',
|
|
88
|
+
'~E': '¬E',
|
|
89
|
+
'!E': '¬E',
|
|
90
|
+
'DNE': '¬E',
|
|
91
|
+
// Contradiction
|
|
92
|
+
'⊥I': '⊥I',
|
|
93
|
+
'#I': '⊥I',
|
|
94
|
+
'_|_I': '⊥I',
|
|
95
|
+
'⊥E': '⊥E',
|
|
96
|
+
'#E': '⊥E',
|
|
97
|
+
'_|_E': '⊥E',
|
|
98
|
+
'X': '⊥E',
|
|
99
|
+
// Quantifiers
|
|
100
|
+
'∀I': '∀I',
|
|
101
|
+
'@I': '∀I',
|
|
102
|
+
'∀E': '∀E',
|
|
103
|
+
'@E': '∀E',
|
|
104
|
+
'∃I': '∃I',
|
|
105
|
+
'$I': '∃I',
|
|
106
|
+
'∃E': '∃E',
|
|
107
|
+
'$E': '∃E',
|
|
108
|
+
// Identity
|
|
109
|
+
'=I': '=I',
|
|
110
|
+
'=E': '=E',
|
|
111
|
+
// Meta rules
|
|
112
|
+
'LEM': 'LEM',
|
|
113
|
+
'CQ': 'CQ'
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Export empty object to make this a module (types are in JSDoc)
|
|
117
|
+
var _default = exports["default"] = {};
|