@cilix/lightjs 0.0.1

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.
Files changed (5) hide show
  1. package/README.md +1010 -0
  2. package/cli.js +163 -0
  3. package/core.js +2730 -0
  4. package/index.js +364 -0
  5. package/package.json +26 -0
package/core.js ADDED
@@ -0,0 +1,2730 @@
1
+ /*
2
+ * Copyright 2023 Matthew Levenstein
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining
5
+ * a copy of this software and associated documentation files (the “Software”),
6
+ * to deal in the Software without restriction, including without limitation
7
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8
+ * and/or sell copies of the Software, and to permit persons to whom the
9
+ * Software is furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
17
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
18
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
19
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ */
21
+
22
+ const fs = require('fs');
23
+ const path = require("path");
24
+
25
+ module.exports = (config) => {
26
+ const _files = {};
27
+
28
+ function throw_error (err) {
29
+ err.origin = 'light';
30
+ throw err;
31
+ };
32
+
33
+ const valid_tags = [
34
+ 'a',
35
+ 'abbr',
36
+ 'acronym',
37
+ 'address',
38
+ 'applet',
39
+ 'area',
40
+ 'article',
41
+ 'aside',
42
+ 'audio',
43
+ 'b',
44
+ 'base',
45
+ 'basefont',
46
+ 'bdi',
47
+ 'bdo',
48
+ 'bgsound',
49
+ 'big',
50
+ 'blink',
51
+ 'blockquote',
52
+ 'body',
53
+ 'br',
54
+ 'button',
55
+ 'canvas',
56
+ 'caption',
57
+ 'center',
58
+ 'cite',
59
+ 'code',
60
+ 'col',
61
+ 'colgroup',
62
+ 'command',
63
+ 'content',
64
+ 'data',
65
+ 'datalist',
66
+ 'dd',
67
+ 'del',
68
+ 'details',
69
+ 'dfn',
70
+ 'dialog',
71
+ 'dir',
72
+ 'div',
73
+ 'dl',
74
+ 'dt',
75
+ 'element',
76
+ 'em',
77
+ 'embed',
78
+ 'fieldset',
79
+ 'figcaption',
80
+ 'figure',
81
+ 'font',
82
+ 'footer',
83
+ 'form',
84
+ 'frame',
85
+ 'frameset',
86
+ 'h1',
87
+ 'h2',
88
+ 'h3',
89
+ 'h4',
90
+ 'h5',
91
+ 'h6',
92
+ 'head',
93
+ 'header',
94
+ 'hgroup',
95
+ 'hr',
96
+ 'html',
97
+ 'i',
98
+ 'iframe',
99
+ 'image',
100
+ 'img',
101
+ 'input',
102
+ 'ins',
103
+ 'isindex',
104
+ 'kbd',
105
+ 'keygen',
106
+ 'label',
107
+ 'legend',
108
+ 'li',
109
+ 'link',
110
+ 'listing',
111
+ 'main',
112
+ 'map',
113
+ 'mark',
114
+ 'marquee',
115
+ 'math',
116
+ 'menu',
117
+ 'menuitem',
118
+ 'meta',
119
+ 'meter',
120
+ 'multicol',
121
+ 'nav',
122
+ 'nextid',
123
+ 'nobr',
124
+ 'noembed',
125
+ 'noframes',
126
+ 'noscript',
127
+ 'object',
128
+ 'ol',
129
+ 'optgroup',
130
+ 'option',
131
+ 'output',
132
+ 'p',
133
+ 'param',
134
+ 'picture',
135
+ 'plaintext',
136
+ 'pre',
137
+ 'progress',
138
+ 'q',
139
+ 'rb',
140
+ 'rbc',
141
+ 'rp',
142
+ 'rt',
143
+ 'rtc',
144
+ 'ruby',
145
+ 's',
146
+ 'samp',
147
+ 'script',
148
+ 'search',
149
+ 'section',
150
+ 'select',
151
+ 'shadow',
152
+ 'slot',
153
+ 'small',
154
+ 'source',
155
+ 'spacer',
156
+ 'span',
157
+ 'strike',
158
+ 'strong',
159
+ 'style',
160
+ 'sub',
161
+ 'summary',
162
+ 'sup',
163
+ 'table',
164
+ 'tbody',
165
+ 'td',
166
+ 'template',
167
+ 'textarea',
168
+ 'tfoot',
169
+ 'th',
170
+ 'thead',
171
+ 'time',
172
+ 'title',
173
+ 'tr',
174
+ 'track',
175
+ 'tt',
176
+ 'u',
177
+ 'ul',
178
+ 'var',
179
+ 'video',
180
+ 'wbr',
181
+ 'xmp',
182
+ // svg tags
183
+ 'svg',
184
+ 'altGlyph',
185
+ 'altGlyphDef',
186
+ 'altGlyphItem',
187
+ 'animate',
188
+ 'animateColor',
189
+ 'animateMotion',
190
+ 'animateTransform',
191
+ 'circle',
192
+ 'clipPath',
193
+ 'color-profile',
194
+ 'cursor',
195
+ 'defs',
196
+ 'desc',
197
+ 'ellipse',
198
+ 'feBlend',
199
+ 'feColorMatrix',
200
+ 'feComponentTransfer',
201
+ 'feComposite',
202
+ 'feConvolveMatrix',
203
+ 'feDiffuseLighting',
204
+ 'feDisplacementMap',
205
+ 'feDistantLight',
206
+ 'feFlood',
207
+ 'feFuncA',
208
+ 'feFuncB',
209
+ 'feFuncG',
210
+ 'feFuncR',
211
+ 'feGaussianBlur',
212
+ 'feImage',
213
+ 'feMerge',
214
+ 'feMergeNode',
215
+ 'feMorphology',
216
+ 'feOffset',
217
+ 'fePointLight',
218
+ 'feSpecularLighting',
219
+ 'feSpotLight',
220
+ 'feTile',
221
+ 'feTurbulence',
222
+ 'filter',
223
+ 'font',
224
+ 'font-face',
225
+ 'font-face-format',
226
+ 'font-face-name',
227
+ 'font-face-src',
228
+ 'font-face-uri',
229
+ 'foreignObject',
230
+ 'g',
231
+ 'glyph',
232
+ 'glyphRef',
233
+ 'hkern',
234
+ 'image',
235
+ 'line',
236
+ 'linearGradient',
237
+ 'marker',
238
+ 'mask',
239
+ 'metadata',
240
+ 'missing-glyph',
241
+ 'mpath',
242
+ 'path',
243
+ 'pattern',
244
+ 'polygon',
245
+ 'polyline',
246
+ 'radialGradient',
247
+ 'rect',
248
+ 'script',
249
+ 'set',
250
+ 'stop',
251
+ 'style',
252
+ 'svg',
253
+ 'switch',
254
+ 'symbol',
255
+ 'text',
256
+ 'textPath',
257
+ 'title',
258
+ 'tref',
259
+ 'tspan',
260
+ 'use',
261
+ 'view',
262
+ 'vkern',
263
+ // end svg tags
264
+ ]
265
+
266
+
267
+ const void_tags = [
268
+ 'area',
269
+ 'base',
270
+ 'basefont',
271
+ 'bgsound',
272
+ 'br',
273
+ 'col',
274
+ 'command',
275
+ 'embed',
276
+ 'frame',
277
+ 'hr',
278
+ 'image',
279
+ 'img',
280
+ 'input',
281
+ 'isindex',
282
+ 'keygen',
283
+ 'link',
284
+ 'menuitem',
285
+ 'meta',
286
+ 'nextid',
287
+ 'param',
288
+ 'source',
289
+ 'track',
290
+ 'wbr'
291
+ ];
292
+
293
+ const getPathInfo = (p, base) => {
294
+ const full = base ? path.resolve(base, p) : path.resolve(p);
295
+ const parent = path.dirname(full);
296
+ return {
297
+ full,
298
+ parent
299
+ };
300
+ };
301
+
302
+ const openFile = (name) => {
303
+ const text = fs.readFileSync(name).toString();
304
+ _files[name] = text;
305
+ return text;
306
+ };
307
+
308
+ const resolveNodeModule = (name) => {
309
+ let filepath = null;
310
+ for (const p of module.paths) {
311
+ const f = path.join(p, name) + '.light';
312
+ if (fs.existsSync(f)) {
313
+ filepath = f;
314
+ break;
315
+ }
316
+ const dir = path.join(p, name, 'index.light');
317
+ if (fs.existsSync(dir)) {
318
+ filepath = dir;
319
+ break;
320
+ }
321
+ const package = path.join(p, name, 'package.json');
322
+ if (fs.existsSync(package)) {
323
+ const packageJson = JSON.parse(fs.readFileSync(package, 'utf8'));
324
+ if (packageJson.main) {
325
+ const mainFilePath = path.join(p, name, packageJson.main);
326
+ if (fs.existsSync(mainFilePath)) {
327
+ filepath = mainFilePath;
328
+ break;
329
+ }
330
+ }
331
+ }
332
+ }
333
+ return filepath;
334
+ }
335
+
336
+ const tokenize = (prog, file, offset) => {
337
+ let cursor = 0, end_pos = prog.length - 1;
338
+ let tokens = [{ type: "file_begin", data: file, pos: 0, file: file }];
339
+ let keywords = [
340
+ "tag",
341
+ "each",
342
+ "if",
343
+ "in",
344
+ "else",
345
+ "import",
346
+ "yield",
347
+ "on",
348
+ "export",
349
+ "file",
350
+ "const",
351
+ "let",
352
+ "nosync",
353
+ "as",
354
+ "global"
355
+ ];
356
+
357
+ let symbols = [
358
+ ".",
359
+ "#",
360
+ "=",
361
+ "[",
362
+ "]",
363
+ ";",
364
+ "{",
365
+ "}",
366
+ "(",
367
+ ")",
368
+ ":",
369
+ "$",
370
+ ",",
371
+ ">",
372
+ "<",
373
+ "?",
374
+ "|",
375
+ "+",
376
+ "/",
377
+ "-",
378
+ "*",
379
+ "%",
380
+ "!",
381
+ "@"
382
+ ];
383
+
384
+ function is_newline (c) {
385
+ return c == '\n' || c == '\r'
386
+ }
387
+
388
+ // amazing
389
+ // https://stackoverflow.com/a/32567789
390
+ function is_letter (c) {
391
+ return c.toLowerCase() != c.toUpperCase();
392
+ }
393
+
394
+ function break_into_chunks(text, cursor) {
395
+ let chunks = [];
396
+ let chunk = "";
397
+ let i = 0,
398
+ max = text.length;
399
+ let in_expr = false;
400
+ let pos = cursor;
401
+ while (i < max) {
402
+ if (text[i] === "{" && text[i+1] === "{" && in_expr === false) {
403
+ in_expr = true;
404
+ chunks.push({ type: "chunk", data: chunk, pos: pos, file: file });
405
+ chunk = "{{";
406
+ i += 2;
407
+ pos = cursor + i - 1;
408
+ } else if (text[i] === "}" && text[i+1] === "}" && in_expr === true) {
409
+ in_expr = false;
410
+ chunk += "}}";
411
+ let toks = tokenize(chunk, file, pos);
412
+ toks.shift(); //file_begin
413
+ toks.pop(); //eof
414
+ toks.forEach(function(t) {
415
+ chunks.push(t);
416
+ });
417
+ chunk = "";
418
+ i += 2;
419
+ pos = cursor + i + 1;
420
+ } else {
421
+ chunk += text[i++];
422
+ }
423
+ }
424
+ chunks.push({ type: "chunk", data: chunk, pos: pos, file: file });
425
+ return chunks;
426
+ }
427
+
428
+ let offs = offset || 0;
429
+
430
+ while (true) {
431
+ let c = prog[cursor];
432
+ let tok = { type: "", data: "", pos: offs + cursor, file: file };
433
+
434
+ if (cursor > end_pos) {
435
+ tok.type = "eof";
436
+ tokens.push(tok);
437
+ break;
438
+ } else if (c === " " || is_newline(c) || c === "\t") {
439
+ let i = cursor;
440
+ while (
441
+ i <= end_pos &&
442
+ (prog[i] === " " || prog[i] === "\t" || is_newline(prog[i]))
443
+ ) {
444
+ i++;
445
+ }
446
+ cursor = i;
447
+ continue;
448
+ } else if (c === "/" && prog[cursor + 1] === "/") {
449
+ let i = cursor;
450
+ while (c !== "\n" && i <= end_pos) c = prog[++i];
451
+ cursor = i;
452
+ continue;
453
+ } else if (c === "/" && prog[cursor + 1] === "*") {
454
+ let i = cursor + 2;
455
+ while (true) {
456
+ if (i >= end_pos) break;
457
+ if (prog[i] === '*' && prog[i+1] === '/') {
458
+ i += 2;
459
+ break;
460
+ }
461
+ i++;
462
+ }
463
+ cursor = i;
464
+ continue;
465
+ } else if (c >= "0" && c <= "9") {
466
+ let num = "";
467
+ let i = cursor;
468
+ let dot = false;
469
+ while ((c >= "0" && c <= "9") || c === ".") {
470
+ if (c === ".") {
471
+ if (dot) break;
472
+ else dot = true;
473
+ }
474
+ num += c;
475
+ c = prog[++i];
476
+ }
477
+ cursor = i;
478
+ tok.type = "number";
479
+ tok.data = parseFloat(num);
480
+ } else if (
481
+ (c === '-' && prog[cursor+1] === '-' && is_letter(prog[cursor+2])) ||
482
+ is_letter(c) || c === '_'
483
+ ){
484
+ let i = cursor;
485
+ tok.data = "";
486
+ while (
487
+ c &&
488
+ (is_letter(c) ||
489
+ (c >= "0" && c <= "9") ||
490
+ (c === "_") ||
491
+ (c === "-"))
492
+ ) {
493
+ tok.data += c;
494
+ c = prog[++i];
495
+ }
496
+ cursor = i;
497
+ let idx = keywords.indexOf(tok.data);
498
+ if (idx !== -1) {
499
+ tok.type = keywords[idx];
500
+ } else {
501
+ tok.type = "ident";
502
+ }
503
+ if (tok.data === "true" || tok.data === "false") {
504
+ tok.type = "bool";
505
+ tok.data = tok.data === "true";
506
+ } else if (tok.data === "null") {
507
+ tok.type = "null";
508
+ tok.data = null;
509
+ }
510
+ } else if (c === "<" && prog[cursor + 1] === "=" && prog[cursor + 2] === ">") {
511
+ tok.type = "<=>";
512
+ tok.data = "<=>";
513
+ cursor += 3;
514
+ } else if (c === ":" && prog[cursor + 1] === ":") {
515
+ tok.type = "::";
516
+ tok.data = "::";
517
+ cursor += 2;
518
+ } else if (c === "<" && prog[cursor + 1] === "=") {
519
+ tok.type = "<=";
520
+ tok.data = "<=";
521
+ cursor += 2;
522
+ } else if (c === ">" && prog[cursor + 1] === "=") {
523
+ tok.type = ">=";
524
+ tok.data = ">=";
525
+ cursor += 2;
526
+ } else if (c === "=" && prog[cursor + 1] === "=") {
527
+ tok.type = "==";
528
+ tok.data = "==";
529
+ cursor += 2;
530
+ } else if (c === "!" && prog[cursor + 1] === "=") {
531
+ tok.type = "!=";
532
+ tok.data = "!=";
533
+ cursor += 2;
534
+ } else if (c === "&" && prog[cursor + 1] === "&") {
535
+ tok.type = "&&";
536
+ tok.data = "&&";
537
+ cursor += 2;
538
+ } else if (c === "|" && prog[cursor + 1] === "|") {
539
+ tok.type = "||";
540
+ tok.data = "||";
541
+ cursor += 2;
542
+ } else if (
543
+ c === '"' &&
544
+ prog[cursor + 1] === '"' &&
545
+ prog[cursor + 2] === '"'
546
+ ) {
547
+ let str = "";
548
+ let i = cursor + 3;
549
+ while (true) {
550
+ if (i > end_pos) {
551
+ throw_error({ msg: "unterminated long string", pos: offs + cursor, file: file });
552
+ } else if (
553
+ prog[i] === '"' &&
554
+ prog[i + 1] === '"' &&
555
+ prog[i + 2] === '"'
556
+ ) {
557
+ i += 3;
558
+ break;
559
+ }
560
+ str += prog[i++];
561
+ }
562
+ tokens.push({ type: 'string', pos: offs + cursor, file: file });
563
+ tokens.push({ type: 'chunk', data: str, pos: offs + cursor, file: file })
564
+ cursor = i;
565
+ continue;
566
+ } else if (c === '`') {
567
+ let i = cursor + 1;
568
+ let text = '';
569
+ while (true) {
570
+ if (i > end_pos) {
571
+ throw_error({ msg: "unterminated string", pos: offs + cursor, file: file });
572
+ }
573
+ if (prog[i] === '`') {
574
+ i++;
575
+ break;
576
+ }
577
+ if (prog[i] === "\\" && prog[i + 1] === '`') {
578
+ text += prog[i + 1];
579
+ i += 2;
580
+ }
581
+ text += prog[i++];
582
+ }
583
+ let lines = text.split(/\r?\n/);
584
+ const start = lines[0];
585
+ const end = lines[lines.length - 1];
586
+ if (start === '') lines.shift();
587
+ if (!/\S/.test(end)) {
588
+ const len = end.length;
589
+ lines = lines.map((line) => {
590
+ return line.slice(len);
591
+ });
592
+ }
593
+ text = lines.join('\n');
594
+ tokens.push({ type: 'string', pos: offs + cursor, file: file });
595
+ tokens.push({ type: 'chunk', data: text, pos: offs + cursor, file: file })
596
+ cursor = i;
597
+ continue;
598
+ } else if (c === '"' || c === "'") {
599
+ let del = c;
600
+ let i = cursor + 1;
601
+ let text = '';
602
+ while (true) {
603
+ if (i > end_pos || is_newline(prog[i])) {
604
+ throw_error({ msg: "unterminated string", pos: offs + cursor, file: file });
605
+ }
606
+ if (prog[i] === del) {
607
+ i++;
608
+ break;
609
+ }
610
+ if (prog[i] === "\\" && prog[i + 1] === del) {
611
+ text += prog[i + 1];
612
+ i += 2;
613
+ }
614
+ text += prog[i++];
615
+ }
616
+ let chunks = break_into_chunks(text, cursor);
617
+ tokens.push({ type: 'string', pos: offs + cursor, file: file });
618
+ if (chunks.length > 1) {
619
+ chunks.forEach(function(c) {
620
+ tokens.push(c);
621
+ });
622
+ } else {
623
+ tokens.push({ type: 'chunk', data: text, pos: offs + cursor, file: file })
624
+ }
625
+ cursor = i;
626
+ continue;
627
+ } else if (c === "-" && prog[cursor + 1] === "-" && prog[cursor + 2] === "-") {
628
+ let i = cursor + 3;
629
+ let found = false;
630
+ while (i <= (end_pos - 2)) {
631
+ if (
632
+ prog[i] === "-" &&
633
+ prog[i + 1] === "-" &&
634
+ prog[i + 2] === "-"
635
+ ) {
636
+ i += 3;
637
+ found = true;
638
+ break;
639
+ }
640
+ tok.data += prog[i++];
641
+ }
642
+ if (!found) {
643
+ throw_error({ msg: "expected closing ---", pos: offs + cursor, file: file });
644
+ }
645
+ cursor = i;
646
+ tok.type = "js_context";
647
+ } else if (symbols.indexOf(c) !== -1) {
648
+ tok.type = c;
649
+ tok.data = c;
650
+ cursor++;
651
+ } else {
652
+ tok.type = tok.data = c;
653
+ cursor++;
654
+ }
655
+ tokens.push(tok);
656
+ }
657
+
658
+ return tokens;
659
+ };
660
+
661
+ const parse = (tokens) => {
662
+ let tok = tokens[0];
663
+ let cursor = 0;
664
+ let in_tag = false;
665
+ let ast = { type: 'file', data: { file: tok.file }, children: [] };
666
+ let parent = ast;
667
+
668
+ function ast_node(type, data) {
669
+ let node = { type, data, children: [] };
670
+ parent.children.push(node);
671
+ return node;
672
+ }
673
+
674
+ function next() {
675
+ tok = tokens[++cursor];
676
+ if (cursor === tokens.length) return 0;
677
+ return 1;
678
+ }
679
+
680
+ function unexpected() {
681
+ throw_error({ msg: "unexpected " + tok.type, pos: tok.pos, file: tok.file });
682
+ }
683
+
684
+ function expect(t) {
685
+ if (tok.type === t) {
686
+ next();
687
+ } else {
688
+ throw_error({
689
+ msg: "expected: " + t + " found: " + tok.type,
690
+ pos: tok.pos,
691
+ file: tok.file
692
+ });
693
+ }
694
+ }
695
+
696
+ function accept(t) {
697
+ if (tok.type === t) {
698
+ next();
699
+ return true;
700
+ }
701
+ return false;
702
+ }
703
+
704
+ function peek(t) {
705
+ if (tok.type === t) {
706
+ return true;
707
+ }
708
+ return false;
709
+ }
710
+
711
+ function get_dir (path) {
712
+ let del = path.indexOf('\\') > -1 ? '\\' : '/'
713
+ let dir = path.split(del);
714
+ dir.pop();
715
+ return dir.join(del);
716
+ }
717
+
718
+ function parse_string () {
719
+ let data = [];
720
+ let pos = tok.pos;
721
+ let file = tok.file;
722
+ expect('string');
723
+ while (true) {
724
+ let d = tok.data;
725
+ if (accept('chunk')) {
726
+ data.push({ type: 'chunk', data: d });
727
+ } else if (accept('{')) {
728
+ expect('{');
729
+ data.push(parse_expr());
730
+ expect('}');
731
+ expect('}');
732
+ } else {
733
+ break;
734
+ }
735
+ }
736
+ return { type: 'string', data: data, pos: pos, file: file }
737
+ }
738
+
739
+ function parse_acc () {
740
+ let acc = null;
741
+
742
+ while (true) {
743
+ if (accept('.')) {
744
+ let p = tok.pos, f = tok.file;
745
+ if (!acc) acc = [];
746
+ // because .ident is short for ['ident'] as in javascript
747
+ acc.push({
748
+ type: "string",
749
+ data: [{
750
+ type: "chunk",
751
+ data: tok.data
752
+ }],
753
+ pos: p,
754
+ file: f
755
+ });
756
+ expect('ident');
757
+ } else if (accept('[')) {
758
+ if (!acc) acc = [];
759
+ acc.push(parse_expr());
760
+ expect(']');
761
+ } else {
762
+ break;
763
+ }
764
+ }
765
+
766
+ return acc;
767
+ }
768
+
769
+ function parse_atom () {
770
+ let unop = tok.data;
771
+ let expr = { pos: tok.pos, file: tok.file };
772
+ if (accept('!') || accept('-')) {
773
+ return {
774
+ type: 'unop',
775
+ op: unop,
776
+ data: parse_atom(),
777
+ pos: tok.pos,
778
+ file: tok.file
779
+ };
780
+ }
781
+ if (peek('number') || peek('bool') || peek('null')) {
782
+ expr.type = tok.type;
783
+ expr.data = tok.data;
784
+ next();
785
+ } else if (peek('string')) {
786
+ expr = parse_string();
787
+ } else if (peek('ident')) {
788
+ expr = {
789
+ pos: tok.pos,
790
+ file: tok.file,
791
+ type: 'ident',
792
+ data: tok.data
793
+ };
794
+ next();
795
+ } else if (accept('(')) {
796
+ expr = {
797
+ type: 'parenthetical',
798
+ data: parse_expr(),
799
+ pos: expr.pos,
800
+ file: expr.file
801
+ };
802
+ expect(')');
803
+ } else if (peek('{')) {
804
+ expr.type = 'object';
805
+ expr.data = parse_object();
806
+ } else if (peek('[')) {
807
+ expr.type = 'array';
808
+ expr.data = parse_array();
809
+ } else {
810
+ unexpected();
811
+ }
812
+ let acc = parse_acc();
813
+ if (acc) {
814
+ acc.unshift(expr);
815
+ return {
816
+ type: 'accumulator',
817
+ data: acc,
818
+ pos: expr.pos,
819
+ file: expr.file
820
+ };
821
+ }
822
+ return expr
823
+ }
824
+
825
+ function get_precendence (op) {
826
+ return {
827
+ '||': 1,
828
+ '&&': 2,
829
+ '==': 3,
830
+ '!=': 3,
831
+ '<=': 4,
832
+ '>=': 4,
833
+ '>': 4,
834
+ '<': 4,
835
+ '+': 5,
836
+ '-': 5,
837
+ '*': 6,
838
+ '/': 6,
839
+ '%': 6
840
+ }[op] || null;
841
+ }
842
+
843
+ function parse_expr1 (min_prec) {
844
+ let expr = parse_atom();
845
+
846
+ while (true) {
847
+ let op = tok.data;
848
+ let prec = get_precendence(op);
849
+
850
+ if (!prec || prec < min_prec) {
851
+ break;
852
+ }
853
+
854
+ next();
855
+
856
+ expr = {
857
+ type: 'binop',
858
+ op: op,
859
+ data: [expr, parse_expr1(prec + 1)]
860
+ };
861
+ }
862
+
863
+ if (min_prec < 1 && accept('?')) {
864
+ expr = {
865
+ type: 'ternary',
866
+ data: [expr],
867
+ pos: expr.pos,
868
+ file: expr.file
869
+ };
870
+ expr.data.push(parse_expr1(0));
871
+ expect(':');
872
+ expr.data.push(parse_expr1(0));
873
+ }
874
+
875
+ return expr;
876
+ }
877
+
878
+ const pipeables = {
879
+ 'repeat': 1,
880
+ 'length': 0,
881
+ 'map': 1,
882
+ 'filter': 1,
883
+ 'toupper': 0,
884
+ 'tolower': 0,
885
+ 'split': 1,
886
+ 'includes': 1,
887
+ 'indexof': 1,
888
+ 'reverse': 0,
889
+ 'todata': 0,
890
+ 'replace': 2,
891
+ 'tostring': 0,
892
+ 'join': 1,
893
+ 'keys': 0,
894
+ 'values': 0,
895
+ 'trim': 0,
896
+ 'sin': 0,
897
+ 'cos': 0,
898
+ 'tan': 0,
899
+ 'sqrt': 0,
900
+ 'ceil': 0,
901
+ 'floor': 0,
902
+ 'rand': 0,
903
+ 'slice': 2
904
+ };
905
+
906
+ function parse_expr (min_prec) {
907
+ if (min_prec == null) min_prec = 0;
908
+ let expr = parse_expr1(min_prec);
909
+ while (true) {
910
+ if (accept('|')) {
911
+ const func = tok.data;
912
+ const { pos, file } = tok;
913
+ expect('ident');
914
+ let count = pipeables[func];
915
+ if (count == null) {
916
+ throw_error({
917
+ msg: `Cannot pipe into: ${func}`,
918
+ pos,
919
+ file
920
+ });
921
+ }
922
+ const args = [func, expr];
923
+ while (count > 0) {
924
+ args.push(parse_expr1(0));
925
+ count--;
926
+ }
927
+ expr = {
928
+ type: 'pipe',
929
+ data: args
930
+ };
931
+ } else {
932
+ break;
933
+ }
934
+ }
935
+ return expr;
936
+ }
937
+
938
+ function parse_object() {
939
+ let obj = {};
940
+ expect("{");
941
+ if (!peek('}')) {
942
+ while (true) {
943
+ let key = tok.data;
944
+ expect("ident");
945
+ expect(":");
946
+ obj[key] = parse_expr();
947
+ if (!accept(",")) break;
948
+ }
949
+ }
950
+ expect("}");
951
+ return obj;
952
+ }
953
+
954
+ function parse_array() {
955
+ let arr = [];
956
+ expect("[");
957
+ if (!peek(']')) {
958
+ while (true) {
959
+ arr.push(parse_expr());
960
+ if (!accept(",")) break;
961
+ }
962
+ }
963
+ expect("]");
964
+ return arr;
965
+ }
966
+
967
+ function parse_class_list() {
968
+ let classes = [];
969
+ while (true) {
970
+ if (!accept(".")) break;
971
+ classes.push({
972
+ type: "string",
973
+ data: [{
974
+ type: 'chunk',
975
+ data: tok.data
976
+ }],
977
+ pos: tok.pos,
978
+ file: tok.file
979
+ });
980
+ expect("ident");
981
+ }
982
+ return {
983
+ type: "array",
984
+ data: classes
985
+ };
986
+ }
987
+
988
+ function parse_attributes() {
989
+ let attr = {};
990
+ let events = [];
991
+ while (true) {
992
+ let key = tok.data;
993
+ if (accept("ident") || accept("as")) {
994
+ // allow ':' in attribute names
995
+ while (accept(':')) {
996
+ key += ':'
997
+ key += tok.data;
998
+ expect("ident");
999
+ }
1000
+ if (accept("=")) {
1001
+ if (accept("{")) {
1002
+ attr[key] = parse_expr();
1003
+ expect("}");
1004
+ } else if (peek("string")) {
1005
+ attr[key] = parse_string();
1006
+ } else {
1007
+ throw_error({
1008
+ msg: "unexpected " + tok.type,
1009
+ pos: tok.pos,
1010
+ file: tok.file
1011
+ });
1012
+ }
1013
+ } else {
1014
+ attr[key] = { type: "bool", data: true };
1015
+ }
1016
+ } else if (accept("on")) {
1017
+ expect(":");
1018
+ let handler;
1019
+ let evt = tok.data;
1020
+ expect("ident");
1021
+ expect("=");
1022
+ handler = parse_strict_string();
1023
+ let nosync = accept('nosync');
1024
+ events.push({ type: evt, handler: handler, sync: !nosync });
1025
+ } else {
1026
+ break;
1027
+ }
1028
+ }
1029
+ return [attr, events];
1030
+ }
1031
+
1032
+ function parse_custom_tag_body () {
1033
+ in_tag = true;
1034
+ parse_tag_list();
1035
+ in_tag = false;
1036
+ }
1037
+
1038
+ function parse_tag() {
1039
+ let name = tok.data;
1040
+ let { pos, file } = tok;
1041
+ let ns = null;
1042
+ expect("ident");
1043
+ if (accept('::')) {
1044
+ ns = name;
1045
+ name = tok.data;
1046
+ expect('ident');
1047
+ }
1048
+ let classlist = parse_class_list();
1049
+ let attr_data = parse_attributes();
1050
+ let events = attr_data[1];
1051
+ let attr = attr_data[0];
1052
+ if (classlist.data.length > 0) {
1053
+ if (attr.class) {
1054
+ if (attr.class.type === 'array') {
1055
+ attr.class.data = classlist.data.concat(attr.class.data);
1056
+ } else {
1057
+ classlist.data.push(attr.class);
1058
+ attr.class = classlist;
1059
+ }
1060
+ } else {
1061
+ attr.class = classlist;
1062
+ }
1063
+ }
1064
+ let node = ast_node('tag', {
1065
+ name: name,
1066
+ namespace: ns,
1067
+ attributes: attr,
1068
+ events: events,
1069
+ pos,
1070
+ file
1071
+ });
1072
+ let current = parent;
1073
+ parent = node;
1074
+ if (accept("[")) {
1075
+ parse_tag_list();
1076
+ expect("]");
1077
+ } else if (peek("string")) {
1078
+ let str = parse_string();
1079
+ ast_node('textnode', str);
1080
+ } else {
1081
+ unexpected();
1082
+ }
1083
+ parent = current;
1084
+ }
1085
+
1086
+ function parse_if_statement() {
1087
+ expect("(");
1088
+ let condition = parse_expr();
1089
+ expect(")");
1090
+ let current = parent;
1091
+ let node = ast_node('if', condition);
1092
+ parent = node;
1093
+ let pass = ast_node('block');
1094
+ parent = pass;
1095
+ if (accept("[")) {
1096
+ parse_tag_list();
1097
+ expect("]");
1098
+ } else {
1099
+ parse_tag();
1100
+ }
1101
+ if (accept("else")) {
1102
+ parent = node;
1103
+ let fail = ast_node('block');
1104
+ parent = fail;
1105
+ if (accept("[")) {
1106
+ parse_tag_list();
1107
+ expect("]");
1108
+ } else if (accept("if")) {
1109
+ parse_if_statement();
1110
+ } else {
1111
+ parse_tag();
1112
+ }
1113
+ }
1114
+ parent = current;
1115
+ }
1116
+
1117
+ const parse_tag_list = () => {
1118
+ if (accept("if")) {
1119
+ parse_if_statement();
1120
+ parse_tag_list();
1121
+ } else if (accept("each")) {
1122
+ expect("(");
1123
+ let it1, it0 = tok.data;
1124
+ expect("ident");
1125
+ if (accept(",")) {
1126
+ it1 = tok.data;
1127
+ expect("ident");
1128
+ }
1129
+ expect("in");
1130
+ let list = parse_expr();
1131
+ let node = ast_node('each', {
1132
+ list: list,
1133
+ iterators: [it0, it1]
1134
+ });
1135
+ expect(")");
1136
+ let current = parent;
1137
+ parent = node;
1138
+ if (accept("[")) {
1139
+ parse_tag_list();
1140
+ expect("]");
1141
+ } else {
1142
+ parse_tag();
1143
+ }
1144
+ parent = current;
1145
+ parse_tag_list();
1146
+ } else if (peek("ident")) {
1147
+ parse_tag();
1148
+ parse_tag_list();
1149
+ } else if (peek("string")) {
1150
+ let str = parse_string();
1151
+ ast_node('textnode', str);
1152
+ parse_tag_list();
1153
+ } else if (peek("yield")) {
1154
+ ast_node('yield', { pos: tok.pos, file: tok.file });
1155
+ next();
1156
+ parse_tag_list();
1157
+ } else if (in_tag && (peek('global') || peek('const') || peek('let'))) {
1158
+ parse_assignment();
1159
+ parse_tag_list();
1160
+ } else if (in_tag && peek('js_context')) {
1161
+ parent.js = tok.data;
1162
+ ast_node('js', { js: tok.data, pos: tok.pos, file: tok.file });
1163
+ next();
1164
+ parse_tag_list();
1165
+ }
1166
+ }
1167
+
1168
+ function parse_custom_tag() {
1169
+ expect("tag");
1170
+ let tag = tok.data;
1171
+ let pos = tok.pos, file = tok.file;
1172
+ expect("ident");
1173
+ expect("[");
1174
+ let node = ast_node('custom', { name: tag, pos: pos, file: file });
1175
+ let current = parent;
1176
+ parent = node;
1177
+ parse_custom_tag_body();
1178
+ parent = current;
1179
+ expect("]");
1180
+ return tag;
1181
+ }
1182
+
1183
+ function parse_strict_string () {
1184
+ expect('string');
1185
+ let data = tok.data;
1186
+ next();
1187
+ if (peek('{')) {
1188
+ throw_error({
1189
+ msg: 'cannot interpolate here',
1190
+ pos: tok.pos,
1191
+ file: tok.file
1192
+ });
1193
+ }
1194
+ return data;
1195
+ }
1196
+
1197
+ const parse_rhs = () => {
1198
+ let val;
1199
+ if (accept("file")) {
1200
+ const t = tok;
1201
+ const curr = getPathInfo(tok.file);
1202
+ const pathInfo = getPathInfo(parse_strict_string(), curr.parent);
1203
+ let file;
1204
+ try {
1205
+ file = openFile(pathInfo.full);
1206
+ } catch (e) {
1207
+ throw_error({
1208
+ msg: e.message,
1209
+ pos: t.pos,
1210
+ file: t.file
1211
+ });
1212
+ }
1213
+ val = {
1214
+ pos: tok.pos,
1215
+ file: tok.file,
1216
+ type: 'string',
1217
+ data: [{ type: 'chunk', data: file }]
1218
+ };
1219
+ } else {
1220
+ val = parse_expr();
1221
+ }
1222
+ return val;
1223
+ }
1224
+
1225
+ function parse_assignment () {
1226
+ const global = tok.data === 'global';
1227
+ next();
1228
+ let dst = { data: tok.data, pos: tok.pos, file: tok.file };
1229
+ expect('ident');
1230
+ accept("=");
1231
+ let val = parse_rhs();
1232
+ ast_node('set', {
1233
+ global,
1234
+ lhs: dst,
1235
+ rhs: val
1236
+ });
1237
+ }
1238
+
1239
+ function parse_file () {
1240
+ while (true) {
1241
+ if (tok.type === "file_begin") {
1242
+ next();
1243
+ } else if (tok.type === "eof") {
1244
+ if (!next()) {
1245
+ break;
1246
+ }
1247
+ } else if (accept('import')) {
1248
+ const t = tok;
1249
+ const curr = getPathInfo(tok.file);
1250
+ const pathStr = parse_strict_string();
1251
+ let ns;
1252
+ if (accept('as')) {
1253
+ ns = tok.data;
1254
+ expect('ident');
1255
+ }
1256
+ let pathInfo = { full: null };
1257
+ if (pathStr[0] !== '.' && pathStr.indexOf('.light') === -1) {
1258
+ pathInfo.full = resolveNodeModule(pathStr);
1259
+ }
1260
+ if (!pathInfo.full) {
1261
+ pathInfo = getPathInfo(pathStr, curr.parent);
1262
+ }
1263
+ let file;
1264
+ try {
1265
+ file = openFile(pathInfo.full);
1266
+ } catch (e) {
1267
+ throw_error({
1268
+ msg: e.message,
1269
+ pos: t.pos,
1270
+ file: t.file
1271
+ });
1272
+ }
1273
+ let toks = tokenize(file, pathInfo.full);
1274
+ let _ast = parse(toks);
1275
+ if (ns) _ast.data.namespace = ns;
1276
+ let node = ast_node('file', _ast.data);
1277
+ node.children = _ast.children;
1278
+ } else if (accept("export")) {
1279
+ let id, pos = tok.pos;
1280
+ let file = tok.file;
1281
+ if (peek('tag')) {
1282
+ id = parse_custom_tag();
1283
+ } else {
1284
+ id = tok.data;
1285
+ expect('ident');
1286
+ }
1287
+ ast_node('export', {
1288
+ name: id,
1289
+ pos: pos,
1290
+ file: file
1291
+ });
1292
+ } else if (tok.type === "ident") {
1293
+ parse_tag_list();
1294
+ } else if (peek("tag")) {
1295
+ parse_custom_tag();
1296
+ } else if (peek('const') || peek('let') || peek('global')) {
1297
+ parse_assignment();
1298
+ } else if (peek('js_context')) {
1299
+ ast_node('js', { js: tok.data, pos: tok.pos, file: tok.file });
1300
+ js_found = true;
1301
+ next();
1302
+ } else {
1303
+ throw_error({ msg: "unexpected: " + tok.type, pos: tok.pos, file: tok.file });
1304
+ }
1305
+ }
1306
+ }
1307
+
1308
+ parse_file();
1309
+
1310
+ return ast;
1311
+ };
1312
+
1313
+ const execute = (ast, initial_state, js, flush) => {
1314
+ let html = '';
1315
+
1316
+ const stack = [];
1317
+ const ctx = [];
1318
+ const globals = { data: initial_state };
1319
+
1320
+ let inScript = false;
1321
+ let inStyle = false;
1322
+
1323
+ const found = { head: false, head: false, body: false };
1324
+
1325
+ const emit = flush ? (txt) => {
1326
+ if (html.length > 100) {
1327
+ flush(html);
1328
+ html = txt;
1329
+ } else {
1330
+ html += txt;
1331
+ }
1332
+ } : (txt) => {
1333
+ html += txt;
1334
+ };
1335
+
1336
+ const getType = (v) => {
1337
+ if (Array.isArray(v)) return 'array';
1338
+ if (v === null) return null;
1339
+ if (typeof v === 'object') return 'object';
1340
+ if (typeof v === 'string') return 'string';
1341
+ if (typeof v === 'number') return 'number';
1342
+ if (typeof v === 'boolean') return 'boolean';
1343
+ return 'undefined';
1344
+ };
1345
+
1346
+ const evaluate = (n) => {
1347
+ walk(n);
1348
+ return stack.pop();
1349
+ };
1350
+
1351
+ const err = (msg, node) => {
1352
+ throw_error({ msg, file: node.file, pos: node.pos });
1353
+ };
1354
+
1355
+ const escapeHTML = (txt) => {
1356
+ return txt.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
1357
+ }
1358
+
1359
+ const assertType = (v, t, n) => {
1360
+ const type = getType(v);
1361
+ if (typeof t === 'string') t = [t];
1362
+ if (t.indexOf(type) === -1) {
1363
+ err(`Expected ${t.join('|')}, got ${type}`, n);
1364
+ }
1365
+ };
1366
+
1367
+ const walk = (node) => {
1368
+ switch (node.type) {
1369
+ case 'custom': {
1370
+ ctx[ctx.length - 1].tags[node.data.name] = {
1371
+ ctx: ctx[ctx.length - 1],
1372
+ children: node.children
1373
+ };
1374
+ } break;
1375
+ case 'file': {
1376
+ const namespace = node.data.namespace;
1377
+ ctx.push({
1378
+ state: [{}],
1379
+ yield: null,
1380
+ tags: {},
1381
+ namespaces: {},
1382
+ children: node.children,
1383
+ exports: []
1384
+ });
1385
+ node.children.forEach(walk);
1386
+ const c = ctx.pop();
1387
+ if (namespace) {
1388
+ const ns = {};
1389
+ ctx[ctx.length - 1].namespaces[namespace] = ns;
1390
+ c.exports.forEach((e) => {
1391
+ ns[e] = c.tags[e];
1392
+ });
1393
+ } else {
1394
+ c.exports.forEach((e) => {
1395
+ ctx[ctx.length - 1].tags[e] = c.tags[e];
1396
+ });
1397
+ }
1398
+ } break;
1399
+ case 'export': {
1400
+ if (!ctx[ctx.length - 1].tags[node.data.name]) {
1401
+ err(`Undefined tag: ${node.data.name}`, node.data);
1402
+ }
1403
+ ctx[ctx.length - 1].exports.push(node.data.name);
1404
+ } break;
1405
+ case 'tag': {
1406
+ const c = ctx[ctx.length - 1];
1407
+ const attr = node.data.attributes;
1408
+ const namespace = node.data.namespace;
1409
+ let custom = c.tags[node.data.name];
1410
+ if (namespace) {
1411
+ const ns = c.namespaces[namespace];
1412
+ if (ns[node.data.name]) {
1413
+ custom = ns[node.data.name];
1414
+ }
1415
+ }
1416
+ if (custom) {
1417
+ const props = {};
1418
+ for (let a in attr) {
1419
+ props[a] = evaluate(attr[a]);
1420
+ }
1421
+ ctx.push({
1422
+ state: [custom.ctx.state[0], { props }],
1423
+ yield: () => {
1424
+ const ref = ctx.pop();
1425
+ node.children.forEach(walk);
1426
+ ctx.push(ref);
1427
+ },
1428
+ tags: custom.ctx.tags,
1429
+ children: node.children
1430
+ });
1431
+ custom.children.forEach(walk);
1432
+ ctx.pop();
1433
+ break;
1434
+ }
1435
+ // todo: html -> head, body enforcement
1436
+ if (valid_tags.indexOf(node.data.name) === -1) {
1437
+ err(`Invalid tag: ${node.data.name}`, node.data);
1438
+ }
1439
+ if (node.data.name === 'script') {
1440
+ inScript = true;
1441
+ }
1442
+ if (node.data.name === 'style') {
1443
+ inStyle = true;
1444
+ }
1445
+ if (node.data.name === 'html') {
1446
+ emit('<!DOCTYPE html>');
1447
+ }
1448
+ emit('<');
1449
+ emit(node.data.name);
1450
+ for (let a in attr) {
1451
+ let val = evaluate(attr[a]);
1452
+ const t = getType(val);
1453
+ if (val !== false || a === 'innerHTML') {
1454
+ emit(' ');
1455
+ emit(a.replace('bind:', ''));
1456
+ emit('="');
1457
+ if (t === 'array') {
1458
+ val = val.map((c) => {
1459
+ if (getType(c) === 'object') {
1460
+ return Object.keys(c).filter((k) => c[k]).join(' ');
1461
+ }
1462
+ return c;
1463
+ }).join(' ');
1464
+ } else if (t === 'object') {
1465
+ val = Object.keys(val).filter((k) => v[k]).join(' ');
1466
+ } else if (t === 'string') {
1467
+ val = val.replace(/"/g, '&quot;');
1468
+ }
1469
+ emit(val);
1470
+ emit('"');
1471
+ }
1472
+ }
1473
+ emit('>');
1474
+ if (void_tags.indexOf(node.data.name) === -1) {
1475
+ if (node.data.name === 'html') {
1476
+ if (found.html) err('html tag may only be used once', node.data);
1477
+ found.html = true;
1478
+ } else if (!found.html) {
1479
+ err('html tag must be the first tag printed', node.data);
1480
+ } else if (node.data.name === 'head') {
1481
+ if (found.head) err('head tag may only be used once', node.data);
1482
+ found.head = true;
1483
+ } else if (node.data.name === 'body') {
1484
+ if (!found.head) err('Expected head tag', node.data);
1485
+ if (found.body) err('body tag may only be used once', node.data);
1486
+ found.body = true;
1487
+ }
1488
+ node.children.forEach(walk);
1489
+ if (node.data.name === 'head') {
1490
+ if (js) {
1491
+ emit('<script>(function (data) {');
1492
+ emit(js);
1493
+ emit('})(');
1494
+ emit(JSON.stringify(initial_state));
1495
+ emit(');<' + '/' + 'script>');
1496
+ } else {
1497
+ const rt = `(function (data){${js}})(${JSON.stringify(initial_state)})`;
1498
+ emit('<script>' + rt + '<' + '/script>');
1499
+ }
1500
+ }
1501
+ emit('</');
1502
+ emit(node.data.name);
1503
+ emit('>');
1504
+ inScript = false;
1505
+ }
1506
+ } break;
1507
+ case 'if': {
1508
+ if (evaluate(node.data)) {
1509
+ walk(node.children[0]);
1510
+ } else if (node.children[1]) {
1511
+ walk(node.children[1]);
1512
+ }
1513
+ } break;
1514
+ case 'each': {
1515
+ const it = node.data.iterators;
1516
+ const val = evaluate(node.data.list);
1517
+ const t = getType(val);
1518
+ const s = {};
1519
+ ctx[ctx.length - 1].state.push(s);
1520
+ if (t === 'array' || t === 'string') {
1521
+ for (let i = 0; i < val.length; i++) {
1522
+ s[it[0]] = val[i];
1523
+ s[it[1]] = i;
1524
+ node.children.forEach(walk);
1525
+ }
1526
+ } else if (t === 'object') {
1527
+ const keys = Object.keys(val);
1528
+ for (let i = 0; i < keys.length; i++) {
1529
+ s[it[0]] = keys[i];
1530
+ s[it[1]] = val[keys[i]];
1531
+ node.children.forEach(walk);
1532
+ }
1533
+ } else {
1534
+ err('Value is not iterable', node.data.list);
1535
+ }
1536
+ ctx[ctx.length - 1].state.pop();
1537
+ } break;
1538
+ case 'yield': {
1539
+ const c = ctx[ctx.length - 1];
1540
+ if (c.yield) {
1541
+ c.yield();
1542
+ } else {
1543
+ err('Cannot yield outside of a custom tag', node.data);
1544
+ }
1545
+ } break;
1546
+ case 'set': {
1547
+ const k = node.data.lhs.data;
1548
+ const c = ctx[ctx.length - 1];
1549
+ if (node.data.global) {
1550
+ globals[k] = evaluate(node.data.rhs);
1551
+ } else {
1552
+ c.state[c.state.length - 1][k] = evaluate(node.data.rhs);
1553
+ }
1554
+ } break;
1555
+ case 'textnode': {
1556
+ if (inScript || inStyle) {
1557
+ emit(evaluate(node.data));
1558
+ } else {
1559
+ emit(escapeHTML(evaluate(node.data)));
1560
+ }
1561
+ } break;
1562
+ case 'null':
1563
+ case 'number':
1564
+ case 'bool':
1565
+ case 'chunk':
1566
+ stack.push(node.data);
1567
+ break;
1568
+ case 'string': {
1569
+ let str = '';
1570
+ node.data.forEach((c) => {
1571
+ // todo: make sure the top of the stack contains a scalar value
1572
+ str += evaluate(c);
1573
+ });
1574
+ stack.push(str);
1575
+ } break;
1576
+ case 'object': {
1577
+ const keys = Object.keys(node.data);
1578
+ const obj = {};
1579
+ keys.forEach((k) => {
1580
+ obj[k] = evaluate(node.data[k]);
1581
+ });
1582
+ stack.push(obj);
1583
+ } break;
1584
+ case 'array': {
1585
+ const data = [];
1586
+ node.data.forEach((d) => {
1587
+ data.push(evaluate(d));
1588
+ });
1589
+ stack.push(data);
1590
+ } break;
1591
+ case 'ident': {
1592
+ const v = node.data;
1593
+ const c = ctx[ctx.length - 1];
1594
+ let found = false;
1595
+ if (globals[v] !== undefined) {
1596
+ stack.push(globals[v]);
1597
+ break;
1598
+ }
1599
+ for (let i = c.state.length - 1; i >= 0; i--) {
1600
+ if (c.state[i][v] !== undefined) {
1601
+ stack.push(c.state[i][v]);
1602
+ found = true;
1603
+ break;
1604
+ }
1605
+ }
1606
+ if (!found) {
1607
+ err(v + ' is undefined', node);
1608
+ }
1609
+ } break;
1610
+ case 'accumulator': {
1611
+ let v = node.data[0];
1612
+ let prev = v.data;
1613
+ let ptr = evaluate(v);
1614
+ for (let i = 1; i < node.data.length; i++) {
1615
+ v = node.data[i];
1616
+ const str = evaluate(v);
1617
+ assertType(str, ['string', 'number'], node.data[i]);
1618
+ if (ptr == null) {
1619
+ err(prev + ' is not defined', node.data[i-1]);
1620
+ }
1621
+ prev = str;
1622
+ ptr = ptr[str];
1623
+ }
1624
+ stack.push(ptr);
1625
+ } break;
1626
+ case 'ternary': {
1627
+ const v0 = evaluate(node.data[0]);
1628
+ const v1 = evaluate(node.data[1]);
1629
+ const v2 = evaluate(node.data[2]);
1630
+ stack.push(v0 ? v1 : v2);
1631
+ } break;
1632
+ case 'unop': {
1633
+ const v = evaluate(node.data);
1634
+ assertType(v, 'number', node.data);
1635
+ if (node.op === '-') {
1636
+ stack.push(-v);
1637
+ } else if (node.op === '!') {
1638
+ stack.push(!v);
1639
+ }
1640
+ } break;
1641
+ case 'binop': {
1642
+ const op = node.op;
1643
+ let v1 = node.data[0];
1644
+ let v2 = node.data[1];
1645
+ if (op === '&&') {
1646
+ stack.push(evaluate(v1) && evaluate(v2));
1647
+ break;
1648
+ } else if (op === '||') {
1649
+ stack.push(evaluate(v1) || evaluate(v2));
1650
+ break;
1651
+ }
1652
+ v1 = evaluate(v1);
1653
+ v2 = evaluate(v2);
1654
+ if (op === '==') {
1655
+ stack.push(v1 === v2);
1656
+ } else if (op === '!=') {
1657
+ stack.push(v1 !== v2);
1658
+ } else if (op === '<=') {
1659
+ stack.push(v1 <= v2);
1660
+ } else if (op === '>=') {
1661
+ stack.push(v1 >= v2);
1662
+ } else if (op === '>') {
1663
+ assertType(v1, 'number', node.data[0]);
1664
+ assertType(v2, 'number', node.data[1]);
1665
+ stack.push(v1 > v2);
1666
+ } else if (op === '<') {
1667
+ assertType(v1, 'number', node.data[0]);
1668
+ assertType(v2, 'number', node.data[1]);
1669
+ stack.push(v1 < v2);
1670
+ } else if (op === '+') {
1671
+ assertType(v1, ['number','string'], node.data[0]);
1672
+ assertType(v2, ['number', 'string'], node.data[1]);
1673
+ stack.push(v1 + v2);
1674
+ } else if (op === '-') {
1675
+ assertType(v1, 'number', node.data[0]);
1676
+ assertType(v2, 'number', node.data[1]);
1677
+ stack.push(v1 - v2);
1678
+ } else if (op === '*') {
1679
+ assertType(v1, 'number', node.data[0]);
1680
+ assertType(v2, 'number', node.data[1]);
1681
+ stack.push(v1 * v2);
1682
+ } else if (op === '/') {
1683
+ assertType(v1, 'number', node.data[0]);
1684
+ assertType(v2, 'number', node.data[1]);
1685
+ stack.push(v1 / v2);
1686
+ } else if (op === '%') {
1687
+ assertType(v1, 'number', node.data[0]);
1688
+ assertType(v2, 'number', node.data[1]);
1689
+ stack.push(v1 % v2);
1690
+ }
1691
+ } break;
1692
+ case 'parenthetical': {
1693
+ walk(node.data);
1694
+ } break;
1695
+ case 'pipe': {
1696
+ const op = node.data[0];
1697
+ switch (op) {
1698
+ case 'repeat': {
1699
+ const arr = [];
1700
+ const count = evaluate(node.data[2]);
1701
+ for (let i = 0; i < count; i++) {
1702
+ arr.push(evaluate(node.data[1]));
1703
+ }
1704
+ stack.push(arr);
1705
+ } break;
1706
+ case 'length': {
1707
+ const data = evaluate(node.data[1]);
1708
+ assertType(data, ['string', 'array'], node.data[1]);
1709
+ stack.push(data.length);
1710
+ } break;
1711
+ case 'filter':
1712
+ case 'map': {
1713
+ const t = node.data[0]; // map or filter
1714
+ const s = {};
1715
+ const l = evaluate(node.data[1]);
1716
+ const r = node.data[2];
1717
+ const c = ctx[ctx.length - 1];
1718
+ c.state.push(s);
1719
+ const res = l[t]((_a, _b) => {
1720
+ s._a = _a;
1721
+ s._b = _b;
1722
+ return evaluate(r);
1723
+ });
1724
+ stack.push(res);
1725
+ c.state.pop();
1726
+ } break;
1727
+ case 'toupper': {
1728
+ const val = evaluate(node.data[1]);
1729
+ assertType(val, 'string', node.data[1]);
1730
+ stack.push(val.toUpperCase());
1731
+ } break;
1732
+ case 'tolower': {
1733
+ const val = evaluate(node.data[1]);
1734
+ assertType(val, 'string', node.data[1]);
1735
+ stack.push(val.toLowerCase());
1736
+ } break;
1737
+ case 'split': {
1738
+ const val = evaluate(node.data[1]);
1739
+ const del = evaluate(node.data[2]);
1740
+ assertType(val, 'string', node.data[1]);
1741
+ assertType(del, 'string', node.data[2]);
1742
+ stack.push(val.split(del));
1743
+ } break;
1744
+ case 'includes': {
1745
+ const val = evaluate(node.data[1]);
1746
+ const del = evaluate(node.data[2]);
1747
+ stack.push(val.indexOf(del) > -1);
1748
+ } break;
1749
+ case 'indexof': {
1750
+ const val = evaluate(node.data[1]);
1751
+ const del = evaluate(node.data[2]);
1752
+ assertType(val, ['string', 'array'], node.data[1]);
1753
+ assertType(del, ['string', 'number'], node.data[2]);
1754
+ stack.push(val.indexOf(del));
1755
+ } break;
1756
+ case 'reverse': {
1757
+ const val = evaluate(node.data[1]);
1758
+ const t = getType(val);
1759
+ if (t === 'array') {
1760
+ stack.push(val.reverse());
1761
+ } else if (t === 'string') {
1762
+ stack.push(val.split('').reverse().join(''));
1763
+ } else {
1764
+ err(`Expected string or array`, node.data[1]);
1765
+ }
1766
+ } break;
1767
+ case 'todata': {
1768
+ const val = evaluate(node.data[1]);
1769
+ if (getType(val) === 'string') {
1770
+ if (!isNaN(val)) {
1771
+ stack.push(parseFloat(val));
1772
+ } else {
1773
+ try {
1774
+ stack.push(JSON.parse(val));
1775
+ } catch (_) {
1776
+ stack.push(val);
1777
+ }
1778
+ }
1779
+ } else {
1780
+ stack.push(val);
1781
+ }
1782
+ } break;
1783
+ case 'replace': {
1784
+ const e = evaluate(node.data[1]);
1785
+ const r = evaluate(node.data[2]);
1786
+ const n = evaluate(node.data[3]);
1787
+ assertType(e, 'string', node.data[1]);
1788
+ assertType(r, 'string', node.data[2]);
1789
+ assertType(n, 'string', node.data[3]);
1790
+ stack.push(e.replaceAll(r, n));
1791
+ } break;
1792
+ case 'slice': {
1793
+ const v = evaluate(node.data[1]);
1794
+ const s = evaluate(node.data[2]);
1795
+ const e = evaluate(node.data[3]);
1796
+ assertType(v, ['string', 'array'], node.data[1]);
1797
+ assertType(s, 'number', node.data[2]);
1798
+ assertType(e, 'number', node.data[3]);
1799
+ stack.push(v.slice(s, e));
1800
+ } break;
1801
+ case 'tostring': {
1802
+ const val = evaluate(node.data[1]);
1803
+ const t = getType(val);
1804
+ if (t === 'object' || t === 'null' || t === 'array') {
1805
+ return JSON.stringify(val);
1806
+ } else {
1807
+ return e.toString();
1808
+ }
1809
+ } break;
1810
+ case 'join': {
1811
+ const val = evaluate(node.data[1]);
1812
+ const del = evaluate(node.data[2]);
1813
+ assertType(val, 'array', node.data[1]);
1814
+ assertType(del, 'string', node.data[2]);
1815
+ stack.push(val.join(del));
1816
+ } break;
1817
+ case 'trim': {
1818
+ const val = evaluate(node.data[1]);
1819
+ assertType(val, 'string', node.data[1]);
1820
+ stack.push(val.trim());
1821
+ } break;
1822
+ case 'keys': {
1823
+ const val = evaluate(node.data[1]);
1824
+ assertType(val, 'object', node.data[1]);
1825
+ stack.push(Object.keys(val));
1826
+ } break;
1827
+ case 'values': {
1828
+ const val = evaluate(node.data[1]);
1829
+ assertType(val, 'object', node.data[1]);
1830
+ stack.push(Object.values(val));
1831
+ } break;
1832
+ case 'sin':
1833
+ case 'cos':
1834
+ case 'tan':
1835
+ case 'sqrt':
1836
+ case 'ceil':
1837
+ case 'floor': {
1838
+ const val = evaluate(node.data[1]);
1839
+ assertType(val, 'number', node.data[1]);
1840
+ stack.push(Math[op](val));
1841
+ } break;
1842
+ case 'rand': {
1843
+ const val = evaluate(node.data[1]);
1844
+ assertType(val, 'number', node.data[1]);
1845
+ stack.push(Math.random() * val);
1846
+ } break;
1847
+ default:
1848
+ break;
1849
+ }
1850
+ } break;
1851
+ default: {
1852
+ if (node.children) {
1853
+ node.children.forEach(walk);
1854
+ }
1855
+ } break;
1856
+ }
1857
+ };
1858
+
1859
+ walk(ast);
1860
+
1861
+ if (flush && html.length > 0) {
1862
+ flush(html);
1863
+ }
1864
+
1865
+ return html;
1866
+ };
1867
+
1868
+ const printError = (err) => {
1869
+ let prog = _files[err.file];
1870
+ let index = err.pos;
1871
+
1872
+ function get_line_info (index) {
1873
+ let i = 0, c = 1, last = 0;
1874
+ while (i < index) {
1875
+ if (prog[i++] === '\n') {
1876
+ last = i;
1877
+ c++;
1878
+ }
1879
+ }
1880
+ return { line: c, start: last, offset: index - last };
1881
+ }
1882
+
1883
+ function line_text (start) {
1884
+ let i = start, line = '';
1885
+ while (prog[i] !== '\n' && i < prog.length) {
1886
+ line += prog[i++];
1887
+ }
1888
+ return line;
1889
+ }
1890
+
1891
+ function line_to_pos (l) {
1892
+ let i = 0, count = 1;
1893
+ while (i < prog.length) {
1894
+ if (count === l) break;
1895
+ if (prog[i] === '\n') count++;
1896
+ i++;
1897
+ }
1898
+ return i;
1899
+ }
1900
+
1901
+ function digit_count (num) {
1902
+ return num.toString().length;
1903
+ }
1904
+
1905
+ let info = get_line_info(index);
1906
+
1907
+ const red = '\x1b[31m';
1908
+ const dim = '\x1b[2m';
1909
+ const yellow = '\x1b[33m';
1910
+ const reset = '\x1b[0m';
1911
+ const bright = '\x1b[1m';
1912
+
1913
+ console.log(`\n${red}Error: ${bright}${err.file}${reset}`);
1914
+ console.log(`\nLine ${info.line}:${info.offset}: ${yellow}${err.msg}${reset}\n`);
1915
+ console.log(`${dim}${info.line - 1}| ${reset}${line_text(line_to_pos(info.line - 1))}`);
1916
+ console.log(`${dim}${info.line}| ${reset}${line_text(info.start)}`);
1917
+ console.log(`${red}${'-'.repeat(digit_count(info.line) + 2 + info.offset)}^${reset}`);
1918
+
1919
+ return [
1920
+ `\nError: ${err.file}`,
1921
+ `\nLine ${info.line}:${info.offset}: ${err.msg}\n`,
1922
+ `${info.line - 1}| ${line_text(line_to_pos(info.line - 1))}`,
1923
+ `${info.line}| ${line_text(info.start)}`,
1924
+ `${'-'.repeat(digit_count(info.line) + 2 + info.offset)}^`
1925
+ ].join('\n');
1926
+ }
1927
+
1928
+ const light_runtime = `
1929
+ var $$states = {};
1930
+ var $$rendered = {};
1931
+ var $$is_syncing = false;
1932
+ var $$is_svg = false;
1933
+ var $$nodes = [];
1934
+
1935
+ function $$a (node, attrs, isSvg) {
1936
+ var old = node.__old;
1937
+ var ns = 'http://www.w3.org/2000/xlink';
1938
+ if (typeof attrs === 'string') {
1939
+ if (node.nodeValue !== attrs) {
1940
+ node.nodeValue = attrs;
1941
+ }
1942
+ return;
1943
+ }
1944
+ Object.keys(attrs).forEach(function (p) {
1945
+ var a = attrs[p];
1946
+ var v = a && a.constructor === Array ? a.map(function (v) {
1947
+ return typeof v === 'object' ? Object.keys(v).filter(function (k) {
1948
+ return v[k];
1949
+ }).join(' ') : v
1950
+ }).join(' ')
1951
+ : a != null && typeof a === 'object' ? Object.keys(a).filter(function (k) {
1952
+ return a[k];
1953
+ }).join(' ') : a;
1954
+ if (!$$is_svg && p in node) {
1955
+ if (old[p] !== v) {
1956
+ old[p] = v;
1957
+ node[p] = v;
1958
+ }
1959
+ } else if (v === false || v == null) {
1960
+ if ($$is_svg && (p === 'href' || p === 'xlink:href')) {
1961
+ node.removeAttributeNS(ns, 'href');
1962
+ } else {
1963
+ node.removeAttribute(p);
1964
+ }
1965
+ } else {
1966
+ if ($$is_svg && (p === 'href' || p === 'xlink:href')) {
1967
+ if (old[p] !== v) {
1968
+ node.setAttributeNS(ns, 'href', v);
1969
+ old[p] = v;
1970
+ }
1971
+ } else if (old[p] !== v) {
1972
+ node.setAttribute(p, v);
1973
+ old[p] = v;
1974
+ }
1975
+ }
1976
+ });
1977
+ return;
1978
+ }
1979
+
1980
+ function $$addEventListeners (node, events) {
1981
+ var keys = Object.keys(events);
1982
+ if (!node.__eventRefs) node.__eventRefs = {};
1983
+ else {
1984
+ for (var e in node.__eventRefs) {
1985
+ node.removeEventListener(e, node.__eventRefs[e]);
1986
+ }
1987
+ }
1988
+ keys.forEach(function (event) {
1989
+ node.addEventListener(event, events[event]);
1990
+ node.__eventRefs[event] = events[event];
1991
+ });
1992
+ }
1993
+
1994
+ function $$each (list, fn) {
1995
+ if (Array.isArray(list) || typeof list === 'string') {
1996
+ for (var i = 0; i < list.length; i++) {
1997
+ fn(list[i], i, i);
1998
+ }
1999
+ } else if (typeof list === 'object' && list != null) {
2000
+ var keys = Object.keys(list);
2001
+ for (var i = 0; i < keys.length; i++) {
2002
+ fn(keys[i], list[keys[i]], i);
2003
+ }
2004
+ } else {
2005
+ throw new Error(list + ' is not iterable');
2006
+ }
2007
+ }
2008
+
2009
+ function $$create (type) {
2010
+ var node, xmlns = 'http://www.w3.org/2000/svg';
2011
+ if (type === 'text') {
2012
+ node = document.createTextNode('');
2013
+ } else if ($$is_svg) {
2014
+ node = document.createElementNS(xmlns, type);
2015
+ } else {
2016
+ node = document.createElement(type);
2017
+ }
2018
+ return node;
2019
+ }
2020
+
2021
+ function $$clean () {
2022
+ var node = $$nodes.pop();
2023
+ var parent = node.ref;
2024
+ var num = node.processed;
2025
+ while (parent.childNodes[num]) {
2026
+ parent.removeChild(parent.childNodes[num]);
2027
+ }
2028
+ }
2029
+
2030
+ function $$parent () {
2031
+ var node = $$nodes[$$nodes.length - 1];
2032
+ var child = node.ref.childNodes[node.processed++];
2033
+ return { parent: node.ref, child: child };
2034
+ }
2035
+
2036
+ function $$e (type, id, attrs, events, children) {
2037
+ var node, _ = $$parent();
2038
+ var child = _.child, parent = _.parent;
2039
+ var tag = child && child.tagName ? child.tagName.toLowerCase() : null;
2040
+ if (type === 'svg') $$is_svg = true;
2041
+ if (child && child.__id === id) {
2042
+ node = child;
2043
+ } else if (child && !child.__id && tag === type) {
2044
+ node = child;
2045
+ node.__id = id;
2046
+ node.__old = {};
2047
+ } else {
2048
+ node = $$create(type);
2049
+ node.__id = id;
2050
+ node.__old = {};
2051
+ if (child) {
2052
+ parent.replaceChild(node, child);
2053
+ } else {
2054
+ parent.appendChild(node);
2055
+ }
2056
+ }
2057
+ $$a(node, attrs);
2058
+ $$addEventListeners(node, events);
2059
+ if (children && !attrs.innerHTML) {
2060
+ $$nodes.push({ ref: node, processed: 0 });
2061
+ children();
2062
+ $$clean();
2063
+ }
2064
+ if (type === 'svg') {
2065
+ $$is_svg = false;
2066
+ }
2067
+ }
2068
+
2069
+ function $$repeat(val, count) {
2070
+ var vals = [];
2071
+ for (var i = 0; i < count; i++) {
2072
+ if (typeof val === 'object') {
2073
+ vals.push(JSON.parse(JSON.stringify(val)));
2074
+ } else {
2075
+ vals.push(val);
2076
+ }
2077
+ }
2078
+ return vals;
2079
+ }
2080
+
2081
+ function $$todata(val) {
2082
+ if (typeof val === 'string') {
2083
+ if (!isNaN(val)) {
2084
+ return parseFloat(val);
2085
+ } else {
2086
+ try {
2087
+ return JSON.parse(val);
2088
+ } catch (e) {
2089
+ return val;
2090
+ }
2091
+ }
2092
+ } else {
2093
+ return val;
2094
+ }
2095
+ }
2096
+
2097
+ function $call() {
2098
+ var args = Array.prototype.slice.apply(arguments);
2099
+ var name = args.shift();
2100
+ return fetch('/', {
2101
+ method: 'POST',
2102
+ headers: { 'Content-Type': 'application/json' },
2103
+ body: JSON.stringify({
2104
+ type: 'ADOM_SERVER_FUNCTION',
2105
+ name: name,
2106
+ args: args
2107
+ })
2108
+ }).then(function (data) {
2109
+ return data.json();
2110
+ });
2111
+ }
2112
+
2113
+ function $$set_event (events, event, fn) {
2114
+ events[event] = fn;
2115
+ }
2116
+
2117
+ function $$emit_event (event, data) {
2118
+ var callback = this.events[event];
2119
+ if (callback) {
2120
+ callback(data);
2121
+ }
2122
+ };
2123
+
2124
+ function $$c (init) {
2125
+ return function (id, props, events, yield_fn) {
2126
+ var isNew = false;
2127
+ var pStr = JSON.stringify(props);
2128
+ var $state = $$states[id];
2129
+ if (!$state) {
2130
+ $state = { events: {}, props: {}, propsStr: pStr };
2131
+ isNew = true;
2132
+ }
2133
+ for (var prop in props) {
2134
+ $state.props[prop] = props[prop];
2135
+ }
2136
+ if (isNew) {
2137
+ $state.body = init($state.props, $$emit_event.bind($state), function (event, cb) {
2138
+ $$set_event($state.events, event, cb);
2139
+ });
2140
+ }
2141
+ for (var event in events) {
2142
+ $$set_event($state.events, event, events[event]);
2143
+ }
2144
+ const oldp = $state.propsStr;
2145
+ $$emit_event.call($state, 'prerender');
2146
+ if (pStr !== oldp) {
2147
+ $$emit_event.call($state, 'change', props, JSON.parse(oldp));
2148
+ }
2149
+ $state.body(id, $state.props, yield_fn);
2150
+ $state.propsStr = pStr;
2151
+ $$rendered[id] = true;
2152
+ $$states[id] = $state;
2153
+ if (isNew) {
2154
+ $$emit_event.call($state, 'mount');
2155
+ }
2156
+ $$emit_event.call($state, 'render');
2157
+ }
2158
+ }
2159
+
2160
+ function $$clean_states () {
2161
+ for (var id in $$states) {
2162
+ if (!$$rendered[id]) {
2163
+ $$emit_event.call($$states[id], 'unmount');
2164
+ delete $$states[id];
2165
+ }
2166
+ }
2167
+ $$rendered = {};
2168
+ }
2169
+ `;
2170
+
2171
+ const generateRuntime = (ast, actions) => {
2172
+ const out = [{ code: '', transform: false }];
2173
+ const fileList = [];
2174
+ const fileIdMap = {};
2175
+ const globals = {};
2176
+
2177
+ let custom = false;
2178
+ let fileIdx = -1;
2179
+ let loop_depth = 0;
2180
+ let tagId = 0;
2181
+
2182
+ const emit = (txt) => {
2183
+ out[out.length - 1].code += txt;
2184
+ if (fileIdx > -1) {
2185
+ const n = fileList[fileIdx].name;
2186
+ out[out.length - 1].parent_dir = getPathInfo(n).parent;
2187
+ }
2188
+ };
2189
+
2190
+ const createFileList = (node) => {
2191
+ if (node.type === 'file') {
2192
+ node.children.forEach((child) => {
2193
+ if (child.type === 'set' && child.data.global) {
2194
+ const g = globals[child.data.lhs.data];
2195
+ if (g && g === node.data.file) {
2196
+ throw_error({
2197
+ msg: 'Global already declared',
2198
+ pos: node.data.pos,
2199
+ file: node.data.file
2200
+ });
2201
+ } else {
2202
+ globals[child.data.lhs.data] = node.data.file;
2203
+ }
2204
+ }
2205
+ createFileList(child);
2206
+ });
2207
+ if (fileIdMap[node.data.file] == null) {
2208
+ fileIdMap[node.data.file] = fileList.length;
2209
+ fileList.push({
2210
+ name: node.data.file,
2211
+ ast: node,
2212
+ exports: {},
2213
+ tags: {},
2214
+ namespaces: {}
2215
+ });
2216
+ }
2217
+ }
2218
+ };
2219
+
2220
+ function idGen() {
2221
+ const indexes = [];
2222
+ if (custom) {
2223
+ indexes.push(`$$id`);
2224
+ }
2225
+ indexes.push(`'a-${tagId++}'`);
2226
+ for (let i = 0; i < loop_depth; i++) {
2227
+ indexes.push(`__index${i}`);
2228
+ }
2229
+ return indexes.join(" + '-' + ");
2230
+ }
2231
+
2232
+ function walk(node) {
2233
+ switch (node.type) {
2234
+ case 'if': {
2235
+ emit('if (');
2236
+ walk(node.data);
2237
+ emit(') {\n');
2238
+ node.children[0].children.forEach(walk);
2239
+ if (node.children[1]) {
2240
+ emit('} else {\n');
2241
+ node.children[1].children.forEach(walk);
2242
+ }
2243
+ emit('}\n');
2244
+ } break;
2245
+ case 'each': {
2246
+ emit('$$each(');
2247
+ walk(node.data.list);
2248
+ emit(', function (');
2249
+ const arg0 = node.data.iterators[0];
2250
+ const arg1 = node.data.iterators[1] || '___value';
2251
+ emit(`${arg0}, ${arg1}, __index${loop_depth}) {\n`);
2252
+ loop_depth++;
2253
+ node.children.forEach(walk);
2254
+ loop_depth--;
2255
+ emit('});\n');
2256
+ } break;
2257
+ case 'yield':
2258
+ emit('$$yield();');
2259
+ break;
2260
+ case 'export':
2261
+ emit('$components.');
2262
+ emit(node.data.name);
2263
+ emit(' = $');
2264
+ emit(node.data.name);
2265
+ emit(';\n');
2266
+ fileList[fileIdx].exports[node.data.name] = true;
2267
+ break;
2268
+ case 'js':
2269
+ out.push({
2270
+ code: '',
2271
+ transform: true,
2272
+ pos: node.data.pos,
2273
+ file: node.data.file
2274
+ });
2275
+ emit(node.data.js);
2276
+ out.push({ code: '', transform: false });
2277
+ break;
2278
+ case 'file':
2279
+ const namespace = node.data.namespace;
2280
+ const id = fileIdMap[node.data.file];
2281
+ const ex = fileList[id].exports;
2282
+ if (namespace) {
2283
+ emit('var $');
2284
+ emit(namespace);
2285
+ emit(' = $f');
2286
+ emit(id);
2287
+ emit('.components;');
2288
+ const ns = {};
2289
+ fileList[fileIdx].namespaces[namespace] = ns;
2290
+ for (let e in ex) {
2291
+ ns[e] = fileList[id].tags[e];
2292
+ }
2293
+ } else {
2294
+ for (let e in ex) {
2295
+ emit('var $');
2296
+ emit(e);
2297
+ emit(' = $f');
2298
+ emit(id);
2299
+ emit('.components.');
2300
+ emit(e);
2301
+ emit(';\n');
2302
+ fileList[fileIdx].tags[e] = fileList[id].tags[e];
2303
+ }
2304
+ }
2305
+ break;
2306
+ case 'custom': {
2307
+ let written = false;
2308
+ emit('var $');
2309
+ emit(node.data.name);
2310
+ emit(' = $$c(function (props, $emit, $on) {\n');
2311
+ fileList[fileIdx].tags[node.data.name] = node.children;
2312
+ if (node.children.length) {
2313
+ custom = true;
2314
+ node.children.forEach((child) => {
2315
+ if (child.type !== 'set' && child.type !== 'js' && !written) {
2316
+ emit('return function($$id, props, $$yield) {\n');
2317
+ written = true;
2318
+ }
2319
+ walk(child);
2320
+ });
2321
+ custom = false;
2322
+ emit('}\n');
2323
+ }
2324
+ emit('});\n');
2325
+ } break;
2326
+ case 'tag': {
2327
+ const namespace = node.data.namespace;
2328
+ const attr = node.data.attributes;
2329
+ const evts = node.data.events;
2330
+ if (namespace) {
2331
+ const ns = fileList[fileIdx].namespaces[namespace];
2332
+ if (ns) {
2333
+ if (ns[node.data.name]) {
2334
+ emit('$');
2335
+ emit(namespace);
2336
+ emit('.')
2337
+ emit(node.data.name);
2338
+ emit('(');
2339
+ emit(idGen());
2340
+ emit(', {');
2341
+ } else {
2342
+ throw_error({ msg: 'Invalid tag', pos: node.data.pos, file: node.data.file });
2343
+ }
2344
+ } else {
2345
+ throw_error({ msg: 'Invalid namespace', pos: node.data.pos, file: node.data.file });
2346
+ }
2347
+ } else if (fileList[fileIdx].tags[node.data.name]) {
2348
+ emit('$');
2349
+ emit(node.data.name);
2350
+ emit('(');
2351
+ emit(idGen());
2352
+ emit(', {');
2353
+ } else {
2354
+ if (node.data.name === 'head') {
2355
+ break;
2356
+ }
2357
+ if (node.data.name === 'html' || node.data.name === 'body') {
2358
+ emit('(function () {');
2359
+ node.children.forEach(walk);
2360
+ emit('})();\n');
2361
+ break;
2362
+ }
2363
+ if (valid_tags.indexOf(node.data.name) === -1) {
2364
+ throw_error({ msg: 'Invalid tag', pos: node.data.pos, file: node.data.file });
2365
+ }
2366
+ emit('$$e("');
2367
+ emit(node.data.name);
2368
+ emit('", ');
2369
+ emit(idGen());
2370
+ emit(', {');
2371
+ }
2372
+ for (let a in attr) {
2373
+ // todo: handle other values
2374
+ if (a === 'bind:value') {
2375
+ evts.push({ type: 'input', handler: `${attr[a].data} = $e.target.value;`, sync: true });
2376
+ attr.value = { type: 'ident', data: attr[a].data };
2377
+ a = 'value';
2378
+ }
2379
+ emit('"');
2380
+ emit(a);
2381
+ emit('": ');
2382
+ walk(attr[a]);
2383
+ emit(', ');
2384
+ }
2385
+ emit('}, {');
2386
+ for (let e of evts) {
2387
+ emit('"');
2388
+ emit(e.type);
2389
+ emit('": function($e) {');
2390
+ emit(e.handler);
2391
+ emit(`;${e.sync ? ' $sync();' : ''} }, `);
2392
+ }
2393
+ emit('}');
2394
+ if (node.children.length) {
2395
+ emit(', function () {\n');
2396
+ node.children.forEach(walk);
2397
+ emit('});\n');
2398
+ } else {
2399
+ emit(');\n')
2400
+ }
2401
+ } break;
2402
+ case 'textnode': {
2403
+ emit('$$e("text", ');
2404
+ emit(idGen());
2405
+ emit(', ');
2406
+ walk(node.data);
2407
+ emit(', {});')
2408
+ } break;
2409
+ case 'set': {
2410
+ if (!node.data.global) {
2411
+ emit('var ');
2412
+ }
2413
+ emit(node.data.lhs.data);
2414
+ emit(' = ');
2415
+ walk(node.data.rhs);
2416
+ emit(';\n');
2417
+ } break;
2418
+ case 'null':
2419
+ emit('null');
2420
+ break;
2421
+ case 'ident':
2422
+ case 'number':
2423
+ case 'bool':
2424
+ emit(node.data.toString());
2425
+ break;
2426
+ case 'chunk':
2427
+ emit('"' + node.data.replace(/"/g, '\\"').replace(/(\r\n|\n|\r)/gm, '\\n') + '"');
2428
+ break;
2429
+ case 'string':
2430
+ node.data.forEach(function (c, i) {
2431
+ walk(c);
2432
+ if (i < node.data.length - 1) {
2433
+ emit(' + ');
2434
+ }
2435
+ });
2436
+ break;
2437
+ case 'accumulator': {
2438
+ walk(node.data[0]);
2439
+ for (let i = 1; i < node.data.length; i++) {
2440
+ emit('[');
2441
+ walk(node.data[i]);
2442
+ emit(']');
2443
+ }
2444
+ } break;
2445
+ case 'array': {
2446
+ emit('[');
2447
+ node.data.forEach((i) => {
2448
+ walk(i);
2449
+ emit(', ');
2450
+ });
2451
+ emit(']');
2452
+ } break;
2453
+ case 'object': {
2454
+ const keys = Object.keys(node.data);
2455
+ emit('{');
2456
+ keys.forEach((k) => {
2457
+ emit(`"${k}": `)
2458
+ walk(node.data[k]);
2459
+ emit(', ');
2460
+ });
2461
+ emit('}');
2462
+ } break;
2463
+ case 'ternary': {
2464
+ emit('((');
2465
+ walk(node.data[0]);
2466
+ emit(')?(');
2467
+ walk(node.data[1]);
2468
+ emit('):(');
2469
+ walk(node.data[2]);
2470
+ emit('))');
2471
+ } break;
2472
+ case 'unop': {
2473
+ emit(node.op);
2474
+ emit('(');
2475
+ walk(node.data);
2476
+ emit(')');
2477
+ } break;
2478
+ case 'binop': {
2479
+ emit('(');
2480
+ walk(node.data[0]);
2481
+ emit(node.op);
2482
+ walk(node.data[1]);
2483
+ emit(')');
2484
+ } break;
2485
+ case 'parenthetical': {
2486
+ emit('(');
2487
+ walk(node.data);
2488
+ emit(')');
2489
+ } break;
2490
+ case 'pipe': {
2491
+ switch(node.data[0]) {
2492
+ case 'repeat': {
2493
+ emit('$$repeat(');
2494
+ walk(node.data[1]);
2495
+ emit(', ');
2496
+ walk(node.data[2]);
2497
+ emit(')');
2498
+ } break;
2499
+ case 'length': {
2500
+ walk(node.data[1]);
2501
+ emit('.length');
2502
+ } break;
2503
+ case 'map':
2504
+ case 'filter': {
2505
+ walk(node.data[1]);
2506
+ emit('.');
2507
+ emit(node.data[0]);
2508
+ emit('(function (_a, _b) { return ');
2509
+ walk(node.data[2]);
2510
+ emit('; })');
2511
+ } break;
2512
+ case 'toupper': {
2513
+ emit('(');
2514
+ walk(node.data[1]);
2515
+ emit(').toUpperCase()');
2516
+ } break;
2517
+ case 'tolower': {
2518
+ emit('(');
2519
+ walk(node.data[1]);
2520
+ emit(').toLowerCase()');
2521
+ } break;
2522
+ case 'split': {
2523
+ emit('(');
2524
+ walk(node.data[1]);
2525
+ emit(').split(');
2526
+ walk(node.data[2]);
2527
+ emit(')');
2528
+ } break;
2529
+ case 'includes': {
2530
+ emit('((');
2531
+ walk(node.data[1]);
2532
+ emit(').indexOf(');
2533
+ walk(node.data[2]);
2534
+ emit(') > -1)');
2535
+ } break;
2536
+ case 'indexof': {
2537
+ emit('(');
2538
+ walk(node.data[1]);
2539
+ emit(').indexOf(');
2540
+ walk(node.data[2]);
2541
+ emit(')');
2542
+ } break;
2543
+ case 'reverse': {
2544
+ emit('(Array.isArray(');
2545
+ walk(node.data[1]);
2546
+ emit(') ? (');
2547
+ walk(node.data[1]);
2548
+ emit(').reverse() : (');
2549
+ walk(node.data[1]);
2550
+ emit(").split('').reverse().join(''))");
2551
+ } break;
2552
+ case 'todata': {
2553
+ emit('$$todata(');
2554
+ walk(node.data[1]);
2555
+ emit(')');
2556
+ } break;
2557
+ case 'replace': {
2558
+ emit('(');
2559
+ walk(node.data[1]);
2560
+ emit(').replaceAll(');
2561
+ walk(node.data[2]);
2562
+ emit(', ');
2563
+ walk(node.data[3]);
2564
+ emit(')');
2565
+ } break;
2566
+ case 'slice': {
2567
+ emit('(');
2568
+ walk(node.data[1]);
2569
+ emit(').slice(');
2570
+ walk(node.data[2]);
2571
+ emit(', ');
2572
+ walk(node.data[3]);
2573
+ emit(')');
2574
+ } break;
2575
+ case 'tostring': {
2576
+ emit('(typeof (');
2577
+ walk(node.data[1]);
2578
+ emit(") === 'object' ? JSON.stringify(");
2579
+ walk(node.data[1]);
2580
+ emit(') : (');
2581
+ walk(node.data[1]);
2582
+ emit(').toString())');
2583
+ } break;
2584
+ case 'join': {
2585
+ emit('(');
2586
+ walk(node.data[1]);
2587
+ emit(').join(');
2588
+ walk(node.data[2]);
2589
+ emit(')');
2590
+ } break;
2591
+ case 'trim': {
2592
+ emit('(');
2593
+ walk(node.data[1]);
2594
+ emit(').trim()');
2595
+ } break;
2596
+ case 'keys': {
2597
+ emit('Object.keys(');
2598
+ walk(node.data[1]);
2599
+ emit(')');
2600
+ } break;
2601
+ case 'values': {
2602
+ emit('Object.values(');
2603
+ walk(node.data[1]);
2604
+ emit(')');
2605
+ } break;
2606
+ case 'sin':
2607
+ case 'cos':
2608
+ case 'tan':
2609
+ case 'sqrt':
2610
+ case 'ceil':
2611
+ case 'floor': {
2612
+ emit('Math.');
2613
+ emit(node.data[0]);
2614
+ emit('(');
2615
+ walk(node.data[1]);
2616
+ emit(')');
2617
+ } break;
2618
+ case 'rand': {
2619
+ emit('(Math.random() * ');
2620
+ walk(node.data[1]);
2621
+ emit(')');
2622
+ } break;
2623
+ default:
2624
+ break;
2625
+ }
2626
+ }
2627
+ default:
2628
+ break;
2629
+ }
2630
+ }
2631
+
2632
+ createFileList(ast);
2633
+
2634
+ emit(`document.addEventListener('DOMContentLoaded', function () {\n`);
2635
+ emit(`${light_runtime}`);
2636
+ emit('var $sync = function () {};\n');
2637
+ for (let g in globals) {
2638
+ emit('var ');
2639
+ emit(g);
2640
+ emit(';\n')
2641
+ }
2642
+ if (actions) {
2643
+ for (let a of actions) {
2644
+ emit(`var $${a} = $call.bind(undefined, '${a}');\n`);
2645
+ }
2646
+ }
2647
+ fileList.forEach((file, i) => {
2648
+ let written = false;
2649
+ const children = file.ast.children;
2650
+ fileIdx = i;
2651
+ emit('var $f');
2652
+ emit(i);
2653
+ emit(' = function () {\n');
2654
+ emit('var $components = {};\n');
2655
+ children.forEach((child) => {
2656
+ if (child.type === 'tag' && !written) {
2657
+ emit('$sync = function () {\n');
2658
+ emit('if ($$is_syncing === false) {\n');
2659
+ emit('$$is_syncing = true;\n');
2660
+ emit(`$$nodes.push({ ref: document.body, processed: 0 });\n`);
2661
+ written = true;
2662
+ }
2663
+
2664
+ walk(child);
2665
+ });
2666
+ if (written) {
2667
+ emit('$$clean();\n');
2668
+ emit('$$clean_states();\n');
2669
+ emit('$$is_syncing = false;\n');
2670
+ emit('}\n');
2671
+ emit('};\n');
2672
+ }
2673
+ emit('return { components: $components };\n');
2674
+ emit('}();\n');
2675
+ });
2676
+
2677
+ emit('$sync();\n');
2678
+ emit('});\n');
2679
+
2680
+ return out;
2681
+ };
2682
+
2683
+ const renderToAst = async (file, actions) => {
2684
+ const fileText = openFile(file);
2685
+ const tokens = tokenize(fileText, file);
2686
+ const ast = parse(tokens);
2687
+ const runtime = generateRuntime(ast, actions);
2688
+ let js;
2689
+
2690
+ if (config.jsTransform) {
2691
+ js = (await Promise.all(runtime.map(async (chunk) => {
2692
+ if (chunk.transform) {
2693
+ return await config.jsTransform(chunk);
2694
+ }
2695
+ return chunk.code;
2696
+ }))).join('\n');
2697
+ } else {
2698
+ js = runtime.map((chunk) => chunk.code).join('\n');
2699
+ }
2700
+
2701
+ if (config.jsPostProcess) {
2702
+ js = await config.jsPostProcess(js);
2703
+ }
2704
+
2705
+ return { ast, js };
2706
+ };
2707
+
2708
+ const renderToHTML = (ir, data, flush) => {
2709
+ if (typeof ir === 'string') {
2710
+ ir = JSON.parse(ir);
2711
+ }
2712
+ const html = execute(ir.ast, data || {}, ir.js, flush);
2713
+ return html;
2714
+ };
2715
+
2716
+ const render = async (file, data, flush, actions) => {
2717
+ const result = await renderToAst(file, actions);
2718
+ const html = renderToHTML(result, data, flush);
2719
+
2720
+ return html;
2721
+ };
2722
+
2723
+ const renderToCache = async (file, actions) => {
2724
+ const result = await renderToAst(file, actions);
2725
+ return JSON.stringify(result);
2726
+ };
2727
+
2728
+ return { render, renderToCache, renderToHTML, printError };
2729
+ };
2730
+