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.
- package/LICENSE +21 -0
- package/README.md +84 -0
- package/dist/bobe.cjs.js +681 -0
- package/dist/bobe.esm.js +676 -0
- package/dist/bobe.umd.js +689 -0
- package/dist/compiler/src/__test__/hello.d.ts +0 -0
- package/dist/compiler/src/index.d.ts +138 -0
- package/dist/shared/util.d.ts +22 -0
- package/package.json +37 -0
package/dist/bobe.cjs.js
ADDED
|
@@ -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;
|