amis-formula 1.3.13 → 2.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/__mocks__/styleMock.js +6 -0
- package/__mocks__/svgMock.js +5 -0
- package/__tests__/__snapshots__/lexer.test.ts.snap +31 -0
- package/__tests__/__snapshots__/parser.test.ts.snap +2306 -0
- package/__tests__/evalute.test.ts +437 -0
- package/__tests__/fomula.test.ts +194 -0
- package/__tests__/jest.setup.js +22 -0
- package/__tests__/lexer.test.ts +55 -0
- package/__tests__/parser.test.ts +201 -0
- package/dist/evalutor.d.ts +5 -0
- package/dist/filter.d.ts +6 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3032 -3841
- package/dist/lexer.d.ts +1 -29
- package/dist/parser.d.ts +1 -26
- package/dist/types.d.ts +83 -0
- package/package.json +1 -1
- package/rollup.config.js +3 -1
- package/scripts/genDoc.ts +162 -0
- package/scripts/lib.ts +55 -0
- package/src/evalutor.ts +1938 -0
- package/src/filter.ts +48 -0
- package/src/index.ts +35 -0
- package/src/lexer.ts +775 -0
- package/src/parser.ts +875 -0
- package/src/types.ts +119 -0
- package/tsconfig.json +36 -0
- package/.prettierrc +0 -20
- package/dist/util.d.ts +0 -36
package/src/lexer.ts
ADDED
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
import {LexerOptions, Token, TokenTypeName} from './types';
|
|
2
|
+
|
|
3
|
+
export const enum TokenEnum {
|
|
4
|
+
BooleanLiteral = 1,
|
|
5
|
+
RAW,
|
|
6
|
+
Variable,
|
|
7
|
+
OpenScript,
|
|
8
|
+
CloseScript,
|
|
9
|
+
EOF,
|
|
10
|
+
Identifier,
|
|
11
|
+
Literal,
|
|
12
|
+
NumericLiteral,
|
|
13
|
+
Punctuator,
|
|
14
|
+
StringLiteral,
|
|
15
|
+
RegularExpression,
|
|
16
|
+
TemplateRaw,
|
|
17
|
+
TemplateLeftBrace,
|
|
18
|
+
TemplateRightBrace,
|
|
19
|
+
OpenFilter,
|
|
20
|
+
Char
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const TokenName: {
|
|
24
|
+
[propName: string]: TokenTypeName;
|
|
25
|
+
} = {};
|
|
26
|
+
TokenName[TokenEnum.BooleanLiteral] = 'Boolean';
|
|
27
|
+
TokenName[TokenEnum.RAW] = 'Raw';
|
|
28
|
+
TokenName[TokenEnum.Variable] = 'Variable';
|
|
29
|
+
TokenName[TokenEnum.OpenScript] = 'OpenScript';
|
|
30
|
+
TokenName[TokenEnum.CloseScript] = 'CloseScript';
|
|
31
|
+
TokenName[TokenEnum.EOF] = 'EOF';
|
|
32
|
+
TokenName[TokenEnum.Identifier] = 'Identifier';
|
|
33
|
+
TokenName[TokenEnum.Literal] = 'Literal';
|
|
34
|
+
TokenName[TokenEnum.NumericLiteral] = 'Numeric';
|
|
35
|
+
TokenName[TokenEnum.Punctuator] = 'Punctuator';
|
|
36
|
+
TokenName[TokenEnum.StringLiteral] = 'String';
|
|
37
|
+
TokenName[TokenEnum.RegularExpression] = 'RegularExpression';
|
|
38
|
+
TokenName[TokenEnum.TemplateRaw] = 'TemplateRaw';
|
|
39
|
+
TokenName[TokenEnum.TemplateLeftBrace] = 'TemplateLeftBrace';
|
|
40
|
+
TokenName[TokenEnum.TemplateRightBrace] = 'TemplateRightBrace';
|
|
41
|
+
TokenName[TokenEnum.OpenFilter] = 'OpenFilter';
|
|
42
|
+
TokenName[TokenEnum.Char] = 'Char';
|
|
43
|
+
|
|
44
|
+
const mainStates = {
|
|
45
|
+
START: 0,
|
|
46
|
+
SCRIPT: 1,
|
|
47
|
+
EXPRESSION: 2,
|
|
48
|
+
BLOCK: 3,
|
|
49
|
+
Template: 4,
|
|
50
|
+
Filter: 5
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const rawStates = {
|
|
54
|
+
START: 0,
|
|
55
|
+
ESCAPE: 1
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const numberStates = {
|
|
59
|
+
START: 0,
|
|
60
|
+
ZERO: 1,
|
|
61
|
+
DIGIT: 2,
|
|
62
|
+
POINT: 3,
|
|
63
|
+
DIGIT_FRACTION: 4,
|
|
64
|
+
EXP: 5
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const stringStates = {
|
|
68
|
+
START: 0,
|
|
69
|
+
START_QUOTE_OR_CHAR: 1,
|
|
70
|
+
ESCAPE: 2
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const filterStates = {
|
|
74
|
+
START: 0,
|
|
75
|
+
Func: 1,
|
|
76
|
+
SEP: 2,
|
|
77
|
+
ESCAPE: 3
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const punctuatorList = [
|
|
81
|
+
'===',
|
|
82
|
+
'!==',
|
|
83
|
+
'>>>',
|
|
84
|
+
'==',
|
|
85
|
+
'!=',
|
|
86
|
+
'<>',
|
|
87
|
+
'<=',
|
|
88
|
+
'>=',
|
|
89
|
+
'||',
|
|
90
|
+
'&&',
|
|
91
|
+
'++',
|
|
92
|
+
'--',
|
|
93
|
+
'<<',
|
|
94
|
+
'>>',
|
|
95
|
+
'**',
|
|
96
|
+
'+=',
|
|
97
|
+
'*=',
|
|
98
|
+
'/=',
|
|
99
|
+
'<',
|
|
100
|
+
'>',
|
|
101
|
+
'=',
|
|
102
|
+
'*',
|
|
103
|
+
'/',
|
|
104
|
+
'-',
|
|
105
|
+
'+',
|
|
106
|
+
'^',
|
|
107
|
+
'!',
|
|
108
|
+
'~',
|
|
109
|
+
'%',
|
|
110
|
+
'&',
|
|
111
|
+
'|',
|
|
112
|
+
'(',
|
|
113
|
+
')',
|
|
114
|
+
'[',
|
|
115
|
+
']',
|
|
116
|
+
'{',
|
|
117
|
+
'}',
|
|
118
|
+
'?',
|
|
119
|
+
':',
|
|
120
|
+
';',
|
|
121
|
+
',',
|
|
122
|
+
'.',
|
|
123
|
+
'$'
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
const escapes = {
|
|
127
|
+
'"': 0, // Quotation mask
|
|
128
|
+
'\\': 1, // Reverse solidus
|
|
129
|
+
'/': 2, // Solidus
|
|
130
|
+
'b': 3, // Backspace
|
|
131
|
+
'f': 4, // Form feed
|
|
132
|
+
'n': 5, // New line
|
|
133
|
+
'r': 6, // Carriage return
|
|
134
|
+
't': 7, // Horizontal tab
|
|
135
|
+
'u': 8 // 4 hexadecimal digits
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
function isDigit1to9(char: string) {
|
|
139
|
+
return char >= '1' && char <= '9';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function isDigit(char: string) {
|
|
143
|
+
return char >= '0' && char <= '9';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isExp(char: string) {
|
|
147
|
+
return char === 'e' || char === 'E';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function escapeString(text: string, allowedLetter: Array<string> = []) {
|
|
151
|
+
return text.replace(/\\(.)/g, function (_, text) {
|
|
152
|
+
return text === 'b'
|
|
153
|
+
? '\b'
|
|
154
|
+
: text === 'f'
|
|
155
|
+
? '\f'
|
|
156
|
+
: text === 'n'
|
|
157
|
+
? '\n'
|
|
158
|
+
: text === 'r'
|
|
159
|
+
? '\r'
|
|
160
|
+
: text === 't'
|
|
161
|
+
? '\t'
|
|
162
|
+
: text === 'v'
|
|
163
|
+
? '\v'
|
|
164
|
+
: ~allowedLetter.indexOf(text)
|
|
165
|
+
? text
|
|
166
|
+
: _;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function formatNumber(value: string) {
|
|
171
|
+
return Number(value);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function lexer(input: string, options?: LexerOptions) {
|
|
175
|
+
let line = 1;
|
|
176
|
+
let column = 1;
|
|
177
|
+
let index = 0;
|
|
178
|
+
let mainState = mainStates.START;
|
|
179
|
+
const states: Array<any> = [mainState];
|
|
180
|
+
let tokenCache: Array<Token> = [];
|
|
181
|
+
const allowFilter = options?.allowFilter !== false;
|
|
182
|
+
|
|
183
|
+
if (options?.evalMode || options?.variableMode) {
|
|
184
|
+
pushState(mainStates.EXPRESSION);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function pushState(state: any) {
|
|
188
|
+
states.push((mainState = state));
|
|
189
|
+
}
|
|
190
|
+
function popState() {
|
|
191
|
+
states.pop();
|
|
192
|
+
mainState = states[states.length - 1];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function position(value?: string) {
|
|
196
|
+
if (value && typeof value === 'string') {
|
|
197
|
+
const lines = value.split(/[\r\n]+/);
|
|
198
|
+
return {
|
|
199
|
+
index: index + value.length,
|
|
200
|
+
line: line + lines.length - 1,
|
|
201
|
+
column: column + lines[lines.length - 1].length
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {index: index, line, column};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function eof(): Token | void | null {
|
|
209
|
+
if (index >= input.length) {
|
|
210
|
+
return {
|
|
211
|
+
type: TokenName[TokenEnum.EOF],
|
|
212
|
+
value: undefined,
|
|
213
|
+
start: position(),
|
|
214
|
+
end: position()
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function raw(): Token | void | null {
|
|
220
|
+
if (mainState !== mainStates.START) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let buffer = '';
|
|
225
|
+
let state = rawStates.START;
|
|
226
|
+
let i = index;
|
|
227
|
+
|
|
228
|
+
while (i < input.length) {
|
|
229
|
+
const ch = input[i];
|
|
230
|
+
|
|
231
|
+
if (state === rawStates.ESCAPE) {
|
|
232
|
+
if (escapes.hasOwnProperty(ch) || ch === '$') {
|
|
233
|
+
buffer += ch;
|
|
234
|
+
i++;
|
|
235
|
+
state = rawStates.START;
|
|
236
|
+
} else {
|
|
237
|
+
const pos = position(buffer + ch);
|
|
238
|
+
throw new SyntaxError(
|
|
239
|
+
`Unexpected token ${ch} in ${pos.line}:${pos.column}`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
if (ch === '\\') {
|
|
244
|
+
buffer += ch;
|
|
245
|
+
i++;
|
|
246
|
+
state = rawStates.ESCAPE;
|
|
247
|
+
continue;
|
|
248
|
+
} else if (ch === '$') {
|
|
249
|
+
const nextCh = input[i + 1];
|
|
250
|
+
if (nextCh === '{') {
|
|
251
|
+
break;
|
|
252
|
+
} else if (nextCh === '$') {
|
|
253
|
+
// $$ 用法兼容
|
|
254
|
+
tokenCache.push({
|
|
255
|
+
type: TokenName[TokenEnum.Variable],
|
|
256
|
+
value: '&',
|
|
257
|
+
raw: '$$',
|
|
258
|
+
start: position(input.substring(index, i)),
|
|
259
|
+
end: position(input.substring(index, i + 2))
|
|
260
|
+
});
|
|
261
|
+
break;
|
|
262
|
+
} else {
|
|
263
|
+
// 支持旧的 $varName 的取值方法
|
|
264
|
+
const match = /^[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*/.exec(
|
|
265
|
+
input.substring(i + 1)
|
|
266
|
+
);
|
|
267
|
+
if (match) {
|
|
268
|
+
tokenCache.push({
|
|
269
|
+
type: TokenName[TokenEnum.Variable],
|
|
270
|
+
value: match[0],
|
|
271
|
+
raw: match[0],
|
|
272
|
+
start: position(input.substring(index, i)),
|
|
273
|
+
end: position(input.substring(index, i + 1 + match[0].length))
|
|
274
|
+
});
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
i++;
|
|
280
|
+
buffer += ch;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (i > index) {
|
|
285
|
+
return {
|
|
286
|
+
type: TokenName[TokenEnum.RAW],
|
|
287
|
+
value: escapeString(buffer, ['`', '$']),
|
|
288
|
+
raw: buffer,
|
|
289
|
+
start: position(),
|
|
290
|
+
end: position(buffer)
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
return tokenCache.length ? tokenCache.shift() : null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function openScript() {
|
|
297
|
+
if (mainState === mainStates.Template) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const ch = input[index];
|
|
302
|
+
if (ch === '$') {
|
|
303
|
+
const nextCh = input[index + 1];
|
|
304
|
+
if (nextCh === '{') {
|
|
305
|
+
pushState(mainStates.SCRIPT);
|
|
306
|
+
const value = input.substring(index, index + 2);
|
|
307
|
+
return {
|
|
308
|
+
type: TokenName[TokenEnum.OpenScript],
|
|
309
|
+
value,
|
|
310
|
+
start: position(),
|
|
311
|
+
end: position(value)
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function expression() {
|
|
319
|
+
if (
|
|
320
|
+
mainState !== mainStates.SCRIPT &&
|
|
321
|
+
mainState !== mainStates.EXPRESSION &&
|
|
322
|
+
mainState !== mainStates.BLOCK &&
|
|
323
|
+
mainState !== mainStates.Filter
|
|
324
|
+
) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const token =
|
|
329
|
+
literal() ||
|
|
330
|
+
identifier() ||
|
|
331
|
+
numberLiteral() ||
|
|
332
|
+
stringLiteral() ||
|
|
333
|
+
punctuator() ||
|
|
334
|
+
char();
|
|
335
|
+
|
|
336
|
+
if (token?.value === '{') {
|
|
337
|
+
pushState(mainStates.BLOCK);
|
|
338
|
+
} else if (token?.value === '}') {
|
|
339
|
+
if (mainState === mainStates.Filter) {
|
|
340
|
+
popState();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const prevState = mainState;
|
|
344
|
+
popState();
|
|
345
|
+
|
|
346
|
+
if (
|
|
347
|
+
prevState === mainStates.SCRIPT ||
|
|
348
|
+
prevState === mainStates.EXPRESSION
|
|
349
|
+
) {
|
|
350
|
+
return {
|
|
351
|
+
type: TokenName[
|
|
352
|
+
prevState === mainStates.EXPRESSION
|
|
353
|
+
? TokenEnum.TemplateRightBrace
|
|
354
|
+
: TokenEnum.CloseScript
|
|
355
|
+
],
|
|
356
|
+
value: token!.value,
|
|
357
|
+
start: position(),
|
|
358
|
+
end: position(token!.value)
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// filter 过滤器部分需要特殊处理
|
|
364
|
+
if (
|
|
365
|
+
mainState === mainStates.SCRIPT &&
|
|
366
|
+
token?.value === '|' &&
|
|
367
|
+
allowFilter
|
|
368
|
+
) {
|
|
369
|
+
pushState(mainStates.Filter);
|
|
370
|
+
return {
|
|
371
|
+
type: TokenName[TokenEnum.OpenFilter],
|
|
372
|
+
value: '|',
|
|
373
|
+
start: position(),
|
|
374
|
+
end: position('|')
|
|
375
|
+
};
|
|
376
|
+
} else if (mainState === mainStates.Filter && token?.value === '|') {
|
|
377
|
+
return {
|
|
378
|
+
type: TokenName[TokenEnum.OpenFilter],
|
|
379
|
+
value: '|',
|
|
380
|
+
start: position(),
|
|
381
|
+
end: position('|')
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (!token && input[index] === '`') {
|
|
386
|
+
pushState(mainStates.Template);
|
|
387
|
+
return {
|
|
388
|
+
type: TokenName[TokenEnum.Punctuator],
|
|
389
|
+
value: '`',
|
|
390
|
+
start: position(),
|
|
391
|
+
end: position('`')
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return token;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function char() {
|
|
399
|
+
if (mainState !== mainStates.Filter) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
let i = index;
|
|
404
|
+
let ch = input[i];
|
|
405
|
+
if (ch === '\\') {
|
|
406
|
+
const nextCh = input[i + 1];
|
|
407
|
+
|
|
408
|
+
if (
|
|
409
|
+
nextCh === '$' ||
|
|
410
|
+
~punctuatorList.indexOf(nextCh) ||
|
|
411
|
+
escapes.hasOwnProperty(nextCh)
|
|
412
|
+
) {
|
|
413
|
+
i++;
|
|
414
|
+
ch =
|
|
415
|
+
nextCh === 'b'
|
|
416
|
+
? '\b'
|
|
417
|
+
: nextCh === 'f'
|
|
418
|
+
? '\f'
|
|
419
|
+
: nextCh === 'n'
|
|
420
|
+
? '\n'
|
|
421
|
+
: nextCh === 'r'
|
|
422
|
+
? '\r'
|
|
423
|
+
: nextCh === 't'
|
|
424
|
+
? '\t'
|
|
425
|
+
: nextCh === 'v'
|
|
426
|
+
? '\v'
|
|
427
|
+
: nextCh;
|
|
428
|
+
} else {
|
|
429
|
+
const pos = position(input.substring(index, index + 2));
|
|
430
|
+
throw new SyntaxError(
|
|
431
|
+
`Unexpected token ${nextCh} in ${pos.line}:${pos.column}`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const token = {
|
|
436
|
+
type: TokenName[TokenEnum.Char],
|
|
437
|
+
value: ch,
|
|
438
|
+
start: position(),
|
|
439
|
+
end: position(input.substring(index, i + 1))
|
|
440
|
+
};
|
|
441
|
+
return token;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function template(): Token | void | null {
|
|
445
|
+
if (mainState !== mainStates.Template) {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
let state = stringStates.START;
|
|
449
|
+
let i = index;
|
|
450
|
+
while (i < input.length) {
|
|
451
|
+
const ch = input[i];
|
|
452
|
+
|
|
453
|
+
if (state === stringStates.ESCAPE) {
|
|
454
|
+
if (escapes.hasOwnProperty(ch) || ch === '`' || ch === '$') {
|
|
455
|
+
i++;
|
|
456
|
+
state = stringStates.START_QUOTE_OR_CHAR;
|
|
457
|
+
} else {
|
|
458
|
+
const pos = position(input.substring(index, i + 1));
|
|
459
|
+
throw new SyntaxError(
|
|
460
|
+
`Unexpected token ${ch} in ${pos.line}:${pos.column}`
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
} else if (ch === '\\') {
|
|
464
|
+
i++;
|
|
465
|
+
state = stringStates.ESCAPE;
|
|
466
|
+
} else if (ch === '`') {
|
|
467
|
+
popState();
|
|
468
|
+
tokenCache.push({
|
|
469
|
+
type: TokenName[TokenEnum.Punctuator],
|
|
470
|
+
value: '`',
|
|
471
|
+
start: position(input.substring(index, i)),
|
|
472
|
+
end: position(input.substring(index, i + 1))
|
|
473
|
+
});
|
|
474
|
+
break;
|
|
475
|
+
} else if (ch === '$') {
|
|
476
|
+
const nextCh = input[i + 1];
|
|
477
|
+
if (nextCh === '{') {
|
|
478
|
+
pushState(mainStates.EXPRESSION);
|
|
479
|
+
tokenCache.push({
|
|
480
|
+
type: TokenName[TokenEnum.TemplateLeftBrace],
|
|
481
|
+
value: '${',
|
|
482
|
+
start: position(input.substring(index, i)),
|
|
483
|
+
end: position(input.substring(index, i + 2))
|
|
484
|
+
});
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
i++;
|
|
488
|
+
} else {
|
|
489
|
+
i++;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (i > index) {
|
|
493
|
+
const value = input.substring(index, i);
|
|
494
|
+
return {
|
|
495
|
+
type: TokenName[TokenEnum.TemplateRaw],
|
|
496
|
+
value: escapeString(value, ['`', '$']),
|
|
497
|
+
raw: value,
|
|
498
|
+
start: position(),
|
|
499
|
+
end: position(value)
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
return tokenCache.length ? tokenCache.shift() : null;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function skipWhiteSpace() {
|
|
506
|
+
while (index < input.length) {
|
|
507
|
+
const ch = input[index];
|
|
508
|
+
if (ch === '\r') {
|
|
509
|
+
// CR (Unix)
|
|
510
|
+
index++;
|
|
511
|
+
line++;
|
|
512
|
+
column = 1;
|
|
513
|
+
if (input.charAt(index) === '\n') {
|
|
514
|
+
// CRLF (Windows)
|
|
515
|
+
index++;
|
|
516
|
+
}
|
|
517
|
+
} else if (ch === '\n') {
|
|
518
|
+
// LF (MacOS)
|
|
519
|
+
index++;
|
|
520
|
+
line++;
|
|
521
|
+
column = 1;
|
|
522
|
+
} else if (ch === '\t' || ch === ' ') {
|
|
523
|
+
index++;
|
|
524
|
+
column++;
|
|
525
|
+
} else {
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function punctuator() {
|
|
532
|
+
const find = punctuatorList.find(
|
|
533
|
+
punctuator =>
|
|
534
|
+
input.substring(index, index + punctuator.length) === punctuator
|
|
535
|
+
);
|
|
536
|
+
if (find) {
|
|
537
|
+
return {
|
|
538
|
+
type: TokenName[TokenEnum.Punctuator],
|
|
539
|
+
value: find,
|
|
540
|
+
start: position(),
|
|
541
|
+
end: position(find)
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function literal() {
|
|
548
|
+
let keyword = input.substring(index, index + 4).toLowerCase();
|
|
549
|
+
let value: any = keyword;
|
|
550
|
+
let isLiteral = false;
|
|
551
|
+
if (keyword === 'true' || keyword === 'null') {
|
|
552
|
+
isLiteral = true;
|
|
553
|
+
value = keyword === 'true' ? true : null;
|
|
554
|
+
} else if (
|
|
555
|
+
(keyword = input.substring(index, index + 5).toLowerCase()) === 'false'
|
|
556
|
+
) {
|
|
557
|
+
isLiteral = true;
|
|
558
|
+
value = false;
|
|
559
|
+
} else if (
|
|
560
|
+
(keyword = input.substring(index, index + 9).toLowerCase()) ===
|
|
561
|
+
'undefined'
|
|
562
|
+
) {
|
|
563
|
+
isLiteral = true;
|
|
564
|
+
value = undefined;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (isLiteral) {
|
|
568
|
+
return {
|
|
569
|
+
type:
|
|
570
|
+
value === true || value === false
|
|
571
|
+
? TokenName[TokenEnum.BooleanLiteral]
|
|
572
|
+
: TokenName[TokenEnum.Literal],
|
|
573
|
+
value,
|
|
574
|
+
raw: keyword,
|
|
575
|
+
start: position(),
|
|
576
|
+
end: position(keyword)
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function numberLiteral() {
|
|
583
|
+
let i = index;
|
|
584
|
+
|
|
585
|
+
let passedValueIndex = i;
|
|
586
|
+
let state = numberStates.START;
|
|
587
|
+
|
|
588
|
+
iterator: while (i < input.length) {
|
|
589
|
+
const char = input.charAt(i);
|
|
590
|
+
|
|
591
|
+
switch (state) {
|
|
592
|
+
case numberStates.START: {
|
|
593
|
+
if (char === '0') {
|
|
594
|
+
passedValueIndex = i + 1;
|
|
595
|
+
state = numberStates.ZERO;
|
|
596
|
+
} else if (isDigit1to9(char)) {
|
|
597
|
+
passedValueIndex = i + 1;
|
|
598
|
+
state = numberStates.DIGIT;
|
|
599
|
+
} else {
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
case numberStates.ZERO: {
|
|
606
|
+
if (char === '.') {
|
|
607
|
+
state = numberStates.POINT;
|
|
608
|
+
} else if (isExp(char)) {
|
|
609
|
+
state = numberStates.EXP;
|
|
610
|
+
} else {
|
|
611
|
+
break iterator;
|
|
612
|
+
}
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
case numberStates.DIGIT: {
|
|
617
|
+
if (isDigit(char)) {
|
|
618
|
+
passedValueIndex = i + 1;
|
|
619
|
+
} else if (char === '.') {
|
|
620
|
+
state = numberStates.POINT;
|
|
621
|
+
} else if (isExp(char)) {
|
|
622
|
+
state = numberStates.EXP;
|
|
623
|
+
} else {
|
|
624
|
+
break iterator;
|
|
625
|
+
}
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
case numberStates.POINT: {
|
|
630
|
+
if (isDigit(char)) {
|
|
631
|
+
passedValueIndex = i + 1;
|
|
632
|
+
state = numberStates.DIGIT_FRACTION;
|
|
633
|
+
} else {
|
|
634
|
+
break iterator;
|
|
635
|
+
}
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
case numberStates.DIGIT_FRACTION: {
|
|
640
|
+
if (isDigit(char)) {
|
|
641
|
+
passedValueIndex = i + 1;
|
|
642
|
+
} else if (isExp(char)) {
|
|
643
|
+
state = numberStates.EXP;
|
|
644
|
+
} else {
|
|
645
|
+
break iterator;
|
|
646
|
+
}
|
|
647
|
+
break;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
i++;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (passedValueIndex > 0) {
|
|
655
|
+
const value = input.slice(index, passedValueIndex);
|
|
656
|
+
return {
|
|
657
|
+
type: TokenName[TokenEnum.NumericLiteral],
|
|
658
|
+
value: formatNumber(value),
|
|
659
|
+
raw: value,
|
|
660
|
+
start: position(),
|
|
661
|
+
end: position(value)
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function stringLiteral() {
|
|
669
|
+
let startQuote = '"';
|
|
670
|
+
let state = stringStates.START;
|
|
671
|
+
let i = index;
|
|
672
|
+
while (i < input.length) {
|
|
673
|
+
const ch = input[i];
|
|
674
|
+
|
|
675
|
+
if (state === stringStates.START) {
|
|
676
|
+
if (ch === '"' || ch === "'") {
|
|
677
|
+
startQuote = ch;
|
|
678
|
+
i++;
|
|
679
|
+
state = stringStates.START_QUOTE_OR_CHAR;
|
|
680
|
+
} else {
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
} else if (state === stringStates.ESCAPE) {
|
|
684
|
+
if (escapes.hasOwnProperty(ch) || ch === startQuote) {
|
|
685
|
+
i++;
|
|
686
|
+
state = stringStates.START_QUOTE_OR_CHAR;
|
|
687
|
+
} else {
|
|
688
|
+
const pos = position(input.substring(index, i + 1));
|
|
689
|
+
throw new SyntaxError(
|
|
690
|
+
`Unexpected token ${ch} in ${pos.line}:${pos.column}`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
} else if (ch === '\\') {
|
|
694
|
+
i++;
|
|
695
|
+
state = stringStates.ESCAPE;
|
|
696
|
+
} else if (ch === startQuote) {
|
|
697
|
+
i++;
|
|
698
|
+
break;
|
|
699
|
+
} else {
|
|
700
|
+
i++;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (i > index) {
|
|
704
|
+
const value = input.substring(index, i);
|
|
705
|
+
return {
|
|
706
|
+
type: TokenName[TokenEnum.StringLiteral],
|
|
707
|
+
value: escapeString(value.substring(1, value.length - 1), [startQuote]),
|
|
708
|
+
raw: value,
|
|
709
|
+
start: position(),
|
|
710
|
+
end: position(value)
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function identifier() {
|
|
717
|
+
// 变量模式是 resolveVariable 的时候使用的
|
|
718
|
+
// 这个纯变量获取模式,不支持其他什么表达式
|
|
719
|
+
// 仅仅支持 xxx.xxx 或者 xxx[ exression ] 这类语法
|
|
720
|
+
// 所以纯变量模式支持纯数字作为变量名
|
|
721
|
+
const reg = options?.variableMode
|
|
722
|
+
? /^[\u4e00-\u9fa5A-Za-z0-9_$@][\u4e00-\u9fa5A-Za-z0-9_\-$@]*/
|
|
723
|
+
: /^(?:[\u4e00-\u9fa5A-Za-z_$@]([\u4e00-\u9fa5A-Za-z0-9_\-$@]|\\(?:\.|\[|\]|\(|\)|\{|\}|\s|=|!|>|<|\||&|\+|-|\*|\/|\^|~|%|&|\?|:|;|,))*|\d+[\u4e00-\u9fa5A-Za-z_$@](?:[\u4e00-\u9fa5A-Za-z0-9_\-$@]|\\(?:\.|\[|\]|\(|\)|\{|\}|\s|=|!|>|<|\||&|\+|-|\*|\/|\^|~|%|&|\?|:|;|,))*)/;
|
|
724
|
+
|
|
725
|
+
const match = reg.exec(
|
|
726
|
+
input.substring(index, index + 256) // 变量长度不能超过 256
|
|
727
|
+
);
|
|
728
|
+
if (match) {
|
|
729
|
+
return {
|
|
730
|
+
type: TokenName[TokenEnum.Identifier],
|
|
731
|
+
value: match[0].replace(
|
|
732
|
+
/\\(\.|\[|\]|\(|\)|\{|\}|\s|=|!|>|<|\||&|\+|-|\*|\/|\^|~|%|&|\?|:|;|,)/g,
|
|
733
|
+
(_, v) => v
|
|
734
|
+
),
|
|
735
|
+
start: position(),
|
|
736
|
+
end: position(match[0])
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function getNextToken(): Token | void | null {
|
|
743
|
+
if (tokenCache.length) {
|
|
744
|
+
return tokenCache.shift()!;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (
|
|
748
|
+
mainState === mainStates.SCRIPT ||
|
|
749
|
+
mainState === mainStates.EXPRESSION ||
|
|
750
|
+
mainState === mainStates.BLOCK
|
|
751
|
+
) {
|
|
752
|
+
skipWhiteSpace();
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return eof() || raw() || openScript() || expression() || template();
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return {
|
|
759
|
+
next: function () {
|
|
760
|
+
const token = getNextToken();
|
|
761
|
+
|
|
762
|
+
if (token) {
|
|
763
|
+
index = token.end.index;
|
|
764
|
+
line = token.end.line;
|
|
765
|
+
column = token.end.column;
|
|
766
|
+
return token;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const pos = position();
|
|
770
|
+
throw new SyntaxError(
|
|
771
|
+
`unexpected character "${input[index]}" at ${pos.line}:${pos.column}`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
}
|