prettier-plugin-wolfram 0.7.11 → 0.7.13
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/package.json
CHANGED
package/src/parser/adapter.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { makeLineIndex, nodeSource, offsetToLineCol } from "./position.js";
|
|
2
2
|
import { INFIX_OPS, BINARY_OPS, PREFIX_OPS, POSTFIX_OPS, opName } from "./operators.js";
|
|
3
|
+
import { IMPLICIT_NULL_SYMBOL } from "./sentinels.js";
|
|
3
4
|
|
|
4
5
|
const LEAF_KIND = { symbol: "Symbol", integer: "Integer", real: "Real", string: "String", comment: "Token`Comment" };
|
|
5
6
|
|
|
@@ -47,13 +48,24 @@ export function adapt(tree, source, preprocessedSource, map) {
|
|
|
47
48
|
|
|
48
49
|
function shouldHoistTopLevelSemicolonChain(node, ctx) {
|
|
49
50
|
if (node.type !== "infix" || operatorLiteral(node, ctx) !== ";") return false;
|
|
50
|
-
return hasTrailingSemicolon(node) || spansMultipleLines(node, ctx);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Returns true if the infix node ends with
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
return hasTrailingSemicolon(node, ctx) || spansMultipleLines(node, ctx);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Returns true if the infix node ends with ";" and has no real RHS. Raw trailing
|
|
55
|
+
// semicolons may be represented as a MISSING node, while preprocessed ones use
|
|
56
|
+
// the internal fake-null symbol.
|
|
57
|
+
function hasTrailingSemicolon(node, ctx) {
|
|
58
|
+
for (let i = node.childCount - 1; i >= 0; i--) {
|
|
59
|
+
const child = node.child(i);
|
|
60
|
+
if (child.isMissing) return true;
|
|
61
|
+
if (child.isNamed) {
|
|
62
|
+
if (child.type === "comment") continue;
|
|
63
|
+
if (isImplicitNullSymbol(child, ctx)) return true;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return child.type === ";";
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
function spansMultipleLines(node, ctx) {
|
|
@@ -61,6 +73,13 @@ function spansMultipleLines(node, ctx) {
|
|
|
61
73
|
return source?.[0]?.[0] !== source?.[1]?.[0];
|
|
62
74
|
}
|
|
63
75
|
|
|
76
|
+
function isImplicitNullSymbol(node, ctx) {
|
|
77
|
+
return (
|
|
78
|
+
node?.type === "symbol" &&
|
|
79
|
+
ctx.source.slice(node.startIndex, node.endIndex) === IMPLICIT_NULL_SYMBOL
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
64
83
|
// Collect all leaf statements from a semicolon infix chain (possibly left-recursive),
|
|
65
84
|
// flattening the left-associative structure into a linear list of segments.
|
|
66
85
|
// Each segment is { nodes: TSNode[], semiToken: TSNode|null } where semiToken is the ";" that follows.
|
|
@@ -76,7 +95,7 @@ function collectSemicolonSegments(node, ctx, segments) {
|
|
|
76
95
|
if (!c.isNamed) {
|
|
77
96
|
// ";" operator token
|
|
78
97
|
semiToken = c;
|
|
79
|
-
} else if (c.isMissing) {
|
|
98
|
+
} else if (c.isMissing || isImplicitNullSymbol(c, ctx)) {
|
|
80
99
|
// trailing implicit null — rhs is nothing (trailing semicolon)
|
|
81
100
|
} else if (c.type === "comment") {
|
|
82
101
|
if (lhs === null) leadingComments.push(c);
|
|
@@ -138,7 +157,10 @@ function isLeadingCommentForNextSegment(comment, semiToken, ctx) {
|
|
|
138
157
|
function hoistSemicolonChildren(node, ctx, out) {
|
|
139
158
|
const segments = [];
|
|
140
159
|
collectSemicolonSegments(node, ctx, segments);
|
|
160
|
+
emitSemicolonSegments(segments, ctx, out);
|
|
161
|
+
}
|
|
141
162
|
|
|
163
|
+
function emitSemicolonSegments(segments, ctx, out) {
|
|
142
164
|
for (const seg of segments) {
|
|
143
165
|
for (const c of seg.leadingComments ?? []) out.push(leaf(c, ctx));
|
|
144
166
|
if (seg.nodes.length === 0) continue;
|
|
@@ -195,6 +217,9 @@ function namedChildren(node) {
|
|
|
195
217
|
}
|
|
196
218
|
|
|
197
219
|
function leaf(node, ctx, kind = LEAF_KIND[node.type]) {
|
|
220
|
+
if (isImplicitNullSymbol(node, ctx)) {
|
|
221
|
+
return { type: "LeafNode", kind: "Token`Fake`ImplicitNull", value: "", source: nodeSource(node, ctx.lineIndex) };
|
|
222
|
+
}
|
|
198
223
|
return { type: "LeafNode", kind, value: ctx.source.slice(node.startIndex, node.endIndex), source: nodeSource(node, ctx.lineIndex) };
|
|
199
224
|
}
|
|
200
225
|
|
package/src/parser/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { dirname, resolve } from "path";
|
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
4
|
import { readFileSync } from "fs";
|
|
5
5
|
import { adapt } from "./adapter.js";
|
|
6
|
+
import { IMPLICIT_NULL_SYMBOL } from "./sentinels.js";
|
|
6
7
|
|
|
7
8
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
const WASM_PATH = resolve(here, "tree-sitter-wolfram.wasm");
|
|
@@ -24,10 +25,11 @@ async function getLanguage() {
|
|
|
24
25
|
// Returns { text, map } where `text` is the preprocessed source and `map` is an
|
|
25
26
|
// index translation table: map[i] is the original-source character offset that
|
|
26
27
|
// corresponds to preprocessed-text offset i (map[text.length] === src.length).
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
28
|
+
// Length-changing transforms collapse a run of spaces into a single
|
|
29
|
+
// InvisibleTimes char or insert an internal fake Null symbol after terminating
|
|
30
|
+
// semicolons. This map lets callers translate tree-sitter node positions
|
|
31
|
+
// (computed on the preprocessed text) back to exact offsets in the original
|
|
32
|
+
// source, without lossy line/col round-trips.
|
|
31
33
|
export function preprocess(src) {
|
|
32
34
|
let result = "";
|
|
33
35
|
const map = [];
|
|
@@ -37,6 +39,37 @@ export function preprocess(src) {
|
|
|
37
39
|
for (let k = start; k < end; k++) map.push(k);
|
|
38
40
|
result += src.slice(start, end);
|
|
39
41
|
};
|
|
42
|
+
const appendSynthetic = (text, originalOffset) => {
|
|
43
|
+
for (let k = 0; k < text.length; k++) map.push(originalOffset);
|
|
44
|
+
result += text;
|
|
45
|
+
};
|
|
46
|
+
const skipTrivia = (start) => {
|
|
47
|
+
let j = start;
|
|
48
|
+
while (j < n) {
|
|
49
|
+
if (/\s/.test(src[j])) {
|
|
50
|
+
j++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (src[j] === "(" && src[j + 1] === "*") {
|
|
54
|
+
j += 2;
|
|
55
|
+
let depth = 1;
|
|
56
|
+
while (j < n && depth > 0) {
|
|
57
|
+
if (src[j] === "(" && src[j + 1] === "*") { depth++; j += 2; }
|
|
58
|
+
else if (src[j] === "*" && src[j + 1] === ")") { depth--; j += 2; }
|
|
59
|
+
else j++;
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
return j;
|
|
66
|
+
};
|
|
67
|
+
const isTerminatorBoundary = (offset) =>
|
|
68
|
+
offset >= n ||
|
|
69
|
+
src[offset] === "]" ||
|
|
70
|
+
src[offset] === "}" ||
|
|
71
|
+
src[offset] === ")" ||
|
|
72
|
+
(src[offset] === "|" && src[offset + 1] === ">");
|
|
40
73
|
let i = 0;
|
|
41
74
|
while (i < n) {
|
|
42
75
|
// Skip quoted string
|
|
@@ -63,6 +96,15 @@ export function preprocess(src) {
|
|
|
63
96
|
copyVerbatim(start, i);
|
|
64
97
|
continue;
|
|
65
98
|
}
|
|
99
|
+
if (src[i] === ";" && src[i - 1] !== ";" && src[i + 1] !== ";") {
|
|
100
|
+
const boundary = skipTrivia(i + 1);
|
|
101
|
+
if (isTerminatorBoundary(boundary)) {
|
|
102
|
+
copyVerbatim(i, boundary);
|
|
103
|
+
appendSynthetic(IMPLICIT_NULL_SYMBOL, boundary);
|
|
104
|
+
i = boundary;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
66
108
|
// Two or more spaces between word chars on same line → InvisibleTimes
|
|
67
109
|
if (src[i] === " " && src[i + 1] === " ") {
|
|
68
110
|
// Check previous meaningful char is a word char
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const IMPLICIT_NULL_SYMBOL = "$$PrettierWolframImplicitNull$$";
|
|
Binary file
|