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