rip-lang 3.8.9 → 3.9.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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rip-lang",
3
- "version": "3.8.9",
3
+ "version": "3.9.0",
4
4
  "description": "A modern language that compiles to JavaScript",
5
5
  "type": "module",
6
6
  "main": "src/compiler.js",
@@ -67,7 +67,7 @@
67
67
  "author": "Steve Shreeve <steve.shreeve@gmail.com>",
68
68
  "license": "MIT",
69
69
  "devDependencies": {
70
- "@rip-lang/api": "workspace:*",
71
- "@rip-lang/ui": "workspace:*"
70
+ "@rip-lang/api": "1.1.5",
71
+ "@rip-lang/ui": "0.2.0"
72
72
  }
73
73
  }
package/src/compiler.js CHANGED
@@ -822,7 +822,6 @@ export class CodeGenerator {
822
822
  let ctrlOp = str(rawCtrlOp);
823
823
  let isReturn = ctrlSexpr[0] === 'return';
824
824
  let targetCode = this.generate(target, 'value');
825
- if (typeof target === 'string') this.programVars.add(target);
826
825
  let exprCode = this.generate(expr, 'value');
827
826
  let ctrlValue = ctrlSexpr.length > 1 ? ctrlSexpr[1] : null;
828
827
  let ctrlCode = isReturn
@@ -3254,7 +3253,7 @@ export class Compiler {
3254
3253
  // Component Support (prototype installation)
3255
3254
  // =============================================================================
3256
3255
 
3257
- installComponentSupport(CodeGenerator);
3256
+ installComponentSupport(CodeGenerator, Lexer);
3258
3257
 
3259
3258
  // =============================================================================
3260
3259
  // Type Support (enum generator)
package/src/components.js CHANGED
@@ -1,8 +1,9 @@
1
1
  // Component System — Fine-grained reactive components for Rip
2
2
  //
3
- // Architecture: installComponentSupport(CodeGenerator) adds methods to the
4
- // CodeGenerator prototype, enabling component compilation. A separate
5
- // getComponentRuntime() emits runtime helpers only when components are used.
3
+ // Architecture: installComponentSupport(CodeGenerator, Lexer) adds methods to
4
+ // both prototypes — render rewriting on the Lexer, component code generation
5
+ // on the CodeGenerator. A separate getComponentRuntime() emits runtime helpers
6
+ // only when components are used.
6
7
  //
7
8
  // Naming: All render-tree generators use generate* (consistent with compiler).
8
9
 
@@ -54,7 +55,354 @@ function getMemberName(target) {
54
55
  // Prototype Installation
55
56
  // ============================================================================
56
57
 
57
- export function installComponentSupport(CodeGenerator) {
58
+ export function installComponentSupport(CodeGenerator, Lexer) {
59
+
60
+ // ==========================================================================
61
+ // Lexer: Render block rewriter
62
+ // ==========================================================================
63
+ // Transforms template syntax inside render blocks:
64
+ // - Implicit div for class-only selectors: .card → div.card
65
+ // - Combine #id selectors: div # main → div#main
66
+ // - Two-way binding: value <=> username → __bind_value__: username
67
+ // - Event modifiers: @click.prevent: → [@click.prevent]:
68
+ // - Dynamic classes: div.('card', x && 'active') → div.__clsx(...)
69
+ // - Implicit nesting: inject -> before INDENT for template elements
70
+ // - Hyphenated attributes: data-foo: "x" → "data-foo": "x"
71
+ // ==========================================================================
72
+
73
+ Lexer.prototype.rewriteRender = function() {
74
+ let gen = (tag, val, origin) => {
75
+ let t = [tag, val];
76
+ t.pre = 0;
77
+ t.data = null;
78
+ t.loc = origin?.loc ?? {r: 0, c: 0, n: 0};
79
+ t.spaced = false;
80
+ t.newLine = false;
81
+ t.generated = true;
82
+ if (origin) t.origin = origin;
83
+ return t;
84
+ };
85
+
86
+ let inRender = false;
87
+ let renderIndentLevel = 0;
88
+ let currentIndent = 0;
89
+ let pendingCallEnds = [];
90
+
91
+ let isHtmlTag = (name) => {
92
+ let tagPart = name.split('#')[0];
93
+ return TEMPLATE_TAGS.has(tagPart);
94
+ };
95
+
96
+ let isComponent = (name) => {
97
+ if (!name || typeof name !== 'string') return false;
98
+ return /^[A-Z]/.test(name);
99
+ };
100
+
101
+ let isTemplateTag = (name) => {
102
+ return isHtmlTag(name) || isComponent(name);
103
+ };
104
+
105
+ let startsWithTag = (tokens, i) => {
106
+ let j = i;
107
+ while (j > 0) {
108
+ let pt = tokens[j - 1][0];
109
+ if (pt === 'TERMINATOR' || pt === 'RENDER') {
110
+ break;
111
+ }
112
+ if (pt === 'INDENT' || pt === 'OUTDENT') {
113
+ let jt = tokens[j][0];
114
+ if (jt === 'CALL_END' || jt === ')') {
115
+ let open = jt === 'CALL_END' ? 'CALL_START' : '(';
116
+ let depth = 1;
117
+ let k = j - 1;
118
+ while (k >= 0 && depth > 0) {
119
+ let kt = tokens[k][0];
120
+ if (kt === jt) depth++;
121
+ else if (kt === open) depth--;
122
+ if (depth > 0) k--;
123
+ }
124
+ j = k;
125
+ continue;
126
+ }
127
+ break;
128
+ }
129
+ if (pt === 'CALL_END' || pt === ')') {
130
+ let open = pt === 'CALL_END' ? 'CALL_START' : '(';
131
+ let depth = 1;
132
+ let k = j - 2;
133
+ while (k >= 0 && depth > 0) {
134
+ let kt = tokens[k][0];
135
+ if (kt === 'CALL_END' || kt === ')') depth++;
136
+ else if (kt === 'CALL_START' || kt === '(') depth--;
137
+ if (depth > 0) k--;
138
+ }
139
+ j = k;
140
+ continue;
141
+ }
142
+ j--;
143
+ }
144
+ return tokens[j] && tokens[j][0] === 'IDENTIFIER' && isTemplateTag(tokens[j][1]);
145
+ };
146
+
147
+ this.scanTokens(function(token, i, tokens) {
148
+ let tag = token[0];
149
+ let nextToken = i < tokens.length - 1 ? tokens[i + 1] : null;
150
+
151
+ // Track entering render blocks
152
+ if (tag === 'RENDER') {
153
+ inRender = true;
154
+ renderIndentLevel = currentIndent + 1;
155
+ return 1;
156
+ }
157
+
158
+ // Track indentation
159
+ if (tag === 'INDENT') {
160
+ currentIndent++;
161
+ return 1;
162
+ }
163
+
164
+ if (tag === 'OUTDENT') {
165
+ currentIndent--;
166
+
167
+ // Insert pending CALL_END(s) after this OUTDENT
168
+ let inserted = 0;
169
+ while (pendingCallEnds.length > 0 && pendingCallEnds[pendingCallEnds.length - 1] > currentIndent) {
170
+ let callEndToken = gen('CALL_END', ')', token);
171
+ tokens.splice(i + 1 + inserted, 0, callEndToken);
172
+ pendingCallEnds.pop();
173
+ inserted++;
174
+ }
175
+
176
+ // Exit render block when we outdent past where it started
177
+ if (inRender && currentIndent < renderIndentLevel) {
178
+ inRender = false;
179
+ }
180
+ return 1 + inserted;
181
+ }
182
+
183
+ // Only process if we're inside a render block
184
+ if (!inRender) return 1;
185
+
186
+ // ─────────────────────────────────────────────────────────────────────
187
+ // Hyphenated attributes
188
+ // data-lucide: "search" → "data-lucide": "search"
189
+ // ─────────────────────────────────────────────────────────────────────
190
+ if (tag === 'IDENTIFIER' && !token.spaced) {
191
+ let parts = [token[1]];
192
+ let j = i + 1;
193
+ while (j + 1 < tokens.length) {
194
+ let hyphen = tokens[j];
195
+ let nextPart = tokens[j + 1];
196
+ if (hyphen[0] === '-' && !hyphen.spaced &&
197
+ (nextPart[0] === 'IDENTIFIER' || nextPart[0] === 'PROPERTY')) {
198
+ parts.push(nextPart[1]);
199
+ j += 2;
200
+ if (nextPart[0] === 'PROPERTY') break;
201
+ } else {
202
+ break;
203
+ }
204
+ }
205
+ if (parts.length > 1 && j > i + 1 && tokens[j - 1][0] === 'PROPERTY') {
206
+ token[0] = 'STRING';
207
+ token[1] = `"${parts.join('-')}"`;
208
+ tokens.splice(i + 1, j - i - 1);
209
+ return 1;
210
+ }
211
+ }
212
+
213
+ // ─────────────────────────────────────────────────────────────────────
214
+ // Implicit div for class-only selectors
215
+ // .card → div.card
216
+ // ─────────────────────────────────────────────────────────────────────
217
+ if (tag === '.') {
218
+ let prevToken = i > 0 ? tokens[i - 1] : null;
219
+ let prevTag = prevToken ? prevToken[0] : null;
220
+ if (prevTag === 'INDENT' || prevTag === 'TERMINATOR') {
221
+ if (nextToken && nextToken[0] === 'PROPERTY') {
222
+ let divToken = gen('IDENTIFIER', 'div', token);
223
+ tokens.splice(i, 0, divToken);
224
+ return 2;
225
+ }
226
+ }
227
+ }
228
+
229
+ // ─────────────────────────────────────────────────────────────────────
230
+ // Combine #id selectors
231
+ // div # main → div#main
232
+ // ─────────────────────────────────────────────────────────────────────
233
+ if (tag === 'IDENTIFIER' || tag === 'PROPERTY') {
234
+ let next = tokens[i + 1];
235
+ let nextNext = tokens[i + 2];
236
+ if (next && next[0] === '#' && nextNext && (nextNext[0] === 'PROPERTY' || nextNext[0] === 'IDENTIFIER')) {
237
+ token[1] = token[1] + '#' + nextNext[1];
238
+ if (nextNext.spaced) token.spaced = true;
239
+ tokens.splice(i + 1, 2);
240
+ return 1;
241
+ }
242
+ }
243
+
244
+ // ─────────────────────────────────────────────────────────────────────
245
+ // Two-way binding
246
+ // value <=> username → __bind_value__: username
247
+ // ─────────────────────────────────────────────────────────────────────
248
+ if (tag === 'BIND') {
249
+ let prevToken = i > 0 ? tokens[i - 1] : null;
250
+ let nextBindToken = tokens[i + 1];
251
+ if (prevToken && (prevToken[0] === 'IDENTIFIER' || prevToken[0] === 'PROPERTY') &&
252
+ nextBindToken && nextBindToken[0] === 'IDENTIFIER') {
253
+ prevToken[1] = `__bind_${prevToken[1]}__`;
254
+ token[0] = ':';
255
+ token[1] = ':';
256
+ return 1;
257
+ }
258
+ }
259
+
260
+ // ─────────────────────────────────────────────────────────────────────
261
+ // Event modifiers
262
+ // @click.prevent: handler → [@click.prevent]: handler
263
+ // ─────────────────────────────────────────────────────────────────────
264
+ if (tag === '@') {
265
+ let j = i + 1;
266
+ if (j < tokens.length && tokens[j][0] === 'PROPERTY') {
267
+ j++;
268
+ while (j + 1 < tokens.length && tokens[j][0] === '.' && tokens[j + 1][0] === 'PROPERTY') {
269
+ j += 2;
270
+ }
271
+ if (j > i + 2 && j < tokens.length && tokens[j][0] === ':') {
272
+ let openBracket = gen('[', '[', token);
273
+ tokens.splice(i, 0, openBracket);
274
+ let closeBracket = gen(']', ']', tokens[j + 1]);
275
+ tokens.splice(j + 1, 0, closeBracket);
276
+ return 2;
277
+ }
278
+ }
279
+ }
280
+
281
+ // ─────────────────────────────────────────────────────────────────────
282
+ // Dynamic classes
283
+ // div.('card', x && 'active') → div.__clsx('card', x && 'active')
284
+ // .('card') → div.__clsx('card')
285
+ // ─────────────────────────────────────────────────────────────────────
286
+ if (tag === '.' && nextToken && nextToken[0] === '(') {
287
+ let prevToken = i > 0 ? tokens[i - 1] : null;
288
+ let prevTag = prevToken ? prevToken[0] : null;
289
+ let atLineStart = prevTag === 'INDENT' || prevTag === 'TERMINATOR';
290
+
291
+ let cxToken = gen('PROPERTY', '__clsx', token);
292
+ nextToken[0] = 'CALL_START';
293
+ let depth = 1;
294
+ for (let j = i + 2; j < tokens.length && depth > 0; j++) {
295
+ if (tokens[j][0] === '(' || tokens[j][0] === 'CALL_START') depth++;
296
+ else if (tokens[j][0] === ')') {
297
+ depth--;
298
+ if (depth === 0) tokens[j][0] = 'CALL_END';
299
+ } else if (tokens[j][0] === 'CALL_END') depth--;
300
+ }
301
+
302
+ if (atLineStart) {
303
+ let divToken = gen('IDENTIFIER', 'div', token);
304
+ tokens.splice(i, 0, divToken);
305
+ tokens.splice(i + 2, 0, cxToken);
306
+ return 3;
307
+ } else {
308
+ tokens.splice(i + 1, 0, cxToken);
309
+ return 2;
310
+ }
311
+ }
312
+
313
+ // ─────────────────────────────────────────────────────────────────────
314
+ // Implicit nesting (inject -> before INDENT)
315
+ // ─────────────────────────────────────────────────────────────────────
316
+ if (nextToken && nextToken[0] === 'INDENT') {
317
+ if (tag === '->' || tag === '=>' || tag === 'CALL_START' || tag === '(') {
318
+ return 1;
319
+ }
320
+
321
+ let isTemplateElement = false;
322
+ let prevTag = i > 0 ? tokens[i - 1][0] : null;
323
+ let isAfterControlFlow = prevTag === 'IF' || prevTag === 'UNLESS' || prevTag === 'WHILE' || prevTag === 'UNTIL' || prevTag === 'WHEN';
324
+
325
+ // Detect __clsx CALL_END early — OUTDENT tokens inside multi-line .()
326
+ // args prevent startsWithTag from seeing the template tag, so we check
327
+ // for __clsx ownership first by counting balanced CALL_START/CALL_END.
328
+ let isClsxCallEnd = false;
329
+ if (tag === 'CALL_END') {
330
+ let depth = 1;
331
+ for (let j = i - 1; j >= 0 && depth > 0; j--) {
332
+ if (tokens[j][0] === 'CALL_END') depth++;
333
+ else if (tokens[j][0] === 'CALL_START') {
334
+ depth--;
335
+ if (depth === 0 && j > 0 && tokens[j - 1][0] === 'PROPERTY' && tokens[j - 1][1] === '__clsx') {
336
+ isClsxCallEnd = true;
337
+ }
338
+ }
339
+ }
340
+ }
341
+
342
+ if (isClsxCallEnd) {
343
+ isTemplateElement = true;
344
+ } else if (tag === 'IDENTIFIER' && isTemplateTag(token[1]) && !isAfterControlFlow) {
345
+ isTemplateElement = true;
346
+ } else if (tag === 'PROPERTY' || tag === 'STRING' || tag === 'CALL_END' || tag === ')') {
347
+ isTemplateElement = startsWithTag(tokens, i);
348
+ }
349
+ else if (tag === 'IDENTIFIER' && i > 1 && tokens[i - 1][0] === '...') {
350
+ if (startsWithTag(tokens, i)) {
351
+ let commaToken = gen(',', ',', token);
352
+ let arrowToken = gen('->', '->', token);
353
+ arrowToken.newLine = true;
354
+ tokens.splice(i + 1, 0, commaToken, arrowToken);
355
+ return 3;
356
+ }
357
+ }
358
+
359
+ if (isTemplateElement) {
360
+ let isClassOrIdTail = tag === 'PROPERTY' && i > 0 && (tokens[i - 1][0] === '.' || tokens[i - 1][0] === '#');
361
+
362
+ if (isClsxCallEnd) {
363
+ let callStartToken = gen('CALL_START', '(', token);
364
+ let arrowToken = gen('->', '->', token);
365
+ arrowToken.newLine = true;
366
+ tokens.splice(i + 1, 0, callStartToken, arrowToken);
367
+ pendingCallEnds.push(currentIndent + 1);
368
+ return 3;
369
+ } else if ((tag === 'IDENTIFIER' && isTemplateTag(token[1])) || isClassOrIdTail) {
370
+ // Bare tag or tag.class/tag#id (no other args): inject CALL_START -> and manage CALL_END
371
+ let callStartToken = gen('CALL_START', '(', token);
372
+ let arrowToken = gen('->', '->', token);
373
+ arrowToken.newLine = true;
374
+ tokens.splice(i + 1, 0, callStartToken, arrowToken);
375
+ pendingCallEnds.push(currentIndent + 1);
376
+ return 3;
377
+ } else {
378
+ // Tag with args: inject , -> (call wrapping handled by addImplicitBracesAndParens)
379
+ let commaToken = gen(',', ',', token);
380
+ let arrowToken = gen('->', '->', token);
381
+ arrowToken.newLine = true;
382
+ tokens.splice(i + 1, 0, commaToken, arrowToken);
383
+ return 3;
384
+ }
385
+ }
386
+ }
387
+
388
+ // ─────────────────────────────────────────────────────────────────────
389
+ // Bare component reference (PascalCase, no children, no args)
390
+ // Counter → Counter() so it gets treated as a component instantiation
391
+ // ─────────────────────────────────────────────────────────────────────
392
+ if (tag === 'IDENTIFIER' && isComponent(token[1]) &&
393
+ nextToken && (nextToken[0] === 'OUTDENT' || nextToken[0] === 'TERMINATOR')) {
394
+ tokens.splice(i + 1, 0, gen('CALL_START', '(', token), gen('CALL_END', ')', token));
395
+ return 3;
396
+ }
397
+
398
+ return 1;
399
+ });
400
+ };
401
+
402
+ // ==========================================================================
403
+ // CodeGenerator: Component compilation
404
+ // ==========================================================================
405
+
58
406
  const proto = CodeGenerator.prototype;
59
407
 
60
408
  // ==========================================================================
@@ -67,7 +415,7 @@ export function installComponentSupport(CodeGenerator) {
67
415
  */
68
416
  proto.localizeVar = function(line) {
69
417
  let result = line.replace(/this\.(_el\d+|_t\d+|_anchor\d+|_frag\d+|_slot\d+|_c\d+|_inst\d+|_empty\d+)/g, '$1');
70
- result = result.replace(/\bthis\./g, 'ctx.');
418
+ result = result.replace(/\bthis\b/g, 'ctx');
71
419
  return result;
72
420
  };
73
421
 
@@ -860,7 +1208,7 @@ export function installComponentSupport(CodeGenerator) {
860
1208
  /__effect\(\(\) => \{/g,
861
1209
  'disposers.push(__effect(() => {'
862
1210
  ).replace(
863
- /\}\);$/g,
1211
+ /\}\);$/gm,
864
1212
  '}));'
865
1213
  );
866
1214
  factoryLines.push(` ${wrappedLine}`);
@@ -979,7 +1327,7 @@ export function installComponentSupport(CodeGenerator) {
979
1327
  /__effect\(\(\) => \{/g,
980
1328
  'disposers.push(__effect(() => {'
981
1329
  ).replace(
982
- /\}\);$/g,
1330
+ /\}\);$/gm,
983
1331
  '}));'
984
1332
  );
985
1333
  factoryLines.push(` ${wrappedLine}`);