rip-lang 3.13.135 → 3.13.136

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.13.135",
3
+ "version": "3.13.136",
4
4
  "description": "A modern language that compiles to JavaScript",
5
5
  "type": "module",
6
6
  "main": "src/compiler.js",
package/src/compiler.js CHANGED
@@ -663,17 +663,6 @@ export class CodeEmitter {
663
663
  else body.push(stmt);
664
664
  }
665
665
 
666
- // Classify program-level variables for inline declarations
667
- let prevInlinePending = this._inlineVarsPending;
668
- let programInlineVars = new Set();
669
- if (this.programVars.size > 0 && body.length > 0) {
670
- let classified = this.classifyVarsForInlining(body, this.programVars);
671
- programInlineVars = classified.inlineVars;
672
- // '_' is always emitted as 'var _' (not 'let'), so must not be inlined
673
- programInlineVars.delete('_');
674
- if (programInlineVars.size > 0) this._inlineVarsPending = new Set(programInlineVars);
675
- }
676
-
677
666
  // Generate body first to detect needed helpers
678
667
  let blockStmts = ['def', 'class', 'if', 'for-in', 'for-of', 'for-as', 'while', 'loop', 'switch', 'try'];
679
668
  let stmtEntries = body.map((stmt, index) => {
@@ -700,8 +689,6 @@ export class CodeEmitter {
700
689
  });
701
690
  let statementsCode = stmtEntries.map(e => e.code).join('\n');
702
691
 
703
- this._inlineVarsPending = prevInlinePending;
704
-
705
692
  let needsBlank = false;
706
693
 
707
694
  if (imports.length > 0) {
@@ -713,7 +700,7 @@ export class CodeEmitter {
713
700
  let hasUnderscore = this.programVars.has('_');
714
701
  if (hasUnderscore) this.programVars.delete('_');
715
702
  if (this.programVars.size > 0) {
716
- let vars = Array.from(this.programVars).filter(v => !programInlineVars.has(v)).sort().join(', ');
703
+ let vars = Array.from(this.programVars).sort().join(', ');
717
704
  if (vars) {
718
705
  if (needsBlank) code += '\n';
719
706
  code += `let ${vars};\n`;
@@ -911,14 +898,6 @@ export class CodeEmitter {
911
898
  if (ctrlOp === '||') return `(() => { const __v = ${exprCode}; if (!__v) ${ctrlCode}; return (${targetCode} = __v); })()`;
912
899
  return `(() => { const __v = ${exprCode}; if (__v) ${ctrlCode}; return (${targetCode} = __v); })()`;
913
900
  }
914
- // Inline `let` for control flow — split into declaration + guard
915
- let tgtName = (typeof target === 'string') ? target : (target instanceof String) ? str(target) : null;
916
- if (tgtName && context === 'statement' && this._inlineVarsPending?.delete(tgtName)) {
917
- let ind = this.indent();
918
- if (ctrlOp === '??') return `let ${targetCode} = ${exprCode};\n${ind}if (${targetCode} == null) ${ctrlCode}`;
919
- if (ctrlOp === '||') return `let ${targetCode} = ${exprCode};\n${ind}if (!${targetCode}) ${ctrlCode}`;
920
- return `let ${targetCode} = ${exprCode};\n${ind}if (${targetCode}) ${ctrlCode}`;
921
- }
922
901
  if (ctrlOp === '??') return `if ((${targetCode} = ${exprCode}) == null) ${ctrlCode}`;
923
902
  if (ctrlOp === '||') return `if (!(${targetCode} = ${exprCode})) ${ctrlCode}`;
924
903
  return `if ((${targetCode} = ${exprCode})) ${ctrlCode}`;
@@ -962,8 +941,6 @@ export class CodeEmitter {
962
941
  let unwrapped = Array.isArray(wrappedValue) && wrappedValue.length === 1 ? wrappedValue[0] : wrappedValue;
963
942
  let fullValue = [binOp, left, unwrapped];
964
943
  let t = this.emit(target, 'value'), c = this.emit(condition, 'value'), v = this.emit(fullValue, 'value');
965
- let tgtName = (typeof target === 'string') ? target : (target instanceof String) ? str(target) : null;
966
- if (tgtName && this._inlineVarsPending?.delete(tgtName)) return `let ${t};\n${this.indent()}if (${c}) ${t} = ${v}`;
967
944
  return `if (${c}) ${t} = ${v}`;
968
945
  }
969
946
  }
@@ -978,8 +955,6 @@ export class CodeEmitter {
978
955
  let t = this.emit(target, 'value');
979
956
  let condCode = this.unwrapLogical(this.emit(condition, 'value'));
980
957
  let v = this.emit(unwrapped, 'value');
981
- let tgtName = (typeof target === 'string') ? target : (target instanceof String) ? str(target) : null;
982
- if (tgtName && this._inlineVarsPending?.delete(tgtName)) return `let ${t};\n${this.indent()}if (${condCode}) ${t} = ${v}`;
983
958
  return `if (${condCode}) ${t} = ${v}`;
984
959
  }
985
960
  }
@@ -1006,12 +981,6 @@ export class CodeEmitter {
1006
981
  let isObjLit = this.is(value, 'object');
1007
982
  if (!isObjLit) valueCode = this.unwrap(valueCode);
1008
983
 
1009
- // Inline `let` at first assignment for variables classified as inlinable
1010
- let targetName = (typeof target === 'string') ? target : (target instanceof String) ? str(target) : null;
1011
- if (head === '=' && targetName && context === 'statement' && this._inlineVarsPending?.delete(targetName)) {
1012
- return `let ${targetCode} = ${valueCode}`;
1013
- }
1014
-
1015
984
  let needsParensVal = context === 'value';
1016
985
  let needsParensObj = context === 'statement' && this.is(target, 'object');
1017
986
  if (needsParensVal || needsParensObj) return `(${targetCode} ${op} ${valueCode})`;
@@ -2442,19 +2411,9 @@ export class CodeEmitter {
2442
2411
  this.restMiddleParam = null;
2443
2412
  }
2444
2413
 
2445
- // Classify variables: inline `let` at first assignment vs hoist to top
2446
- let prevInlinePending = this._inlineVarsPending;
2447
- let inlineVars = new Set();
2448
- if (newVars.size > 0 && statements.length > 0) {
2449
- let classified = this.classifyVarsForInlining(statements, newVars);
2450
- inlineVars = classified.inlineVars;
2451
- if (inlineVars.size > 0) this._inlineVarsPending = new Set(inlineVars);
2452
- }
2453
-
2454
2414
  this.indentLevel++;
2455
2415
  let code = '{\n';
2456
- let hoistVars = new Set([...newVars].filter(v => !inlineVars.has(v)));
2457
- if (hoistVars.size > 0) code += this.indent() + `let ${Array.from(hoistVars).sort().join(', ')};\n`;
2416
+ if (newVars.size > 0) code += this.indent() + `let ${Array.from(newVars).sort().join(', ')};\n`;
2458
2417
 
2459
2418
  let firstIsSuper = autoAssignments.length > 0 && statements.length > 0 &&
2460
2419
  Array.isArray(statements[0]) && statements[0][0] === 'super';
@@ -2485,8 +2444,6 @@ export class CodeEmitter {
2485
2444
  if (typeof target === 'string' && Array.isArray(value)) {
2486
2445
  let vh = value[0];
2487
2446
  if (vh === 'comprehension' || vh === 'for-in') {
2488
- // Comprehension targets with inline vars: declare before the loop
2489
- if (this._inlineVarsPending?.delete(target)) code += this.indent() + `let ${target};\n`;
2490
2447
  this.comprehensionTarget = target;
2491
2448
  code += this.emit(value, 'value');
2492
2449
  this.comprehensionTarget = null;
@@ -2494,14 +2451,6 @@ export class CodeEmitter {
2494
2451
  return;
2495
2452
  }
2496
2453
  }
2497
- // Handle inline vars at last statement (auto-return): split into let + return
2498
- if ((typeof target === 'string' || target instanceof String) && this._inlineVarsPending?.has(str(target))) {
2499
- this._inlineVarsPending.delete(str(target));
2500
- let assignCode = this.emit(stmt, 'statement');
2501
- code += this.indent() + 'let ' + this.addSemicolon(stmt, assignCode) + '\n';
2502
- code += this.indent() + `return ${str(target)};\n`;
2503
- return;
2504
- }
2505
2454
  }
2506
2455
 
2507
2456
  let needsReturn = !isConstructor && !sideEffectOnly && isLast &&
@@ -2530,7 +2479,6 @@ export class CodeEmitter {
2530
2479
  if (!noRetStmts.includes(lastH)) code += this.indent() + 'return;\n';
2531
2480
  }
2532
2481
 
2533
- this._inlineVarsPending = prevInlinePending;
2534
2482
  this.indentLevel--;
2535
2483
  code += this.indent() + '}';
2536
2484
  this.scopeStack.pop();
@@ -3163,197 +3111,6 @@ export class CodeEmitter {
3163
3111
  return false;
3164
3112
  }
3165
3113
 
3166
- // ---------------------------------------------------------------------------
3167
- // Variable inlining — emit `let` at first assignment instead of hoisting
3168
- // ---------------------------------------------------------------------------
3169
-
3170
- // Check if an s-expression references a variable name (stopping at function boundaries)
3171
- referencesVar(sexpr, varName) {
3172
- if (!sexpr) return false;
3173
- if (sexpr instanceof String) return str(sexpr) === varName;
3174
- if (typeof sexpr === 'string') return sexpr === varName;
3175
- if (!Array.isArray(sexpr)) return false;
3176
- let h = sexpr[0];
3177
- let hs = (typeof h === 'string') ? h : (h instanceof String) ? str(h) : null;
3178
- if (hs === 'def' || hs === '->' || hs === '=>' || hs === 'effect') return false;
3179
- // Property access: only check the object, not the property name
3180
- if (hs === '.' || hs === '?.') return this.referencesVar(sexpr[1], varName);
3181
- // Object literal: check values but not simple string keys
3182
- if (hs === 'object') {
3183
- for (let i = 1; i < sexpr.length; i++) {
3184
- let pair = sexpr[i];
3185
- if (Array.isArray(pair)) {
3186
- if (this.is(pair, '...')) { if (this.referencesVar(pair[1], varName)) return true; }
3187
- else if (pair.length >= 2) { if (this.referencesVar(pair[pair.length - 1], varName)) return true; }
3188
- } else { if (this.referencesVar(pair, varName)) return true; }
3189
- }
3190
- return false;
3191
- }
3192
- return sexpr.some(item => this.referencesVar(item, varName));
3193
- }
3194
-
3195
- // Check if the first reference to a variable in DFS order is a `=` assignment
3196
- // at statement level (not inside a value expression like another assignment's RHS).
3197
- // Returns true only when it's safe to emit `let` at the first assignment site.
3198
- firstRefIsAssignment(sexpr, varName) {
3199
- let result = null; // null = not found, 'write' = statement-level assignment, 'read' = read/value
3200
- let isVar = (n) => (n instanceof String ? str(n) : n) === varName;
3201
- let walk = (node, inValue) => {
3202
- if (result !== null) return;
3203
- if (!node) return;
3204
- if (!Array.isArray(node)) { if (isVar(node)) result = 'read'; return; }
3205
- let h = node[0];
3206
- let hs = (typeof h === 'string') ? h : (h instanceof String) ? str(h) : null;
3207
- if (hs === 'def' || hs === '->' || hs === '=>' || hs === 'effect') return;
3208
- // Assignment to our variable
3209
- if (hs === '=' && (typeof node[1] === 'string' || node[1] instanceof String) && str(node[1]) === varName) {
3210
- if (inValue) { result = 'read'; return; } // In value context — can't emit `let` here
3211
- result = this.referencesVar(node[2], varName) ? 'read' : 'write';
3212
- return;
3213
- }
3214
- // Compound assignment reads the variable
3215
- if (CodeEmitter.ASSIGNMENT_OPS.has(hs) && hs !== '=' &&
3216
- (typeof node[1] === 'string' || node[1] instanceof String) && str(node[1]) === varName) {
3217
- result = 'read'; return;
3218
- }
3219
- // Property access: only check object
3220
- if (hs === '.' || hs === '?.') { walk(node[1], inValue); return; }
3221
- // Object literal: skip simple string keys
3222
- if (hs === 'object') {
3223
- for (let i = 1; i < node.length; i++) {
3224
- let pair = node[i];
3225
- if (Array.isArray(pair)) {
3226
- if (this.is(pair, '...')) walk(pair[1], true);
3227
- else if (pair.length >= 2) walk(pair[pair.length - 1], true);
3228
- } else walk(pair, true);
3229
- }
3230
- return;
3231
- }
3232
- // Block: children are at statement level
3233
- if (hs === 'block') { for (let i = 1; i < node.length; i++) walk(node[i], false); return; }
3234
- // If: condition is value, branches maintain parent context
3235
- if (hs === 'if') { walk(node[1], true); for (let i = 2; i < node.length; i++) walk(node[i], inValue); return; }
3236
- // Loops: iterable/condition is value, body is statement
3237
- if (hs === 'for-in' || hs === 'for-of' || hs === 'for-as') { walk(node[2], true); if (node.length > 3) walk(node[node.length - 1], false); return; }
3238
- if (hs === 'while') { walk(node[1], true); walk(node[2], false); return; }
3239
- // Try: blocks are statement level
3240
- if (hs === 'try') { for (let i = 1; i < node.length; i++) walk(node[i], false); return; }
3241
- // Other assignments: RHS is value context
3242
- if (CodeEmitter.ASSIGNMENT_OPS.has(hs)) { walk(node[2], true); return; }
3243
- // Default: everything else is value context
3244
- for (let i = 0; i < node.length; i++) walk(node[i], true);
3245
- };
3246
- walk(sexpr, false); // top of a statement is statement context
3247
- return result === 'write';
3248
- }
3249
-
3250
- // Check if all references to a variable within a statement are contained in a
3251
- // single block child. Returns false if the variable appears at multiple nesting
3252
- // levels (e.g., inside an if-branch AND at the same level as the if).
3253
- allRefsInSingleBlock(sexpr, varName) {
3254
- if (!Array.isArray(sexpr)) return false;
3255
- let h = sexpr[0];
3256
- let hs = (typeof h === 'string') ? h : (h instanceof String) ? str(h) : null;
3257
- // For 'if': check condition + each branch. Variable must appear in exactly one branch,
3258
- // and NOT in the condition. Then recurse into that branch.
3259
- if (hs === 'if') {
3260
- if (this.referencesVar(sexpr[1], varName)) return false; // in condition
3261
- let branchCount = 0;
3262
- let refBranch = null;
3263
- for (let i = 2; i < sexpr.length; i++) {
3264
- if (this.referencesVar(sexpr[i], varName)) { branchCount++; refBranch = sexpr[i]; }
3265
- }
3266
- if (branchCount !== 1) return false;
3267
- return this.allRefsInSingleBlock(refBranch, varName);
3268
- }
3269
- // For 'block': variable must appear in only one child statement.
3270
- // Then recurse into that child.
3271
- if (hs === 'block') {
3272
- let childCount = 0;
3273
- let refChild = null;
3274
- for (let i = 1; i < sexpr.length; i++) {
3275
- if (this.referencesVar(sexpr[i], varName)) { childCount++; refChild = sexpr[i]; }
3276
- }
3277
- if (childCount !== 1) return false;
3278
- // If the single child IS a direct assignment to this variable, we've bottomed out — safe.
3279
- if (Array.isArray(refChild) && refChild[0] === '=' &&
3280
- (typeof refChild[1] === 'string' || refChild[1] instanceof String) &&
3281
- str(refChild[1]) === varName) return true;
3282
- return this.allRefsInSingleBlock(refChild, varName);
3283
- }
3284
- // For loops: iterable/condition must not reference var, body is a block — recurse.
3285
- if (hs === 'for-in' || hs === 'for-of' || hs === 'for-as') {
3286
- if (this.referencesVar(sexpr[2], varName)) return false;
3287
- return true;
3288
- }
3289
- if (hs === 'while') {
3290
- if (this.referencesVar(sexpr[1], varName)) return false;
3291
- return true;
3292
- }
3293
- // For try: recurse
3294
- if (hs === 'try') return true;
3295
- return false;
3296
- }
3297
-
3298
- // Classify variables into those that can have `let` inlined at their first assignment
3299
- // vs those that must be hoisted to the function/program top.
3300
- classifyVarsForInlining(statements, vars) {
3301
- if (vars.size === 0) return { inlineVars: new Set(), hoistVars: new Set(vars) };
3302
-
3303
- // Pre-scan: variables with type annotations (::) must be hoisted. The DTS header
3304
- // declares them as `let x: Type;` — inlining `let x = value;` in the body would
3305
- // create a duplicate declaration, causing TS2454 ("used before being assigned").
3306
- let typedVars = new Set();
3307
- let findTypedAssigns = (node) => {
3308
- if (!Array.isArray(node)) return;
3309
- if (node[0] === '=' && node[1] instanceof String && node[1].type && vars.has(str(node[1]))) {
3310
- typedVars.add(str(node[1]));
3311
- }
3312
- for (let i = 1; i < node.length; i++) findTypedAssigns(node[i]);
3313
- };
3314
- for (let stmt of statements) findTypedAssigns(stmt);
3315
-
3316
- // For each variable, find which statement indices reference it
3317
- let varStmts = new Map();
3318
- for (let v of vars) varStmts.set(v, []);
3319
- for (let i = 0; i < statements.length; i++) {
3320
- for (let v of vars) {
3321
- if (this.referencesVar(statements[i], v)) varStmts.get(v).push(i);
3322
- }
3323
- }
3324
- let inlineVars = new Set(), hoistVars = new Set();
3325
- for (let [v, indices] of varStmts) {
3326
- if (indices.length === 0) { hoistVars.add(v); continue; }
3327
- // Typed variables must be hoisted — DTS header already declares them
3328
- if (typedVars.has(v)) { hoistVars.add(v); continue; }
3329
- let firstIdx = indices[0];
3330
- let firstStmt = statements[firstIdx];
3331
- // Check if the first statement containing this variable is a direct `=` assignment to it
3332
- let isDirectAssign = Array.isArray(firstStmt) && firstStmt[0] === '=' &&
3333
- (typeof firstStmt[1] === 'string' || firstStmt[1] instanceof String) &&
3334
- str(firstStmt[1]) === v &&
3335
- !this.referencesVar(firstStmt[2], v);
3336
- if (isDirectAssign) {
3337
- // First reference is a direct body-level assignment — safe to inline `let` here
3338
- // (same scope as hoisted `let`, but TS can infer the type from the initializer)
3339
- inlineVars.add(v);
3340
- } else if (indices.length === 1 && !this.is(firstStmt, 'switch') &&
3341
- this.allRefsInSingleBlock(firstStmt, v) &&
3342
- this.firstRefIsAssignment(firstStmt, v)) {
3343
- // All references are within a single block branch of a non-switch statement
3344
- // (e.g., all inside one if-branch, one for body, etc.) and the first DFS
3345
- // reference is a direct assignment. JS 'let' block scoping is safe here
3346
- // because the variable never escapes that single block.
3347
- inlineVars.add(v);
3348
- } else {
3349
- // Variable spans multiple statements, is in a switch, or first ref is a read.
3350
- // Must hoist to ensure correct scoping.
3351
- hoistVars.add(v);
3352
- }
3353
- }
3354
- return { inlineVars, hoistVars };
3355
- }
3356
-
3357
3114
  // Class helpers
3358
3115
  extractMemberName(mk) {
3359
3116
  if (this.is(mk, '.') && mk[1] === 'this') return mk[2];
package/src/typecheck.js CHANGED
@@ -916,6 +916,59 @@ export function compileForCheck(filePath, source, compiler, opts = {}) {
916
916
  headerDts = ARIA_TYPE_DECLS.join('\n') + '\n' + headerDts;
917
917
  }
918
918
 
919
+ // Inline hoisted `let` declarations at their first assignment in the shadow
920
+ // TS file. Rip always hoists `let` to the top of scope for correct JS
921
+ // scoping. For TypeScript inference, we move the declaration inline so TS
922
+ // can infer types from initializers (e.g., `let x; x = 1` → `let x = 1`).
923
+ // Only rewrites same-scope, straight-line assignments — stops at control
924
+ // flow to avoid changing binding visibility in the shadow file.
925
+ if (hasTypes && code) {
926
+ const cl = code.split('\n');
927
+ for (let i = 0; i < cl.length; i++) {
928
+ const m = cl[i].match(/^(\s*)let\s+([A-Za-z_$][\w$]*(?:\s*,\s*[A-Za-z_$][\w$]*)*)\s*;\s*$/);
929
+ if (!m) continue;
930
+ // Only process hoist-position lets (first non-blank line after `{` or start of file)
931
+ let prev = null;
932
+ for (let k = i - 1; k >= 0; k--) { if (cl[k].trim() !== '') { prev = cl[k]; break; } }
933
+ if (prev !== null && !/\{\s*$/.test(prev)) continue;
934
+
935
+ const indent = m[1];
936
+ const vars = m[2].split(/\s*,\s*/);
937
+ const inlined = new Set();
938
+ const bailed = new Set();
939
+ const reEsc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
940
+
941
+ for (let j = i + 1; j < cl.length; j++) {
942
+ const line = cl[j];
943
+ if (line.trim() === '') continue;
944
+ // End of scope
945
+ if (new RegExp('^' + reEsc(indent) + '}').test(line)) break;
946
+ // Skip deeper-indented lines
947
+ if (line.startsWith(indent + ' ')) continue;
948
+ // Stop at structural statements
949
+ if (new RegExp('^' + reEsc(indent) + '(?:if\\b|for\\b|while\\b|switch\\b|try\\b|catch\\b|finally\\b|function\\b|class\\b|do\\b|\\{|\\})').test(line)) break;
950
+
951
+ for (const v of vars) {
952
+ if (inlined.has(v) || bailed.has(v)) continue;
953
+ const ve = reEsc(v);
954
+ const assignRe = new RegExp('^' + reEsc(indent) + ve + '\\s*=\\s*(.*);\\s*$');
955
+ const assign = line.match(assignRe);
956
+ if (assign) {
957
+ cl[j] = `${indent}let ${v} = ${assign[1]};`;
958
+ inlined.add(v);
959
+ continue;
960
+ }
961
+ if (new RegExp('\\b' + ve + '\\b').test(line)) bailed.add(v);
962
+ }
963
+ if (vars.every(v => inlined.has(v) || bailed.has(v))) break;
964
+ }
965
+
966
+ const remaining = vars.filter(v => !inlined.has(v));
967
+ cl[i] = remaining.length ? `${indent}let ${remaining.join(', ')};` : '';
968
+ }
969
+ code = cl.filter(l => l !== '').join('\n');
970
+ }
971
+
919
972
  let tsContent = (hasTypes ? headerDts + '\n' : '') + code;
920
973
  const headerLines = hasTypes ? countLines(headerDts + '\n') : 1;
921
974