@hyperfixi/core 3.1.1 → 3.3.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 (97) hide show
  1. package/README.md +28 -36
  2. package/dist/ast/command-slots.d.ts +1 -1
  3. package/dist/ast/nodes.d.ts +2 -0
  4. package/dist/ast-utils/index.cjs +193 -48
  5. package/dist/ast-utils/index.mjs +193 -48
  6. package/dist/ast-utils/interchange/types.d.ts +19 -1
  7. package/dist/behaviors/index.cjs +19 -0
  8. package/dist/behaviors/index.mjs +19 -0
  9. package/dist/bundle-generator/index.cjs +36 -17
  10. package/dist/bundle-generator/index.mjs +36 -17
  11. package/dist/bundle-generator/parser-templates.d.ts +1 -1
  12. package/dist/chunks/bridge-CI9SOl8u.js +2 -0
  13. package/dist/chunks/browser-modular-7CpI5VF2.js +2 -0
  14. package/dist/chunks/index-BwXDEK0m.js +2 -0
  15. package/dist/chunks/index-DQi2o8Lk.js +2 -0
  16. package/dist/chunks/index-uCOQ8Qn0.js +2 -0
  17. package/dist/commands/animation/transition.d.ts +12 -6
  18. package/dist/commands/helpers/loop-executor.d.ts +2 -2
  19. package/dist/commands/helpers/numeric-target-parser.d.ts +1 -1
  20. package/dist/commands/helpers/variable-access.d.ts +5 -2
  21. package/dist/commands/helpers/write-target.d.ts +5 -0
  22. package/dist/commands/index.cjs +938 -425
  23. package/dist/commands/index.mjs +938 -425
  24. package/dist/commands/templates/render.d.ts +4 -4
  25. package/dist/commands/utility/beep.d.ts +0 -3
  26. package/dist/expressions/index.cjs +7 -13
  27. package/dist/expressions/index.mjs +7 -13
  28. package/dist/htmx/i18n-hooks.d.ts +4 -1
  29. package/dist/hyperfixi-browser-classic-i18n.js +1 -1
  30. package/dist/hyperfixi-browser.js +1 -1
  31. package/dist/hyperfixi-classic-i18n.js +1 -1
  32. package/dist/hyperfixi-hx-v4.js +1 -1
  33. package/dist/hyperfixi-hx.js +1 -1
  34. package/dist/hyperfixi-hybrid-complete.js +1 -1
  35. package/dist/hyperfixi-hybrid-hx.js +1 -1
  36. package/dist/hyperfixi-multilingual.js +1 -1
  37. package/dist/hyperfixi.js +1 -1
  38. package/dist/hyperfixi.mjs +1 -1
  39. package/dist/index.cjs +1149 -485
  40. package/dist/index.min.js +1 -1
  41. package/dist/index.mjs +1149 -485
  42. package/dist/lib/swap-executor.d.ts +1 -1
  43. package/dist/lokascript-browser-classic-i18n.js +1 -1
  44. package/dist/lokascript-browser.js +1 -1
  45. package/dist/lokascript-hybrid-complete.js +1 -1
  46. package/dist/lokascript-hybrid-hx.js +1 -1
  47. package/dist/lokascript-multilingual.js +1 -1
  48. package/dist/metadata.cjs +9 -9
  49. package/dist/metadata.d.ts +1 -1
  50. package/dist/metadata.mjs +9 -9
  51. package/dist/parser/command-grammar.d.ts +1 -0
  52. package/dist/parser/full-parser.cjs +396 -153
  53. package/dist/parser/full-parser.mjs +396 -153
  54. package/dist/parser/helpers/parsing-helpers.d.ts +3 -0
  55. package/dist/parser/hybrid/index.cjs +3 -0
  56. package/dist/parser/hybrid/index.mjs +3 -0
  57. package/dist/parser/hybrid/parser-core.cjs +3 -0
  58. package/dist/parser/hybrid/parser-core.mjs +3 -0
  59. package/dist/parser/hybrid-parser.cjs +3 -0
  60. package/dist/parser/hybrid-parser.mjs +3 -0
  61. package/dist/parser/parser-types.d.ts +2 -0
  62. package/dist/parser/parser.d.ts +9 -0
  63. package/dist/parser/pratt-parser.d.ts +2 -0
  64. package/dist/registry/index.cjs +535 -210
  65. package/dist/registry/index.mjs +535 -210
  66. package/dist/utils/beep.d.ts +8 -0
  67. package/dist/version.d.ts +1 -1
  68. package/package.json +21 -18
  69. package/vocab/htmx/ar.js +22 -20
  70. package/vocab/htmx/bn.js +21 -5
  71. package/vocab/htmx/de.js +33 -10
  72. package/vocab/htmx/en.js +4 -1
  73. package/vocab/htmx/es.js +28 -9
  74. package/vocab/htmx/fr.js +28 -8
  75. package/vocab/htmx/he.js +22 -1
  76. package/vocab/htmx/hi.js +28 -7
  77. package/vocab/htmx/id.js +29 -10
  78. package/vocab/htmx/it.js +26 -5
  79. package/vocab/htmx/ja.js +36 -11
  80. package/vocab/htmx/ko.js +27 -6
  81. package/vocab/htmx/ms.js +24 -1
  82. package/vocab/htmx/pl.js +27 -7
  83. package/vocab/htmx/pt.js +30 -9
  84. package/vocab/htmx/qu.js +32 -9
  85. package/vocab/htmx/ru.js +28 -6
  86. package/vocab/htmx/sw.js +28 -8
  87. package/vocab/htmx/th.js +19 -1
  88. package/vocab/htmx/tl.js +24 -1
  89. package/vocab/htmx/tr.js +34 -8
  90. package/vocab/htmx/uk.js +28 -6
  91. package/vocab/htmx/vi.js +27 -19
  92. package/vocab/htmx/zh.js +28 -9
  93. package/dist/chunks/bridge-D7qrHhV0.js +0 -2
  94. package/dist/chunks/browser-modular-C4LFxfoS.js +0 -2
  95. package/dist/chunks/index-C7lqD4mo.js +0 -2
  96. package/dist/chunks/index-CAPhqA6k.js +0 -2
  97. package/dist/chunks/index-CLdRyZaP.js +0 -2
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @lokascript/core
1
+ # @hyperfixi/core
2
2
 
3
3
  An experimental hyperscript engine that provides fast parsing, command execution, and comprehensive error handling for web applications. Built with TypeScript-first design.
4
4
 
@@ -7,7 +7,7 @@ An experimental hyperscript engine that provides fast parsing, command execution
7
7
  - 🎯 **\_hyperscript Compatible** - Tested via gallery examples, bundle compatibility matrix, and command/expression browser tests
8
8
  - 🚀 **High Performance** - Optimized tokenizer and parser for large expressions
9
9
  - 🔧 **TypeScript First** - Complete type safety with comprehensive type definitions
10
- - 🧪 **Thoroughly Tested** - 2800+ tests with 98.5%+ reliability
10
+ - 🧪 **Thoroughly Tested** - Vitest unit suites plus Playwright browser tests
11
11
  - 🌊 **Complete Command System** - All major commands implemented (PUT, SET, ADD, SHOW/HIDE, etc.)
12
12
  - ⚡ **HTML Integration** - Automatic `_=""` attribute processing and event binding
13
13
  - 🛡️ **Error Recovery** - Graceful handling of syntax errors with helpful guidance
@@ -15,26 +15,26 @@ An experimental hyperscript engine that provides fast parsing, command execution
15
15
  ## Installation
16
16
 
17
17
  ```bash
18
- npm install @lokascript/core
18
+ npm install @hyperfixi/core
19
19
  # or
20
- yarn add @lokascript/core
20
+ yarn add @hyperfixi/core
21
21
  ```
22
22
 
23
23
  ## Quick Start
24
24
 
25
25
  ```typescript
26
- import { hyperscript } from '@lokascript/core';
26
+ import { hyperscript } from '@hyperfixi/core';
27
27
 
28
28
  // Simple expression evaluation
29
- const result = await hyperscript.run('5 + 3 * 2'); // Returns 11
29
+ const result = await hyperscript.eval('5 + 3 * 2'); // Returns 11
30
30
 
31
31
  // DOM manipulation with commands
32
32
  const button = document.getElementById('myButton');
33
33
  const context = hyperscript.createContext(button);
34
34
 
35
- await hyperscript.run('hide me', context); // Hides the button
36
- await hyperscript.run('put "Hello World" into my innerHTML', context);
37
- await hyperscript.run('set my className to "active"', context);
35
+ await hyperscript.eval('hide me', context); // Hides the button
36
+ await hyperscript.eval('put "Hello World" into my innerHTML', context);
37
+ await hyperscript.eval('set my className to "active"', context);
38
38
  ```
39
39
 
40
40
  ### HTML Integration (Automatic)
@@ -52,18 +52,18 @@ await hyperscript.run('set my className to "active"', context);
52
52
 
53
53
  ## Debugging
54
54
 
55
- LokaScript includes a built-in debug control API for troubleshooting compilation and execution issues.
55
+ HyperFixi includes a built-in debug control API for troubleshooting compilation and execution issues.
56
56
 
57
57
  ### Enable Debug Logging
58
58
 
59
59
  ```javascript
60
60
  // In browser console
61
- lokascript.debugControl.enable(); // Enable detailed logging
61
+ hyperfixi.debugControl.enable(); // Enable detailed logging
62
62
  // Reload page to see logs
63
63
 
64
- lokascript.debugControl.disable(); // Disable logging
65
- lokascript.debugControl.isEnabled(); // Check if enabled
66
- lokascript.debugControl.status(); // Get detailed status
64
+ hyperfixi.debugControl.disable(); // Disable logging
65
+ hyperfixi.debugControl.isEnabled(); // Check if enabled
66
+ hyperfixi.debugControl.status(); // Get detailed status
67
67
  ```
68
68
 
69
69
  Debug settings persist across page reloads via localStorage. Logs include:
@@ -94,13 +94,13 @@ that fell back to its English rendering — reports.
94
94
 
95
95
  ## Runtime Hooks
96
96
 
97
- LokaScript provides a hooks system for observing and intercepting command execution:
97
+ HyperFixi provides a hooks system for observing and intercepting command execution:
98
98
 
99
99
  ```javascript
100
- import { HookRegistry, createHooks } from '@lokascript/core';
100
+ import { hyperscript } from '@hyperfixi/core';
101
101
 
102
102
  // Create hooks for logging, analytics, or debugging
103
- const hooks = createHooks({
103
+ const hooks = {
104
104
  beforeExecute: ctx => {
105
105
  console.log(`Executing: ${ctx.commandName}`);
106
106
  },
@@ -115,34 +115,26 @@ const hooks = createHooks({
115
115
  // Return true to skip command execution
116
116
  return name === 'disabled-command';
117
117
  },
118
- });
118
+ };
119
119
 
120
120
  // Register hooks with the runtime
121
- lokascript.registerHooks('my-hooks', hooks);
121
+ hyperscript.registerHooks('my-hooks', hooks);
122
122
  ```
123
123
 
124
- Built-in hook utilities:
125
-
126
- - `loggingHooks()` - Pre-configured debug logging hooks
127
- - `createTimingHooks()` - Performance timing hooks
128
-
129
124
  ## Cleanup & Memory Management
130
125
 
131
126
  The runtime automatically tracks event listeners and observers for cleanup when elements are removed from the DOM. You can also manually trigger cleanup:
132
127
 
133
128
  ```javascript
134
- // Clean up a specific element
135
- lokascript.cleanup(element);
136
-
137
- // Clean up element and all descendants
138
- lokascript.cleanupTree(containerElement);
129
+ // Clean up an element and all its descendants
130
+ hyperscript.cleanup(containerElement);
139
131
 
140
132
  // Get cleanup statistics
141
- const stats = lokascript.getCleanupStats();
133
+ const stats = hyperscript.getDefaultRuntime().getCleanupStats();
142
134
  // { elementsTracked: 5, listeners: 12, observers: 2, ... }
143
135
 
144
136
  // Full runtime shutdown
145
- lokascript.destroy();
137
+ hyperscript.getDefaultRuntime().destroy();
146
138
  ```
147
139
 
148
140
  ## API Reference
@@ -153,12 +145,12 @@ For complete API documentation, see [API.md](./docs/API.md).
153
145
 
154
146
  - `hyperscript.compile(code)` - Compile hyperscript to AST
155
147
  - `hyperscript.execute(ast, context)` - Execute compiled AST
156
- - `hyperscript.run(code, context)` - Compile and execute in one step
148
+ - `hyperscript.eval(code, context)` - Compile and execute in one step
157
149
  - `hyperscript.createContext(element)` - Create execution context
158
- - `evalHyperScript(code, context)` - \_hyperscript compatibility API
150
+ - `evalHyperScript(code, context)` - \_hyperscript compatibility API (a global in the `hyperfixi.js` browser bundle)
159
151
  - `hyperscript.registerHooks(name, hooks)` - Register runtime hooks
160
- - `hyperscript.cleanup(element)` - Clean up element resources
161
- - `hyperscript.destroy()` - Full runtime shutdown
152
+ - `hyperscript.cleanup(element)` - Clean up an element and its descendants
153
+ - `hyperscript.getDefaultRuntime().destroy()` - Full runtime shutdown
162
154
 
163
155
  ## Supported Features
164
156
 
@@ -193,7 +185,7 @@ See [EXAMPLES.md](./docs/EXAMPLES.md) for comprehensive usage examples.
193
185
 
194
186
  ## Compatibility Testing
195
187
 
196
- This package includes compatibility tests that validate LokaScript against the official \_hyperscript library:
188
+ This package includes compatibility tests that validate HyperFixi against the official \_hyperscript library:
197
189
 
198
190
  ```bash
199
191
  # Run compatibility tests with official hyperscript test suite
@@ -58,7 +58,7 @@ export declare const COMMAND_SLOTS: {
58
58
  readonly throw: readonly [];
59
59
  readonly toggle: readonly ["as", "between", "for", "from", "on", "until"];
60
60
  readonly unless: readonly [];
61
- readonly transition: readonly ["on", "over", "to", "with"];
61
+ readonly transition: readonly ["from", "on", "over", "pairs", "to", "using", "with"];
62
62
  readonly trigger: readonly ["on", "to", "with"];
63
63
  readonly wait: readonly [];
64
64
  };
@@ -60,6 +60,7 @@ export interface CallExpressionNode extends BaseNode {
60
60
  readonly callee: Expr;
61
61
  readonly arguments: Expr[];
62
62
  readonly isConstructor?: boolean;
63
+ readonly closestTo?: Expr;
63
64
  }
64
65
  export interface MemberExpressionNode extends BaseNode {
65
66
  readonly type: 'memberExpression';
@@ -182,6 +183,7 @@ export interface EventHandlerNode extends BaseNode {
182
183
  readonly events?: string[];
183
184
  readonly condition?: Expr;
184
185
  readonly target?: string;
186
+ readonly targetExpression?: Expr;
185
187
  readonly selector?: string;
186
188
  readonly args?: string[];
187
189
  readonly attributeName?: string;
@@ -2254,6 +2254,14 @@ function convertNode(node, infer) {
2254
2254
  ...pos(node),
2255
2255
  };
2256
2256
  case 'identifier':
2257
+ if (node.scope === 'element' || node.scope === 'local' || node.scope === 'global') {
2258
+ return {
2259
+ type: 'variable',
2260
+ name: (node.name ?? ''),
2261
+ scope: node.scope,
2262
+ ...pos(node),
2263
+ };
2264
+ }
2257
2265
  return {
2258
2266
  type: 'identifier',
2259
2267
  value: (node.name ?? node.value ?? ''),
@@ -2276,6 +2284,15 @@ function convertNode(node, infer) {
2276
2284
  operand: convertNode(node.argument ?? node.operand, infer),
2277
2285
  ...pos(node),
2278
2286
  };
2287
+ case 'typeCheckExpression':
2288
+ return {
2289
+ type: 'typeCheck',
2290
+ value: convertNode(node.value, infer),
2291
+ typeName: String(node.typeName),
2292
+ nullOk: node.nullOk !== false,
2293
+ negated: node.negated === true,
2294
+ ...pos(node),
2295
+ };
2279
2296
  case 'timeExpression':
2280
2297
  return { type: 'literal', value: node.value, ...pos(node) };
2281
2298
  case 'templateLiteral':
@@ -2287,6 +2304,28 @@ function convertNode(node, infer) {
2287
2304
  scope: (node.scope ?? 'local'),
2288
2305
  ...pos(node),
2289
2306
  };
2307
+ case 'arrayLiteral':
2308
+ return {
2309
+ type: 'array',
2310
+ elements: (node.elements ?? []).map(element => convertNode(element, infer)),
2311
+ ...pos(node),
2312
+ };
2313
+ case 'attributeAccess':
2314
+ return {
2315
+ type: 'possessive',
2316
+ object: { type: 'identifier', value: 'me' },
2317
+ property: `@${node.attributeName}`,
2318
+ ...pos(node),
2319
+ };
2320
+ case 'propertyOfExpression': {
2321
+ const property = node.property;
2322
+ return {
2323
+ type: 'possessive',
2324
+ object: convertNode(node.target, infer),
2325
+ property: (property?.name ?? property?.value ?? ''),
2326
+ ...pos(node),
2327
+ };
2328
+ }
2290
2329
  case 'htmlSelector':
2291
2330
  return {
2292
2331
  type: 'selector',
@@ -2338,6 +2377,21 @@ function convertCommand$1(node, infer) {
2338
2377
  if (name === 'repeat') {
2339
2378
  return convertRepeatCommand(node, infer);
2340
2379
  }
2380
+ const original = node.originalCommand;
2381
+ const sum = node.modifiers?.to;
2382
+ const counted = node.args?.[0];
2383
+ if (name === 'set' &&
2384
+ (original === 'increment' || original === 'decrement') &&
2385
+ sum?.type === 'binaryExpression' &&
2386
+ counted) {
2387
+ return convertCommand$1({
2388
+ ...node,
2389
+ name: original,
2390
+ args: [counted],
2391
+ modifiers: { by: sum.right },
2392
+ originalCommand: undefined,
2393
+ }, infer);
2394
+ }
2341
2395
  const args = (node.args ?? []).map(arg => convertNode(arg, infer));
2342
2396
  const target = node.target ? convertNode(node.target, infer) : undefined;
2343
2397
  const modifiers = node.modifiers
@@ -2386,6 +2440,64 @@ function convertIfCommand(node, infer) {
2386
2440
  };
2387
2441
  }
2388
2442
  function convertRepeatCommand(node, infer) {
2443
+ const slots = (node.modifiers ?? {});
2444
+ const form = slotText(slots.loopType);
2445
+ if (form === undefined)
2446
+ return convertPositionalRepeat(node, infer);
2447
+ const [bodyBlock, elseBlock] = (node.args ?? []);
2448
+ const body = extractBlockCommands(bodyBlock, infer);
2449
+ const indexName = slotText(slots.index);
2450
+ const shared = {
2451
+ ...(indexName ? { indexName } : {}),
2452
+ ...(elseBlock ? { elseBody: extractBlockCommands(elseBlock, infer) } : {}),
2453
+ ...pos(node),
2454
+ };
2455
+ const operand = (slot) => (slot ? convertNode(slot, infer) : undefined);
2456
+ switch (form) {
2457
+ case 'for':
2458
+ return {
2459
+ type: 'foreach',
2460
+ itemName: slotText(slots.for) ?? 'it',
2461
+ collection: operand(slots.in) ?? { type: 'literal', value: null },
2462
+ body,
2463
+ ...shared,
2464
+ };
2465
+ case 'times':
2466
+ return { type: 'repeat', count: operand(slots.times), body, ...shared };
2467
+ case 'while':
2468
+ case 'until': {
2469
+ const test = operand(slots[form]) ?? { type: 'literal', value: form === 'until' };
2470
+ return {
2471
+ type: 'while',
2472
+ condition: form === 'until' ? { type: 'unary', operator: 'not', operand: test } : test,
2473
+ ...(slots.bottomTested?.value === true ? { bottomTested: true } : {}),
2474
+ body,
2475
+ ...shared,
2476
+ };
2477
+ }
2478
+ case 'until-event': {
2479
+ const event = slotText(slots.event);
2480
+ if (!event) {
2481
+ return { type: 'error', message: "'repeat until event' has no event name", ...pos(node) };
2482
+ }
2483
+ const target = operand(slots.from);
2484
+ return {
2485
+ type: 'repeat',
2486
+ untilEvent: event,
2487
+ ...(target ? { untilEventTarget: target } : {}),
2488
+ body,
2489
+ ...shared,
2490
+ };
2491
+ }
2492
+ default:
2493
+ return { type: 'repeat', body, ...shared };
2494
+ }
2495
+ }
2496
+ function slotText(slot) {
2497
+ const text = slot?.value ?? slot?.name;
2498
+ return typeof text === 'string' ? text : undefined;
2499
+ }
2500
+ function convertPositionalRepeat(node, infer) {
2389
2501
  const args = (node.args ?? []);
2390
2502
  if (args.length === 0) {
2391
2503
  return { type: 'repeat', body: [], ...pos(node) };
@@ -2702,6 +2814,15 @@ function toCoreAST(node) {
2702
2814
  prefix: true,
2703
2815
  ...nodePos(node),
2704
2816
  };
2817
+ case 'typeCheck':
2818
+ return {
2819
+ type: 'typeCheckExpression',
2820
+ value: toCoreAST(node.value),
2821
+ typeName: node.typeName,
2822
+ nullOk: node.nullOk,
2823
+ negated: node.negated,
2824
+ ...nodePos(node),
2825
+ };
2705
2826
  case 'member':
2706
2827
  return {
2707
2828
  type: 'memberExpression',
@@ -2712,6 +2833,12 @@ function toCoreAST(node) {
2712
2833
  computed: node.computed ?? false,
2713
2834
  ...nodePos(node),
2714
2835
  };
2836
+ case 'array':
2837
+ return {
2838
+ type: 'arrayLiteral',
2839
+ elements: node.elements.map(element => toCoreAST(element)),
2840
+ ...nodePos(node),
2841
+ };
2715
2842
  case 'possessive':
2716
2843
  return {
2717
2844
  type: 'possessiveExpression',
@@ -2774,72 +2901,85 @@ function convertCommand(node) {
2774
2901
  }
2775
2902
  return result;
2776
2903
  }
2904
+ function block(nodes) {
2905
+ return { type: 'block', commands: nodes.map(n => toCoreAST(n)), ...POS };
2906
+ }
2777
2907
  function convertIf(node) {
2778
- const result = {
2908
+ const [elseIf, ...laterElseIfs] = node.elseIfBranches ?? [];
2909
+ const elseBranch = elseIf
2910
+ ? [
2911
+ {
2912
+ type: 'if',
2913
+ condition: elseIf.condition,
2914
+ thenBranch: elseIf.body,
2915
+ elseIfBranches: laterElseIfs,
2916
+ ...(node.elseBranch ? { elseBranch: node.elseBranch } : {}),
2917
+ },
2918
+ ]
2919
+ : node.elseBranch;
2920
+ return {
2779
2921
  type: 'command',
2780
2922
  name: 'if',
2781
2923
  isBlocking: true,
2782
- condition: toCoreAST(node.condition),
2783
- thenBranch: node.thenBranch.map(n => toCoreAST(n)),
2784
- args: [],
2924
+ args: [
2925
+ toCoreAST(node.condition),
2926
+ block(node.thenBranch),
2927
+ ...(elseBranch ? [block(elseBranch)] : []),
2928
+ ],
2785
2929
  ...nodePos(node),
2786
2930
  };
2787
- if (node.elseBranch) {
2788
- result.elseBranch = node.elseBranch.map(n => toCoreAST(n));
2789
- }
2790
- return result;
2791
2931
  }
2792
- function convertRepeat(node) {
2793
- const result = {
2932
+ function loopCommand(node, form, operands) {
2933
+ const modifiers = { loopType: slotString(form) };
2934
+ for (const [slot, value] of Object.entries(operands)) {
2935
+ if (value)
2936
+ modifiers[slot] = value;
2937
+ }
2938
+ if (node.indexName)
2939
+ modifiers.index = slotString(node.indexName);
2940
+ return {
2794
2941
  type: 'command',
2795
2942
  name: 'repeat',
2796
2943
  isBlocking: true,
2797
- body: node.body.map(n => toCoreAST(n)),
2798
- args: [],
2944
+ args: [block(node.body), ...(node.elseBody ? [block(node.elseBody)] : [])],
2945
+ modifiers,
2799
2946
  ...nodePos(node),
2800
2947
  };
2948
+ }
2949
+ function slotString(value) {
2950
+ return { type: 'string', value, ...POS };
2951
+ }
2952
+ function convertRepeat(node) {
2801
2953
  if (node.count !== undefined) {
2802
- result.count =
2803
- typeof node.count === 'number'
2804
- ? { type: 'literal', value: node.count, raw: String(node.count), ...POS }
2805
- : toCoreAST(node.count);
2806
- result.loopVariant = 'times';
2807
- }
2808
- else if (node.whileCondition !== undefined) {
2809
- result.condition = toCoreAST(node.whileCondition);
2810
- result.loopVariant = 'while';
2811
- }
2812
- else if (node.untilEvent !== undefined) {
2813
- result.untilEvent = node.untilEvent;
2814
- result.loopVariant = 'until';
2954
+ const times = typeof node.count === 'number'
2955
+ ? { type: 'literal', value: node.count, raw: String(node.count), ...POS }
2956
+ : toCoreAST(node.count);
2957
+ return loopCommand(node, 'times', { times });
2958
+ }
2959
+ if (node.whileCondition !== undefined) {
2960
+ return loopCommand(node, 'while', { while: toCoreAST(node.whileCondition) });
2961
+ }
2962
+ if (node.untilEvent !== undefined) {
2963
+ return loopCommand(node, 'until-event', {
2964
+ event: slotString(node.untilEvent),
2965
+ from: node.untilEventTarget ? toCoreAST(node.untilEventTarget) : undefined,
2966
+ });
2815
2967
  }
2816
- return result;
2968
+ return loopCommand(node, 'forever', {});
2817
2969
  }
2818
2970
  function convertForEach(node) {
2819
- return {
2820
- type: 'command',
2821
- name: 'repeat',
2822
- isBlocking: true,
2823
- loopVariant: 'for',
2824
- itemName: node.itemName,
2825
- ...(node.indexName ? { indexName: node.indexName } : {}),
2826
- collection: toCoreAST(node.collection),
2827
- body: node.body.map(n => toCoreAST(n)),
2828
- args: [],
2829
- ...nodePos(node),
2830
- };
2971
+ return loopCommand(node, 'for', {
2972
+ for: slotString(node.itemName),
2973
+ in: toCoreAST(node.collection),
2974
+ });
2831
2975
  }
2832
2976
  function convertWhile(node) {
2833
- return {
2834
- type: 'command',
2835
- name: 'repeat',
2836
- isBlocking: true,
2837
- loopVariant: 'while',
2838
- condition: toCoreAST(node.condition),
2839
- body: node.body.map(n => toCoreAST(n)),
2840
- args: [],
2841
- ...nodePos(node),
2842
- };
2977
+ return loopCommand(node, 'while', {
2978
+ while: toCoreAST(node.condition),
2979
+ bottomTested: node.bottomTested
2980
+ ? { type: 'literal', value: true, raw: 'true', ...POS }
2981
+ : undefined,
2982
+ });
2843
2983
  }
2844
2984
  function flattenEventModifiers(modifiers) {
2845
2985
  if (!modifiers)
@@ -3243,6 +3383,9 @@ function getChildren(node) {
3243
3383
  case 'unary':
3244
3384
  children.push(node.operand);
3245
3385
  break;
3386
+ case 'typeCheck':
3387
+ children.push(node.value);
3388
+ break;
3246
3389
  case 'member': {
3247
3390
  const m = node;
3248
3391
  children.push(m.object);
@@ -3380,6 +3523,8 @@ function friendlyTypeName(node) {
3380
3523
  return `Binary Expression (${node.operator})`;
3381
3524
  case 'unary':
3382
3525
  return `Unary Expression (${node.operator})`;
3526
+ case 'typeCheck':
3527
+ return `Type Check (${node.typeName})`;
3383
3528
  case 'literal':
3384
3529
  return 'Literal';
3385
3530
  case 'identifier':