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/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
+ }