gofront 0.0.5 → 0.0.7

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.
@@ -147,6 +147,15 @@ export const expressionGenMethods = {
147
147
  return "Number.MIN_SAFE_INTEGER";
148
148
  }
149
149
  }
150
+ // io constants
151
+ if (expr.expr.kind === "Ident" && expr.expr.name === "io") {
152
+ switch (expr.field) {
153
+ case "EOF":
154
+ return '"EOF"';
155
+ case "Discard":
156
+ return "{ WriteString(s) { return s.length; }, Write(b) { return b.length; } }";
157
+ }
158
+ }
150
159
  // os constants/vars
151
160
  if (expr.expr.kind === "Ident" && expr.expr.name === "os") {
152
161
  switch (expr.field) {
@@ -184,7 +193,11 @@ export const expressionGenMethods = {
184
193
  }
185
194
 
186
195
  case "IndexExpr": {
187
- const base = this.genExpr(expr.expr);
196
+ const exprType = expr.expr._type;
197
+ const wrapField = this._namedWrapperField(exprType, expr.expr);
198
+ const base = wrapField
199
+ ? `${this.genExpr(expr.expr)}.${wrapField}`
200
+ : this.genExpr(expr.expr);
188
201
  const idx = this.genExpr(expr.index);
189
202
  if (expr._mapValueType && !expr._lvalue) {
190
203
  const zero = this.zeroValueForType(expr._mapValueType);
@@ -196,7 +209,6 @@ export const expressionGenMethods = {
196
209
  return `(${base}[${idx}] ?? ${zero})`;
197
210
  }
198
211
  // String indexing returns a byte (charCodeAt), not a JS character
199
- const exprType = expr.expr._type;
200
212
  const isStr =
201
213
  (exprType?.kind === "basic" && exprType.name === "string") ||
202
214
  (exprType?.kind === "untyped" && exprType.base === "string");
@@ -212,6 +224,7 @@ export const expressionGenMethods = {
212
224
  const hi = expr.high ? this.genExpr(expr.high) : "";
213
225
  if (!lo && !hi) return `${base}.slice()`;
214
226
  if (!hi) return `${base}.slice(${lo})`;
227
+ if (!lo) return `${base}.slice(0, ${hi})`;
215
228
  return `${base}.slice(${lo}, ${hi})`;
216
229
  }
217
230
 
@@ -284,6 +297,10 @@ export const expressionGenMethods = {
284
297
  this._usesError = true;
285
298
  return `__error(${inner})`;
286
299
  }
300
+ // Named wrapper type: NodeFunc(fn) → new NodeFunc(fn)
301
+ if (target && this.namedWrapperNames.has(target)) {
302
+ return `new ${target}(${inner})`;
303
+ }
287
304
  switch (target) {
288
305
  case "string": {
289
306
  // Go string(65) → "A" (Unicode code point), not "65"
@@ -339,6 +356,16 @@ export const expressionGenMethods = {
339
356
  },
340
357
 
341
358
  genCall(expr) {
359
+ // Named type constructor call: Greeter(fn), NodeFunc(fn) → new Greeter(fn)
360
+ if (expr._isTypeConversion) {
361
+ const name = expr._conversionTargetType?.name;
362
+ const arg = this.genExpr(expr.args[0]);
363
+ if (name && this.namedWrapperNames.has(name)) {
364
+ return `new ${name}(${arg})`;
365
+ }
366
+ return arg; // non-wrapper named types: identity
367
+ }
368
+
342
369
  // Unwrap InstantiationExpr for function calls: Foo[int](42) → Foo(42)
343
370
  const funcExpr =
344
371
  expr.func.kind === "InstantiationExpr" ? expr.func.expr : expr.func;
@@ -351,6 +378,10 @@ export const expressionGenMethods = {
351
378
  if (expr._constLen != null) return String(expr._constLen);
352
379
  const arg = expr.args[0];
353
380
  const t = arg?._type;
381
+ const wrapField = this._namedWrapperField(t, arg);
382
+ if (wrapField) {
383
+ return `${this.genExpr(arg)}.${wrapField}.length`;
384
+ }
354
385
  const js = this.genExpr(arg);
355
386
  if (t?.kind === "map") return `Object.keys(${js}).length`;
356
387
  this._usesLen = true;
@@ -443,6 +474,19 @@ export const expressionGenMethods = {
443
474
  if (result !== undefined) return result;
444
475
  }
445
476
 
477
+ // strings.Builder / bytes.Buffer / regexp.Regexp method dispatch
478
+ if (expr.func.kind === "SelectorExpr") {
479
+ const recvType = expr.func.expr._type;
480
+ const recvName = recvType?.name ?? recvType?.base?.name;
481
+ if (recvName === "strings.Builder" || recvName === "bytes.Buffer") {
482
+ const typeName = recvType?.name ?? recvType?.base?.name;
483
+ return this._genBuilderCall(typeName, expr.func.field, expr);
484
+ }
485
+ if (recvName === "regexp.Regexp") {
486
+ return this._genRegexpMethodCall(expr.func.field, expr);
487
+ }
488
+ }
489
+
446
490
  // Mark the callee so SelectorExpr knows it's being called, not used as a value
447
491
  expr.func._callee = true;
448
492
  const rawFn = this.genExpr(expr.func);
@@ -459,6 +503,18 @@ export const expressionGenMethods = {
459
503
  },
460
504
 
461
505
  genAppend(expr) {
506
+ const retType = expr._type;
507
+ const wrapField = this._namedWrapperField(retType, expr.args[0]);
508
+ if (wrapField) {
509
+ // Named slice wrapper: append(g, x) → new G(__append(g._items, x))
510
+ const sliceJS = `${this.genExpr(expr.args[0])}.${wrapField}`;
511
+ const elems = expr.args
512
+ .slice(1)
513
+ .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)));
514
+ if (elems.length === 0) return this.genExpr(expr.args[0]);
515
+ this._usesAppend = true;
516
+ return `new ${retType.name}(__append(${sliceJS}, ${elems.join(", ")}))`;
517
+ }
462
518
  const slice = this.genExpr(expr.args[0]);
463
519
  const elems = expr.args
464
520
  .slice(1)
@@ -543,6 +599,34 @@ export const expressionGenMethods = {
543
599
  return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
544
600
  }
545
601
 
602
+ // Named wrapper type: Group{a, b} → new Group([a, b])
603
+ if (typeName && this.namedWrapperNames.has(typeName)) {
604
+ const namedType = this.checker?.types.get(typeName);
605
+ const u = namedType?.underlying;
606
+ if (u?.kind === "map") {
607
+ const entries = expr.elems
608
+ .map((e) => {
609
+ if (e.kind === "KeyValueExpr") {
610
+ const k =
611
+ e.key.litKind === "STRING"
612
+ ? JSON.stringify(e.key.value)
613
+ : `[${this.genExpr(e.key)}]`;
614
+ return `${k}: ${this.genExpr(e.value)}`;
615
+ }
616
+ return this.genExpr(e);
617
+ })
618
+ .join(", ");
619
+ return `new ${typeName}({ ${entries} })`;
620
+ }
621
+ // Default: slice
622
+ const elems = expr.elems
623
+ .map((e) =>
624
+ e.kind === "KeyValueExpr" ? this.genExpr(e.value) : this.genExpr(e),
625
+ )
626
+ .join(", ");
627
+ return `new ${typeName}([${elems}])`;
628
+ }
629
+
546
630
  // Fallback: named type that's not a struct but has positional elements → treat as struct
547
631
  if (expr.elems.some((e) => e._positionalField)) {
548
632
  if (typeName) {
@@ -604,7 +688,8 @@ export const expressionGenMethods = {
604
688
  if (typeNode.kind === "Ident") return typeNode.name;
605
689
  if (typeNode.kind === "InstantiationExpr")
606
690
  return this.getTypeName(typeNode.expr);
607
- if (typeNode.kind === "SelectorExpr") return typeNode.field;
691
+ if (typeNode.kind === "SelectorExpr")
692
+ return `${this.getTypeName(typeNode.expr)}.${typeNode.field}`;
608
693
  return null;
609
694
  },
610
695
 
@@ -662,6 +747,8 @@ export const expressionGenMethods = {
662
747
  case "TypeName": {
663
748
  const basic = this._zeroForBasicName(typeNode.name);
664
749
  if (basic !== null) return basic;
750
+ if (typeNode.name === "strings.Builder") return '{ _buf: "" }';
751
+ if (typeNode.name === "bytes.Buffer") return "{ _buf: [] }";
665
752
  if (this.structNames.has(typeNode.name))
666
753
  return `new ${typeNode.name}()`;
667
754
  return "null";
@@ -706,6 +793,8 @@ export const expressionGenMethods = {
706
793
  return `{ ${fields} }`;
707
794
  }
708
795
  case "named":
796
+ if (t.name === "strings.Builder") return '{ _buf: "" }';
797
+ if (t.name === "bytes.Buffer") return "{ _buf: [] }";
709
798
  if (this.structNames.has(t.name)) return `new ${t.name}()`;
710
799
  return "null";
711
800
  default:
@@ -776,6 +865,294 @@ export const expressionGenMethods = {
776
865
  return base?.kind === "struct" || base?.kind === "array";
777
866
  },
778
867
 
868
+ _genBuilderCall(typeName, method, expr) {
869
+ // Resolve the receiver — may be a direct var or a &var (pointer)
870
+ const recv = expr.func.expr;
871
+ const isPtr = recv._type?.kind === "pointer";
872
+ const base = isPtr ? `${this.genExpr(recv)}.value` : this.genExpr(recv);
873
+ const args = expr.args.map((a) => this.genExpr(a));
874
+
875
+ if (typeName === "strings.Builder") {
876
+ switch (method) {
877
+ case "WriteString":
878
+ return `(${base}._buf += ${args[0]}, [${args[0]}.length, null])`;
879
+ case "WriteByte":
880
+ case "WriteRune":
881
+ return `(${base}._buf += String.fromCodePoint(${args[0]}))`;
882
+ case "Write":
883
+ return `(${base}._buf += String.fromCharCode(...${args[0]}))`;
884
+ case "String":
885
+ return `${base}._buf`;
886
+ case "Len":
887
+ return `${base}._buf.length`;
888
+ case "Reset":
889
+ return `(${base}._buf = "")`;
890
+ case "Grow":
891
+ return "undefined";
892
+ }
893
+ }
894
+
895
+ if (typeName === "bytes.Buffer") {
896
+ switch (method) {
897
+ case "WriteString":
898
+ return `(${base}._buf.push(...new TextEncoder().encode(${args[0]})), [${args[0]}.length, null])`;
899
+ case "WriteByte":
900
+ return `${base}._buf.push(${args[0]})`;
901
+ case "Write":
902
+ return `(${base}._buf.push(...${args[0]}), [${args[0]}.length, null])`;
903
+ case "String":
904
+ return `new TextDecoder().decode(new Uint8Array(${base}._buf))`;
905
+ case "Bytes":
906
+ return `${base}._buf.slice()`;
907
+ case "Len":
908
+ return `${base}._buf.length`;
909
+ case "Reset":
910
+ return `(${base}._buf = [])`;
911
+ case "Grow":
912
+ return "undefined";
913
+ }
914
+ }
915
+
916
+ return undefined;
917
+ },
918
+
919
+ _genRegexp(fn, a) {
920
+ // Converts a Go regexp pattern string (which may contain inline flags like (?i))
921
+ // into a JS RegExp, extracting the flags into the second constructor argument.
922
+ const makeRegExp = (pat) =>
923
+ `((p) => { const m = /^\\(\\?([gimsuy]+)\\)/.exec(p); return m ? new RegExp(p.slice(m[0].length), m[1]) : new RegExp(p); })(${pat})`;
924
+ switch (fn) {
925
+ case "MustCompile":
926
+ return makeRegExp(a()[0]);
927
+ case "Compile":
928
+ return `[${makeRegExp(a()[0])}, null]`;
929
+ case "MatchString": {
930
+ const [pat, s] = a();
931
+ return `[new RegExp(${pat}).test(${s}), null]`;
932
+ }
933
+ case "QuoteMeta":
934
+ return `${a()[0]}.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&')`;
935
+ }
936
+ return undefined;
937
+ },
938
+
939
+ _genRegexpMethodCall(method, expr) {
940
+ const recv = expr.func.expr;
941
+ // *Regexp is a plain JS RegExp — no .value boxing needed
942
+ const re = this.genExpr(recv);
943
+ const args = expr.args.map((a) => this.genExpr(a));
944
+
945
+ switch (method) {
946
+ case "MatchString":
947
+ return `${re}.test(${args[0]})`;
948
+ case "FindString":
949
+ return `(${re}.exec(${args[0]})?.[0] ?? "")`;
950
+ case "FindStringIndex":
951
+ return `((m => m ? [m.index, m.index + m[0].length] : null)(${re}.exec(${args[0]})))`;
952
+ case "FindAllString":
953
+ return `[...${args[0]}.matchAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"))].slice(0, ${args[1]} < 0 ? undefined : ${args[1]}).map(m => m[0])`;
954
+ case "FindStringSubmatch":
955
+ return `[...(${re}.exec(${args[0]}) ?? [])]`;
956
+ case "FindAllStringSubmatch":
957
+ return `[...${args[0]}.matchAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"))].slice(0, ${args[1]} < 0 ? undefined : ${args[1]}).map(m => [...m])`;
958
+ case "ReplaceAllString":
959
+ return `${args[0]}.replaceAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"), ${args[1]})`;
960
+ case "ReplaceAllLiteralString":
961
+ return `${args[0]}.replaceAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"), ${args[1]}.replace(/\\$/g, '$$$$'))`;
962
+ case "Split":
963
+ return `${args[0]}.split(${re}).slice(0, ${args[1]} < 0 ? undefined : ${args[1]})`;
964
+ case "String":
965
+ return `${re}.source`;
966
+ }
967
+ return undefined;
968
+ },
969
+
970
+ _genSlices(fn, a) {
971
+ switch (fn) {
972
+ case "Contains": {
973
+ const [s, v] = a();
974
+ return `${s}.includes(${v})`;
975
+ }
976
+ case "Index": {
977
+ const [s, v] = a();
978
+ return `${s}.indexOf(${v})`;
979
+ }
980
+ case "Equal": {
981
+ this._usesEqual = true;
982
+ const [a1, b1] = a();
983
+ return `__equal(${a1}, ${b1})`;
984
+ }
985
+ case "Compare": {
986
+ const [a1, b1] = a();
987
+ return `((a, b) => { for (let i = 0; i < a.length && i < b.length; i++) { if (a[i] < b[i]) return -1; if (a[i] > b[i]) return 1; } return a.length - b.length; })(${a1}, ${b1})`;
988
+ }
989
+ case "Sort": {
990
+ const [s] = a();
991
+ return `${s}.sort((a, b) => a < b ? -1 : a > b ? 1 : 0)`;
992
+ }
993
+ case "SortFunc": {
994
+ const [s, cmp] = a();
995
+ return `${s}.sort(${cmp})`;
996
+ }
997
+ case "SortStableFunc": {
998
+ const [s, cmp] = a();
999
+ return `${s}.sort(${cmp})`;
1000
+ }
1001
+ case "IsSorted": {
1002
+ const [s] = a();
1003
+ return `((s) => { for (let i = 1; i < s.length; i++) { if (s[i] < s[i-1]) return false; } return true; })(${s})`;
1004
+ }
1005
+ case "IsSortedFunc": {
1006
+ const [s, cmp] = a();
1007
+ return `((s, f) => { for (let i = 1; i < s.length; i++) { if (f(s[i], s[i-1]) < 0) return false; } return true; })(${s}, ${cmp})`;
1008
+ }
1009
+ case "Reverse": {
1010
+ const [s] = a();
1011
+ return `${s}.reverse()`;
1012
+ }
1013
+ case "Max": {
1014
+ const [s] = a();
1015
+ return `Math.max(...${s})`;
1016
+ }
1017
+ case "Min": {
1018
+ const [s] = a();
1019
+ return `Math.min(...${s})`;
1020
+ }
1021
+ case "MaxFunc": {
1022
+ const [s, cmp] = a();
1023
+ return `((s, f) => s.reduce((m, x) => f(x, m) > 0 ? x : m))(${s}, ${cmp})`;
1024
+ }
1025
+ case "MinFunc": {
1026
+ const [s, cmp] = a();
1027
+ return `((s, f) => s.reduce((m, x) => f(x, m) < 0 ? x : m))(${s}, ${cmp})`;
1028
+ }
1029
+ case "Clone": {
1030
+ const [s] = a();
1031
+ return `${s}.slice()`;
1032
+ }
1033
+ case "Compact": {
1034
+ const [s] = a();
1035
+ return `((s) => s.filter((v, i) => i === 0 || v !== s[i-1]))(${s})`;
1036
+ }
1037
+ case "CompactFunc": {
1038
+ const [s, eq] = a();
1039
+ return `((s, f) => s.filter((v, i) => i === 0 || !f(v, s[i-1])))(${s}, ${eq})`;
1040
+ }
1041
+ case "Concat": {
1042
+ const args = a();
1043
+ return `[].concat(${args.join(", ")})`;
1044
+ }
1045
+ case "Delete": {
1046
+ const [s, i, j] = a();
1047
+ return `[...${s}.slice(0, ${i}), ...${s}.slice(${j})]`;
1048
+ }
1049
+ case "DeleteFunc": {
1050
+ const [s, fn2] = a();
1051
+ return `${s}.filter((v) => !${fn2}(v))`;
1052
+ }
1053
+ case "Insert": {
1054
+ const args = a();
1055
+ const s = args[0];
1056
+ const i = args[1];
1057
+ const vs = args.slice(2);
1058
+ return `[...${s}.slice(0, ${i}), ${vs.join(", ")}, ...${s}.slice(${i})]`;
1059
+ }
1060
+ case "Replace": {
1061
+ const args = a();
1062
+ const s = args[0];
1063
+ const i = args[1];
1064
+ const j = args[2];
1065
+ const vs = args.slice(3);
1066
+ return `[...${s}.slice(0, ${i}), ${vs.join(", ")}, ...${s}.slice(${j})]`;
1067
+ }
1068
+ case "Grow":
1069
+ case "Clip": {
1070
+ const [s] = a();
1071
+ return `${s}.slice()`;
1072
+ }
1073
+ }
1074
+ return undefined;
1075
+ },
1076
+
1077
+ _genIo(fn, _a, expr) {
1078
+ switch (fn) {
1079
+ case "WriteString": {
1080
+ const writerArg = expr.args[0];
1081
+ const writerType = writerArg._type;
1082
+ const typeName =
1083
+ writerType?.name ??
1084
+ (writerType?.kind === "pointer" ? writerType.base?.name : null);
1085
+ const w = this.genExpr(writerArg);
1086
+ const s = this.genExpr(expr.args[1]);
1087
+ const isPtr = writerType?.kind === "pointer";
1088
+ const base = isPtr ? `${w}.value` : w;
1089
+ if (typeName === "strings.Builder") {
1090
+ return `((b,s) => { b._buf += s; return [s.length, null]; })(${base}, ${s})`;
1091
+ }
1092
+ if (typeName === "bytes.Buffer") {
1093
+ return `((b,s) => { b._buf.push(...new TextEncoder().encode(s)); return [s.length, null]; })(${base}, ${s})`;
1094
+ }
1095
+ // Generic: auto-dereference pointer, then dispatch on concrete writer type.
1096
+ // strings.Builder has { _buf: string }, bytes.Buffer has { _buf: [] }.
1097
+ return `((b,s) => { const w = b?.value ?? b; if (typeof w.WriteString === "function") return [w.WriteString(s), null]; if (typeof w._buf === "string") { w._buf += s; return [s.length, null]; } if (Array.isArray(w._buf)) { w._buf.push(...new TextEncoder().encode(s)); return [s.length, null]; } return [0, "io.WriteString: unsupported writer"]; })(${w}, ${s})`;
1098
+ }
1099
+ }
1100
+ return undefined;
1101
+ },
1102
+
1103
+ _genHtml(fn, a) {
1104
+ switch (fn) {
1105
+ case "EscapeString": {
1106
+ const [s] = a();
1107
+ return `${s}.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&#34;").replace(/'/g,"&#39;")`;
1108
+ }
1109
+ case "UnescapeString": {
1110
+ const [s] = a();
1111
+ return `${s}.replace(/&#39;/g,"'").replace(/&#34;/g,'"').replace(/&gt;/g,">").replace(/&lt;/g,"<").replace(/&amp;/g,"&")`;
1112
+ }
1113
+ }
1114
+ return undefined;
1115
+ },
1116
+
1117
+ _genMaps(fn, a) {
1118
+ switch (fn) {
1119
+ case "Keys": {
1120
+ const [m] = a();
1121
+ return `Object.keys(${m})`;
1122
+ }
1123
+ case "Values": {
1124
+ const [m] = a();
1125
+ return `Object.values(${m})`;
1126
+ }
1127
+ case "Clone": {
1128
+ const [m] = a();
1129
+ return `({...${m}})`;
1130
+ }
1131
+ case "Copy": {
1132
+ const [dst, src] = a();
1133
+ return `Object.assign(${dst}, ${src})`;
1134
+ }
1135
+ case "Equal": {
1136
+ this._usesEqual = true;
1137
+ const [a1, b1] = a();
1138
+ return `__equal(${a1}, ${b1})`;
1139
+ }
1140
+ case "EqualFunc": {
1141
+ const [m1, m2, eq] = a();
1142
+ return `((a, b, f) => { const ka = Object.keys(a); if (ka.length !== Object.keys(b).length) return false; return ka.every(k => k in b && f(a[k], b[k])); })(${m1}, ${m2}, ${eq})`;
1143
+ }
1144
+ case "Delete": {
1145
+ const [m, k] = a();
1146
+ return `(delete ${m}[${k}], undefined)`;
1147
+ }
1148
+ case "DeleteFunc": {
1149
+ const [m, fn2] = a();
1150
+ return `Object.keys(${m}).forEach(k => { if (${fn2}(k, ${m}[k])) delete ${m}[k]; })`;
1151
+ }
1152
+ }
1153
+ return undefined;
1154
+ },
1155
+
779
1156
  typeComment(typeNode) {
780
1157
  if (!typeNode) return "unknown";
781
1158
  switch (typeNode.kind) {
@@ -815,11 +1192,170 @@ export const expressionGenMethods = {
815
1192
  return this._genErrors(fn, a);
816
1193
  case "time":
817
1194
  return this._genTime(fn, a);
1195
+ case "regexp":
1196
+ return this._genRegexp(fn, a);
1197
+ case "slices":
1198
+ return this._genSlices(fn, a);
1199
+ case "maps":
1200
+ return this._genMaps(fn, a);
1201
+ case "html":
1202
+ return this._genHtml(fn, a);
1203
+ case "io":
1204
+ return this._genIo(fn, a, expr);
1205
+ case "gom":
1206
+ return this._genGom(fn, expr);
818
1207
  default:
819
1208
  return undefined;
820
1209
  }
821
1210
  },
822
1211
 
1212
+ _genGom(fn, expr) {
1213
+ const a = () =>
1214
+ expr.args.map((e) =>
1215
+ e._spread ? `...${this.genExpr(e)}` : this.genExpr(e),
1216
+ );
1217
+ const elementTags = {
1218
+ Div: "div",
1219
+ Section: "section",
1220
+ Article: "article",
1221
+ Aside: "aside",
1222
+ Header: "header",
1223
+ Footer: "footer",
1224
+ Main: "main",
1225
+ Nav: "nav",
1226
+ Figure: "figure",
1227
+ H1: "h1",
1228
+ H2: "h2",
1229
+ H3: "h3",
1230
+ H4: "h4",
1231
+ H5: "h5",
1232
+ H6: "h6",
1233
+ Span: "span",
1234
+ A: "a",
1235
+ Strong: "strong",
1236
+ Em: "em",
1237
+ Code: "code",
1238
+ Pre: "pre",
1239
+ Small: "small",
1240
+ Mark: "mark",
1241
+ P: "p",
1242
+ Br: "br",
1243
+ Hr: "hr",
1244
+ Ul: "ul",
1245
+ Ol: "ol",
1246
+ Li: "li",
1247
+ Dl: "dl",
1248
+ Dt: "dt",
1249
+ Dd: "dd",
1250
+ Form: "form",
1251
+ Input: "input",
1252
+ Button: "button",
1253
+ Textarea: "textarea",
1254
+ Select: "select",
1255
+ Option: "option",
1256
+ Label: "label",
1257
+ Fieldset: "fieldset",
1258
+ Legend: "legend",
1259
+ Img: "img",
1260
+ Video: "video",
1261
+ Audio: "audio",
1262
+ Canvas: "canvas",
1263
+ Table: "table",
1264
+ Thead: "thead",
1265
+ Tbody: "tbody",
1266
+ Tfoot: "tfoot",
1267
+ Tr: "tr",
1268
+ Th: "th",
1269
+ Td: "td",
1270
+ };
1271
+ if (elementTags[fn]) {
1272
+ const tag = elementTags[fn];
1273
+ const args = a();
1274
+ if (args.length === 0)
1275
+ return `(()=>({Mount(p){const e=document.createElement("${tag}");p.appendChild(e);}}))()`;
1276
+ return (
1277
+ `((...c)=>({Mount(p){const e=document.createElement("${tag}");c.forEach(n=>n?.Mount?.(e));p.appendChild(e);}}))` +
1278
+ `(${args.join(",")})`
1279
+ );
1280
+ }
1281
+ const attrHelpers = {
1282
+ For: "for",
1283
+ Name: "name",
1284
+ Value: "value",
1285
+ Target: "target",
1286
+ Rel: "rel",
1287
+ Alt: "alt",
1288
+ Title: "title",
1289
+ Lang: "lang",
1290
+ Action: "action",
1291
+ Method: "method",
1292
+ AutoComplete: "autocomplete",
1293
+ Draggable: "draggable",
1294
+ Role: "role",
1295
+ StyleAttr: "style",
1296
+ };
1297
+ if (attrHelpers[fn]) {
1298
+ const [v] = a();
1299
+ return `((v)=>({Mount(e){e.setAttribute("${attrHelpers[fn]}",v)}}))(${v})`;
1300
+ }
1301
+ if (fn === "AriaLabel") {
1302
+ const [v] = a();
1303
+ return `((v)=>({Mount(e){e.setAttribute("aria-label",v)}}))(${v})`;
1304
+ }
1305
+ const boolAttrs = {
1306
+ Disabled: "disabled",
1307
+ Checked: "checked",
1308
+ Selected: "selected",
1309
+ Readonly: "readonly",
1310
+ };
1311
+ if (boolAttrs[fn])
1312
+ return `({Mount(e){e.setAttribute("${boolAttrs[fn]}","")}})`;
1313
+
1314
+ const args = a();
1315
+ switch (fn) {
1316
+ case "El": {
1317
+ const [tag, ...children] = args;
1318
+ if (children.length === 0)
1319
+ return `((t)=>({Mount(p){const e=document.createElement(t);p.appendChild(e);}})) (${tag})`;
1320
+ return (
1321
+ `((t,...c)=>({Mount(p){const e=document.createElement(t);c.forEach(n=>n?.Mount?.(e));p.appendChild(e);}}))` +
1322
+ `(${tag},${children.join(",")})`
1323
+ );
1324
+ }
1325
+ case "Text": {
1326
+ const [s] = args;
1327
+ return `((s)=>({Mount(p){p.appendChild(document.createTextNode(s))}}))(${s})`;
1328
+ }
1329
+ case "Attr": {
1330
+ const [name, value] = args;
1331
+ return `((n,v)=>({Mount(e){e.setAttribute(n,v)}}))(${name},${value})`;
1332
+ }
1333
+ case "Class":
1334
+ return `((v)=>({Mount(e){e.className=v}}))(${args[0]})`;
1335
+ case "Type":
1336
+ return `((v)=>({Mount(e){e.type=v}}))(${args[0]})`;
1337
+ case "Href":
1338
+ return `((v)=>({Mount(e){e.href=v}}))(${args[0]})`;
1339
+ case "Src":
1340
+ return `((v)=>({Mount(e){e.src=v}}))(${args[0]})`;
1341
+ case "Placeholder":
1342
+ return `((v)=>({Mount(e){e.placeholder=v}}))(${args[0]})`;
1343
+ case "DataAttr":
1344
+ return `((k,v)=>({Mount(e){e.setAttribute("data-"+k,v)}}))(${args[0]},${args[1]})`;
1345
+ case "If":
1346
+ return `((c,n)=>c?n:{Mount(){}})(${args[0]},${args[1]})`;
1347
+ case "Map":
1348
+ return `((s,f)=>{const c=s.map(f);return{_items:c,Mount(p){c.forEach(n=>n.Mount(p))}}})(${args[0]},${args[1]})`;
1349
+ case "Style":
1350
+ return `((s)=>({Mount(p){const e=document.createElement("style");e.textContent=s;p.appendChild(e);}})) (${args[0]})`;
1351
+ case "Mount":
1352
+ return `((sel,n)=>{const e=document.querySelector(sel);e.innerHTML="";n.Mount(e)})(${args[0]},${args[1]})`;
1353
+ case "MountTo":
1354
+ return `((sel,n)=>{const e=document.querySelector(sel);n.Mount(e)})(${args[0]},${args[1]})`;
1355
+ }
1356
+ return undefined;
1357
+ },
1358
+
823
1359
  _genFmt(fn, _a, expr) {
824
1360
  const fmtArgs = expr.args.map((e) => this.genExpr(e)).join(", ");
825
1361
  switch (fn) {
@@ -843,6 +1379,52 @@ export const expressionGenMethods = {
843
1379
  case "Println":
844
1380
  this._usesSprintf = true;
845
1381
  return `console.log(__sprintf(${fmtArgs}))`;
1382
+ case "Fprintf":
1383
+ case "Fprintln":
1384
+ case "Fprint": {
1385
+ this._usesSprintf = true;
1386
+ const writerArg = expr.args[0];
1387
+ const rest = expr.args.slice(1);
1388
+ const restJs = rest.map((e) => this.genExpr(e)).join(", ");
1389
+ // Resolve the writer to its underlying buffer expression
1390
+ const writerType = writerArg._type;
1391
+ const targetTypeName =
1392
+ writerType?.name ??
1393
+ (writerType?.kind === "pointer" ? writerType.base?.name : null);
1394
+ let buf;
1395
+ if (targetTypeName === "strings.Builder") {
1396
+ const w = this.genExpr(writerArg);
1397
+ buf =
1398
+ writerType?.kind === "pointer" ? `${w}.value._buf` : `${w}._buf`;
1399
+ } else if (targetTypeName === "bytes.Buffer") {
1400
+ const w = this.genExpr(writerArg);
1401
+ const base = writerType?.kind === "pointer" ? `${w}.value` : w;
1402
+ buf = null;
1403
+ if (fn === "Fprintf") {
1404
+ return `((__b,__s)=>{ __b._buf.push(...new TextEncoder().encode(__s)); })(${base}, __sprintf(${restJs}))`;
1405
+ }
1406
+ const valJs =
1407
+ fn === "Fprintln"
1408
+ ? `__sprintf("%v\\n", ${restJs})`
1409
+ : `__sprintf("%v", ${restJs})`;
1410
+ return `((__b,__s)=>{ __b._buf.push(...new TextEncoder().encode(__s)); })(${base}, ${valJs})`;
1411
+ } else {
1412
+ // Generic io.Writer fallback — call .WriteString if available
1413
+ const w = this.genExpr(writerArg);
1414
+ const formatted =
1415
+ fn === "Fprintf"
1416
+ ? `__sprintf(${restJs})`
1417
+ : fn === "Fprintln"
1418
+ ? `__sprintf("%v\\n", ${restJs})`
1419
+ : `__sprintf("%v", ${restJs})`;
1420
+ return `${w}.WriteString(${formatted})`;
1421
+ }
1422
+ // strings.Builder path
1423
+ if (fn === "Fprintf") return `(${buf} += __sprintf(${restJs}))`;
1424
+ if (fn === "Fprintln")
1425
+ return `(${buf} += __sprintf("%v\\n", ${restJs}))`;
1426
+ return `(${buf} += __sprintf("%v", ${restJs}))`;
1427
+ }
846
1428
  default:
847
1429
  return undefined;
848
1430
  }
@@ -862,7 +1444,7 @@ export const expressionGenMethods = {
862
1444
  case "LastIndex":
863
1445
  return `${args[0]}.lastIndexOf(${args[1]})`;
864
1446
  case "Count":
865
- return `${args[0]}.split(${args[1]}).length - 1`;
1447
+ return `((s, sep) => sep === "" ? s.length + 1 : s.split(sep).length - 1)(${args[0]}, ${args[1]})`;
866
1448
  case "Repeat":
867
1449
  return `${args[0]}.repeat(${args[1]})`;
868
1450
  case "Replace":
@@ -878,9 +1460,9 @@ export const expressionGenMethods = {
878
1460
  case "Trim":
879
1461
  return `${args[0]}.replace(new RegExp(\`^[\${${args[1]}}]+|[\${${args[1]}}]+$\`, "g"), "")`;
880
1462
  case "TrimPrefix":
881
- return `(${args[0]}.startsWith(${args[1]}) ? ${args[0]}.slice(${args[1]}.length) : ${args[0]})`;
1463
+ return `((s, pre) => s.startsWith(pre) ? s.slice(pre.length) : s)(${args[0]}, ${args[1]})`;
882
1464
  case "TrimSuffix":
883
- return `(${args[0]}.endsWith(${args[1]}) ? ${args[0]}.slice(0, -${args[1]}.length) : ${args[0]})`;
1465
+ return `((s, suf) => !suf.length || !s.endsWith(suf) ? s : s.slice(0, -suf.length))(${args[0]}, ${args[1]})`;
884
1466
  case "TrimLeft":
885
1467
  return `${args[0]}.replace(new RegExp(\`^[\${${args[1]}}]+\`), "")`;
886
1468
  case "TrimRight":
@@ -910,7 +1492,7 @@ export const expressionGenMethods = {
910
1492
  case "Index":
911
1493
  return `${__bs}(${args[0]}).indexOf(${__bs}(${args[1]}))`;
912
1494
  case "Count":
913
- return `${__bs}(${args[0]}).split(${__bs}(${args[1]})).length - 1`;
1495
+ return `((b, sep) => sep.length === 0 ? b.length + 1 : ${__bs}(b).split(${__bs}(sep)).length - 1)(${args[0]}, ${args[1]})`;
914
1496
  case "Repeat":
915
1497
  return `${__sb}(${__bs}(${args[0]}).repeat(${args[1]}))`;
916
1498
  case "Replace":