@cube-drone/marquee-parser 0.7.2 → 0.9.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/dist/blocks.js +82 -16
- package/package.json +1 -1
- package/src/blocks.ts +85 -18
package/dist/blocks.js
CHANGED
|
@@ -46,6 +46,53 @@ function mark(T, cur, node, startLine, endLine) {
|
|
|
46
46
|
}
|
|
47
47
|
return node;
|
|
48
48
|
}
|
|
49
|
+
/** mark() for a node that ends mid-line: a leaf directive that shares its
|
|
50
|
+
* line with what follows its closer. */
|
|
51
|
+
function markTo(T, cur, node, startLine, endOffset) {
|
|
52
|
+
if (T !== null && cur.starts !== null) {
|
|
53
|
+
T.spans.set(node, { start: lineStartOf(cur, startLine), end: endOffset });
|
|
54
|
+
}
|
|
55
|
+
return node;
|
|
56
|
+
}
|
|
57
|
+
/** The leaf closer on a directive open line: the first `:::` after the
|
|
58
|
+
* name that sits outside a quoted value and is followed by end-of-line or
|
|
59
|
+
* whitespace (spec: Directives). `closerEnd` is the index just past it;
|
|
60
|
+
* `restStart` is where non-blank text after it begins, or null. Null for a
|
|
61
|
+
* close line or a line with no delimited closer (a container open). The
|
|
62
|
+
* cut is lexical - it happens before the head is judged well-formed - so
|
|
63
|
+
* the words after a closer are never lost to a bad attribute. */
|
|
64
|
+
function leafCut(line) {
|
|
65
|
+
const rest = line.slice(3);
|
|
66
|
+
if (rest === "" || rest.startsWith(" ") || rest.startsWith("\t")) {
|
|
67
|
+
return null; // a close line
|
|
68
|
+
}
|
|
69
|
+
let quoted = false;
|
|
70
|
+
for (let i = 3; i < line.length; i += 1) {
|
|
71
|
+
const c = line[i];
|
|
72
|
+
if (quoted) {
|
|
73
|
+
if (c === "\\") {
|
|
74
|
+
i += 1;
|
|
75
|
+
}
|
|
76
|
+
else if (c === '"') {
|
|
77
|
+
quoted = false;
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (c === '"') {
|
|
82
|
+
quoted = true;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (c === ":" && line.startsWith(":::", i)) {
|
|
86
|
+
const after = line[i + 3];
|
|
87
|
+
if (after === undefined || after === " " || after === "\t") {
|
|
88
|
+
const closerEnd = i + 3;
|
|
89
|
+
const trimmed = line.slice(closerEnd).replace(/^[ \t]+/, "");
|
|
90
|
+
return { closerEnd, restStart: trimmed === "" ? null : line.length - trimmed.length };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
49
96
|
/** Build the logical inline text (segments joined by real source newlines)
|
|
50
97
|
* plus the code-point -> source-offset map the inline parser records from. */
|
|
51
98
|
function inlineCtx(T, segs) {
|
|
@@ -90,7 +137,14 @@ function parseContainer(cur, ctx, openDir, T) {
|
|
|
90
137
|
if (line.startsWith(":::")) {
|
|
91
138
|
const li = cur.pos;
|
|
92
139
|
cur.pos += 1;
|
|
93
|
-
|
|
140
|
+
// A leaf's closer ends the directive line; whatever follows it is
|
|
141
|
+
// parsed as if it began on the next line (spec: Directives). The
|
|
142
|
+
// head is judged on its own, and the remainder is re-injected below.
|
|
143
|
+
const cut = leafCut(line);
|
|
144
|
+
const head = cut === null ? line : line.slice(0, cut.closerEnd);
|
|
145
|
+
const headEnd = () => cut === null ? lineEndOf(cur, li) : lineStartOf(cur, li) + cut.closerEnd;
|
|
146
|
+
const one = (node) => T !== null && cur.starts !== null ? markTo(T, cur, node, li, headEnd()) : node;
|
|
147
|
+
const dir = classifyDirective(head);
|
|
94
148
|
if (dir.kind === "close") {
|
|
95
149
|
if (openDir !== null && dir.name === null) {
|
|
96
150
|
return out;
|
|
@@ -98,29 +152,41 @@ function parseContainer(cur, ctx, openDir, T) {
|
|
|
98
152
|
if (openDir !== null && dir.name === openDir) {
|
|
99
153
|
return out;
|
|
100
154
|
}
|
|
101
|
-
out.push(
|
|
155
|
+
out.push(one(invalid(openDir !== null ? "mismatched_close" : "stray_close")));
|
|
102
156
|
}
|
|
103
157
|
else if (dir.kind === "open") {
|
|
104
158
|
if (ctx.dirDepth >= MAX_DIRECTIVE_DEPTH) {
|
|
105
|
-
out.push(
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
const attrs = parseAttrs(dir.attrsSrc);
|
|
109
|
-
if (!attrs.ok) {
|
|
110
|
-
out.push(mark(T, cur, invalid(attrs.reason), li, li));
|
|
159
|
+
out.push(one(invalid("depth_exceeded")));
|
|
111
160
|
}
|
|
112
161
|
else {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
162
|
+
const attrs = parseAttrs(dir.attrsSrc);
|
|
163
|
+
if (!attrs.ok) {
|
|
164
|
+
out.push(one(invalid(attrs.reason)));
|
|
165
|
+
}
|
|
166
|
+
else if (dir.leaf) {
|
|
167
|
+
out.push(one({ type: "directive", name: dir.name, attrs: attrs.attrs, children: [] }));
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
const children = parseContainer(cur, { ...ctx, dirDepth: ctx.dirDepth + 1 }, dir.name, T);
|
|
171
|
+
// A container's close line (or EOF auto-close) has been consumed
|
|
172
|
+
// by the child parse: the span runs to the last consumed line.
|
|
173
|
+
const endLi = Math.max(li, cur.pos - 1);
|
|
174
|
+
out.push(mark(T, cur, { type: "directive", name: dir.name, attrs: attrs.attrs, children }, li, endLi));
|
|
175
|
+
}
|
|
120
176
|
}
|
|
121
177
|
}
|
|
122
178
|
else {
|
|
123
|
-
out.push(
|
|
179
|
+
out.push(one(invalid(dir.reason)));
|
|
180
|
+
}
|
|
181
|
+
if (cut !== null && cut.restStart !== null) {
|
|
182
|
+
// The words after the closer become the current line, and the
|
|
183
|
+
// cursor steps back onto it: a leaf never recurses, so nothing has
|
|
184
|
+
// advanced past li. Positions follow the cut.
|
|
185
|
+
cur.lines[li] = line.slice(cut.restStart);
|
|
186
|
+
if (cur.starts !== null) {
|
|
187
|
+
cur.starts[li] = cur.starts[li] + cut.restStart;
|
|
188
|
+
}
|
|
189
|
+
cur.pos = li;
|
|
124
190
|
}
|
|
125
191
|
continue;
|
|
126
192
|
}
|
package/package.json
CHANGED
package/src/blocks.ts
CHANGED
|
@@ -80,6 +80,54 @@ function mark(T: PosTracker | null, cur: Cursor, node: Node, startLine: number,
|
|
|
80
80
|
return node;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/** mark() for a node that ends mid-line: a leaf directive that shares its
|
|
84
|
+
* line with what follows its closer. */
|
|
85
|
+
function markTo(T: PosTracker | null, cur: Cursor, node: Node, startLine: number, endOffset: number): Node {
|
|
86
|
+
if (T !== null && cur.starts !== null) {
|
|
87
|
+
T.spans.set(node, { start: lineStartOf(cur, startLine), end: endOffset });
|
|
88
|
+
}
|
|
89
|
+
return node;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The leaf closer on a directive open line: the first `:::` after the
|
|
93
|
+
* name that sits outside a quoted value and is followed by end-of-line or
|
|
94
|
+
* whitespace (spec: Directives). `closerEnd` is the index just past it;
|
|
95
|
+
* `restStart` is where non-blank text after it begins, or null. Null for a
|
|
96
|
+
* close line or a line with no delimited closer (a container open). The
|
|
97
|
+
* cut is lexical - it happens before the head is judged well-formed - so
|
|
98
|
+
* the words after a closer are never lost to a bad attribute. */
|
|
99
|
+
function leafCut(line: string): { closerEnd: number; restStart: number | null } | null {
|
|
100
|
+
const rest = line.slice(3);
|
|
101
|
+
if (rest === "" || rest.startsWith(" ") || rest.startsWith("\t")) {
|
|
102
|
+
return null; // a close line
|
|
103
|
+
}
|
|
104
|
+
let quoted = false;
|
|
105
|
+
for (let i = 3; i < line.length; i += 1) {
|
|
106
|
+
const c = line[i]!;
|
|
107
|
+
if (quoted) {
|
|
108
|
+
if (c === "\\") {
|
|
109
|
+
i += 1;
|
|
110
|
+
} else if (c === '"') {
|
|
111
|
+
quoted = false;
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (c === '"') {
|
|
116
|
+
quoted = true;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (c === ":" && line.startsWith(":::", i)) {
|
|
120
|
+
const after = line[i + 3];
|
|
121
|
+
if (after === undefined || after === " " || after === "\t") {
|
|
122
|
+
const closerEnd = i + 3;
|
|
123
|
+
const trimmed = line.slice(closerEnd).replace(/^[ \t]+/, "");
|
|
124
|
+
return { closerEnd, restStart: trimmed === "" ? null : line.length - trimmed.length };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
83
131
|
/** Build the logical inline text (segments joined by real source newlines)
|
|
84
132
|
* plus the code-point -> source-offset map the inline parser records from. */
|
|
85
133
|
function inlineCtx(
|
|
@@ -129,7 +177,16 @@ function parseContainer(cur: Cursor, ctx: Ctx, openDir: string | null, T: PosTra
|
|
|
129
177
|
if (line.startsWith(":::")) {
|
|
130
178
|
const li = cur.pos;
|
|
131
179
|
cur.pos += 1;
|
|
132
|
-
|
|
180
|
+
// A leaf's closer ends the directive line; whatever follows it is
|
|
181
|
+
// parsed as if it began on the next line (spec: Directives). The
|
|
182
|
+
// head is judged on its own, and the remainder is re-injected below.
|
|
183
|
+
const cut = leafCut(line);
|
|
184
|
+
const head = cut === null ? line : line.slice(0, cut.closerEnd);
|
|
185
|
+
const headEnd = (): number =>
|
|
186
|
+
cut === null ? lineEndOf(cur, li) : lineStartOf(cur, li) + cut.closerEnd;
|
|
187
|
+
const one = (node: Node): Node =>
|
|
188
|
+
T !== null && cur.starts !== null ? markTo(T, cur, node, li, headEnd()) : node;
|
|
189
|
+
const dir = classifyDirective(head);
|
|
133
190
|
if (dir.kind === "close") {
|
|
134
191
|
if (openDir !== null && dir.name === null) {
|
|
135
192
|
return out;
|
|
@@ -137,28 +194,38 @@ function parseContainer(cur: Cursor, ctx: Ctx, openDir: string | null, T: PosTra
|
|
|
137
194
|
if (openDir !== null && dir.name === openDir) {
|
|
138
195
|
return out;
|
|
139
196
|
}
|
|
140
|
-
out.push(
|
|
197
|
+
out.push(one(invalid(openDir !== null ? "mismatched_close" : "stray_close")));
|
|
141
198
|
} else if (dir.kind === "open") {
|
|
142
199
|
if (ctx.dirDepth >= MAX_DIRECTIVE_DEPTH) {
|
|
143
|
-
out.push(
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
const attrs = parseAttrs(dir.attrsSrc);
|
|
147
|
-
if (!attrs.ok) {
|
|
148
|
-
out.push(mark(T, cur, invalid(attrs.reason), li, li));
|
|
200
|
+
out.push(one(invalid("depth_exceeded")));
|
|
149
201
|
} else {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
202
|
+
const attrs = parseAttrs(dir.attrsSrc);
|
|
203
|
+
if (!attrs.ok) {
|
|
204
|
+
out.push(one(invalid(attrs.reason)));
|
|
205
|
+
} else if (dir.leaf) {
|
|
206
|
+
out.push(one({ type: "directive", name: dir.name, attrs: attrs.attrs, children: [] }));
|
|
207
|
+
} else {
|
|
208
|
+
const children = parseContainer(cur, { ...ctx, dirDepth: ctx.dirDepth + 1 }, dir.name, T);
|
|
209
|
+
// A container's close line (or EOF auto-close) has been consumed
|
|
210
|
+
// by the child parse: the span runs to the last consumed line.
|
|
211
|
+
const endLi = Math.max(li, cur.pos - 1);
|
|
212
|
+
out.push(
|
|
213
|
+
mark(T, cur, { type: "directive", name: dir.name, attrs: attrs.attrs, children }, li, endLi),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
159
216
|
}
|
|
160
217
|
} else {
|
|
161
|
-
out.push(
|
|
218
|
+
out.push(one(invalid(dir.reason)));
|
|
219
|
+
}
|
|
220
|
+
if (cut !== null && cut.restStart !== null) {
|
|
221
|
+
// The words after the closer become the current line, and the
|
|
222
|
+
// cursor steps back onto it: a leaf never recurses, so nothing has
|
|
223
|
+
// advanced past li. Positions follow the cut.
|
|
224
|
+
cur.lines[li] = line.slice(cut.restStart);
|
|
225
|
+
if (cur.starts !== null) {
|
|
226
|
+
cur.starts[li] = cur.starts[li]! + cut.restStart;
|
|
227
|
+
}
|
|
228
|
+
cur.pos = li;
|
|
162
229
|
}
|
|
163
230
|
continue;
|
|
164
231
|
}
|