codemirror-lang-raku 0.1.0 → 0.3.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/CHANGELOG.md +86 -0
- package/README.md +121 -48
- package/dist/index.cjs +1676 -301
- package/dist/index.js +1678 -303
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -1,59 +1,98 @@
|
|
|
1
1
|
import { ContextTracker, ExternalTokenizer, LRParser } from '@lezer/lr';
|
|
2
|
-
import {
|
|
3
|
-
import { syntaxTree, LRLanguage, indentNodeProp,
|
|
2
|
+
import { NodeWeakMap, parseMixed } from '@lezer/common';
|
|
3
|
+
import { syntaxTree, delimitedIndent, LRLanguage, indentNodeProp, foldNodeProp, foldInside, LanguageSupport } from '@codemirror/language';
|
|
4
4
|
import { tags, styleTags } from '@lezer/highlight';
|
|
5
|
+
import { Prec, EditorState } from '@codemirror/state';
|
|
5
6
|
|
|
6
7
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
8
|
+
const smiley = 201,
|
|
9
|
+
noSmiley = 202,
|
|
10
|
+
BlockComment = 1,
|
|
11
|
+
podStart = 2,
|
|
12
|
+
podEnd = 203,
|
|
13
|
+
podEndDirective = 3,
|
|
14
|
+
podText = 204,
|
|
15
|
+
PodDirective = 4,
|
|
16
|
+
PodHeading = 5,
|
|
17
|
+
PodStrong = 6,
|
|
18
|
+
PodEmphasis = 7,
|
|
19
|
+
PodCode = 8,
|
|
20
|
+
PodLink = 9,
|
|
21
|
+
PodFormat = 10,
|
|
22
|
+
rawHeredoc = 11,
|
|
23
|
+
interpolatingHeredoc = 12,
|
|
24
|
+
heredocStart = 205,
|
|
25
|
+
methodDot = 13,
|
|
26
|
+
MethodName = 14,
|
|
27
|
+
declaredName = 206,
|
|
28
|
+
declaredMethodName = 207,
|
|
29
|
+
Version = 15,
|
|
30
|
+
regexStart = 208,
|
|
31
|
+
regexBodyEnd = 209,
|
|
32
|
+
regexLitOpen = 210,
|
|
33
|
+
regexLitClose = 211,
|
|
34
|
+
regexText = 212,
|
|
35
|
+
CharacterClass = 16,
|
|
36
|
+
Assertion = 17,
|
|
37
|
+
regexQuote = 18,
|
|
38
|
+
regexOperator = 19,
|
|
39
|
+
regexComment = 20,
|
|
40
|
+
regexBlockComment = 21,
|
|
41
|
+
regexCapture = 22,
|
|
42
|
+
quoteContent = 213,
|
|
43
|
+
quoteNestOpen = 214,
|
|
44
|
+
quoteNestClose = 215,
|
|
45
|
+
quoteEnd = 216,
|
|
46
|
+
rawString = 217,
|
|
47
|
+
quoteStart = 218,
|
|
48
|
+
interpolatingWordList = 23,
|
|
49
|
+
regexLiteral = 24,
|
|
50
|
+
transliteration = 25,
|
|
51
|
+
plainName = 219,
|
|
52
|
+
wordOperator = 26,
|
|
53
|
+
multiName = 220,
|
|
54
|
+
operatorVariable = 27,
|
|
55
|
+
LineComment = 28,
|
|
56
|
+
DocComment = 29,
|
|
57
|
+
Pod = 31,
|
|
58
|
+
radixNumber = 32,
|
|
59
|
+
PackageName = 45,
|
|
60
|
+
RoutineName = 52,
|
|
61
|
+
methodRoutineName = 55,
|
|
62
|
+
multiRoutineName = 56,
|
|
63
|
+
RegexName = 61,
|
|
64
|
+
regexBody = 63,
|
|
65
|
+
VariableName = 65,
|
|
66
|
+
AttributeName = 66,
|
|
67
|
+
SpecialVariable = 67,
|
|
68
|
+
EnumName = 71,
|
|
69
|
+
SubsetName = 74,
|
|
70
|
+
ConstantName = 77,
|
|
71
|
+
Identifier = 79,
|
|
72
|
+
TypeName = 80,
|
|
73
|
+
Number = 81,
|
|
74
|
+
quotedString = 82,
|
|
75
|
+
NestedDelimiters = 83,
|
|
76
|
+
Interpolation = 84,
|
|
77
|
+
PairKey = 85,
|
|
78
|
+
multi = 96,
|
|
79
|
+
proto = 97,
|
|
80
|
+
only = 98,
|
|
81
|
+
self = 165,
|
|
82
|
+
True = 166,
|
|
83
|
+
False = 167,
|
|
84
|
+
Nil = 168,
|
|
85
|
+
Operator = 187,
|
|
86
|
+
Regex = 191,
|
|
87
|
+
Heredoc = 192,
|
|
88
|
+
StringLiteral = 193;
|
|
50
89
|
|
|
51
90
|
// Opening bracket -> closing bracket, for the delimiters of quotes and
|
|
52
91
|
// embedded comments. Raku accepts any Unicode bracket pair; this lists
|
|
53
92
|
// the ones that are likely to be typed.
|
|
54
|
-
const brackets = {};
|
|
93
|
+
const brackets$1 = {};
|
|
55
94
|
for (let pair of ["()", "[]", "{}", "<>", "«»", "「」", "『』", "()", "[]", "{}", "【】", "〈〉", "《》", "〔〕"])
|
|
56
|
-
brackets[pair.charCodeAt(0)] = pair.charCodeAt(1);
|
|
95
|
+
brackets$1[pair.charCodeAt(0)] = pair.charCodeAt(1);
|
|
57
96
|
function isAsciiLetter(ch) { return ch >= 97 /* Ch.a */ && ch <= 122 /* Ch.z */ || ch >= 65 /* Ch.A */ && ch <= 90 /* Ch.Z */; }
|
|
58
97
|
function isDigit(ch) { return ch >= 48 /* Ch._0 */ && ch <= 57 /* Ch._9 */; }
|
|
59
98
|
// The characters of the grammar's whitespace token.
|
|
@@ -116,41 +155,126 @@ function word(input, from, to) {
|
|
|
116
155
|
}
|
|
117
156
|
// A quote that is being read piece by piece because it interpolates.
|
|
118
157
|
class Quote {
|
|
119
|
-
constructor(parent, open, close, count,
|
|
120
|
-
//
|
|
121
|
-
|
|
158
|
+
constructor(parent, open, close, count, interpolates,
|
|
159
|
+
// Whether a backslash that does not start an escape still
|
|
160
|
+
// takes the delimiter after it out of play: q:c[a \] b]
|
|
161
|
+
escapes) {
|
|
122
162
|
this.parent = parent;
|
|
123
163
|
this.open = open;
|
|
124
164
|
this.close = close;
|
|
125
165
|
this.count = count;
|
|
126
|
-
this.
|
|
127
|
-
this.
|
|
166
|
+
this.interpolates = interpolates;
|
|
167
|
+
this.escapes = escapes;
|
|
168
|
+
let hash = parent ? parent.hash : 7;
|
|
169
|
+
for (let part of [open, count, interpolates, escapes ? 1 : 0])
|
|
170
|
+
hash = (hash * 31 + part) | 0;
|
|
171
|
+
this.hash = hash;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const noHeredocs = [];
|
|
175
|
+
// The quote that the text of a heredoc is read as. It has no
|
|
176
|
+
// delimiters, and ends where the text does.
|
|
177
|
+
const heredocQuote = new Quote(null, -1, -1, 1, 63 /* Interpolate.All */, true);
|
|
178
|
+
// The body of a regex declaration, or the inside of a regex literal,
|
|
179
|
+
// that is being read piece by piece.
|
|
180
|
+
class RegexBody {
|
|
181
|
+
constructor(// The body around the block of code that this one is in.
|
|
182
|
+
parent,
|
|
183
|
+
// How many braces are open in a block of code in the
|
|
184
|
+
// regex. Zero in the regex itself.
|
|
185
|
+
braces,
|
|
186
|
+
// For a regex literal, the position in the document of
|
|
187
|
+
// the delimiter that closes it. It is not part of the
|
|
188
|
+
// hash, which is to be the same before and after an edit
|
|
189
|
+
// that moves the literal.
|
|
190
|
+
literalEnd = -1) {
|
|
191
|
+
this.parent = parent;
|
|
192
|
+
this.braces = braces;
|
|
193
|
+
this.literalEnd = literalEnd;
|
|
194
|
+
this.hash = ((parent ? parent.hash * 31 : 0) + 7919 * (braces + 1) + (literalEnd < 0 ? 0 : 104729)) | 0;
|
|
195
|
+
}
|
|
196
|
+
withBraces(braces) { return new RegexBody(this.parent, braces, this.literalEnd); }
|
|
197
|
+
}
|
|
198
|
+
// A Pod block that is being read piece by piece.
|
|
199
|
+
class PodBlock {
|
|
200
|
+
constructor(kind,
|
|
201
|
+
// What follows `=begin`, which `=end` has to repeat.
|
|
202
|
+
name,
|
|
203
|
+
// Whether it holds code, in which formatting codes are not read.
|
|
204
|
+
code) {
|
|
205
|
+
this.kind = kind;
|
|
206
|
+
this.name = name;
|
|
207
|
+
this.code = code;
|
|
208
|
+
let hash = 101 + kind * 7 + (code ? 3 : 0);
|
|
209
|
+
for (let i = 0; i < name.length; i++)
|
|
210
|
+
hash = (hash * 31 + name.charCodeAt(i)) | 0;
|
|
211
|
+
this.hash = hash;
|
|
128
212
|
}
|
|
129
213
|
}
|
|
130
214
|
class Context {
|
|
131
|
-
constructor(mode, quote
|
|
215
|
+
constructor(mode, quote,
|
|
216
|
+
// The heredocs that were opened on the current line, whose
|
|
217
|
+
// text starts on the next one.
|
|
218
|
+
heredocs, pod,
|
|
219
|
+
// The body of a regex declaration that this is in.
|
|
220
|
+
regex, hash = -1) {
|
|
132
221
|
this.mode = mode;
|
|
133
222
|
this.quote = quote;
|
|
223
|
+
this.heredocs = heredocs;
|
|
224
|
+
this.pod = pod;
|
|
225
|
+
this.regex = regex;
|
|
226
|
+
if (hash < 0) {
|
|
227
|
+
hash = (quote ? quote.hash : 0) ^ (pod ? pod.hash : 0) ^ (regex ? regex.hash : 0);
|
|
228
|
+
for (let { terminator, interpolates } of heredocs) {
|
|
229
|
+
hash = (hash * 31 + (interpolates ? 19 : 17)) | 0;
|
|
230
|
+
for (let i = 0; i < terminator.length; i++)
|
|
231
|
+
hash = (hash * 31 + terminator.charCodeAt(i)) | 0;
|
|
232
|
+
}
|
|
233
|
+
// Negative values stand for "not worked out yet".
|
|
234
|
+
hash &= 0x7fffffff;
|
|
235
|
+
}
|
|
236
|
+
this.hash = hash;
|
|
237
|
+
}
|
|
238
|
+
withMode(mode) {
|
|
239
|
+
return mode == this.mode ? this : new Context(mode, this.quote, this.heredocs, this.pod, this.regex, this.hash);
|
|
240
|
+
}
|
|
241
|
+
withHeredocs(heredocs) {
|
|
242
|
+
return heredocs == this.heredocs ? this : new Context(this.mode, this.quote, heredocs, this.pod, this.regex);
|
|
243
|
+
}
|
|
244
|
+
withQuote(mode, quote, heredocs = this.heredocs) {
|
|
245
|
+
return new Context(mode, quote, heredocs, this.pod, this.regex);
|
|
246
|
+
}
|
|
247
|
+
withPod(pod) { return new Context(this.mode, this.quote, this.heredocs, pod, this.regex); }
|
|
248
|
+
withRegex(regex) {
|
|
249
|
+
return new Context(this.mode, this.quote, this.heredocs, this.pod, regex);
|
|
134
250
|
}
|
|
135
|
-
// An old node is only reused where this is the same as where it was
|
|
136
|
-
// made. The mode is left out: it changes with every token, which
|
|
137
|
-
// would rule out nearly all reuse, and `reuse` works it out again.
|
|
138
|
-
get hash() { return this.quote ? this.quote.hash : 0; }
|
|
139
|
-
withMode(mode) { return mode == this.mode ? this : new Context(mode, this.quote); }
|
|
140
251
|
}
|
|
252
|
+
// The nodes of a string: a quote, the token of a word list that
|
|
253
|
+
// interpolates, and the tree of its inside, which takes the place of
|
|
254
|
+
// that token.
|
|
255
|
+
const stringTerms = new Set([quotedString, interpolatingWordList, StringLiteral]);
|
|
141
256
|
// The mode after a token or a node, for those where the term settles
|
|
142
257
|
// it. Shifting a token and reusing a node both look here, so that they
|
|
143
258
|
// agree.
|
|
144
259
|
const termModes = new Map();
|
|
145
|
-
for (let term of [VariableName, AttributeName, SpecialVariable, Number, radixNumber, Version, MethodName,
|
|
146
|
-
PairKey, rawString, quoteEnd,
|
|
260
|
+
for (let term of [VariableName, AttributeName, SpecialVariable, operatorVariable, Number, radixNumber, Version, MethodName,
|
|
261
|
+
PairKey, rawString, quoteEnd, ...stringTerms, regexLiteral, transliteration, regexBody,
|
|
262
|
+
// The tree of the inside of a regex literal, which takes the
|
|
263
|
+
// place of the literal's token in a node that is reused.
|
|
264
|
+
Regex, self, True, False, Nil,
|
|
265
|
+
// The pieces of a regex. What follows them is more of
|
|
266
|
+
// the regex, or the brace that closes it.
|
|
267
|
+
regexText, CharacterClass, Assertion, regexQuote, regexOperator, regexComment,
|
|
268
|
+
regexBlockComment, regexCapture])
|
|
147
269
|
termModes.set(term, 1 /* Mode.AfterTerm */);
|
|
148
|
-
for (let term of [Identifier, TypeName, smiley, declaredName, declaredMethodName, PackageName, RoutineName,
|
|
149
|
-
methodRoutineName, RegexName, EnumName, SubsetName, ConstantName])
|
|
270
|
+
for (let term of [Identifier, TypeName, smiley, noSmiley, declaredName, declaredMethodName, PackageName, RoutineName,
|
|
271
|
+
methodRoutineName, multiName, multiRoutineName, RegexName, EnumName, SubsetName, ConstantName])
|
|
150
272
|
termModes.set(term, 2 /* Mode.AfterName */);
|
|
151
273
|
// The `}` of "{...}" in a string does not take a subscript.
|
|
152
|
-
for (let term of [quoteContent, Interpolation])
|
|
274
|
+
for (let term of [quoteContent, quoteNestOpen, quoteNestClose, NestedDelimiters, Interpolation])
|
|
153
275
|
termModes.set(term, 0 /* Mode.Term */);
|
|
276
|
+
for (let term of [multi, proto, only])
|
|
277
|
+
termModes.set(term, 4 /* Mode.AfterMulti */);
|
|
154
278
|
// The mode after an operator, from its first three characters. `after`
|
|
155
279
|
// is the mode before it.
|
|
156
280
|
function modeAfterOperator(first, second, third, after) {
|
|
@@ -165,7 +289,8 @@ function modeAfterOperator(first, second, third, after) {
|
|
|
165
289
|
}
|
|
166
290
|
// The mode after a token that the terms file has no name for, from its
|
|
167
291
|
// first character. Keywords and identifiers come through here alike;
|
|
168
|
-
// an identifier gets its mode when the Identifier node is reduced
|
|
292
|
+
// an identifier gets its mode when the Identifier node is reduced,
|
|
293
|
+
// from modeAfter, which also looks at how it is spelled.
|
|
169
294
|
function modeAfterChar(first) {
|
|
170
295
|
if (first == 41 /* Ch.ParenClose */ || first == 93 /* Ch.BracketClose */ || first == 36 /* Ch.Dollar */ || first == 64 /* Ch.At */)
|
|
171
296
|
return 1 /* Mode.AfterTerm */;
|
|
@@ -174,9 +299,20 @@ function modeAfterChar(first) {
|
|
|
174
299
|
return 2 /* Mode.AfterName */;
|
|
175
300
|
return first == 125 /* Ch.BraceClose */ ? 3 /* Mode.AfterBlock */ : 0 /* Mode.Term */;
|
|
176
301
|
}
|
|
302
|
+
// The built-in names that stand for a value and take no arguments. The
|
|
303
|
+
// constant `e` is left out: a routine or a variable of one's own is too
|
|
304
|
+
// often named that.
|
|
305
|
+
const termWords = /^(pi|π|tau|τ|now|time|rand)$/;
|
|
177
306
|
// The mode after the token of the given term at the input's position.
|
|
178
307
|
// `before` is the mode before it.
|
|
179
308
|
function modeAfter(term, input, offset, before) {
|
|
309
|
+
// A term that takes no arguments is followed by an operator: pi /2
|
|
310
|
+
if (term == Identifier) {
|
|
311
|
+
let end = nameEnd(input, offset);
|
|
312
|
+
// Most names are longer than these, and are not looked at further.
|
|
313
|
+
if (end - offset <= 4 && termWords.test(word(input, offset, end)))
|
|
314
|
+
return 1 /* Mode.AfterTerm */;
|
|
315
|
+
}
|
|
180
316
|
let mode = termModes.get(term);
|
|
181
317
|
if (mode != null)
|
|
182
318
|
return mode;
|
|
@@ -185,71 +321,196 @@ function modeAfter(term, input, offset, before) {
|
|
|
185
321
|
return modeAfterOperator(first, input.peek(offset + 1), input.peek(offset + 2), before);
|
|
186
322
|
return modeAfterChar(first);
|
|
187
323
|
}
|
|
188
|
-
|
|
324
|
+
// The context at the start of a parse. The tokens that start the parse
|
|
325
|
+
// of the inside of another token are only looked for while the context
|
|
326
|
+
// is still this one, which spares asking the parser whether they can
|
|
327
|
+
// be shifted at every other token.
|
|
328
|
+
const startContext = new Context(0 /* Mode.Term */, null, noHeredocs, null, null);
|
|
329
|
+
// The tokens of the text of a heredoc, and the tree of its inside,
|
|
330
|
+
// which takes the place of the token of one that interpolates.
|
|
331
|
+
const heredocTerms = new Set([rawHeredoc, interpolatingHeredoc, Heredoc]);
|
|
332
|
+
const skippedTerms = new Set([LineComment, DocComment, BlockComment, ...heredocTerms, Pod, podStart, podEnd, podEndDirective,
|
|
333
|
+
podText, PodDirective, PodHeading, PodStrong, PodEmphasis, PodCode, PodLink, PodFormat]);
|
|
334
|
+
// The heredoc that the quote at offset `at` opens, when it opens one.
|
|
335
|
+
// Its terminator is what the quote holds: END for q:to/END/
|
|
336
|
+
function heredocAt(input, at) {
|
|
337
|
+
let first = input.peek(at);
|
|
338
|
+
if (first != 113 /* Ch.q */ && first != 81 /* Ch.Q */)
|
|
339
|
+
return null;
|
|
340
|
+
let opening = readOpening(input, at);
|
|
341
|
+
if (!opening || !opening.heredoc)
|
|
342
|
+
return null;
|
|
343
|
+
let end = opening.start;
|
|
344
|
+
while (input.peek(end) != opening.close && input.peek(end) != 10 /* Ch.Newline */ && input.peek(end) >= 0)
|
|
345
|
+
end++;
|
|
346
|
+
// An empty one, as in the `q:to//` of a quote that is being typed,
|
|
347
|
+
// would end the heredoc at the next blank line.
|
|
348
|
+
let terminator = word(input, opening.start, end).trim();
|
|
349
|
+
return terminator ? { terminator, interpolates: opening.interpolates == 63 /* Interpolate.All */ } : null;
|
|
350
|
+
}
|
|
351
|
+
// `heredocs`, with the heredoc that the quote at offset `at` opens, if
|
|
352
|
+
// it opens one.
|
|
353
|
+
function withHeredocAt(heredocs, input, at) {
|
|
354
|
+
let heredoc = heredocAt(input, at);
|
|
355
|
+
return heredoc ? heredocs.concat(heredoc) : heredocs;
|
|
356
|
+
}
|
|
357
|
+
function hasNewline(input, from, to) {
|
|
358
|
+
for (let i = from; i < to; i++)
|
|
359
|
+
if (input.peek(i) == 10 /* Ch.Newline */)
|
|
360
|
+
return true;
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
// The heredocs that are open after a reused node, of which the input is
|
|
364
|
+
// at the start. As when tokens are shifted, a line break between tokens
|
|
365
|
+
// and the text of a heredoc close the ones before them. `inQuote` tells
|
|
366
|
+
// that the node is a run of pieces of a quote.
|
|
367
|
+
function heredocsAfter(node, input, before, inQuote) {
|
|
368
|
+
// Most reused nodes are single tokens.
|
|
369
|
+
if (!node.children.length && !stringTerms.has(node.type.id) && !heredocTerms.has(node.type.id))
|
|
370
|
+
return before;
|
|
371
|
+
let cursor = node.cursor(), found = [], closedAt = -1;
|
|
372
|
+
// Goes over the node at the cursor from its end, and tells whether
|
|
373
|
+
// the heredocs before some point in it are closed.
|
|
374
|
+
function scan(text = false) {
|
|
375
|
+
let type = cursor.type.id, { from, to } = cursor;
|
|
376
|
+
if (heredocTerms.has(type)) {
|
|
377
|
+
closedAt = to;
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
let isString = stringTerms.has(type);
|
|
381
|
+
if (isString) {
|
|
382
|
+
let heredoc = heredocAt(input, from);
|
|
383
|
+
if (heredoc)
|
|
384
|
+
found.push({ pos: from, heredoc });
|
|
385
|
+
}
|
|
386
|
+
// The tree of the inside of a regex literal or of a word list. It
|
|
387
|
+
// was one token when it was shifted, which opens no heredoc.
|
|
388
|
+
if (type == Regex || type == StringLiteral)
|
|
389
|
+
return false;
|
|
390
|
+
if (!cursor.lastChild())
|
|
391
|
+
return false;
|
|
392
|
+
// Between the pieces of a string lies its text, not whitespace.
|
|
393
|
+
let gaps = !isString && !text && type != NestedDelimiters;
|
|
394
|
+
for (let gapEnd = to;;) {
|
|
395
|
+
if (gaps && hasNewline(input, cursor.to, gapEnd)) {
|
|
396
|
+
closedAt = gapEnd;
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
if (scan())
|
|
400
|
+
break;
|
|
401
|
+
gapEnd = cursor.from;
|
|
402
|
+
if (!cursor.prevSibling()) {
|
|
403
|
+
if (gaps && hasNewline(input, from, gapEnd))
|
|
404
|
+
closedAt = gapEnd;
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
cursor.parent();
|
|
409
|
+
return closedAt >= 0;
|
|
410
|
+
}
|
|
411
|
+
// A piece on its own, such as an Interpolation, is a named node.
|
|
412
|
+
scan(inQuote && node.type.isAnonymous);
|
|
413
|
+
if (!found.length && closedAt < 0)
|
|
414
|
+
return before;
|
|
415
|
+
let after = found.filter(opener => opener.pos >= closedAt).sort((a, b) => a.pos - b.pos).map(opener => opener.heredoc);
|
|
416
|
+
return closedAt < 0 ? before.concat(after) : after.length ? after : noHeredocs;
|
|
417
|
+
}
|
|
418
|
+
// The last token in `node` that says what the mode after it is: one
|
|
419
|
+
// that is not a comment or Pod, which leave the mode as it was. A node
|
|
420
|
+
// that settles the mode by itself counts as a token. Null when the
|
|
421
|
+
// node holds nothing but comments.
|
|
422
|
+
function lastToken$1(node) {
|
|
423
|
+
if (termModes.has(node.type.id) || !node.lastChild)
|
|
424
|
+
return skippedTerms.has(node.type.id) ? null : node;
|
|
425
|
+
for (let child = node.lastChild; child; child = child.prevSibling) {
|
|
426
|
+
let found = skippedTerms.has(child.type.id) ? null : lastToken$1(child);
|
|
427
|
+
if (found)
|
|
428
|
+
return found;
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
189
432
|
const trackContext = new ContextTracker({
|
|
190
|
-
start:
|
|
433
|
+
start: startContext,
|
|
191
434
|
shift(context, term, _stack, input) {
|
|
435
|
+
if (term == rawHeredoc || term == interpolatingHeredoc)
|
|
436
|
+
return context.withHeredocs(noHeredocs);
|
|
437
|
+
// The text of a heredoc that interpolates is read by a parser of
|
|
438
|
+
// its own, as a quote without delimiters.
|
|
439
|
+
if (term == heredocStart)
|
|
440
|
+
return context.withQuote(0 /* Mode.Term */, heredocQuote);
|
|
441
|
+
if (term == podStart)
|
|
442
|
+
return context.withPod(readPodStart(input));
|
|
443
|
+
if (term == podEnd || term == podEndDirective)
|
|
444
|
+
return context.withPod(null);
|
|
192
445
|
if (skippedTerms.has(term))
|
|
193
446
|
return context;
|
|
447
|
+
let { regex } = context;
|
|
448
|
+
if (term == regexStart)
|
|
449
|
+
return context.withRegex(new RegexBody(regex, 0));
|
|
450
|
+
if (term == regexBodyEnd)
|
|
451
|
+
return context.withRegex(regex && regex.parent).withMode(termModes.get(regexBody));
|
|
452
|
+
// The inside of a regex literal is read by a parser of its own, of
|
|
453
|
+
// which these are the first and the last token. The closing
|
|
454
|
+
// delimiter is found the way termToken found it when it made the
|
|
455
|
+
// literal one token, so that the two agree on where the regex ends.
|
|
456
|
+
if (term == regexLitOpen) {
|
|
457
|
+
let opening = regexOpening(input), end = opening ? regexEnd(input, opening) : -1;
|
|
458
|
+
// Without one, which takes a parse that does not start from a
|
|
459
|
+
// literal, the regex runs to the end of what is parsed.
|
|
460
|
+
return context.withRegex(new RegexBody(null, 0, end < 0 ? Infinity : input.pos + end));
|
|
461
|
+
}
|
|
462
|
+
if (term == regexLitClose)
|
|
463
|
+
return context.withRegex(null);
|
|
464
|
+
// The braces of a block of code in a regex, and of the blocks in it.
|
|
465
|
+
// The tokens `{` and `}` have no term to go by.
|
|
466
|
+
if (regex && !termModes.has(term)) {
|
|
467
|
+
if (input.next == 123 /* Ch.BraceOpen */)
|
|
468
|
+
context = context.withRegex(regex.withBraces(regex.braces + 1));
|
|
469
|
+
else if (input.next == 125 /* Ch.BraceClose */ && regex.braces > 0)
|
|
470
|
+
context = context.withRegex(regex.withBraces(regex.braces - 1));
|
|
471
|
+
}
|
|
194
472
|
switch (term) {
|
|
195
473
|
case quoteStart: {
|
|
196
|
-
let opening = readOpening(input);
|
|
197
|
-
|
|
198
|
-
: context;
|
|
199
|
-
}
|
|
200
|
-
case quoteNestOpen:
|
|
201
|
-
case quoteNestClose: {
|
|
202
|
-
let quote = context.quote;
|
|
203
|
-
if (!quote)
|
|
474
|
+
let opening = wordListOpening(input, 0) || readOpening(input);
|
|
475
|
+
if (!opening)
|
|
204
476
|
return context;
|
|
205
|
-
let
|
|
206
|
-
return
|
|
477
|
+
let quote = new Quote(context.quote, opening.open, opening.close, opening.count, opening.interpolates, opening.escapes);
|
|
478
|
+
return context.withQuote(0 /* Mode.Term */, quote, withHeredocAt(context.heredocs, input, 0));
|
|
207
479
|
}
|
|
208
480
|
case quoteEnd:
|
|
209
|
-
return
|
|
481
|
+
return context.withQuote(1 /* Mode.AfterTerm */, context.quote && context.quote.parent);
|
|
210
482
|
}
|
|
211
483
|
// The whitespace token has no term to go by. It is the only token
|
|
212
|
-
// that starts with a space.
|
|
213
|
-
|
|
484
|
+
// that starts with a space. A line break in it leaves the heredocs
|
|
485
|
+
// that found no terminator behind.
|
|
486
|
+
if (!termModes.has(term) && isSpace(input.next)) {
|
|
487
|
+
if (!context.heredocs.length)
|
|
488
|
+
return context;
|
|
489
|
+
for (let pos = 0; isSpace(input.peek(pos)); pos++)
|
|
490
|
+
if (input.peek(pos) == 10 /* Ch.Newline */)
|
|
491
|
+
return context.withHeredocs(noHeredocs);
|
|
214
492
|
return context;
|
|
493
|
+
}
|
|
494
|
+
if (term == rawString)
|
|
495
|
+
context = context.withHeredocs(withHeredocAt(context.heredocs, input, 0));
|
|
215
496
|
return context.withMode(modeAfter(term, input, 0, context.mode));
|
|
216
497
|
},
|
|
217
|
-
reduce(context, term) {
|
|
218
|
-
|
|
498
|
+
reduce(context, term, _stack, input) {
|
|
499
|
+
// The input is at the start of the node that is reduced.
|
|
500
|
+
return term == Identifier || term == Interpolation ? context.withMode(modeAfter(term, input, 0, context.mode))
|
|
501
|
+
: context;
|
|
219
502
|
},
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
let term;
|
|
230
|
-
if (last instanceof Tree) {
|
|
231
|
-
term = last.type.id;
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
// A buffer holds its nodes in pre-order, as type, start, end and
|
|
235
|
-
// the index after the node's last descendant.
|
|
236
|
-
let { buffer } = last, index = 0;
|
|
237
|
-
for (let from = 0, to = buffer.length;;) {
|
|
238
|
-
index = from;
|
|
239
|
-
while (buffer[index + 3] < to)
|
|
240
|
-
index = buffer[index + 3];
|
|
241
|
-
from = index + 4;
|
|
242
|
-
to = buffer[index + 3];
|
|
243
|
-
if (from == to || termModes.has(buffer[index]))
|
|
244
|
-
break;
|
|
245
|
-
}
|
|
246
|
-
term = buffer[index];
|
|
247
|
-
offset += buffer[index + 1];
|
|
248
|
-
}
|
|
249
|
-
if (skippedTerms.has(term))
|
|
250
|
-
return context;
|
|
503
|
+
// A node that is reused leaves the quote and the regex of the context
|
|
504
|
+
// as they are: the grammar pairs the delimiters that nest in a quote
|
|
505
|
+
// (NestedDelimiters) and the braces of a block, so a node closes what
|
|
506
|
+
// it opens.
|
|
507
|
+
reuse(context, node, stack, input) {
|
|
508
|
+
let inQuote = context.quote != null && inQuoteText(stack);
|
|
509
|
+
context = context.withHeredocs(heredocsAfter(node, input, context.heredocs, inQuote));
|
|
510
|
+
// The node's positions count from its start, where the input is.
|
|
511
|
+
let last = lastToken$1(node.topNode);
|
|
251
512
|
// What comes before a trailing `++` in a reused node is a term.
|
|
252
|
-
return context.withMode(modeAfter(
|
|
513
|
+
return last ? context.withMode(modeAfter(last.type.id, input, last.from, 1 /* Mode.AfterTerm */)) : context;
|
|
253
514
|
},
|
|
254
515
|
hash: context => context.hash
|
|
255
516
|
});
|
|
@@ -257,7 +518,7 @@ function context(stack) { return stack.context; }
|
|
|
257
518
|
// Whether a token of `size` characters at the current position is where
|
|
258
519
|
// a term starts, as opposed to an infix operator.
|
|
259
520
|
function inTermPosition(input, mode, size) {
|
|
260
|
-
if (mode == 0 /* Mode.Term */)
|
|
521
|
+
if (mode == 0 /* Mode.Term */ || mode == 4 /* Mode.AfterMulti */)
|
|
261
522
|
return true;
|
|
262
523
|
if (mode == 1 /* Mode.AfterTerm */)
|
|
263
524
|
return false;
|
|
@@ -270,21 +531,28 @@ function inTermPosition(input, mode, size) {
|
|
|
270
531
|
// #`(( ... )), needs the same number of closing ones. Brackets of the
|
|
271
532
|
// same kind nest. An unclosed comment runs to the end of the input.
|
|
272
533
|
const blockComment = new ExternalTokenizer(input => {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
let open = input.peek(2), close = brackets[open];
|
|
276
|
-
if (close == null)
|
|
534
|
+
let end = embeddedCommentEnd(input, 0);
|
|
535
|
+
if (!end)
|
|
277
536
|
return;
|
|
278
|
-
let count = 1;
|
|
279
|
-
while (input.peek(2 + count) == open)
|
|
280
|
-
count++;
|
|
281
|
-
let end = rawEnd(input, 2 + count, { open, close, count, escapes: false });
|
|
282
537
|
if (end < 0)
|
|
283
538
|
skipToEnd(input);
|
|
284
539
|
else
|
|
285
540
|
input.advance(end);
|
|
286
541
|
input.acceptToken(BlockComment);
|
|
287
542
|
});
|
|
543
|
+
// The offset after the embedded comment that starts at offset `at`.
|
|
544
|
+
// Zero when none starts there, and -1 when it is not closed.
|
|
545
|
+
function embeddedCommentEnd(input, at) {
|
|
546
|
+
if (input.peek(at) != 35 /* Ch.Hash */ || input.peek(at + 1) != 96 /* Ch.Backtick */)
|
|
547
|
+
return 0;
|
|
548
|
+
let open = input.peek(at + 2), close = brackets$1[open];
|
|
549
|
+
if (close == null)
|
|
550
|
+
return 0;
|
|
551
|
+
let count = 1;
|
|
552
|
+
while (input.peek(at + 2 + count) == open)
|
|
553
|
+
count++;
|
|
554
|
+
return rawEnd(input, at + 2 + count, { open, close, count, escapes: false });
|
|
555
|
+
}
|
|
288
556
|
function isBlank(ch) { return ch == 32 /* Ch.Space */ || ch == 9 /* Ch.Tab */ || ch == 13 /* Ch.Return */; }
|
|
289
557
|
// The offset of the first character at or after `pos` that is not a blank.
|
|
290
558
|
function blanksEnd(input, pos) {
|
|
@@ -292,6 +560,18 @@ function blanksEnd(input, pos) {
|
|
|
292
560
|
pos++;
|
|
293
561
|
return pos;
|
|
294
562
|
}
|
|
563
|
+
// How far a token looks over the whitespace after it, to see what
|
|
564
|
+
// follows. A token must not look far past its end: see the note on
|
|
565
|
+
// lookahead in AGENTS.md.
|
|
566
|
+
const maxGap = 20;
|
|
567
|
+
// The offset of the first character at or after `pos` that is not a
|
|
568
|
+
// blank, or not whitespace when `lines` is set, going over at most
|
|
569
|
+
// maxGap characters.
|
|
570
|
+
function gapEnd(input, pos, lines = false) {
|
|
571
|
+
for (let end = pos + maxGap; pos < end && (lines ? isSpace : isBlank)(input.peek(pos));)
|
|
572
|
+
pos++;
|
|
573
|
+
return pos;
|
|
574
|
+
}
|
|
295
575
|
// Whether only blanks separate the current position from the start of its line.
|
|
296
576
|
function atLineStart(input) {
|
|
297
577
|
for (let i = -1;; i--) {
|
|
@@ -321,121 +601,263 @@ function skipBlanks(input) {
|
|
|
321
601
|
// Skips blanks, and tells whether the rest of the line starts with `text`.
|
|
322
602
|
function lineStartsWith(input, text) {
|
|
323
603
|
skipBlanks(input);
|
|
604
|
+
return startsWith(input, 0, text);
|
|
605
|
+
}
|
|
606
|
+
// Whether the text from offset `pos` starts with `text`, as a whole name.
|
|
607
|
+
function startsWith(input, pos, text) {
|
|
324
608
|
for (let i = 0; i < text.length; i++)
|
|
325
|
-
if (input.peek(i) != text.charCodeAt(i))
|
|
609
|
+
if (input.peek(pos + i) != text.charCodeAt(i))
|
|
326
610
|
return false;
|
|
327
|
-
return !continuesName(input, text.length);
|
|
611
|
+
return !continuesName(input, pos + text.length);
|
|
328
612
|
}
|
|
329
613
|
const podDirective = /^(begin|for|end|finish|head\d*|item\d*|para|code|input|output|defn|comment|table|pod|rakudoc|config|alias|nested|data|[A-Z]{2,})$/;
|
|
330
|
-
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
|
|
334
|
-
const podToken = new ExternalTokenizer(input => {
|
|
614
|
+
const podCodeBlock = /^(code|input|output)$/;
|
|
615
|
+
// The offset after the directive that starts at the current position,
|
|
616
|
+
// as in `=begin` or `=head1`, or -1 when there is none.
|
|
617
|
+
function podDirectiveEnd(input) {
|
|
335
618
|
if (input.next != 61 /* Ch.Equals */ || !isAsciiLetter(input.peek(1)) || !atLineStart(input))
|
|
336
|
-
return;
|
|
619
|
+
return -1;
|
|
337
620
|
let end = 1;
|
|
338
621
|
while (isAsciiLetter(input.peek(end)) || isDigit(input.peek(end)))
|
|
339
622
|
end++;
|
|
340
623
|
let directive = word(input, 1, end);
|
|
341
624
|
if (!podDirective.test(directive) || continuesName(input, end))
|
|
342
|
-
return;
|
|
625
|
+
return -1;
|
|
343
626
|
// A semantic block such as =TITLE has to start in the first column.
|
|
344
627
|
// An indented `=FOO + 1` continues an assignment.
|
|
345
628
|
if (directive.charCodeAt(0) <= 90 /* Ch.Z */ && input.peek(-1) >= 0 && input.peek(-1) != 10 /* Ch.Newline */)
|
|
629
|
+
return -1;
|
|
630
|
+
return end;
|
|
631
|
+
}
|
|
632
|
+
// The offset after the name that goes with a directive which ends at
|
|
633
|
+
// offset `end`: `=begin pod`, `=for comment`, `=end code`. The other
|
|
634
|
+
// directives have none.
|
|
635
|
+
function podNameEnd(input, end) {
|
|
636
|
+
if (!/^(begin|for|end)$/.test(word(input, 1, end)))
|
|
637
|
+
return end;
|
|
638
|
+
let name = nameEnd(input, blanksEnd(input, end));
|
|
639
|
+
return name < 0 ? end : name;
|
|
640
|
+
}
|
|
641
|
+
// The block that the directive at the current position starts.
|
|
642
|
+
// =begin name ... =end name delimited, can hold blocks of other names
|
|
643
|
+
// =finish the rest of the file
|
|
644
|
+
// =for name, =head1, ... up to the next blank line
|
|
645
|
+
function readPodStart(input) {
|
|
646
|
+
let end = podDirectiveEnd(input), directive = word(input, 1, end);
|
|
647
|
+
if (directive == "finish")
|
|
648
|
+
return new PodBlock(2 /* PodKind.Finish */, "", false);
|
|
649
|
+
let nameStart = blanksEnd(input, end), name = word(input, nameStart, Math.max(nameStart, nameEnd(input, nameStart)));
|
|
650
|
+
if (directive == "begin")
|
|
651
|
+
return new PodBlock(0 /* PodKind.Delimited */, name, podCodeBlock.test(name));
|
|
652
|
+
return new PodBlock(1 /* PodKind.Paragraph */, "", podCodeBlock.test(directive == "for" ? name : directive));
|
|
653
|
+
}
|
|
654
|
+
// Whether the current position is right after a `=head` directive,
|
|
655
|
+
// where the text of the heading starts.
|
|
656
|
+
function atHeadingText(input) {
|
|
657
|
+
let pos = -1;
|
|
658
|
+
while (isDigit(input.peek(pos)))
|
|
659
|
+
pos--;
|
|
660
|
+
for (let i = 0; i < 5; i++)
|
|
661
|
+
if (input.peek(pos - i) != "=head".charCodeAt(4 - i))
|
|
662
|
+
return false;
|
|
663
|
+
for (pos -= 5; isBlank(input.peek(pos)); pos--) { }
|
|
664
|
+
return input.peek(pos) < 0 || input.peek(pos) == 10 /* Ch.Newline */;
|
|
665
|
+
}
|
|
666
|
+
// Whether a formatting code starts at offset `pos`: B<, C<<, L«
|
|
667
|
+
function isFormattingCode(input, pos) {
|
|
668
|
+
let ch = input.peek(pos), after = input.peek(pos + 1), before = input.peek(pos - 1);
|
|
669
|
+
return ch >= 65 /* Ch.A */ && ch <= 90 /* Ch.Z */ && (after == 60 /* Ch.Less */ || after == 171 /* Ch.GuillemetOpen */) &&
|
|
670
|
+
!isAsciiLetter(before) && !isDigit(before);
|
|
671
|
+
}
|
|
672
|
+
// The offset after the formatting code at the current position. When
|
|
673
|
+
// the code is not closed in its paragraph, this is the negated offset
|
|
674
|
+
// of the end of the text that was gone over to find that out.
|
|
675
|
+
function formattingCodeEnd(input) {
|
|
676
|
+
let open = input.peek(1), close = brackets$1[open], count = 1;
|
|
677
|
+
while (open == 60 /* Ch.Less */ && input.peek(1 + count) == open)
|
|
678
|
+
count++;
|
|
679
|
+
for (let pos = 1 + count, depth = 1;; pos++) {
|
|
680
|
+
let ch = input.peek(pos);
|
|
681
|
+
if (ch < 0)
|
|
682
|
+
return -pos;
|
|
683
|
+
if (ch == 10 /* Ch.Newline */) {
|
|
684
|
+
// A blank line or a directive ends the paragraph.
|
|
685
|
+
let next = blanksEnd(input, pos + 1), after = input.peek(next);
|
|
686
|
+
if (after < 0 || after == 10 /* Ch.Newline */ || after == 61 /* Ch.Equals */ && isAsciiLetter(input.peek(next + 1)))
|
|
687
|
+
return -pos;
|
|
688
|
+
}
|
|
689
|
+
else if (ch == close && repeats(input, pos, close, count)) {
|
|
690
|
+
pos += count - 1;
|
|
691
|
+
if (--depth == 0)
|
|
692
|
+
return pos + 1;
|
|
693
|
+
}
|
|
694
|
+
else if (ch == open && count == 1) {
|
|
695
|
+
// C<a < b> is not closed by the first `>` when another `<` came before it.
|
|
696
|
+
depth++;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
const formattingTerms = { 66: PodStrong, 73: PodEmphasis, 67: PodCode, 76: PodLink };
|
|
701
|
+
// The pieces of a Pod block, which starts with a `=directive` at the
|
|
702
|
+
// start of a line. What it is, and with that where it ends, is kept in
|
|
703
|
+
// the context.
|
|
704
|
+
const podToken = new ExternalTokenizer((input, stack) => {
|
|
705
|
+
let { pod } = context(stack);
|
|
706
|
+
if (!pod) {
|
|
707
|
+
let end = podDirectiveEnd(input);
|
|
708
|
+
if (end > 0)
|
|
709
|
+
input.acceptToken(podStart, podNameEnd(input, end));
|
|
346
710
|
return;
|
|
347
|
-
input.advance(end);
|
|
348
|
-
if (directive == "finish") {
|
|
349
|
-
skipToEnd(input);
|
|
350
711
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
input.
|
|
359
|
-
if (!lineStartsWith(input, "=end"))
|
|
360
|
-
continue;
|
|
361
|
-
input.advance(4);
|
|
362
|
-
if (lineStartsWith(input, name)) {
|
|
363
|
-
skipLine(input);
|
|
364
|
-
break;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
712
|
+
let next = input.next;
|
|
713
|
+
if (next < 0)
|
|
714
|
+
return input.acceptToken(podEnd);
|
|
715
|
+
if (next == 10 /* Ch.Newline */ && pod.kind == 1 /* PodKind.Paragraph */) {
|
|
716
|
+
// Ends before a blank line or another directive.
|
|
717
|
+
let pos = blanksEnd(input, 1), ch = input.peek(pos);
|
|
718
|
+
if (ch == 10 /* Ch.Newline */ || ch < 0 || ch == 61 /* Ch.Equals */ && isAsciiLetter(input.peek(pos + 1)))
|
|
719
|
+
return input.acceptToken(podEnd);
|
|
367
720
|
}
|
|
368
|
-
|
|
369
|
-
|
|
721
|
+
let directive = pod.kind == 0 /* PodKind.Delimited */ ? podDirectiveEnd(input) : -1;
|
|
722
|
+
if (directive > 0) {
|
|
723
|
+
if (directive == 4 && startsWith(input, 1, "end") && startsWith(input, blanksEnd(input, 4), pod.name)) {
|
|
370
724
|
skipLine(input);
|
|
371
|
-
|
|
372
|
-
break;
|
|
373
|
-
// Stop before a blank line or another directive.
|
|
374
|
-
let i = blanksEnd(input, 1), ch = input.peek(i);
|
|
375
|
-
if (ch == 10 /* Ch.Newline */ || ch < 0 || ch == 61 /* Ch.Equals */ && isAsciiLetter(input.peek(i + 1)))
|
|
376
|
-
break;
|
|
377
|
-
input.advance();
|
|
725
|
+
return input.acceptToken(podEndDirective);
|
|
378
726
|
}
|
|
727
|
+
return input.acceptToken(PodDirective, podNameEnd(input, directive));
|
|
728
|
+
}
|
|
729
|
+
if (atHeadingText(input) && !isLineEnd(input, 0)) {
|
|
730
|
+
skipLine(input);
|
|
731
|
+
return input.acceptToken(PodHeading);
|
|
379
732
|
}
|
|
380
|
-
|
|
733
|
+
if (!pod.code && isFormattingCode(input, 0)) {
|
|
734
|
+
let end = formattingCodeEnd(input);
|
|
735
|
+
if (end > 0)
|
|
736
|
+
return input.acceptToken(formattingTerms[next] || PodFormat, end);
|
|
737
|
+
// What looked like a code is text. The text that was gone over is
|
|
738
|
+
// made one token, so that no token has looked beyond its own end.
|
|
739
|
+
// A token that does keeps the tokens before it from being reused.
|
|
740
|
+
return input.acceptToken(podText, -end);
|
|
741
|
+
}
|
|
742
|
+
// Text, up to where one of the above may apply.
|
|
743
|
+
for (;;) {
|
|
744
|
+
input.advance();
|
|
745
|
+
let ch = input.next;
|
|
746
|
+
if (ch < 0 || ch == 10 /* Ch.Newline */ || !pod.code && isFormattingCode(input, 0))
|
|
747
|
+
break;
|
|
748
|
+
// A directive can only follow the blanks at the start of a line.
|
|
749
|
+
if (ch == 61 /* Ch.Equals */ && atLineStart(input))
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
input.acceptToken(podText);
|
|
381
753
|
});
|
|
382
754
|
const quoteWords = {
|
|
383
755
|
q: "raw", Q: "raw", qw: "raw", qww: "raw", qx: "raw", Qw: "raw", Qww: "raw", Qx: "raw",
|
|
384
756
|
qq: "interpolating", qqw: "interpolating", qqww: "interpolating", qqx: "interpolating",
|
|
385
757
|
rx: "regex", m: "regex", ms: "regex",
|
|
386
|
-
s: "substitution", ss: "substitution", S: "substitution",
|
|
758
|
+
s: "substitution", ss: "substitution", S: "substitution",
|
|
759
|
+
// Like a substitution, but its first part is not a regex.
|
|
760
|
+
tr: "transliteration", TR: "transliteration"
|
|
387
761
|
};
|
|
762
|
+
// The kinds of interpolation that the adverbs of a quote switch.
|
|
763
|
+
const interpolationAdverbs = new Map([
|
|
764
|
+
["c", 1 /* Interpolate.Closure */], ["closure", 1 /* Interpolate.Closure */],
|
|
765
|
+
["s", 2 /* Interpolate.Scalar */], ["scalar", 2 /* Interpolate.Scalar */],
|
|
766
|
+
["a", 4 /* Interpolate.Array */], ["array", 4 /* Interpolate.Array */],
|
|
767
|
+
["h", 8 /* Interpolate.Hash */], ["hash", 8 /* Interpolate.Hash */],
|
|
768
|
+
["f", 16 /* Interpolate.Function */], ["function", 16 /* Interpolate.Function */],
|
|
769
|
+
["b", 32 /* Interpolate.Backslash */], ["backslash", 32 /* Interpolate.Backslash */],
|
|
770
|
+
["qq", 63 /* Interpolate.All */], ["double", 63 /* Interpolate.All */]
|
|
771
|
+
]);
|
|
772
|
+
// The opening of a word list that interpolates, at offset `at`: << or «
|
|
773
|
+
function wordListOpening(input, at) {
|
|
774
|
+
let open = input.peek(at), count = open == 60 /* Ch.Less */ ? 2 : 1;
|
|
775
|
+
if (open != 171 /* Ch.GuillemetOpen */ && !(open == 60 /* Ch.Less */ && input.peek(at + 1) == 60 /* Ch.Less */))
|
|
776
|
+
return null;
|
|
777
|
+
return { open, close: brackets$1[open], count, start: at + count, kind: "interpolating",
|
|
778
|
+
interpolates: 63 /* Interpolate.All */, escapes: true };
|
|
779
|
+
}
|
|
780
|
+
// The opening of a quote that starts with a quote character at offset `at`.
|
|
781
|
+
function quoteCharOpening(open, close, at, interpolates, escapes = true) {
|
|
782
|
+
return { open, close, count: 1, start: at + 1, kind: interpolates ? "interpolating" : "raw", interpolates, escapes };
|
|
783
|
+
}
|
|
388
784
|
// The delimiters, other than brackets, that are accepted after a quote word.
|
|
389
785
|
function isQuoteDelimiter(ch) {
|
|
390
786
|
return ch == 47 /* Ch.Slash */ || ch == 33 /* Ch.Bang */ || ch == 124 /* Ch.Pipe */ || ch == 126 /* Ch.Tilde */ || ch == 94 /* Ch.Caret */ ||
|
|
391
787
|
ch == 37 /* Ch.Percent */ || ch == 64 /* Ch.At */ || ch == 34 /* Ch.DoubleQuote */ || ch == 39 /* Ch.Apostrophe */;
|
|
392
788
|
}
|
|
393
|
-
// Reads the start of a quote at
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
789
|
+
// Reads the start of a quote at offset `at`: a quote character, or a
|
|
790
|
+
// quote word (q, qq, m, s, ...) with its adverbs and its delimiter.
|
|
791
|
+
// The offsets in the result count from the current position.
|
|
792
|
+
function readOpening(input, at = 0) {
|
|
793
|
+
let next = input.peek(at);
|
|
397
794
|
if (next == 34 /* Ch.DoubleQuote */)
|
|
398
|
-
return
|
|
795
|
+
return quoteCharOpening(next, next, at, 63 /* Interpolate.All */);
|
|
399
796
|
if (next == 39 /* Ch.Apostrophe */)
|
|
400
|
-
return
|
|
797
|
+
return quoteCharOpening(next, next, at, 0);
|
|
401
798
|
if (next == 0x201c /* “ */)
|
|
402
|
-
return
|
|
799
|
+
return quoteCharOpening(next, 0x201d, at, 63 /* Interpolate.All */);
|
|
403
800
|
if (next == 0x2018 /* ‘ */)
|
|
404
|
-
return
|
|
801
|
+
return quoteCharOpening(next, 0x2019, at, 0);
|
|
405
802
|
if (next == 0x300c /* 「 */)
|
|
406
|
-
return
|
|
803
|
+
return quoteCharOpening(next, 0x300d, at, 0, false);
|
|
407
804
|
if (!isAsciiLetter(next))
|
|
408
805
|
return null;
|
|
409
|
-
let pos = 1;
|
|
806
|
+
let pos = at + 1;
|
|
410
807
|
while (isAsciiLetter(input.peek(pos)))
|
|
411
808
|
pos++;
|
|
412
|
-
if (pos > 4)
|
|
809
|
+
if (pos - at > 4)
|
|
413
810
|
return null;
|
|
414
|
-
let name = word(input,
|
|
811
|
+
let name = word(input, at, pos), kind = quoteWords[name];
|
|
415
812
|
if (!kind)
|
|
416
813
|
return null;
|
|
417
|
-
// Adverbs: :w, :to, :g, :x(2)
|
|
418
|
-
|
|
814
|
+
// Adverbs: :w, :to, :g, :x(2). Those of a regex mean other things
|
|
815
|
+
// than those of a quote: m:s/a b/
|
|
816
|
+
let adverbs = false, heredoc = false, isQuote = kind == "raw" || kind == "interpolating";
|
|
817
|
+
let interpolates = kind == "interpolating" ? 63 /* Interpolate.All */ : 0;
|
|
818
|
+
// Whether a backslash escapes the delimiter, apart from the escapes
|
|
819
|
+
// of :b. That comes with q and its :q adverb: Q and qq:!b have none.
|
|
820
|
+
let escapes = isQuote ? kind == "raw" && next == 113 /* Ch.q */ : true;
|
|
419
821
|
while (input.peek(pos) == 58 /* Ch.Colon */ && (isAsciiLetter(input.peek(pos + 1)) || input.peek(pos + 1) == 33 /* Ch.Bang */)) {
|
|
420
822
|
adverbs = true;
|
|
823
|
+
let negated = input.peek(pos + 1) == 33 /* Ch.Bang */, adverbStart = pos + (negated ? 2 : 1);
|
|
421
824
|
pos += 2;
|
|
422
825
|
while (isAsciiLetter(input.peek(pos)) || isDigit(input.peek(pos)))
|
|
423
826
|
pos++;
|
|
827
|
+
let adverb = word(input, adverbStart, pos);
|
|
828
|
+
// An argument that is False or 0 switches the adverb off: q:c(False)
|
|
424
829
|
if (input.peek(pos) == 40 /* Ch.ParenOpen */) {
|
|
830
|
+
let argStart = pos + 1;
|
|
425
831
|
while (input.peek(pos) != 41 /* Ch.ParenClose */) {
|
|
426
832
|
if (input.peek(pos) < 0 || input.peek(pos) == 10 /* Ch.Newline */)
|
|
427
833
|
return null;
|
|
428
834
|
pos++;
|
|
429
835
|
}
|
|
836
|
+
if (/^\s*(False|0)\s*$/.test(word(input, argStart, pos)))
|
|
837
|
+
negated = true;
|
|
430
838
|
pos++;
|
|
431
839
|
}
|
|
840
|
+
if (isQuote) {
|
|
841
|
+
let bits = interpolationAdverbs.get(adverb);
|
|
842
|
+
if (bits != null)
|
|
843
|
+
interpolates = negated ? interpolates & ~bits : interpolates | bits;
|
|
844
|
+
else if (!negated && (adverb == "q" || adverb == "single"))
|
|
845
|
+
escapes = true;
|
|
846
|
+
else if (!negated && (adverb == "to" || adverb == "heredoc"))
|
|
847
|
+
heredoc = true;
|
|
848
|
+
}
|
|
432
849
|
}
|
|
433
850
|
if (!adverbs && continuesName(input, pos))
|
|
434
851
|
return null;
|
|
435
|
-
// The delimiter
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
|
|
852
|
+
// The delimiter follows directly, or, for q, qq and Q, after blanks
|
|
853
|
+
// on the same line: q {...}. Not for m, s and the others: `s { ... }`
|
|
854
|
+
// and `m ($x)` are more often calls of a routine of one's own.
|
|
855
|
+
if (isQuote && isBlank(input.peek(pos))) {
|
|
856
|
+
let bracket = gapEnd(input, pos);
|
|
857
|
+
if (brackets$1[input.peek(bracket)] != null && input.peek(bracket) != 40 /* Ch.ParenOpen */)
|
|
858
|
+
pos = bracket;
|
|
859
|
+
}
|
|
860
|
+
let open = input.peek(pos), close = brackets$1[open];
|
|
439
861
|
if (close == null) {
|
|
440
862
|
if (!isQuoteDelimiter(open))
|
|
441
863
|
return null;
|
|
@@ -448,7 +870,11 @@ function readOpening(input) {
|
|
|
448
870
|
if (close != open)
|
|
449
871
|
while (input.peek(pos + count) == open)
|
|
450
872
|
count++;
|
|
451
|
-
|
|
873
|
+
if (isQuote)
|
|
874
|
+
kind = interpolates ? "interpolating" : "raw";
|
|
875
|
+
if (interpolates & 32 /* Interpolate.Backslash */)
|
|
876
|
+
escapes = true;
|
|
877
|
+
return { open, close, count, start: pos + count, kind, interpolates, escapes, heredoc };
|
|
452
878
|
}
|
|
453
879
|
// The offset after the delimiter that closes a quote whose content
|
|
454
880
|
// starts at `pos`, or -1 when it is not closed.
|
|
@@ -491,7 +917,7 @@ function quotedEnd(input, pos) {
|
|
|
491
917
|
// The offset of the delimiter that closes a regex whose content starts
|
|
492
918
|
// at `pos`, or -1. Delimiters do not count inside quotes, character
|
|
493
919
|
// classes, comments and code blocks.
|
|
494
|
-
function regexEnd(input, pos, open, close, count) {
|
|
920
|
+
function regexEnd(input, { start: pos, open, close, count }) {
|
|
495
921
|
for (let depth = 1;;) {
|
|
496
922
|
let ch = input.peek(pos);
|
|
497
923
|
if (ch < 0)
|
|
@@ -539,13 +965,20 @@ function regexEnd(input, pos, open, close, count) {
|
|
|
539
965
|
break;
|
|
540
966
|
pos = more;
|
|
541
967
|
}
|
|
968
|
+
// The bracket that closes the class is not one of the regex: rx[ <[a]> ]
|
|
969
|
+
pos++;
|
|
542
970
|
}
|
|
543
971
|
else if (ch == 35 /* Ch.Hash */) {
|
|
544
|
-
|
|
545
|
-
|
|
972
|
+
// An embedded comment, or one that runs to the end of the line.
|
|
973
|
+
let end = embeddedCommentEnd(input, pos);
|
|
974
|
+
if (end > 0)
|
|
975
|
+
pos = end;
|
|
976
|
+
else
|
|
977
|
+
while (input.peek(pos) >= 0 && input.peek(pos) != 10 /* Ch.Newline */)
|
|
978
|
+
pos++;
|
|
546
979
|
}
|
|
547
980
|
else if (ch == 123 /* Ch.BraceOpen */ && close != 125 /* Ch.BraceClose */) {
|
|
548
|
-
let end = regexEnd(input, pos + 1, 123 /* Ch.BraceOpen */, 125 /* Ch.BraceClose */, 1);
|
|
981
|
+
let end = regexEnd(input, { start: pos + 1, open: 123 /* Ch.BraceOpen */, close: 125 /* Ch.BraceClose */, count: 1 });
|
|
549
982
|
pos = end < 0 ? pos + 1 : end + 1;
|
|
550
983
|
}
|
|
551
984
|
else {
|
|
@@ -555,25 +988,41 @@ function regexEnd(input, pos, open, close, count) {
|
|
|
555
988
|
}
|
|
556
989
|
// The offset after a regex or substitution, given its opening, or -1.
|
|
557
990
|
function regexTokenEnd(input, opening) {
|
|
558
|
-
let
|
|
559
|
-
let end = regexEnd(input, opening.start, open, close, count);
|
|
991
|
+
let end = regexEnd(input, opening);
|
|
560
992
|
if (end < 0)
|
|
561
993
|
return -1;
|
|
562
|
-
end += count;
|
|
994
|
+
end += opening.count;
|
|
563
995
|
// s/a/b/ has a second part. s{a} = 'b' does not.
|
|
564
|
-
if (opening.kind
|
|
996
|
+
if (opening.kind != "regex" && opening.open == opening.close)
|
|
565
997
|
end = rawEnd(input, end, opening);
|
|
566
998
|
return end;
|
|
567
999
|
}
|
|
1000
|
+
// The opening of a bare regex: /a/
|
|
1001
|
+
const slashOpening = { open: 47 /* Ch.Slash */, close: 47 /* Ch.Slash */, count: 1, start: 1, kind: "regex",
|
|
1002
|
+
interpolates: 0, escapes: true };
|
|
1003
|
+
// The opening of the regex literal at the current position. The token
|
|
1004
|
+
// that makes a literal one token, and the parse of its inside, both
|
|
1005
|
+
// find its closing delimiter from this, so that they agree.
|
|
1006
|
+
function regexOpening(input) {
|
|
1007
|
+
if (input.next == 47 /* Ch.Slash */)
|
|
1008
|
+
return slashOpening;
|
|
1009
|
+
let opening = readOpening(input);
|
|
1010
|
+
return opening && (opening.kind == "regex" || opening.kind == "substitution") ? opening : null;
|
|
1011
|
+
}
|
|
568
1012
|
// The offset after a word list that opens at the current position, or
|
|
569
1013
|
// -1. A list that spans lines has to hold plain words, so that a stray
|
|
570
1014
|
// `<` does not swallow the code up to some later `>`. `strict` is for
|
|
571
1015
|
// a subscript, which has to be on one line, and cannot hold what
|
|
572
1016
|
// would make it a comparison: `$a<5 && $b>3`.
|
|
1017
|
+
//
|
|
1018
|
+
// A list with double angles is rarely a stray operator, and may hold
|
|
1019
|
+
// code over a few lines. Not over more: one that is being typed would
|
|
1020
|
+
// swallow the code up to some later `>>`.
|
|
573
1021
|
function wordListEnd(input, strict) {
|
|
574
1022
|
let open = input.next, close = open == 171 /* Ch.GuillemetOpen */ ? 187 /* Ch.GuillemetClose */ : 62 /* Ch.Greater */;
|
|
575
1023
|
let count = open == 60 /* Ch.Less */ && input.peek(1) == 60 /* Ch.Less */ ? 2 : 1;
|
|
576
|
-
|
|
1024
|
+
let codeLines = open == 171 /* Ch.GuillemetOpen */ || count == 2 ? maxCodeWordListLines : 1;
|
|
1025
|
+
for (let pos = count, lines = 1, code = false;; pos++) {
|
|
577
1026
|
let ch = input.peek(pos);
|
|
578
1027
|
if (ch == close && repeats(input, pos, close, count))
|
|
579
1028
|
return pos + count;
|
|
@@ -584,7 +1033,7 @@ function wordListEnd(input, strict) {
|
|
|
584
1033
|
else if (ch == 59 /* Ch.Semicolon */ || ch == 123 /* Ch.BraceOpen */ || ch == 125 /* Ch.BraceClose */ ||
|
|
585
1034
|
ch == 40 /* Ch.ParenOpen */ || ch == 41 /* Ch.ParenClose */)
|
|
586
1035
|
code = true;
|
|
587
|
-
if (lines && (code || strict))
|
|
1036
|
+
if (lines > 1 && (code && lines > codeLines || strict))
|
|
588
1037
|
return -1;
|
|
589
1038
|
if (strict && (code || ch == 36 /* Ch.Dollar */ || ch == 64 /* Ch.At */ || ch == 37 /* Ch.Percent */ || ch == 38 /* Ch.Amp */ ||
|
|
590
1039
|
ch == 124 /* Ch.Pipe */ || ch == 61 /* Ch.Equals */ || ch == 44 /* Ch.Comma */ || ch == 63 /* Ch.Question */ ||
|
|
@@ -592,19 +1041,77 @@ function wordListEnd(input, strict) {
|
|
|
592
1041
|
return -1;
|
|
593
1042
|
}
|
|
594
1043
|
}
|
|
1044
|
+
// How many lines a <<...>> list that holds code may span.
|
|
1045
|
+
const maxCodeWordListLines = 10;
|
|
1046
|
+
// How long a <<...>> subscript can be. Whether it is closed is found
|
|
1047
|
+
// out from its start, and a token must not look far past its end.
|
|
1048
|
+
const maxDoubleSubscript = 24;
|
|
1049
|
+
// Whether a character can start or end the content of a <<...>>
|
|
1050
|
+
// subscript: part of a word, a variable or a quote. An operator
|
|
1051
|
+
// character cannot, which tells the hyper operators apart: @a<<+>>@b,
|
|
1052
|
+
// @a«R-»@b
|
|
1053
|
+
function isSubscriptEdge(ch) {
|
|
1054
|
+
return isIdentifierStart(ch) || isDigit(ch) || ch == 39 /* Ch.Apostrophe */ || ch == 34 /* Ch.DoubleQuote */;
|
|
1055
|
+
}
|
|
1056
|
+
// The offset after the <<...>> subscript at the current position, with
|
|
1057
|
+
// the given opening, or -1. It is short, on one line, and holds no
|
|
1058
|
+
// punctuation of statements.
|
|
1059
|
+
function doubleSubscriptEnd(input, { start, close, count }) {
|
|
1060
|
+
let first = input.peek(start);
|
|
1061
|
+
if (!isSubscriptEdge(first) && first != 36 /* Ch.Dollar */ && first != 64 /* Ch.At */ && first != 37 /* Ch.Percent */ && first != 38 /* Ch.Amp */)
|
|
1062
|
+
return -1;
|
|
1063
|
+
for (let pos = start; pos < maxDoubleSubscript; pos++) {
|
|
1064
|
+
let ch = input.peek(pos);
|
|
1065
|
+
if (ch == close && repeats(input, pos, close, count))
|
|
1066
|
+
return isSubscriptEdge(input.peek(pos - 1)) ? pos + count : -1;
|
|
1067
|
+
if (ch < 0 || ch == 10 /* Ch.Newline */ || ch == 59 /* Ch.Semicolon */ || ch == 123 /* Ch.BraceOpen */ || ch == 125 /* Ch.BraceClose */ ||
|
|
1068
|
+
ch == 40 /* Ch.ParenOpen */ || ch == 41 /* Ch.ParenClose */)
|
|
1069
|
+
return -1;
|
|
1070
|
+
}
|
|
1071
|
+
return -1;
|
|
1072
|
+
}
|
|
1073
|
+
// Whether every `{` between two offsets has its `}` there, and the
|
|
1074
|
+
// other way around.
|
|
1075
|
+
function hasBalancedBraces(input, from, to) {
|
|
1076
|
+
let depth = 0;
|
|
1077
|
+
for (let pos = from; pos < to; pos++) {
|
|
1078
|
+
let ch = input.peek(pos);
|
|
1079
|
+
if (ch == 123 /* Ch.BraceOpen */)
|
|
1080
|
+
depth++;
|
|
1081
|
+
else if (ch == 125 /* Ch.BraceClose */ && --depth < 0)
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
return depth == 0;
|
|
1085
|
+
}
|
|
595
1086
|
// A `<` subscript right after a term: %h<key>
|
|
596
1087
|
function subscriptEnd(input) {
|
|
597
1088
|
if (input.next != 60 /* Ch.Less */ || isSpace(input.peek(-1)) || input.peek(1) == 60 /* Ch.Less */)
|
|
598
1089
|
return -1;
|
|
599
1090
|
return wordListEnd(input, true);
|
|
600
1091
|
}
|
|
1092
|
+
// The keywords that are routines or prefix operators, and can be called
|
|
1093
|
+
// with parentheses. `if(1)` and `my($x)` are not calls of this kind.
|
|
1094
|
+
const callableKeywords = /^(take|return|emit|next|last|redo|proceed|succeed|so|not)$/;
|
|
1095
|
+
// The words that declare what follows `multi`, `proto` or `only`.
|
|
1096
|
+
const declarators$1 = /^(sub|method|submethod|token|rule|regex)$/;
|
|
1097
|
+
// Whether a declarator starts with the given word, as `su` starts `sub`.
|
|
1098
|
+
function startsDeclarator(name) {
|
|
1099
|
+
return ["sub", "method", "submethod", "token", "rule", "regex"].some(declarator => declarator.startsWith(name));
|
|
1100
|
+
}
|
|
1101
|
+
const operatorCategory = /^(infix|prefix|postfix|circumfix|postcircumfix|term)$/;
|
|
601
1102
|
function isWordOperator(name) {
|
|
602
1103
|
return name == "x" || name == "xx" || name == "min" || name == "max" || name == "Z" || name == "X";
|
|
603
1104
|
}
|
|
604
1105
|
// The tokens that depend on whether a term or an operator is expected.
|
|
605
1106
|
const termToken = new ExternalTokenizer((input, stack) => {
|
|
606
|
-
if (!stack.canShift(
|
|
1107
|
+
if (!stack.canShift(regexLiteral)) {
|
|
1108
|
+
// At the start of the parse of the inside of a word list that
|
|
1109
|
+
// interpolates, its opening delimiter.
|
|
1110
|
+
let opening = context(stack) == startContext && stack.canShift(quoteStart) ? wordListOpening(input, 0) : null;
|
|
1111
|
+
if (opening)
|
|
1112
|
+
input.acceptToken(quoteStart, opening.start);
|
|
607
1113
|
return;
|
|
1114
|
+
}
|
|
608
1115
|
let next = input.next, { mode } = context(stack);
|
|
609
1116
|
if (isIdentifierStart(next)) {
|
|
610
1117
|
// The Z, X and R meta operators: Z+, X~, R-, Z=>
|
|
@@ -616,14 +1123,29 @@ const termToken = new ExternalTokenizer((input, stack) => {
|
|
|
616
1123
|
return input.acceptToken(wordOperator, pos);
|
|
617
1124
|
}
|
|
618
1125
|
// A word before `=>` is a key, whatever it spells.
|
|
619
|
-
let end = nameEnd(input, 0), after = end;
|
|
620
|
-
while (input.peek(after) == 32 /* Ch.Space */ || input.peek(after) == 9 /* Ch.Tab */)
|
|
621
|
-
after++;
|
|
1126
|
+
let end = nameEnd(input, 0), after = gapEnd(input, end);
|
|
622
1127
|
if (input.peek(after) == 61 /* Ch.Equals */ && input.peek(after + 1) == 62 /* Ch.Greater */)
|
|
623
|
-
return input.acceptToken(
|
|
1128
|
+
return input.acceptToken(plainName, end);
|
|
1129
|
+
// After `multi`, `proto` or `only`, a name that is not a declarator
|
|
1130
|
+
// is that of a sub: multi foo($x) { }
|
|
1131
|
+
// Only with a signature or a body right after it. A name alone may
|
|
1132
|
+
// be a declarator that is still being typed (`multi su`), and
|
|
1133
|
+
// `only` and `proto` can be routines of one's own (`only foo, 1`).
|
|
1134
|
+
// The signature may start on the next line, unless the name can
|
|
1135
|
+
// still become a declarator: `multi su` above a line with a `(`.
|
|
1136
|
+
if (mode == 4 /* Mode.AfterMulti */ && !(end <= 9 && declarators$1.test(word(input, 0, end)))) {
|
|
1137
|
+
let nameTo = categoryEnd(input, 0, end), opens = (ch) => ch == 40 /* Ch.ParenOpen */ || ch == 123 /* Ch.BraceOpen */;
|
|
1138
|
+
if (opens(input.peek(gapEnd(input, nameTo))) ||
|
|
1139
|
+
opens(input.peek(gapEnd(input, nameTo, true))) && !startsDeclarator(word(input, 0, end)))
|
|
1140
|
+
return input.acceptToken(multiName, nameTo);
|
|
1141
|
+
}
|
|
1142
|
+
// So is one of the keywords that are routines, when it is called
|
|
1143
|
+
// with parentheses: take(1). They are 2 to 7 characters long.
|
|
1144
|
+
if (input.peek(end) == 40 /* Ch.ParenOpen */ && end <= 7 && callableKeywords.test(word(input, 0, end)))
|
|
1145
|
+
return input.acceptToken(plainName, end);
|
|
624
1146
|
if (mode == 1 /* Mode.AfterTerm */) {
|
|
625
1147
|
// x, xx, min, max, Z and X are operators where one is expected.
|
|
626
|
-
if (isWordOperator(word(input, 0, end)))
|
|
1148
|
+
if (end <= 3 && isWordOperator(word(input, 0, end)))
|
|
627
1149
|
input.acceptToken(wordOperator, end);
|
|
628
1150
|
return;
|
|
629
1151
|
}
|
|
@@ -632,11 +1154,20 @@ const termToken = new ExternalTokenizer((input, stack) => {
|
|
|
632
1154
|
// Squeezed between two terms, a sigil is the operator: $a%b. With a
|
|
633
1155
|
// space before it, it is a variable: `my Array[Int] %h`.
|
|
634
1156
|
if (mode == 1 /* Mode.AfterTerm */ && isIdentifierStart(input.peek(1)) && !isSpace(input.peek(-1)))
|
|
635
|
-
input.acceptToken(wordOperator, 1);
|
|
1157
|
+
return input.acceptToken(wordOperator, 1);
|
|
1158
|
+
// An operator as a routine: &infix:<+>
|
|
1159
|
+
if (next == 38 /* Ch.Amp */) {
|
|
1160
|
+
let end = nameEnd(input, 1);
|
|
1161
|
+
if (end > 0 && operatorCategory.test(word(input, 1, end))) {
|
|
1162
|
+
let categoryTo = categoryEnd(input, 1, end);
|
|
1163
|
+
if (categoryTo > end)
|
|
1164
|
+
input.acceptToken(operatorVariable, categoryTo);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
636
1167
|
return;
|
|
637
1168
|
}
|
|
638
1169
|
else if (next == 60 /* Ch.Less */ || next == 171 /* Ch.GuillemetOpen */) {
|
|
639
|
-
let end;
|
|
1170
|
+
let end, opening = wordListOpening(input, 0);
|
|
640
1171
|
if (inTermPosition(input, mode, 1)) {
|
|
641
1172
|
// [<] and [<=] are reductions, <-> starts a pointy block.
|
|
642
1173
|
let after = input.peek(1);
|
|
@@ -644,19 +1175,31 @@ const termToken = new ExternalTokenizer((input, stack) => {
|
|
|
644
1175
|
return;
|
|
645
1176
|
end = wordListEnd(input, false);
|
|
646
1177
|
}
|
|
1178
|
+
else if (opening && !isSpace(input.peek(-1))) {
|
|
1179
|
+
// A subscript with double angles, which touches its term:
|
|
1180
|
+
// %h«$key», :a<<b $c>>
|
|
1181
|
+
end = doubleSubscriptEnd(input, opening);
|
|
1182
|
+
}
|
|
647
1183
|
else {
|
|
648
1184
|
end = subscriptEnd(input);
|
|
649
1185
|
}
|
|
650
|
-
if (end
|
|
651
|
-
|
|
652
|
-
|
|
1186
|
+
if (end <= 0)
|
|
1187
|
+
return;
|
|
1188
|
+
// <<a $b>> and «a $b» interpolate. The list is one token here:
|
|
1189
|
+
// that it is closed has to be known where it starts. Its inside
|
|
1190
|
+
// is parsed again, piece by piece (see index.ts), by rules that
|
|
1191
|
+
// count nested delimiters, escapes and the braces of blocks. A
|
|
1192
|
+
// list that those rules would end elsewhere is not read inside.
|
|
1193
|
+
let interpolates = opening && rawEnd(input, opening.start, opening) == end &&
|
|
1194
|
+
hasBalancedBraces(input, opening.start, end);
|
|
1195
|
+
return input.acceptToken(interpolates ? interpolatingWordList : rawString, end);
|
|
653
1196
|
}
|
|
654
1197
|
else if (next == 47 /* Ch.Slash */) {
|
|
655
1198
|
if (input.peek(1) == 47 /* Ch.Slash */ || !inTermPosition(input, mode, 1))
|
|
656
1199
|
return;
|
|
657
|
-
let end =
|
|
1200
|
+
let end = regexTokenEnd(input, slashOpening);
|
|
658
1201
|
if (end > 0)
|
|
659
|
-
input.acceptToken(
|
|
1202
|
+
input.acceptToken(regexLiteral, end);
|
|
660
1203
|
return;
|
|
661
1204
|
}
|
|
662
1205
|
let opening = readOpening(input);
|
|
@@ -664,9 +1207,15 @@ const termToken = new ExternalTokenizer((input, stack) => {
|
|
|
664
1207
|
return;
|
|
665
1208
|
if (opening.kind == "interpolating")
|
|
666
1209
|
return input.acceptToken(quoteStart, opening.start);
|
|
667
|
-
|
|
1210
|
+
if (opening.kind != "raw") {
|
|
1211
|
+
let end = regexTokenEnd(input, opening);
|
|
1212
|
+
if (end > 0)
|
|
1213
|
+
input.acceptToken(opening.kind == "transliteration" ? transliteration : regexLiteral, end);
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
let end = rawEnd(input, opening.start, opening);
|
|
668
1217
|
if (end > 0)
|
|
669
|
-
input.acceptToken(
|
|
1218
|
+
input.acceptToken(rawString, end);
|
|
670
1219
|
});
|
|
671
1220
|
// Whether a sigil other than `$` interpolates: only when a subscript or
|
|
672
1221
|
// a call follows its name. `end` is the offset after the name.
|
|
@@ -679,17 +1228,25 @@ function hasPostfix(input, end) {
|
|
|
679
1228
|
let methodEnd = nameEnd(input, end + 1);
|
|
680
1229
|
return methodEnd > 0 && input.peek(methodEnd) == 40 /* Ch.ParenOpen */;
|
|
681
1230
|
}
|
|
1231
|
+
// Whether the parser is between the pieces of a quote, as opposed to
|
|
1232
|
+
// in the code of a block in it.
|
|
1233
|
+
function inQuoteText(stack) { return stack.canShift(quoteContent); }
|
|
1234
|
+
// Whether the parser is, in a quote, right after a variable or after a
|
|
1235
|
+
// subscript or a call on one: where the grammar takes a quotePostfix.
|
|
1236
|
+
// This is asked of the parser and not of the mode, which is not known
|
|
1237
|
+
// to this precision after a reused node that ends in text of the quote.
|
|
1238
|
+
function afterQuotedVariable(stack) { return stack.canShift(rawString); }
|
|
682
1239
|
// The pieces of a quote that interpolates.
|
|
683
1240
|
const quoteToken = new ExternalTokenizer((input, stack) => {
|
|
684
|
-
if (!stack
|
|
1241
|
+
if (!inQuoteText(stack))
|
|
685
1242
|
return;
|
|
686
|
-
let { quote
|
|
1243
|
+
let { quote } = context(stack);
|
|
687
1244
|
if (!quote)
|
|
688
1245
|
return;
|
|
689
|
-
let { open, close, count } = quote, nests = open != close;
|
|
1246
|
+
let { open, close, count, interpolates } = quote, nests = open != close;
|
|
690
1247
|
// After a variable, a subscript or a call continues the interpolation.
|
|
691
1248
|
// `[` and `(` are left to the grammar's own tokens.
|
|
692
|
-
if (
|
|
1249
|
+
if (afterQuotedVariable(stack)) {
|
|
693
1250
|
if (input.next == 91 /* Ch.BracketOpen */ || input.next == 40 /* Ch.ParenOpen */)
|
|
694
1251
|
return;
|
|
695
1252
|
let end = subscriptEnd(input);
|
|
@@ -697,6 +1254,9 @@ const quoteToken = new ExternalTokenizer((input, stack) => {
|
|
|
697
1254
|
return input.acceptToken(rawString, end);
|
|
698
1255
|
}
|
|
699
1256
|
let start = input.pos;
|
|
1257
|
+
// The text of a heredoc ends where the input does.
|
|
1258
|
+
if (input.next < 0 && quote == heredocQuote)
|
|
1259
|
+
return input.acceptToken(quoteEnd);
|
|
700
1260
|
for (;;) {
|
|
701
1261
|
let next = input.next;
|
|
702
1262
|
if (next < 0)
|
|
@@ -706,11 +1266,27 @@ const quoteToken = new ExternalTokenizer((input, stack) => {
|
|
|
706
1266
|
if (input.pos > start)
|
|
707
1267
|
break;
|
|
708
1268
|
input.advance(count);
|
|
709
|
-
|
|
1269
|
+
// Inside a nested pair, a closing delimiter closes that pair.
|
|
1270
|
+
return input.acceptToken(!atClose ? quoteNestOpen : stack.canShift(quoteNestClose) ? quoteNestClose : quoteEnd);
|
|
710
1271
|
}
|
|
711
|
-
if (next == 92 /* Ch.Backslash */
|
|
712
|
-
|
|
713
|
-
|
|
1272
|
+
if (next == 92 /* Ch.Backslash */) {
|
|
1273
|
+
if (interpolates & 32 /* Interpolate.Backslash */)
|
|
1274
|
+
break;
|
|
1275
|
+
// Still keeps a delimiter after it from closing the quote.
|
|
1276
|
+
if (quote.escapes && input.peek(1) >= 0)
|
|
1277
|
+
input.advance();
|
|
1278
|
+
}
|
|
1279
|
+
else if (next == 36 /* Ch.Dollar */) {
|
|
1280
|
+
if (interpolates & 2 /* Interpolate.Scalar */)
|
|
1281
|
+
break;
|
|
1282
|
+
}
|
|
1283
|
+
else if (next == 123 /* Ch.BraceOpen */) {
|
|
1284
|
+
if (interpolates & 1 /* Interpolate.Closure */)
|
|
1285
|
+
break;
|
|
1286
|
+
}
|
|
1287
|
+
else if (next == 64 /* Ch.At */ && interpolates & 4 /* Interpolate.Array */ ||
|
|
1288
|
+
next == 37 /* Ch.Percent */ && interpolates & 8 /* Interpolate.Hash */ ||
|
|
1289
|
+
next == 38 /* Ch.Amp */ && interpolates & 16 /* Interpolate.Function */) {
|
|
714
1290
|
let end = nameEnd(input, 1);
|
|
715
1291
|
if (end > 0 && hasPostfix(input, end))
|
|
716
1292
|
break;
|
|
@@ -723,37 +1299,27 @@ const quoteToken = new ExternalTokenizer((input, stack) => {
|
|
|
723
1299
|
// The text of a heredoc: the lines after the one that holds its opener,
|
|
724
1300
|
// up to the line that holds only its terminator. As a skipped token, it
|
|
725
1301
|
// leaves the rest of the opener's line to be parsed as usual. The
|
|
726
|
-
//
|
|
727
|
-
//
|
|
728
|
-
//
|
|
729
|
-
|
|
1302
|
+
// context holds the terminators of the heredocs that the line opened.
|
|
1303
|
+
//
|
|
1304
|
+
// The text is one token, also when it interpolates: that it has a
|
|
1305
|
+
// terminator has to be known where it starts. A text in which
|
|
1306
|
+
// everything interpolates is a token of its own kind, the inside of
|
|
1307
|
+
// which is parsed again (see index.ts), from an empty token that puts
|
|
1308
|
+
// the quote in the context. When a line opens several heredocs, their
|
|
1309
|
+
// texts are one token, which interpolates if all of them do.
|
|
1310
|
+
const heredocToken = new ExternalTokenizer((input, stack) => {
|
|
1311
|
+
if (context(stack) == startContext && stack.canShift(heredocStart))
|
|
1312
|
+
return input.acceptToken(heredocStart);
|
|
1313
|
+
let { heredocs } = context(stack);
|
|
1314
|
+
if (!heredocs.length)
|
|
1315
|
+
return;
|
|
730
1316
|
let pos = blanksEnd(input, 0);
|
|
731
1317
|
if (input.peek(pos) != 10 /* Ch.Newline */)
|
|
732
1318
|
return;
|
|
733
|
-
// Read the line that just ended, and look for heredoc openers in it.
|
|
734
|
-
let lineStart = 0;
|
|
735
|
-
while (lineStart > -500 && input.peek(lineStart - 1) >= 0 && input.peek(lineStart - 1) != 10 /* Ch.Newline */)
|
|
736
|
-
lineStart--;
|
|
737
|
-
let line = word(input, lineStart, 0);
|
|
738
|
-
if (line.indexOf(":to") < 0 && line.indexOf(":heredoc") < 0 || /^\s*#/.test(line))
|
|
739
|
-
return;
|
|
740
|
-
let opener = /(?:^|[^\w$@%&.'-])[qQ]\w*(?::\w+)*:(?:to|heredoc)(?::\w+)*\s*(\S)/g, found;
|
|
741
|
-
let terminators = [];
|
|
742
|
-
while (found = opener.exec(line)) {
|
|
743
|
-
let open = found[1], close = String.fromCharCode(brackets[open.charCodeAt(0)] || open.charCodeAt(0));
|
|
744
|
-
let end = line.indexOf(close, opener.lastIndex);
|
|
745
|
-
if (end < 0)
|
|
746
|
-
continue;
|
|
747
|
-
terminators.push(line.slice(opener.lastIndex, end).trim());
|
|
748
|
-
opener.lastIndex = end + 1;
|
|
749
|
-
}
|
|
750
|
-
if (!terminators.length)
|
|
751
|
-
return;
|
|
752
1319
|
input.advance(pos);
|
|
753
|
-
for (let terminator of
|
|
1320
|
+
for (let { terminator } of heredocs) {
|
|
754
1321
|
for (;;) {
|
|
755
|
-
// Without its terminator, this is not taken to be a heredoc.
|
|
756
|
-
// opener is found by its text, and may be in a comment or a string.
|
|
1322
|
+
// Without its terminator, this is not taken to be a heredoc.
|
|
757
1323
|
if (input.next < 0)
|
|
758
1324
|
return;
|
|
759
1325
|
input.advance();
|
|
@@ -764,22 +1330,218 @@ const heredocToken = new ExternalTokenizer(input => {
|
|
|
764
1330
|
skipLine(input);
|
|
765
1331
|
}
|
|
766
1332
|
}
|
|
767
|
-
input.acceptToken(
|
|
1333
|
+
input.acceptToken(heredocs.every(heredoc => heredoc.interpolates) ? interpolatingHeredoc : rawHeredoc);
|
|
768
1334
|
});
|
|
769
|
-
|
|
770
|
-
const
|
|
771
|
-
|
|
1335
|
+
const regexDeclarator = /^(token|rule|regex)$/;
|
|
1336
|
+
const regexDeclaratorPrefix = /^(proto|multi|my|our)$/;
|
|
1337
|
+
// Whether the line that starts at offset `pos` starts a regex
|
|
1338
|
+
// declaration: a declarator, a name, and further on a brace, as in
|
|
1339
|
+
// `token name {`. In a regex, the words alone can be literals:
|
|
1340
|
+
// `token | rule`
|
|
1341
|
+
function startsRegexDeclaration(input, pos) {
|
|
1342
|
+
let start = blanksEnd(input, pos), end = nameEnd(input, start);
|
|
1343
|
+
// proto token, my regex
|
|
1344
|
+
if (end > 0 && isBlank(input.peek(end)) && regexDeclaratorPrefix.test(word(input, start, end))) {
|
|
1345
|
+
start = blanksEnd(input, end);
|
|
1346
|
+
end = nameEnd(input, start);
|
|
1347
|
+
}
|
|
1348
|
+
if (end < 0 || end - start > 5 || !isBlank(input.peek(end)) || !regexDeclarator.test(word(input, start, end)))
|
|
1349
|
+
return false;
|
|
1350
|
+
let name = blanksEnd(input, end), after = nameEnd(input, name);
|
|
1351
|
+
if (after < 0)
|
|
1352
|
+
return false;
|
|
1353
|
+
for (after = categoryEnd(input, name, after);; after++) {
|
|
1354
|
+
let next = input.peek(after);
|
|
1355
|
+
if (next == 123 /* Ch.BraceOpen */)
|
|
1356
|
+
return true;
|
|
1357
|
+
if (next < 0 || next == 10 /* Ch.Newline */)
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
// The characters that quantify, combine and anchor in a regex.
|
|
1362
|
+
function isRegexOperator(ch) {
|
|
1363
|
+
return ch == 43 /* Ch.Plus */ || ch == 42 /* Ch.Star */ || ch == 63 /* Ch.Question */ || ch == 124 /* Ch.Pipe */ || ch == 38 /* Ch.Amp */ || ch == 94 /* Ch.Caret */ ||
|
|
1364
|
+
ch == 126 /* Ch.Tilde */ || ch == 37 /* Ch.Percent */;
|
|
1365
|
+
}
|
|
1366
|
+
// The offset after the `<...>` at the current position in a regex.
|
|
1367
|
+
// When it is not closed on its line, this is the negated offset of the
|
|
1368
|
+
// end of the text that was gone over to find that out.
|
|
1369
|
+
function assertionEnd(input) {
|
|
1370
|
+
for (let pos = 1, depth = 1;;) {
|
|
1371
|
+
let ch = input.peek(pos);
|
|
1372
|
+
if (ch < 0 || ch == 10 /* Ch.Newline */)
|
|
1373
|
+
return -pos;
|
|
1374
|
+
if (ch == 92 /* Ch.Backslash */) {
|
|
1375
|
+
pos += input.peek(pos + 1) == 10 /* Ch.Newline */ || input.peek(pos + 1) < 0 ? 1 : 2;
|
|
1376
|
+
}
|
|
1377
|
+
else if (ch == 39 /* Ch.Apostrophe */ || ch == 34 /* Ch.DoubleQuote */) {
|
|
1378
|
+
let end = quotedEnd(input, pos);
|
|
1379
|
+
pos = end < 0 ? pos + 1 : end;
|
|
1380
|
+
}
|
|
1381
|
+
else if (ch == 91 /* Ch.BracketOpen */) {
|
|
1382
|
+
// The brackets of a character class can hold a `>` and a `}`,
|
|
1383
|
+
// and can span lines.
|
|
1384
|
+
let end = pos + 1;
|
|
1385
|
+
while (input.peek(end) != 93 /* Ch.BracketClose */ && input.peek(end) >= 0)
|
|
1386
|
+
end += input.peek(end) == 92 /* Ch.Backslash */ ? 2 : 1;
|
|
1387
|
+
pos = input.peek(end) == 93 /* Ch.BracketClose */ ? end + 1 : pos + 1;
|
|
1388
|
+
}
|
|
1389
|
+
else if (ch == 123 /* Ch.BraceOpen */) {
|
|
1390
|
+
// So can a block of code: <?{ $x > 1 }>
|
|
1391
|
+
let end = pos + 1;
|
|
1392
|
+
for (let braces = 1; braces > 0; end++) {
|
|
1393
|
+
let inner = input.peek(end);
|
|
1394
|
+
if (inner < 0 || inner == 10 /* Ch.Newline */)
|
|
1395
|
+
return -pos;
|
|
1396
|
+
if (inner == 123 /* Ch.BraceOpen */)
|
|
1397
|
+
braces++;
|
|
1398
|
+
else if (inner == 125 /* Ch.BraceClose */)
|
|
1399
|
+
braces--;
|
|
1400
|
+
}
|
|
1401
|
+
pos = end;
|
|
1402
|
+
}
|
|
1403
|
+
else if (ch == 60 /* Ch.Less */) {
|
|
1404
|
+
depth++;
|
|
1405
|
+
pos++;
|
|
1406
|
+
}
|
|
1407
|
+
else if (ch == 62 /* Ch.Greater */) {
|
|
1408
|
+
pos++;
|
|
1409
|
+
if (--depth == 0)
|
|
1410
|
+
return pos;
|
|
1411
|
+
}
|
|
1412
|
+
else {
|
|
1413
|
+
pos++;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
// The pieces of the body of a token, rule or regex declaration. The
|
|
1418
|
+
// context tells whether the position is in such a body, and whether it
|
|
1419
|
+
// is in a block of code there, which is left to the other tokenizers.
|
|
1420
|
+
const regexToken = new ExternalTokenizer((input, stack) => {
|
|
1421
|
+
let current = context(stack), { regex } = current, next = input.next;
|
|
1422
|
+
if (!regex || regex.braces > 0) {
|
|
1423
|
+
// At the start of the parse of the inside of a regex literal, the
|
|
1424
|
+
// opening delimiter: `/`, or a quote word with its adverbs: rx:i/
|
|
1425
|
+
if (current == startContext && stack.canShift(regexLitOpen)) {
|
|
1426
|
+
let opening = regexOpening(input);
|
|
1427
|
+
if (opening)
|
|
1428
|
+
input.acceptToken(regexLitOpen, opening.start);
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
// Outside of a regex, or in a block of code in one, where another
|
|
1432
|
+
// regex can be declared. An empty body, `{}`, has no regex.
|
|
1433
|
+
if (next >= 0 && next != 125 /* Ch.BraceClose */ && stack.canShift(regexStart))
|
|
1434
|
+
input.acceptToken(regexStart);
|
|
772
1435
|
return;
|
|
773
|
-
let end = regexEnd(input, 0, 123 /* Ch.BraceOpen */, 125 /* Ch.BraceClose */, 1);
|
|
774
|
-
if (end < 0) {
|
|
775
|
-
// Not closed: the rest of the input.
|
|
776
|
-
skipToEnd(input);
|
|
777
|
-
if (input.pos > stack.pos)
|
|
778
|
-
input.acceptToken(regexBody);
|
|
779
1436
|
}
|
|
780
|
-
|
|
781
|
-
|
|
1437
|
+
let { literalEnd } = regex;
|
|
1438
|
+
if (literalEnd >= 0) {
|
|
1439
|
+
// A literal ends at its closing delimiter. The rest of what is
|
|
1440
|
+
// parsed, which for s/a/b/ includes the replacement, is one token.
|
|
1441
|
+
if (next < 0 || input.pos >= literalEnd) {
|
|
1442
|
+
skipToEnd(input);
|
|
1443
|
+
return input.acceptToken(regexLitClose);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
else if (next < 0 || next == 125 /* Ch.BraceClose */ || next == 10 /* Ch.Newline */ && startsRegexDeclaration(input, 1)) {
|
|
1447
|
+
// The body ends at its closing brace. One that is not closed ends
|
|
1448
|
+
// before the next regex declaration, which a body does not hold, so
|
|
1449
|
+
// that the declarations after it are still found.
|
|
1450
|
+
return input.acceptToken(regexBodyEnd);
|
|
782
1451
|
}
|
|
1452
|
+
// A block of code, and an escape, are tokens of the grammar. An
|
|
1453
|
+
// escape does not take a line break, where the check above is made.
|
|
1454
|
+
if (next == 123 /* Ch.BraceOpen */)
|
|
1455
|
+
return;
|
|
1456
|
+
if (next == 92 /* Ch.Backslash */ && input.peek(1) >= 0 && input.peek(1) != 10 /* Ch.Newline */)
|
|
1457
|
+
return;
|
|
1458
|
+
// How far a piece of a literal can go: up to its closing delimiter.
|
|
1459
|
+
// The pieces are read by other rules than the ones that found that
|
|
1460
|
+
// delimiter, and where the two disagree, the delimiter wins. A piece
|
|
1461
|
+
// that would go over it is text.
|
|
1462
|
+
let limit = literalEnd < 0 ? Infinity : literalEnd - input.pos;
|
|
1463
|
+
if (next == 35 /* Ch.Hash */) {
|
|
1464
|
+
// An embedded comment, #`( ... ), or one that runs to the end of the line.
|
|
1465
|
+
let end = embeddedCommentEnd(input, 0);
|
|
1466
|
+
if (end > 0)
|
|
1467
|
+
return input.acceptToken(regexBlockComment, end);
|
|
1468
|
+
skipLine(input);
|
|
1469
|
+
return input.acceptToken(regexComment);
|
|
1470
|
+
}
|
|
1471
|
+
if (next == 39 /* Ch.Apostrophe */ || next == 34 /* Ch.DoubleQuote */) {
|
|
1472
|
+
let end = quotedEnd(input, 0);
|
|
1473
|
+
if (end > 0 && end <= limit)
|
|
1474
|
+
return input.acceptToken(regexQuote, end);
|
|
1475
|
+
}
|
|
1476
|
+
else if (next == 60 /* Ch.Less */ || next == 62 /* Ch.Greater */ || next == 171 /* Ch.GuillemetOpen */ || next == 187 /* Ch.GuillemetClose */ ||
|
|
1477
|
+
next == 41 /* Ch.ParenClose */ && input.peek(1) == 62 /* Ch.Greater */) {
|
|
1478
|
+
// Word boundaries and capture markers stand alone: << >> « » <( )>
|
|
1479
|
+
let after = input.peek(1);
|
|
1480
|
+
if (next != 60 /* Ch.Less */ || after == 60 /* Ch.Less */ || after == 40 /* Ch.ParenOpen */) {
|
|
1481
|
+
let size = next == 171 /* Ch.GuillemetOpen */ || next == 187 /* Ch.GuillemetClose */ ? 1 : 2;
|
|
1482
|
+
if (next != 62 /* Ch.Greater */ || after == 62 /* Ch.Greater */)
|
|
1483
|
+
return input.acceptToken(regexOperator, size);
|
|
1484
|
+
}
|
|
1485
|
+
else {
|
|
1486
|
+
let end = assertionEnd(input);
|
|
1487
|
+
if (end > limit)
|
|
1488
|
+
return input.acceptToken(regexText, 1);
|
|
1489
|
+
if (end > 0) {
|
|
1490
|
+
// <[a..z]>, <-[a]>, <+alpha-[b]>, <?[c]>
|
|
1491
|
+
let kind = after == 63 /* Ch.Question */ || after == 33 /* Ch.Bang */ ? input.peek(2) : after;
|
|
1492
|
+
let isClass = kind == 91 /* Ch.BracketOpen */ || kind == 45 /* Ch.Hyphen */ || kind == 43 /* Ch.Plus */;
|
|
1493
|
+
return input.acceptToken(isClass ? CharacterClass : Assertion, end);
|
|
1494
|
+
}
|
|
1495
|
+
// What is not closed is text, up to the brace that closes the
|
|
1496
|
+
// body if there is one on the line.
|
|
1497
|
+
let stop = 1;
|
|
1498
|
+
while (stop < -end && input.peek(stop) != 125 /* Ch.BraceClose */)
|
|
1499
|
+
stop++;
|
|
1500
|
+
return input.acceptToken(regexText, stop);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
else if (next == 36 /* Ch.Dollar */ || next == 64 /* Ch.At */) {
|
|
1504
|
+
let after = input.peek(1);
|
|
1505
|
+
// A named capture: $<name>
|
|
1506
|
+
if (next == 36 /* Ch.Dollar */ && after == 60 /* Ch.Less */) {
|
|
1507
|
+
let end = nameEnd(input, 2);
|
|
1508
|
+
if (end > 0 && input.peek(end) == 62 /* Ch.Greater */)
|
|
1509
|
+
return input.acceptToken(regexCapture, end + 1);
|
|
1510
|
+
}
|
|
1511
|
+
// A variable is a token of the grammar: $x, $0, $/, @list, $!attr, $*dynamic
|
|
1512
|
+
// Not the `$` before the delimiter that closes a literal: /a$/
|
|
1513
|
+
if (limit != 1) {
|
|
1514
|
+
if (isIdentifierStart(after) || next == 36 /* Ch.Dollar */ && (isDigit(after) || after == 47 /* Ch.Slash */))
|
|
1515
|
+
return;
|
|
1516
|
+
if ((after == 33 /* Ch.Bang */ || after == 46 /* Ch.Dot */ || after == 42 /* Ch.Star */ || after == 63 /* Ch.Question */) &&
|
|
1517
|
+
isIdentifierStart(input.peek(2)))
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
// Otherwise `$` and `$$` anchor.
|
|
1521
|
+
if (next == 36 /* Ch.Dollar */) {
|
|
1522
|
+
while (input.next == 36 /* Ch.Dollar */ && input.pos != literalEnd)
|
|
1523
|
+
input.advance();
|
|
1524
|
+
return input.acceptToken(regexOperator);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
else if (isRegexOperator(next)) {
|
|
1528
|
+
while (isRegexOperator(input.next) && input.pos != literalEnd)
|
|
1529
|
+
input.advance();
|
|
1530
|
+
return input.acceptToken(regexOperator);
|
|
1531
|
+
}
|
|
1532
|
+
// Text, up to where one of the above may apply.
|
|
1533
|
+
for (;;) {
|
|
1534
|
+
input.advance();
|
|
1535
|
+
let ch = input.next;
|
|
1536
|
+
if (ch < 0 || input.pos == literalEnd ||
|
|
1537
|
+
ch == 10 /* Ch.Newline */ || ch == 92 /* Ch.Backslash */ || ch == 39 /* Ch.Apostrophe */ || ch == 34 /* Ch.DoubleQuote */ ||
|
|
1538
|
+
ch == 60 /* Ch.Less */ || ch == 62 /* Ch.Greater */ || ch == 171 /* Ch.GuillemetOpen */ || ch == 187 /* Ch.GuillemetClose */ ||
|
|
1539
|
+
ch == 41 /* Ch.ParenClose */ && input.peek(1) == 62 /* Ch.Greater */ ||
|
|
1540
|
+
ch == 123 /* Ch.BraceOpen */ || ch == 125 /* Ch.BraceClose */ || ch == 35 /* Ch.Hash */ || ch == 36 /* Ch.Dollar */ ||
|
|
1541
|
+
ch == 64 /* Ch.At */ || isRegexOperator(ch))
|
|
1542
|
+
break;
|
|
1543
|
+
}
|
|
1544
|
+
input.acceptToken(regexText);
|
|
783
1545
|
});
|
|
784
1546
|
// ---- Names ----
|
|
785
1547
|
// The dot of a method call, with its optional `?`, `+`, `*`, `^` or `&`
|
|
@@ -794,26 +1556,37 @@ const methodDotToken = new ExternalTokenizer((input, stack) => {
|
|
|
794
1556
|
next = input.peek(++size);
|
|
795
1557
|
if (!isIdentifierStart(next))
|
|
796
1558
|
return;
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
(mode != 1 /* Mode.AfterTerm */ || input.peek(nameEnd(input, size)) != 40 /* Ch.ParenOpen */))
|
|
1559
|
+
if (context(stack).quote && inQuoteText(stack) &&
|
|
1560
|
+
(!afterQuotedVariable(stack) || input.peek(nameEnd(input, size)) != 40 /* Ch.ParenOpen */))
|
|
800
1561
|
return;
|
|
801
1562
|
input.acceptToken(methodDot, size);
|
|
802
1563
|
});
|
|
803
|
-
// The offset after an operator name's `:<...>` part, as in infix
|
|
804
|
-
// and term:sym<x>, or `pos` when there is
|
|
805
|
-
|
|
1564
|
+
// The offset after an operator name's `:<...>` part, as in infix:<+>,
|
|
1565
|
+
// circumfix:<[ ]>, infix:['+'] and term:sym<x>, or `pos` when there is
|
|
1566
|
+
// none there. The name before it runs from `nameStart` to `pos`.
|
|
1567
|
+
function categoryEnd(input, nameStart, pos) {
|
|
806
1568
|
if (input.peek(pos) != 58 /* Ch.Colon */)
|
|
807
1569
|
return pos;
|
|
808
1570
|
let open = pos + 1;
|
|
809
1571
|
if (word(input, open, open + 3) == "sym")
|
|
810
1572
|
open += 3;
|
|
811
|
-
let
|
|
1573
|
+
let first = input.peek(open);
|
|
1574
|
+
if (first == 91 /* Ch.BracketOpen */) {
|
|
1575
|
+
// Only a quoted operator: ['+']
|
|
1576
|
+
let quote = input.peek(open + 1);
|
|
1577
|
+
if (quote != 39 /* Ch.Apostrophe */ && quote != 34 /* Ch.DoubleQuote */)
|
|
1578
|
+
return pos;
|
|
1579
|
+
let end = quotedEnd(input, open + 1);
|
|
1580
|
+
return end > 0 && input.peek(end) == 93 /* Ch.BracketClose */ ? end + 1 : pos;
|
|
1581
|
+
}
|
|
1582
|
+
let close = first == 60 /* Ch.Less */ ? 62 /* Ch.Greater */ : first == 171 /* Ch.GuillemetOpen */ ? 187 /* Ch.GuillemetClose */ : -1;
|
|
812
1583
|
if (close < 0)
|
|
813
1584
|
return pos;
|
|
1585
|
+
// The two halves of a circumfix are separated by one space.
|
|
1586
|
+
let spaces = /circumfix$/.test(word(input, nameStart, pos)) ? 1 : 0;
|
|
814
1587
|
for (let i = open + 1;; i++) {
|
|
815
1588
|
let ch = input.peek(i);
|
|
816
|
-
if (ch < 0 || isSpace(ch))
|
|
1589
|
+
if (ch < 0 || isSpace(ch) && (ch != 32 /* Ch.Space */ || i == open + 1 || --spaces < 0))
|
|
817
1590
|
return pos;
|
|
818
1591
|
// The operator itself can be `>`, so the last closer before a break counts.
|
|
819
1592
|
if (ch == close && !(input.peek(i + 1) == close))
|
|
@@ -834,7 +1607,7 @@ const nameToken = new ExternalTokenizer((input, stack) => {
|
|
|
834
1607
|
input.acceptToken(MethodName, end);
|
|
835
1608
|
return;
|
|
836
1609
|
}
|
|
837
|
-
end = categoryEnd(input, end);
|
|
1610
|
+
end = categoryEnd(input, mark, end);
|
|
838
1611
|
if (!mark && stack.canShift(declaredName))
|
|
839
1612
|
input.acceptToken(declaredName, end);
|
|
840
1613
|
else if (stack.canShift(declaredMethodName))
|
|
@@ -842,12 +1615,20 @@ const nameToken = new ExternalTokenizer((input, stack) => {
|
|
|
842
1615
|
});
|
|
843
1616
|
// The type smiley in Int:D, Str:U and Any:_. It has to touch the type
|
|
844
1617
|
// name, and must not be the start of a longer pair such as :Default.
|
|
1618
|
+
//
|
|
1619
|
+
// A type name without a smiley ends in an empty token. Without it, the
|
|
1620
|
+
// parser has to read the token after the name to know that the name is
|
|
1621
|
+
// complete, and when a long comment is in between, it records that as
|
|
1622
|
+
// a lookahead, which keeps the tokens before it from being reused.
|
|
845
1623
|
const smileyToken = new ExternalTokenizer((input, stack) => {
|
|
846
|
-
if (
|
|
1624
|
+
if (!stack.canShift(noSmiley))
|
|
847
1625
|
return;
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1626
|
+
if (input.next == 58 /* Ch.Colon */) {
|
|
1627
|
+
let kind = input.peek(1);
|
|
1628
|
+
if ((kind == 68 /* Ch.D */ || kind == 85 /* Ch.U */ || kind == 95 /* Ch.Underscore */) && !continuesName(input, 2))
|
|
1629
|
+
return input.acceptToken(smiley, 2);
|
|
1630
|
+
}
|
|
1631
|
+
input.acceptToken(noSmiley, 0);
|
|
851
1632
|
});
|
|
852
1633
|
// v6, v6.d, v1.2.3, v1.2+, v1.*. A name such as v8-engine is not a version.
|
|
853
1634
|
const versionToken = new ExternalTokenizer((input, stack) => {
|
|
@@ -882,27 +1663,27 @@ const versionToken = new ExternalTokenizer((input, stack) => {
|
|
|
882
1663
|
});
|
|
883
1664
|
|
|
884
1665
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
|
885
|
-
const spec_identifier = {__proto__:null,class:
|
|
886
|
-
const spec_capIdentifier = {__proto__:null,True:
|
|
1666
|
+
const spec_identifier = {__proto__:null,class:80, role:82, grammar:84, module:86, package:88, sub:102, method:106, submethod:108, token:116, rule:118, regex:120, enum:140, subset:146, constant:152, my:172, our:174, has:176, state:178, temp:180, let:182, anon:184, augment:186, supersede:188, unit:190, multi:192, proto:194, only:196, if:198, elsif:200, else:202, unless:204, with:206, orwith:208, without:210, for:212, while:214, until:216, loop:218, repeat:220, given:222, when:224, default:226, do:228, gather:230, take:232, try:234, return:236, next:238, last:240, redo:242, proceed:244, succeed:246, react:248, whenever:250, supply:252, emit:254, start:256, use:258, need:260, import:262, require:264, no:266, is:268, does:270, of:272, returns:274, handles:276, where:278, will:280, trusts:282, hides:284, and:286, or:288, not:290, xor:292, so:294, andthen:296, orelse:298, notandthen:300, div:302, mod:304, gcd:306, lcm:308, eq:310, ne:312, lt:314, gt:316, le:318, ge:320, cmp:322, leg:324, eqv:326, but:328, self:330};
|
|
1667
|
+
const spec_capIdentifier = {__proto__:null,True:332, False:334, Nil:336, BEGIN:338, CHECK:340, INIT:342, END:344, ENTER:346, LEAVE:348, KEEP:350, UNDO:352, FIRST:354, NEXT:356, LAST:358, PRE:360, POST:362, CATCH:364, CONTROL:366, QUIT:368, CLOSE:370, DOC:372};
|
|
887
1668
|
const parser$1 = LRParser.deserialize({
|
|
888
1669
|
version: 14,
|
|
889
|
-
states: "*
|
|
890
|
-
stateData: "
|
|
891
|
-
goto: "
|
|
892
|
-
nodeNames: "⚠ BlockComment
|
|
893
|
-
maxTerm:
|
|
1670
|
+
states: "/`QfQDdOOO*zO#tOOO+POWOOO+UO,UOOO+uOSO'#C{O,PQDdO'#DPO,WQDdO'#DSO,_QDdO'#D^OOQ.W'#Df'#DfO,fQDdO'#D`O5zQ8WO'#D|OOQ.W'#D}'#D}O6PQ7gO'#EOOOQ.W'#EQ'#EQO6sO!%SO'#EQOOQ.W'#HU'#HUOOQ.W'#HT'#HTOOQ.W'#Gf'#GfQfQDdOOO6zQEUO'#DTO7RQEUO'#D`O7YQEUO'#D`O@rQEUO'#DgO@|QEUO'#DsOJqQEUO'#DvO!&fQEUO'#DyOOQ.W'#H^'#H^O!0ZO!!^OOO!1XO!%SOOOOOS'#Gg'#GgO!1`OSO,59gOOQ.W,59g,59gOOQ.W,59k,59kO!1jQDdO,59kOOQ.W,59n,59nO!1qQDdO,59nOOQ.W,59x,59xO!1xQDdO,59xOOQ.W'#Gh'#GhO!;bQDdO'#HWOOQ.W'#HW'#HWOOQ.W,59z,59zOOQ.W,5:h,5:hOOQ.W,5:j,5:jO!;iO!%SO'#ERO!;pQDdO'#ESOOO&j'#HZ'#HZO!;wO!0jO'#HZOOO&j'#Gj'#GjO!<uO!%SO,5:lOOQ.W,5:l,5:lOOQ.W-E:d-E:dOOQ.W'#DZ'#DZOOQ.W,59o,59oO,fQDdO,59oOOQ.W'#Db'#DbO,fQDdO,59zOOQ.W'#De'#DeO!<|QDdO,5:ROOQ.W'#Dk'#DkO!=TQ:[O'#DlOOQ.W,5:R,5:RO!<|QDdO,5:ROOQ.W'#Du'#DuOOQ.W,5:_,5:_OOQ.W'#Dx'#DxOOQ.W,5:b,5:bOOQ.W'#D{'#D{OOQ.W,5:e,5:eO!=]QDdO'#DrOOO#t'#HX'#HXOOO#t'#Gi'#GiO!0ZO!!^OOQOOOOOO!1XO!%SOOOOOS-E:e-E:eOOQ.W1G/R1G/ROOQ.W1G/V1G/VOOQ.W1G/Y1G/YOOQ.W1G/d1G/dOOQ.W-E:f-E:fOOQ.W,5=r,5=rOOO&j,5:m,5:mO!=dO!%SO,5:mOOO&j,5:n,5:nO!=kQDdO,5:nOOO2Q'#H]'#H]OOO2Q'#Gk'#GkO!=rO!0jO,5=uOOO&j-E:h-E:hOOQ.W1G0W1G0WOOQ.W1G/Z1G/ZOOQ.W1G/f1G/fOOQ.W1G/m1G/mO!?kO!!^O'#DmOOQ.W,5:W,5:WO!?rQ7fO,5:WO!?wQDdO1G/mOOO#t,5:^,5:^O!@OQDdO,5:^OOO#t-E:g-E:gOOO&j1G0X1G0XOOO&j1G0Y1G0YOOO2Q-E:i-E:iO!@VO!!^O,5:XOOQY,5:X,5:XOOQ.W1G/r1G/rOOQ.W7+%X7+%XOOO#t1G/x1G/xOOQY1G/s1G/s",
|
|
1671
|
+
stateData: "!@j~O%vOSlOSmOSPOSZOS[OS~OQSO]YO__Og_Oh_Oi_Oj_Ok_Op_OrTOuUOxcOycOzcO{cO|cO!PVO!R`O!TdO!VeO!WeO![fO!]fO!^fO!c_O!d_O!e_O!hgO!khO!niO!s_O!w_O!xjO!yjO!zjO!{jO!|jO!}jO#OjO#PjO#QjO#RjO#SjO#TjO#UjO#VjO#WjO#XjO#YjO#ZjO#[jO#]jO#^jO#_jO#`jO#ajO#bjO#cjO#djO#ejO#fjO#gjO#hjO#ijO#jjO#kjO#ljO#mjO#njO#ojO#pjO#qjO#rjO#sjO#tjO#ujO#vjO#wjO#xjO#yjO#zjO#{jO#|jO#}jO$OjO$PjO$QjO$RjO$SjO$TjO$UjO$VjO$WjO$XjO$YjO$ZjO$[jO$]jO$^jO$_jO$`jO$ajO$bjO$cjO$djO$ejO$fjO$gjO$hjO$ijO$jjO$kjO$ljO$mjO$njO$ojO$pjO$qjO$rjO$sjO$tjO$ujO$vjO$wjO$xjO$yjO$zjO${jO$|jO$}jO%OjO%PjO%QjO%R_O%S_O%T_O%U_O%q]O%r^O%sZO%tWO%yZO%|[O&O_O~O%jkO~O%elO~O%rlO~OSmOTmOUmOVmOWmOXmOYmO%dmO~ORoO%coO~P+ZOqpO~PfOtrO~PfO!OtO~PfOQSO]YO__Og_Oh_Oi_Oj_Ok_Op_OrTOuUOxcOycOzcO{cO|cO!PVO!RxO!TdO!VeO!WeO![fO!]fO!^fO!c_O!d_O!e_O!hgO!khO!niO!s_O!w_O!xjO!yjO!zjO!{jO!|jO!}jO#OjO#PjO#QjO#RjO#SjO#TjO#UjO#VjO#WjO#XjO#YjO#ZjO#[jO#]jO#^jO#_jO#`jO#ajO#bjO#cjO#djO#ejO#fjO#gjO#hjO#ijO#jjO#kjO#ljO#mjO#njO#ojO#pjO#qjO#rjO#sjO#tjO#ujO#vjO#wjO#xjO#yjO#zjO#{jO#|jO#}jO$OjO$PjO$QjO$RjO$SjO$TjO$UjO$VjO$WjO$XjO$YjO$ZjO$[jO$]jO$^jO$_jO$`jO$ajO$bjO$cjO$djO$ejO$fjO$gjO$hjO$ijO$jjO$kjO$ljO$mjO$njO$ojO$pjO$qjO$rjO$sjO$tjO$ujO$vjO$wjO$xjO$yjO$zjO${jO$|jO$}jO%OjO%PjO%QjO%R_O%S_O%T_O%U_O%q]O%r^O%sZO%tWO%yZO%|[O&O_O~O^zO~O%a{O%b{O~O!P}O!b!OO!c!PO!d!PO!e!PO%m!OO%n|O&O!PO~O%p!SO~P6XO%f!UO~P,fO%f!XO~P,fO%g!ZO~P,fOQSO]YO__Og_Oh_Oi_Oj_Ok_Op_OrTOuUOxcOycOzcO{cO|cO!P!^O!TdO!VeO!WeO![fO!]fO!^fO!c_O!d_O!e_O!hgO!khO!niO!s_O!w_O!xjO!yjO!zjO!{jO!|jO!}jO#OjO#PjO#QjO#RjO#SjO#TjO#UjO#VjO#WjO#XjO#YjO#ZjO#[jO#]jO#^jO#_jO#`jO#ajO#bjO#cjO#djO#ejO#fjO#gjO#hjO#ijO#jjO#kjO#ljO#mjO#njO#ojO#pjO#qjO#rjO#sjO#tjO#ujO#vjO#wjO#xjO#yjO#zjO#{jO#|jO#}jO$OjO$PjO$QjO$RjO$SjO$TjO$UjO$VjO$WjO$XjO$YjO$ZjO$[jO$]jO$^jO$_jO$`jO$ajO$bjO$cjO$djO$ejO$fjO$gjO$hjO$ijO$jjO$kjO$ljO$mjO$njO$ojO$pjO$qjO$rjO$sjO$tjO$ujO$vjO$wjO$xjO$yjO$zjO${jO$|jO$}jO%OjO%PjO%QjO%R_O%S_O%T_O%U_O%q]O%r^O%sZO%tWO%yZO%|[O&O_O~O!R!_O%f!]O~P7aO%f!aOQ!gX]!gX_!gXg!gXh!gXi!gXj!gXk!gXp!gXr!gXu!gXx!gXy!gXz!gX{!gX|!gX!P!gX!R!gX!T!gX!V!gX!W!gX![!gX!]!gX!^!gX!c!gX!d!gX!e!gX!h!gX!k!gX!n!gX!s!gX!w!gX!x!gX!y!gX!z!gX!{!gX!|!gX!}!gX#O!gX#P!gX#Q!gX#R!gX#S!gX#T!gX#U!gX#V!gX#W!gX#X!gX#Y!gX#Z!gX#[!gX#]!gX#^!gX#_!gX#`!gX#a!gX#b!gX#c!gX#d!gX#e!gX#f!gX#g!gX#h!gX#i!gX#j!gX#k!gX#l!gX#m!gX#n!gX#o!gX#p!gX#q!gX#r!gX#s!gX#t!gX#u!gX#v!gX#w!gX#x!gX#y!gX#z!gX#{!gX#|!gX#}!gX$O!gX$P!gX$Q!gX$R!gX$S!gX$T!gX$U!gX$V!gX$W!gX$X!gX$Y!gX$Z!gX$[!gX$]!gX$^!gX$_!gX$`!gX$a!gX$b!gX$c!gX$d!gX$e!gX$f!gX$g!gX$h!gX$i!gX$j!gX$k!gX$l!gX$m!gX$n!gX$o!gX$p!gX$q!gX$r!gX$s!gX$t!gX$u!gX$v!gX$w!gX$x!gX$y!gX$z!gX${!gX$|!gX$}!gX%O!gX%P!gX%Q!gX%R!gX%S!gX%T!gX%U!gX%`!gX%q!gX%r!gX%s!gX%t!gX%y!gX%|!gX&O!gXq!gXt!gX!O!gX~O%f!cOQ!jX]!jX_!jXg!jXh!jXi!jXj!jXk!jXp!jXr!jXu!jXx!jXy!jXz!jX{!jX|!jX!P!jX!R!jX!T!jX!V!jX!W!jX![!jX!]!jX!^!jX!c!jX!d!jX!e!jX!h!jX!k!jX!n!jX!s!jX!w!jX!x!jX!y!jX!z!jX!{!jX!|!jX!}!jX#O!jX#P!jX#Q!jX#R!jX#S!jX#T!jX#U!jX#V!jX#W!jX#X!jX#Y!jX#Z!jX#[!jX#]!jX#^!jX#_!jX#`!jX#a!jX#b!jX#c!jX#d!jX#e!jX#f!jX#g!jX#h!jX#i!jX#j!jX#k!jX#l!jX#m!jX#n!jX#o!jX#p!jX#q!jX#r!jX#s!jX#t!jX#u!jX#v!jX#w!jX#x!jX#y!jX#z!jX#{!jX#|!jX#}!jX$O!jX$P!jX$Q!jX$R!jX$S!jX$T!jX$U!jX$V!jX$W!jX$X!jX$Y!jX$Z!jX$[!jX$]!jX$^!jX$_!jX$`!jX$a!jX$b!jX$c!jX$d!jX$e!jX$f!jX$g!jX$h!jX$i!jX$j!jX$k!jX$l!jX$m!jX$n!jX$o!jX$p!jX$q!jX$r!jX$s!jX$t!jX$u!jX$v!jX$w!jX$x!jX$y!jX$z!jX${!jX$|!jX$}!jX%O!jX%P!jX%Q!jX%R!jX%S!jX%T!jX%U!jX%`!jX%q!jX%r!jX%s!jX%t!jX%y!jX%|!jX&O!jXq!jXt!jX!O!jX~O%f!eOQ!mX]!mX_!mXg!mXh!mXi!mXj!mXk!mXp!mXr!mXu!mXx!mXy!mXz!mX{!mX|!mX!P!mX!R!mX!T!mX!V!mX!W!mX![!mX!]!mX!^!mX!c!mX!d!mX!e!mX!h!mX!k!mX!n!mX!s!mX!w!mX!x!mX!y!mX!z!mX!{!mX!|!mX!}!mX#O!mX#P!mX#Q!mX#R!mX#S!mX#T!mX#U!mX#V!mX#W!mX#X!mX#Y!mX#Z!mX#[!mX#]!mX#^!mX#_!mX#`!mX#a!mX#b!mX#c!mX#d!mX#e!mX#f!mX#g!mX#h!mX#i!mX#j!mX#k!mX#l!mX#m!mX#n!mX#o!mX#p!mX#q!mX#r!mX#s!mX#t!mX#u!mX#v!mX#w!mX#x!mX#y!mX#z!mX#{!mX#|!mX#}!mX$O!mX$P!mX$Q!mX$R!mX$S!mX$T!mX$U!mX$V!mX$W!mX$X!mX$Y!mX$Z!mX$[!mX$]!mX$^!mX$_!mX$`!mX$a!mX$b!mX$c!mX$d!mX$e!mX$f!mX$g!mX$h!mX$i!mX$j!mX$k!mX$l!mX$m!mX$n!mX$o!mX$p!mX$q!mX$r!mX$s!mX$t!mX$u!mX$v!mX$w!mX$x!mX$y!mX$z!mX${!mX$|!mX$}!mX%O!mX%P!mX%Q!mX%R!mX%S!mX%T!mX%U!mX%`!mX%q!mX%r!mX%s!mX%t!mX%y!mX%|!mX&O!mXq!mXt!mX!O!mX~O`!hOa!hOb!hOc!hOd!hOe!hOf!hO!P!gO!b!hO!c!hO!d!hO!e!hO%k!kO%l!hO~O%p!kO~P6XOR!nO%c!nO~P+ZOq!oO~PfOt!pO~PfO!O!qO~PfOQSO]YO__Og_Oh_Oi_Oj_Ok_Op_OrTOuUOxcOycOzcO{cO|cO!PVO!TdO!VeO!WeO![fO!]fO!^fO!c_O!d_O!e_O!hgO!khO!niO!s_O!w_O!xjO!yjO!zjO!{jO!|jO!}jO#OjO#PjO#QjO#RjO#SjO#TjO#UjO#VjO#WjO#XjO#YjO#ZjO#[jO#]jO#^jO#_jO#`jO#ajO#bjO#cjO#djO#ejO#fjO#gjO#hjO#ijO#jjO#kjO#ljO#mjO#njO#ojO#pjO#qjO#rjO#sjO#tjO#ujO#vjO#wjO#xjO#yjO#zjO#{jO#|jO#}jO$OjO$PjO$QjO$RjO$SjO$TjO$UjO$VjO$WjO$XjO$YjO$ZjO$[jO$]jO$^jO$_jO$`jO$ajO$bjO$cjO$djO$ejO$fjO$gjO$hjO$ijO$jjO$kjO$ljO$mjO$njO$ojO$pjO$qjO$rjO$sjO$tjO$ujO$vjO$wjO$xjO$yjO$zjO${jO$|jO$}jO%OjO%PjO%QjO%R_O%S_O%T_O%U_O%q]O%r^O%sZO%tWO%yZO%|[O&O_O~O!R!sO~P!2PO%o!tO~P6XO!O!vO~PfO]YOrTOuUO%q!xO!P%}X!b%}X!c%}X!d%}X!e%}X%m%}X%n%}X%p%}X&O%}X%o%}X~O%p!|O~P6XO!R#PO~P7aO!O#RO%h#QO~O!O#UO~PfO%o#XO~P6XO!O#YO~PfO]YOrTOuUO%q!xO!P%}a!b%}a!c%}a!d%}a!e%}a%m%}a%n%}a%p%}a&O%}a%o%}a~O`!hOa!hOb!hOc!hOd!hOe!hOf!hO!P!gO!b!hO!c!hO!d!hO!e!hO%l!hO~O%i#]O~P!>pO!O#^O~O!R#_O~P7aO!O#`O~PfO%i#aO~P!>pOml!d!e!cp!w%R&O%T%U~",
|
|
1672
|
+
goto: ",r&RPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP&SPPP&mPP&m&SPPPPP'[PP'_P&SP'xPP'{(O&SPPP(i(l(vPPPP(y&SP)P&SP)S&SP)V&m&S&SP&S)Y)YPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP)b)|*S*g*q+PPPPPPPPPPPPPPPPPPPPPPPP+V+eP,Q,`P,fP,n&S!P_OTUVXbcdefqsuw}!W!Y![!`!g!w#T#V!O_OTUVXbcdefqsuw}!W!Y![!`!g!w#T#VT!x!P!zR!Wch`OTUVbqsu}!g!w#V[xXcde!W!YR!swR!YdR!Ye!PXOTUVXbcdefqsuw}!W!Y![!`!g!w#T#VR!`fQ!_fS#P![!`R#_#TR#S!^X!hk!j#Q#[R!bgR!dhR!fi]!O^l|!R!l!uQbOQqTQsUQuV[!Tbqsu!w#VQ!w}R#V!gQnSR!mn[wXcde!W!YQ![fU!rw![#TR#T!`Q!jkS#W!j#[R#[#QQ!R^Q!llQ!u|V!{!R!l!uQ!z!PR#Z!ziaOTUVbqsu}!g!w#Vh`OTUVbqsu}!g!w#VgvXcdefw!W!Y![!`#TUyXdeQ!VcQ!}!WR#O!YX!ik!j#Q#[]!Q^l|!R!l!uT!y!P!z",
|
|
1673
|
+
nodeNames: "⚠ BlockComment PodDirective PodDirective PodDirective PodHeading PodStrong PodEmphasis PodCode PodLink PodFormat Heredoc Heredoc . MethodName Version CharacterClass Assertion StringLiteral Operator LineComment BlockComment VariableName StringLiteral Regex Regex Operator VariableName LineComment DocComment Program Pod Number ) ( Parens ] [ Brackets PackageDeclaration class role grammar module package PackageName } { Block ; RoutineDeclaration sub RoutineName method submethod RoutineName RoutineName RegexDeclaration token rule regex RegexName Block Regex Escape VariableName AttributeName SpecialVariable Block EnumDeclaration enum EnumName SubsetDeclaration subset SubsetName ConstantDeclaration constant ConstantName MethodCall Identifier TypeName Number StringLiteral NestedDelimiters Interpolation PairKey my our has state temp let anon augment supersede unit multi proto only if elsif else unless with orwith without for while until loop repeat given when default do gather take try return next last redo proceed succeed react whenever supply emit start use need import require no is does of returns handles where will trusts hides and or not xor so andthen orelse notandthen div mod gcd lcm eq ne lt gt le ge cmp leg eqv but self True False Nil BEGIN CHECK INIT END ENTER LEAVE KEEP UNDO FIRST NEXT LAST PRE POST CATCH CONTROL QUIT CLOSE DOC Operator , : . Regex Heredoc StringLiteral",
|
|
1674
|
+
maxTerm: 232,
|
|
894
1675
|
context: trackContext,
|
|
895
1676
|
nodeProps: [
|
|
896
|
-
["openedBy",
|
|
897
|
-
["closedBy",
|
|
1677
|
+
["openedBy", 33,"(",36,"[",46,"{"],
|
|
1678
|
+
["closedBy", 34,")",37,"]",47,"}"]
|
|
898
1679
|
],
|
|
899
|
-
skippedNodes: [0,1,
|
|
900
|
-
repeatNodeCount:
|
|
901
|
-
tokenData: "!$`~R!aX^%Wpq%Wqr%{st']tu(|uv5Rvw8axy:Wyz:]z{%{{|%{|}:b}!O%{!O!P:g!P!Q%{!Q!R;V!R![=f![!]@Z!]!^FQ!^!_%{!_!`%{!`!a%{!a!b%{!b!cFV!c!}Gy!}#OJu#O#PJz#P#Q! T#Q#R%{#R#S! Y#T#o! Y#o#p!$U#p#q%{#q#r!$Z#r#s%{#y#z%W$f$g%W$p$q! Y$q$r%{${$|! Y%Q%R! Y%R%S%{%W%o! Y%o%p%{%p&a! Y&a&b%{&b#BY! Y#BY#BZ%W#BZ$IS! Y$IS$I_%W$Iz$I{%{$I|$JO%W$JT$JU%W$KV$KW%W%&Y%+t%{&/x&Et! Y&FU&FV%W&Gh;'S! Y;'S;=`!#x?&r?Bj! Y?Hw?Ic! Y?Ii?JT! Y?J`?L{! Y~%]Y
|
|
902
|
-
tokenizers: [blockComment, podToken, heredocToken, methodDotToken, nameToken,
|
|
903
|
-
topRules: {"Program":[0,
|
|
904
|
-
specialized: [{term:
|
|
905
|
-
tokenPrec:
|
|
1680
|
+
skippedNodes: [0,1,11,12,28,29],
|
|
1681
|
+
repeatNodeCount: 6,
|
|
1682
|
+
tokenData: "!$`~R!aX^%Wpq%Wqr%{st']tu(|uv5Rvw8axy:Wyz:]z{%{{|%{|}:b}!O%{!O!P:g!P!Q%{!Q!R;V!R![=f![!]@Z!]!^FQ!^!_%{!_!`%{!`!a%{!a!b%{!b!cFV!c!}Gy!}#OJu#O#PJz#P#Q! T#Q#R%{#R#S! Y#T#o! Y#o#p!$U#p#q%{#q#r!$Z#r#s%{#y#z%W$f$g%W$p$q! Y$q$r%{${$|! Y%Q%R! Y%R%S%{%W%o! Y%o%p%{%p&a! Y&a&b%{&b#BY! Y#BY#BZ%W#BZ$IS! Y$IS$I_%W$Iz$I{%{$I|$JO%W$JT$JU%W$KV$KW%W%&Y%+t%{&/x&Et! Y&FU&FV%W&Gh;'S! Y;'S;=`!#x?&r?Bj! Y?Hw?Ic! Y?Ii?JT! Y?J`?L{! Y~%]Y%v~X^%Wpq%W#y#z%W$f$g%W#BY#BZ%W$IS$I_%W$I|$JO%W$JT$JU%W$KV$KW%W&FU&FV%WP&Qc%RPqr%{z{%{{|%{}!O%{!P!Q%{!^!_%{!_!`%{!`!a%{!a!b%{#O#P%{#Q#R%{#p#q%{#r#s%{$q$r%{%R%S%{%o%p%{&a&b%{$Iz$I{%{%&Y%+t%{~'bWl~OY'zZ!_'z!_!`(c!`#p'z#p#q(c#q;'S'z;'S;=`(]<%lO'z~(PSl~OY'zZ;'S'z;'S;=`(]<%lO'z~(`P;=`<%l'z~(jSm~l~OY(cZ;'S(c;'S;=`(v<%lO(c~(yP;=`<%l(c~)Rk&O~qr*vz{.|!O!P-h!P!Q1x!Q![1}![!]2V!_!`.|!a!b.|!c!}3_#Q#R2V#R#S3_#T#o3_#r#s.|$p$q3_${$|3_%Q%R3_%W%o3_%p&a3_&b#BY3_#BZ$IS3_&/x&Et3_&Gh;'S3_;'S;=`4{?&r?Bj3_?Hw?Ic3_?Ii?JT3_?J`?L{3_~*{a!c~!c!},Q#R#S,Q#T#o,Q$p$q,Q${$|,Q%Q%R,Q%W%o,Q%p&a,Q&b#BY,Q#BZ$IS,Q&/x&Et,Q&Gh;'S,Q;'S;=`.p?&r?Bj,Q?Hw?Ic,Q?Ii?JT,Q?J`?L{,Q~,Ve!d~wx-h}!O-h!Q![,Q![!].v!c!},Q#R#S,Q#T#o,Q$p$q,Q${$|,Q%Q%R,Q%W%o,Q%p&a,Q&b#BY,Q#BZ$IS,Q&/x&Et,Q&Gh;'S,Q;'S;=`.p?&r?Bj,Q?Hw?Ic,Q?Ii?JT,Q?J`?L{,Q~-ka!c!},Q#R#S,Q#T#o,Q$p$q,Q${$|,Q%Q%R,Q%W%o,Q%p&a,Q&b#BY,Q#BZ$IS,Q&/x&Et,Q&Gh;'S,Q;'S;=`.p?&r?Bj,Q?Hw?Ic,Q?Ii?JT,Q?J`?L{,Q~.sP;=`<%l,Q~.yP![!]-h~/Pa!c!}0U#R#S0U#T#o0U$p$q0U${$|0U%Q%R0U%W%o0U%p&a0U&b#BY0U#BZ$IS0U&/x&Et0U&Gh;'S0U;'S;=`1r?&r?Bj0U?Hw?Ic0U?Ii?JT0U?J`?L{0U~0Ze!e~wx.|}!O.|!Q![0U![!]1l!c!}0U#R#S0U#T#o0U$p$q0U${$|0U%Q%R0U%W%o0U%p&a0U&b#BY0U#BZ$IS0U&/x&Et0U&Gh;'S0U;'S;=`1r?&r?Bj0U?Hw?Ic0U?Ii?JT0U?J`?L{0U~1oP![!].|~1uP;=`<%l0U~1}O!c~~2SP!c~!Q![1}~2Ya!c!}3_#R#S3_#T#o3_$p$q3_${$|3_%Q%R3_%W%o3_%p&a3_&b#BY3_#BZ$IS3_&/x&Et3_&Gh;'S3_;'S;=`4{?&r?Bj3_?Hw?Ic3_?Ii?JT3_?J`?L{3_~3de!c~wx2V}!O2V!Q![3_![!]4u!c!}3_#R#S3_#T#o3_$p$q3_${$|3_%Q%R3_%W%o3_%p&a3_&b#BY3_#BZ$IS3_&/x&Et3_&Gh;'S3_;'S;=`4{?&r?Bj3_?Hw?Ic3_?Ii?JT3_?J`?L{3_~4xP![!]2V~5OP;=`<%l3_~5Wj%RPqr-huv6xz{.|!O!P-h![!]2V!_!`7V!a!b.|!c!}3_#Q#R2V#R#S3_#T#o3_#r#s.|$p$q3_${$|3_%Q%R3_%W%o3_%p&a3_&b#BY3_#BZ$IS3_&/x&Et3_&Gh;'S3_;'S;=`4{?&r?Bj3_?Hw?Ic3_?Ii?JT3_?J`?L{3_P6}P%RP!_!`7QP7VO%RP~7[a%RP!c!}0U#R#S0U#T#o0U$p$q0U${$|0U%Q%R0U%W%o0U%p&a0U&b#BY0U#BZ$IS0U&/x&Et0U&Gh;'S0U;'S;=`1r?&r?Bj0U?Hw?Ic0U?Ii?JT0U?J`?L{0U~8fj%RPqr-hvw6xz{.|!O!P-h![!]2V!_!`7V!a!b.|!c!}3_#Q#R2V#R#S3_#T#o3_#r#s.|$p$q3_${$|3_%Q%R3_%W%o3_%p&a3_&b#BY3_#BZ$IS3_&/x&Et3_&Gh;'S3_;'S;=`4{?&r?Bj3_?Hw?Ic3_?Ii?JT3_?J`?L{3_~:]Or~~:bOq~~:gO%S~~:lQ%U~!O!P:r!_!`7QP:wQ%RP!O!P:}#Q#R7QP;SP%RP#Q#R7Q~;[^!s~!O!P<W!Q![=f!d!e>T!f!g>o!g!h<r!q!r?T!z!{?i#R#S=}#U#V>T#W#X>o#X#Y<r#]#^=a#c#d?T#l#m?i~<ZP!Q![<^~<cT!s~!Q![<^!g!h<r#R#S<W#X#Y<r#]#^=a~<uR{|=O}!O=O!Q![=U~=RP!Q![=U~=ZQ!s~!Q![=U#]#^=a~=fO!s~~=kU!s~!O!P<W!Q![=f!g!h<r#R#S=}#X#Y<r#]#^=a~>QP!Q![=f~>WR!Q!R>a!R!S>a#R#S>a~>fR!s~!Q!R>a!R!S>a#R#S>a~>rQ!Q![>x#R#S>x~>}Q!s~!Q![>x#R#S>x~?WQ!Q!Y?^#R#S?^~?cQ!s~!Q!Y?^#R#S?^~?lS!Q![?x!c!i?x#R#S?x#T#Z?x~?}S!s~!Q![?x!c!i?x#R#S?x#T#Z?x~@`e%T~qrAq!Q![Dm![!]Et!_!`7Q!c!}By#R#SBy#T#oBy$p$qBy${$|By%Q%RBy%W%oBy%p&aBy&b#BYBy#BZ$ISBy&/x&EtBy&Gh;'SBy;'S;=`Dg?&r?BjBy?Hw?IcBy?Ii?JTBy?J`?L{By~Ata!c!}By#R#SBy#T#oBy$p$qBy${$|By%Q%RBy%W%oBy%p&aBy&b#BYBy#BZ$ISBy&/x&EtBy&Gh;'SBy;'S;=`Dg?&r?BjBy?Hw?IcBy?Ii?JTBy?J`?L{By~COe!w~wxAq}!OAq!Q![By![!]Da!c!}By#R#SBy#T#oBy$p$qBy${$|By%Q%RBy%W%oBy%p&aBy&b#BYBy#BZ$ISBy&/x&EtBy&Gh;'SBy;'S;=`Dg?&r?BjBy?Hw?IcBy?Ii?JTBy?J`?L{By~DdP![!]Aq~DjP;=`<%lBy~DpQ!Q![Dm!^!_Dv~DyT!O!PEY!Q![EY!c!}EY#R#SEY#T#oEY~E]U!O!PEY!Q![EY!`!aEo!c!}EY#R#SEY#T#oEY~EtOp~PEwP![!]EzPE}P!_!`7Q~FVO!R~~F[i&O~qr-hz{.|!O!P-h![!]2V!_!`.|!a!b.|!c!}3_#Q#R2V#R#S3_#T#o3_#r#s.|$p$q3_${$|3_%Q%R3_%W%o3_%p&a3_&b#BY3_#BZ$IS3_&/x&Et3_&Gh;'S3_;'S;=`4{?&r?Bj3_?Hw?Ic3_?Ii?JT3_?J`?L{3_~HOe%|~wxIa}!OIa!Q![Gy![!]Jo!c!}Gy#R#SGy#T#oGy$p$qGy${$|Gy%Q%RGy%W%oGy%p&aGy&b#BYGy#BZ$ISGy&/x&EtGy&Gh;'SGy;'S;=`Ji?&r?BjGy?Hw?IcGy?Ii?JTGy?J`?L{Gy~Ida!c!}Gy#R#SGy#T#oGy$p$qGy${$|Gy%Q%RGy%W%oGy%p&aGy&b#BYGy#BZ$ISGy&/x&EtGy&Gh;'SGy;'S;=`Ji?&r?BjGy?Hw?IcGy?Ii?JTGy?J`?L{Gy~JlP;=`<%lGy~JrP![!]Ia~JzOu~RKPu%RPOqMdqrMirzMdz{Mi{|Mi|}Md}!OMi!O!PMd!P!QMi!Q!^Md!^!_Mi!_!`Mi!`!aMi!a!bMi!b#OMd#O#PMi#P#QMd#Q#RMi#R#pMd#p#qMi#q#rMd#r#sMi#s$qMd$q$rMi$r%RMd%R%SMi%S%oMd%o%pMi%p&aMd&a&bMi&b$IzMd$Iz$I{Mi$I{%&YMd%&Y%+tMi%+t;'SMd;'S;=`N{;=`OMdQMiO!bQRMpc!bQ%RPqr%{z{%{{|%{}!O%{!P!Q%{!^!_%{!_!`%{!`!a%{!a!b%{#O#P%{#Q#R%{#p#q%{#r#s%{$q$r%{%R%S%{%o%p%{&a&b%{$Iz$I{%{%&Y%+t%{Q! QP!bQ;=`<%lMd~! YOt~~! _e%y~wx!!p}!O!!p!Q![! Y![!]!$O!c!}! Y#R#S! Y#T#o! Y$p$q! Y${$|! Y%Q%R! Y%W%o! Y%p&a! Y&b#BY! Y#BZ$IS! Y&/x&Et! Y&Gh;'S! Y;'S;=`!#x?&r?Bj! Y?Hw?Ic! Y?Ii?JT! Y?J`?L{! Y~!!sa!c!}! Y#R#S! Y#T#o! Y$p$q! Y${$|! Y%Q%R! Y%W%o! Y%p&a! Y&b#BY! Y#BZ$IS! Y&/x&Et! Y&Gh;'S! Y;'S;=`!#x?&r?Bj! Y?Hw?Ic! Y?Ii?JT! Y?J`?L{! Y~!#{P;=`<%l! Y~!$RP![!]!!p~!$ZO!P~~!$`O!O~",
|
|
1683
|
+
tokenizers: [smileyToken, blockComment, podToken, heredocToken, methodDotToken, nameToken, versionToken, regexToken, quoteToken, termToken, 0, 1],
|
|
1684
|
+
topRules: {"Program":[0,30],"Regex":[1,191],"Heredoc":[2,192],"StringLiteral":[3,193]},
|
|
1685
|
+
specialized: [{term: 225, get: (value) => spec_identifier[value] || -1},{term: 228, get: (value) => spec_capIdentifier[value] || -1}],
|
|
1686
|
+
tokenPrec: 3556
|
|
906
1687
|
});
|
|
907
1688
|
|
|
908
1689
|
// The keywords of the grammar, with the tag each is highlighted with.
|
|
@@ -921,6 +1702,279 @@ const keywordTags = {
|
|
|
921
1702
|
self: tags.self
|
|
922
1703
|
};
|
|
923
1704
|
|
|
1705
|
+
const scopes = new NodeWeakMap();
|
|
1706
|
+
const scopeNodes = /^(Block|Interpolation|Program)$/;
|
|
1707
|
+
const variableNodes = /^(VariableName|AttributeName)$/;
|
|
1708
|
+
const declarators = /^(my|our|has|state)$/;
|
|
1709
|
+
/// Whether a variable is a placeholder, which is declared by being
|
|
1710
|
+
/// used: $^a, and the named $:a
|
|
1711
|
+
function isPlaceholder(name) { return /^[$@%&][\^:]/.test(name); }
|
|
1712
|
+
// The name of the variable without a sigil that the backslash `node`
|
|
1713
|
+
// comes right before, as in `\x`. A capitalized one reads as a type.
|
|
1714
|
+
function sigillessName(state, node) {
|
|
1715
|
+
let next = node.nextSibling;
|
|
1716
|
+
return node.name == "Operator" && next && (next.name == "Identifier" || next.name == "TypeName") &&
|
|
1717
|
+
next.from == node.to && state.sliceDoc(node.from, node.to) == "\\" ? next : null;
|
|
1718
|
+
}
|
|
1719
|
+
// Whether a block is a subscript, as in %h{$key}: it touches the term
|
|
1720
|
+
// before it. It is not a scope for placeholder variables.
|
|
1721
|
+
function isSubscript(block) {
|
|
1722
|
+
let prev = block.prevSibling;
|
|
1723
|
+
return block.name == "Block" && prev != null && prev.to == block.from && /^[A-Z]/.test(prev.name) && prev.name != "Operator";
|
|
1724
|
+
}
|
|
1725
|
+
/// Whether a completion is for a name with a sigil.
|
|
1726
|
+
function hasSigil(completion) { return /^[$@%&]/.test(completion.label); }
|
|
1727
|
+
function variable(label) {
|
|
1728
|
+
return { label, type: label[0] == "&" ? "function" : /^.[!.]/.test(label) ? "property" : "variable", boost: 1 };
|
|
1729
|
+
}
|
|
1730
|
+
function isArrow(text) { return text == "->" || text == "<->"; }
|
|
1731
|
+
class Gatherer {
|
|
1732
|
+
constructor(state) {
|
|
1733
|
+
this.state = state;
|
|
1734
|
+
this.names = [];
|
|
1735
|
+
this.methods = [];
|
|
1736
|
+
}
|
|
1737
|
+
text(node) { return this.state.sliceDoc(node.from, node.to); }
|
|
1738
|
+
declare(node) {
|
|
1739
|
+
let label = this.text(node);
|
|
1740
|
+
// A name without a sigil: my \x
|
|
1741
|
+
if (!variableNodes.test(node.name))
|
|
1742
|
+
return this.names.push({ label, type: "variable", boost: 1 });
|
|
1743
|
+
this.names.push(variable(label));
|
|
1744
|
+
// `has $.x` also makes the private `$!x`, and `$^a` can be written `$a`.
|
|
1745
|
+
if (label[1] == ".")
|
|
1746
|
+
this.names.push(variable(label[0] + "!" + label.slice(2)));
|
|
1747
|
+
else if (isPlaceholder(label))
|
|
1748
|
+
this.names.push(variable(label[0] + label.slice(2)));
|
|
1749
|
+
}
|
|
1750
|
+
// Declares the name after `node`, when `node` is the backslash of a
|
|
1751
|
+
// variable without a sigil: my \x = 1, sub f(\x) { }
|
|
1752
|
+
declareSigilless(node) {
|
|
1753
|
+
let name = sigillessName(this.state, node);
|
|
1754
|
+
if (name)
|
|
1755
|
+
this.declare(name);
|
|
1756
|
+
return name != null;
|
|
1757
|
+
}
|
|
1758
|
+
// The variables of a signature, or of the parentheses of `my (...)`.
|
|
1759
|
+
// What follows a `=` or a `where`, up to the next comma, is a default
|
|
1760
|
+
// value or a constraint, and uses variables rather than declaring them.
|
|
1761
|
+
declareAll(node) {
|
|
1762
|
+
let uses = false;
|
|
1763
|
+
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
1764
|
+
let { name } = child;
|
|
1765
|
+
if (name == ",")
|
|
1766
|
+
uses = false;
|
|
1767
|
+
else if (name == "where" || name == "Operator" && this.text(child) == "=")
|
|
1768
|
+
uses = true;
|
|
1769
|
+
else if (uses)
|
|
1770
|
+
continue;
|
|
1771
|
+
else if (this.declareSigilless(child))
|
|
1772
|
+
continue;
|
|
1773
|
+
else if (variableNodes.test(name))
|
|
1774
|
+
this.declare(child);
|
|
1775
|
+
else if (name == "Parens" || name == "Brackets")
|
|
1776
|
+
this.declareAll(child);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
addMethods(scope) {
|
|
1780
|
+
for (let method of scopeOf(scope, this.state).methods)
|
|
1781
|
+
this.methods.push(method);
|
|
1782
|
+
}
|
|
1783
|
+
// Goes over the content of a node that is not a scope of its own.
|
|
1784
|
+
content(node) {
|
|
1785
|
+
// Whether a declarator was seen, and what it declares is yet to come.
|
|
1786
|
+
let declaring = false;
|
|
1787
|
+
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
1788
|
+
let { name } = child;
|
|
1789
|
+
if (declarators.test(name)) {
|
|
1790
|
+
declaring = true;
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
// my Int $x, my Array[Int] @y
|
|
1794
|
+
if (declaring && (name == "TypeName" || name == "Brackets"))
|
|
1795
|
+
continue;
|
|
1796
|
+
// A placeholder variable declares itself where it is used: { $^a + $^b }
|
|
1797
|
+
if (name == "VariableName" && isPlaceholder(this.text(child)))
|
|
1798
|
+
this.declare(child);
|
|
1799
|
+
else if (declaring && this.declareSigilless(child))
|
|
1800
|
+
continue;
|
|
1801
|
+
else if (declaring && variableNodes.test(name))
|
|
1802
|
+
this.declare(child);
|
|
1803
|
+
else if (declaring && name == "Parens")
|
|
1804
|
+
this.declareAll(child);
|
|
1805
|
+
else if (scopeNodes.test(name)) {
|
|
1806
|
+
// The placeholders in a subscript are those of the block around it.
|
|
1807
|
+
if (isSubscript(child))
|
|
1808
|
+
this.placeholders(child);
|
|
1809
|
+
this.addMethods(child);
|
|
1810
|
+
}
|
|
1811
|
+
else if (/Declaration$/.test(name))
|
|
1812
|
+
this.declaration(child);
|
|
1813
|
+
else if (child.firstChild)
|
|
1814
|
+
this.content(child);
|
|
1815
|
+
// The name of a constant with a sigil is a variable after the
|
|
1816
|
+
// declaration node: constant $LIMIT = 3
|
|
1817
|
+
declaring = name == "ConstantDeclaration" && child.lastChild.name == "constant";
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
// The placeholder variables in a node, and in the subscripts in it.
|
|
1821
|
+
placeholders(node) {
|
|
1822
|
+
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
1823
|
+
if (child.name == "VariableName") {
|
|
1824
|
+
if (isPlaceholder(this.text(child)))
|
|
1825
|
+
this.declare(child);
|
|
1826
|
+
}
|
|
1827
|
+
else if (child.firstChild && (!scopeNodes.test(child.name) || isSubscript(child)))
|
|
1828
|
+
this.placeholders(child);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
declaration(node) {
|
|
1832
|
+
let kind = node.firstChild.name;
|
|
1833
|
+
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
1834
|
+
let { name } = child;
|
|
1835
|
+
if (name == "RoutineName") {
|
|
1836
|
+
let label = this.text(child);
|
|
1837
|
+
// An operator is not called by its name: infix:<+>
|
|
1838
|
+
if (/:[<«\[]/.test(label))
|
|
1839
|
+
continue;
|
|
1840
|
+
// `multi foo` has no declarator, and declares a sub.
|
|
1841
|
+
if (kind == "sub" || kind == "RoutineName")
|
|
1842
|
+
this.names.push({ label, type: "function", boost: 1 }, variable("&" + label));
|
|
1843
|
+
// A private method is called as self!name, where nothing is completed.
|
|
1844
|
+
else if (/^[^!^]/.test(label))
|
|
1845
|
+
this.methods.push({ label, type: "method", boost: 1 });
|
|
1846
|
+
}
|
|
1847
|
+
else if (name == "PackageName") {
|
|
1848
|
+
this.names.push({ label: this.text(child), type: "class", boost: 1 });
|
|
1849
|
+
}
|
|
1850
|
+
else if (name == "EnumName" || name == "SubsetName") {
|
|
1851
|
+
this.names.push({ label: this.text(child), type: "type", boost: 1 });
|
|
1852
|
+
}
|
|
1853
|
+
else if (name == "ConstantName") {
|
|
1854
|
+
let label = this.text(child);
|
|
1855
|
+
this.names.push(/^[$@%&]/.test(label) ? variable(label) : { label, type: "constant", boost: 1 });
|
|
1856
|
+
}
|
|
1857
|
+
else if (scopeNodes.test(name)) {
|
|
1858
|
+
this.addMethods(child);
|
|
1859
|
+
}
|
|
1860
|
+
else if (name != "Parens" && child.firstChild) {
|
|
1861
|
+
// The signature belongs to the scope of the body.
|
|
1862
|
+
this.content(child);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
// The parameters of a block: those of the routine that it is the body
|
|
1867
|
+
// of, or the ones after the arrow of a pointy block.
|
|
1868
|
+
parameters(block) {
|
|
1869
|
+
let parent = block.parent;
|
|
1870
|
+
if (parent && parent.name == "RoutineDeclaration") {
|
|
1871
|
+
for (let child = parent.firstChild; child; child = child.nextSibling)
|
|
1872
|
+
if (child.name == "Parens")
|
|
1873
|
+
this.declareAll(child);
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
// Going backwards, the variables of a parameter come before the
|
|
1877
|
+
// `=` of its default value.
|
|
1878
|
+
let found = [], parameter = [];
|
|
1879
|
+
for (let prev = block.prevSibling; prev; prev = prev.prevSibling) {
|
|
1880
|
+
let { name } = prev;
|
|
1881
|
+
if (name == ";" || name == "Block" || /Declaration$/.test(name))
|
|
1882
|
+
return;
|
|
1883
|
+
if (name == "VariableName") {
|
|
1884
|
+
parameter.push(prev);
|
|
1885
|
+
}
|
|
1886
|
+
else if (prev.prevSibling && sigillessName(this.state, prev.prevSibling)) {
|
|
1887
|
+
// -> \item { }
|
|
1888
|
+
parameter.push(prev);
|
|
1889
|
+
}
|
|
1890
|
+
else if (name == ",") {
|
|
1891
|
+
found = found.concat(parameter);
|
|
1892
|
+
parameter = [];
|
|
1893
|
+
}
|
|
1894
|
+
else if (name == "Operator") {
|
|
1895
|
+
let text = this.text(prev);
|
|
1896
|
+
if (text == "=")
|
|
1897
|
+
parameter = [];
|
|
1898
|
+
else if (isArrow(text)) {
|
|
1899
|
+
for (let node of found.concat(parameter))
|
|
1900
|
+
this.declare(node);
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
// The names that the scope node `node` declares in its content. The
|
|
1908
|
+
// parameters of a block are not part of its node, and are left out.
|
|
1909
|
+
function scopeOf(node, state) {
|
|
1910
|
+
let scope = scopes.get(node);
|
|
1911
|
+
if (scope)
|
|
1912
|
+
return scope;
|
|
1913
|
+
let gatherer = new Gatherer(state);
|
|
1914
|
+
gatherer.content(node);
|
|
1915
|
+
scope = { names: gatherer.names, methods: gatherer.methods };
|
|
1916
|
+
scopes.set(node, scope);
|
|
1917
|
+
return scope;
|
|
1918
|
+
}
|
|
1919
|
+
/// The names that are in scope at `node`, innermost scope first, and
|
|
1920
|
+
/// the methods of the whole document.
|
|
1921
|
+
function localNames(node, state) {
|
|
1922
|
+
let names = [], methods = [];
|
|
1923
|
+
for (let cur = node; cur; cur = cur.parent) {
|
|
1924
|
+
if (!scopeNodes.test(cur.name))
|
|
1925
|
+
continue;
|
|
1926
|
+
let scope = scopeOf(cur, state);
|
|
1927
|
+
// An edit to the parameters does not replace the block's node, so
|
|
1928
|
+
// these are read every time.
|
|
1929
|
+
let parameters = new Gatherer(state);
|
|
1930
|
+
parameters.parameters(cur);
|
|
1931
|
+
for (let name of parameters.names)
|
|
1932
|
+
names.push(name);
|
|
1933
|
+
for (let name of scope.names)
|
|
1934
|
+
names.push(name);
|
|
1935
|
+
methods = scope.methods;
|
|
1936
|
+
}
|
|
1937
|
+
return { names, methods };
|
|
1938
|
+
}
|
|
1939
|
+
/// Whether a variable is in a place where variables are declared, so
|
|
1940
|
+
/// that its name is a new one. The variable is a child of `parent`, and
|
|
1941
|
+
/// comes after the sibling `prev`.
|
|
1942
|
+
function declaresVariable(state, parent, prev) {
|
|
1943
|
+
let text = (node) => state.sliceDoc(node.from, node.to);
|
|
1944
|
+
// A signature, or the list of `my ($a, $b)`, unless the variable is
|
|
1945
|
+
// in a default value or in a constraint.
|
|
1946
|
+
if (parent.name == "Parens") {
|
|
1947
|
+
let before = parent.prevSibling;
|
|
1948
|
+
if (parent.parent && parent.parent.name == "RoutineDeclaration" || before && declarators.test(before.name)) {
|
|
1949
|
+
for (let node = prev; node && node.name != ","; node = node.prevSibling)
|
|
1950
|
+
if (node.name == "where" || node.name == "Operator" && text(node) == "=")
|
|
1951
|
+
return false;
|
|
1952
|
+
return true;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
// After a declarator, or after the arrow of a pointy block.
|
|
1956
|
+
let first = true;
|
|
1957
|
+
for (let node = prev; node; node = node.prevSibling) {
|
|
1958
|
+
let { name } = node;
|
|
1959
|
+
// `my $a, $b` declares the first one only.
|
|
1960
|
+
if (declarators.test(name))
|
|
1961
|
+
return first;
|
|
1962
|
+
if (name == ";" || name == "Block" || name == "{" || /Declaration$/.test(name))
|
|
1963
|
+
return false;
|
|
1964
|
+
if (name == "Operator") {
|
|
1965
|
+
let operator = text(node);
|
|
1966
|
+
if (isArrow(operator))
|
|
1967
|
+
return true;
|
|
1968
|
+
if (operator == "=")
|
|
1969
|
+
return false;
|
|
1970
|
+
}
|
|
1971
|
+
// Only a type can stand between a declarator and its variable.
|
|
1972
|
+
if (name != "TypeName" && name != "Brackets")
|
|
1973
|
+
first = false;
|
|
1974
|
+
}
|
|
1975
|
+
return false;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
924
1978
|
function completions(type, ...labels) {
|
|
925
1979
|
return labels.join(" ").split(" ").map(label => ({ label, type }));
|
|
926
1980
|
}
|
|
@@ -937,6 +1991,7 @@ const globals = [...keywords, ...constants, ...types, ...routines];
|
|
|
937
1991
|
// `:key` is not a name from the list.
|
|
938
1992
|
const nameTail = /^(\w[\w:'\-]*)?$/;
|
|
939
1993
|
const specialVariableTail = /^[$@%&][*?][\w\-]*$/;
|
|
1994
|
+
const variableTail = /^[$@%&][!.^:]?[\w'\-]*$/;
|
|
940
1995
|
// Whether the position of `node` holds code, as opposed to the text of
|
|
941
1996
|
// a string, a regex, a comment or Pod. An interpolation is code again.
|
|
942
1997
|
function inCode(node) {
|
|
@@ -963,8 +2018,26 @@ function isPrivateMethodName(tree, state, node) {
|
|
|
963
2018
|
return invocant.to == mark.from &&
|
|
964
2019
|
/^(self|VariableName|AttributeName|SpecialVariable|Identifier|TypeName|Parens|Brackets|[)\]])$/.test(invocant.name);
|
|
965
2020
|
}
|
|
966
|
-
|
|
967
|
-
|
|
2021
|
+
// The names declared in the document, followed by the built-in ones
|
|
2022
|
+
// that they do not hide.
|
|
2023
|
+
function withLocal(local, builtin) {
|
|
2024
|
+
if (!local.length)
|
|
2025
|
+
return builtin;
|
|
2026
|
+
let labels = new Set(), result = [];
|
|
2027
|
+
for (let completion of local) {
|
|
2028
|
+
if (labels.has(completion.label))
|
|
2029
|
+
continue;
|
|
2030
|
+
labels.add(completion.label);
|
|
2031
|
+
result.push(completion);
|
|
2032
|
+
}
|
|
2033
|
+
for (let completion of builtin)
|
|
2034
|
+
if (!labels.has(completion.label))
|
|
2035
|
+
result.push(completion);
|
|
2036
|
+
return result;
|
|
2037
|
+
}
|
|
2038
|
+
/// A completion source for Raku keywords, for the commonly used
|
|
2039
|
+
/// built-in types, routines, methods and special variables, and for the
|
|
2040
|
+
/// names that the document declares.
|
|
968
2041
|
const rakuCompletionSource = context => {
|
|
969
2042
|
let { state, pos } = context, tree = syntaxTree(state), node = tree.resolveInner(pos, -1);
|
|
970
2043
|
if (!inCode(node))
|
|
@@ -973,46 +2046,337 @@ const rakuCompletionSource = context => {
|
|
|
973
2046
|
let special = node.name == "SpecialVariable" ? node : context.matchBefore(/[$@%&][*?]$/);
|
|
974
2047
|
if (special)
|
|
975
2048
|
return { from: special.from, options: specialVariables, validFor: specialVariableTail };
|
|
2049
|
+
// A variable, or a sigil that is about to be one. A lone `%` or `&`
|
|
2050
|
+
// is as often an operator, and has to be followed by a name first.
|
|
2051
|
+
let isVariableNode = node.name == "VariableName" || node.name == "AttributeName";
|
|
2052
|
+
let sigil = isVariableNode ? null : context.matchBefore(/[$@]$|[$@%&][!.^:]$/);
|
|
2053
|
+
if (isVariableNode || sigil) {
|
|
2054
|
+
let from = isVariableNode ? node.from : sigil.from;
|
|
2055
|
+
// A lone sigil is not a node. It lies in the node around it.
|
|
2056
|
+
let parent = isVariableNode ? node.parent : tree.resolveInner(from, 0);
|
|
2057
|
+
let prev = isVariableNode ? node.prevSibling : parent.childBefore(from);
|
|
2058
|
+
if (declaresVariable(state, parent, prev))
|
|
2059
|
+
return null;
|
|
2060
|
+
// A placeholder variable is declared by being used, also by the
|
|
2061
|
+
// one that is being typed, which is not offered for itself.
|
|
2062
|
+
let typed = isVariableNode ? state.sliceDoc(from, node.to) : "";
|
|
2063
|
+
let options = withLocal(localNames(parent, state).names.filter(hasSigil), []);
|
|
2064
|
+
if (isPlaceholder(typed))
|
|
2065
|
+
options = options.filter(completion => completion.label != typed);
|
|
2066
|
+
return options.length ? { from, options, validFor: variableTail } : null;
|
|
2067
|
+
}
|
|
2068
|
+
let local = localNames(node, state), localMethods = () => withLocal(local.methods, methods);
|
|
976
2069
|
if (node.name == "MethodName")
|
|
977
|
-
return { from: node.from, options:
|
|
2070
|
+
return { from: node.from, options: localMethods(), validFor: nameTail };
|
|
978
2071
|
// A dot or `.=` with no name after it yet. Two dots are a range operator.
|
|
979
2072
|
let before = state.sliceDoc(Math.max(0, pos - 2), pos);
|
|
980
2073
|
if (before == ".=")
|
|
981
|
-
return { from: pos, options:
|
|
2074
|
+
return { from: pos, options: localMethods(), validFor: nameTail };
|
|
982
2075
|
if (/(^|[^.])\.$/.test(before)) {
|
|
983
2076
|
// The dot of `1.5` and of `v6.d` is far more often typed than a
|
|
984
2077
|
// method call on a number.
|
|
985
2078
|
let numeric = /^(Number|Version)$/.test(tree.resolveInner(pos - 1, -1).name);
|
|
986
|
-
return numeric && !context.explicit ? null : { from: pos, options:
|
|
2079
|
+
return numeric && !context.explicit ? null : { from: pos, options: localMethods(), validFor: nameTail };
|
|
987
2080
|
}
|
|
2081
|
+
let localGlobals = () => withLocal(local.names.filter(completion => !hasSigil(completion)), globals);
|
|
988
2082
|
// A keyword's node is named after the keyword, as are those of `,` and `;`.
|
|
989
2083
|
let isKeyword = /^\w/.test(node.name) && node.name == state.sliceDoc(node.from, node.to);
|
|
990
2084
|
if (isKeyword || node.name == "Identifier" || node.name == "TypeName") {
|
|
991
2085
|
if (isPrivateMethodName(tree, state, node))
|
|
992
2086
|
return null;
|
|
993
|
-
// `$x .= trim` calls a method.
|
|
994
2087
|
let prev = node.prevSibling;
|
|
2088
|
+
// The name of a variable without a sigil, where it is declared: my \x
|
|
2089
|
+
if (prev && prev.to == node.from && state.sliceDoc(prev.from, prev.to) == "\\" &&
|
|
2090
|
+
declaresVariable(state, node.parent, prev.prevSibling))
|
|
2091
|
+
return null;
|
|
2092
|
+
// `$x .= trim` calls a method.
|
|
995
2093
|
let afterDotAssign = prev != null && prev.name == "Operator" && state.sliceDoc(prev.from, prev.to) == ".=";
|
|
996
|
-
return { from: node.from, options: afterDotAssign ?
|
|
2094
|
+
return { from: node.from, options: afterDotAssign ? localMethods() : localGlobals(), validFor: nameTail };
|
|
997
2095
|
}
|
|
998
|
-
// Other names (
|
|
999
|
-
//
|
|
1000
|
-
//
|
|
2096
|
+
// Other names (pair keys, names being declared) are made up by the
|
|
2097
|
+
// user, so only a position where no token has been started gets the
|
|
2098
|
+
// whole list, and only on request.
|
|
1001
2099
|
if (context.explicit && /^[\s(\[{,;=]?$/.test(state.sliceDoc(Math.max(0, pos - 1), pos)))
|
|
1002
|
-
return { from: pos, options:
|
|
2100
|
+
return { from: pos, options: localGlobals(), validFor: nameTail };
|
|
1003
2101
|
return null;
|
|
1004
2102
|
};
|
|
1005
2103
|
|
|
2104
|
+
// The grammar has no statement nodes, so whether a line continues the
|
|
2105
|
+
// statement before it is worked out here, from the token that precedes
|
|
2106
|
+
// the line and from what the line starts with.
|
|
2107
|
+
const skipped = /^(LineComment|DocComment|BlockComment|Pod|Heredoc)$/;
|
|
2108
|
+
const brackets = /^(Block|Parens|Brackets|Interpolation|Program)$/;
|
|
2109
|
+
const wordOperators = /^(and|or|xor|andthen|orelse|notandthen|div|mod|gcd|lcm|eq|ne|lt|gt|le|ge|cmp|leg|eqv|but)$/;
|
|
2110
|
+
// Operators that end a term or stand for a whole statement.
|
|
2111
|
+
const closingOperators = /^(\+\+|--|\.\.\.|!!!|\?\?\?)$/;
|
|
2112
|
+
// The words that start a trait. `of` is left out: it is as often a
|
|
2113
|
+
// name of one's own.
|
|
2114
|
+
const traits = "is|does|returns|handles|where|will";
|
|
2115
|
+
/// The lines that are indented again when they are typed: one that
|
|
2116
|
+
/// closes a bracket, and one that starts with a trait, which is only
|
|
2117
|
+
/// known to continue a statement once the trait's word is there.
|
|
2118
|
+
const indentOnInput = new RegExp("^\\s*(?:[\\}\\]\\)]|(?:" + traits + ")\\s)$");
|
|
2119
|
+
// A line that starts with an infix: an operator that has a space after
|
|
2120
|
+
// it and more on the line, an operator that is a word, a statement
|
|
2121
|
+
// modifier, or a trait (`is export`). A prefix operator touches its
|
|
2122
|
+
// operand, and `...` stands alone.
|
|
2123
|
+
const startsWithInfix = new RegExp("^\\s*(?:[~+\\-*/%<>=|&^?!]+[ \\t]+\\S|(?:" + wordOperators.source.slice(2, -2) +
|
|
2124
|
+
"|if|unless|for|while|until|given|when|with|without" +
|
|
2125
|
+
"|" + traits + ")(?![\\w'-]))");
|
|
2126
|
+
const startsWithMethod = /^\s*\.[^\s\d.]/;
|
|
2127
|
+
// The last token of `node`. A string, a regex, a heredoc and a Pod
|
|
2128
|
+
// block count as one token: what they end in is not code.
|
|
2129
|
+
const wholeTokens = /^(StringLiteral|Regex|Heredoc|Pod)$/;
|
|
2130
|
+
function lastToken(node) {
|
|
2131
|
+
while (node.lastChild && !wholeTokens.test(node.name))
|
|
2132
|
+
node = node.lastChild;
|
|
2133
|
+
return node;
|
|
2134
|
+
}
|
|
2135
|
+
// The last token that ends at or before `pos` in `bracket`, not
|
|
2136
|
+
// counting comments.
|
|
2137
|
+
function tokenBefore(bracket, pos) {
|
|
2138
|
+
let node = bracket.resolveInner(pos, -1);
|
|
2139
|
+
// Either the node that ends there, or the one around the position.
|
|
2140
|
+
if (node.to > pos)
|
|
2141
|
+
node = node.childBefore(pos);
|
|
2142
|
+
while (node) {
|
|
2143
|
+
let token = lastToken(node);
|
|
2144
|
+
if (!skipped.test(token.name))
|
|
2145
|
+
return token;
|
|
2146
|
+
// Go to what comes before the comment, in this node or around it.
|
|
2147
|
+
while (node && !node.prevSibling)
|
|
2148
|
+
node = node.parent;
|
|
2149
|
+
node = node && node.prevSibling;
|
|
2150
|
+
}
|
|
2151
|
+
return null;
|
|
2152
|
+
}
|
|
2153
|
+
function bracketAround(node) {
|
|
2154
|
+
for (let parent = node.parent; parent; parent = parent.parent)
|
|
2155
|
+
if (brackets.test(parent.name))
|
|
2156
|
+
return parent;
|
|
2157
|
+
return null;
|
|
2158
|
+
}
|
|
2159
|
+
// The bracket node whose content a token is part of. The brackets of a
|
|
2160
|
+
// node are part of the content around that node.
|
|
2161
|
+
function bracketOf(token) {
|
|
2162
|
+
let around = bracketAround(token);
|
|
2163
|
+
return around && around == token.parent && /^[()\[\]{}]$/.test(token.name) ? bracketAround(around) : around;
|
|
2164
|
+
}
|
|
2165
|
+
// The first node after `node` that is not a comment.
|
|
2166
|
+
function nextCode(node) {
|
|
2167
|
+
do
|
|
2168
|
+
node = node && node.nextSibling;
|
|
2169
|
+
while (node && skipped.test(node.name));
|
|
2170
|
+
return node;
|
|
2171
|
+
}
|
|
2172
|
+
// Whether a block in braces holds the pairs of a hash, in which a comma
|
|
2173
|
+
// separates items that line up.
|
|
2174
|
+
function holdsPairs(block, state) {
|
|
2175
|
+
let first = block.name == "Block" ? nextCode(block.firstChild) : null;
|
|
2176
|
+
if (!first)
|
|
2177
|
+
return false;
|
|
2178
|
+
if (first.name == "PairKey")
|
|
2179
|
+
return true;
|
|
2180
|
+
let second = nextCode(first);
|
|
2181
|
+
return second != null && second.name == "Operator" && state.sliceDoc(second.from, second.to) == "=>";
|
|
2182
|
+
}
|
|
2183
|
+
// Whether a line continues a statement of `bracket`. The line starts at
|
|
2184
|
+
// `pos` with the text `after`.
|
|
2185
|
+
function continuesStatement(state, bracket, pos, after) {
|
|
2186
|
+
// The lines of a regex are not statements: its `+` and `|` are not
|
|
2187
|
+
// the operators of an expression.
|
|
2188
|
+
for (let node = bracket.resolveInner(pos, -1); node && node.from >= bracket.from; node = node.parent) {
|
|
2189
|
+
if (node.name == "Regex")
|
|
2190
|
+
return false;
|
|
2191
|
+
if (brackets.test(node.name))
|
|
2192
|
+
break;
|
|
2193
|
+
}
|
|
2194
|
+
let before = tokenBefore(bracket, pos);
|
|
2195
|
+
if (!before || bracketOf(before) != bracket)
|
|
2196
|
+
return false;
|
|
2197
|
+
let name = before.name, text = state.sliceDoc(before.from, before.to);
|
|
2198
|
+
if (name == "Operator")
|
|
2199
|
+
return !closingOperators.test(text);
|
|
2200
|
+
if (wordOperators.test(name) && name == text)
|
|
2201
|
+
return true;
|
|
2202
|
+
if (name == ",")
|
|
2203
|
+
return !holdsPairs(bracket, state);
|
|
2204
|
+
if (name == ";" || name == "{")
|
|
2205
|
+
return false;
|
|
2206
|
+
if (name == "}") {
|
|
2207
|
+
// A block ends its statement. A subscript, which touches the term
|
|
2208
|
+
// that it follows, does not: %h{$key}
|
|
2209
|
+
let block = before.parent;
|
|
2210
|
+
return block.from > 0 && !/\s/.test(state.sliceDoc(block.from - 1, block.from)) && startsWithInfix.test(after);
|
|
2211
|
+
}
|
|
2212
|
+
// After the end of a statement, a `.` calls a method on the topic.
|
|
2213
|
+
return startsWithInfix.test(after) || startsWithMethod.test(after);
|
|
2214
|
+
}
|
|
2215
|
+
// Whether the content of a bracket node starts on the line of its
|
|
2216
|
+
// opening bracket, which `delimitedIndent` then aligns the rest with.
|
|
2217
|
+
function isAligned(node, state) {
|
|
2218
|
+
let open = node.firstChild, next = nextCode(open);
|
|
2219
|
+
return open != null && next != null && next != node.lastChild && next.from < state.doc.lineAt(open.from).to;
|
|
2220
|
+
}
|
|
2221
|
+
const bodyKeywords = /^(if|elsif|else|unless|with|orwith|without|for|while|until|loop|repeat|given|when|default|whenever|react|supply|gather|do|try|start)$/;
|
|
2222
|
+
const declarationPrefixes = /^(my|our|has|state|anon|augment|supersede|unit|multi|proto|only)$/;
|
|
2223
|
+
// The first node of the statement that a block is the body of, when it
|
|
2224
|
+
// is the body of a control statement or of a declaration. A block that
|
|
2225
|
+
// is a term in an expression has none: `my $f = sub { ... }`
|
|
2226
|
+
function statementOfBody(block, state) {
|
|
2227
|
+
let declaration = block.parent && /Declaration$/.test(block.parent.name) ? block.parent : null;
|
|
2228
|
+
let first = declaration || block;
|
|
2229
|
+
for (let prev = first.prevSibling; prev; prev = prev.prevSibling) {
|
|
2230
|
+
if (skipped.test(prev.name))
|
|
2231
|
+
continue;
|
|
2232
|
+
if (prev.name == ";" || prev.name == "{")
|
|
2233
|
+
break;
|
|
2234
|
+
// A closing brace that is followed by a line break ends a statement.
|
|
2235
|
+
if (lastToken(prev).name == "}" && /\n/.test(state.sliceDoc(prev.to, first.from)))
|
|
2236
|
+
break;
|
|
2237
|
+
if (declaration && !declarationPrefixes.test(prev.name))
|
|
2238
|
+
return null;
|
|
2239
|
+
first = prev;
|
|
2240
|
+
}
|
|
2241
|
+
return declaration || bodyKeywords.test(first.name) ? first : null;
|
|
2242
|
+
}
|
|
2243
|
+
/// The indentation of the lines in a node that holds statements between
|
|
2244
|
+
/// braces: that of `delimitedIndent`, and one unit more for a line that
|
|
2245
|
+
/// continues a statement.
|
|
2246
|
+
function statementIndent(closing) {
|
|
2247
|
+
let delimited = delimitedIndent({ closing });
|
|
2248
|
+
return (context) => {
|
|
2249
|
+
let { node, state, unit } = context, after = context.textAfter;
|
|
2250
|
+
let closed = after.slice(/^\s*/.exec(after)[0].length).startsWith(closing);
|
|
2251
|
+
// The body of a statement is indented from the line where the
|
|
2252
|
+
// statement starts, which need not be the line of its brace.
|
|
2253
|
+
// if $a
|
|
2254
|
+
// && $b {
|
|
2255
|
+
// say 1;
|
|
2256
|
+
// }
|
|
2257
|
+
let statement = isAligned(node, state) ? null : statementOfBody(node, state);
|
|
2258
|
+
let indent = statement ? context.lineIndent(statement.from) + (closed ? 0 : unit) : delimited(context);
|
|
2259
|
+
return !closed && continuesStatement(state, node, context.pos, after) ? indent + unit : indent;
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
/// The indentation of the lines at the top level.
|
|
2263
|
+
function topIndent(context) {
|
|
2264
|
+
return continuesStatement(context.state, context.node, context.pos, context.textAfter) ? context.unit : 0;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
function closing(open) { return String.fromCharCode(brackets$1[open.charCodeAt(0)]); }
|
|
2268
|
+
// The brackets to try, in this order. A bracket that is repeated has
|
|
2269
|
+
// to be closed as many times.
|
|
2270
|
+
const pairs = ["(", "[", "{", "<", "«", "「", "((", "[[", "{{", "(((", "[[[", "{{{"].map(open => {
|
|
2271
|
+
return { open, close: closing(open).repeat(open.length) };
|
|
2272
|
+
});
|
|
2273
|
+
// Whether `text` can stand between the delimiters: none of them is
|
|
2274
|
+
// left open in it, and none closes more than were opened.
|
|
2275
|
+
function fits(text, { open, close }) {
|
|
2276
|
+
let depth = 0;
|
|
2277
|
+
for (let pos = 0; pos < text.length;) {
|
|
2278
|
+
if (text.startsWith(close, pos)) {
|
|
2279
|
+
if (--depth < 0)
|
|
2280
|
+
return false;
|
|
2281
|
+
pos += close.length;
|
|
2282
|
+
}
|
|
2283
|
+
else if (text.startsWith(open, pos)) {
|
|
2284
|
+
depth++;
|
|
2285
|
+
pos += open.length;
|
|
2286
|
+
}
|
|
2287
|
+
else {
|
|
2288
|
+
pos++;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
return depth == 0;
|
|
2292
|
+
}
|
|
2293
|
+
// The delimiters of the comment that a block comment command would
|
|
2294
|
+
// remove for the range, if there is one: the range is that comment, or
|
|
2295
|
+
// all of its text, give or take whitespace.
|
|
2296
|
+
function commentAt(state, from, to) {
|
|
2297
|
+
let start = from + /^\s*/.exec(state.sliceDoc(from, to))[0].length;
|
|
2298
|
+
let node = syntaxTree(state).resolveInner(start, 1);
|
|
2299
|
+
if (node.name != "BlockComment")
|
|
2300
|
+
return null;
|
|
2301
|
+
let text = state.sliceDoc(node.from, node.to), open = /^#`(.)\1*/.exec(text);
|
|
2302
|
+
if (!open || brackets$1[open[1].charCodeAt(0)] == null)
|
|
2303
|
+
return null;
|
|
2304
|
+
let close = closing(open[1]).repeat(open[0].length - 2);
|
|
2305
|
+
if (!text.endsWith(close))
|
|
2306
|
+
return null;
|
|
2307
|
+
let blank = (a, b) => a >= b || !/\S/.test(state.sliceDoc(a, b));
|
|
2308
|
+
let isComment = start == node.from && blank(node.to, to);
|
|
2309
|
+
let isContent = blank(node.from + open[0].length, from) && blank(to, node.to - close.length) && to <= node.to;
|
|
2310
|
+
return isComment || isContent ? { open: open[0], close } : null;
|
|
2311
|
+
}
|
|
2312
|
+
function blockTokens(state, pos) {
|
|
2313
|
+
// The command asks at the start of what it works on, which is a
|
|
2314
|
+
// selection range, or the first text of a selected line.
|
|
2315
|
+
let range = state.selection.ranges.find(range => range.from == pos);
|
|
2316
|
+
let existing = range && commentAt(state, range.from, range.to);
|
|
2317
|
+
if (existing)
|
|
2318
|
+
return existing;
|
|
2319
|
+
// The command that goes by lines wraps up to the end of the line, so
|
|
2320
|
+
// the brackets have to fit that text as well.
|
|
2321
|
+
let texts = [], line = state.doc.lineAt(pos);
|
|
2322
|
+
if (range) {
|
|
2323
|
+
texts.push(state.sliceDoc(range.from, range.to));
|
|
2324
|
+
line = state.doc.lineAt(range.to);
|
|
2325
|
+
}
|
|
2326
|
+
else {
|
|
2327
|
+
let onLine = state.selection.ranges.find(range => range.from <= pos && range.from >= line.from);
|
|
2328
|
+
if (onLine)
|
|
2329
|
+
line = state.doc.lineAt(onLine.to);
|
|
2330
|
+
}
|
|
2331
|
+
if (!range || range.to < line.to)
|
|
2332
|
+
texts.push(state.sliceDoc(pos, line.to));
|
|
2333
|
+
// The spaces keep a bracket at the edge of the text from joining the delimiters.
|
|
2334
|
+
let pair = pairs.find(pair => texts.every(text => fits(" " + text + " ", pair))) || pairs[0];
|
|
2335
|
+
return { open: "#`" + pair.open, close: pair.close };
|
|
2336
|
+
}
|
|
2337
|
+
/// Language data that gives `language` block comment tokens which fit
|
|
2338
|
+
/// the selection. It takes precedence over the fixed tokens in the
|
|
2339
|
+
/// language's own data.
|
|
2340
|
+
function embeddedCommentTokens(language) {
|
|
2341
|
+
return Prec.high(EditorState.languageData.of((state, pos, side) => {
|
|
2342
|
+
if (!language.isActiveAt(state, pos, side))
|
|
2343
|
+
return [];
|
|
2344
|
+
// Only worked out when asked for: this is called for every kind of
|
|
2345
|
+
// language data, and the line comment commands do not read `block`.
|
|
2346
|
+
return [{ get commentTokens() {
|
|
2347
|
+
return { line: "#", get block() { return blockTokens(state, pos); } };
|
|
2348
|
+
} }];
|
|
2349
|
+
}));
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
// A regex literal is one token to the parser of a program: whether it
|
|
2353
|
+
// is closed has to be known where it starts, and a token that is read
|
|
2354
|
+
// piece by piece cannot look that far ahead (see AGENTS.md). Its inside
|
|
2355
|
+
// is parsed by the same grammar, from the top rule for a regex literal,
|
|
2356
|
+
// and that tree takes the place of the token's node. The inner parser
|
|
2357
|
+
// is made from `base` so that it has the same props, among them the
|
|
2358
|
+
// language data that LRLanguage.define puts on top nodes.
|
|
2359
|
+
function nestedParsers(base) {
|
|
2360
|
+
// From the term of a token to the parser of its inside. The
|
|
2361
|
+
// generator registers a top rule under its node name.
|
|
2362
|
+
let parsers = new Map();
|
|
2363
|
+
let wrap = parseMixed(node => parsers.get(node.type.id) || null);
|
|
2364
|
+
// The inner parsers are wrapped as well: a regex can hold a block of
|
|
2365
|
+
// code, and that can hold a regex.
|
|
2366
|
+
parsers.set(regexLiteral, { parser: base.configure({ top: "Regex", wrap }) });
|
|
2367
|
+
parsers.set(interpolatingHeredoc, { parser: base.configure({ top: "Heredoc", wrap }) });
|
|
2368
|
+
parsers.set(interpolatingWordList, { parser: base.configure({ top: "StringLiteral", wrap }) });
|
|
2369
|
+
return wrap;
|
|
2370
|
+
}
|
|
1006
2371
|
/// The Lezer parser for Raku, without the editor-specific node props.
|
|
1007
|
-
const parser = parser$1;
|
|
1008
|
-
|
|
1009
|
-
/// highlighting and indentation information.
|
|
1010
|
-
const rakuLanguage = LRLanguage.define({
|
|
2372
|
+
const parser = parser$1.configure({ wrap: nestedParsers(parser$1) });
|
|
2373
|
+
const baseLanguage = LRLanguage.define({
|
|
1011
2374
|
name: "raku",
|
|
1012
|
-
parser: parser.configure({
|
|
2375
|
+
parser: parser$1.configure({
|
|
1013
2376
|
props: [
|
|
1014
2377
|
indentNodeProp.add({
|
|
1015
|
-
|
|
2378
|
+
Program: topIndent,
|
|
2379
|
+
"Block Interpolation": statementIndent("}"),
|
|
1016
2380
|
Parens: delimitedIndent({ closing: ")" }),
|
|
1017
2381
|
Brackets: delimitedIndent({ closing: "]" }),
|
|
1018
2382
|
// The lines of these are text, which keeps its indentation.
|
|
@@ -1026,22 +2390,33 @@ const rakuLanguage = LRLanguage.define({
|
|
|
1026
2390
|
// A heredoc starts with the line break before its first line.
|
|
1027
2391
|
Heredoc(tree) { return { from: tree.from, to: tree.to }; }
|
|
1028
2392
|
}),
|
|
1029
|
-
styleTags(Object.assign(Object.assign({}, keywordTags), { "True False": tags.bool, Nil: tags.null, Identifier: tags.function(tags.variableName), TypeName: tags.typeName, PackageName: tags.definition(tags.className), "EnumName SubsetName": tags.definition(tags.typeName), ConstantName: tags.definition(tags.constant(tags.variableName)), "RoutineName RegexName": tags.function(tags.definition(tags.variableName)), MethodName: tags.function(tags.propertyName), VariableName: tags.variableName, AttributeName: tags.propertyName, SpecialVariable: tags.special(tags.variableName), PairKey: tags.attributeName, Number: tags.number, Version: tags.literal,
|
|
2393
|
+
styleTags(Object.assign(Object.assign({}, keywordTags), { "True False": tags.bool, Nil: tags.null, Identifier: tags.function(tags.variableName), TypeName: tags.typeName, PackageName: tags.definition(tags.className), "EnumName SubsetName": tags.definition(tags.typeName), ConstantName: tags.definition(tags.constant(tags.variableName)), "RoutineName RegexName": tags.function(tags.definition(tags.variableName)), MethodName: tags.function(tags.propertyName), VariableName: tags.variableName, AttributeName: tags.propertyName, SpecialVariable: tags.special(tags.variableName), PairKey: tags.attributeName, Number: tags.number, Version: tags.literal,
|
|
2394
|
+
// A pair of delimiters in a quote is text of it, and a tag
|
|
2395
|
+
// does not reach the text in a child node.
|
|
2396
|
+
"StringLiteral Heredoc NestedDelimiters": tags.string, Regex: tags.regexp, CharacterClass: tags.character, Assertion: tags.function(tags.variableName), "Pod PodFormat": tags.docComment, PodDirective: tags.meta,
|
|
2397
|
+
// These are still part of a comment, and are styled as one where
|
|
2398
|
+
// a theme has no style for the second tag.
|
|
2399
|
+
PodHeading: [tags.docComment, tags.heading], PodStrong: [tags.docComment, tags.strong], PodEmphasis: [tags.docComment, tags.emphasis], PodCode: [tags.docComment, tags.monospace], PodLink: [tags.docComment, tags.link], Escape: tags.escape, "Interpolation/{ Interpolation/}": tags.special(tags.brace), LineComment: tags.lineComment, BlockComment: tags.blockComment, DocComment: tags.docComment, Operator: tags.operator, ".": tags.derefOperator, ", ;": tags.separator, ":": tags.punctuation, "( )": tags.paren, "[ ]": tags.squareBracket, "{ }": tags.brace }))
|
|
1030
2400
|
]
|
|
1031
2401
|
}),
|
|
1032
2402
|
languageData: {
|
|
1033
2403
|
// An embedded comment can use any bracket. Parentheses are the usual choice.
|
|
1034
2404
|
commentTokens: { line: "#", block: { open: "#`(", close: ")" } },
|
|
1035
2405
|
closeBrackets: { brackets: ["(", "[", "{", "'", '"', "「"] },
|
|
1036
|
-
indentOnInput
|
|
2406
|
+
indentOnInput
|
|
1037
2407
|
}
|
|
1038
2408
|
});
|
|
1039
|
-
///
|
|
1040
|
-
///
|
|
2409
|
+
/// A language provider based on the Lezer Raku parser, extended with
|
|
2410
|
+
/// highlighting and indentation information.
|
|
2411
|
+
const rakuLanguage = baseLanguage.configure({ wrap: nestedParsers(baseLanguage.parser) });
|
|
2412
|
+
/// Completion of Raku keywords, of the commonly used built-in types,
|
|
2413
|
+
/// routines, methods and special variables, and of the names that the
|
|
2414
|
+
/// document declares.
|
|
1041
2415
|
const rakuCompletion = rakuLanguage.data.of({ autocomplete: rakuCompletionSource });
|
|
1042
|
-
/// Raku language support, with completion
|
|
2416
|
+
/// Raku language support, with completion, and with block comment
|
|
2417
|
+
/// tokens that fit the selection.
|
|
1043
2418
|
function raku() {
|
|
1044
|
-
return new LanguageSupport(rakuLanguage, [rakuCompletion]);
|
|
2419
|
+
return new LanguageSupport(rakuLanguage, [rakuCompletion, embeddedCommentTokens(rakuLanguage)]);
|
|
1045
2420
|
}
|
|
1046
2421
|
|
|
1047
2422
|
export { parser, raku, rakuCompletion, rakuLanguage };
|