fitch-js 0.1.0 → 1.0.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/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Fitch-JS
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/fitch-js.svg)](https://www.npmjs.com/package/fitch-js)
4
+ [![license](https://img.shields.io/npm/l/fitch-js.svg)](https://github.com/lagomorph-labs/fitch-js/blob/main/LICENSE)
5
+
3
6
  **We are not affiliated with the [Open Logic Project](https://openlogicproject.org/) or [Carnap](https://carnap.io/) in any way. (Currently)**
4
7
 
5
8
  This is a JavaScript library for validating Fitch-style natural deduction proofs. It provides functionality to check the validity of logical proofs in both propositional and first-order logic entirely in the browser, with no server-side dependencies.
@@ -154,24 +157,26 @@ fitch-js/
154
157
  └── package.json
155
158
  ```
156
159
 
157
- ## Development Setup
160
+ ## Installation
158
161
 
159
- 1. Clone the repository
160
- 2. Install dependencies:
161
- ```bash
162
- cd fitch-js
163
- npm install
164
- ```
165
- 3. Run tests:
166
- ```bash
167
- npm test
168
- ```
162
+ ```bash
163
+ npm install fitch-js
164
+ ```
165
+
166
+ Or with yarn:
167
+
168
+ ```bash
169
+ yarn add fitch-js
170
+ ```
169
171
 
170
172
  ## Usage
171
173
 
172
174
  ```javascript
173
- const { parseFormula } = require('./src/parser');
174
- const { checkProof } = require('./src/proofChecker');
175
+ // ESM
176
+ import { parseFormula, checkProof, parseProofText } from 'fitch-js';
177
+
178
+ // CommonJS
179
+ const { parseFormula, checkProof, parseProofText } = require('fitch-js');
175
180
 
176
181
  // Example: Existential Elimination
177
182
  const proof = {
@@ -251,6 +256,22 @@ Restrictions:
251
256
  - Subproof must start with assumption P(c)
252
257
  - Q must be derivable from the subproof
253
258
 
259
+ ## Development
260
+
261
+ 1. Clone the repository
262
+ 2. Install dependencies:
263
+ ```bash
264
+ npm install
265
+ ```
266
+ 3. Run tests:
267
+ ```bash
268
+ npm test
269
+ ```
270
+ 4. Build:
271
+ ```bash
272
+ npm run build
273
+ ```
274
+
254
275
  ## Contributing
255
276
 
256
277
  Contributions are welcome! Please feel free to submit a Pull Request. Before contributing:
package/dist/cjs/index.js CHANGED
@@ -75,6 +75,12 @@ Object.defineProperty(exports, "getLineForPosition", {
75
75
  return _index.getLineForPosition;
76
76
  }
77
77
  });
78
+ Object.defineProperty(exports, "getProofLineForDocumentLine", {
79
+ enumerable: true,
80
+ get: function get() {
81
+ return _index.getProofLineForDocumentLine;
82
+ }
83
+ });
78
84
  Object.defineProperty(exports, "isLineAvailable", {
79
85
  enumerable: true,
80
86
  get: function get() {
@@ -21,6 +21,12 @@ Object.defineProperty(exports, "getLineForPosition", {
21
21
  return _textParser.getLineForPosition;
22
22
  }
23
23
  });
24
+ Object.defineProperty(exports, "getProofLineForDocumentLine", {
25
+ enumerable: true,
26
+ get: function get() {
27
+ return _textParser.getProofLineForDocumentLine;
28
+ }
29
+ });
24
30
  Object.defineProperty(exports, "parseProofText", {
25
31
  enumerable: true,
26
32
  get: function get() {
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.getDocumentLineForProofLine = getDocumentLineForProofLine;
7
7
  exports.getLineForPosition = getLineForPosition;
8
+ exports.getProofLineForDocumentLine = getProofLineForDocumentLine;
8
9
  exports.parseProofText = parseProofText;
9
10
  var _parser = require("../parser.js");
10
11
  var _symbols = require("../symbols.js");
@@ -59,17 +60,30 @@ function parseProofText(text) {
59
60
  var goalText = null;
60
61
  /** @type {number[]} */
61
62
  var subproofStack = [];
63
+
64
+ // Build document line -> proof line mapping for citation translation
65
+ // Users write document line numbers (1-indexed), we need proof line numbers
66
+ /** @type {Map<number, number>} */
67
+ var docToProofLine = new Map();
68
+ var proofCount = 0;
62
69
  for (var i = 0; i < rawLines.length; i++) {
63
- var raw = rawLines[i];
64
- var trimmed = raw.trim();
70
+ var trimmed = rawLines[i].trim();
71
+ if (trimmed && !trimmed.startsWith('//')) {
72
+ proofCount++;
73
+ docToProofLine.set(i + 1, proofCount); // 1-indexed doc line -> 1-indexed proof line
74
+ }
75
+ }
76
+ for (var _i = 0; _i < rawLines.length; _i++) {
77
+ var raw = rawLines[_i];
78
+ var _trimmed = raw.trim();
65
79
 
66
80
  // Skip empty lines
67
- if (!trimmed) continue;
81
+ if (!_trimmed) continue;
68
82
 
69
83
  // Handle comments
70
- if (trimmed.startsWith('//')) {
84
+ if (_trimmed.startsWith('//')) {
71
85
  // Check for goal declaration
72
- var goalMatch = trimmed.match(/\/\/\s*Goal:\s*(.+)/i);
86
+ var goalMatch = _trimmed.match(/\/\/\s*Goal:\s*(.+)/i);
73
87
  if (goalMatch) {
74
88
  goalText = goalMatch[1].trim();
75
89
  try {
@@ -95,38 +109,55 @@ function parseProofText(text) {
95
109
  }
96
110
 
97
111
  // Parse the line content (strip leading pipes and whitespace)
98
- var content = trimmed.replace(/^\|+\s*/, '');
112
+ var content = _trimmed.replace(/^\|+\s*/, '');
99
113
  var colonIndex = content.lastIndexOf(':');
100
114
  var formulaText = '';
101
115
  var ruleText = '';
102
116
  /** @type {number[]} */
103
117
  var citations = [];
104
118
  /** @type {SubproofCitation[]} */
105
- var subproofCitations = [];
119
+ var subproofs = [];
120
+ /** @type {string[]} */
121
+ var issues = [];
106
122
  if (colonIndex !== -1) {
107
123
  formulaText = content.substring(0, colonIndex).trim();
108
124
  var rulePart = content.substring(colonIndex + 1).trim();
109
125
  var ruleParts = rulePart.split(/\s+/);
110
126
  ruleText = ruleParts[0] || '';
111
127
 
112
- // Parse citations
128
+ // Parse citations - translate from document line numbers to proof line numbers
113
129
  for (var j = 1; j < ruleParts.length; j++) {
114
130
  var cit = ruleParts[j];
115
131
  if (cit.includes('-')) {
116
132
  var _cit$split$map = cit.split('-').map(Number),
117
133
  _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
- });
134
+ startDoc = _cit$split$map2[0],
135
+ endDoc = _cit$split$map2[1];
136
+ if (!isNaN(startDoc) && !isNaN(endDoc)) {
137
+ // Translate document lines to proof lines
138
+ var start = docToProofLine.get(startDoc);
139
+ var end = docToProofLine.get(endDoc);
140
+ if (start === undefined || end === undefined) {
141
+ // Invalid citation - reference to non-proof line (comment/blank)
142
+ issues.push("Invalid subproof citation ".concat(cit, ": references non-proof line"));
143
+ } else {
144
+ subproofs.push({
145
+ start: start,
146
+ end: end
147
+ });
148
+ }
125
149
  }
126
150
  } else {
127
- var num = parseInt(cit, 10);
128
- if (!isNaN(num)) {
129
- citations.push(num);
151
+ var docLineNum = parseInt(cit, 10);
152
+ if (!isNaN(docLineNum)) {
153
+ // Translate document line to proof line
154
+ var proofLineNum = docToProofLine.get(docLineNum);
155
+ if (proofLineNum === undefined) {
156
+ // Invalid citation - reference to non-proof line (comment/blank)
157
+ issues.push("Invalid citation ".concat(docLineNum, ": references non-proof line"));
158
+ } else {
159
+ citations.push(proofLineNum);
160
+ }
130
161
  }
131
162
  }
132
163
  }
@@ -137,8 +168,6 @@ function parseProofText(text) {
137
168
  // Parse formula
138
169
  /** @type {Formula|null} */
139
170
  var formula = null;
140
- /** @type {string[]} */
141
- var issues = [];
142
171
  try {
143
172
  if (formulaText) {
144
173
  formula = (0, _parser.parseFormula)((0, _symbols.convertAsciiToSymbols)(formulaText));
@@ -161,7 +190,7 @@ function parseProofText(text) {
161
190
  formula: formula,
162
191
  rule: normalizedRule,
163
192
  citations: citations,
164
- subproofCitations: subproofCitations,
193
+ subproofs: subproofs,
165
194
  depth: depth,
166
195
  location: [].concat(subproofStack),
167
196
  issues: issues
@@ -219,4 +248,28 @@ function getDocumentLineForProofLine(text, proofLineNum) {
219
248
  }
220
249
  }
221
250
  return 0;
251
+ }
252
+
253
+ /**
254
+ * Finds the proof line number for a given document line number
255
+ *
256
+ * @param {string} text - The original document text
257
+ * @param {number} documentLineNum - The 1-indexed document line number (as users see it)
258
+ * @returns {number} The 1-indexed proof line number, or 0 if not a proof line
259
+ */
260
+ function getProofLineForDocumentLine(text, documentLineNum) {
261
+ var textLines = text.split('\n');
262
+ var proofLineCount = 0;
263
+ var targetIndex = documentLineNum - 1; // Convert to 0-indexed
264
+
265
+ for (var i = 0; i < textLines.length; i++) {
266
+ var trimmed = textLines[i].trim();
267
+ if (trimmed && !trimmed.startsWith('//')) {
268
+ proofLineCount++;
269
+ if (i === targetIndex) {
270
+ return proofLineCount;
271
+ }
272
+ }
273
+ }
274
+ return 0; // Not a valid proof line
222
275
  }
@@ -21,7 +21,7 @@ exports["default"] = exports.RULE_MAP = void 0;
21
21
  * @property {Formula|null} formula - The parsed formula, or null if parsing failed
22
22
  * @property {string} rule - The normalized rule name (e.g., '∧I', 'Pr')
23
23
  * @property {number[]} citations - Line numbers cited in justification
24
- * @property {SubproofCitation[]} subproofCitations - Subproof ranges cited
24
+ * @property {SubproofCitation[]} subproofs - Subproof ranges cited
25
25
  * @property {number} depth - Subproof nesting depth (0 = main proof)
26
26
  * @property {number[]} location - Path through subproof tree
27
27
  * @property {string[]} issues - Parse errors for this line
package/dist/esm/index.js CHANGED
@@ -14,4 +14,4 @@ export { isVariable, isLineAvailable, isValidCitation } from './ruleValidators.j
14
14
  export { convertAsciiToSymbols, convertSymbolsToAscii, symbolMappings } from './symbols.js';
15
15
 
16
16
  // Text-format proof parsing
17
- export { parseProofText, getLineForPosition, getDocumentLineForProofLine, RULE_MAP } from './proof/index.js';
17
+ export { parseProofText, getLineForPosition, getDocumentLineForProofLine, getProofLineForDocumentLine, RULE_MAP } from './proof/index.js';
@@ -3,5 +3,5 @@
3
3
  * @module proof
4
4
  */
5
5
 
6
- export { parseProofText, getLineForPosition, getDocumentLineForProofLine } from './textParser.js';
6
+ export { parseProofText, getLineForPosition, getDocumentLineForProofLine, getProofLineForDocumentLine } from './textParser.js';
7
7
  export { RULE_MAP } from './types.js';
@@ -54,17 +54,30 @@ export function parseProofText(text) {
54
54
  var goalText = null;
55
55
  /** @type {number[]} */
56
56
  var subproofStack = [];
57
+
58
+ // Build document line -> proof line mapping for citation translation
59
+ // Users write document line numbers (1-indexed), we need proof line numbers
60
+ /** @type {Map<number, number>} */
61
+ var docToProofLine = new Map();
62
+ var proofCount = 0;
57
63
  for (var i = 0; i < rawLines.length; i++) {
58
- var raw = rawLines[i];
59
- var trimmed = raw.trim();
64
+ var trimmed = rawLines[i].trim();
65
+ if (trimmed && !trimmed.startsWith('//')) {
66
+ proofCount++;
67
+ docToProofLine.set(i + 1, proofCount); // 1-indexed doc line -> 1-indexed proof line
68
+ }
69
+ }
70
+ for (var _i = 0; _i < rawLines.length; _i++) {
71
+ var raw = rawLines[_i];
72
+ var _trimmed = raw.trim();
60
73
 
61
74
  // Skip empty lines
62
- if (!trimmed) continue;
75
+ if (!_trimmed) continue;
63
76
 
64
77
  // Handle comments
65
- if (trimmed.startsWith('//')) {
78
+ if (_trimmed.startsWith('//')) {
66
79
  // Check for goal declaration
67
- var goalMatch = trimmed.match(/\/\/\s*Goal:\s*(.+)/i);
80
+ var goalMatch = _trimmed.match(/\/\/\s*Goal:\s*(.+)/i);
68
81
  if (goalMatch) {
69
82
  goalText = goalMatch[1].trim();
70
83
  try {
@@ -90,38 +103,55 @@ export function parseProofText(text) {
90
103
  }
91
104
 
92
105
  // Parse the line content (strip leading pipes and whitespace)
93
- var content = trimmed.replace(/^\|+\s*/, '');
106
+ var content = _trimmed.replace(/^\|+\s*/, '');
94
107
  var colonIndex = content.lastIndexOf(':');
95
108
  var formulaText = '';
96
109
  var ruleText = '';
97
110
  /** @type {number[]} */
98
111
  var citations = [];
99
112
  /** @type {SubproofCitation[]} */
100
- var subproofCitations = [];
113
+ var subproofs = [];
114
+ /** @type {string[]} */
115
+ var issues = [];
101
116
  if (colonIndex !== -1) {
102
117
  formulaText = content.substring(0, colonIndex).trim();
103
118
  var rulePart = content.substring(colonIndex + 1).trim();
104
119
  var ruleParts = rulePart.split(/\s+/);
105
120
  ruleText = ruleParts[0] || '';
106
121
 
107
- // Parse citations
122
+ // Parse citations - translate from document line numbers to proof line numbers
108
123
  for (var j = 1; j < ruleParts.length; j++) {
109
124
  var cit = ruleParts[j];
110
125
  if (cit.includes('-')) {
111
126
  var _cit$split$map = cit.split('-').map(Number),
112
127
  _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
- });
128
+ startDoc = _cit$split$map2[0],
129
+ endDoc = _cit$split$map2[1];
130
+ if (!isNaN(startDoc) && !isNaN(endDoc)) {
131
+ // Translate document lines to proof lines
132
+ var start = docToProofLine.get(startDoc);
133
+ var end = docToProofLine.get(endDoc);
134
+ if (start === undefined || end === undefined) {
135
+ // Invalid citation - reference to non-proof line (comment/blank)
136
+ issues.push("Invalid subproof citation ".concat(cit, ": references non-proof line"));
137
+ } else {
138
+ subproofs.push({
139
+ start: start,
140
+ end: end
141
+ });
142
+ }
120
143
  }
121
144
  } else {
122
- var num = parseInt(cit, 10);
123
- if (!isNaN(num)) {
124
- citations.push(num);
145
+ var docLineNum = parseInt(cit, 10);
146
+ if (!isNaN(docLineNum)) {
147
+ // Translate document line to proof line
148
+ var proofLineNum = docToProofLine.get(docLineNum);
149
+ if (proofLineNum === undefined) {
150
+ // Invalid citation - reference to non-proof line (comment/blank)
151
+ issues.push("Invalid citation ".concat(docLineNum, ": references non-proof line"));
152
+ } else {
153
+ citations.push(proofLineNum);
154
+ }
125
155
  }
126
156
  }
127
157
  }
@@ -132,8 +162,6 @@ export function parseProofText(text) {
132
162
  // Parse formula
133
163
  /** @type {Formula|null} */
134
164
  var formula = null;
135
- /** @type {string[]} */
136
- var issues = [];
137
165
  try {
138
166
  if (formulaText) {
139
167
  formula = parseFormula(convertAsciiToSymbols(formulaText));
@@ -156,7 +184,7 @@ export function parseProofText(text) {
156
184
  formula: formula,
157
185
  rule: normalizedRule,
158
186
  citations: citations,
159
- subproofCitations: subproofCitations,
187
+ subproofs: subproofs,
160
188
  depth: depth,
161
189
  location: [].concat(subproofStack),
162
190
  issues: issues
@@ -214,4 +242,28 @@ export function getDocumentLineForProofLine(text, proofLineNum) {
214
242
  }
215
243
  }
216
244
  return 0;
245
+ }
246
+
247
+ /**
248
+ * Finds the proof line number for a given document line number
249
+ *
250
+ * @param {string} text - The original document text
251
+ * @param {number} documentLineNum - The 1-indexed document line number (as users see it)
252
+ * @returns {number} The 1-indexed proof line number, or 0 if not a proof line
253
+ */
254
+ export function getProofLineForDocumentLine(text, documentLineNum) {
255
+ var textLines = text.split('\n');
256
+ var proofLineCount = 0;
257
+ var targetIndex = documentLineNum - 1; // Convert to 0-indexed
258
+
259
+ for (var i = 0; i < textLines.length; i++) {
260
+ var trimmed = textLines[i].trim();
261
+ if (trimmed && !trimmed.startsWith('//')) {
262
+ proofLineCount++;
263
+ if (i === targetIndex) {
264
+ return proofLineCount;
265
+ }
266
+ }
267
+ }
268
+ return 0; // Not a valid proof line
217
269
  }
@@ -15,7 +15,7 @@
15
15
  * @property {Formula|null} formula - The parsed formula, or null if parsing failed
16
16
  * @property {string} rule - The normalized rule name (e.g., '∧I', 'Pr')
17
17
  * @property {number[]} citations - Line numbers cited in justification
18
- * @property {SubproofCitation[]} subproofCitations - Subproof ranges cited
18
+ * @property {SubproofCitation[]} subproofs - Subproof ranges cited
19
19
  * @property {number} depth - Subproof nesting depth (0 = main proof)
20
20
  * @property {number[]} location - Path through subproof tree
21
21
  * @property {string[]} issues - Parse errors for this line
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fitch-js",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "description": "A JavaScript library for validating Fitch-style natural deduction proofs",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -42,4 +42,4 @@
42
42
  "jest": "^29.7.0",
43
43
  "typescript": "^5.3.3"
44
44
  }
45
- }
45
+ }