@canvasengine/compiler 2.0.0-beta.47 → 2.0.0-beta.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,850 @@
1
+ {
2
+ function generateError(message, location) {
3
+ const { start, end } = location;
4
+ const errorMessage = `${message}\n` +
5
+ `at line ${start.line}, column ${start.column} to line ${end.line}, column ${end.column}`;
6
+ throw new Error(errorMessage);
7
+ }
8
+
9
+ /*——— Custom error handler for syntax errors ———*/
10
+ function parseError(error) {
11
+ // error.expected : array of { type, description }
12
+ // error.found : string | null
13
+ // error.location : { start, end }
14
+ const { expected, found, location } = error;
15
+
16
+ // Group expected items by description to avoid duplicates
17
+ const uniqueExpected = [...new Set(expected.map(e => e.description))];
18
+
19
+ // Format the expected values in a more readable way
20
+ const expectedDesc = uniqueExpected
21
+ .map(desc => `'${desc}'`)
22
+ .join(' or ');
23
+
24
+ const foundDesc = found === null ? 'end of input' : `'${found}'`;
25
+
26
+ generateError(
27
+ `Syntax error: expected ${expectedDesc} but found ${foundDesc}`,
28
+ location
29
+ );
30
+ }
31
+
32
+ // List of standard HTML DOM elements
33
+ const domElements = new Set([
34
+ 'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 'samp', 's', 'script', 'section', 'select', 'slot', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr'
35
+ ]);
36
+
37
+ // Framework components that should NOT be transformed to DOM elements
38
+ const frameworkComponents = new Set([
39
+ 'Canvas', 'Container', 'Sprite', 'Text', 'DOMElement', 'Svg', 'Button'
40
+ ]);
41
+
42
+ const voidElements = new Set([
43
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta',
44
+ 'param', 'source', 'track', 'wbr'
45
+ ]);
46
+
47
+ // DisplayObject special attributes that should not be in attrs
48
+ const displayObjectAttributes = new Set([
49
+ 'x', 'y', 'scale', 'anchor', 'skew', 'tint', 'rotation', 'angle',
50
+ 'zIndex', 'roundPixels', 'cursor', 'visible', 'alpha', 'pivot', 'filters', 'maskOf',
51
+ 'blendMode', 'filterArea', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight',
52
+ 'aspectRatio', 'flexGrow', 'flexShrink', 'flexBasis', 'rowGap', 'columnGap',
53
+ 'positionType', 'top', 'right', 'bottom', 'left', 'objectFit', 'objectPosition',
54
+ 'transformOrigin', 'flexDirection', 'justifyContent', 'alignItems', 'alignContent',
55
+ 'alignSelf', 'margin', 'padding', 'border', 'gap', 'blur', 'shadow'
56
+ ]);
57
+
58
+ function isDOMElement(tagName) {
59
+ // Don't transform framework components to DOM elements
60
+ if (frameworkComponents.has(tagName)) {
61
+ return false;
62
+ }
63
+ return domElements.has(tagName.toLowerCase());
64
+ }
65
+
66
+ function isVoidElement(tagName) {
67
+ return voidElements.has(tagName.toLowerCase());
68
+ }
69
+
70
+ function formatAttributes(attributes) {
71
+ if (attributes.length === 0) {
72
+ return null;
73
+ }
74
+
75
+ // Check if there's exactly one attribute and it's a spread attribute
76
+ if (attributes.length === 1 && attributes[0].startsWith('...')) {
77
+ // Return the identifier directly, removing the '...'
78
+ return attributes[0].substring(3);
79
+ }
80
+
81
+ // Otherwise, format as an object literal
82
+ const formattedAttrs = attributes.map(attr => {
83
+ // If it's a spread attribute, keep it as is
84
+ if (attr.startsWith('...')) {
85
+ return attr;
86
+ }
87
+ // If it's a standalone attribute (doesn't contain ':'), format as shorthand property 'name'
88
+ if (!attr.includes(':')) {
89
+ return attr; // JS object literal shorthand
90
+ }
91
+ // Otherwise (key: value), keep it as is
92
+ return attr;
93
+ });
94
+
95
+ return `{ ${formattedAttrs.join(', ')} }`;
96
+ }
97
+
98
+ function formatDOMElement(tagName, attributes) {
99
+ if (attributes.length === 0) {
100
+ return `h(DOMElement, { element: "${tagName}" })`;
101
+ }
102
+
103
+ const { domAttrs, displayObjectAttrs } = splitAttributes(attributes);
104
+
105
+ // Build the result
106
+ const parts = [`element: "${tagName}"`];
107
+
108
+ if (domAttrs.length > 0) {
109
+ parts.push(`attrs: { ${domAttrs.join(', ')} }`);
110
+ }
111
+
112
+ if (displayObjectAttrs.length > 0) {
113
+ parts.push(...displayObjectAttrs);
114
+ }
115
+
116
+ return `h(DOMElement, { ${parts.join(', ')} })`;
117
+ }
118
+
119
+ function splitAttributes(attributes) {
120
+ const domAttrs = [];
121
+ const displayObjectAttrs = [];
122
+ const classValues = [];
123
+ let classInsertIndex = null;
124
+
125
+ attributes.forEach(attr => {
126
+ // Handle spread attributes
127
+ if (attr.startsWith('...')) {
128
+ displayObjectAttrs.push(attr);
129
+ return;
130
+ }
131
+
132
+ // Extract attribute name and value (if present)
133
+ let attrName;
134
+ let attrValue;
135
+ if (attr.includes(':')) {
136
+ const colonIndex = attr.indexOf(':');
137
+ attrName = attr.slice(0, colonIndex).trim().replace(/['"]/g, '');
138
+ attrValue = attr.slice(colonIndex + 1).trim();
139
+ } else {
140
+ // Standalone attribute
141
+ attrName = attr.replace(/['"]/g, '');
142
+ }
143
+
144
+ // Check if it's a DisplayObject attribute
145
+ if (displayObjectAttributes.has(attrName)) {
146
+ displayObjectAttrs.push(attr);
147
+ return;
148
+ }
149
+
150
+ if (attrName === 'class' && attrValue !== undefined) {
151
+ classValues.push(attrValue);
152
+ if (classInsertIndex === null) {
153
+ classInsertIndex = domAttrs.length;
154
+ }
155
+ return;
156
+ }
157
+
158
+ domAttrs.push(attr);
159
+ });
160
+
161
+ if (classValues.length > 0) {
162
+ const mergedClass = classValues.length === 1
163
+ ? `class: ${classValues[0]}`
164
+ : `class: [${classValues.join(', ')}]`;
165
+ if (classInsertIndex === null) {
166
+ domAttrs.push(mergedClass);
167
+ } else {
168
+ domAttrs.splice(classInsertIndex, 0, mergedClass);
169
+ }
170
+ }
171
+
172
+ return { domAttrs, displayObjectAttrs };
173
+ }
174
+
175
+ function hasFunctionCall(value) {
176
+ return /[a-zA-Z_][a-zA-Z0-9_]*\s*\(/.test(value);
177
+ }
178
+
179
+ function hasIdentifier(value) {
180
+ return /[a-zA-Z_]/.test(value);
181
+ }
182
+
183
+ function isSimpleAccessor(value) {
184
+ return /^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$/.test(value.trim());
185
+ }
186
+
187
+ function transformBareIdentifiersToSignals(value) {
188
+ return value.replace(/\b([a-zA-Z_][a-zA-Z0-9_]*)\b/g, (match, name, offset) => {
189
+ if (['true', 'false', 'null'].includes(name)) {
190
+ return match;
191
+ }
192
+
193
+ const beforeMatch = value.substring(0, offset);
194
+ const singleQuotesBefore = (beforeMatch.match(/'/g) || []).length;
195
+ const doubleQuotesBefore = (beforeMatch.match(/"/g) || []).length;
196
+ if (singleQuotesBefore % 2 === 1 || doubleQuotesBefore % 2 === 1) {
197
+ return match;
198
+ }
199
+
200
+ const charBefore = offset > 0 ? value[offset - 1] : '';
201
+ const charAfter = offset + match.length < value.length ? value[offset + match.length] : '';
202
+
203
+ if (charBefore === '.' || charAfter === '.') {
204
+ return match;
205
+ }
206
+
207
+ const afterSlice = value.slice(offset + match.length);
208
+ if (/^\s*\(/.test(afterSlice)) {
209
+ return match;
210
+ }
211
+
212
+ return `${name}()`;
213
+ });
214
+ }
215
+ }
216
+
217
+ start
218
+ = _ elements:(element)* _ {
219
+ if (elements.length === 1) {
220
+ return elements[0];
221
+ }
222
+ return `[${elements.join(',')}]`;
223
+ }
224
+
225
+ element "component or control structure"
226
+ = forLoop
227
+ / ifCondition
228
+ / svgElement
229
+ / domElementWithText
230
+ / domElementWithMixedContent
231
+ / selfClosingElement
232
+ / voidElement
233
+ / openCloseElement
234
+ / openUnclosedTag
235
+ / comment
236
+
237
+ selfClosingElement "self-closing component tag"
238
+ = _ "<" _ tagName:tagName _ attributes:attributes _ "/>" _ {
239
+ // Check if it's a DOM element
240
+ if (isDOMElement(tagName)) {
241
+ return formatDOMElement(tagName, attributes);
242
+ }
243
+ // Otherwise, treat as regular component
244
+ const attrsString = formatAttributes(attributes);
245
+ return attrsString ? `h(${tagName}, ${attrsString})` : `h(${tagName})`;
246
+ }
247
+
248
+ voidElement "void DOM element tag"
249
+ = _ "<" _ tagName:tagName &{ return isVoidElement(tagName); } _ attributes:attributes _ ">" _ {
250
+ return formatDOMElement(tagName, attributes);
251
+ }
252
+
253
+ domElementWithText "DOM element with text content"
254
+ = "<" _ tagName:tagName &{ return !isVoidElement(tagName); } _ attributes:attributes _ ">" _ text:simpleTextContent _ "</" _ closingTagName:tagName _ ">" _ {
255
+ if (tagName !== closingTagName) {
256
+ generateError(
257
+ `Mismatched tag: opened <${tagName}> but closed </${closingTagName}>`,
258
+ location()
259
+ );
260
+ }
261
+
262
+ if (isDOMElement(tagName)) {
263
+ if (attributes.length === 0) {
264
+ return `h(DOMElement, { element: "${tagName}", textContent: ${text} })`;
265
+ }
266
+
267
+ const { domAttrs, displayObjectAttrs } = splitAttributes(attributes);
268
+
269
+ // Build the result
270
+ const parts = [`element: "${tagName}"`];
271
+
272
+ if (domAttrs.length > 0) {
273
+ parts.push(`attrs: { ${domAttrs.join(', ')} }`);
274
+ }
275
+
276
+ parts.push(`textContent: ${text}`);
277
+
278
+ if (displayObjectAttrs.length > 0) {
279
+ parts.push(...displayObjectAttrs);
280
+ }
281
+
282
+ return `h(DOMElement, { ${parts.join(', ')} })`;
283
+ }
284
+
285
+ // If not a DOM element, fall back to regular parsing
286
+ return null;
287
+ }
288
+
289
+ domElementWithMixedContent "DOM element with mixed content"
290
+ = "<" _ tagName:tagName &{ return isDOMElement(tagName) && !isVoidElement(tagName); } _ attributes:attributes _ ">" _ children:domContent _ "</" _ closingTagName:tagName _ ">" _ {
291
+ if (tagName !== closingTagName) {
292
+ generateError(
293
+ `Mismatched tag: opened <${tagName}> but closed </${closingTagName}>`,
294
+ location()
295
+ );
296
+ }
297
+
298
+ const childrenContent = children ? children : null;
299
+
300
+ if (attributes.length === 0) {
301
+ if (childrenContent) {
302
+ return `h(DOMElement, { element: "${tagName}" }, ${childrenContent})`;
303
+ } else {
304
+ return `h(DOMElement, { element: "${tagName}" })`;
305
+ }
306
+ }
307
+
308
+ const { domAttrs, displayObjectAttrs } = splitAttributes(attributes);
309
+
310
+ // Build the result
311
+ const parts = [`element: "${tagName}"`];
312
+
313
+ if (domAttrs.length > 0) {
314
+ parts.push(`attrs: { ${domAttrs.join(', ')} }`);
315
+ }
316
+
317
+ if (displayObjectAttrs.length > 0) {
318
+ parts.push(...displayObjectAttrs);
319
+ }
320
+
321
+ if (childrenContent) {
322
+ return `h(DOMElement, { ${parts.join(', ')} }, ${childrenContent})`;
323
+ } else {
324
+ return `h(DOMElement, { ${parts.join(', ')} })`;
325
+ }
326
+ }
327
+
328
+ simpleTextContent "simple text content"
329
+ = parts:(simpleDynamicPart / simpleTextPart)+ {
330
+ const validParts = parts.filter(p => p !== null);
331
+ if (validParts.length === 0) return null;
332
+ if (validParts.length === 1) return validParts[0];
333
+
334
+ // Multiple parts - need to concatenate
335
+ const normalizedParts = validParts.map(part => {
336
+ if (typeof part === 'string' && part.startsWith('computed(() => ') && part.endsWith(')')) {
337
+ return part.slice('computed(() => '.length, -1);
338
+ }
339
+ return part;
340
+ });
341
+ const hasSignals = normalizedParts.some(part => part && part.includes && part.includes('()'));
342
+ if (hasSignals) {
343
+ return `computed(() => ${normalizedParts.join(' + ')})`;
344
+ }
345
+ return normalizedParts.join(' + ');
346
+ }
347
+
348
+ simpleTextPart "simple text part"
349
+ = !("@for" / "@if") text:$([^<{@]+) {
350
+ const trimmed = text.trim();
351
+ if (!trimmed) return null;
352
+ const escaped = text
353
+ .replace(/\\/g, '\\\\')
354
+ .replace(/'/g, "\\'")
355
+ .replace(/\r/g, '\\r')
356
+ .replace(/\n/g, '\\n')
357
+ .replace(/\t/g, '\\t');
358
+ return `'${escaped}'`;
359
+ }
360
+
361
+ simpleDynamicPart "simple dynamic part"
362
+ = "{{" _ expr:attributeValue _ "}}" {
363
+ const trimmedExpr = expr.trim();
364
+ if (!trimmedExpr) {
365
+ return trimmedExpr;
366
+ }
367
+ if (hasFunctionCall(trimmedExpr)) {
368
+ return `computed(() => ${trimmedExpr})`;
369
+ }
370
+ return trimmedExpr;
371
+ }
372
+ / "{" _ expr:attributeValue _ "}" {
373
+ const trimmedExpr = expr.trim();
374
+ if (!trimmedExpr) {
375
+ return trimmedExpr;
376
+ }
377
+ if (hasFunctionCall(trimmedExpr)) {
378
+ return `computed(() => ${trimmedExpr})`;
379
+ }
380
+ return trimmedExpr;
381
+ }
382
+
383
+ openCloseElement "component with content"
384
+ = "<" _ tagName:tagName _ attributes:attributes _ ">" _ content:content _ "</" _ closingTagName:tagName _ ">" _ {
385
+ if (tagName !== closingTagName) {
386
+ generateError(
387
+ `Mismatched tag: opened <${tagName}> but closed </${closingTagName}>`,
388
+ location()
389
+ );
390
+ }
391
+
392
+ // Check if it's a DOM element
393
+ if (isDOMElement(tagName)) {
394
+ const children = content ? content : null;
395
+
396
+ if (attributes.length === 0) {
397
+ if (children) {
398
+ return `h(DOMElement, { element: "${tagName}" }, ${children})`;
399
+ } else {
400
+ return `h(DOMElement, { element: "${tagName}" })`;
401
+ }
402
+ }
403
+
404
+ const { domAttrs, displayObjectAttrs } = splitAttributes(attributes);
405
+
406
+ // Build the result
407
+ const parts = [`element: "${tagName}"`];
408
+
409
+ if (domAttrs.length > 0) {
410
+ parts.push(`attrs: { ${domAttrs.join(', ')} }`);
411
+ }
412
+
413
+ if (displayObjectAttrs.length > 0) {
414
+ parts.push(...displayObjectAttrs);
415
+ }
416
+
417
+ if (children) {
418
+ return `h(DOMElement, { ${parts.join(', ')} }, ${children})`;
419
+ } else {
420
+ return `h(DOMElement, { ${parts.join(', ')} })`;
421
+ }
422
+ }
423
+
424
+ // Otherwise, treat as regular component
425
+ const attrsString = formatAttributes(attributes);
426
+ const children = content ? content : null;
427
+ if (attrsString && children) {
428
+ return `h(${tagName}, ${attrsString}, ${children})`;
429
+ } else if (attrsString) {
430
+ return `h(${tagName}, ${attrsString})`;
431
+ } else if (children) {
432
+ return `h(${tagName}, null, ${children})`;
433
+ } else {
434
+ return `h(${tagName})`;
435
+ }
436
+ }
437
+
438
+ attributes "component attributes"
439
+ = attrs:(attribute (_ attribute)*)? {
440
+ return attrs
441
+ ? [attrs[0]].concat(attrs[1].map(a => a[1]))
442
+ : [];
443
+ }
444
+
445
+ attribute "attribute"
446
+ = staticAttribute
447
+ / dynamicAttribute
448
+ / eventHandler
449
+ / spreadAttribute
450
+ / unclosedQuote
451
+ / unclosedBrace
452
+
453
+ spreadAttribute "spread attribute"
454
+ = "..." expr:(functionCallExpr / dotNotation) {
455
+ return "..." + expr;
456
+ }
457
+
458
+ functionCallExpr "function call"
459
+ = name:dotNotation "(" args:functionArgs? ")" {
460
+ return `${name}(${args || ''})`;
461
+ }
462
+
463
+ dotNotation "property access"
464
+ = first:identifier rest:("." identifier)* {
465
+ return text();
466
+ }
467
+
468
+ eventHandler "event handler"
469
+ = "@" eventName:identifier _ "=" _ "{" _ handlerName:attributeValue _ "}" {
470
+ const needsQuotes = /[^a-zA-Z0-9_$]/.test(eventName);
471
+ const formattedName = needsQuotes ? `'${eventName}'` : eventName;
472
+ return `${formattedName}: ${handlerName}`;
473
+ }
474
+ / "@" eventName:attributeName _ {
475
+ const needsQuotes = /[^a-zA-Z0-9_$]/.test(eventName);
476
+ return needsQuotes ? `'${eventName}'` : eventName;
477
+ }
478
+
479
+ dynamicAttribute "dynamic attribute"
480
+ = attributeName:attributeName _ "=" _ "{" _ attributeValue:attributeValue _ "}" {
481
+ // Check if attributeName needs to be quoted (contains dash or other invalid JS identifier chars)
482
+ const needsQuotes = /[^a-zA-Z0-9_$]/.test(attributeName);
483
+ const formattedName = needsQuotes ? `'${attributeName}'` : attributeName;
484
+
485
+
486
+ // If it's a complex object with strings, preserve it as is
487
+ if (attributeValue.trim().startsWith('{') && attributeValue.trim().endsWith('}') &&
488
+ (attributeValue.includes('"') || attributeValue.includes("'"))) {
489
+ return `${formattedName}: ${attributeValue}`;
490
+ }
491
+
492
+ // If it's a template string, keep it as-is
493
+ if (attributeValue.trim().startsWith('`') && attributeValue.trim().endsWith('`')) {
494
+ return formattedName + ': ' + attributeValue;
495
+ }
496
+
497
+ // Handle other types of values
498
+ if (attributeValue.startsWith('h(') || attributeValue.includes('=>')) {
499
+ return `${formattedName}: ${attributeValue}`;
500
+ }
501
+
502
+ const trimmedValue = attributeValue.trim();
503
+ if (trimmedValue.match(/^[a-zA-Z_]\w*$/)) {
504
+ return `${formattedName}: ${attributeValue}`;
505
+ }
506
+
507
+ if (/^\d+(\.\d+)?$/.test(trimmedValue) || ['true', 'false', 'null'].includes(trimmedValue)) {
508
+ return `${formattedName}: ${attributeValue}`;
509
+ }
510
+
511
+ if (isSimpleAccessor(trimmedValue)) {
512
+ return `${formattedName}: ${attributeValue}`;
513
+ }
514
+
515
+ const isObjectLiteral = trimmedValue.startsWith('{ ') && trimmedValue.endsWith(' }');
516
+ const isArrayLiteral = trimmedValue.startsWith('[') && trimmedValue.endsWith(']');
517
+ if (isObjectLiteral || isArrayLiteral) {
518
+ return `${formattedName}: ${attributeValue}`;
519
+ }
520
+
521
+ if (hasFunctionCall(trimmedValue)) {
522
+ return `${formattedName}: computed(() => ${attributeValue})`;
523
+ }
524
+
525
+ if (!hasIdentifier(trimmedValue)) {
526
+ return `${formattedName}: ${attributeValue}`;
527
+ }
528
+
529
+ const computedValue = transformBareIdentifiersToSignals(attributeValue);
530
+ return `${formattedName}: computed(() => ${computedValue})`;
531
+ }
532
+ / attributeName:attributeName _ {
533
+ const needsQuotes = /[^a-zA-Z0-9_$]/.test(attributeName);
534
+ return needsQuotes ? `'${attributeName}'` : attributeName;
535
+ }
536
+
537
+ attributeValue "attribute value"
538
+ = element
539
+ / functionWithElement
540
+ / objectLiteral
541
+ / $([^{}]* ("{" [^{}]* "}" [^{}]*)*) {
542
+ return text().trim()
543
+ }
544
+
545
+ objectLiteral "object literal"
546
+ = "{" _ objContent:objectContent _ "}" {
547
+ return `{ ${objContent} }`;
548
+ }
549
+
550
+ objectContent
551
+ = prop:objectProperty rest:(_ "," _ objectProperty)* {
552
+ return [prop].concat(rest.map(r => r[3])).join(', ');
553
+ }
554
+ / "" { return ""; }
555
+
556
+ objectProperty
557
+ = key:identifier _ ":" _ value:propertyValue {
558
+ return `${key}: ${value}`;
559
+ }
560
+ / key:identifier {
561
+ return key;
562
+ }
563
+
564
+ propertyValue
565
+ = nestedObject
566
+ / element
567
+ / functionWithElement
568
+ / stringLiteral
569
+ / number
570
+ / identifier
571
+
572
+ nestedObject
573
+ = "{" _ objContent:objectContent _ "}" {
574
+ return `{ ${objContent} }`;
575
+ }
576
+
577
+ stringLiteral
578
+ = '"' chars:[^"]* '"' { return text(); }
579
+ / "'" chars:[^']* "'" { return text(); }
580
+
581
+ functionWithElement "function expression"
582
+ = "(" _ params:functionParams? _ ")" _ "=>" _ elem:element {
583
+ return `${params ? `(${params}) =>` : '() =>'} ${elem}`;
584
+ }
585
+
586
+ functionParams
587
+ = destructuredParams
588
+ / simpleParams
589
+
590
+ destructuredParams
591
+ = "{" _ param:identifier rest:(_ "," _ identifier)* _ "}" {
592
+ return `{${[param].concat(rest.map(r => r[3])).join(', ')}}`;
593
+ }
594
+
595
+ simpleParams
596
+ = param:identifier rest:(_ "," _ identifier)* {
597
+ return [param].concat(rest.map(r => r[3])).join(', ');
598
+ }
599
+
600
+ staticAttribute "static attribute"
601
+ = attributeName:attributeName _ "=" _ "\"" attributeValue:staticValue "\"" {
602
+ const needsQuotes = /[^a-zA-Z0-9_$]/.test(attributeName);
603
+ const formattedName = needsQuotes ? `'${attributeName}'` : attributeName;
604
+ return `${formattedName}: ${attributeValue}`;
605
+ }
606
+
607
+ eventAttribute
608
+ = "(" _ eventName:eventName _ ")" _ "=" _ "\"" eventAction:eventAction "\"" {
609
+ return `${eventName}: () => { ${eventAction} }`;
610
+ }
611
+
612
+ staticValue
613
+ = [^"]+ {
614
+ var val = text();
615
+ return `'${val}'`
616
+ }
617
+
618
+ content "component content"
619
+ = elements:(element)* {
620
+ const filteredElements = elements.filter(el => el !== null);
621
+ if (filteredElements.length === 0) return null;
622
+ if (filteredElements.length === 1) return filteredElements[0];
623
+ return `[${filteredElements.join(', ')}]`;
624
+ }
625
+
626
+ domContent "DOM content"
627
+ = elements:(domContentPart)* {
628
+ const filteredElements = elements.filter(el => el !== null);
629
+ if (filteredElements.length === 0) return null;
630
+ if (filteredElements.length === 1) return filteredElements[0];
631
+ return `[${filteredElements.join(', ')}]`;
632
+ }
633
+
634
+ domContentPart
635
+ = element
636
+ / simpleTextContent
637
+
638
+
639
+
640
+ textNode
641
+ = text:$([^<]+) {
642
+ const trimmed = text.trim();
643
+ return trimmed ? `'${trimmed}'` : null;
644
+ }
645
+
646
+ textElement
647
+ = text:[^<>]+ {
648
+ const trimmed = text.join('').trim();
649
+ return trimmed ? JSON.stringify(trimmed) : null;
650
+ }
651
+
652
+ forLoop "for loop"
653
+ = _ "@for" _ "(" _ variableName:(tupleDestructuring / identifier) _ "of" _ iterable:iterable _ ")" _ "{" _ content:content _ "}" _ {
654
+ return `loop(${iterable}, ${variableName} => ${content})`;
655
+ }
656
+
657
+ tupleDestructuring "destructuring pattern"
658
+ = "(" _ first:identifier _ "," _ second:identifier _ ")" {
659
+ return `(${first}, ${second})`;
660
+ }
661
+
662
+ ifCondition "if condition"
663
+ = _ "@if" _ "(" _ condition:condition _ ")" _ "{" _ content:content _ "}" _ elseIfs:elseIfClause* elseClause:elseClause? _ {
664
+ let result = `cond(${condition}, () => ${content}`;
665
+
666
+ // Add else if clauses
667
+ elseIfs.forEach(elseIf => {
668
+ result += `, [${elseIf.condition}, () => ${elseIf.content}]`;
669
+ });
670
+
671
+ // Add else clause if present
672
+ if (elseClause) {
673
+ result += `, () => ${elseClause}`;
674
+ }
675
+
676
+ result += ')';
677
+ return result;
678
+ }
679
+
680
+ elseIfClause "else if clause"
681
+ = _ "@else" _ "if" _ "(" _ condition:condition _ ")" _ "{" _ content:content _ "}" _ {
682
+ return { condition, content };
683
+ }
684
+
685
+ elseClause "else clause"
686
+ = _ "@else" _ "{" _ content:content _ "}" _ {
687
+ return content;
688
+ }
689
+
690
+ tagName "tag name"
691
+ = tagExpression
692
+
693
+ tagExpression "tag expression"
694
+ = first:tagPart rest:("." tagPart)* {
695
+ return text();
696
+ }
697
+
698
+ tagPart "tag part"
699
+ = name:[a-zA-Z][a-zA-Z0-9]* args:("(" functionArgs? ")")? {
700
+ return text();
701
+ }
702
+
703
+ attributeName "attribute name"
704
+ = [a-zA-Z][a-zA-Z0-9-]* { return text(); }
705
+
706
+ eventName
707
+ = [a-zA-Z][a-zA-Z0-9-]* { return text(); }
708
+
709
+ variableName
710
+ = [a-zA-Z_][a-zA-Z0-9_]* { return text(); }
711
+
712
+ iterable "iterable expression"
713
+ = id:identifier "(" _ args:functionArgs? _ ")" { // Direct function call
714
+ return `${id}(${args || ''})`;
715
+ }
716
+ / first:identifier "." rest:dotFunctionChain { // Dot notation possibly with function call
717
+ return `${first}.${rest}`;
718
+ }
719
+ / id:identifier { return id; }
720
+
721
+ dotFunctionChain
722
+ = segment:identifier "(" _ args:functionArgs? _ ")" rest:("." dotFunctionChain)? {
723
+ const restStr = rest ? `.${rest[1]}` : '';
724
+ return `${segment}(${args || ''})${restStr}`;
725
+ }
726
+ / segment:identifier rest:("." dotFunctionChain)? {
727
+ const restStr = rest ? `.${rest[1]}` : '';
728
+ return `${segment}${restStr}`;
729
+ }
730
+
731
+ condition "condition expression"
732
+ = text:$(conditionChunk*) {
733
+ const originalText = text.trim();
734
+ if (!originalText) {
735
+ return originalText;
736
+ }
737
+
738
+ const hasOperator = /[!<>=&|]/.test(originalText);
739
+ if (hasOperator) {
740
+ return `computed(() => ${originalText})`;
741
+ }
742
+
743
+ return originalText;
744
+ }
745
+
746
+ conditionChunk
747
+ = "(" conditionChunk* ")"
748
+ / [^()]
749
+
750
+ functionCall "function call"
751
+ = name:identifier "(" args:functionArgs? ")" {
752
+ return `${name}(${args || ''})`;
753
+ }
754
+
755
+ functionCallWithArgs "function call with complex args"
756
+ = name:identifier "(" args:complexFunctionArgs? ")" {
757
+ return `${name}(${args || ''})`;
758
+ }
759
+
760
+ functionArgs
761
+ = arg:functionArg rest:("," _ functionArg)* {
762
+ return [arg].concat(rest.map(r => r[2])).join(', ');
763
+ }
764
+
765
+ complexFunctionArgs
766
+ = arg:complexFunctionArg rest:("," _ complexFunctionArg)* {
767
+ return [arg].concat(rest.map(r => r[2])).join(', ');
768
+ }
769
+
770
+ functionArg
771
+ = _ value:(identifier / number / string) _ {
772
+ return value;
773
+ }
774
+
775
+ complexFunctionArg "complex function argument"
776
+ = _ value:complexArgExpression _ {
777
+ return value.trim();
778
+ }
779
+
780
+ complexArgExpression "complex argument expression"
781
+ = $([^,)]* ("(" [^)]* ")" [^,)]*)*) {
782
+ return text().trim();
783
+ }
784
+
785
+ number
786
+ = [0-9]+ ("." [0-9]+)? { return text(); }
787
+
788
+ string
789
+ = '"' chars:[^"]* '"' { return text(); }
790
+ / "'" chars:[^']* "'" { return text(); }
791
+
792
+ eventAction
793
+ = [^"]* { return text(); }
794
+
795
+ _ 'whitespace'
796
+ = [ \t\n\r]*
797
+
798
+ identifier
799
+ = [a-zA-Z_][a-zA-Z0-9_]* { return text(); }
800
+
801
+ comment
802
+ = singleComment+ {
803
+ return null
804
+ }
805
+
806
+ singleComment
807
+ = "<!--" _ content:((!("-->") .)* "-->") _ {
808
+ return null;
809
+ }
810
+
811
+ // Add a special error detection rule for unclosed tags
812
+ openUnclosedTag "unclosed tag"
813
+ = "<" _ tagName:tagName &{ return !isVoidElement(tagName); } _ attributes:attributes _ ">" _ content:content _ !("</" _ closingTagName:tagName _ ">") {
814
+ generateError(
815
+ `Unclosed tag: <${tagName}> is missing its closing tag`,
816
+ location()
817
+ );
818
+ }
819
+
820
+ // Add error detection for unclosed quotes in static attributes
821
+ unclosedQuote "unclosed string"
822
+ = attributeName:attributeName _ "=" _ "\"" [^"]* !("\"") {
823
+ generateError(
824
+ `Missing closing quote in attribute '${attributeName}'`,
825
+ location()
826
+ );
827
+ }
828
+
829
+ // Add error detection for unclosed braces in dynamic attributes
830
+ unclosedBrace "unclosed brace"
831
+ = attributeName:attributeName _ "=" _ "{" !("}" / _ "}") [^{}]* {
832
+ generateError(
833
+ `Missing closing brace in dynamic attribute '${attributeName}'`,
834
+ location()
835
+ );
836
+ }
837
+
838
+ svgElement "SVG element"
839
+ = "<svg" attrs:([^>]*) ">" content:svgInnerContent "</svg>" _ {
840
+ const attributes = attrs.join('').trim();
841
+ // Clean up the content by removing extra whitespace and newlines
842
+ const cleanContent = content.replace(/\s+/g, ' ').trim();
843
+ const rawContent = `<svg${attributes ? ' ' + attributes : ''}>${cleanContent}</svg>`;
844
+ return `h(Svg, { content: \`${rawContent}\` })`;
845
+ }
846
+
847
+ svgInnerContent "SVG inner content"
848
+ = content:$((!("</svg>") .)*) {
849
+ return content;
850
+ }
package/dist/index.js CHANGED
@@ -119,15 +119,18 @@ function shaderLoader() {
119
119
  }
120
120
  function canvasengine() {
121
121
  const filter = createFilter("**/*.ce");
122
+ const useLegacyGrammar = process.env.CANVASENGINE_COMPILER_V1 === "1";
122
123
  const __filename = fileURLToPath(import.meta.url);
123
124
  const __dirname = path.dirname(__filename);
125
+ const grammarFile = useLegacyGrammar ? "grammar.pegjs" : "grammar2.pegjs";
124
126
  const grammar = fs.readFileSync(
125
- path.join(__dirname, "grammar.pegjs"),
127
+ path.join(__dirname, grammarFile),
126
128
  "utf8"
127
129
  );
128
130
  const parser = generate(grammar);
129
131
  const isDev = process.env.NODE_ENV === "dev";
130
132
  const FLAG_COMMENT = "/*--[TPL]--*/";
133
+ let warnedAboutGrammar = false;
131
134
  const PRIMITIVE_COMPONENTS = [
132
135
  "Canvas",
133
136
  "Sprite",
@@ -157,6 +160,15 @@ function canvasengine() {
157
160
  name: "vite-plugin-ce",
158
161
  transform(code, id) {
159
162
  if (!filter(id)) return null;
163
+ if (!warnedAboutGrammar) {
164
+ warnedAboutGrammar = true;
165
+ const legacyNote = "Set CANVASENGINE_COMPILER_V1=1 to compile with the legacy grammar (v1).";
166
+ if (useLegacyGrammar) {
167
+ console.warn(`[canvasengine] Using legacy grammar v1. ${legacyNote}`);
168
+ } else {
169
+ console.warn(`[canvasengine] Breaking change: compiler grammar v2 is now the default. ${legacyNote}`);
170
+ }
171
+ }
160
172
  const scriptMatch = code.match(/<script>([\s\S]*?)<\/script>/);
161
173
  let scriptContent = scriptMatch ? scriptMatch[1].trim() : "";
162
174
  const styleTagMatch = code.match(/<style([^>]*)>([\s\S]*?)<\/style>/);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../index.ts"],"sourcesContent":["import { createFilter } from \"vite\";\nimport { parse } from \"acorn\";\nimport fs from \"fs\";\nimport pkg from \"peggy\";\nimport path from \"path\";\nimport * as ts from \"typescript\";\nimport { fileURLToPath } from 'url';\n\nconst { generate } = pkg;\n\nconst DEV_SRC = \"../../src\"\n\n/**\n * Generates a short hash (8 characters, letters only) from a string\n * \n * @param {string} str - The string to hash\n * @returns {string} - An 8-character hash containing only lowercase letters (a-z)\n */\nfunction generateHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n // Convert to positive number and map to letters only (a-z)\n // Use modulo to map to 26 letters, then convert to character\n const positiveHash = Math.abs(hash);\n let result = '';\n for (let i = 0; i < 8; i++) {\n const letterIndex = (positiveHash + i * 31) % 26; // 31 is a prime to spread values\n result += String.fromCharCode(97 + letterIndex); // 97 is 'a'\n }\n return result;\n}\n\n/**\n * Formats a syntax error message with visual pointer to the error location\n * \n * @param {string} template - The template content that failed to parse\n * @param {object} error - The error object with location information\n * @returns {string} - Formatted error message with a visual pointer\n * \n * @example\n * ```\n * const errorMessage = showErrorMessage(\"<Canvas>test(d)</Canvas>\", syntaxError);\n * // Returns a formatted error message with an arrow pointing to 'd'\n * ```\n */\nfunction showErrorMessage(template: string, error: any): string {\n if (!error.location) {\n return `Syntax error: ${error.message}`;\n }\n\n const lines = template.split('\\n');\n const { line, column } = error.location.start;\n const errorLine = lines[line - 1] || '';\n \n // Create a visual pointer with an arrow\n const pointer = ' '.repeat(column - 1) + '^';\n \n return `Syntax error at line ${line}, column ${column}: ${error.message}\\n\\n` +\n `${errorLine}\\n${pointer}\\n`;\n}\n\n/**\n * Scopes CSS selectors by prefixing them with a class selector\n * \n * This function prefixes all CSS rule selectors (not @rules) with a class\n * selector to scope the styles to a specific component instance.\n * \n * @param {string} css - The CSS content to scope\n * @param {string} scopeClass - The unique scope class to use (without the dot)\n * @returns {string} - The scoped CSS content\n * \n * @example\n * ```\n * const scoped = scopeCSS('.my-class { color: red; }', 'ce-scope-abc123');\n * // Returns: '.ce-scope-abc123 .my-class { color: red; }'\n * ```\n */\nfunction scopeCSS(css: string, scopeClass: string): string {\n const scopeSelector = `.${scopeClass}`;\n \n // Process CSS by finding rule blocks while skipping @rules\n let result = '';\n let i = 0;\n let depth = 0;\n let inRule = false;\n let selectorBuffer = '';\n \n while (i < css.length) {\n const char = css[i];\n \n if (char === '@' && !inRule && selectorBuffer === '') {\n // Found @rule - copy it as-is until matching closing brace\n const atRuleStart = i;\n i++; // Skip '@'\n \n // Find the opening brace\n while (i < css.length && css[i] !== '{') {\n i++;\n }\n \n if (i < css.length) {\n // Found opening brace, now find matching closing brace\n depth = 1;\n i++; // Skip '{'\n \n while (i < css.length && depth > 0) {\n if (css[i] === '{') depth++;\n else if (css[i] === '}') depth--;\n i++;\n }\n \n // Copy entire @rule as-is\n result += css.substring(atRuleStart, i);\n }\n continue;\n }\n \n if (char === '{' && !inRule) {\n // Start of a rule block - scope the selector we just collected\n const selectorText = selectorBuffer.trim();\n \n if (selectorText) {\n // Split selectors by comma and scope each one\n const scopedSelectors = selectorText\n .split(',')\n .map(sel => {\n const trimmed = sel.trim();\n return trimmed ? `${scopeSelector} ${trimmed}` : trimmed;\n })\n .join(', ');\n \n result += scopedSelectors;\n }\n result += ' {';\n inRule = true;\n depth = 1;\n selectorBuffer = '';\n } else if (char === '{' && inRule) {\n // Nested brace\n result += char;\n depth++;\n } else if (char === '}' && inRule) {\n result += char;\n depth--;\n if (depth === 0) {\n inRule = false;\n }\n } else if (!inRule) {\n // Collecting selector\n selectorBuffer += char;\n } else {\n // Inside rule block\n result += char;\n if (char === '{') depth++;\n }\n \n i++;\n }\n \n // Add any remaining selector (shouldn't happen in valid CSS, but handle it)\n if (selectorBuffer.trim()) {\n const scopedSelectors = selectorBuffer.trim()\n .split(',')\n .map(sel => {\n const trimmed = sel.trim();\n return trimmed ? `${scopeSelector} ${trimmed}` : trimmed;\n })\n .join(', ');\n result += scopedSelectors;\n }\n \n return result;\n}\n\n/**\n * Vite plugin to load shader files (.frag, .vert, .wgsl) as text strings\n * \n * This plugin allows importing shader files directly as string literals in your code.\n * It supports fragment shaders (.frag), vertex shaders (.vert), and WebGPU shaders (.wgsl).\n * The content is loaded as a raw string and can be used directly with graphics APIs.\n * \n * @returns {object} - Vite plugin configuration object\n * \n * @example\n * ```typescript\n * // In your vite.config.ts\n * import { shaderLoader } from './path/to/compiler'\n * \n * export default defineConfig({\n * plugins: [shaderLoader()]\n * })\n * \n * // In your code\n * import fragmentShader from './shader.frag'\n * import vertexShader from './shader.vert'\n * import computeShader from './shader.wgsl'\n * \n * console.log(fragmentShader) // Raw shader code as string\n * ```\n */\nexport function shaderLoader() {\n const filter = createFilter(/\\.(frag|vert|wgsl)$/);\n\n return {\n name: \"vite-plugin-shader-loader\",\n transform(code: string, id: string) {\n if (!filter(id)) return;\n\n // Escape the shader code to be safely embedded in a JavaScript string\n const escapedCode = code\n .replace(/\\\\/g, '\\\\\\\\') // Escape backslashes\n .replace(/`/g, '\\\\`') // Escape backticks\n .replace(/\\$/g, '\\\\$'); // Escape dollar signs\n\n // Return the shader content as a default export string\n return {\n code: `export default \\`${escapedCode}\\`;`,\n map: null,\n };\n },\n };\n}\n\nexport default function canvasengine() {\n const filter = createFilter(\"**/*.ce\");\n\n // Convert import.meta.url to a file path\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n\n const grammar = fs.readFileSync(\n path.join(__dirname, \"grammar.pegjs\"),\n \"utf8\"\n );\n const parser = generate(grammar);\n const isDev = process.env.NODE_ENV === \"dev\";\n const FLAG_COMMENT = \"/*--[TPL]--*/\";\n\n const PRIMITIVE_COMPONENTS = [\n \"Canvas\",\n \"Sprite\",\n \"Text\",\n \"Viewport\",\n \"Graphics\",\n \"Container\",\n \"Navigation\",\n \"ImageMap\",\n \"NineSliceSprite\",\n \"Rect\",\n \"Circle\",\n \"Ellipse\",\n \"Triangle\",\n \"TilingSprite\",\n \"svg\",\n \"Video\",\n \"Mesh\",\n \"Svg\",\n \"DOMContainer\",\n \"DOMElement\",\n \"DOMSprite\",\n \"Button\",\n \"Joystick\"\n ];\n\n return {\n name: \"vite-plugin-ce\",\n transform(code: string, id: string) {\n if (!filter(id)) return null;\n\n // Extract the script content\n const scriptMatch = code.match(/<script>([\\s\\S]*?)<\\/script>/);\n let scriptContent = scriptMatch ? scriptMatch[1].trim() : \"\";\n \n // Extract the style tag with attributes and content\n const styleTagMatch = code.match(/<style([^>]*)>([\\s\\S]*?)<\\/style>/);\n let styleContent = \"\";\n let isScoped = false;\n \n if (styleTagMatch) {\n const styleAttributes = styleTagMatch[1].trim();\n styleContent = styleTagMatch[2].trim();\n \n // Check if scoped attribute is present\n isScoped = /scoped(?:\\s|>|$)/.test(styleAttributes);\n }\n \n // Remove script and style tags from template before parsing\n let template = code\n .replace(/<script>[\\s\\S]*?<\\/script>/, \"\")\n .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/, \"\")\n .replace(/^\\s+|\\s+$/g, '');\n\n let parsedTemplate;\n try {\n parsedTemplate = parser.parse(template);\n } catch (error) {\n const errorMsg = showErrorMessage(template, error);\n throw new Error(`Error parsing template in file ${id}:\\n${errorMsg}`);\n }\n\n // trick to avoid typescript remove imports in scriptContent\n scriptContent += FLAG_COMMENT + parsedTemplate\n\n let transpiledCode = ts.transpileModule(scriptContent, {\n compilerOptions: {\n module: ts.ModuleKind.Preserve,\n },\n }).outputText;\n\n // remove code after /*---*/\n transpiledCode = transpiledCode.split(FLAG_COMMENT)[0]\n\n // Use Acorn to parse the script content\n const parsed = parse(transpiledCode, {\n sourceType: \"module\",\n ecmaVersion: 2020,\n });\n\n // Extract imports\n const imports = parsed.body.filter(\n (node) => node.type === \"ImportDeclaration\"\n );\n\n // Extract non-import statements from scriptContent\n const nonImportCode = parsed.body\n .filter((node) => node.type !== \"ImportDeclaration\")\n .map((node) => transpiledCode.slice(node.start, node.end))\n .join(\"\\n\");\n\n let importsCode = imports\n .map((imp) => {\n let importCode = transpiledCode.slice(imp.start, imp.end);\n if (isDev && importCode.includes(\"from 'canvasengine'\")) {\n importCode = importCode.replace(\n \"from 'canvasengine'\",\n `from '${DEV_SRC}'`\n );\n }\n return importCode;\n })\n .join(\"\\n\");\n\n // Define an array for required imports\n const requiredImports = [\"h\", \"computed\", \"cond\", \"loop\"];\n\n // Check for missing imports\n const missingImports = requiredImports.filter(\n (importName) =>\n !imports.some(\n (imp) =>\n imp.specifiers &&\n imp.specifiers.some(\n (spec) =>\n spec.type === \"ImportSpecifier\" &&\n spec.imported && \n 'name' in spec.imported &&\n spec.imported.name === importName\n )\n )\n );\n\n // Add missing imports\n if (missingImports.length > 0) {\n const additionalImportCode = `import { ${missingImports.join(\n \", \"\n )} } from ${isDev ? `'${DEV_SRC}'` : \"'canvasengine'\"};`;\n importsCode = `${additionalImportCode}\\n${importsCode}`;\n }\n\n // Check for primitive components in parsedTemplate\n const primitiveImports = PRIMITIVE_COMPONENTS.filter((component) =>\n parsedTemplate.includes(`h(${component}`)\n );\n\n // Add missing imports for primitive components\n primitiveImports.forEach((component) => {\n const importStatement = `import { ${component} } from ${\n isDev ? `'${DEV_SRC}'` : \"'canvasengine'\"\n };`;\n if (!importsCode.includes(importStatement)) {\n importsCode = `${importStatement}\\n${importsCode}`;\n }\n });\n\n // Process CSS: scope it if scoped attribute is present\n let processedStyleContent = styleContent;\n let scopeClass = '';\n \n if (isScoped && styleContent) {\n // Generate short hash (8 characters) based on file path\n const fileHash = generateHash(id);\n scopeClass = fileHash;\n processedStyleContent = scopeCSS(styleContent, scopeClass);\n \n // Add _scopeClass prop to all DOMContainer in the template\n // Pattern: h(DOMContainer, { ... }) or h(DOMContainer) or h(DOMContainer, null, ...)\n parsedTemplate = parsedTemplate.replace(\n /h\\(DOMContainer\\s*,\\s*(\\{([^}]*)\\}|null)\\s*(,\\s*[^)]*)?\\)/g,\n (match, propsPart, propsContent, childrenPart) => {\n if (propsPart === 'null') {\n // h(DOMContainer, null, ...) -> h(DOMContainer, { _scopeClass: '...' }, ...)\n return `h(DOMContainer, { _scopeClass: '${scopeClass}' }${childrenPart || ''})`;\n } else {\n // h(DOMContainer, { ... }, ...) -> h(DOMContainer, { _scopeClass: '...', ... }, ...)\n // Need to insert _scopeClass at the beginning of the props object\n return `h(DOMContainer, { _scopeClass: '${scopeClass}', ${propsContent || ''} }${childrenPart || ''})`;\n }\n }\n );\n \n // Also handle h(DOMContainer) without props\n parsedTemplate = parsedTemplate.replace(\n /h\\(DOMContainer\\s*\\)(?!\\s*\\()/g,\n `h(DOMContainer, { _scopeClass: '${scopeClass}' })`\n );\n }\n \n // Escape style content for safe embedding in JavaScript string (using single quotes)\n // We need to escape: backslashes, single quotes, and line breaks\n const escapedStyleContent = processedStyleContent\n .replace(/\\\\/g, '\\\\\\\\') // Escape backslashes first\n .replace(/'/g, \"\\\\'\") // Escape single quotes\n .replace(/\\n/g, '\\\\n') // Escape newlines\n .replace(/\\r/g, '\\\\r'); // Escape carriage returns\n\n // Generate unique ID for style element based on file path\n const styleId = `ce-style-${id.replace(/[^a-zA-Z0-9]/g, '-')}`;\n\n // Generate CSS injection code if style content exists\n // Use single quotes to avoid escaping issues with backticks\n const styleInjectionCode = styleContent ? \n '// Inject CSS styles into the document head\\n' +\n `if (typeof document !== 'undefined' && !document.getElementById('${styleId}')) {\\n` +\n ' const styleElement = document.createElement(\\'style\\');\\n' +\n ` styleElement.id = '${styleId}';\\n` +\n ` styleElement.textContent = '${escapedStyleContent}';\\n` +\n ' document.head.appendChild(styleElement);\\n' +\n '}\\n'\n : '';\n \n\n // Generate the output\n const output = String.raw`\n ${importsCode}\n import { useProps, useDefineProps } from ${isDev ? `'${DEV_SRC}'` : \"'canvasengine'\"}\n ${styleInjectionCode}\n export default function component($$props) {\n const $props = useProps($$props)\n const defineProps = useDefineProps($$props)\n ${nonImportCode}\n let $this = ${parsedTemplate}\n return $this\n }\n `;\n\n return {\n code: output,\n map: null,\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../index.ts"],"sourcesContent":["import { createFilter } from \"vite\";\nimport { parse } from \"acorn\";\nimport fs from \"fs\";\nimport pkg from \"peggy\";\nimport path from \"path\";\nimport * as ts from \"typescript\";\nimport { fileURLToPath } from 'url';\n\nconst { generate } = pkg;\n\nconst DEV_SRC = \"../../src\"\n\n/**\n * Generates a short hash (8 characters, letters only) from a string\n * \n * @param {string} str - The string to hash\n * @returns {string} - An 8-character hash containing only lowercase letters (a-z)\n */\nfunction generateHash(str: string): string {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n // Convert to positive number and map to letters only (a-z)\n // Use modulo to map to 26 letters, then convert to character\n const positiveHash = Math.abs(hash);\n let result = '';\n for (let i = 0; i < 8; i++) {\n const letterIndex = (positiveHash + i * 31) % 26; // 31 is a prime to spread values\n result += String.fromCharCode(97 + letterIndex); // 97 is 'a'\n }\n return result;\n}\n\n/**\n * Formats a syntax error message with visual pointer to the error location\n * \n * @param {string} template - The template content that failed to parse\n * @param {object} error - The error object with location information\n * @returns {string} - Formatted error message with a visual pointer\n * \n * @example\n * ```\n * const errorMessage = showErrorMessage(\"<Canvas>test(d)</Canvas>\", syntaxError);\n * // Returns a formatted error message with an arrow pointing to 'd'\n * ```\n */\nfunction showErrorMessage(template: string, error: any): string {\n if (!error.location) {\n return `Syntax error: ${error.message}`;\n }\n\n const lines = template.split('\\n');\n const { line, column } = error.location.start;\n const errorLine = lines[line - 1] || '';\n \n // Create a visual pointer with an arrow\n const pointer = ' '.repeat(column - 1) + '^';\n \n return `Syntax error at line ${line}, column ${column}: ${error.message}\\n\\n` +\n `${errorLine}\\n${pointer}\\n`;\n}\n\n/**\n * Scopes CSS selectors by prefixing them with a class selector\n * \n * This function prefixes all CSS rule selectors (not @rules) with a class\n * selector to scope the styles to a specific component instance.\n * \n * @param {string} css - The CSS content to scope\n * @param {string} scopeClass - The unique scope class to use (without the dot)\n * @returns {string} - The scoped CSS content\n * \n * @example\n * ```\n * const scoped = scopeCSS('.my-class { color: red; }', 'ce-scope-abc123');\n * // Returns: '.ce-scope-abc123 .my-class { color: red; }'\n * ```\n */\nfunction scopeCSS(css: string, scopeClass: string): string {\n const scopeSelector = `.${scopeClass}`;\n \n // Process CSS by finding rule blocks while skipping @rules\n let result = '';\n let i = 0;\n let depth = 0;\n let inRule = false;\n let selectorBuffer = '';\n \n while (i < css.length) {\n const char = css[i];\n \n if (char === '@' && !inRule && selectorBuffer === '') {\n // Found @rule - copy it as-is until matching closing brace\n const atRuleStart = i;\n i++; // Skip '@'\n \n // Find the opening brace\n while (i < css.length && css[i] !== '{') {\n i++;\n }\n \n if (i < css.length) {\n // Found opening brace, now find matching closing brace\n depth = 1;\n i++; // Skip '{'\n \n while (i < css.length && depth > 0) {\n if (css[i] === '{') depth++;\n else if (css[i] === '}') depth--;\n i++;\n }\n \n // Copy entire @rule as-is\n result += css.substring(atRuleStart, i);\n }\n continue;\n }\n \n if (char === '{' && !inRule) {\n // Start of a rule block - scope the selector we just collected\n const selectorText = selectorBuffer.trim();\n \n if (selectorText) {\n // Split selectors by comma and scope each one\n const scopedSelectors = selectorText\n .split(',')\n .map(sel => {\n const trimmed = sel.trim();\n return trimmed ? `${scopeSelector} ${trimmed}` : trimmed;\n })\n .join(', ');\n \n result += scopedSelectors;\n }\n result += ' {';\n inRule = true;\n depth = 1;\n selectorBuffer = '';\n } else if (char === '{' && inRule) {\n // Nested brace\n result += char;\n depth++;\n } else if (char === '}' && inRule) {\n result += char;\n depth--;\n if (depth === 0) {\n inRule = false;\n }\n } else if (!inRule) {\n // Collecting selector\n selectorBuffer += char;\n } else {\n // Inside rule block\n result += char;\n if (char === '{') depth++;\n }\n \n i++;\n }\n \n // Add any remaining selector (shouldn't happen in valid CSS, but handle it)\n if (selectorBuffer.trim()) {\n const scopedSelectors = selectorBuffer.trim()\n .split(',')\n .map(sel => {\n const trimmed = sel.trim();\n return trimmed ? `${scopeSelector} ${trimmed}` : trimmed;\n })\n .join(', ');\n result += scopedSelectors;\n }\n \n return result;\n}\n\n/**\n * Vite plugin to load shader files (.frag, .vert, .wgsl) as text strings\n * \n * This plugin allows importing shader files directly as string literals in your code.\n * It supports fragment shaders (.frag), vertex shaders (.vert), and WebGPU shaders (.wgsl).\n * The content is loaded as a raw string and can be used directly with graphics APIs.\n * \n * @returns {object} - Vite plugin configuration object\n * \n * @example\n * ```typescript\n * // In your vite.config.ts\n * import { shaderLoader } from './path/to/compiler'\n * \n * export default defineConfig({\n * plugins: [shaderLoader()]\n * })\n * \n * // In your code\n * import fragmentShader from './shader.frag'\n * import vertexShader from './shader.vert'\n * import computeShader from './shader.wgsl'\n * \n * console.log(fragmentShader) // Raw shader code as string\n * ```\n */\nexport function shaderLoader() {\n const filter = createFilter(/\\.(frag|vert|wgsl)$/);\n\n return {\n name: \"vite-plugin-shader-loader\",\n transform(code: string, id: string) {\n if (!filter(id)) return;\n\n // Escape the shader code to be safely embedded in a JavaScript string\n const escapedCode = code\n .replace(/\\\\/g, '\\\\\\\\') // Escape backslashes\n .replace(/`/g, '\\\\`') // Escape backticks\n .replace(/\\$/g, '\\\\$'); // Escape dollar signs\n\n // Return the shader content as a default export string\n return {\n code: `export default \\`${escapedCode}\\`;`,\n map: null,\n };\n },\n };\n}\n\nexport default function canvasengine() {\n const filter = createFilter(\"**/*.ce\");\n const useLegacyGrammar = process.env.CANVASENGINE_COMPILER_V1 === \"1\";\n\n // Convert import.meta.url to a file path\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n\n const grammarFile = useLegacyGrammar ? \"grammar.pegjs\" : \"grammar2.pegjs\";\n const grammar = fs.readFileSync(\n path.join(__dirname, grammarFile),\n \"utf8\"\n );\n const parser = generate(grammar);\n const isDev = process.env.NODE_ENV === \"dev\";\n const FLAG_COMMENT = \"/*--[TPL]--*/\";\n let warnedAboutGrammar = false;\n\n const PRIMITIVE_COMPONENTS = [\n \"Canvas\",\n \"Sprite\",\n \"Text\",\n \"Viewport\",\n \"Graphics\",\n \"Container\",\n \"Navigation\",\n \"ImageMap\",\n \"NineSliceSprite\",\n \"Rect\",\n \"Circle\",\n \"Ellipse\",\n \"Triangle\",\n \"TilingSprite\",\n \"svg\",\n \"Video\",\n \"Mesh\",\n \"Svg\",\n \"DOMContainer\",\n \"DOMElement\",\n \"DOMSprite\",\n \"Button\",\n \"Joystick\"\n ];\n\n return {\n name: \"vite-plugin-ce\",\n transform(code: string, id: string) {\n if (!filter(id)) return null;\n if (!warnedAboutGrammar) {\n warnedAboutGrammar = true;\n const legacyNote = \"Set CANVASENGINE_COMPILER_V1=1 to compile with the legacy grammar (v1).\";\n if (useLegacyGrammar) {\n console.warn(`[canvasengine] Using legacy grammar v1. ${legacyNote}`);\n } else {\n console.warn(`[canvasengine] Breaking change: compiler grammar v2 is now the default. ${legacyNote}`);\n }\n }\n\n // Extract the script content\n const scriptMatch = code.match(/<script>([\\s\\S]*?)<\\/script>/);\n let scriptContent = scriptMatch ? scriptMatch[1].trim() : \"\";\n \n // Extract the style tag with attributes and content\n const styleTagMatch = code.match(/<style([^>]*)>([\\s\\S]*?)<\\/style>/);\n let styleContent = \"\";\n let isScoped = false;\n \n if (styleTagMatch) {\n const styleAttributes = styleTagMatch[1].trim();\n styleContent = styleTagMatch[2].trim();\n \n // Check if scoped attribute is present\n isScoped = /scoped(?:\\s|>|$)/.test(styleAttributes);\n }\n \n // Remove script and style tags from template before parsing\n let template = code\n .replace(/<script>[\\s\\S]*?<\\/script>/, \"\")\n .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/, \"\")\n .replace(/^\\s+|\\s+$/g, '');\n\n let parsedTemplate;\n try {\n parsedTemplate = parser.parse(template);\n } catch (error) {\n const errorMsg = showErrorMessage(template, error);\n throw new Error(`Error parsing template in file ${id}:\\n${errorMsg}`);\n }\n\n // trick to avoid typescript remove imports in scriptContent\n scriptContent += FLAG_COMMENT + parsedTemplate\n\n let transpiledCode = ts.transpileModule(scriptContent, {\n compilerOptions: {\n module: ts.ModuleKind.Preserve,\n },\n }).outputText;\n\n // remove code after /*---*/\n transpiledCode = transpiledCode.split(FLAG_COMMENT)[0]\n\n // Use Acorn to parse the script content\n const parsed = parse(transpiledCode, {\n sourceType: \"module\",\n ecmaVersion: 2020,\n });\n\n // Extract imports\n const imports = parsed.body.filter(\n (node) => node.type === \"ImportDeclaration\"\n );\n\n // Extract non-import statements from scriptContent\n const nonImportCode = parsed.body\n .filter((node) => node.type !== \"ImportDeclaration\")\n .map((node) => transpiledCode.slice(node.start, node.end))\n .join(\"\\n\");\n\n let importsCode = imports\n .map((imp) => {\n let importCode = transpiledCode.slice(imp.start, imp.end);\n if (isDev && importCode.includes(\"from 'canvasengine'\")) {\n importCode = importCode.replace(\n \"from 'canvasengine'\",\n `from '${DEV_SRC}'`\n );\n }\n return importCode;\n })\n .join(\"\\n\");\n\n // Define an array for required imports\n const requiredImports = [\"h\", \"computed\", \"cond\", \"loop\"];\n\n // Check for missing imports\n const missingImports = requiredImports.filter(\n (importName) =>\n !imports.some(\n (imp) =>\n imp.specifiers &&\n imp.specifiers.some(\n (spec) =>\n spec.type === \"ImportSpecifier\" &&\n spec.imported && \n 'name' in spec.imported &&\n spec.imported.name === importName\n )\n )\n );\n\n // Add missing imports\n if (missingImports.length > 0) {\n const additionalImportCode = `import { ${missingImports.join(\n \", \"\n )} } from ${isDev ? `'${DEV_SRC}'` : \"'canvasengine'\"};`;\n importsCode = `${additionalImportCode}\\n${importsCode}`;\n }\n\n // Check for primitive components in parsedTemplate\n const primitiveImports = PRIMITIVE_COMPONENTS.filter((component) =>\n parsedTemplate.includes(`h(${component}`)\n );\n\n // Add missing imports for primitive components\n primitiveImports.forEach((component) => {\n const importStatement = `import { ${component} } from ${\n isDev ? `'${DEV_SRC}'` : \"'canvasengine'\"\n };`;\n if (!importsCode.includes(importStatement)) {\n importsCode = `${importStatement}\\n${importsCode}`;\n }\n });\n\n // Process CSS: scope it if scoped attribute is present\n let processedStyleContent = styleContent;\n let scopeClass = '';\n \n if (isScoped && styleContent) {\n // Generate short hash (8 characters) based on file path\n const fileHash = generateHash(id);\n scopeClass = fileHash;\n processedStyleContent = scopeCSS(styleContent, scopeClass);\n \n // Add _scopeClass prop to all DOMContainer in the template\n // Pattern: h(DOMContainer, { ... }) or h(DOMContainer) or h(DOMContainer, null, ...)\n parsedTemplate = parsedTemplate.replace(\n /h\\(DOMContainer\\s*,\\s*(\\{([^}]*)\\}|null)\\s*(,\\s*[^)]*)?\\)/g,\n (match, propsPart, propsContent, childrenPart) => {\n if (propsPart === 'null') {\n // h(DOMContainer, null, ...) -> h(DOMContainer, { _scopeClass: '...' }, ...)\n return `h(DOMContainer, { _scopeClass: '${scopeClass}' }${childrenPart || ''})`;\n } else {\n // h(DOMContainer, { ... }, ...) -> h(DOMContainer, { _scopeClass: '...', ... }, ...)\n // Need to insert _scopeClass at the beginning of the props object\n return `h(DOMContainer, { _scopeClass: '${scopeClass}', ${propsContent || ''} }${childrenPart || ''})`;\n }\n }\n );\n \n // Also handle h(DOMContainer) without props\n parsedTemplate = parsedTemplate.replace(\n /h\\(DOMContainer\\s*\\)(?!\\s*\\()/g,\n `h(DOMContainer, { _scopeClass: '${scopeClass}' })`\n );\n }\n \n // Escape style content for safe embedding in JavaScript string (using single quotes)\n // We need to escape: backslashes, single quotes, and line breaks\n const escapedStyleContent = processedStyleContent\n .replace(/\\\\/g, '\\\\\\\\') // Escape backslashes first\n .replace(/'/g, \"\\\\'\") // Escape single quotes\n .replace(/\\n/g, '\\\\n') // Escape newlines\n .replace(/\\r/g, '\\\\r'); // Escape carriage returns\n\n // Generate unique ID for style element based on file path\n const styleId = `ce-style-${id.replace(/[^a-zA-Z0-9]/g, '-')}`;\n\n // Generate CSS injection code if style content exists\n // Use single quotes to avoid escaping issues with backticks\n const styleInjectionCode = styleContent ? \n '// Inject CSS styles into the document head\\n' +\n `if (typeof document !== 'undefined' && !document.getElementById('${styleId}')) {\\n` +\n ' const styleElement = document.createElement(\\'style\\');\\n' +\n ` styleElement.id = '${styleId}';\\n` +\n ` styleElement.textContent = '${escapedStyleContent}';\\n` +\n ' document.head.appendChild(styleElement);\\n' +\n '}\\n'\n : '';\n \n\n // Generate the output\n const output = String.raw`\n ${importsCode}\n import { useProps, useDefineProps } from ${isDev ? `'${DEV_SRC}'` : \"'canvasengine'\"}\n ${styleInjectionCode}\n export default function component($$props) {\n const $props = useProps($$props)\n const defineProps = useDefineProps($$props)\n ${nonImportCode}\n let $this = ${parsedTemplate}\n return $this\n }\n `;\n\n return {\n code: output,\n map: null,\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canvasengine/compiler",
3
- "version": "2.0.0-beta.47",
3
+ "version": "2.0.0-beta.48",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -8,7 +8,7 @@
8
8
  "dist"
9
9
  ],
10
10
  "scripts": {
11
- "build": "tsup && cp grammar.pegjs ../../docs/public/grammar.pegjs",
11
+ "build": "tsup && cp grammar2.pegjs ../../docs/public/grammar.pegjs",
12
12
  "dev": "tsup --watch"
13
13
  },
14
14
  "type": "module",