lilact 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/Icon/r +0 -0
  2. package/LICENSE.TXT +26 -0
  3. package/README.md +116 -0
  4. package/bin/copy-demo.js +26 -0
  5. package/bin/transpile-dir.js +112 -0
  6. package/bin/transpile.js +137 -0
  7. package/dist/lilact.development.js +9344 -0
  8. package/dist/lilact.development.js.map +1 -0
  9. package/dist/lilact.production.min.js +63 -0
  10. package/dist/lilact.production.min.js.map +1 -0
  11. package/docs/.nojekyll +1 -0
  12. package/docs/assets/hierarchy.js +1 -0
  13. package/docs/assets/highlight.css +71 -0
  14. package/docs/assets/icons.js +18 -0
  15. package/docs/assets/icons.svg +1 -0
  16. package/docs/assets/main.js +60 -0
  17. package/docs/assets/navigation.js +1 -0
  18. package/docs/assets/search.js +1 -0
  19. package/docs/assets/style.css +1633 -0
  20. package/docs/classes/accessories.ErrorBoundary.html +37 -0
  21. package/docs/classes/accessories.Suspense.html +41 -0
  22. package/docs/classes/components.Component.html +45 -0
  23. package/docs/classes/components.HTMLComponent.html +32 -0
  24. package/docs/classes/components.RootComponent.html +32 -0
  25. package/docs/classes/transition.Transition.html +31 -0
  26. package/docs/functions/accessories.Spinner.html +9 -0
  27. package/docs/functions/components.createComponent.html +8 -0
  28. package/docs/functions/components.createRoot.html +5 -0
  29. package/docs/functions/components.render.html +5 -0
  30. package/docs/functions/hooks.createContext.html +3 -0
  31. package/docs/functions/hooks.useActionState.html +3 -0
  32. package/docs/functions/hooks.useCallback.html +5 -0
  33. package/docs/functions/hooks.useContext.html +3 -0
  34. package/docs/functions/hooks.useDeferredValue.html +4 -0
  35. package/docs/functions/hooks.useEffect.html +3 -0
  36. package/docs/functions/hooks.useHook.html +5 -0
  37. package/docs/functions/hooks.useId.html +4 -0
  38. package/docs/functions/hooks.useLayoutEffect.html +3 -0
  39. package/docs/functions/hooks.useLocalStorage.html +4 -0
  40. package/docs/functions/hooks.useMemo.html +4 -0
  41. package/docs/functions/hooks.useReducer.html +6 -0
  42. package/docs/functions/hooks.useRef.html +4 -0
  43. package/docs/functions/hooks.useState.html +3 -0
  44. package/docs/functions/hooks.useTransition.html +3 -0
  45. package/docs/functions/jsx.transpileJSX.html +6 -0
  46. package/docs/functions/misc.Fragment.html +3 -0
  47. package/docs/functions/misc.classNames.html +2 -0
  48. package/docs/functions/misc.deepEqual.html +3 -0
  49. package/docs/functions/misc.findDOMNode.html +3 -0
  50. package/docs/functions/misc.getComponentByPointer.html +3 -0
  51. package/docs/functions/misc.isAsync.html +3 -0
  52. package/docs/functions/misc.isClass.html +3 -0
  53. package/docs/functions/misc.isEmpty.html +3 -0
  54. package/docs/functions/misc.isError.html +3 -0
  55. package/docs/functions/misc.isThenable.html +3 -0
  56. package/docs/functions/misc.isValidElement.html +3 -0
  57. package/docs/functions/misc.shallowEqual.html +3 -0
  58. package/docs/functions/redux.Provider.html +5 -0
  59. package/docs/functions/redux.connect.html +5 -0
  60. package/docs/functions/redux.useDispatch.html +3 -0
  61. package/docs/functions/redux.useSelector.html +5 -0
  62. package/docs/functions/redux.useStore.html +3 -0
  63. package/docs/functions/router.HashRouter.html +5 -0
  64. package/docs/functions/router.Link.html +12 -0
  65. package/docs/functions/router.NavLink.html +10 -0
  66. package/docs/functions/router.Route.html +6 -0
  67. package/docs/functions/router.Routes.html +4 -0
  68. package/docs/functions/router.useLocation.html +3 -0
  69. package/docs/functions/router.useNavigate.html +3 -0
  70. package/docs/functions/run.require.html +14 -0
  71. package/docs/functions/run.run.html +13 -0
  72. package/docs/functions/run.runScripts.html +7 -0
  73. package/docs/functions/run.traceError.html +6 -0
  74. package/docs/functions/timers.animationFramePromise.html +3 -0
  75. package/docs/functions/timers.clearInterval.html +2 -0
  76. package/docs/functions/timers.clearTimeout.html +2 -0
  77. package/docs/functions/timers.grabTimers.html +3 -0
  78. package/docs/functions/timers.pauseTimers.html +2 -0
  79. package/docs/functions/timers.releaseTimers.html +2 -0
  80. package/docs/functions/timers.resetTimers.html +2 -0
  81. package/docs/functions/timers.resumeTimers.html +2 -0
  82. package/docs/functions/timers.setInterval.html +5 -0
  83. package/docs/functions/timers.setTimeout.html +4 -0
  84. package/docs/functions/timers.timeoutPromise.html +4 -0
  85. package/docs/functions/transition.CSSTransition.html +18 -0
  86. package/docs/functions/transition.TransitionGroup.html +5 -0
  87. package/docs/hierarchy.html +1 -0
  88. package/docs/index.html +88 -0
  89. package/docs/media/icon.png +0 -0
  90. package/docs/modules/accessories.html +1 -0
  91. package/docs/modules/components.html +1 -0
  92. package/docs/modules/hooks.html +1 -0
  93. package/docs/modules/jsx.html +1 -0
  94. package/docs/modules/misc.html +1 -0
  95. package/docs/modules/redux.html +1 -0
  96. package/docs/modules/router.html +1 -0
  97. package/docs/modules/run.html +1 -0
  98. package/docs/modules/timers.html +1 -0
  99. package/docs/modules/transition.html +1 -0
  100. package/docs/modules.html +1 -0
  101. package/docs/static/demos/actionstate.jsx +41 -0
  102. package/docs/static/demos/boundary.jsx +52 -0
  103. package/docs/static/demos/context.jsx +68 -0
  104. package/docs/static/demos/css-transition.jsx +65 -0
  105. package/docs/static/demos/modal.jsx +41 -0
  106. package/docs/static/demos/proptypes.jsx +37 -0
  107. package/docs/static/demos/reducer.jsx +71 -0
  108. package/docs/static/demos/redux.jsx +58 -0
  109. package/docs/static/demos/router.jsx +153 -0
  110. package/docs/static/demos/stopwatch.jsx +42 -0
  111. package/docs/static/demos/suspense.jsx +48 -0
  112. package/docs/static/demos/transition.jsx +40 -0
  113. package/docs/static/demos/use-differed.jsx +30 -0
  114. package/docs/static/demos/usetransition.jsx +37 -0
  115. package/docs/static/index.html +212 -0
  116. package/docs/static/lilact.development.js +9344 -0
  117. package/docs/static/lilact.production.min.js +63 -0
  118. package/docs/static/prismjs/LICENSE.md +21 -0
  119. package/docs/static/prismjs/prism-jsx.min.js +1 -0
  120. package/docs/static/prismjs/prism.min.css +1 -0
  121. package/docs/static/prismjs/prism.min.js +16 -0
  122. package/docs/variables/jsx.transpilerConfig.html +1 -0
  123. package/docs/variables/misc.Children.html +10 -0
  124. package/icon.png +0 -0
  125. package/package.json +39 -0
  126. package/root/demos/actionstate.jsx +41 -0
  127. package/root/demos/boundary.jsx +52 -0
  128. package/root/demos/context.jsx +68 -0
  129. package/root/demos/css-transition.jsx +65 -0
  130. package/root/demos/modal.jsx +41 -0
  131. package/root/demos/proptypes.jsx +37 -0
  132. package/root/demos/reducer.jsx +71 -0
  133. package/root/demos/redux.jsx +58 -0
  134. package/root/demos/router.jsx +153 -0
  135. package/root/demos/stopwatch.jsx +42 -0
  136. package/root/demos/suspense.jsx +48 -0
  137. package/root/demos/transition.jsx +40 -0
  138. package/root/demos/use-differed.jsx +30 -0
  139. package/root/demos/usetransition.jsx +37 -0
  140. package/root/index.html +212 -0
  141. package/root/lilact.development.js +9344 -0
  142. package/root/lilact.production.min.js +63 -0
  143. package/root/prismjs/LICENSE.md +21 -0
  144. package/root/prismjs/prism-jsx.min.js +1 -0
  145. package/root/prismjs/prism.min.css +1 -0
  146. package/root/prismjs/prism.min.js +16 -0
  147. package/src/accessories.jsx +287 -0
  148. package/src/components.jsx +1063 -0
  149. package/src/events.jsx +175 -0
  150. package/src/hooks.jsx +461 -0
  151. package/src/jsx.addons.js +70 -0
  152. package/src/jsx.js +1168 -0
  153. package/src/lilact.jsx +115 -0
  154. package/src/loader.cjs +59 -0
  155. package/src/misc.jsx +419 -0
  156. package/src/redux.jsx +198 -0
  157. package/src/router.jsx +282 -0
  158. package/src/run.jsx +275 -0
  159. package/src/timers.jsx +313 -0
  160. package/src/transition.jsx +244 -0
  161. package/src/vlq.js +94 -0
  162. package/tsconfig.json +15 -0
  163. package/typedoc.json +23 -0
  164. package/webpack.config.js +196 -0
package/src/jsx.js ADDED
@@ -0,0 +1,1168 @@
1
+ /*
2
+
3
+ Lilact
4
+ Copyright (C) 2024-2026 Arash Kazemi <contact.arash.kazemi@gmail.com>
5
+ All rights reserved.
6
+
7
+ BSD-2-Clause
8
+
9
+ Redistribution and use in source and binary forms, with or without
10
+ modification, are permitted provided that the following conditions are met:
11
+
12
+ * Redistributions of source code must retain the above copyright
13
+ notice, this list of conditions and the following disclaimer.
14
+ * Redistributions in binary form must reproduce the above copyright
15
+ notice, this list of conditions and the following disclaimer in the
16
+ documentation and/or other materials provided with the distribution.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
22
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ */
30
+
31
+ // This is a JSX transpiler that covers all the standard with some
32
+ // additional features like a nice preprocessor. It doesn't rely on any other
33
+ // library, even Lilact itself. It also generates sourcemap internally!
34
+ // It is not very fast, the xml look-ahead can be quite expensive, but it is not
35
+ // slow at all.
36
+
37
+ // Check the webpack config and also the transpiler.js helper script for
38
+ // usage outside Lilact. Also make sure to check the default configuration.
39
+
40
+ // There are some situations that are solved with tricks, as some aspects of
41
+ // javascripts import a complete parser and I didn't want to get into that!
42
+ // Works almosr perfectly so far (all observed bugs except 1 are fixed), works
43
+ // even on nasty things like the compressed version of tensorflow.js! Feel free
44
+ // to report any bugs, and please include a testable example. Thanks! :)
45
+ //
46
+ // note: until an efficient fix is found, make sure the slashes inside
47
+ // a regexp are escaped, even if they work without escaping.
48
+
49
+
50
+ // todo: injected string try catchs should be generated by a function
51
+
52
+ import * as jsxAddons from './jsx.addons.js';
53
+
54
+ export const transpilerConfig = {
55
+ addons: jsxAddons,
56
+ setFunctionLabels: true,
57
+ enableLabelStack: false,
58
+ injectLabels: true,
59
+ // todo: alt+shift+7 on apple abc extended layout is better i think,
60
+ // but my current editor doesn't support syntax highlighting with it.
61
+ preprocessorDelimiter: 'ʔ', // alt+shift+. on apple abc extended layout
62
+
63
+
64
+ required: {},
65
+ func_labels: {},
66
+ func_num: Math.floor(Math.random()*10000)
67
+ }
68
+
69
+
70
+ const TRANSPILER_OUTPUT = Symbol.for("LILACT:TRANSPILER_OUTPUT");
71
+
72
+ import * as vlq from './vlq.js';
73
+
74
+ let raiseError;
75
+ let tab=0;
76
+
77
+ function lookAhead(f, code, index, ...args)
78
+ {
79
+ const b = f(code, index, ...args);
80
+ if(b) {
81
+ return [b.end, b];
82
+ }
83
+ return [index];
84
+ }
85
+
86
+ function parseRegex(code, index, container)
87
+ {
88
+ // previous token can only be(end with) ;=,([{}!^~&|:?*-+\\/<> or directive or
89
+ // the slash can only be the first character
90
+ // the backward scan automatically passes for i=0
91
+
92
+ // todo: i have doubts about including preprocessorDelimiter
93
+
94
+ let i = index;
95
+ const dels = `;=,([{}!^~&|:?*-+\\/<>${transpilerConfig.preprocessorDelimiter}`;
96
+ while(--i>0) {
97
+ if( ' \t\n\r'.indexOf(code[i])!==-1 ) continue;
98
+ if( dels.indexOf(code[i])!==-1 ) break;
99
+ return;
100
+ }
101
+
102
+ const b = {
103
+ type: "regex",
104
+ begin: index++
105
+ };
106
+
107
+ while(index<code.length) {
108
+ const ch = code[index];
109
+ switch(ch) {
110
+ case '\n':
111
+ return;
112
+ case '/':
113
+ b.end = index+1;
114
+ if(container) container.children.push(b);
115
+ return b;
116
+ case '\\':
117
+ index++;
118
+ default:
119
+ index++;
120
+ }
121
+ }
122
+ }
123
+
124
+ function parseComment(code, index, container)
125
+ {
126
+ const b = {
127
+ type: "comment",
128
+ begin: index++
129
+ };
130
+
131
+ if(code[index]!=='*' && code[index]!=='/') {
132
+ return parseRegex(code, index-1, container);
133
+ }
134
+
135
+ const is_multiline = code[index]==='*';
136
+
137
+ index++;
138
+
139
+ while(index<code.length) {
140
+
141
+ const ch = code[index];
142
+ switch(ch) {
143
+
144
+ case '*':
145
+ if(is_multiline && code[index+1]==='/') {
146
+ b.end=index+2;
147
+ if(container) container.children.push(b);
148
+ return b;
149
+ }
150
+ break;
151
+
152
+ case '\n':
153
+ if(!is_multiline) {
154
+ b.end = index+1;
155
+ if(container) container.children.push(b);
156
+ return b;
157
+ }
158
+ break;
159
+
160
+ }
161
+
162
+ index++;
163
+ }
164
+
165
+ raiseError(`unterminated comment`, index);
166
+ }
167
+
168
+
169
+ function parseDirective(code, index, container)
170
+ {
171
+ let i=index+1;
172
+ while( code[i] &&
173
+ (code[i]!=='\n' && code[i]!==transpilerConfig.preprocessorDelimiter) ||
174
+ code[i-1]==='\\')
175
+ {
176
+ i++;
177
+ }
178
+
179
+ const is_exp = code[i]===transpilerConfig.preprocessorDelimiter;
180
+
181
+ if(is_exp) {
182
+ const b = {
183
+ type: "directive",
184
+ begin: index,
185
+ cbegin: index,
186
+ end: i+1,
187
+
188
+ expression: code.slice(index+1,i).replace(/\\(.)/gs, '$1'),
189
+ value: ''
190
+ };
191
+
192
+ if(container) container.children.push(b);
193
+ return b;
194
+
195
+ }
196
+ else {
197
+ let j=index;
198
+
199
+ while(j-->=0 && (code[j]===' ' || code[j]==='\t'));
200
+ const is_first = j===-1 || code[j]==='\n';
201
+
202
+ if(is_first) {
203
+ const b = {
204
+ type: "directive",
205
+ begin: index,
206
+ cbegin: index,
207
+ end: i+1,
208
+
209
+ pragma: code.slice(index+1,i+1).replace(/\\(.)/gs, '$1'),
210
+ value: ''
211
+ };
212
+ if(container) container.children.push(b);
213
+ return b;
214
+ }
215
+ }
216
+
217
+ raiseError('error in preprocessor statement.')
218
+ }
219
+
220
+
221
+ // note: pragma blocks should begin and end in same block main trunk (not its children).
222
+ // note: preprocessor code must be JS, not JSX.
223
+
224
+ function preprocessPragmas(node, context)
225
+ {
226
+ const all_nodes = [];
227
+
228
+ var segments = ["const {" + Object.keys(jsxAddons).join(', ') + "} = transpilerConfig.addons;"];
229
+
230
+ var scope_stack = [];
231
+ var last_block = null;
232
+
233
+ const clone_block = (i, val)=>{
234
+ last_block = typeof(all_nodes[i])==='object'?
235
+ structuredClone(
236
+ { ...all_nodes[i], children: undefined, attributes: {} }
237
+ )
238
+ :all_nodes[i];
239
+
240
+ if(last_block.type==='directive') {
241
+ last_block.value = val;
242
+ }
243
+
244
+ scope_stack[0].out ??= [];
245
+ scope_stack[0].out.push(last_block);
246
+
247
+ return last_block;
248
+ };
249
+
250
+ const clone_attr = (i, name)=>{
251
+ last_block = typeof(all_nodes[i])==='object'?
252
+ structuredClone(
253
+ { ...all_nodes[i], children: undefined, attributes: {} }
254
+ )
255
+ :all_nodes[i];
256
+
257
+ scope_stack[0].attributes[name] = last_block;
258
+
259
+ return last_block;
260
+ };
261
+
262
+ const push_scope = ()=>{ scope_stack = [last_block, scope_stack]; };
263
+ const pop_scope = ()=>{ scope_stack = scope_stack[1]; };
264
+
265
+ const vine_traverse = (nd)=> {
266
+
267
+ if(typeof(nd)!=='object') return segments;
268
+
269
+ segments.push(`push_scope();`);
270
+
271
+ if(nd.attributes) {
272
+ for(const attr in nd.attributes) {
273
+ const ch = nd.attributes[attr];
274
+
275
+ all_nodes.push(ch);
276
+
277
+ segments.push(`clone_attr(${all_nodes.length-1}, "${attr}");`);
278
+ vine_traverse(ch);
279
+
280
+ }
281
+ }
282
+ if(nd.children) {
283
+ for(const ch of nd.children) {
284
+ all_nodes.push(ch);
285
+
286
+ if(ch.type==='directive' && ch.expression===undefined) {
287
+ segments.push(ch.pragma);
288
+ }
289
+ else {
290
+ if(ch.expression)
291
+ segments.push(`clone_block(${all_nodes.length-1}, ${ch.expression});`);
292
+ else
293
+ segments.push(`clone_block(${all_nodes.length-1});`);
294
+ vine_traverse(ch);
295
+ }
296
+ }
297
+ }
298
+
299
+ segments.push(`pop_scope();`);
300
+
301
+ return segments;
302
+ }
303
+
304
+
305
+ node.out = [];
306
+ last_block = node;
307
+ push_scope();
308
+ vine_traverse(node);
309
+
310
+ const cmd = segments.join('\n');
311
+ eval(cmd);
312
+
313
+ return segments;
314
+ }
315
+
316
+
317
+ function parseString(code, index, q, container)
318
+ {
319
+ const b = {
320
+ type: "string",
321
+ begin: index++,
322
+ cbegin: index
323
+ };
324
+
325
+ while(index<code.length) {
326
+
327
+ const ch = code[index];
328
+
329
+ switch(ch) {
330
+
331
+ case q:
332
+ b.cend=index;
333
+ b.end=index+1;
334
+ if(container) container.children.push(b);
335
+ return b;
336
+
337
+ case '\n':
338
+ if(q!=='\`') raiseError(`unterminated string`, index);
339
+ break;
340
+
341
+ case '$':
342
+ if(q==='`') {
343
+ index++;
344
+ if(code[index]==='{') {
345
+ [index] = lookAhead( parseJS, code, index, true, container );
346
+ index--;
347
+ }
348
+ }
349
+ break;
350
+
351
+ case '\\':
352
+ index++;
353
+ break;
354
+ }
355
+
356
+ index++;
357
+ }
358
+
359
+ raiseError(`unterminated string`, index);
360
+ }
361
+
362
+
363
+ function parseXMLContent(code, index, container, eols)
364
+ {
365
+ let i = index;
366
+ let cur = index;
367
+
368
+ while(i<code.length) {
369
+ switch(code[i]) {
370
+
371
+ case '<':
372
+ if(code[i+1]==='/') {
373
+
374
+ i+=2;
375
+
376
+ let j=i;
377
+
378
+ while(j<code.length && code[j]!=='>') {
379
+ j++;
380
+ }
381
+
382
+ if(j===code.length) {
383
+ return null; //throw `ill-formed xml (slash) ${j}`;
384
+ }
385
+
386
+ const tag = code.substring(i,j).trim();
387
+
388
+ if(container.tag!==tag) {
389
+ throw `ill-formed xml (close tag) [${container.tag},${tag}] ${j}`;
390
+ }
391
+
392
+ container.end = j+1;
393
+
394
+ container.cbegin = index+1; // content begin index
395
+ container.cend = i-2; // content end index
396
+
397
+ return j;
398
+ }
399
+ else {
400
+ [i] = lookAhead( parseXML, code, i, container );
401
+
402
+ if(i>cur) {
403
+ cur = i;
404
+ i--;
405
+ }
406
+ else cur++;
407
+
408
+ }
409
+ break;
410
+
411
+ case '{':
412
+ const j=i;
413
+ [i] = lookAhead( parseJS, code, i, true, container );
414
+ i--;
415
+ break;
416
+
417
+ }
418
+ i++;
419
+ }
420
+
421
+ return cur;
422
+ }
423
+
424
+
425
+ function parseXML(code, index, container)
426
+ {
427
+ // delimiters
428
+ const delims = [ ' ','\t','\n', '/','&','^','%','|','!','~','+','*','?',
429
+ '<','>',';',',','=','{','}','(',')','[',']','\'','\"',
430
+ '\`', '\\', '', undefined ];
431
+
432
+ const skip_spaces =
433
+ ()=> { while( code[index]===' ' || code[index]==='\t' || code[index]==='\n' ) index++; };
434
+
435
+ const b = { // block
436
+ type: "xml",
437
+ begin: index++,
438
+ children: [],
439
+ attributes: {},
440
+ self_closing: false,
441
+ js_attributes: []
442
+ };
443
+
444
+ skip_spaces();
445
+
446
+ let i = index;
447
+ while(i<code.length && delims.indexOf(code[i])===-1) i++;
448
+ if(i===code.length) return;
449
+
450
+ b.tag = code.substring(index,i);
451
+
452
+ index = i;
453
+
454
+
455
+ let last_attr = undefined;
456
+
457
+ while(index<code.length) {
458
+ i = index;
459
+
460
+ while( i<code.length && delims.indexOf(code[i])===-1 ) {
461
+ i++;
462
+ }
463
+ if(i===code.length) return;
464
+
465
+ const tok = code.substring(index,i);
466
+ if(tok.length) {
467
+ last_attr = tok;
468
+ b.attributes[tok] = true;
469
+ }
470
+
471
+ index = i;
472
+
473
+ switch(code[index]) {
474
+
475
+ case '=':
476
+
477
+ if(last_attr) {
478
+ index++;
479
+ skip_spaces();
480
+
481
+ if(code[index]==='/') { // todo: comment after =, should be tested
482
+ [i] = lookAhead( parseComment, code, index );
483
+ if(i>index) index = i;
484
+ else index++;
485
+ }
486
+
487
+ let av; // attribute value
488
+
489
+ switch(code[index]) {
490
+ case '\'':
491
+ case '\"':
492
+ //case '\`':
493
+ [index, av] = lookAhead( parseString, code, index, code[index] );
494
+ b.attributes[last_attr] = av;
495
+ last_attr = undefined;
496
+ break;
497
+
498
+ case '{':
499
+ [index, av] = lookAhead( parseJS, code, index, true );
500
+ b.attributes[last_attr] = av;
501
+ last_attr = undefined;
502
+ break;
503
+
504
+ default:
505
+ {
506
+ i = index;
507
+ while( i<code.length && delims.indexOf(code[i])===-1 ) {
508
+ i++;
509
+ }
510
+
511
+ if(i===code.length) {
512
+ return; // not xml
513
+ }
514
+
515
+ const tok = code.substring(index,i);
516
+ index = i;
517
+
518
+ b.attributes[last_attr] = tok;
519
+ last_attr = undefined;
520
+ }
521
+ }
522
+ }
523
+ else {
524
+ //console.log(`ill-formed xml (attribute)`, index);
525
+ return; // not xml
526
+ }
527
+
528
+ break;
529
+
530
+ case '{':
531
+ let jsc;
532
+ [index, jsc] = lookAhead( parseJS, code, index, true );
533
+ jsc.is_xml_js = true;
534
+ b.js_attributes.push(jsc);
535
+ break;
536
+
537
+ case '/':
538
+ if(code[index+1]==='>') {
539
+ b.end = index+2;
540
+ b.cbegin = b.begin;
541
+ b.cend = b.end;
542
+
543
+ //b.children = undefined;
544
+ b.self_closing = true;
545
+
546
+ if(container) container.children.push(b);
547
+ return b;
548
+ }
549
+
550
+ [i] = lookAhead( parseComment, code, index );
551
+ if(i>index) index = i;
552
+ else index++;
553
+
554
+ break;
555
+
556
+ case '>':
557
+ const i = parseXMLContent(code, index, b);
558
+
559
+ // not xml
560
+ // todo: side effects must be checked
561
+ if(i===null) return;
562
+
563
+ if(i>index) index = i;
564
+ else index++;
565
+
566
+ if(b.end) {
567
+ if(container) container.children.push(b);
568
+ return b;
569
+ }
570
+ else {
571
+ index++;
572
+ }
573
+ break;
574
+
575
+ case ' ':
576
+ case '\t':
577
+ case '\n':
578
+ skip_spaces();
579
+ break;
580
+
581
+ default:
582
+ return;
583
+
584
+ }
585
+ }
586
+ }
587
+
588
+
589
+ function parseParanthesis(code, index, container)
590
+ {
591
+ const b = { // block
592
+ type: "paranthesis",
593
+ begin: index,
594
+ cbegin: ++index,
595
+ children: [],
596
+ self_closing: false
597
+ };
598
+
599
+ while(index<code.length) {
600
+ const ch = code[index];
601
+
602
+ switch(ch) {
603
+
604
+ case '<':{
605
+ let [i] = lookAhead( parseXML, code, index, b );
606
+ if(i>index) index = i;
607
+ else index++;
608
+ break;
609
+ }
610
+ case '\"':
611
+ case '\'':
612
+ case '\`':
613
+ [index] = lookAhead( parseString, code, index, ch );
614
+ break;
615
+
616
+ case '{':
617
+ [index] = lookAhead( parseJS, code, index, true, b );
618
+ break;
619
+
620
+ case '(':
621
+ [index] = lookAhead( parseParanthesis, code, index, b );
622
+ break;
623
+
624
+ case '}':
625
+ raiseError(`unmatched curly bracket`, index);
626
+ break;
627
+
628
+ case ')':
629
+ b.end = index+1;
630
+ b.cend = index;
631
+ if(container) container.children.push(b);
632
+ return b;
633
+
634
+ case '/':
635
+ const [i] = lookAhead( parseComment, code, index, b );
636
+ if(i>index) index = i;
637
+ else index++;
638
+ break;
639
+
640
+ case '\\':
641
+ index++;
642
+ default:
643
+ index++;
644
+ break;
645
+ }
646
+ }
647
+
648
+ raiseError(`unterminated paranthesis block (started at ${b.begin})`, index);
649
+ }
650
+
651
+
652
+ function parseJS(code, index=0, is_block=false, container)
653
+ {
654
+ const b = { // block
655
+ type: "js",
656
+ begin: index,
657
+ cbegin: index+=(is_block?1:0),
658
+ children: []
659
+ };
660
+
661
+ while(index<code.length) {
662
+ const ch = code[index];
663
+
664
+ switch(ch) {
665
+
666
+ case '<': {
667
+ let [i] = lookAhead( parseXML, code, index, b );
668
+
669
+ if(i>index) index = i;
670
+ else index++;
671
+ break;
672
+ }
673
+
674
+ case '\"':
675
+ case '\'':
676
+ case '\`':
677
+ [index] = lookAhead( parseString, code, index, ch );
678
+ break;
679
+
680
+ case '{': {
681
+ const i = index;
682
+ [index] = lookAhead( parseJS, code, index, true, b );
683
+ break;
684
+ }
685
+
686
+ case '(': // this is only for function detection
687
+ [index] = lookAhead( parseParanthesis, code, index, b );
688
+ break;
689
+
690
+ case ')': // this is only for function detection
691
+ raiseError(`unmatched paranthesis ${index} [from ${b.begin}]`, index);
692
+ break;
693
+
694
+ case '}':
695
+ if(is_block) {
696
+ b.end = index+1;
697
+ b.cend = index;
698
+ if(container) container.children.push(b);
699
+ return b;
700
+ }
701
+ raiseError(`unmatched curly bracket`, index);
702
+ break;
703
+
704
+ case '/':
705
+ {
706
+ let [i] = lookAhead( parseComment, code, index, b );
707
+ if(i>index) index = i;
708
+ else index++;
709
+ break;
710
+ }
711
+
712
+ case transpilerConfig.preprocessorDelimiter:
713
+ {
714
+ let [i] = lookAhead( parseDirective, code, index, b );
715
+ if(i>index) index = i;
716
+ else index++;
717
+ break;
718
+ }
719
+
720
+ case '\\':
721
+ index++;
722
+
723
+ default:
724
+ index++;
725
+ }
726
+ }
727
+
728
+ if(is_block) raiseError(`unterminated js block at ${index} (started at ${b.begin})`);
729
+
730
+ b.end = index;
731
+ b.cend = index;
732
+ if(container) container.children.push(b);
733
+ return b;
734
+ }
735
+
736
+
737
+ // this is for lilact internal use, it is a workaround for approximating error location
738
+ // in eval when transpiling and running jsx directly in browser.
739
+
740
+ function labelFunctions(node, eols)
741
+ {
742
+
743
+ node.already_labeled = true;
744
+
745
+ function getNext(i, step=1)
746
+ {
747
+ while( (i+=step) < node.children.length && i>=0) {
748
+ const ch = node.children[i];
749
+
750
+ if(typeof(ch)==='string') {
751
+ const t = ch.trim();
752
+ if(t.length) return [i, t];
753
+ }
754
+ if(typeof(ch)==='object' && ch.type!=='comment') {
755
+ return [i,ch];
756
+ }
757
+ }
758
+ return [null,null];
759
+ }
760
+
761
+ let i, nxt, prev;
762
+ for(let chi=0; chi<node.children.length; chi++) {
763
+
764
+ if(typeof(node.children[chi])==='object' && node.children[chi].type==='paranthesis') {
765
+
766
+ [i, prev] = getNext(chi, -1);
767
+ if(prev===null) continue;
768
+
769
+ if(typeof(prev)!=='string' || ['switch', 'catch', 'try', 'class'].find((x)=>prev.trim().endsWith(x))!==undefined) continue;
770
+ let label = prev;
771
+
772
+ [i, prev] = getNext(i, -1);
773
+ if(prev===null) continue;
774
+ if(typeof(prev)==='string' && ['extern', 'class'].indexOf(prev)!==-1) continue;
775
+
776
+
777
+ [i, nxt] = getNext(chi);
778
+
779
+ if(nxt===null) continue;
780
+
781
+ if(transpilerConfig.injectTraceLabels && typeof(nxt)==='object' && nxt.type==='js') {
782
+ const begin = getRowCol(eols, chi); // begin is always the location of function args paranthesis
783
+ nxt.children.splice(1,0, `/*LILACTBLOCK${++transpilerConfig.func_num}:${begin}:${label}*/try{`);
784
+ if(transpilerConfig.enableLabelStack) {
785
+ nxt.children.splice(nxt.children.length-1, 0, `} catch(e){ if(typeof(e)!=='object') e=new Error(e);e.lilact_trace=[${transpilerConfig.func_num},e.lilact_trace];throw e}`);
786
+ }
787
+ else {
788
+ nxt.children.splice(nxt.children.length-1, 0, `} catch(e){ if(typeof(e)!=='object') e=new Error(e);e.lilact_trace=${transpilerConfig.func_num};throw e}`);
789
+ }
790
+ chi += 2;
791
+ }
792
+ }
793
+
794
+ else if(node.children[chi]==='=>') {
795
+ [i, prev] = getNext(chi, -1);
796
+
797
+ if(typeof(prev)==='object' && prev.type==='paranthesis') {
798
+
799
+ const begin = getRowCol(eols, prev.begin); // begin is always the location of function args paranthesis
800
+
801
+ [i, nxt] = getNext(chi);
802
+
803
+ if(typeof(nxt)==='object' && nxt.type==='js') {
804
+
805
+ if(transpilerConfig.injectTraceLabels) {
806
+
807
+ nxt.children.splice(1,0,
808
+ `/*LILACTBLOCK${++transpilerConfig.func_num}:${begin}:<ARROW>*/try {`);
809
+
810
+ if(transpilerConfig.enableLabelStack) {
811
+ nxt.children.splice(nxt.children.length-1, 0,
812
+ `} catch(e){if(typeof(e)!=='object') e=new Error(e);e.lilact_trace=[${transpilerConfig.func_num},e.lilact_trace];throw e}`);
813
+ }
814
+ else {
815
+ nxt.children.splice(nxt.children.length-1, 0,
816
+ `} catch(e){if(typeof(e)!=='object') e=new Error(e);e.lilact_trace=${transpilerConfig.func_num};throw e}`);
817
+ }
818
+
819
+ }
820
+ chi += 2;
821
+ }
822
+ }
823
+ }
824
+
825
+ }
826
+
827
+ }
828
+
829
+ function scanEOLs(code)
830
+ {
831
+ const endlines = [0];
832
+
833
+ for(let i in code) {
834
+ if(code[i]==='\n') {
835
+ endlines.push(parseInt(i));
836
+ }
837
+ }
838
+ endlines.push(code.length);
839
+
840
+ return endlines;
841
+ }
842
+
843
+ function getRowCol(eols, i)
844
+ {
845
+ if(!eols.last) eols.last=1;
846
+
847
+ while(i>eols[eols.last]) {
848
+ eols.last++;
849
+ if(eols.last===eols.length) {
850
+ eols.last=eols.length-1;
851
+ break;
852
+ }
853
+ }
854
+ while(i<eols[eols.last-1] && eols.last>1) {
855
+ eols.last--;
856
+ }
857
+
858
+ return [eols.last-1, i-eols[eols.last-1]];
859
+ }
860
+
861
+
862
+ function generateSourceMap(json, path, jsx_eols, out_eols, mappings=[])
863
+ {
864
+ let mpps = [];
865
+
866
+ const sourcemap = {
867
+ "version":3,
868
+ "file":path,
869
+ "sourceRoot":"",
870
+ "sources":[path],
871
+ "names":[],
872
+ "mappings":""
873
+ };
874
+
875
+
876
+ const scan_leaves = (node)=>{
877
+ if(!!node.children) {
878
+
879
+ for(const ch of node.children) {
880
+ scan_leaves(ch);
881
+ }
882
+ }
883
+ if(node.begin!==undefined && node.out_index!==undefined) {
884
+ mpps.push( [ ...getRowCol(out_eols,node.out_index), ...getRowCol(jsx_eols,node.begin), node ] );
885
+ }
886
+ }
887
+
888
+ scan_leaves(json);
889
+
890
+ mpps = mpps.sort( (a,b)=>{
891
+ if (a[0] == b[0]) {
892
+ return a[1] - b[1];
893
+ }
894
+ return a[0] - b[0];
895
+ }
896
+ );
897
+
898
+ mappings.push(...mpps);
899
+
900
+ let mstr = "";
901
+ let r=0, oc=0;
902
+
903
+ let lr = 0, lc=0;
904
+
905
+ for(let i=0; i<mpps.length;i++) {
906
+ const m = mpps[i];
907
+
908
+ while(r<m[0]) {
909
+ oc=0;
910
+ mstr+=(";");
911
+ r++;
912
+ }
913
+
914
+ mstr+=',';
915
+
916
+ mstr+=( vlq.encode(m[1]-oc,0,m[2]-lr,m[3]-lc) );
917
+
918
+ oc = m[1];
919
+ lr = m[2];
920
+ lc = m[3];
921
+ }
922
+
923
+ sourcemap.mappings = mstr.substring(1).replace(/;,/g, ";");
924
+
925
+ return "\n\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(JSON.stringify(sourcemap));
926
+
927
+ }
928
+
929
+
930
+ /**
931
+ * This is the function that transpiles jsx code to js. It is not recommended to use directly.
932
+ *
933
+ * Use `Lilact.require` instead, or `Lilact.run` available in `run`, or add a `<scirpt type="text/jsx" .../>` to your html.
934
+ *
935
+ * Lilact jsx parser doesn't rely on the Lilact itself, and can be used by other engines. The easiest way
936
+ * is using the `bin/transpile.js` script. It has its own help.
937
+ *
938
+ * @returns The transpiled javascript code.
939
+ */
940
+
941
+ export function transpileJSX( jsx, {
942
+ factory = "Lilact.createComponent",
943
+ path = "anonymous",
944
+ appendSourcemap = true,
945
+ mappings = [],
946
+ injectTraceLabels = false,
947
+ discardComments = false
948
+ } = {} )
949
+ {
950
+
951
+ // func_num is shared so consecutive transpilations create new ids each time.
952
+ transpilerConfig.func_num ??= 0;
953
+ transpilerConfig.preprocessorDelimiter ??= 'ʔ'; // alt+shift+. on apple abc extended layout
954
+
955
+ transpilerConfig.injectTraceLabels ??= injectTraceLabels; // alt+shift+. on apple abc extended layout
956
+
957
+ const eols = scanEOLs(jsx);
958
+
959
+ raiseError = ((eols, msg, index)=>{
960
+ const er = new Error(msg);
961
+ er.name = 'JSXParseError';
962
+ [er.lineNumber, er.columnNumber] = getRowCol(eols, index);
963
+ er.lilact_trace = 'parse';
964
+ throw er;
965
+ }).bind(null, eols);
966
+
967
+ const tokenize_re = /([\{\}\(\),;\[\]\n]|[\s^\n]+)/g;
968
+
969
+ const json = parseJS(jsx);
970
+ json.data = jsx;
971
+
972
+ const prepare = (node)=>{
973
+
974
+ if(node.attributes!==undefined) {
975
+ for(const attr in node.attributes) {
976
+ if(typeof(node.attributes[attr])==='object') {
977
+ prepare(node.attributes[attr]);
978
+ }
979
+ }
980
+ }
981
+
982
+ if(node.children!==undefined && !node.self_closing) {
983
+
984
+ let i = node.type==='js'? node.begin : (node.cbegin || node.begin);
985
+ for(let chi=0; chi<node.children.length; chi++) {
986
+ const ch = node.children[chi];
987
+
988
+ if(ch.begin>i) {
989
+ let s = jsx.substring(i, ch.begin);
990
+
991
+ if(node.type==='xml') s = s.trim();
992
+ if(s.length) {
993
+ if(node.type==='xml') {
994
+ node.children.splice(chi, 0, '"'+s.replaceAll("\n", "\\\n")+'"');
995
+ }
996
+ else {
997
+ node.children.splice(chi, 0, ...s.split(tokenize_re));
998
+ }
999
+ chi++;
1000
+ }
1001
+ }
1002
+ i = ch.end;
1003
+
1004
+ prepare(ch);
1005
+ }
1006
+
1007
+ const e = node.type==='js'? node.end : (node.cend || node.end);
1008
+
1009
+ if(i<e && !node.self_closing) {
1010
+ let s = jsx.substring(i, e);
1011
+ if(node.type==='xml') s = s.trim();
1012
+ if(s.length) {
1013
+ if(node.type==='xml') {
1014
+ node.children.push( '"'+s.replaceAll("\n", "\\\n")+'"' );
1015
+ }
1016
+ else {
1017
+ node.children.push( ...s.split(tokenize_re) );
1018
+ }
1019
+ }
1020
+ }
1021
+
1022
+ node.children = node.children.filter( (x)=>x!=="" );
1023
+
1024
+ if(transpilerConfig.setFunctionLabels && !node.already_labeled) {
1025
+ labelFunctions(node,eols);
1026
+ }
1027
+ }
1028
+ }
1029
+
1030
+ prepare(json);
1031
+
1032
+ preprocessPragmas(json);
1033
+
1034
+ const codify = (outlen, node, is_attr=false, is_xml=false)=> {
1035
+
1036
+ if(typeof(node)!=='object') return node;
1037
+
1038
+ node.out_index = outlen;
1039
+
1040
+ if(node.type==='string') return jsx.substring(node.begin, node.end);
1041
+ if(node.type==='regex') return jsx.substring(node.begin, node.end);
1042
+ if(node.type==='comment') return (discardComments || is_attr)?'':jsx.substring(node.begin, node.end);
1043
+ if(node.type==='directive') return node.value;
1044
+
1045
+
1046
+ if(node.type==='paranthesis') {
1047
+ let out = "(";
1048
+
1049
+ if(node.out) {
1050
+ for(const ch of node.out) {
1051
+ out+=codify(outlen+out.length-1, ch);
1052
+ }
1053
+ }
1054
+ return out + ")";
1055
+ }
1056
+
1057
+ if(node.type==='js') {
1058
+ let out = "";
1059
+
1060
+ if(node.out) {
1061
+ for(const ch of node.out) {
1062
+ if(is_xml && ch.type==='comment') continue;
1063
+ out+=codify( outlen + out.length - (is_attr?1:0), ch);
1064
+ }
1065
+ }
1066
+
1067
+ if(is_attr) {
1068
+ return out.substring(1, out.length-1);
1069
+ }
1070
+
1071
+ // todo: this is very simple, it should parse recursively
1072
+ if(node?.is_xml_js) {
1073
+ return jsx.substring(node.begin+1, node.end-1);
1074
+ }
1075
+
1076
+ return out;
1077
+ }
1078
+
1079
+ if(node.type==='xml') {
1080
+
1081
+ if(node.tag.length===0) {
1082
+ node.tag = "null";
1083
+ }
1084
+ else if(node.tag[0]!==node.tag[0].toUpperCase()) {
1085
+ node.tag = `"${node.tag}"`;
1086
+ }
1087
+
1088
+ let out = '';
1089
+
1090
+
1091
+ let is_first=true;
1092
+
1093
+ for(const a of node.js_attributes) {
1094
+ let oo = codify(outlen + out.length, a, false);
1095
+
1096
+ out+=`${is_first?'':', '}${oo}`
1097
+ is_first=false;
1098
+ }
1099
+
1100
+
1101
+ if(node.attributes) {
1102
+ for(const a in node.attributes) {
1103
+ out+=`${is_first?'':', '}"${a}": ${codify(outlen + out.length, node.attributes[a], true)}`
1104
+ is_first=false;
1105
+ }
1106
+ }
1107
+
1108
+ out+=" }";
1109
+
1110
+ if(node.out) {
1111
+ for(const ch of node.out) {
1112
+ const o = codify(outlen + out.length, ch, true, true);
1113
+ if(o.length>0) {
1114
+ out+=`, ${o}`
1115
+ is_first=false;
1116
+ }
1117
+ }
1118
+ }
1119
+
1120
+ const loc = getRowCol(eols,node.begin);
1121
+ out = `${factory}( ${node.tag}, { ${out} )`; //lilact_jsx_loc:["${path}", ${loc}]
1122
+
1123
+ return out;
1124
+ }
1125
+ }
1126
+
1127
+ let out = '';
1128
+ if(injectTraceLabels) {
1129
+ out=`/*LILACTBLOCK${++transpilerConfig.func_num}:0,0:<EXEC>*/try{`
1130
+
1131
+ transpilerConfig.func_labels[transpilerConfig.func_num] = {
1132
+ path,
1133
+ row: 1,
1134
+ col: 1,
1135
+ label: transpilerConfig.func_num,
1136
+ required: transpilerConfig.required[path]
1137
+ };
1138
+
1139
+ }
1140
+ out+=codify(0, json);
1141
+
1142
+ if(injectTraceLabels) {
1143
+ if(transpilerConfig.enableLabelStack) {
1144
+ out += `}catch(e){ if(typeof(e)!=='object') e=new Error(e);e.lilact_trace=[${transpilerConfig.func_num},e.lilact_trace];throw e}`;
1145
+ }
1146
+ else {
1147
+ out += `}catch(e){ if(typeof(e)!=='object') e=new Error(e);e.lilact_trace=${transpilerConfig.func_num};throw e}`;
1148
+ }
1149
+ }
1150
+
1151
+ const inline_sm = generateSourceMap(json, path, eols, scanEOLs(out), mappings);
1152
+ if(appendSourcemap) {
1153
+ out += inline_sm;
1154
+ }
1155
+
1156
+ if(globalThis[TRANSPILER_OUTPUT]) {
1157
+ try {
1158
+ const fs = import('fs');
1159
+ fs.writeFileSync(globalThis[TRANSPILER_OUTPUT], out);
1160
+ }
1161
+ catch(e) {}
1162
+ globalThis[TRANSPILER_OUTPUT] = undefined;
1163
+ }
1164
+ //console.log(out);
1165
+
1166
+ return out;
1167
+ }
1168
+