rip-lang 3.6.1 → 3.7.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.
package/src/compiler.js CHANGED
@@ -206,7 +206,9 @@ export class CodeGenerator {
206
206
  'continue-if': 'generateContinueIf',
207
207
  '?': 'generateExistential',
208
208
  '?:': 'generateTernary',
209
+ '|>': 'generatePipe',
209
210
  'loop': 'generateLoop',
211
+ 'loop-n': 'generateLoopN',
210
212
  'await': 'generateAwait',
211
213
  'yield': 'generateYield',
212
214
  'yield-from': 'generateYieldFrom',
@@ -362,9 +364,9 @@ export class CodeGenerator {
362
364
  if (typeof target === 'string' || target instanceof String) {
363
365
  let varName = str(target);
364
366
  if (!this.reactiveVars?.has(varName)) this.programVars.add(varName);
365
- } else if (Array.isArray(target) && target[0] === 'array') {
367
+ } else if (this.is(target, 'array')) {
366
368
  this.collectVarsFromArray(target, this.programVars);
367
- } else if (Array.isArray(target) && target[0] === 'object') {
369
+ } else if (this.is(target, 'object')) {
368
370
  this.collectVarsFromObject(target, this.programVars);
369
371
  }
370
372
  this.collectProgramVariables(value);
@@ -392,13 +394,13 @@ export class CodeGenerator {
392
394
  this.collectProgramVariables(rest[0]);
393
395
  if (rest.length >= 2 && Array.isArray(rest[1]) && rest[1].length === 2 && rest[1][0] !== 'block') {
394
396
  let [param, catchBlock] = rest[1];
395
- if (param && Array.isArray(param) && param[0] === 'object') {
397
+ if (param && this.is(param, 'object')) {
396
398
  param.slice(1).forEach(pair => {
397
399
  if (Array.isArray(pair) && pair.length === 2 && typeof pair[1] === 'string') {
398
400
  this.programVars.add(pair[1]);
399
401
  }
400
402
  });
401
- } else if (param && Array.isArray(param) && param[0] === 'array') {
403
+ } else if (param && this.is(param, 'array')) {
402
404
  param.slice(1).forEach(item => {
403
405
  if (typeof item === 'string') this.programVars.add(item);
404
406
  });
@@ -425,8 +427,8 @@ export class CodeGenerator {
425
427
  if (CodeGenerator.ASSIGNMENT_OPS.has(head)) {
426
428
  let [target, value] = rest;
427
429
  if (typeof target === 'string') vars.add(target);
428
- else if (Array.isArray(target) && target[0] === 'array') this.collectVarsFromArray(target, vars);
429
- else if (Array.isArray(target) && target[0] === 'object') this.collectVarsFromObject(target, vars);
430
+ else if (this.is(target, 'array')) this.collectVarsFromArray(target, vars);
431
+ else if (this.is(target, 'object')) this.collectVarsFromObject(target, vars);
430
432
  collect(value);
431
433
  return;
432
434
  }
@@ -435,11 +437,11 @@ export class CodeGenerator {
435
437
  collect(rest[0]);
436
438
  if (rest.length >= 2 && Array.isArray(rest[1]) && rest[1].length === 2 && rest[1][0] !== 'block') {
437
439
  let [param, catchBlock] = rest[1];
438
- if (param && Array.isArray(param) && param[0] === 'object') {
440
+ if (param && this.is(param, 'object')) {
439
441
  param.slice(1).forEach(pair => {
440
442
  if (Array.isArray(pair) && pair.length === 2 && typeof pair[1] === 'string') vars.add(pair[1]);
441
443
  });
442
- } else if (param && Array.isArray(param) && param[0] === 'array') {
444
+ } else if (param && this.is(param, 'array')) {
443
445
  param.slice(1).forEach(item => { if (typeof item === 'string') vars.add(item); });
444
446
  }
445
447
  collect(catchBlock);
@@ -603,7 +605,7 @@ export class CodeGenerator {
603
605
  let [obj, prop] = head.slice(1);
604
606
  let objCode = this.generate(obj, 'value');
605
607
  let needsParens = CodeGenerator.NUMBER_LITERAL_RE.test(objCode) ||
606
- (Array.isArray(obj) && (obj[0] === 'object' || obj[0] === 'await' || obj[0] === 'yield'));
608
+ ((this.is(obj, 'object') || this.is(obj, 'await') || this.is(obj, 'yield')));
607
609
  let base = needsParens ? `(${objCode})` : objCode;
608
610
  calleeCode = `${base}.${str(prop)}`;
609
611
  } else {
@@ -638,9 +640,9 @@ export class CodeGenerator {
638
640
  let blockStmts = ['def', 'class', 'if', 'unless', 'for-in', 'for-of', 'for-as', 'while', 'until', 'loop', 'switch', 'try'];
639
641
  let statementsCode = other.map((stmt, index) => {
640
642
  let isSingle = other.length === 1 && imports.length === 0 && exports.length === 0;
641
- let isObj = Array.isArray(stmt) && stmt[0] === 'object';
643
+ let isObj = this.is(stmt, 'object');
642
644
  let isObjComp = isObj && stmt.length === 2 && Array.isArray(stmt[1]) && Array.isArray(stmt[1][1]) && stmt[1][1][0] === 'comprehension';
643
- let isAlreadyExpr = Array.isArray(stmt) && (stmt[0] === 'comprehension' || stmt[0] === 'object-comprehension' || stmt[0] === 'do-iife');
645
+ let isAlreadyExpr = (this.is(stmt, 'comprehension') || this.is(stmt, 'object-comprehension') || this.is(stmt, 'do-iife'));
644
646
  let hasNoVars = this.programVars.size === 0;
645
647
  let needsParens = isSingle && isObj && hasNoVars && !isAlreadyExpr && !isObjComp;
646
648
  let isLast = index === other.length - 1;
@@ -672,24 +674,28 @@ export class CodeGenerator {
672
674
  needsBlank = true;
673
675
  }
674
676
 
675
- if (this.helpers.has('slice')) { code += 'const slice = [].slice;\n'; needsBlank = true; }
676
- if (this.helpers.has('modulo')) { code += 'const modulo = (n, d) => { n = +n; d = +d; return (n % d + d) % d; };\n'; needsBlank = true; }
677
- if (this.helpers.has('toSearchable')) {
678
- code += 'const toSearchable = (v, allowNewlines) => {\n';
679
- code += ' if (typeof v === "string") return !allowNewlines && /[\\n\\r]/.test(v) ? null : v;\n';
680
- code += ' if (v == null) return "";\n';
681
- code += ' if (typeof v === "number" || typeof v === "bigint" || typeof v === "boolean") return String(v);\n';
682
- code += ' if (typeof v === "symbol") return v.description || "";\n';
683
- code += ' if (v instanceof Uint8Array || v instanceof ArrayBuffer) {\n';
684
- code += ' return new TextDecoder().decode(v instanceof Uint8Array ? v : new Uint8Array(v));\n';
685
- code += ' }\n';
686
- code += ' if (Array.isArray(v)) return v.join(",");\n';
687
- code += ' if (typeof v.toString === "function" && v.toString !== Object.prototype.toString) {\n';
688
- code += ' try { return v.toString(); } catch { return ""; }\n';
689
- code += ' }\n';
690
- code += ' return "";\n';
691
- code += '};\n';
692
- needsBlank = true;
677
+ let skip = this.options.skipPreamble;
678
+
679
+ if (!skip) {
680
+ if (this.helpers.has('slice')) { code += 'const slice = [].slice;\n'; needsBlank = true; }
681
+ if (this.helpers.has('modulo')) { code += 'const modulo = (n, d) => { n = +n; d = +d; return (n % d + d) % d; };\n'; needsBlank = true; }
682
+ if (this.helpers.has('toSearchable')) {
683
+ code += 'const toSearchable = (v, allowNewlines) => {\n';
684
+ code += ' if (typeof v === "string") return !allowNewlines && /[\\n\\r]/.test(v) ? null : v;\n';
685
+ code += ' if (v == null) return "";\n';
686
+ code += ' if (typeof v === "number" || typeof v === "bigint" || typeof v === "boolean") return String(v);\n';
687
+ code += ' if (typeof v === "symbol") return v.description || "";\n';
688
+ code += ' if (v instanceof Uint8Array || v instanceof ArrayBuffer) {\n';
689
+ code += ' return new TextDecoder().decode(v instanceof Uint8Array ? v : new Uint8Array(v));\n';
690
+ code += ' }\n';
691
+ code += ' if (Array.isArray(v)) return v.join(",");\n';
692
+ code += ' if (typeof v.toString === "function" && v.toString !== Object.prototype.toString) {\n';
693
+ code += ' try { return v.toString(); } catch { return ""; }\n';
694
+ code += ' }\n';
695
+ code += ' return "";\n';
696
+ code += '};\n';
697
+ needsBlank = true;
698
+ }
693
699
  }
694
700
 
695
701
  // Generate exports code early so component/reactivity flags are set before runtime checks
@@ -698,7 +704,7 @@ export class CodeGenerator {
698
704
  exportsCode = '\n' + exports.map(s => this.addSemicolon(s, this.generate(s, 'statement'))).join('\n');
699
705
  }
700
706
 
701
- if (this.usesReactivity && !this.options.skipReactiveRuntime) {
707
+ if (this.usesReactivity && !skip) {
702
708
  if (typeof globalThis !== 'undefined' && globalThis.__rip) {
703
709
  code += 'const { __state, __computed, __effect, __batch, __readonly, __setErrorHandler, __handleError, __catchErrors } = globalThis.__rip;\n';
704
710
  } else {
@@ -707,7 +713,7 @@ export class CodeGenerator {
707
713
  needsBlank = true;
708
714
  }
709
715
 
710
- if (this.usesTemplates && !this.options.skipComponentRuntime) {
716
+ if (this.usesTemplates && !skip) {
711
717
  if (typeof globalThis !== 'undefined' && globalThis.__ripComponent) {
712
718
  code += 'const { isSignal, __pushComponent, __popComponent, setContext, getContext, hasContext, __cx__ } = globalThis.__ripComponent;\n';
713
719
  } else {
@@ -716,7 +722,7 @@ export class CodeGenerator {
716
722
  needsBlank = true;
717
723
  }
718
724
 
719
- if (this.dataSection !== null && this.dataSection !== undefined) {
725
+ if (this.dataSection !== null && this.dataSection !== undefined && !skip) {
720
726
  code += 'var DATA;\n_setDataSection();\n';
721
727
  needsBlank = true;
722
728
  }
@@ -741,6 +747,24 @@ export class CodeGenerator {
741
747
  return `(${op}${this.generate(rest[0], 'value')})`;
742
748
  }
743
749
  let [left, right] = rest;
750
+ // String repeat: "str" * n → "str".repeat(n)
751
+ if (op === '*') {
752
+ let leftStr = left?.valueOf?.() ?? left;
753
+ if (typeof leftStr === 'string' && /^["']/.test(leftStr)) {
754
+ return `${this.generate(left, 'value')}.repeat(${this.generate(right, 'value')})`;
755
+ }
756
+ }
757
+ // Chained comparisons: (< (< a b) c) → ((a < b) && (b < c))
758
+ let COMPARE_OPS = new Set(['<', '>', '<=', '>=']);
759
+ if (COMPARE_OPS.has(op) && Array.isArray(left)) {
760
+ let leftOp = left[0]?.valueOf?.() ?? left[0];
761
+ if (COMPARE_OPS.has(leftOp)) {
762
+ let a = this.generate(left[1], 'value');
763
+ let b = this.generate(left[2], 'value');
764
+ let c = this.generate(right, 'value');
765
+ return `((${a} ${leftOp} ${b}) && (${b} ${op} ${c}))`;
766
+ }
767
+ }
744
768
  if (op === '!?') {
745
769
  let l = this.generate(left, 'value'), r = this.generate(right, 'value');
746
770
  return `(${l} !== undefined ? ${l} : ${r})`;
@@ -776,7 +800,7 @@ export class CodeGenerator {
776
800
  let op = head === '?=' ? '??=' : head;
777
801
 
778
802
  // Validate: no sigils in assignment targets (except void function syntax)
779
- let isFnValue = Array.isArray(value) && (value[0] === '->' || value[0] === '=>' || value[0] === 'def');
803
+ let isFnValue = (this.is(value, '->') || this.is(value, '=>') || this.is(value, 'def'));
780
804
  if (target instanceof String && meta(target, 'await') !== undefined && !isFnValue) {
781
805
  let sigil = meta(target, 'await') === true ? '!' : '&';
782
806
  throw new Error(`Cannot use ${sigil} sigil in variable declaration '${str(target)}'.`);
@@ -787,8 +811,8 @@ export class CodeGenerator {
787
811
  }
788
812
 
789
813
  // Empty destructuring — just evaluate RHS
790
- let isEmptyArr = Array.isArray(target) && target[0] === 'array' && target.length === 1;
791
- let isEmptyObj = Array.isArray(target) && target[0] === 'object' && target.length === 1;
814
+ let isEmptyArr = this.is(target, 'array', 0);
815
+ let isEmptyObj = this.is(target, 'object', 0);
792
816
  if (isEmptyArr || isEmptyObj) {
793
817
  let v = this.generate(value, 'value');
794
818
  return (isEmptyObj && context === 'statement') ? `(${v})` : v;
@@ -817,8 +841,8 @@ export class CodeGenerator {
817
841
  }
818
842
 
819
843
  // Middle/leading rest in array destructuring
820
- if (Array.isArray(target) && target[0] === 'array') {
821
- let restIdx = target.slice(1).findIndex(el => (Array.isArray(el) && el[0] === '...') || el === '...');
844
+ if (this.is(target, 'array')) {
845
+ let restIdx = target.slice(1).findIndex(el => (this.is(el, '...')) || el === '...');
822
846
  if (restIdx !== -1 && restIdx < target.length - 2) {
823
847
  let elements = target.slice(1);
824
848
  let afterRest = elements.slice(restIdx + 1);
@@ -832,10 +856,10 @@ export class CodeGenerator {
832
856
  elements.forEach(el => {
833
857
  if (el === ',' || el === '...') return;
834
858
  if (typeof el === 'string') this.programVars.add(el);
835
- else if (Array.isArray(el) && el[0] === '...' && typeof el[1] === 'string') this.programVars.add(el[1]);
859
+ else if (this.is(el, '...') && typeof el[1] === 'string') this.programVars.add(el[1]);
836
860
  });
837
861
  let restEl = elements[restIdx];
838
- let restVar = Array.isArray(restEl) && restEl[0] === '...' ? restEl[1] : null;
862
+ let restVar = this.is(restEl, '...') ? restEl[1] : null;
839
863
  let stmts = [];
840
864
  if (beforePattern) stmts.push(`[${beforePattern}] = ${valueCode}`);
841
865
  if (restVar) stmts.push(`[...${restVar}] = ${valueCode}.slice(${restIdx}, -${afterCount})`);
@@ -849,7 +873,7 @@ export class CodeGenerator {
849
873
  if (context === 'statement' && head === '=' && Array.isArray(value) &&
850
874
  (value[0] === '||' || value[0] === '&&') && value.length === 3) {
851
875
  let [binOp, left, right] = value;
852
- if (Array.isArray(right) && (right[0] === 'unless' || right[0] === 'if') && right.length === 3) {
876
+ if ((this.is(right, 'unless') || this.is(right, 'if')) && right.length === 3) {
853
877
  let [condType, condition, wrappedValue] = right;
854
878
  let unwrapped = Array.isArray(wrappedValue) && wrappedValue.length === 1 ? wrappedValue[0] : wrappedValue;
855
879
  let fullValue = [binOp, left, unwrapped];
@@ -889,11 +913,11 @@ export class CodeGenerator {
889
913
  }
890
914
 
891
915
  let valueCode = this.generate(value, 'value');
892
- let isObjLit = Array.isArray(value) && value[0] === 'object';
916
+ let isObjLit = this.is(value, 'object');
893
917
  if (!isObjLit) valueCode = this.unwrap(valueCode);
894
918
 
895
919
  let needsParensVal = context === 'value';
896
- let needsParensObj = context === 'statement' && Array.isArray(target) && target[0] === 'object';
920
+ let needsParensObj = context === 'statement' && this.is(target, 'object');
897
921
  if (needsParensVal || needsParensObj) return `(${targetCode} ${op} ${valueCode})`;
898
922
  return `${targetCode} ${op} ${valueCode}`;
899
923
  }
@@ -908,7 +932,7 @@ export class CodeGenerator {
908
932
  let objCode = this.generate(obj, 'value');
909
933
  this.suppressReactiveUnwrap = false;
910
934
  let needsParens = CodeGenerator.NUMBER_LITERAL_RE.test(objCode) ||
911
- (Array.isArray(obj) && (obj[0] === 'object' || obj[0] === 'await' || obj[0] === 'yield'));
935
+ ((this.is(obj, 'object') || this.is(obj, 'await') || this.is(obj, 'yield')));
912
936
  let base = needsParens ? `(${objCode})` : objCode;
913
937
  if (meta(prop, 'await') === true) return `await ${base}.${str(prop)}()`;
914
938
  if (meta(prop, 'predicate')) return `(${base}.${str(prop)} != null)`;
@@ -932,27 +956,41 @@ export class CodeGenerator {
932
956
 
933
957
  generateIndexAccess(head, rest) {
934
958
  let [arr, index] = rest;
935
- if (Array.isArray(index) && (index[0] === '..' || index[0] === '...')) {
959
+ if ((this.is(index, '..') || this.is(index, '...'))) {
936
960
  let isIncl = index[0] === '..';
937
961
  let arrCode = this.generate(arr, 'value');
938
962
  let [start, end] = index.slice(1);
939
963
  if (start === null && end === null) return `${arrCode}.slice()`;
940
964
  if (start === null) {
941
- if (isIncl && this.isNegativeOneLiteral(end)) return `${arrCode}.slice(0)`;
965
+ if (isIncl && this.is(end, '-', 1) && (str(end[1]) ?? end[1]) == 1) return `${arrCode}.slice(0)`;
942
966
  let e = this.generate(end, 'value');
943
967
  return isIncl ? `${arrCode}.slice(0, +${e} + 1 || 9e9)` : `${arrCode}.slice(0, ${e})`;
944
968
  }
945
969
  if (end === null) return `${arrCode}.slice(${this.generate(start, 'value')})`;
946
970
  let s = this.generate(start, 'value');
947
- if (isIncl && this.isNegativeOneLiteral(end)) return `${arrCode}.slice(${s})`;
971
+ if (isIncl && this.is(end, '-', 1) && (str(end[1]) ?? end[1]) == 1) return `${arrCode}.slice(${s})`;
948
972
  let e = this.generate(end, 'value');
949
973
  return isIncl ? `${arrCode}.slice(${s}, +${e} + 1 || 9e9)` : `${arrCode}.slice(${s}, ${e})`;
950
974
  }
975
+ // Negative literal index: arr[-1] → arr.at(-1)
976
+ if (this.is(index, '-', 1)) {
977
+ let n = str(index[1]) ?? index[1];
978
+ if (typeof n === 'number' || (typeof n === 'string' && /^\d+$/.test(n))) {
979
+ return `${this.generate(arr, 'value')}.at(-${n})`;
980
+ }
981
+ }
951
982
  return `${this.generate(arr, 'value')}[${this.unwrap(this.generate(index, 'value'))}]`;
952
983
  }
953
984
 
954
985
  generateOptIndex(head, rest) {
955
986
  let [arr, index] = rest;
987
+ // Negative literal index: arr?[-1] → arr?.at(-1)
988
+ if (this.is(index, '-', 1)) {
989
+ let n = str(index[1]) ?? index[1];
990
+ if (typeof n === 'number' || (typeof n === 'string' && /^\d+$/.test(n))) {
991
+ return `${this.generate(arr, 'value')}?.at(-${n})`;
992
+ }
993
+ }
956
994
  return `${this.generate(arr, 'value')}?.[${this.generate(index, 'value')}]`;
957
995
  }
958
996
 
@@ -978,6 +1016,7 @@ export class CodeGenerator {
978
1016
 
979
1017
  generateThinArrow(head, rest, context, sexpr) {
980
1018
  let [params, body] = rest;
1019
+ if ((!params || (Array.isArray(params) && params.length === 0)) && this.containsIt(body)) params = ['it'];
981
1020
  let sideEffectOnly = this.nextFunctionIsVoid || false;
982
1021
  this.nextFunctionIsVoid = false;
983
1022
  let paramList = this.generateParamList(params);
@@ -990,6 +1029,7 @@ export class CodeGenerator {
990
1029
 
991
1030
  generateFatArrow(head, rest, context, sexpr) {
992
1031
  let [params, body] = rest;
1032
+ if ((!params || (Array.isArray(params) && params.length === 0)) && this.containsIt(body)) params = ['it'];
993
1033
  let sideEffectOnly = this.nextFunctionIsVoid || false;
994
1034
  this.nextFunctionIsVoid = false;
995
1035
  let paramList = this.generateParamList(params);
@@ -1001,7 +1041,7 @@ export class CodeGenerator {
1001
1041
  let prefix = isAsync ? 'async ' : '';
1002
1042
 
1003
1043
  if (!sideEffectOnly) {
1004
- if (Array.isArray(body) && body[0] === 'block' && body.length === 2) {
1044
+ if (this.is(body, 'block') && body.length === 2) {
1005
1045
  let expr = body[1];
1006
1046
  if (!Array.isArray(expr) || expr[0] !== 'return') {
1007
1047
  return `${prefix}${paramSyntax} => ${this.generate(expr, 'value')}`;
@@ -1021,19 +1061,19 @@ export class CodeGenerator {
1021
1061
  let [expr] = rest;
1022
1062
  if (this.sideEffectOnly) return 'return';
1023
1063
 
1024
- if (Array.isArray(expr) && expr[0] === 'unless') {
1064
+ if (this.is(expr, 'unless')) {
1025
1065
  let [, condition, body] = expr;
1026
1066
  let val = Array.isArray(body) && body.length === 1 ? body[0] : body;
1027
1067
  return `if (!${this.generate(condition, 'value')}) return ${this.generate(val, 'value')}`;
1028
1068
  }
1029
- if (Array.isArray(expr) && expr[0] === 'if') {
1069
+ if (this.is(expr, 'if')) {
1030
1070
  let [, condition, body, ...elseParts] = expr;
1031
1071
  if (elseParts.length === 0) {
1032
1072
  let val = Array.isArray(body) && body.length === 1 ? body[0] : body;
1033
1073
  return `if (${this.generate(condition, 'value')}) return ${this.generate(val, 'value')}`;
1034
1074
  }
1035
1075
  }
1036
- if (Array.isArray(expr) && expr[0] === 'new' && Array.isArray(expr[1]) && expr[1][0] === 'unless') {
1076
+ if (this.is(expr, 'new') && Array.isArray(expr[1]) && expr[1][0] === 'unless') {
1037
1077
  let [, unlessNode] = expr;
1038
1078
  let [, condition, body] = unlessNode;
1039
1079
  let val = Array.isArray(body) && body.length === 1 ? body[0] : body;
@@ -1073,10 +1113,10 @@ export class CodeGenerator {
1073
1113
  let [target, body] = rest;
1074
1114
  this.usesReactivity = true;
1075
1115
  let bodyCode;
1076
- if (Array.isArray(body) && body[0] === 'block') {
1116
+ if (this.is(body, 'block')) {
1077
1117
  let stmts = this.withIndent(() => this.formatStatements(body.slice(1)));
1078
1118
  bodyCode = `{\n${stmts.join('\n')}\n${this.indent()}}`;
1079
- } else if (Array.isArray(body) && (body[0] === '->' || body[0] === '=>')) {
1119
+ } else if ((this.is(body, '->') || this.is(body, '=>'))) {
1080
1120
  let fnCode = this.generate(body, 'value');
1081
1121
  if (target) return `const ${str(target) ?? this.generate(target, 'value')} = __effect(${fnCode})`;
1082
1122
  return `__effect(${fnCode})`;
@@ -1101,15 +1141,49 @@ export class CodeGenerator {
1101
1141
  return `(${this.generate(rest[0], 'value')} != null)`;
1102
1142
  }
1103
1143
 
1104
- generateTernary(head, rest) {
1144
+ generateTernary(head, rest, context) {
1105
1145
  let [cond, then_, else_] = rest;
1146
+
1147
+ // Hoist assignment: (cond ? (x = a) : b) → x = (cond ? a : b)
1148
+ // Enables: x = "admin" if cond else "member" without parens
1149
+ let thenHead = then_?.[0]?.valueOf?.() ?? then_?.[0];
1150
+ if (thenHead === '=' && Array.isArray(then_)) {
1151
+ let target = this.generate(then_[1], 'value');
1152
+ let thenVal = this.generate(then_[2], 'value');
1153
+ let elseVal = this.generate(else_, 'value');
1154
+ return `${target} = (${this.unwrap(this.generate(cond, 'value'))} ? ${thenVal} : ${elseVal})`;
1155
+ }
1156
+
1106
1157
  return `(${this.unwrap(this.generate(cond, 'value'))} ? ${this.generate(then_, 'value')} : ${this.generate(else_, 'value')})`;
1107
1158
  }
1108
1159
 
1160
+ generatePipe(head, rest) {
1161
+ let [left, right] = rest;
1162
+ let leftCode = this.generate(left, 'value');
1163
+ // Detect function calls: [fn, ...args] where fn is an identifier or accessor
1164
+ if (Array.isArray(right) && right.length > 1) {
1165
+ let fn = right[0];
1166
+ let isCall = Array.isArray(fn) || (typeof fn === 'string' && /^[a-zA-Z_$]/.test(fn));
1167
+ if (isCall) {
1168
+ let fnCode = this.generate(fn, 'value');
1169
+ let args = right.slice(1).map(a => this.generate(a, 'value'));
1170
+ return `${fnCode}(${leftCode}, ${args.join(', ')})`;
1171
+ }
1172
+ }
1173
+ // Simple reference or property access — call with left as sole arg
1174
+ return `${this.generate(right, 'value')}(${leftCode})`;
1175
+ }
1176
+
1109
1177
  generateLoop(head, rest) {
1110
1178
  return `while (true) ${this.generateLoopBody(rest[0])}`;
1111
1179
  }
1112
1180
 
1181
+ generateLoopN(head, rest) {
1182
+ let [count, body] = rest;
1183
+ let n = this.generate(count, 'value');
1184
+ return `for (let _i = 0; _i < ${n}; _i++) ${this.generateLoopBody(body)}`;
1185
+ }
1186
+
1113
1187
  generateAwait(head, rest) { return `await ${this.generate(rest[0], 'value')}`; }
1114
1188
 
1115
1189
  generateYield(head, rest) {
@@ -1154,7 +1228,7 @@ export class CodeGenerator {
1154
1228
  let varsArray = Array.isArray(vars) ? vars : [vars];
1155
1229
  let noVar = varsArray.length === 0;
1156
1230
  let [itemVar, indexVar] = noVar ? ['_i', null] : varsArray;
1157
- let itemVarPattern = (Array.isArray(itemVar) && (itemVar[0] === 'array' || itemVar[0] === 'object'))
1231
+ let itemVarPattern = ((this.is(itemVar, 'array') || this.is(itemVar, 'object')))
1158
1232
  ? this.generateDestructuringPattern(itemVar) : itemVar;
1159
1233
 
1160
1234
  // Stepped iteration
@@ -1162,7 +1236,7 @@ export class CodeGenerator {
1162
1236
  let iterCode = this.generate(iterable, 'value');
1163
1237
  let idxName = indexVar || '_i';
1164
1238
  let stepCode = this.generate(step, 'value');
1165
- let isNeg = this.isNegativeStep(step);
1239
+ let isNeg = this.is(step, '-', 1);
1166
1240
  let isMinus1 = isNeg && (step[1] === '1' || step[1] === 1 || str(step[1]) === '1');
1167
1241
  let isPlus1 = !isNeg && (step === '1' || step === 1 || str(step) === '1');
1168
1242
 
@@ -1172,7 +1246,7 @@ export class CodeGenerator {
1172
1246
  else if (isNeg) loopHeader = `for (let ${idxName} = ${iterCode}.length - 1; ${idxName} >= 0; ${idxName} += ${stepCode}) `;
1173
1247
  else loopHeader = `for (let ${idxName} = 0; ${idxName} < ${iterCode}.length; ${idxName} += ${stepCode}) `;
1174
1248
 
1175
- if (Array.isArray(body) && body[0] === 'block') {
1249
+ if (this.is(body, 'block')) {
1176
1250
  let stmts = body.slice(1);
1177
1251
  this.indentLevel++;
1178
1252
  let lines = [];
@@ -1204,7 +1278,7 @@ export class CodeGenerator {
1204
1278
  if (indexVar) {
1205
1279
  let iterCode = this.generate(iterable, 'value');
1206
1280
  let code = `for (let ${indexVar} = 0; ${indexVar} < ${iterCode}.length; ${indexVar}++) `;
1207
- if (Array.isArray(body) && body[0] === 'block') {
1281
+ if (this.is(body, 'block')) {
1208
1282
  code += '{\n';
1209
1283
  this.indentLevel++;
1210
1284
  code += this.indent() + `const ${itemVarPattern} = ${iterCode}[${indexVar}];\n`;
@@ -1234,7 +1308,7 @@ export class CodeGenerator {
1234
1308
  let isExcl = iterHead === '...';
1235
1309
  let [start, end] = iterable.slice(1);
1236
1310
  let isSimple = (e) => typeof e === 'number' || typeof e === 'string' && !e.includes('(') ||
1237
- (e instanceof String && !str(e).includes('(')) || (Array.isArray(e) && e[0] === '.');
1311
+ (e instanceof String && !str(e).includes('(')) || (this.is(e, '.'));
1238
1312
  if (isSimple(start) && isSimple(end)) {
1239
1313
  let s = this.generate(start, 'value'), e = this.generate(end, 'value');
1240
1314
  let cmp = isExcl ? '<' : '<=';
@@ -1258,7 +1332,7 @@ export class CodeGenerator {
1258
1332
  let code = `for (const ${keyVar} in ${objCode}) `;
1259
1333
 
1260
1334
  if (own && !valueVar && !guard) {
1261
- if (Array.isArray(body) && body[0] === 'block') {
1335
+ if (this.is(body, 'block')) {
1262
1336
  this.indentLevel++;
1263
1337
  let stmts = [`if (!Object.hasOwn(${objCode}, ${keyVar})) continue;`, ...body.slice(1).map(s => this.addSemicolon(s, this.generate(s, 'statement')))];
1264
1338
  this.indentLevel--;
@@ -1268,7 +1342,7 @@ export class CodeGenerator {
1268
1342
  }
1269
1343
 
1270
1344
  if (valueVar) {
1271
- if (Array.isArray(body) && body[0] === 'block') {
1345
+ if (this.is(body, 'block')) {
1272
1346
  let stmts = body.slice(1);
1273
1347
  this.indentLevel++;
1274
1348
  let lines = [];
@@ -1304,15 +1378,15 @@ export class CodeGenerator {
1304
1378
  let iterable = rest[1], isAwait = rest[2], guard = rest[3], body = rest[4];
1305
1379
 
1306
1380
  let needsTempVar = false, destructStmts = [];
1307
- if (Array.isArray(firstVar) && firstVar[0] === 'array') {
1381
+ if (this.is(firstVar, 'array')) {
1308
1382
  let elements = firstVar.slice(1);
1309
- let restIdx = elements.findIndex(el => (Array.isArray(el) && el[0] === '...') || el === '...');
1383
+ let restIdx = elements.findIndex(el => (this.is(el, '...')) || el === '...');
1310
1384
  if (restIdx !== -1 && restIdx < elements.length - 1) {
1311
1385
  needsTempVar = true;
1312
1386
  let afterRest = elements.slice(restIdx + 1), afterCount = afterRest.length;
1313
1387
  let beforeRest = elements.slice(0, restIdx);
1314
1388
  let restEl = elements[restIdx];
1315
- let restVar = Array.isArray(restEl) && restEl[0] === '...' ? restEl[1] : '_rest';
1389
+ let restVar = this.is(restEl, '...') ? restEl[1] : '_rest';
1316
1390
  let beforePattern = beforeRest.map(el => el === ',' ? '' : typeof el === 'string' ? el : this.generate(el, 'value')).join(', ');
1317
1391
  let firstPattern = beforePattern ? `${beforePattern}, ...${restVar}` : `...${restVar}`;
1318
1392
  let afterPattern = afterRest.map(el => el === ',' ? '' : typeof el === 'string' ? el : this.generate(el, 'value')).join(', ');
@@ -1322,7 +1396,7 @@ export class CodeGenerator {
1322
1396
  elements.forEach(el => {
1323
1397
  if (el === ',' || el === '...') return;
1324
1398
  if (typeof el === 'string') this.programVars.add(el);
1325
- else if (Array.isArray(el) && el[0] === '...' && typeof el[1] === 'string') this.programVars.add(el[1]);
1399
+ else if (this.is(el, '...') && typeof el[1] === 'string') this.programVars.add(el[1]);
1326
1400
  });
1327
1401
  }
1328
1402
  }
@@ -1331,7 +1405,7 @@ export class CodeGenerator {
1331
1405
  let awaitKw = isAwait ? 'await ' : '';
1332
1406
  let itemVarPattern;
1333
1407
  if (needsTempVar) itemVarPattern = '_item';
1334
- else if (Array.isArray(firstVar) && (firstVar[0] === 'array' || firstVar[0] === 'object'))
1408
+ else if ((this.is(firstVar, 'array') || this.is(firstVar, 'object')))
1335
1409
  itemVarPattern = this.generateDestructuringPattern(firstVar);
1336
1410
  else itemVarPattern = firstVar;
1337
1411
 
@@ -1410,7 +1484,7 @@ export class CodeGenerator {
1410
1484
  let [key, container] = rest;
1411
1485
  let keyCode = this.generate(key, 'value');
1412
1486
  let isNeg = meta(sexpr[0], 'invert');
1413
- if (Array.isArray(container) && container[0] === 'object') {
1487
+ if (this.is(container, 'object')) {
1414
1488
  let result = `(${keyCode} in ${this.generate(container, 'value')})`;
1415
1489
  return isNeg ? `(!${result})` : result;
1416
1490
  }
@@ -1438,7 +1512,7 @@ export class CodeGenerator {
1438
1512
 
1439
1513
  generateNew(head, rest) {
1440
1514
  let [call] = rest;
1441
- if (Array.isArray(call) && (call[0] === '.' || call[0] === '?.')) {
1515
+ if ((this.is(call, '.') || this.is(call, '?.'))) {
1442
1516
  let [accType, target, prop] = call;
1443
1517
  if (Array.isArray(target) && !target[0].startsWith) {
1444
1518
  return `(${this.generate(['new', target], 'value')}).${prop}`;
@@ -1479,7 +1553,7 @@ export class CodeGenerator {
1479
1553
  let codes = elements.map(el => {
1480
1554
  if (el === ',') return '';
1481
1555
  if (el === '...') return '';
1482
- if (Array.isArray(el) && el[0] === '...') return `...${this.generate(el[1], 'value')}`;
1556
+ if (this.is(el, '...')) return `...${this.generate(el[1], 'value')}`;
1483
1557
  return this.generate(el, 'value');
1484
1558
  }).join(', ');
1485
1559
  return hasTrailingElision ? `[${codes},]` : `[${codes}]`;
@@ -1494,11 +1568,11 @@ export class CodeGenerator {
1494
1568
  }
1495
1569
 
1496
1570
  let codes = pairs.map(pair => {
1497
- if (Array.isArray(pair) && pair[0] === '...') return `...${this.generate(pair[1], 'value')}`;
1571
+ if (this.is(pair, '...')) return `...${this.generate(pair[1], 'value')}`;
1498
1572
  let [key, value, operator] = pair;
1499
1573
  let keyCode;
1500
- if (Array.isArray(key) && key[0] === 'dynamicKey') keyCode = `[${this.generate(key[1], 'value')}]`;
1501
- else if (Array.isArray(key) && key[0] === 'str') keyCode = `[${this.generate(key, 'value')}]`;
1574
+ if (this.is(key, 'dynamicKey')) keyCode = `[${this.generate(key[1], 'value')}]`;
1575
+ else if (this.is(key, 'str')) keyCode = `[${this.generate(key, 'value')}]`;
1502
1576
  else keyCode = this.generate(key, 'value');
1503
1577
  let valCode = this.generate(value, 'value');
1504
1578
  if (operator === '=') return `${keyCode} = ${valCode}`;
@@ -1533,22 +1607,22 @@ export class CodeGenerator {
1533
1607
  let needsReturns = context === 'value';
1534
1608
  let tryCode = 'try ';
1535
1609
  let tryBlock = rest[0];
1536
- tryCode += (needsReturns && Array.isArray(tryBlock) && tryBlock[0] === 'block')
1610
+ tryCode += (needsReturns && this.is(tryBlock, 'block'))
1537
1611
  ? this.generateBlockWithReturns(tryBlock) : this.generate(tryBlock, 'statement');
1538
1612
 
1539
1613
  if (rest.length >= 2 && Array.isArray(rest[1]) && rest[1].length === 2 && rest[1][0] !== 'block') {
1540
1614
  let [param, catchBlock] = rest[1];
1541
1615
  tryCode += ' catch';
1542
- if (param && Array.isArray(param) && (param[0] === 'object' || param[0] === 'array')) {
1616
+ if (param && (this.is(param, 'object') || this.is(param, 'array'))) {
1543
1617
  tryCode += ' (error)';
1544
1618
  let destructStmt = `(${this.generate(param, 'value')} = error)`;
1545
- catchBlock = Array.isArray(catchBlock) && catchBlock[0] === 'block'
1619
+ catchBlock = this.is(catchBlock, 'block')
1546
1620
  ? ['block', destructStmt, ...catchBlock.slice(1)]
1547
1621
  : ['block', destructStmt, catchBlock];
1548
1622
  } else if (param) {
1549
1623
  tryCode += ` (${param})`;
1550
1624
  }
1551
- tryCode += ' ' + ((needsReturns && Array.isArray(catchBlock) && catchBlock[0] === 'block')
1625
+ tryCode += ' ' + ((needsReturns && this.is(catchBlock, 'block'))
1552
1626
  ? this.generateBlockWithReturns(catchBlock) : this.generate(catchBlock, 'statement'));
1553
1627
  } else if (rest.length === 2) {
1554
1628
  tryCode += ' finally ' + this.generate(rest[1], 'statement');
@@ -1672,7 +1746,7 @@ export class CodeGenerator {
1672
1746
  let va = Array.isArray(vars) ? vars : [vars];
1673
1747
  let noVar = va.length === 0;
1674
1748
  let [itemVar, indexVar] = noVar ? ['_i', null] : va;
1675
- let ivp = (Array.isArray(itemVar) && (itemVar[0] === 'array' || itemVar[0] === 'object'))
1749
+ let ivp = ((this.is(itemVar, 'array') || this.is(itemVar, 'object')))
1676
1750
  ? this.generateDestructuringPattern(itemVar) : itemVar;
1677
1751
 
1678
1752
  if (step && step !== null) {
@@ -1687,7 +1761,7 @@ export class CodeGenerator {
1687
1761
  this.indentLevel++;
1688
1762
  } else {
1689
1763
  let ic = this.generate(iterable, 'value'), idxN = indexVar || '_i', stc = this.generate(step, 'value');
1690
- let isNeg = this.isNegativeStep(step);
1764
+ let isNeg = this.is(step, '-', 1);
1691
1765
  code += isNeg
1692
1766
  ? this.indent() + `for (let ${idxN} = ${ic}.length - 1; ${idxN} >= 0; ${idxN} += ${stc}) {\n`
1693
1767
  : this.indent() + `for (let ${idxN} = 0; ${idxN} < ${ic}.length; ${idxN} += ${stc}) {\n`;
@@ -1707,7 +1781,7 @@ export class CodeGenerator {
1707
1781
  let own = stepOrOwn;
1708
1782
  let va = Array.isArray(vars) ? vars : [vars];
1709
1783
  let [kv, vv] = va;
1710
- let kvp = (Array.isArray(kv) && (kv[0] === 'array' || kv[0] === 'object'))
1784
+ let kvp = ((this.is(kv, 'array') || this.is(kv, 'object')))
1711
1785
  ? this.generateDestructuringPattern(kv) : kv;
1712
1786
  let oc = this.generate(iterable, 'value');
1713
1787
  code += this.indent() + `for (const ${kvp} in ${oc}) {\n`;
@@ -1718,7 +1792,7 @@ export class CodeGenerator {
1718
1792
  let isAwait = iter[3];
1719
1793
  let va = Array.isArray(vars) ? vars : [vars];
1720
1794
  let [fv] = va;
1721
- let ivp = (Array.isArray(fv) && (fv[0] === 'array' || fv[0] === 'object'))
1795
+ let ivp = ((this.is(fv, 'array') || this.is(fv, 'object')))
1722
1796
  ? this.generateDestructuringPattern(fv) : fv;
1723
1797
  code += this.indent() + `for ${isAwait ? 'await ' : ''}(const ${ivp} of ${this.generate(iterable, 'value')}) {\n`;
1724
1798
  this.indentLevel++;
@@ -1739,7 +1813,7 @@ export class CodeGenerator {
1739
1813
  };
1740
1814
 
1741
1815
  let loopStmts = ['for-in', 'for-of', 'for-as', 'while', 'until', 'loop'];
1742
- if (Array.isArray(expr) && expr[0] === 'block') {
1816
+ if (this.is(expr, 'block')) {
1743
1817
  for (let i = 0; i < expr.length - 1; i++) {
1744
1818
  let s = expr[i + 1], isLast = i === expr.length - 2;
1745
1819
  if (!isLast || hasCtrl(s)) {
@@ -1818,24 +1892,24 @@ export class CodeGenerator {
1818
1892
  // First pass: identify bound methods
1819
1893
  let boundMethods = [];
1820
1894
  for (let [mk, mv] of members) {
1821
- let isStatic = this.isStaticMember(mk);
1822
- let isComputed = this.isComputedMember(mk);
1895
+ let isStatic = this.is(mk, '.') && mk[1] === 'this';
1896
+ let isComputed = this.is(mk, 'computed');
1823
1897
  let mName = this.extractMemberName(mk);
1824
- if (this.isBoundMethod(mv) && !isStatic && !isComputed && mName !== 'constructor') boundMethods.push(mName);
1898
+ if (this.is(mv, '=>') && !isStatic && !isComputed && mName !== 'constructor') boundMethods.push(mName);
1825
1899
  }
1826
1900
 
1827
1901
  // Second pass: generate members
1828
1902
  for (let [mk, mv] of members) {
1829
- let isStatic = this.isStaticMember(mk);
1830
- let isComputed = this.isComputedMember(mk);
1903
+ let isStatic = this.is(mk, '.') && mk[1] === 'this';
1904
+ let isComputed = this.is(mk, 'computed');
1831
1905
  let mName = this.extractMemberName(mk);
1832
- if (Array.isArray(mv) && (mv[0] === '->' || mv[0] === '=>')) {
1906
+ if ((this.is(mv, '->') || this.is(mv, '=>'))) {
1833
1907
  let [, params, body] = mv;
1834
1908
  let hasAwait = this.containsAwait(body), hasYield = this.containsYield(body);
1835
1909
  let cleanParams = params, autoAssign = [];
1836
1910
  if (mName === 'constructor') {
1837
1911
  cleanParams = params.map(p => {
1838
- if (Array.isArray(p) && p[0] === '.' && p[1] === 'this') { autoAssign.push(`this.${p[2]} = ${p[2]}`); return p[2]; }
1912
+ if (this.is(p, '.') && p[1] === 'this') { autoAssign.push(`this.${p[2]} = ${p[2]}`); return p[2]; }
1839
1913
  return p;
1840
1914
  });
1841
1915
  for (let bm of boundMethods) autoAssign.unshift(`this.${bm} = this.${bm}.bind(this)`);
@@ -1859,8 +1933,8 @@ export class CodeGenerator {
1859
1933
  let additionalStmts = bodyStmts.slice(1);
1860
1934
  this.indentLevel++;
1861
1935
  for (let [mk, mv] of members) {
1862
- let isStatic = this.isStaticMember(mk), mName = this.extractMemberName(mk);
1863
- if (Array.isArray(mv) && (mv[0] === '->' || mv[0] === '=>')) {
1936
+ let isStatic = this.is(mk, '.') && mk[1] === 'this', mName = this.extractMemberName(mk);
1937
+ if ((this.is(mv, '->') || this.is(mv, '=>'))) {
1864
1938
  let [, params, body] = mv;
1865
1939
  let pList = this.generateParamList(params);
1866
1940
  let prefix = (isStatic ? 'static ' : '') + (this.containsAwait(body) ? 'async ' : '') + (this.containsYield(body) ? '*' : '');
@@ -1876,9 +1950,9 @@ export class CodeGenerator {
1876
1950
  }
1877
1951
  }
1878
1952
  for (let stmt of additionalStmts) {
1879
- if (Array.isArray(stmt) && stmt[0] === 'class') {
1953
+ if (this.is(stmt, 'class')) {
1880
1954
  let [, nestedName, parent, ...nestedBody] = stmt;
1881
- if (Array.isArray(nestedName) && nestedName[0] === '.' && nestedName[1] === 'this') {
1955
+ if (this.is(nestedName, '.') && nestedName[1] === 'this') {
1882
1956
  code += this.indent() + `static ${nestedName[2]} = ${this.generate(['class', null, parent, ...nestedBody], 'value')};\n`;
1883
1957
  }
1884
1958
  } else {
@@ -1889,7 +1963,7 @@ export class CodeGenerator {
1889
1963
  } else {
1890
1964
  this.indentLevel++;
1891
1965
  for (let stmt of bodyStmts) {
1892
- if (Array.isArray(stmt) && stmt[0] === '=' && Array.isArray(stmt[1]) && stmt[1][0] === '.' && stmt[1][1] === 'this') {
1966
+ if (this.is(stmt, '=') && Array.isArray(stmt[1]) && stmt[1][0] === '.' && stmt[1][1] === 'this') {
1893
1967
  code += this.indent() + `static ${stmt[1][2]} = ${this.generate(stmt[2], 'value')};\n`;
1894
1968
  } else {
1895
1969
  code += this.indent() + this.generate(stmt, 'statement') + ';\n';
@@ -1944,13 +2018,13 @@ export class CodeGenerator {
1944
2018
  generateExport(head, rest) {
1945
2019
  let [decl] = rest;
1946
2020
  if (Array.isArray(decl) && decl.every(i => typeof i === 'string')) return `export { ${decl.join(', ')} }`;
1947
- if (Array.isArray(decl) && decl[0] === '=') return `export const ${decl[1]} = ${this.generate(decl[2], 'value')}`;
2021
+ if (this.is(decl, '=')) return `export const ${decl[1]} = ${this.generate(decl[2], 'value')}`;
1948
2022
  return `export ${this.generate(decl, 'statement')}`;
1949
2023
  }
1950
2024
 
1951
2025
  generateExportDefault(head, rest) {
1952
2026
  let [expr] = rest;
1953
- if (Array.isArray(expr) && expr[0] === '=') {
2027
+ if (this.is(expr, '=')) {
1954
2028
  return `const ${expr[1]} = ${this.generate(expr[2], 'value')};\nexport default ${expr[1]}`;
1955
2029
  }
1956
2030
  return `export default ${this.generate(expr, 'statement')}`;
@@ -2042,14 +2116,14 @@ export class CodeGenerator {
2042
2116
  generateDestructuringPattern(pattern) { return this.formatParam(pattern); }
2043
2117
 
2044
2118
  generateParamList(params) {
2045
- let expIdx = params.findIndex(p => Array.isArray(p) && p[0] === 'expansion');
2119
+ let expIdx = params.findIndex(p => this.is(p, 'expansion'));
2046
2120
  if (expIdx !== -1) {
2047
2121
  let before = params.slice(0, expIdx), after = params.slice(expIdx + 1);
2048
2122
  let regular = before.map(p => this.formatParam(p)).join(', ');
2049
2123
  this.expansionAfterParams = after;
2050
2124
  return regular ? `${regular}, ..._rest` : '..._rest';
2051
2125
  }
2052
- let restIdx = params.findIndex(p => Array.isArray(p) && p[0] === 'rest');
2126
+ let restIdx = params.findIndex(p => this.is(p, 'rest'));
2053
2127
  if (restIdx !== -1 && restIdx < params.length - 1) {
2054
2128
  let before = params.slice(0, restIdx), restP = params[restIdx], after = params.slice(restIdx + 1);
2055
2129
  let beforeP = before.map(p => this.formatParam(p));
@@ -2064,24 +2138,24 @@ export class CodeGenerator {
2064
2138
  formatParam(param) {
2065
2139
  if (typeof param === 'string') return param;
2066
2140
  if (param instanceof String) return param.valueOf();
2067
- if (Array.isArray(param) && param[0] === 'rest') return `...${param[1]}`;
2068
- if (Array.isArray(param) && param[0] === 'default') return `${param[1]} = ${this.generate(param[2], 'value')}`;
2069
- if (Array.isArray(param) && param[0] === '.' && param[1] === 'this') return param[2];
2070
- if (Array.isArray(param) && param[0] === 'array') {
2141
+ if (this.is(param, 'rest')) return `...${param[1]}`;
2142
+ if (this.is(param, 'default')) return `${param[1]} = ${this.generate(param[2], 'value')}`;
2143
+ if (this.is(param, '.') && param[1] === 'this') return param[2];
2144
+ if (this.is(param, 'array')) {
2071
2145
  let els = param.slice(1).map(el => {
2072
2146
  if (el === ',') return '';
2073
2147
  if (el === '...') return '';
2074
- if (Array.isArray(el) && el[0] === '...') return `...${el[1]}`;
2075
- if (Array.isArray(el) && el[0] === '=' && typeof el[1] === 'string') return `${el[1]} = ${this.generate(el[2], 'value')}`;
2148
+ if (this.is(el, '...')) return `...${el[1]}`;
2149
+ if (this.is(el, '=') && typeof el[1] === 'string') return `${el[1]} = ${this.generate(el[2], 'value')}`;
2076
2150
  if (typeof el === 'string') return el;
2077
2151
  return this.formatParam(el);
2078
2152
  });
2079
2153
  return `[${els.join(', ')}]`;
2080
2154
  }
2081
- if (Array.isArray(param) && param[0] === 'object') {
2155
+ if (this.is(param, 'object')) {
2082
2156
  let pairs = param.slice(1).map(pair => {
2083
- if (Array.isArray(pair) && pair[0] === '...') return `...${pair[1]}`;
2084
- if (Array.isArray(pair) && pair[0] === 'default') return `${pair[1]} = ${this.generate(pair[2], 'value')}`;
2157
+ if (this.is(pair, '...')) return `...${pair[1]}`;
2158
+ if (this.is(pair, 'default')) return `${pair[1]} = ${this.generate(pair[2], 'value')}`;
2085
2159
  let [key, value] = pair;
2086
2160
  if (key === value) return key;
2087
2161
  return `${key}: ${value}`;
@@ -2116,7 +2190,7 @@ export class CodeGenerator {
2116
2190
  let noRetStmts = ['return', 'throw', 'break', 'continue'];
2117
2191
  let loopStmts = ['for-in', 'for-of', 'for-as', 'while', 'until', 'loop'];
2118
2192
 
2119
- if (Array.isArray(body) && body[0] === 'block') {
2193
+ if (this.is(body, 'block')) {
2120
2194
  let statements = this.unwrapBlock(body);
2121
2195
 
2122
2196
  if (hasExpansionParams && this.expansionAfterParams?.length > 0) {
@@ -2133,7 +2207,7 @@ export class CodeGenerator {
2133
2207
  let afterCount = afterParams.length;
2134
2208
  let extr = [];
2135
2209
  afterParams.forEach((p, i) => {
2136
- let pn = typeof p === 'string' ? p : (Array.isArray(p) && p[0] === 'default') ? p[1] : JSON.stringify(p);
2210
+ let pn = typeof p === 'string' ? p : (this.is(p, 'default')) ? p[1] : JSON.stringify(p);
2137
2211
  extr.push(`const ${pn} = ${restName}[${restName}.length - ${afterCount - i}]`);
2138
2212
  });
2139
2213
  if (afterCount > 0) extr.push(`${restName} = ${restName}.slice(0, -${afterCount})`);
@@ -2161,7 +2235,7 @@ export class CodeGenerator {
2161
2235
 
2162
2236
  if (!isConstructor && !sideEffectOnly && isLast && (h === 'if' || h === 'unless')) {
2163
2237
  let [cond, thenB, ...elseB] = stmt.slice(1);
2164
- let hasMulti = (b) => Array.isArray(b) && b[0] === 'block' && b.length > 2;
2238
+ let hasMulti = (b) => this.is(b, 'block') && b.length > 2;
2165
2239
  if (hasMulti(thenB) || elseB.some(hasMulti)) {
2166
2240
  code += this.generateIfElseWithEarlyReturns(stmt);
2167
2241
  return;
@@ -2252,7 +2326,7 @@ export class CodeGenerator {
2252
2326
  if (body[0] === 'block' || Array.isArray(body[0])) {
2253
2327
  let stmts = body[0] === 'block' ? body.slice(1) : body;
2254
2328
  let lines = this.withIndent(() => stmts.map(s => {
2255
- if (Array.isArray(s) && s[0] === 'comprehension') {
2329
+ if (this.is(s, 'comprehension')) {
2256
2330
  let [, expr, iters, guards] = s;
2257
2331
  return this.indent() + this.generateComprehensionAsLoop(expr, iters, guards);
2258
2332
  }
@@ -2289,7 +2363,7 @@ export class CodeGenerator {
2289
2363
  generateComprehensionWithTarget(expr, iterators, guards, targetVar) {
2290
2364
  let code = '';
2291
2365
  code += this.indent() + `${targetVar} = [];\n`;
2292
- let unwrappedExpr = (Array.isArray(expr) && expr[0] === 'block' && expr.length === 2) ? expr[1] : expr;
2366
+ let unwrappedExpr = (this.is(expr, 'block') && expr.length === 2) ? expr[1] : expr;
2293
2367
 
2294
2368
  if (iterators.length === 1) {
2295
2369
  let [iterType, vars, iterable, stepOrOwn] = iterators[0];
@@ -2298,7 +2372,7 @@ export class CodeGenerator {
2298
2372
  let va = Array.isArray(vars) ? vars : [vars];
2299
2373
  let noVar = va.length === 0;
2300
2374
  let [itemVar, indexVar] = noVar ? ['_i', null] : va;
2301
- let ivp = (Array.isArray(itemVar) && (itemVar[0] === 'array' || itemVar[0] === 'object'))
2375
+ let ivp = ((this.is(itemVar, 'array') || this.is(itemVar, 'object')))
2302
2376
  ? this.generateDestructuringPattern(itemVar) : itemVar;
2303
2377
 
2304
2378
  if (step && step !== null) {
@@ -2311,7 +2385,7 @@ export class CodeGenerator {
2311
2385
  code += this.indent() + `for (let ${ivp} = ${this.generate(s, 'value')}; ${ivp} ${isExcl ? '<' : '<='} ${this.generate(e, 'value')}; ${ivp} += ${this.generate(step, 'value')}) {\n`;
2312
2386
  } else {
2313
2387
  let ic = this.generate(iterable, 'value'), idxN = indexVar || '_i', stc = this.generate(step, 'value');
2314
- let isNeg = this.isNegativeStep(step);
2388
+ let isNeg = this.is(step, '-', 1);
2315
2389
  code += isNeg
2316
2390
  ? this.indent() + `for (let ${idxN} = ${ic}.length - 1; ${idxN} >= 0; ${idxN} += ${stc}) {\n`
2317
2391
  : this.indent() + `for (let ${idxN} = 0; ${idxN} < ${ic}.length; ${idxN} += ${stc}) {\n`;
@@ -2346,7 +2420,7 @@ export class CodeGenerator {
2346
2420
  let va = Array.isArray(vars) ? vars : [vars];
2347
2421
  let noVar = va.length === 0;
2348
2422
  let [itemVar, indexVar] = noVar ? ['_i', null] : va;
2349
- let ivp = (Array.isArray(itemVar) && (itemVar[0] === 'array' || itemVar[0] === 'object'))
2423
+ let ivp = ((this.is(itemVar, 'array') || this.is(itemVar, 'object')))
2350
2424
  ? this.generateDestructuringPattern(itemVar) : itemVar;
2351
2425
 
2352
2426
  if (step && step !== null) {
@@ -2359,7 +2433,7 @@ export class CodeGenerator {
2359
2433
  code += `for (let ${ivp} = ${this.generate(s, 'value')}; ${ivp} ${isExcl ? '<' : '<='} ${this.generate(e, 'value')}; ${ivp} += ${this.generate(step, 'value')}) `;
2360
2434
  } else {
2361
2435
  let ic = this.generate(iterable, 'value'), idxN = indexVar || '_i', stc = this.generate(step, 'value');
2362
- let isNeg = this.isNegativeStep(step);
2436
+ let isNeg = this.is(step, '-', 1);
2363
2437
  let isMinus1 = isNeg && (step[1] === '1' || step[1] === 1 || str(step[1]) === '1');
2364
2438
  let isPlus1 = !isNeg && (step === '1' || step === 1 || str(step) === '1');
2365
2439
  if (isMinus1) code += `for (let ${idxN} = ${ic}.length - 1; ${idxN} >= 0; ${idxN}--) `;
@@ -2427,7 +2501,7 @@ export class CodeGenerator {
2427
2501
  if (iterType === 'for-as') {
2428
2502
  let va = Array.isArray(vars) ? vars : [vars];
2429
2503
  let [fv] = va;
2430
- let ivp = (Array.isArray(fv) && (fv[0] === 'array' || fv[0] === 'object'))
2504
+ let ivp = ((this.is(fv, 'array') || this.is(fv, 'object')))
2431
2505
  ? this.generateDestructuringPattern(fv) : fv;
2432
2506
  code += `for (const ${ivp} of ${this.generate(iterable, 'value')}) `;
2433
2507
  if (guards?.length) {
@@ -2509,7 +2583,7 @@ export class CodeGenerator {
2509
2583
  code += this.indent() + '}';
2510
2584
  for (let branch of elseBranches) {
2511
2585
  code += ' else ';
2512
- if (Array.isArray(branch) && branch[0] === 'if') {
2586
+ if (this.is(branch, 'if')) {
2513
2587
  let [, nc, nt, ...ne] = branch;
2514
2588
  code += `if (${this.generate(nc, 'value')}) {\n`;
2515
2589
  code += this.withIndent(() => this.generateBranchWithReturn(nt));
@@ -2538,8 +2612,8 @@ export class CodeGenerator {
2538
2612
  }
2539
2613
 
2540
2614
  generateIfAsExpression(condition, thenBranch, elseBranches) {
2541
- let needsIIFE = this.isMultiStatementBlock(thenBranch) || this.hasStatementInBranch(thenBranch) ||
2542
- elseBranches.some(b => this.isMultiStatementBlock(b) || this.hasStatementInBranch(b) || this.hasNestedMultiStatement(b));
2615
+ let needsIIFE = this.is(thenBranch, 'block') && thenBranch.length > 2 || this.hasStatementInBranch(thenBranch) ||
2616
+ elseBranches.some(b => this.is(b, 'block') && b.length > 2 || this.hasStatementInBranch(b) || this.hasNestedMultiStatement(b));
2543
2617
  if (needsIIFE) {
2544
2618
  let hasAwait = this.containsAwait(condition) || this.containsAwait(thenBranch) || elseBranches.some(b => this.containsAwait(b));
2545
2619
  let code = `${hasAwait ? 'await ' : ''}(${hasAwait ? 'async ' : ''}() => { `;
@@ -2547,13 +2621,13 @@ export class CodeGenerator {
2547
2621
  code += this.generateBlockWithReturns(thenBranch);
2548
2622
  for (let branch of elseBranches) {
2549
2623
  code += ' else ';
2550
- if (Array.isArray(branch) && branch[0] === 'if') {
2624
+ if (this.is(branch, 'if')) {
2551
2625
  let [_, nc, nt, ...ne] = branch;
2552
2626
  code += `if (${this.generate(nc, 'value')}) `;
2553
2627
  code += this.generateBlockWithReturns(nt);
2554
2628
  for (let nb of ne) {
2555
2629
  code += ' else ';
2556
- if (Array.isArray(nb) && nb[0] === 'if') {
2630
+ if (this.is(nb, 'if')) {
2557
2631
  let [__, nnc, nnt, ...nne] = nb;
2558
2632
  code += `if (${this.generate(nnc, 'value')}) `;
2559
2633
  code += this.generateBlockWithReturns(nnt);
@@ -2571,7 +2645,7 @@ export class CodeGenerator {
2571
2645
  let thenExpr = this.extractExpression(this.unwrapIfBranch(thenBranch));
2572
2646
  let elseExpr = this.buildTernaryChain(elseBranches);
2573
2647
  let condCode = this.generate(condition, 'value');
2574
- if (Array.isArray(condition) && (condition[0] === 'yield' || condition[0] === 'await')) condCode = `(${condCode})`;
2648
+ if ((this.is(condition, 'yield') || this.is(condition, 'await'))) condCode = `(${condCode})`;
2575
2649
  return `(${condCode} ? ${thenExpr} : ${elseExpr})`;
2576
2650
  }
2577
2651
 
@@ -2588,7 +2662,7 @@ export class CodeGenerator {
2588
2662
  if (hasFlow) {
2589
2663
  for (let s of this.unwrapBlock(body)) code += this.indent() + this.generate(s, 'statement') + ';\n';
2590
2664
  } else if (context === 'value') {
2591
- if (Array.isArray(body) && body[0] === 'block' && body.length > 2) {
2665
+ if (this.is(body, 'block') && body.length > 2) {
2592
2666
  let stmts = body.slice(1);
2593
2667
  for (let i = 0; i < stmts.length; i++) {
2594
2668
  if (i === stmts.length - 1) code += this.indent() + `return ${this.generate(stmts[i], 'value')};\n`;
@@ -2598,7 +2672,7 @@ export class CodeGenerator {
2598
2672
  code += this.indent() + `return ${this.extractExpression(body)};\n`;
2599
2673
  }
2600
2674
  } else {
2601
- if (Array.isArray(body) && body[0] === 'block' && body.length > 1) {
2675
+ if (this.is(body, 'block') && body.length > 1) {
2602
2676
  for (let s of body.slice(1)) code += this.indent() + this.generate(s, 'statement') + ';\n';
2603
2677
  } else {
2604
2678
  code += this.indent() + this.generate(body, 'statement') + ';\n';
@@ -2702,14 +2776,17 @@ export class CodeGenerator {
2702
2776
  return result;
2703
2777
  }
2704
2778
 
2705
- isNegativeStep(step) {
2706
- if (!Array.isArray(step) || step.length !== 2) return false;
2707
- return (str(step[0]) ?? step[0]) === '-';
2708
- }
2709
-
2710
- isNegativeOneLiteral(sexpr) {
2711
- return Array.isArray(sexpr) && sexpr[0] === '-' && sexpr.length === 2 &&
2712
- (sexpr[1] === '1' || sexpr[1] === 1 || str(sexpr[1]) === '1');
2779
+ // S-expression pattern match: is(node, op, arity?) → args or null
2780
+ // is(node, '-', 1) on ["-", 5] → [5]
2781
+ // is(node, '[]', 2) on ["[]", a, b] → [a, b]
2782
+ // is(node, 'block') on ["block", ...] → [...]
2783
+ // is("x", '-', 1) → null
2784
+ is(node, op, arity) {
2785
+ if (!Array.isArray(node)) return null;
2786
+ if ((str(node[0]) ?? node[0]) !== op) return null;
2787
+ let args = node.slice(1);
2788
+ if (arity != null && args.length !== arity) return null;
2789
+ return args;
2713
2790
  }
2714
2791
 
2715
2792
  unwrap(code) {
@@ -2772,13 +2849,11 @@ export class CodeGenerator {
2772
2849
  return false;
2773
2850
  }
2774
2851
 
2775
- isMultiStatementBlock(branch) { return Array.isArray(branch) && branch[0] === 'block' && branch.length > 2; }
2776
-
2777
2852
  hasNestedMultiStatement(branch) {
2778
2853
  if (!Array.isArray(branch)) return false;
2779
2854
  if (branch[0] === 'if') {
2780
2855
  let [_, cond, then_, ...elseB] = branch;
2781
- return this.isMultiStatementBlock(then_) || elseB.some(b => this.hasNestedMultiStatement(b));
2856
+ return this.is(then_, 'block') && then_.length > 2 || elseB.some(b => this.hasNestedMultiStatement(b));
2782
2857
  }
2783
2858
  return false;
2784
2859
  }
@@ -2787,7 +2862,7 @@ export class CodeGenerator {
2787
2862
  if (branches.length === 0) return 'undefined';
2788
2863
  if (branches.length === 1) return this.extractExpression(this.unwrapIfBranch(branches[0]));
2789
2864
  let first = branches[0];
2790
- if (Array.isArray(first) && first[0] === 'if') {
2865
+ if (this.is(first, 'if')) {
2791
2866
  let [_, cond, then_, ...rest] = first;
2792
2867
  let thenPart = this.extractExpression(this.unwrapIfBranch(then_));
2793
2868
  let elsePart = this.buildTernaryChain([...rest, ...branches.slice(1)]);
@@ -2886,13 +2961,22 @@ export class CodeGenerator {
2886
2961
  return `'${finalPath}'` + assertion;
2887
2962
  }
2888
2963
 
2964
+ containsIt(sexpr) {
2965
+ if (!sexpr) return false;
2966
+ if (sexpr === 'it' || (sexpr instanceof String && str(sexpr) === 'it')) return true;
2967
+ if (typeof sexpr !== 'object') return false;
2968
+ if (this.is(sexpr, 'def') || this.is(sexpr, '->') || this.is(sexpr, '=>')) return false;
2969
+ if (Array.isArray(sexpr)) return sexpr.some(item => this.containsIt(item));
2970
+ return false;
2971
+ }
2972
+
2889
2973
  containsAwait(sexpr) {
2890
2974
  if (!sexpr) return false;
2891
2975
  if (sexpr instanceof String && meta(sexpr, 'await') === true) return true;
2892
2976
  if (typeof sexpr !== 'object') return false;
2893
- if (Array.isArray(sexpr) && sexpr[0] === 'await') return true;
2894
- if (Array.isArray(sexpr) && sexpr[0] === 'for-as' && sexpr[3] === true) return true;
2895
- if (Array.isArray(sexpr) && (sexpr[0] === 'def' || sexpr[0] === '->' || sexpr[0] === '=>' || sexpr[0] === 'class')) return false;
2977
+ if (this.is(sexpr, 'await')) return true;
2978
+ if (this.is(sexpr, 'for-as') && sexpr[3] === true) return true;
2979
+ if ((this.is(sexpr, 'def') || this.is(sexpr, '->') || this.is(sexpr, '=>') || this.is(sexpr, 'class'))) return false;
2896
2980
  if (Array.isArray(sexpr)) return sexpr.some(item => this.containsAwait(item));
2897
2981
  return false;
2898
2982
  }
@@ -2900,21 +2984,18 @@ export class CodeGenerator {
2900
2984
  containsYield(sexpr) {
2901
2985
  if (!sexpr) return false;
2902
2986
  if (typeof sexpr !== 'object') return false;
2903
- if (Array.isArray(sexpr) && (sexpr[0] === 'yield' || sexpr[0] === 'yield-from')) return true;
2904
- if (Array.isArray(sexpr) && (sexpr[0] === 'def' || sexpr[0] === '->' || sexpr[0] === '=>' || sexpr[0] === 'class')) return false;
2987
+ if ((this.is(sexpr, 'yield') || this.is(sexpr, 'yield-from'))) return true;
2988
+ if ((this.is(sexpr, 'def') || this.is(sexpr, '->') || this.is(sexpr, '=>') || this.is(sexpr, 'class'))) return false;
2905
2989
  if (Array.isArray(sexpr)) return sexpr.some(item => this.containsYield(item));
2906
2990
  return false;
2907
2991
  }
2908
2992
 
2909
2993
  // Class helpers
2910
- isStaticMember(mk) { return Array.isArray(mk) && mk[0] === '.' && mk[1] === 'this'; }
2911
- isComputedMember(mk) { return Array.isArray(mk) && mk[0] === 'computed'; }
2912
2994
  extractMemberName(mk) {
2913
- if (this.isStaticMember(mk)) return mk[2];
2914
- if (this.isComputedMember(mk)) return `[${this.generate(mk[1], 'value')}]`;
2995
+ if (this.is(mk, '.') && mk[1] === 'this') return mk[2];
2996
+ if (this.is(mk, 'computed')) return `[${this.generate(mk[1], 'value')}]`;
2915
2997
  return mk;
2916
2998
  }
2917
- isBoundMethod(mv) { return Array.isArray(mv) && mv[0] === '=>'; }
2918
2999
 
2919
3000
  // ---------------------------------------------------------------------------
2920
3001
  // Reactive Runtime (injected inline when reactive operators are used)
@@ -3166,13 +3247,14 @@ export class Compiler {
3166
3247
  console.log();
3167
3248
  }
3168
3249
 
3169
- // Step 2: Emit .d.ts from annotated tokens (before parsing)
3250
+ // Save annotated tokens for deferred .d.ts emission (after parsing)
3170
3251
  let dts = null;
3252
+ let typeTokens = null;
3171
3253
  if (this.options.types === 'emit' || this.options.types === 'check' || this.options.types === true) {
3172
- dts = emitTypes(tokens);
3254
+ typeTokens = [...tokens];
3173
3255
  }
3174
3256
 
3175
- // Always remove TYPE_DECL markers — the parser doesn't know about them
3257
+ // Remove TYPE_DECL markers — the parser doesn't know about them
3176
3258
  tokens = tokens.filter(t => t[0] !== 'TYPE_DECL');
3177
3259
 
3178
3260
  // Strip leading terminators that may result from removed type declarations
@@ -3180,8 +3262,9 @@ export class Compiler {
3180
3262
  tokens.shift();
3181
3263
  }
3182
3264
 
3183
- // If only terminators remain (type-only source), return early
3265
+ // If only terminators remain (type-only source), emit types and return early
3184
3266
  if (tokens.every(t => t[0] === 'TERMINATOR')) {
3267
+ if (typeTokens) dts = emitTypes(typeTokens, ['program']);
3185
3268
  return { tokens, sexpr: ['program'], code: '', dts, data: dataSection, reactiveVars: {} };
3186
3269
  }
3187
3270
 
@@ -3229,8 +3312,7 @@ export class Compiler {
3229
3312
 
3230
3313
  let generator = new CodeGenerator({
3231
3314
  dataSection,
3232
- skipReactiveRuntime: this.options.skipReactiveRuntime,
3233
- skipComponentRuntime: this.options.skipComponentRuntime,
3315
+ skipPreamble: this.options.skipPreamble,
3234
3316
  reactiveVars: this.options.reactiveVars,
3235
3317
  sourceMap,
3236
3318
  });
@@ -3245,6 +3327,11 @@ export class Compiler {
3245
3327
  code += `\n//# sourceMappingURL=${this.options.filename}.js.map`;
3246
3328
  }
3247
3329
 
3330
+ // Step 5: Emit .d.ts from annotated tokens + parsed s-expression
3331
+ if (typeTokens) {
3332
+ dts = emitTypes(typeTokens, sexpr);
3333
+ }
3334
+
3248
3335
  return { tokens, sexpr, code, dts, map, reverseMap, data: dataSection, reactiveVars: generator.reactiveVars };
3249
3336
  }
3250
3337