bobe 0.0.3

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.
@@ -0,0 +1,676 @@
1
+ class Queue {
2
+ constructor() {
3
+ this.len = 0;
4
+ }
5
+ get first() {
6
+ var _a;
7
+ return (_a = this._first) === null || _a === void 0 ? void 0 : _a.v;
8
+ }
9
+ get last() {
10
+ var _a;
11
+ return (_a = this._last) === null || _a === void 0 ? void 0 : _a.v;
12
+ }
13
+ push(it) {
14
+ this.len++;
15
+ const {
16
+ _last: last
17
+ } = this;
18
+ const item = {
19
+ v: it
20
+ };
21
+ if (!last) {
22
+ this._first = this._last = item;
23
+ return;
24
+ }
25
+ item.prev = this._last;
26
+ last.next = item;
27
+ this._last = item;
28
+ }
29
+ shift() {
30
+ const {
31
+ _first: first
32
+ } = this;
33
+ if (!first) return undefined;
34
+ this.len--;
35
+ const {
36
+ next
37
+ } = first;
38
+ first.next = undefined;
39
+ if (next) {
40
+ next.prev = undefined;
41
+ } else {
42
+ this._last = undefined;
43
+ }
44
+ this._first = next;
45
+ return first.v;
46
+ }
47
+ }
48
+ function isNum(char) {
49
+ return char === '0' || char === '1' || char === '2' || char === '3' || char === '4' || char === '5' || char === '6' || char === '7' || char === '8' || char === '9';
50
+ }
51
+ // const queue = new Queue([1,2,3,4]);
52
+ // queue.shift()
53
+ // queue.pop()
54
+ // // @ts-ignore
55
+ // queue.unshift('a')
56
+ // // @ts-ignore
57
+ // queue.push('b')
58
+ // queue.shift()
59
+ // queue.pop()
60
+ // queue.shift()
61
+ // queue.pop()
62
+ // queue.shift()
63
+ // queue.pop()
64
+ // queue.push(10)
65
+ // queue.array();
66
+
67
+ var TokenType;
68
+ (function (TokenType) {
69
+ TokenType[TokenType["NewLine"] = 0] = "NewLine";
70
+ TokenType[TokenType["Indent"] = 1] = "Indent";
71
+ TokenType[TokenType["Dedent"] = 2] = "Dedent";
72
+ TokenType[TokenType["Identifier"] = 3] = "Identifier";
73
+ TokenType[TokenType["Assign"] = 4] = "Assign";
74
+ TokenType[TokenType["Pipe"] = 5] = "Pipe";
75
+ TokenType[TokenType["Eof"] = 6] = "Eof";
76
+ })(TokenType || (TokenType = {}));
77
+ class Compiler {
78
+ get char() {
79
+ return this.code[this.i];
80
+ }
81
+ get prev() {
82
+ return this.code[this.i - 1];
83
+ }
84
+ get after() {
85
+ return this.code[this.i + 1];
86
+ }
87
+ at(i) {
88
+ return this.code[i];
89
+ }
90
+ next() {
91
+ const prev = this.code[this.i];
92
+ this.i++;
93
+ const curr = this.code[this.i];
94
+ return [prev, curr];
95
+ }
96
+ nextToken() {
97
+ // 已遍历到文件结尾
98
+ if (this.isEof()) {
99
+ return this.token;
100
+ }
101
+ this.token = undefined;
102
+ if (this.waitingTokens.len) {
103
+ const item = this.waitingTokens.shift();
104
+ this.setToken(item.type, item.value);
105
+ return this.token;
106
+ }
107
+ while (1) {
108
+ if (this.needIndent) {
109
+ this.tokenCreator.dent();
110
+ // 遍历到当前标识符非 空白为止
111
+ } else {
112
+ let {
113
+ char
114
+ } = this;
115
+ switch (char) {
116
+ case '\t':
117
+ case ' ':
118
+ // skip, 缩进通过 \n 匹配来激活 needIndent
119
+ break;
120
+ // 找后续所有 newLine
121
+ case '\n':
122
+ this.tokenCreator.newLine();
123
+ // 回车后需要判断缩进
124
+ this.needIndent = true;
125
+ break;
126
+ case '=':
127
+ this.tokenCreator.assignment();
128
+ break;
129
+ case '|':
130
+ this.tokenCreator.pipe();
131
+ break;
132
+ case "'":
133
+ case '"':
134
+ this.tokenCreator.str(char);
135
+ break;
136
+ case '$':
137
+ const handled = this.tokenCreator.dynamic(char);
138
+ if (handled) break;
139
+ default:
140
+ if (isNum(char)) {
141
+ this.tokenCreator.number(char);
142
+ break;
143
+ }
144
+ if (this.testId(char)) {
145
+ this.tokenCreator.identifier(char);
146
+ }
147
+ break;
148
+ }
149
+ // 指向下一个字符
150
+ this.next();
151
+ }
152
+ // 找到 token 即可停止
153
+ if (this.token) {
154
+ break;
155
+ }
156
+ }
157
+ return this.token;
158
+ }
159
+ consume() {
160
+ const token = this.token;
161
+ this.nextToken();
162
+ return token;
163
+ }
164
+ tokenize() {
165
+ var _a, _b;
166
+ do {
167
+ this.nextToken();
168
+ console.log('token:', TokenType[(_a = this.token) === null || _a === void 0 ? void 0 : _a.type], JSON.stringify(((_b = this.token) === null || _b === void 0 ? void 0 : _b.value) || ''));
169
+ } while (!this.isEof());
170
+ }
171
+ constructor() {
172
+ this.i = 0;
173
+ this.tokenIs = (...types) => {
174
+ if (types.length === 1) return types[0] === this.token.type;
175
+ return types.includes(this.token.type);
176
+ };
177
+ this.isEof = () => {
178
+ // 刚开始时 token 不存在
179
+ if (!this.token) return false;
180
+ return this.tokenIs(TokenType.Identifier) && this.token.value === this.EofId;
181
+ };
182
+ this.setToken = (type, value) => {
183
+ this.token = {
184
+ type,
185
+ typeName: TokenType[type],
186
+ value
187
+ };
188
+ this.isFirstToken = false;
189
+ };
190
+ this.TabSize = 2;
191
+ this.Tab = Array.from({
192
+ length: this.TabSize
193
+ }, () => ' ').join('');
194
+ this.IdExp = /[\d\w\/]/;
195
+ this.EofId = `__EOF__${Date.now()}`;
196
+ this.testId = value => {
197
+ if (typeof value !== 'string') return false;
198
+ return this.IdExp.test(value);
199
+ };
200
+ /** 记录历史缩进的长度,相对于行首 */
201
+ this.dentStack = [0];
202
+ this.needIndent = false;
203
+ this.isFirstToken = true;
204
+ /**
205
+ * 有些标识符能产生多个 token
206
+ * 例如 dedent
207
+ * parent1
208
+ * child
209
+ * subChild
210
+ * parent2 <- 产生两个 dedent
211
+ */
212
+ this.waitingTokens = new Queue();
213
+ this.tokenCreator = {
214
+ assignment: () => {
215
+ this.setToken(TokenType.Assign, '=');
216
+ },
217
+ pipe: () => {
218
+ this.setToken(TokenType.Pipe, '|');
219
+ },
220
+ dynamic: char => {
221
+ let nextC = this.after;
222
+ // 不是动态插值
223
+ if (nextC !== '{') {
224
+ return false;
225
+ }
226
+ this.next();
227
+ let value = '${';
228
+ let innerBrace = 0;
229
+ while (1) {
230
+ nextC = this.after;
231
+ value += nextC;
232
+ // 下一个属于本标识符再前进
233
+ this.next();
234
+ if (nextC === '{') {
235
+ innerBrace++;
236
+ }
237
+ if (nextC === '}') {
238
+ // 内部无左括号,说明完成匹配 TODO: 考虑js注释中的括号可能导致匹配错误
239
+ if (!innerBrace) {
240
+ break;
241
+ }
242
+ innerBrace--;
243
+ }
244
+ }
245
+ this.setToken(TokenType.Identifier, value);
246
+ return true;
247
+ },
248
+ newLine: () => {
249
+ let value = '\n';
250
+ let nextC;
251
+ while (1) {
252
+ nextC = this.after;
253
+ if (nextC !== '\n') {
254
+ break;
255
+ }
256
+ value += nextC;
257
+ // 下一个属于本标识符再前进
258
+ this.next();
259
+ }
260
+ // Program 希望第一个 token 一定是 node 节点
261
+ if (this.isFirstToken) {
262
+ return;
263
+ }
264
+ this.setToken(TokenType.NewLine, value);
265
+ },
266
+ dent: () => {
267
+ const handleDent = v => {
268
+ switch (v) {
269
+ case '\t':
270
+ return this.Tab;
271
+ case ' ':
272
+ return ' ';
273
+ case '\n':
274
+ return '\n';
275
+ default:
276
+ return '';
277
+ }
278
+ };
279
+ let value = '';
280
+ let nextC;
281
+ while (1) {
282
+ const nextChar = this.char;
283
+ nextC = handleDent(nextChar);
284
+ // \n 空白 \n 的情况,这行不算
285
+ if (nextC === '\n') {
286
+ this.needIndent = true;
287
+ // 这种情况下需要 next ,即后续从 \n 重新开始匹配
288
+ return true;
289
+ }
290
+ // 比较长度,比上个 indent 长,缩进,比上个 indent 短,dedent
291
+ if (!nextC) {
292
+ this.needIndent = false;
293
+ // 期望 firstToken 是 node,所以这里只要修改第一个节点的基础偏移值即可
294
+ if (this.isFirstToken) {
295
+ this.dentStack[0] = value.length;
296
+ return;
297
+ }
298
+ let currLen = value.length;
299
+ const indentHasLen = currLen > 0;
300
+ const prevLen = this.dentStack[this.dentStack.length - 1];
301
+ if (currLen > prevLen) {
302
+ this.dentStack.push(currLen);
303
+ this.setToken(TokenType.Indent, String(currLen));
304
+ return indentHasLen;
305
+ }
306
+ if (currLen < prevLen) {
307
+ // 一直找到最小
308
+ for (let i = this.dentStack.length - 2; i >= 0; i--) {
309
+ const expLen = this.dentStack[i];
310
+ const prevExpLen = this.dentStack[i + 1];
311
+ // 夹在两者说明缩进大小有问题
312
+ if (currLen > expLen && currLen < prevExpLen) {
313
+ throw SyntaxError('缩进大小不统一');
314
+ }
315
+ // current <= expLen 反缩进
316
+ this.dentStack.pop();
317
+ if (!this.token) {
318
+ this.setToken(TokenType.Dedent, String(expLen));
319
+ }
320
+ // 多余的 dent 缓存在 waitingTokens
321
+ else {
322
+ this.waitingTokens.push({
323
+ type: TokenType.Dedent,
324
+ typeName: TokenType[TokenType.Dedent],
325
+ value: String(expLen)
326
+ });
327
+ }
328
+ if (currLen === expLen) {
329
+ break;
330
+ }
331
+ }
332
+ return indentHasLen;
333
+ }
334
+ // 同级则无视
335
+ return indentHasLen;
336
+ }
337
+ value += nextC;
338
+ this.next();
339
+ }
340
+ },
341
+ identifier: char => {
342
+ let value = char;
343
+ let nextC;
344
+ while (1) {
345
+ nextC = this.after;
346
+ if (!this.testId(nextC)) {
347
+ break;
348
+ }
349
+ value += nextC;
350
+ this.next();
351
+ }
352
+ let realValue = value === 'null' ? null : value === 'undefined' ? undefined : value === 'false' || value === 'true' ? Boolean(value) : value;
353
+ this.setToken(TokenType.Identifier, realValue);
354
+ },
355
+ str: char => {
356
+ let value = '"';
357
+ let nextC;
358
+ let continuousBackslashCount = 0;
359
+ while (1) {
360
+ nextC = this.after;
361
+ value += nextC;
362
+ const memoCount = continuousBackslashCount;
363
+ if (nextC === '\\') {
364
+ continuousBackslashCount++;
365
+ } else {
366
+ continuousBackslashCount = 0;
367
+ }
368
+ this.next();
369
+ /**
370
+ * 引号前 \ 为双数时,全都是字符 \
371
+ * */
372
+ if (nextC === char && memoCount % 2 === 0) {
373
+ break;
374
+ }
375
+ }
376
+ this.setToken(TokenType.Identifier, JSON.parse(value.slice(0, -1) + '"'));
377
+ },
378
+ number: char => {
379
+ let value = char;
380
+ let nextC;
381
+ while (1) {
382
+ nextC = this.after;
383
+ if (!isNum(nextC)) {
384
+ break;
385
+ }
386
+ value += nextC;
387
+ this.next();
388
+ }
389
+ this.setToken(TokenType.Identifier, Number(value));
390
+ },
391
+ eof: () => {
392
+ this.setToken(TokenType.Eof, 'End Of File');
393
+ }
394
+ };
395
+ this.HookId = '_h_o_o_k_';
396
+ this.data = {};
397
+ this._hook = props => {
398
+ const value = this.token.value;
399
+ const isHook = value === this.HookId;
400
+ if (this.hook && isHook) {
401
+ const res = this.hook({
402
+ ...props,
403
+ HookId: this.HookId,
404
+ i: this.hookI
405
+ });
406
+ this.hookI++;
407
+ return [isHook, res];
408
+ }
409
+ return [isHook, value];
410
+ };
411
+ this.hookI = 0;
412
+ }
413
+ preprocess() {
414
+ // 保证开头能通过 换行进行 indent 计算
415
+ this.code = '\n' + this.code;
416
+ // 保证结尾 dedent 能正常配对
417
+ this.code = this.code.trimEnd() + `\n${this.EofId}`;
418
+ // console.log(this.code);
419
+ }
420
+ /**
421
+ * 根节点:
422
+ * 是 一个节点列表
423
+ * <program> ::= <nodeList>
424
+ */
425
+ program() {
426
+ // 初始化第一个 token
427
+ this.consume();
428
+ return this.nodeList();
429
+ }
430
+ /**
431
+ * 节点列表:
432
+ * 可以是一个节点,也可以跟随更多节点
433
+ * <nodeList> ::= <node> <nodeList> <EOF|Dedent>
434
+ * |
435
+ */
436
+ nodeList() {
437
+ const {
438
+ tokenIs
439
+ } = this;
440
+ const nodes = [];
441
+ let _node;
442
+ while (1) {
443
+ // 对于 Program EOF 表示 list 遍历完成
444
+ if (this.isEof()) {
445
+ return nodes;
446
+ }
447
+ // 对于 childList Dedent 表示 childList 遍历完成
448
+ if (tokenIs(TokenType.Dedent)) {
449
+ this.consume();
450
+ return nodes;
451
+ }
452
+ _node = this.node();
453
+ nodes.push(_node);
454
+ }
455
+ }
456
+ /**
457
+ * 单个节点:
458
+ * 由声明部分和(可选的)子节点块组成
459
+ * <node> ::= <declaration> <childrenBlockOpt>
460
+ * */
461
+ node() {
462
+ const _declaration = this.declaration();
463
+ _declaration.children = this.childrenBlockOpt();
464
+ return _declaration;
465
+ }
466
+ /**
467
+ * 声明部分:
468
+ * 包含首行定义和(可选的)多行属性扩展
469
+ * <declaration> ::= <tagName=token> <headerLine> <extensionLines>
470
+ * */
471
+ declaration() {
472
+ this.consume();
473
+ const [isHook, value] = this._hook({});
474
+ let _node;
475
+ if (isHook) {
476
+ const {
477
+ tree,
478
+ data
479
+ } = value();
480
+ _node = tree;
481
+ } else {
482
+ _node = this.createNode(value);
483
+ }
484
+ this.headerLine(_node);
485
+ this.extensionLines(_node);
486
+ return _node;
487
+ }
488
+ /**
489
+ * <extensionLines> ::= PIPE <attributeList> NEWLINE <extensionLines>
490
+ * | ε
491
+ */
492
+ extensionLines(_node) {
493
+ const {
494
+ tokenIs
495
+ } = this;
496
+ while (1) {
497
+ // 终止条件,下一行不是 pipe
498
+ if (!tokenIs(TokenType.Pipe)) {
499
+ return;
500
+ }
501
+ // 开始解析 attributeList
502
+ this.consume();
503
+ this.attributeList(_node);
504
+ // 文件结束了,通常不会发生
505
+ if (!tokenIs(TokenType.NewLine)) {
506
+ return;
507
+ }
508
+ // 换行
509
+ this.consume();
510
+ }
511
+ }
512
+ /**
513
+ * 首行:
514
+ * 节点名称 + 属性列表 + 换行
515
+ * <headerLine> ::= <attributeList> NEWLINE
516
+ */
517
+ headerLine(_node) {
518
+ this.attributeList(_node);
519
+ this.consume();
520
+ }
521
+ /**
522
+ * 属性列表:
523
+ * 可以是空的,或者包含多个属性
524
+ * <attributeList> ::= <attribute> <attributeList>
525
+ * | ε
526
+ *
527
+ * <attribute> ::= <key> <=> <value or dataKey> <=> <value>
528
+ */
529
+ attributeList(_node) {
530
+ let key = '';
531
+ let dataKey = '';
532
+ let defaultValue = undefined;
533
+ let prevIsAssign = false;
534
+ // 是标识符 或 赋值 就 继续累积 props
535
+ while (this.tokenIs(TokenType.Identifier, TokenType.Assign)) {
536
+ const [isHook, value] = this._hook({});
537
+ if (value === '=') {
538
+ prevIsAssign = true;
539
+ }
540
+ // 前一个不是等号,说明是 key
541
+ else if (!prevIsAssign) {
542
+ /*----------------- 开始下一个属性前进行赋值操作 -----------------*/
543
+ // 只声明 key 时 dataKey === key
544
+ if (!dataKey) {
545
+ dataKey = key;
546
+ }
547
+ // 三者都有
548
+ else if (defaultValue != null) ;
549
+ // 第二个值是 dataKey 或 defaultValue,看其是否是 $ 开头
550
+ else {
551
+ const valueOrKey = dataKey;
552
+ if (valueOrKey[0] === '$') {
553
+ dataKey = dataKey.slice(1);
554
+ }
555
+ // 值
556
+ else {
557
+ defaultValue = dataKey;
558
+ dataKey = undefined;
559
+ }
560
+ }
561
+ this.setDataProp(this.data, dataKey, defaultValue);
562
+ this.setProp(_node, key, this.data[dataKey], this.hookI - 1);
563
+ key = value;
564
+ }
565
+ // 前一个是等号
566
+ else {
567
+ if (!dataKey) {
568
+ dataKey = value;
569
+ } else {
570
+ defaultValue = value;
571
+ }
572
+ }
573
+ this.consume();
574
+ }
575
+ }
576
+ config(opt) {
577
+ Object.assign(this, opt);
578
+ }
579
+ createData(data) {
580
+ return data;
581
+ }
582
+ setDataProp(data, key, value) {
583
+ return data[key] = value;
584
+ }
585
+ createNode(name) {
586
+ return {
587
+ name,
588
+ props: {}
589
+ };
590
+ }
591
+ setProp(node, key, value, hookI) {
592
+ node.props[key] = value;
593
+ }
594
+ init(fragments) {
595
+ this.data = this.createData(this.data);
596
+ if (typeof fragments === 'string') {
597
+ this.code = fragments;
598
+ } else {
599
+ this.code = fragments.join(this.HookId);
600
+ }
601
+ return this.preprocess();
602
+ }
603
+ /** 子节点块:
604
+ * 必须被缩进包裹
605
+ * <childrenBlockOpt> ::= INDENT <nodeList>
606
+ * | ε /* 空(表示叶子节点,没有孩子)
607
+ * */
608
+ childrenBlockOpt() {
609
+ // 无 children
610
+ if (!this.tokenIs(TokenType.Indent)) {
611
+ return;
612
+ }
613
+ this.consume();
614
+ const list = this.nodeList();
615
+ return list;
616
+ }
617
+ }
618
+ let ast;
619
+ const updateList = [];
620
+ const cmp = new Compiler();
621
+ function bobe(fragments, ...values) {
622
+ // 增量更新
623
+ if (ast) {
624
+ updateList.forEach(({
625
+ old,
626
+ fn
627
+ }, i) => {
628
+ const val = values[i];
629
+ if (val !== old) {
630
+ console.log('增量更新', val);
631
+ fn(val);
632
+ }
633
+ });
634
+ console.log(JSON.stringify(ast, undefined, 2));
635
+ return ast;
636
+ }
637
+ // 初始化
638
+ cmp.config({
639
+ hook({
640
+ i
641
+ }) {
642
+ return values[i];
643
+ },
644
+ setProp(node, key, value, hookI) {
645
+ const fn = v => {
646
+ node.props[key] = v;
647
+ if (hookI != null) {
648
+ updateList[hookI] = {
649
+ fn,
650
+ old: v
651
+ };
652
+ }
653
+ };
654
+ fn(value);
655
+ }
656
+ });
657
+ cmp.init(Array.from(fragments));
658
+ ast = cmp.program();
659
+ console.log(JSON.stringify(ast, undefined, 2));
660
+ return ast;
661
+ }
662
+ // bobe`
663
+ // node1 k1=1
664
+ // node1_1 k2=false k3=3
665
+ // node1_1_1 k6=null
666
+ // node2
667
+ // | p1=1
668
+ // | p2=2 p3='你好'
669
+ // node2_1
670
+ // | p4=4 p5=${{ v: '🤡' }} p6=6
671
+ // node2_2
672
+ // | p7=7 p8=\${{ v: '🤡' }} p9=aaa
673
+ // node3 v1=1 v2=2 v3=undefined
674
+ // `;
675
+
676
+ export { Compiler, TokenType, bobe };