typenative 0.1.0 → 0.1.1
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/.github/workflows/ci.yml +29 -0
- package/.github/workflows/npm-publish-github-packages.yml +2 -2
- package/CHANGELOG.md +43 -0
- package/CONTRIBUTION.md +130 -0
- package/README.md +251 -213
- package/bin/index.js +1 -1
- package/bin/transpiler.js +2437 -121
- package/conformance.md +651 -0
- package/package.json +9 -61
- package/TODO.md +0 -38
package/bin/transpiler.js
CHANGED
|
@@ -276,6 +276,45 @@ const dangerousNames = new Set([
|
|
|
276
276
|
const renamedFunctions = new Map();
|
|
277
277
|
// Go type of the receiver of the method call being emitted (for method handlers)
|
|
278
278
|
let currentReceiverGoType;
|
|
279
|
+
// Inside an object-literal method/getter, `this` refers to the instance variable
|
|
280
|
+
let thisOverrideName;
|
|
281
|
+
let thisOverrideFn;
|
|
282
|
+
// Classes with accessor members: "Class.prop" in these sets means reads/writes
|
|
283
|
+
// of the property are Get_prop() / Set_prop(...) calls
|
|
284
|
+
const classGetterMethods = new Set();
|
|
285
|
+
const classSetterMethods = new Set();
|
|
286
|
+
// Classes with a generator method named [Symbol.iterator]: className → yield type
|
|
287
|
+
const classIteratorMethods = new Map();
|
|
288
|
+
// Every parsed class declaration by name (for inheritance lookup)
|
|
289
|
+
const declaredClasses = new Map();
|
|
290
|
+
// Class → parent class name (skip the Error special case)
|
|
291
|
+
const classParentNames = new Map();
|
|
292
|
+
// Functions declared `async` (their calls at top level drain before exit)
|
|
293
|
+
const asyncFunctionNames = new Set();
|
|
294
|
+
// Generator functions (declared with `function*`)
|
|
295
|
+
const generatorFunctionNames = new Set();
|
|
296
|
+
// While emitting an async/generator function body: `return`/`yield` send here
|
|
297
|
+
let asyncChannelVar;
|
|
298
|
+
let asyncFunctionNode;
|
|
299
|
+
// Variables holding JS arrays: stored as *[]T so aliasing shares the slice
|
|
300
|
+
// header (push/splice through any alias are visible everywhere)
|
|
301
|
+
const referenceArrays = new Set();
|
|
302
|
+
// Registers a variable's Go type; array-typed variables become *[]T references
|
|
303
|
+
// (dynamic []interface{} arrays stay plain: they flow through runtime helpers)
|
|
304
|
+
function setVarGoType(name, goType) {
|
|
305
|
+
if (!name)
|
|
306
|
+
return;
|
|
307
|
+
if (goType && goType.startsWith('[]') && goType !== '[]interface{}') {
|
|
308
|
+
variableGoTypes.set(name, `*${goType}`);
|
|
309
|
+
referenceArrays.add(name);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
referenceArrays.delete(name);
|
|
313
|
+
if (goType)
|
|
314
|
+
variableGoTypes.set(name, goType);
|
|
315
|
+
else
|
|
316
|
+
variableGoTypes.delete(name);
|
|
317
|
+
}
|
|
279
318
|
// Struct field / member names: Go keywords get a trailing underscore
|
|
280
319
|
function goFieldName(name) {
|
|
281
320
|
return name !== 'main' && dangerousNames.has(name) ? `${name}_` : name;
|
|
@@ -361,8 +400,14 @@ function parseSource(code) {
|
|
|
361
400
|
for (const stmt of sourceFile.statements ?? []) {
|
|
362
401
|
if (!stmt.name || !isIdentifier(stmt.name))
|
|
363
402
|
continue;
|
|
364
|
-
if (isFunctionDeclaration(stmt))
|
|
403
|
+
if (isFunctionDeclaration(stmt)) {
|
|
365
404
|
declaredFunctions.set(stmt.name.text, stmt);
|
|
405
|
+
if ((stmt.modifiers ?? []).some((m) => m.kind === 'AsyncKeyword')) {
|
|
406
|
+
asyncFunctionNames.add(stmt.name.text);
|
|
407
|
+
}
|
|
408
|
+
if (stmt.asteriskToken)
|
|
409
|
+
generatorFunctionNames.add(stmt.name.text);
|
|
410
|
+
}
|
|
366
411
|
else if (isInterfaceDeclaration(stmt))
|
|
367
412
|
declaredInterfaces.set(stmt.name.text, stmt);
|
|
368
413
|
else if (isTypeAliasDeclaration(stmt))
|
|
@@ -458,6 +503,8 @@ export function transpileToNative(code, options) {
|
|
|
458
503
|
variableTypeNodes.clear();
|
|
459
504
|
narrowedVariables.clear();
|
|
460
505
|
nodeCallResultTypes.clear();
|
|
506
|
+
asyncFunctionNames.clear();
|
|
507
|
+
generatorFunctionNames.clear();
|
|
461
508
|
currentFileDir = null;
|
|
462
509
|
const sourceFile = parseSource(code);
|
|
463
510
|
importedPackages.clear();
|
|
@@ -480,26 +527,65 @@ export function transpileToNative(code, options) {
|
|
|
480
527
|
defaultImportNamespaces.clear();
|
|
481
528
|
classStaticMethods.clear();
|
|
482
529
|
classStaticProps.clear();
|
|
530
|
+
classGetterMethods.clear();
|
|
531
|
+
classSetterMethods.clear();
|
|
532
|
+
classIteratorMethods.clear();
|
|
533
|
+
declaredClasses.clear();
|
|
534
|
+
classParentNames.clear();
|
|
535
|
+
referenceArrays.clear();
|
|
483
536
|
usedHelpers.clear();
|
|
484
537
|
helperProvidedPackages.clear();
|
|
485
538
|
mainPackageVariables.length = 0;
|
|
486
539
|
const transpiledCode = visit(sourceFile, { addFunctionOutside: true });
|
|
487
540
|
const transpiledCodeOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
|
|
541
|
+
// Keep the process alive until detached async work and timers settle
|
|
542
|
+
const drains = [];
|
|
543
|
+
if (usedHelpers.has('asyncWait'))
|
|
544
|
+
drains.push('__tnWaitGroup.Wait()');
|
|
545
|
+
if (usedHelpers.has('timerWait'))
|
|
546
|
+
drains.push('__tnTimers.Wait()');
|
|
547
|
+
const drainCode = drains.length > 0 ? `\n\t${drains.join('\n\t')}` : '';
|
|
488
548
|
const main = `package main
|
|
489
549
|
|
|
490
|
-
${[...importedPackages].sort().map((pkg) =>
|
|
550
|
+
${[...importedPackages].sort().map((pkg) => goImportLine(pkg)).join('\n')}
|
|
491
551
|
|
|
492
552
|
${mainPackageVariables.join('\n')}
|
|
493
553
|
|
|
494
554
|
func main() {
|
|
495
|
-
${transpiledCode.trim()}
|
|
555
|
+
${transpiledCode.trim()}${drainCode}
|
|
496
556
|
}
|
|
497
557
|
|
|
498
558
|
${transpiledCodeOutside.trim()}
|
|
499
559
|
${emitGoHelpers()}`.trimEnd();
|
|
500
560
|
return { main, files: localImportFiles };
|
|
501
561
|
}
|
|
502
|
-
export
|
|
562
|
+
export // Property map of an interface, including inherited properties (flattened —
|
|
563
|
+
function collectInterfaceProperties(iface, into, seen = new Set()) {
|
|
564
|
+
let name;
|
|
565
|
+
if (iface.name && isIdentifier(iface.name))
|
|
566
|
+
name = iface.name.text;
|
|
567
|
+
if (name && seen.has(name))
|
|
568
|
+
return;
|
|
569
|
+
if (name)
|
|
570
|
+
seen.add(name);
|
|
571
|
+
for (const clause of iface.heritageClauses ?? []) {
|
|
572
|
+
if (clause.token === 'ExtendsKeyword') {
|
|
573
|
+
for (const type of clause.types ?? []) {
|
|
574
|
+
if (isIdentifier(type.expression)) {
|
|
575
|
+
const base = declaredInterfaces.get(type.expression.text);
|
|
576
|
+
if (base)
|
|
577
|
+
collectInterfaceProperties(base, into, seen);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
for (const member of (iface.members ?? [])) {
|
|
583
|
+
if (isPropertySignature(member) && isIdentifier(member.name)) {
|
|
584
|
+
into.set(member.name.text, getOptionalNodeType(member.type, !!member.questionToken));
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function visit(node, options = {}) {
|
|
503
589
|
let code = '';
|
|
504
590
|
// Reads of a narrowed nullable property path dereference it
|
|
505
591
|
if (narrowedVariables.size > 0 && isPropertyAccessExpression(node) && !options.skipNarrowing) {
|
|
@@ -533,6 +619,10 @@ export function visit(node, options = {}) {
|
|
|
533
619
|
const goAlias = importAliases.get(node.text);
|
|
534
620
|
if (goAlias)
|
|
535
621
|
return goAlias;
|
|
622
|
+
// Array references: reads go through the shared slice-header pointer
|
|
623
|
+
if (referenceArrays.has(node.text)) {
|
|
624
|
+
return `(*${getSafeName(node.text)})`;
|
|
625
|
+
}
|
|
536
626
|
if (narrowedVariables.has(node.text) && isNarrowableReference(node)) {
|
|
537
627
|
return `(*${getSafeName(node.text)})`;
|
|
538
628
|
}
|
|
@@ -550,9 +640,31 @@ export function visit(node, options = {}) {
|
|
|
550
640
|
else if (isTemplateExpression(node)) {
|
|
551
641
|
return visitTemplateExpression(node);
|
|
552
642
|
}
|
|
643
|
+
else if (node.kind === 'TaggedTemplateExpression') {
|
|
644
|
+
// tag`x${2}` → tag(["x", ""], 2)
|
|
645
|
+
const tag = visit(node.tag);
|
|
646
|
+
const template = node.template;
|
|
647
|
+
const parts = [];
|
|
648
|
+
const args = [];
|
|
649
|
+
if (template.kind === 'NoSubstitutionTemplateLiteral') {
|
|
650
|
+
parts.push(toGoStringLiteral(template.text));
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
parts.push(toGoStringLiteral(template.head.text));
|
|
654
|
+
for (const span of template.templateSpans ?? []) {
|
|
655
|
+
args.push(visit(span.expression));
|
|
656
|
+
parts.push(toGoStringLiteral(span.literal.text));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return `${tag}([]string{${parts.join(', ')}}${args.length ? `, ${args.join(', ')}` : ''})`;
|
|
660
|
+
}
|
|
553
661
|
else if (isNumericLiteral(node)) {
|
|
554
662
|
return `float64(${node.text})`;
|
|
555
663
|
}
|
|
664
|
+
else if (node.kind === 'BigIntLiteral') {
|
|
665
|
+
useHelper('bigint');
|
|
666
|
+
return `TnBigInt("${String(node.text).replace(/n$/, '')}")`;
|
|
667
|
+
}
|
|
556
668
|
else if (isToken(node) && node.kind === 'TrueKeyword') {
|
|
557
669
|
return `true`;
|
|
558
670
|
}
|
|
@@ -563,14 +675,20 @@ export function visit(node, options = {}) {
|
|
|
563
675
|
return `nil`;
|
|
564
676
|
}
|
|
565
677
|
else if (isRegularExpressionLiteral(node)) {
|
|
566
|
-
importedPackages.add('regexp');
|
|
567
678
|
const text = node.text; // e.g. /pattern/flags
|
|
568
679
|
const lastSlash = text.lastIndexOf('/');
|
|
569
680
|
const pattern = text.substring(1, lastSlash);
|
|
570
681
|
const flags = text.substring(lastSlash + 1);
|
|
682
|
+
const goPattern = translateJsRegexPattern(pattern);
|
|
683
|
+
// /g needs lastIndex state; backreferences and lookarounds need the
|
|
684
|
+
// backtracking engine — both live in the TnRegex wrapper
|
|
685
|
+
if (needsStatefulRegex(pattern, flags)) {
|
|
686
|
+
useHelper('fancyRegex');
|
|
687
|
+
return `TnRegexCompile("${goPattern}", ${flags.includes('g') ? 'true' : 'false'})`;
|
|
688
|
+
}
|
|
689
|
+
importedPackages.add('regexp');
|
|
571
690
|
const goFlags = jsRegexFlagsToGo(flags);
|
|
572
|
-
|
|
573
|
-
return `regexp.MustCompile("${goFlags}${escaped}")`;
|
|
691
|
+
return `regexp.MustCompile("${goFlags}${goPattern}")`;
|
|
574
692
|
}
|
|
575
693
|
else if (isArrayLiteralExpression(node)) {
|
|
576
694
|
const type = getArrayLiteralElementType(node);
|
|
@@ -617,6 +735,11 @@ export function visit(node, options = {}) {
|
|
|
617
735
|
return `${visit(node.expression)}[int(${visit(node.argumentExpression)})]`;
|
|
618
736
|
}
|
|
619
737
|
else if (isPropertyAccessExpression(node)) {
|
|
738
|
+
// import.meta.url — the file URL of the running program
|
|
739
|
+
if (node.expression.kind === 'MetaProperty' && node.name.text === 'url') {
|
|
740
|
+
useHelper('moduleUrl');
|
|
741
|
+
return 'TnModuleURL()';
|
|
742
|
+
}
|
|
620
743
|
if (isDynamicValue(node.expression) && node.name.text === 'length' && !isCallee(node)) {
|
|
621
744
|
useHelper('dynamic');
|
|
622
745
|
return `TnLength(${visit(node.expression)})`;
|
|
@@ -628,6 +751,15 @@ export function visit(node, options = {}) {
|
|
|
628
751
|
if (hasQuestionDot(node)) {
|
|
629
752
|
return visitOptionalPropertyAccess(node);
|
|
630
753
|
}
|
|
754
|
+
// super.method() → the parent's implementation on the embedded struct
|
|
755
|
+
if (node.expression.kind === 'SuperKeyword') {
|
|
756
|
+
const ownerClass = getEnclosingClassName(node);
|
|
757
|
+
const parentName = ownerClass ? classParentNames.get(ownerClass) : undefined;
|
|
758
|
+
if (parentName) {
|
|
759
|
+
return `self.${goFieldName(parentName)}.${visit(node.name)}`;
|
|
760
|
+
}
|
|
761
|
+
return visit(node.name);
|
|
762
|
+
}
|
|
631
763
|
if (isIdentifier(node.expression) && enumNames.has(node.expression.text)) {
|
|
632
764
|
return `${getSafeName(node.expression.text)}_${getEnumMemberName(node.name)}`;
|
|
633
765
|
}
|
|
@@ -645,48 +777,120 @@ export function visit(node, options = {}) {
|
|
|
645
777
|
const leftSide = visit(node.expression);
|
|
646
778
|
const rightSide = visit(node.name);
|
|
647
779
|
const objectType = resolveExpressionType(node.expression);
|
|
780
|
+
// Anonymous-struct function fields (object-literal methods/getters): a
|
|
781
|
+
// property read invokes the closure, unless it is itself being called
|
|
782
|
+
const leftGoType = inferExpressionType(node.expression);
|
|
783
|
+
if (leftGoType?.includes('struct{') &&
|
|
784
|
+
!isCallee(node) &&
|
|
785
|
+
getStructFieldGoType(leftGoType.replace(/^\*/, ''), node.name.text)?.startsWith('func')) {
|
|
786
|
+
return `${leftSide}.${rightSide}()`;
|
|
787
|
+
}
|
|
788
|
+
// Class getter reads are accessor calls: c.v → c.Get_v()
|
|
789
|
+
if ((leftGoType?.startsWith('*') || node.expression.kind === 'ThisKeyword') &&
|
|
790
|
+
!leftGoType?.startsWith('*TnMap[') &&
|
|
791
|
+
!leftGoType?.includes('struct{') &&
|
|
792
|
+
!leftGoType?.startsWith('*[]')) {
|
|
793
|
+
const className = node.expression.kind === 'ThisKeyword'
|
|
794
|
+
? getEnclosingClassName(node)
|
|
795
|
+
: leftGoType
|
|
796
|
+
? leftGoType.slice(1).replace(/\[.*\]$/, '')
|
|
797
|
+
: undefined;
|
|
798
|
+
if (className && classGetterMethods.has(`${className}.${node.name.text}`)) {
|
|
799
|
+
return `${leftSide}.Get_${rightSide}()`;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
// Record/Map reads via dot syntax are key lookups; method callees keep their name
|
|
803
|
+
if ((objectType === 'Map' || objectType === 'Record') &&
|
|
804
|
+
rightSide !== 'size' &&
|
|
805
|
+
rightSide !== 'length' &&
|
|
806
|
+
!isCallee(node) &&
|
|
807
|
+
!isAssignmentTarget(node)) {
|
|
808
|
+
return `${leftSide}.Get(${toGoStringLiteral(rightSide)})`;
|
|
809
|
+
}
|
|
648
810
|
return getAcessString(leftSide, rightSide, objectType);
|
|
649
811
|
}
|
|
650
812
|
else if (isVariableDeclaration(node)) {
|
|
651
813
|
// Object destructuring: const { x, y } = obj
|
|
652
814
|
if (isObjectBindingPattern(node.name) && node.initializer) {
|
|
653
815
|
const initExpr = visit(node.initializer);
|
|
654
|
-
const
|
|
816
|
+
const initType = inferExpressionType(node.initializer);
|
|
817
|
+
const parts = [];
|
|
818
|
+
for (const el of node.name.elements ?? []) {
|
|
655
819
|
const localName = visit(el.name);
|
|
656
|
-
const
|
|
657
|
-
const
|
|
820
|
+
const rawProp = el.propertyName ?? el.name;
|
|
821
|
+
const propName = isIdentifier(rawProp) ? rawProp.text : visit(rawProp);
|
|
822
|
+
const fieldType = initType
|
|
823
|
+
? getStructFieldGoType(initType.replace(/^\*/, ''), propName)
|
|
824
|
+
: undefined;
|
|
825
|
+
const defaultVal = el.initializer;
|
|
658
826
|
if (defaultVal) {
|
|
659
|
-
|
|
827
|
+
// Missing property (nil) takes the default
|
|
828
|
+
const defType = inferExpressionType(defaultVal) ?? fieldType ?? 'interface{}';
|
|
829
|
+
const tmp = getTempName('destructure');
|
|
830
|
+
useHelper('dynamic');
|
|
831
|
+
parts.push(`${localName} := func() ${defType} { ${tmp} := interface{}((${initExpr}).${goFieldName(propName)}); if TnIsNil(${tmp}) { return ${visit(defaultVal)} }; return TnAs[${defType}](${tmp}) }()`);
|
|
660
832
|
}
|
|
661
|
-
|
|
662
|
-
|
|
833
|
+
else {
|
|
834
|
+
parts.push(`${localName} := (${initExpr}).${goFieldName(propName)}`);
|
|
835
|
+
}
|
|
836
|
+
registerLocalVariable(localName, defaultVal ? (inferExpressionType(defaultVal) ?? fieldType) : fieldType);
|
|
837
|
+
}
|
|
663
838
|
return parts.join(';\n\t');
|
|
664
839
|
}
|
|
665
840
|
// Array destructuring: const [a, b] = arr
|
|
666
841
|
if (isArrayBindingPattern(node.name) && node.initializer) {
|
|
842
|
+
const initGoType = inferExpressionType(node.initializer);
|
|
667
843
|
const initExpr = isDynamicValue(node.initializer)
|
|
668
844
|
? toGoValueOfType(node.initializer, '[]interface{}')
|
|
669
845
|
: visit(node.initializer);
|
|
670
846
|
const tmpVar = getTempName('arr');
|
|
847
|
+
const elemType = initGoType?.startsWith('[]') ? initGoType.slice(2) : undefined;
|
|
671
848
|
const parts = [`${tmpVar} := ${initExpr}`];
|
|
672
849
|
node.name.elements.forEach((el, idx) => {
|
|
673
850
|
if (isOmittedExpression(el))
|
|
674
851
|
return;
|
|
675
852
|
const bindEl = el;
|
|
676
853
|
const localName = visit(bindEl.name);
|
|
677
|
-
|
|
678
|
-
if (
|
|
679
|
-
|
|
854
|
+
const defaultVal = bindEl.initializer;
|
|
855
|
+
if (defaultVal) {
|
|
856
|
+
// Missing element (past the end) takes the default
|
|
857
|
+
const defType = inferExpressionType(defaultVal) ?? elemType ?? 'interface{}';
|
|
858
|
+
parts.push(`${localName} := func() ${defType} { if len(${tmpVar}) > ${idx} { return ${tmpVar}[${idx}] }; return ${visit(defaultVal)} }()`);
|
|
859
|
+
}
|
|
860
|
+
else if (localName === '_' || localName.startsWith('_')) {
|
|
861
|
+
parts.push(`_ = ${tmpVar}`);
|
|
680
862
|
}
|
|
681
863
|
else {
|
|
682
|
-
|
|
864
|
+
// Reading past the end gives undefined in JS: the zero value here
|
|
865
|
+
useHelper('dynamic');
|
|
866
|
+
parts.push(`${localName} := TnAt(${tmpVar}, float64(${idx}))`);
|
|
683
867
|
}
|
|
868
|
+
registerLocalVariable(localName, defaultVal ? inferExpressionType(defaultVal) : elemType);
|
|
684
869
|
});
|
|
685
870
|
return parts.join(';\n\t');
|
|
686
871
|
}
|
|
687
872
|
// `const x: any = expr` keeps inference (x := expr); `any` elsewhere is interface{}
|
|
688
873
|
const isInferredAny = isAnyTypeNode(node.type) && !!node.initializer;
|
|
689
|
-
|
|
874
|
+
let type = isInferredAny ? ':' : getType(node.type);
|
|
875
|
+
// Array aliasing: b = a keeps pointing at the same backing array
|
|
876
|
+
if (isIdentifier(node.name) &&
|
|
877
|
+
node.initializer &&
|
|
878
|
+
isIdentifier(unwrapParentheses(node.initializer)) &&
|
|
879
|
+
referenceArrays.has(unwrapParentheses(node.initializer).text)) {
|
|
880
|
+
const srcName = unwrapParentheses(node.initializer).text;
|
|
881
|
+
const srcType = variableGoTypes.get(srcName) ?? inferExpressionType(node.initializer);
|
|
882
|
+
setVarGoType(node.name.text, srcType?.startsWith('*') ? srcType.slice(1) : srcType);
|
|
883
|
+
const stored = variableGoTypes.get(node.name.text) ?? '*[]interface{}';
|
|
884
|
+
const aliasTop = isVariableStatement(node.parent?.parent) && isSourceFile(node.parent?.parent?.parent);
|
|
885
|
+
if (aliasTop) {
|
|
886
|
+
if (emittingModuleFile) {
|
|
887
|
+
return `var ${getSafeName(node.name.text)} ${stored} = ${getSafeName(srcName)}`;
|
|
888
|
+
}
|
|
889
|
+
mainPackageVariables.push(`var ${getSafeName(node.name.text)} ${stored}`);
|
|
890
|
+
return `${getSafeName(node.name.text)} = ${getSafeName(srcName)}`;
|
|
891
|
+
}
|
|
892
|
+
return `${getSafeName(node.name.text)} := ${getSafeName(srcName)}`;
|
|
893
|
+
}
|
|
690
894
|
// Track variable type for type-aware method dispatch
|
|
691
895
|
if (isIdentifier(node.name)) {
|
|
692
896
|
// A declaration replaces whatever an earlier variable of the same name recorded
|
|
@@ -725,6 +929,9 @@ export function visit(node, options = {}) {
|
|
|
725
929
|
if (node.initializer.expression.text === 'RegExp') {
|
|
726
930
|
variableTypes.set(node.name.text, 'RegExp');
|
|
727
931
|
}
|
|
932
|
+
else if (node.initializer.expression.text === 'Date') {
|
|
933
|
+
variableTypes.set(node.name.text, 'Date');
|
|
934
|
+
}
|
|
728
935
|
else if (classNames.has(node.initializer.expression.text)) {
|
|
729
936
|
variableTypes.set(node.name.text, 'class');
|
|
730
937
|
variableClassNames.set(node.name.text, node.initializer.expression.text);
|
|
@@ -733,6 +940,19 @@ export function visit(node, options = {}) {
|
|
|
733
940
|
else if (node.initializer && isRegularExpressionLiteral(node.initializer)) {
|
|
734
941
|
variableTypes.set(node.name.text, 'RegExp');
|
|
735
942
|
}
|
|
943
|
+
// Array-typed variables hold references (*[]T)
|
|
944
|
+
if (node.type && !isInferredAny) {
|
|
945
|
+
setVarGoType(node.name.text, getType(node.type));
|
|
946
|
+
}
|
|
947
|
+
else if (inferredType) {
|
|
948
|
+
setVarGoType(node.name.text, inferredType);
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
setVarGoType(node.name.text, undefined);
|
|
952
|
+
}
|
|
953
|
+
if (referenceArrays.has(node.name.text) && type.startsWith('[]')) {
|
|
954
|
+
type = variableGoTypes.get(node.name.text);
|
|
955
|
+
}
|
|
736
956
|
}
|
|
737
957
|
let initializer = node.initializer ? `= ${visit(node.initializer)}` : '';
|
|
738
958
|
// Wrap non-nil values assigned to nullable primitive pointer types
|
|
@@ -740,20 +960,28 @@ export function visit(node, options = {}) {
|
|
|
740
960
|
if (node.initializer && (type.startsWith('*') || isDynamicValue(node.initializer))) {
|
|
741
961
|
initializer = `= ${toGoValueOfType(node.initializer, type)}`;
|
|
742
962
|
}
|
|
963
|
+
// Array references take the address of the fresh array
|
|
964
|
+
if (node.initializer &&
|
|
965
|
+
isIdentifier(node.name) &&
|
|
966
|
+
referenceArrays.has(node.name.text) &&
|
|
967
|
+
type === ':') {
|
|
968
|
+
initializer = `= ${toGoValueOfType(node.initializer, variableGoTypes.get(node.name.text))}`;
|
|
969
|
+
}
|
|
743
970
|
// Package scope has no `:=`: module-level declarations need `var x = expr`
|
|
744
971
|
const isTopLevelStatement = isVariableStatement(node.parent?.parent) && isSourceFile(node.parent?.parent?.parent);
|
|
745
972
|
const isPackageScope = emittingModuleFile && isTopLevelStatement;
|
|
746
973
|
if (type === ':' && isPackageScope) {
|
|
747
|
-
return `var ${
|
|
974
|
+
return `var ${getSafeName(node.name.text)} ${initializer}`;
|
|
748
975
|
}
|
|
749
976
|
const isMainTopLevel = !emittingModuleFile && isTopLevelStatement;
|
|
750
977
|
const packageType = type === ':' ? variableGoTypes.get(node.name.text) : type;
|
|
751
978
|
if (isMainTopLevel && packageType && packageType !== 'nil' && isIdentifier(node.name)) {
|
|
752
|
-
const name =
|
|
979
|
+
const name = getSafeName(node.name.text);
|
|
753
980
|
mainPackageVariables.push(`var ${name} ${packageType}`);
|
|
754
981
|
return initializer ? `${name} ${initializer}` : '';
|
|
755
982
|
}
|
|
756
|
-
|
|
983
|
+
const declName = isIdentifier(node.name) ? getSafeName(node.name.text) : visit(node.name);
|
|
984
|
+
return `${type === ':' ? '' : 'var '}${declName} ${type}${type === ':' ? '' : ' '}${initializer}`;
|
|
757
985
|
}
|
|
758
986
|
else if (isCallExpression(node)) {
|
|
759
987
|
if (hasQuestionDot(node) ||
|
|
@@ -787,11 +1015,61 @@ export function visit(node, options = {}) {
|
|
|
787
1015
|
}
|
|
788
1016
|
// Handle setTimeout specially to get raw delay value
|
|
789
1017
|
if (isIdentifier(node.expression) && node.expression.text === 'setTimeout') {
|
|
1018
|
+
useHelper('timerWait');
|
|
790
1019
|
importedPackages.add('time');
|
|
791
1020
|
const callback = visit((node.arguments ?? [])[0]);
|
|
792
1021
|
const delayNode = (node.arguments ?? [])[1];
|
|
793
1022
|
const delay = isNumericLiteral(delayNode) ? delayNode.text : visit(delayNode);
|
|
794
|
-
|
|
1023
|
+
// Registered on the wait group so the process lives until it fires
|
|
1024
|
+
return `__tnTimers.Add(1)\n\t\ttime.AfterFunc(${delay} * time.Millisecond, func() {\n\t\t\tdefer __tnTimers.Done()\n\t\t\t${callback.trim()}()\n\t\t})`;
|
|
1025
|
+
}
|
|
1026
|
+
// p.then(cb) on a channel-based Promise: wait for the value, run the callback
|
|
1027
|
+
if (isPropertyAccessExpression(node.expression) &&
|
|
1028
|
+
node.expression.name.text === 'then' &&
|
|
1029
|
+
!hasQuestionDot(node.expression)) {
|
|
1030
|
+
const recvType = inferExpressionType(node.expression.expression);
|
|
1031
|
+
if (recvType?.startsWith('chan ')) {
|
|
1032
|
+
const elemType = recvType.slice(5) || 'interface{}';
|
|
1033
|
+
const cb = (node.arguments ?? [])[0];
|
|
1034
|
+
if (cb && (isArrowFunction(cb) || isFunctionExpression(cb))) {
|
|
1035
|
+
for (const p of cb.parameters ?? []) {
|
|
1036
|
+
if (isIdentifier(p.name)) {
|
|
1037
|
+
variableGoTypes.set(p.name.text, elemType);
|
|
1038
|
+
variableTypeNodes.delete(p.name.text);
|
|
1039
|
+
variableTypes.delete(p.name.text);
|
|
1040
|
+
variableClassNames.delete(p.name.text);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
useHelper('asyncWait');
|
|
1045
|
+
const recv = visit(node.expression.expression);
|
|
1046
|
+
const cbs = (node.arguments ?? []).map((a) => visit(a));
|
|
1047
|
+
const cbExpr = cbs[0] ?? 'func(interface{}) {}';
|
|
1048
|
+
return `__tnWaitGroup.Add(1)\n\t\tgo func() {\n\t\t\tdefer __tnWaitGroup.Done()\n\t\t\t__v := <-(${recv})\n\t\t\t(${cbExpr})(__v)\n\t\t}()`;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
// Array.from(arrayLike, mapFn) — array-likes are { length } structs
|
|
1052
|
+
if (isPropertyAccessExpression(node.expression) &&
|
|
1053
|
+
isIdentifier(node.expression.expression) &&
|
|
1054
|
+
node.expression.expression.text === 'Array' &&
|
|
1055
|
+
node.expression.name.text === 'from') {
|
|
1056
|
+
const sourceNode = (node.arguments ?? [])[0];
|
|
1057
|
+
const callbackNode = (node.arguments ?? [])[1];
|
|
1058
|
+
const paramTypes = ['interface{}', 'float64'];
|
|
1059
|
+
if (callbackNode && (isArrowFunction(callbackNode) || isFunctionExpression(callbackNode))) {
|
|
1060
|
+
(callbackNode.parameters ?? []).forEach((p, index) => {
|
|
1061
|
+
if (isIdentifier(p.name) && paramTypes[index])
|
|
1062
|
+
variableGoTypes.set(p.name.text, paramTypes[index]);
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
const source = visit(sourceNode);
|
|
1066
|
+
const cb = callbackNode ? visit(callbackNode) : undefined;
|
|
1067
|
+
const srcVar = getTempName('from');
|
|
1068
|
+
const iVar = getTempName('i');
|
|
1069
|
+
const itemExpr = cb
|
|
1070
|
+
? `(${cb})(${paramTypes.slice(0, (callbackNode?.parameters ?? []).length).map((t, i) => (i === 0 ? 'nil' : `float64(${iVar})`)).join(', ')})`
|
|
1071
|
+
: 'nil';
|
|
1072
|
+
return `func() []interface{} { ${srcVar} := ${source}; __n := int(${srcVar}.length); __out := make([]interface{}, 0, __n); for ${iVar} := 0; ${iVar} < __n; ${iVar}++ { __out = append(__out, ${itemExpr}) }; return __out }()`;
|
|
795
1073
|
}
|
|
796
1074
|
const arrayHigherOrderCall = visitArrayHigherOrderCall(node);
|
|
797
1075
|
if (arrayHigherOrderCall) {
|
|
@@ -883,6 +1161,38 @@ export function visit(node, options = {}) {
|
|
|
883
1161
|
const bitwise = visitBitwise(node, op);
|
|
884
1162
|
if (bitwise)
|
|
885
1163
|
return bitwise;
|
|
1164
|
+
// BigInt arithmetic and comparisons (*big.Int)
|
|
1165
|
+
const bigLeft = inferExpressionType(node.left);
|
|
1166
|
+
const bigRight = inferExpressionType(node.right);
|
|
1167
|
+
if (bigLeft === '*tnbig.Int' || bigRight === '*tnbig.Int') {
|
|
1168
|
+
useHelper('bigint');
|
|
1169
|
+
const bl = visit(node.left);
|
|
1170
|
+
const br = visit(node.right);
|
|
1171
|
+
if (op === '==')
|
|
1172
|
+
return `${bl}.Cmp(${br}) == 0`;
|
|
1173
|
+
if (op === '!=')
|
|
1174
|
+
return `${bl}.Cmp(${br}) != 0`;
|
|
1175
|
+
if (op === '<')
|
|
1176
|
+
return `${bl}.Cmp(${br}) < 0`;
|
|
1177
|
+
if (op === '>')
|
|
1178
|
+
return `${bl}.Cmp(${br}) > 0`;
|
|
1179
|
+
if (op === '<=')
|
|
1180
|
+
return `${bl}.Cmp(${br}) <= 0`;
|
|
1181
|
+
if (op === '>=')
|
|
1182
|
+
return `${bl}.Cmp(${br}) >= 0`;
|
|
1183
|
+
if (op === '+')
|
|
1184
|
+
return `new(tnbig.Int).Add(${bl}, ${br})`;
|
|
1185
|
+
if (op === '-')
|
|
1186
|
+
return `new(tnbig.Int).Sub(${bl}, ${br})`;
|
|
1187
|
+
if (op === '*')
|
|
1188
|
+
return `new(tnbig.Int).Mul(${bl}, ${br})`;
|
|
1189
|
+
if (op === '/')
|
|
1190
|
+
return `new(tnbig.Int).Div(${bl}, ${br})`;
|
|
1191
|
+
if (op === '%')
|
|
1192
|
+
return `new(tnbig.Int).Rem(${bl}, ${br})`;
|
|
1193
|
+
if (op === '**')
|
|
1194
|
+
return `new(tnbig.Int).Exp(${bl}, ${br}, nil)`;
|
|
1195
|
+
}
|
|
886
1196
|
if (op === '**') {
|
|
887
1197
|
importedPackages.add('math');
|
|
888
1198
|
return `math.Pow(${visit(node.left)}, ${visit(node.right)})`;
|
|
@@ -901,7 +1211,7 @@ export function visit(node, options = {}) {
|
|
|
901
1211
|
const left = visit(node.left);
|
|
902
1212
|
return `${left} = math.Mod(${left}, ${visit(node.right)})`;
|
|
903
1213
|
}
|
|
904
|
-
if (['<', '>', '<=', '>=', '-', '*', '/', '+'].includes(op)) {
|
|
1214
|
+
if (['<', '>', '<=', '>=', '-', '*', '/', '+', '+=', '-=', '*=', '/='].includes(op)) {
|
|
905
1215
|
const dynamicArithmetic = visitDynamicArithmetic(node, op);
|
|
906
1216
|
if (dynamicArithmetic)
|
|
907
1217
|
return dynamicArithmetic;
|
|
@@ -924,11 +1234,42 @@ export function visit(node, options = {}) {
|
|
|
924
1234
|
if (otherType && otherType !== 'nil' && !isNilableGoType(otherType)) {
|
|
925
1235
|
return op === '!=' ? 'true' : 'false';
|
|
926
1236
|
}
|
|
1237
|
+
// JS loose equality across primitive types (ToNumber coercion)
|
|
1238
|
+
const leftType = inferExpressionType(node.left);
|
|
1239
|
+
const rightType = inferExpressionType(node.right);
|
|
1240
|
+
const hasBool = leftType === 'bool' || rightType === 'bool';
|
|
1241
|
+
const hasNumberOrString = leftType === 'float64' || leftType === 'string' || rightType === 'float64' || rightType === 'string';
|
|
1242
|
+
if (hasBool && hasNumberOrString) {
|
|
1243
|
+
const boolNode = leftType === 'bool' ? node.left : node.right;
|
|
1244
|
+
useHelper('dynamic');
|
|
1245
|
+
const eq = `TnNumber(${visit(boolNode)}) == ${visit(leftType === 'bool' ? node.right : node.left)}`;
|
|
1246
|
+
return op === '==' ? eq : `!(${eq})`;
|
|
1247
|
+
}
|
|
1248
|
+
if ((leftType === 'string' && rightType === 'float64') ||
|
|
1249
|
+
(leftType === 'float64' && rightType === 'string')) {
|
|
1250
|
+
const strNode = leftType === 'string' ? node.left : node.right;
|
|
1251
|
+
const numNode = leftType === 'string' ? node.right : node.left;
|
|
1252
|
+
useHelper('dynamic');
|
|
1253
|
+
const eq = `TnParseFloat(${visit(strNode)}) == ${visit(numNode)}`;
|
|
1254
|
+
return op === '==' ? eq : `!(${eq})`;
|
|
1255
|
+
}
|
|
927
1256
|
}
|
|
928
1257
|
if (op === '=' && isPropertyAccessExpression(node.left) && isDynamicValue(node.left.expression)) {
|
|
929
1258
|
useHelper('dynamic');
|
|
930
1259
|
return `TnSet(${visit(node.left.expression)}, "${node.left.name.text}", ${visit(node.right)})`;
|
|
931
1260
|
}
|
|
1261
|
+
// Class setter writes are accessor calls: c.v = x → c.Set_v(x)
|
|
1262
|
+
if (op === '=' && isPropertyAccessExpression(node.left)) {
|
|
1263
|
+
const ownerGoType = inferExpressionType(node.left.expression);
|
|
1264
|
+
const setterClass = ownerGoType?.startsWith('*') && !ownerGoType.startsWith('*TnMap[') && !ownerGoType.includes('struct{')
|
|
1265
|
+
? ownerGoType.slice(1).replace(/\[.*\]$/, '')
|
|
1266
|
+
: node.left.expression.kind === 'ThisKeyword'
|
|
1267
|
+
? getEnclosingClassName(node)
|
|
1268
|
+
: undefined;
|
|
1269
|
+
if (setterClass && classSetterMethods.has(`${setterClass}.${node.left.name.text}`)) {
|
|
1270
|
+
return `${visit(node.left.expression)}.Set_${visit(node.left.name)}(${visit(node.right)})`;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
932
1273
|
// m[k] = v on an ordered map
|
|
933
1274
|
if (isElementAccessExpression(node.left) && isAssignmentTarget(node.left)) {
|
|
934
1275
|
const mapType = inferExpressionType(node.left.expression);
|
|
@@ -975,6 +1316,17 @@ export function visit(node, options = {}) {
|
|
|
975
1316
|
else if (isParenthesizedExpression(node)) {
|
|
976
1317
|
return `(${visit(node.expression)})`;
|
|
977
1318
|
}
|
|
1319
|
+
else if (node.kind === 'YieldExpression') {
|
|
1320
|
+
// Inside a generator: yield sends the value over the generator's channel
|
|
1321
|
+
if (asyncChannelVar && getEnclosingFunction(node) === asyncFunctionNode) {
|
|
1322
|
+
if (node.asteriskToken) {
|
|
1323
|
+
// yield* other(): forward every value of the delegated generator
|
|
1324
|
+
return `for __yv := range ${visit(node.expression)} {\n\t\t\t${asyncChannelVar} <- __yv\n\t\t}`;
|
|
1325
|
+
}
|
|
1326
|
+
return `${asyncChannelVar} <- ${visit(node.expression ?? 'nil')}`;
|
|
1327
|
+
}
|
|
1328
|
+
return '';
|
|
1329
|
+
}
|
|
978
1330
|
else if (isAwaitExpression(node)) {
|
|
979
1331
|
return `<-${visit(node.expression)}`;
|
|
980
1332
|
}
|
|
@@ -983,6 +1335,15 @@ export function visit(node, options = {}) {
|
|
|
983
1335
|
(options.inline ? '' : ';\n\t'));
|
|
984
1336
|
}
|
|
985
1337
|
else if (isExpressionStatement(node)) {
|
|
1338
|
+
// A detached top-level async call keeps the process alive until it settles
|
|
1339
|
+
const exprStmt = node.expression;
|
|
1340
|
+
if (isCallExpression(exprStmt) &&
|
|
1341
|
+
isIdentifier(exprStmt.expression) &&
|
|
1342
|
+
asyncFunctionNames.has(exprStmt.expression.text) &&
|
|
1343
|
+
!emittingModuleFile) {
|
|
1344
|
+
useHelper('asyncWait');
|
|
1345
|
+
return `__tnWaitGroup.Add(1)\n\t\tgo func() {\n\t\t\tdefer __tnWaitGroup.Done()\n\t\t\t<-${getSafeName(exprStmt.expression.text)}()\n\t\t}();\n\t`;
|
|
1346
|
+
}
|
|
986
1347
|
return visit(node.expression) + (options.inline ? '' : ';\n\t');
|
|
987
1348
|
}
|
|
988
1349
|
else if (isForStatement(node)) {
|
|
@@ -994,7 +1355,8 @@ export function visit(node, options = {}) {
|
|
|
994
1355
|
: visit(node.initializer);
|
|
995
1356
|
const iterated = visit(node.expression, { inline: true });
|
|
996
1357
|
const isOrderedMap = inferExpressionType(node.expression)?.startsWith('*TnMap[');
|
|
997
|
-
|
|
1358
|
+
// A key the body never reads would be "declared and not used" in Go
|
|
1359
|
+
return `for ${varName} := range ${iterated}${isOrderedMap ? '.All()' : ''}${visitLoopBody(node.statement, `_ = ${varName}\n\t\t`)}`;
|
|
998
1360
|
}
|
|
999
1361
|
else if (isForOfStatement(node)) {
|
|
1000
1362
|
// Unwrap Object.entries(x) → treat x as the iterable
|
|
@@ -1079,19 +1441,24 @@ export function visit(node, options = {}) {
|
|
|
1079
1441
|
.join('')}`;
|
|
1080
1442
|
}
|
|
1081
1443
|
else if (isBreakStatement(node)) {
|
|
1082
|
-
return 'break';
|
|
1444
|
+
return node.label ? `break ${getSafeName(node.label.text)}` : 'break';
|
|
1083
1445
|
}
|
|
1084
1446
|
else if (node.kind === 'ContinueStatement') {
|
|
1085
|
-
return 'continue;\n\t';
|
|
1447
|
+
return node.label ? `continue ${getSafeName(node.label.text)};\n\t` : 'continue;\n\t';
|
|
1448
|
+
}
|
|
1449
|
+
else if (node.kind === 'LabeledStatement') {
|
|
1450
|
+
const label = getSafeName(node.label.text);
|
|
1451
|
+
return `${label}:\n\t\t${visit(node.statement)}` + (options.inline ? '' : ';\n\t');
|
|
1086
1452
|
}
|
|
1087
1453
|
else if (isThrowStatement(node)) {
|
|
1088
1454
|
const expr = node.expression;
|
|
1089
1455
|
if (isNewExpression(expr) &&
|
|
1090
1456
|
isIdentifier(expr.expression) &&
|
|
1091
1457
|
expr.expression.text === 'Error') {
|
|
1458
|
+
useHelper('error');
|
|
1092
1459
|
const args = expr.arguments ?? [];
|
|
1093
1460
|
const msg = args.length > 0 ? visit(args[0]) : '""';
|
|
1094
|
-
return `panic(${msg})` + (options.inline ? '' : ';\n\t');
|
|
1461
|
+
return `panic(TnNewError(${msg}))` + (options.inline ? '' : ';\n\t');
|
|
1095
1462
|
}
|
|
1096
1463
|
return `panic(${visit(expr)})` + (options.inline ? '' : ';\n\t');
|
|
1097
1464
|
}
|
|
@@ -1106,6 +1473,12 @@ export function visit(node, options = {}) {
|
|
|
1106
1473
|
node.expression.expression.text === 'Promise') {
|
|
1107
1474
|
return visitPromiseReturn(node.expression, options);
|
|
1108
1475
|
}
|
|
1476
|
+
// Inside an async function or generator the channel carries the result
|
|
1477
|
+
if (asyncChannelVar && getEnclosingFunction(node) === asyncFunctionNode) {
|
|
1478
|
+
if (!node.expression)
|
|
1479
|
+
return 'return';
|
|
1480
|
+
return `${asyncChannelVar} <- ${visit(node.expression)}; return` + (options.inline ? '' : ';\n\t');
|
|
1481
|
+
}
|
|
1109
1482
|
const enclosingFn = getEnclosingFunction(node);
|
|
1110
1483
|
const isAsync = enclosingFn?.modifiers?.some((m) => m.kind === 'AsyncKeyword');
|
|
1111
1484
|
const returnGoType = enclosingFn && !isAsync ? inferFunctionBodyReturnType(enclosingFn) : undefined;
|
|
@@ -1128,6 +1501,14 @@ export function visit(node, options = {}) {
|
|
|
1128
1501
|
const inferredRetType = inferFunctionBodyReturnType(node);
|
|
1129
1502
|
const returnType = inferredRetType ? ` ${inferredRetType}` : '';
|
|
1130
1503
|
if (options.isOutside) {
|
|
1504
|
+
// async functions run their body in a goroutine sending over a channel
|
|
1505
|
+
const isAsync = (node.modifiers ?? []).some((m) => m.kind === 'AsyncKeyword');
|
|
1506
|
+
if (isAsync && node.name) {
|
|
1507
|
+
return emitChannelFunction(node, true);
|
|
1508
|
+
}
|
|
1509
|
+
if (node.asteriskToken && node.name) {
|
|
1510
|
+
return emitChannelFunction(node, false);
|
|
1511
|
+
}
|
|
1131
1512
|
const name = node.name ? visit(node.name, { inline: true }) : '';
|
|
1132
1513
|
const safeName = getSafeName(name);
|
|
1133
1514
|
return `func ${safeName}${typeParams}(${parameterInfo.signature})${returnType} ${visit(node.body, {
|
|
@@ -1153,6 +1534,10 @@ export function visit(node, options = {}) {
|
|
|
1153
1534
|
return `func(${parameterInfo.signature})${returnType} {\n\t\t${parameterInfo.prefixBlockContent}return ${visit(node.body)};\n\t}`;
|
|
1154
1535
|
}
|
|
1155
1536
|
if (!isBlock(node.body)) {
|
|
1537
|
+
// void-style calls (console.*, assert) emit statements, not values
|
|
1538
|
+
if (isVoidStatementCall(node.body)) {
|
|
1539
|
+
return `func(${parameterInfo.signature})${returnType} { ${visit(node.body)}; }`;
|
|
1540
|
+
}
|
|
1156
1541
|
return `func(${parameterInfo.signature})${returnType} { return ${visit(node.body)}; }`;
|
|
1157
1542
|
}
|
|
1158
1543
|
return `func(${parameterInfo.signature})${returnType} ${visit(node.body, {
|
|
@@ -1160,8 +1545,14 @@ export function visit(node, options = {}) {
|
|
|
1160
1545
|
}).trimEnd()}`;
|
|
1161
1546
|
}
|
|
1162
1547
|
else if (node.kind === 'ThisKeyword') {
|
|
1548
|
+
if (thisOverrideName && getEnclosingFunction(node) === thisOverrideFn)
|
|
1549
|
+
return thisOverrideName;
|
|
1163
1550
|
return 'self';
|
|
1164
1551
|
}
|
|
1552
|
+
else if (node.kind === 'PrivateIdentifier') {
|
|
1553
|
+
// #field → a plain (mangled) struct field
|
|
1554
|
+
return goFieldName(node.text.slice(1));
|
|
1555
|
+
}
|
|
1165
1556
|
else if (isEnumDeclaration(node)) {
|
|
1166
1557
|
const enumName = node.name.text;
|
|
1167
1558
|
enumNames.add(enumName);
|
|
@@ -1207,11 +1598,7 @@ export function visit(node, options = {}) {
|
|
|
1207
1598
|
if (options.addFunctionOutside) {
|
|
1208
1599
|
outsideNodes.push(node);
|
|
1209
1600
|
const properties = new Map();
|
|
1210
|
-
|
|
1211
|
-
if (isPropertySignature(member) && isIdentifier(member.name)) {
|
|
1212
|
-
properties.set(member.name.text, getOptionalNodeType(member.type, !!member.questionToken));
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1601
|
+
collectInterfaceProperties(node, properties);
|
|
1215
1602
|
if (properties.size > 0) {
|
|
1216
1603
|
interfacePropertyTypes.set(visit(node.name), properties);
|
|
1217
1604
|
}
|
|
@@ -1245,7 +1632,16 @@ export function visit(node, options = {}) {
|
|
|
1245
1632
|
}
|
|
1246
1633
|
}
|
|
1247
1634
|
if (properties.length > 0 && methods.length === 0) {
|
|
1248
|
-
|
|
1635
|
+
if (extendedInterfaces.length === 0) {
|
|
1636
|
+
return `type ${name}${typeParams} struct {\n${properties.join('\n')}\n}`;
|
|
1637
|
+
}
|
|
1638
|
+
// Flattened inherited properties: literals can set every field directly
|
|
1639
|
+
const flattened = new Map();
|
|
1640
|
+
collectInterfaceProperties(node, flattened);
|
|
1641
|
+
const fields = [];
|
|
1642
|
+
flattened.forEach((propType, prop) => {
|
|
1643
|
+
fields.push(`\t${goFieldName(prop)} ${propType}`);
|
|
1644
|
+
});
|
|
1249
1645
|
return `type ${name}${typeParams} struct {\n${fields.join('\n')}\n}`;
|
|
1250
1646
|
}
|
|
1251
1647
|
const members = [...extendedInterfaces.map((e) => `\t${e}`), ...methods];
|
|
@@ -1256,31 +1652,81 @@ export function visit(node, options = {}) {
|
|
|
1256
1652
|
outsideNodes.push(node);
|
|
1257
1653
|
const className = visit(node.name);
|
|
1258
1654
|
classNames.add(className);
|
|
1655
|
+
declaredClasses.set(className, node);
|
|
1259
1656
|
const properties = new Map();
|
|
1260
1657
|
const methods = new Map();
|
|
1261
1658
|
for (const member of (node.members ?? [])) {
|
|
1262
1659
|
const memberModifiers = member.modifiers;
|
|
1263
1660
|
const isStatic = memberModifiers?.some((m) => m.kind === 'StaticKeyword');
|
|
1264
|
-
|
|
1661
|
+
const isPrivate = member.name?.kind === 'PrivateIdentifier';
|
|
1662
|
+
const memberName = isPrivate ? member.name.text.slice(1) : member.name?.text;
|
|
1663
|
+
if (isPropertyDeclaration(member) && memberName) {
|
|
1265
1664
|
if (isStatic) {
|
|
1266
|
-
classStaticProps.add(`${className}.${
|
|
1665
|
+
classStaticProps.add(`${className}.${memberName}`);
|
|
1267
1666
|
}
|
|
1268
1667
|
else {
|
|
1269
|
-
properties.set(
|
|
1668
|
+
properties.set(memberName, getPropertyDeclarationType(member));
|
|
1270
1669
|
}
|
|
1271
1670
|
}
|
|
1272
|
-
if (isMethodDeclaration(member) &&
|
|
1671
|
+
if (isMethodDeclaration(member) && memberName) {
|
|
1273
1672
|
if (isStatic) {
|
|
1274
|
-
classStaticMethods.add(`${className}.${
|
|
1673
|
+
classStaticMethods.add(`${className}.${memberName}`);
|
|
1275
1674
|
}
|
|
1276
1675
|
else {
|
|
1277
|
-
methods.set(
|
|
1676
|
+
methods.set(memberName, member.type ? getType(member.type) : 'interface{}');
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
if (isGetAccessor(member) && isIdentifier(member.name)) {
|
|
1680
|
+
classGetterMethods.add(`${className}.${member.name.text}`);
|
|
1681
|
+
properties.set(member.name.text, member.type ? getType(member.type) : 'interface{}');
|
|
1682
|
+
}
|
|
1683
|
+
if (isSetAccessor(member) && isIdentifier(member.name)) {
|
|
1684
|
+
classSetterMethods.add(`${className}.${member.name.text}`);
|
|
1685
|
+
}
|
|
1686
|
+
// *[Symbol.iterator]() — the class is iterable via a generator channel
|
|
1687
|
+
if (isMethodDeclaration(member) && member.asteriskToken) {
|
|
1688
|
+
const computed = member.name?.kind === 'ComputedPropertyName' ? member.name.expression : undefined;
|
|
1689
|
+
if (computed &&
|
|
1690
|
+
isPropertyAccessExpression(computed) &&
|
|
1691
|
+
isIdentifier(computed.expression) &&
|
|
1692
|
+
computed.expression.text === 'Symbol' &&
|
|
1693
|
+
computed.name.text === 'iterator') {
|
|
1694
|
+
classIteratorMethods.set(className, getGeneratorYieldType(member));
|
|
1278
1695
|
}
|
|
1279
1696
|
}
|
|
1280
1697
|
}
|
|
1281
1698
|
for (const param of getParameterProperties(node)) {
|
|
1282
1699
|
properties.set(param.name.text, getParameterGoType(param));
|
|
1283
1700
|
}
|
|
1701
|
+
// Inherited members resolve through the subclass instance too
|
|
1702
|
+
let parentClassName = null;
|
|
1703
|
+
for (const clause of node.heritageClauses ?? []) {
|
|
1704
|
+
if (clause.token === 'ExtendsKeyword') {
|
|
1705
|
+
parentClassName = visit(clause.types[0].expression);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
if (parentClassName && parentClassName !== 'Error') {
|
|
1709
|
+
classParentNames.set(className, parentClassName);
|
|
1710
|
+
const parent = declaredClasses.get(parentClassName);
|
|
1711
|
+
if (parent) {
|
|
1712
|
+
const parentMethods = classMethodReturnTypes.get(parentClassName);
|
|
1713
|
+
if (parentMethods) {
|
|
1714
|
+
for (const inheritedMethod of parentMethods.keys()) {
|
|
1715
|
+
if (!methods.has(inheritedMethod)) {
|
|
1716
|
+
methods.set(inheritedMethod, parentMethods.get(inheritedMethod));
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
const parentProps = classPropertyTypes.get(parentClassName);
|
|
1721
|
+
if (parentProps) {
|
|
1722
|
+
for (const inheritedProp of parentProps.keys()) {
|
|
1723
|
+
if (!properties.has(inheritedProp)) {
|
|
1724
|
+
properties.set(inheritedProp, parentProps.get(inheritedProp));
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1284
1730
|
classPropertyTypes.set(className, properties);
|
|
1285
1731
|
classMethodReturnTypes.set(className, methods);
|
|
1286
1732
|
return '';
|
|
@@ -1296,13 +1742,21 @@ export function visit(node, options = {}) {
|
|
|
1296
1742
|
}
|
|
1297
1743
|
}
|
|
1298
1744
|
}
|
|
1745
|
+
const extendsError = parentClass === 'Error';
|
|
1746
|
+
if (extendsError)
|
|
1747
|
+
useHelper('error');
|
|
1748
|
+
if (parentClass && !extendsError)
|
|
1749
|
+
classParentNames.set(name, parentClass);
|
|
1750
|
+
const isAbstractClass = (node.modifiers ?? []).some((m) => m.kind === 'AbstractKeyword');
|
|
1299
1751
|
const fields = [];
|
|
1300
1752
|
if (parentClass) {
|
|
1301
|
-
fields.push(`\t${parentClass}`);
|
|
1753
|
+
fields.push(`\t${extendsError ? 'TnError' : parentClass}`);
|
|
1302
1754
|
}
|
|
1303
1755
|
for (const member of (node.members ?? [])) {
|
|
1304
1756
|
if (isPropertyDeclaration(member)) {
|
|
1305
|
-
const fieldName =
|
|
1757
|
+
const fieldName = member.name?.kind === 'PrivateIdentifier'
|
|
1758
|
+
? goFieldName(member.name.text.slice(1))
|
|
1759
|
+
: visit(member.name);
|
|
1306
1760
|
let fieldType;
|
|
1307
1761
|
if (member.type && isArrayTypeNode(member.type)) {
|
|
1308
1762
|
fieldType = `[]${getType(member.type, true)}`;
|
|
@@ -1318,31 +1772,136 @@ export function visit(node, options = {}) {
|
|
|
1318
1772
|
}
|
|
1319
1773
|
let result = `type ${name}${typeParams} struct {\n${fields.join('\n')}\n}\n\n`;
|
|
1320
1774
|
const ctor = (node.members ?? []).find((m) => isConstructorDeclaration(m));
|
|
1775
|
+
const isCtorSuperCall = (s) => isExpressionStatement(s) &&
|
|
1776
|
+
isCallExpression(s.expression) &&
|
|
1777
|
+
s.expression.expression.kind === 'SuperKeyword';
|
|
1778
|
+
const superCalledInCtor = (ctor?.body?.statements ?? []).some(isCtorSuperCall);
|
|
1321
1779
|
if (ctor) {
|
|
1322
1780
|
const ctorParameterInfo = getFunctionParametersInfo(ctor.parameters ?? []);
|
|
1323
1781
|
const bodyStatements = (ctor.body?.statements ?? [])
|
|
1324
|
-
.
|
|
1325
|
-
|
|
1326
|
-
|
|
1782
|
+
.map((s) => {
|
|
1783
|
+
// super(...) initializes the embedded parent (or the Error message)
|
|
1784
|
+
if (isCtorSuperCall(s)) {
|
|
1785
|
+
const args = (s.expression.arguments ?? []).map((a) => visit(a));
|
|
1786
|
+
if (extendsError)
|
|
1787
|
+
return `self.message = ${args[0] ?? '""'}\n\t\t`;
|
|
1788
|
+
return `self.${goFieldName(parentClass)} = *New${parentClass}(${args.join(', ')})\n\t\t`;
|
|
1327
1789
|
}
|
|
1328
|
-
return
|
|
1790
|
+
return visit(s);
|
|
1329
1791
|
})
|
|
1330
|
-
.map((s) => visit(s))
|
|
1331
1792
|
.join('\t') ?? '';
|
|
1332
1793
|
const parameterAssignments = getParameterProperties(node)
|
|
1333
1794
|
.map((p) => `self.${goFieldName(p.name.text)} = ${visit(p.name)}\n\t\t`)
|
|
1334
1795
|
.join('');
|
|
1335
|
-
|
|
1796
|
+
const parentInit = parentClass && !extendsError && !superCalledInCtor
|
|
1797
|
+
? `self.${goFieldName(parentClass)} = *New${parentClass}()\n\t\t`
|
|
1798
|
+
: '';
|
|
1799
|
+
result += `func New${name}${typeParams}(${ctorParameterInfo.signature}) *${name}${typeParamNames} {\n\t\tself := &${name}${typeParamNames}{}\n\t\t${ctorParameterInfo.prefixBlockContent}${parentInit}${fieldInitializers(node)}${parameterAssignments}${bodyStatements}return self;\n\t}\n\n`;
|
|
1800
|
+
}
|
|
1801
|
+
else if (extendsError) {
|
|
1802
|
+
// class X extends Error {} — the constructor takes the message
|
|
1803
|
+
result += `func New${name}${typeParams}(message string) *${name}${typeParamNames} {\n\t\tself := &${name}${typeParamNames}{}\n\t\tself.message = message\n\t\t${fieldInitializers(node)}return self\n\t}\n\n`;
|
|
1336
1804
|
}
|
|
1337
1805
|
else {
|
|
1338
|
-
|
|
1806
|
+
const parentInit = parentClass && !extendsError ? `self.${goFieldName(parentClass)} = *New${parentClass}()\n\t\t` : '';
|
|
1807
|
+
result += `func New${name}${typeParams}() *${name}${typeParamNames} {\n\t\tself := &${name}${typeParamNames}{}\n\t\t${parentInit}${fieldInitializers(node)}return self\n\t}\n\n`;
|
|
1808
|
+
}
|
|
1809
|
+
// Inherited concrete methods are copied onto the subclass: Go embedding
|
|
1810
|
+
// promotes the parent's receiver, which would break `this` dispatch
|
|
1811
|
+
const ownMethodNames = new Set();
|
|
1812
|
+
for (const member of (node.members ?? [])) {
|
|
1813
|
+
if (!isMethodDeclaration(member) && !isGetAccessor(member))
|
|
1814
|
+
continue;
|
|
1815
|
+
if (member.name?.kind === 'PrivateIdentifier')
|
|
1816
|
+
continue;
|
|
1817
|
+
if (!member.name?.text)
|
|
1818
|
+
continue;
|
|
1819
|
+
ownMethodNames.add(member.name.text);
|
|
1820
|
+
}
|
|
1821
|
+
const copiedMethods = [];
|
|
1822
|
+
if (parentClass && !extendsError) {
|
|
1823
|
+
// Walk the parent chain (iteratively — no local recursive closures)
|
|
1824
|
+
const pending = [parentClass];
|
|
1825
|
+
const seenClasses = new Set();
|
|
1826
|
+
const ancestors = [];
|
|
1827
|
+
while (pending.length > 0) {
|
|
1828
|
+
const nextName = pending.pop();
|
|
1829
|
+
if (!nextName || seenClasses.has(nextName))
|
|
1830
|
+
continue;
|
|
1831
|
+
seenClasses.add(nextName);
|
|
1832
|
+
const ancestor = declaredClasses.get(nextName);
|
|
1833
|
+
if (!ancestor)
|
|
1834
|
+
continue;
|
|
1835
|
+
ancestors.push(ancestor);
|
|
1836
|
+
for (const clause of ancestor.heritageClauses ?? []) {
|
|
1837
|
+
if (clause.token === 'ExtendsKeyword') {
|
|
1838
|
+
pending.push(visit(clause.types[0].expression));
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
// Copy from the topmost ancestor down so overrides shadow correctly
|
|
1843
|
+
let ancestorIndex = ancestors.length - 1;
|
|
1844
|
+
while (ancestorIndex >= 0) {
|
|
1845
|
+
const ancestor = ancestors[ancestorIndex];
|
|
1846
|
+
for (const member of (ancestor.members ?? [])) {
|
|
1847
|
+
if (!isMethodDeclaration(member))
|
|
1848
|
+
continue;
|
|
1849
|
+
if (!member.body || member.asteriskToken)
|
|
1850
|
+
continue; // abstract / generator
|
|
1851
|
+
if (member.modifiers?.some((m) => m.kind === 'StaticKeyword'))
|
|
1852
|
+
continue;
|
|
1853
|
+
if (member.modifiers?.some((m) => m.kind === 'AbstractKeyword'))
|
|
1854
|
+
continue;
|
|
1855
|
+
const methodName = member.name?.kind === 'PrivateIdentifier' ? member.name.text.slice(1) : member.name?.text;
|
|
1856
|
+
if (!methodName || ownMethodNames.has(methodName))
|
|
1857
|
+
continue;
|
|
1858
|
+
ownMethodNames.add(methodName);
|
|
1859
|
+
const methodParameterInfo = getFunctionParametersInfo(member.parameters ?? []);
|
|
1860
|
+
const returnType = member.type ? ` ${getType(member.type)}` : '';
|
|
1861
|
+
copiedMethods.push(`func (self *${name}${typeParamNames}) ${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n`);
|
|
1862
|
+
}
|
|
1863
|
+
ancestorIndex--;
|
|
1864
|
+
}
|
|
1339
1865
|
}
|
|
1340
1866
|
for (const member of (node.members ?? [])) {
|
|
1867
|
+
if (isMethodDeclaration(member) && member.asteriskToken && member.body) {
|
|
1868
|
+
// Generator method: *[Symbol.iterator]() → Symbol_iterator() chan T
|
|
1869
|
+
let genName;
|
|
1870
|
+
if (member.name?.kind === 'ComputedPropertyName') {
|
|
1871
|
+
const expr = member.name.expression;
|
|
1872
|
+
if (isPropertyAccessExpression(expr) &&
|
|
1873
|
+
isIdentifier(expr.expression) &&
|
|
1874
|
+
expr.expression.text === 'Symbol' &&
|
|
1875
|
+
expr.name.text === 'iterator') {
|
|
1876
|
+
genName = 'Symbol_iterator';
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
else if (member.name && member.name.text) {
|
|
1880
|
+
genName = member.name.kind === 'PrivateIdentifier' ? member.name.text.slice(1) : member.name.text;
|
|
1881
|
+
}
|
|
1882
|
+
if (genName) {
|
|
1883
|
+
const yieldType = getGeneratorYieldType(member);
|
|
1884
|
+
const prevChannel = asyncChannelVar;
|
|
1885
|
+
const prevFn = asyncFunctionNode;
|
|
1886
|
+
asyncChannelVar = '__ch';
|
|
1887
|
+
asyncFunctionNode = member;
|
|
1888
|
+
const methodParameterInfo = getFunctionParametersInfo(member.parameters ?? []);
|
|
1889
|
+
const genBody = visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent });
|
|
1890
|
+
asyncChannelVar = prevChannel;
|
|
1891
|
+
asyncFunctionNode = prevFn;
|
|
1892
|
+
result += `func (self *${name}${typeParamNames}) ${genName}(${methodParameterInfo.signature}) chan ${yieldType} {\n\t__ch := make(chan ${yieldType})\n\tgo func() {\n\t\tdefer close(__ch)\n\t\t${genBody.trim()}\n\t}()\n\treturn __ch\n}\n\n`;
|
|
1893
|
+
}
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1341
1896
|
if (isMethodDeclaration(member)) {
|
|
1342
1897
|
const methodName = visit(member.name);
|
|
1343
1898
|
const methodParameterInfo = getFunctionParametersInfo(member.parameters ?? []);
|
|
1344
1899
|
const returnType = member.type ? ` ${getType(member.type)}` : '';
|
|
1345
1900
|
const isStatic = member.modifiers?.some((m) => m.kind === 'StaticKeyword');
|
|
1901
|
+
if (!member.body)
|
|
1902
|
+
continue; // abstract method — implemented by subclasses
|
|
1903
|
+
if (isAbstractClass && !isStatic)
|
|
1904
|
+
continue; // only reachable via subclasses
|
|
1346
1905
|
if (isStatic) {
|
|
1347
1906
|
// Static methods become package-level functions named ClassName_methodName
|
|
1348
1907
|
result += `func ${name}_${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n\n`;
|
|
@@ -1352,24 +1911,27 @@ export function visit(node, options = {}) {
|
|
|
1352
1911
|
}
|
|
1353
1912
|
}
|
|
1354
1913
|
else if (isGetAccessor(member)) {
|
|
1355
|
-
// Getter: get prop() { ... } → func (self *T)
|
|
1914
|
+
// Getter: get prop() { ... } → func (self *T) Get_prop() RetType { ... }
|
|
1356
1915
|
const getterName = visit(member.name);
|
|
1357
1916
|
const returnType = member.type ? ` ${getType(member.type)}` : ' interface{}';
|
|
1358
1917
|
result += `func (self *${name}${typeParamNames}) Get_${getterName}()${returnType} ${visit(member.body)}\n\n`;
|
|
1359
1918
|
}
|
|
1360
1919
|
else if (isSetAccessor(member)) {
|
|
1361
|
-
// Setter: set prop(val) { ... } → func (self *T)
|
|
1920
|
+
// Setter: set prop(val) { ... } → func (self *T) Set_prop(val ValType) { ... }
|
|
1362
1921
|
const setterName = visit(member.name);
|
|
1363
1922
|
const parameterInfo = getFunctionParametersInfo(member.parameters ?? []);
|
|
1364
1923
|
result += `func (self *${name}${typeParamNames}) Set_${setterName}(${parameterInfo.signature}) ${visit(member.body, { prefixBlockContent: parameterInfo.prefixBlockContent })}\n\n`;
|
|
1365
1924
|
}
|
|
1366
1925
|
}
|
|
1926
|
+
result += copiedMethods.join('\n');
|
|
1367
1927
|
// Static property declarations → package-level vars named ClassName_propName
|
|
1368
1928
|
for (const member of (node.members ?? [])) {
|
|
1369
1929
|
if (isPropertyDeclaration(member) &&
|
|
1370
1930
|
member.modifiers?.some((m) => m.kind === 'StaticKeyword')) {
|
|
1371
1931
|
const fieldName = visit(member.name);
|
|
1372
|
-
const fieldType =
|
|
1932
|
+
const fieldType = member.type
|
|
1933
|
+
? getOptionalNodeType(member.type, !!member.questionToken)
|
|
1934
|
+
: toFieldGoType(member.initializer ? inferExpressionType(member.initializer) : undefined);
|
|
1373
1935
|
const initializer = member.initializer ? ` = ${visit(member.initializer)}` : '';
|
|
1374
1936
|
result += `var ${name}_${fieldName} ${fieldType}${initializer}\n\n`;
|
|
1375
1937
|
}
|
|
@@ -1400,6 +1962,25 @@ export function visit(node, options = {}) {
|
|
|
1400
1962
|
if (className === 'Set') {
|
|
1401
1963
|
return visitNewSet(node);
|
|
1402
1964
|
}
|
|
1965
|
+
// new Error(message) → a TnError value with a .message
|
|
1966
|
+
if (className === 'Error') {
|
|
1967
|
+
useHelper('error');
|
|
1968
|
+
const msg = (node.arguments ?? [])[0] ? visit(node.arguments[0]) : '""';
|
|
1969
|
+
return `TnNewError(${msg})`;
|
|
1970
|
+
}
|
|
1971
|
+
// new Date() / new Date(ms) → Go time
|
|
1972
|
+
if (className === 'Date') {
|
|
1973
|
+
importedPackages.add('time');
|
|
1974
|
+
if ((node.arguments ?? []).length === 0)
|
|
1975
|
+
return 'time.Now()';
|
|
1976
|
+
return `time.UnixMilli(int64(${visit(node.arguments[0])})).UTC()`;
|
|
1977
|
+
}
|
|
1978
|
+
// new Array<number>(3) → make([]float64, 3)
|
|
1979
|
+
if (className === 'Array' && (node.typeArguments ?? []).length === 1) {
|
|
1980
|
+
const elementType = getType(node.typeArguments[0]);
|
|
1981
|
+
const length = (node.arguments ?? [])[0] ? visit(node.arguments[0]) : '0';
|
|
1982
|
+
return `make([]${elementType}, int(${length}))`;
|
|
1983
|
+
}
|
|
1403
1984
|
const typeArgs = getTypeArguments((node.typeArguments ?? []));
|
|
1404
1985
|
const args = node.arguments ? (node.arguments ?? []).map((a) => visit(a)) : [];
|
|
1405
1986
|
return `New${className}${typeArgs}(${args.join(', ')})`;
|
|
@@ -1511,6 +2092,20 @@ function toGoValueOfType(expr, goType) {
|
|
|
1511
2092
|
return orderedMapLiteral(mapArgs[0], mapArgs[1], []);
|
|
1512
2093
|
return `${goType}{}`;
|
|
1513
2094
|
}
|
|
2095
|
+
// Array reference slots (*[]T): literals take an address, aliases pass through
|
|
2096
|
+
if (goType && goType.startsWith('*[]')) {
|
|
2097
|
+
if (isIdentifier(expr) && referenceArrays.has(expr.text)) {
|
|
2098
|
+
return getSafeName(expr.text);
|
|
2099
|
+
}
|
|
2100
|
+
if (isArrayLiteralExpression(expr)) {
|
|
2101
|
+
const code = visit(expr);
|
|
2102
|
+
// Spread-built arrays (append chains) are not addressable
|
|
2103
|
+
return code.startsWith('[')
|
|
2104
|
+
? `&${code}`
|
|
2105
|
+
: `func() ${goType} { __v := ${code}; return &__v }()`;
|
|
2106
|
+
}
|
|
2107
|
+
return `func() ${goType} { __v := ${visit(expr)}; return &__v }()`;
|
|
2108
|
+
}
|
|
1514
2109
|
const code = visit(expr);
|
|
1515
2110
|
if (goType && goType !== 'interface{}' && goType !== ':' && isDynamicValue(expr)) {
|
|
1516
2111
|
useHelper('dynamic');
|
|
@@ -1732,7 +2327,16 @@ function visitDynamicArithmetic(node, op) {
|
|
|
1732
2327
|
useHelper('dynamic');
|
|
1733
2328
|
return `TnAs[float64](${visit(side)})`;
|
|
1734
2329
|
};
|
|
1735
|
-
|
|
2330
|
+
// Compound assignment with a dynamic left side reads through the interface
|
|
2331
|
+
if (op.endsWith('=') && leftDynamic) {
|
|
2332
|
+
const binOp = op.slice(0, -1);
|
|
2333
|
+
const converted = isConcat
|
|
2334
|
+
? `${visit(node.left)} + ${visit(node.right)}`
|
|
2335
|
+
: `TnAs[float64](${visit(node.left)}) ${binOp} ${operand(node.right, rightDynamic)}`;
|
|
2336
|
+
useHelper('dynamic');
|
|
2337
|
+
return `${visit(node.left)} = ${converted}`;
|
|
2338
|
+
}
|
|
2339
|
+
return `${operand(node.left, leftDynamic && !op.endsWith('='))} ${op} ${operand(node.right, rightDynamic)}`;
|
|
1736
2340
|
}
|
|
1737
2341
|
// `x === v` where x is *T and v is T: equal only when x is non-nil and *x == v
|
|
1738
2342
|
function visitNullableComparison(node, op) {
|
|
@@ -1915,9 +2519,12 @@ function fieldInitializers(classNode) {
|
|
|
1915
2519
|
return (classNode.members ?? [])
|
|
1916
2520
|
.filter((m) => isPropertyDeclaration(m) &&
|
|
1917
2521
|
m.initializer &&
|
|
1918
|
-
isIdentifier(m.name) &&
|
|
2522
|
+
(isIdentifier(m.name) || m.name?.kind === 'PrivateIdentifier') &&
|
|
1919
2523
|
!(m.modifiers ?? []).some((mod) => mod.kind === 'StaticKeyword'))
|
|
1920
|
-
.map((m) =>
|
|
2524
|
+
.map((m) => {
|
|
2525
|
+
const fieldName = m.name.kind === 'PrivateIdentifier' ? m.name.text.slice(1) : m.name.text;
|
|
2526
|
+
return `self.${goFieldName(fieldName)} = ${toGoValueOfType(m.initializer, getPropertyDeclarationType(m))}\n\t\t`;
|
|
2527
|
+
})
|
|
1921
2528
|
.join('');
|
|
1922
2529
|
}
|
|
1923
2530
|
// A class field's type: declared, else inferred from its initializer (n = 1 → float64)
|
|
@@ -2004,13 +2611,27 @@ function getObjectLiteralGoType(node) {
|
|
|
2004
2611
|
return typeName;
|
|
2005
2612
|
if (isDictionaryLiteral(node))
|
|
2006
2613
|
return mapGoType('string', getDictionaryValueType(node));
|
|
2007
|
-
const fields = (
|
|
2008
|
-
|
|
2009
|
-
|
|
2614
|
+
const fields = new Map();
|
|
2615
|
+
for (const p of node.properties ?? []) {
|
|
2616
|
+
if (p.kind === 'SpreadAssignment') {
|
|
2617
|
+
const srcType = inferExpressionType(p.expression);
|
|
2618
|
+
const parsedFields = parseStructFields((srcType ?? '').replace(/^\*/, ''));
|
|
2619
|
+
for (const entry of parsedFields) {
|
|
2620
|
+
if (!fields.has(entry[0]))
|
|
2621
|
+
fields.set(entry[0], entry[1]);
|
|
2622
|
+
}
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
if ((isMethodDeclaration(p) || isGetAccessor(p)) && isIdentifier(p.name)) {
|
|
2626
|
+
fields.set(goFieldName(p.name.text), methodFuncType(p));
|
|
2627
|
+
continue;
|
|
2628
|
+
}
|
|
2629
|
+
if (!isPropertyAssignment(p) && !isShorthandPropertyAssignment(p))
|
|
2630
|
+
continue;
|
|
2010
2631
|
const value = isPropertyAssignment(p) ? p.initializer : p.name;
|
|
2011
|
-
|
|
2012
|
-
}
|
|
2013
|
-
return `*struct{ ${fields
|
|
2632
|
+
fields.set(goFieldName(p.name.text), toFieldGoType(inferExpressionType(value)));
|
|
2633
|
+
}
|
|
2634
|
+
return `*struct{ ${structFieldsBody(fields)} }`;
|
|
2014
2635
|
}
|
|
2015
2636
|
// Values typed `any`/`unknown` are handled at runtime (TnGet/TnSet/...),
|
|
2016
2637
|
// never by guessing their type
|
|
@@ -2028,8 +2649,13 @@ function isAssignmentTarget(node) {
|
|
|
2028
2649
|
['PlusPlusToken', 'MinusMinusToken'].includes(parent.operator));
|
|
2029
2650
|
}
|
|
2030
2651
|
// `x as T`: an any value cast to an object type stays dynamic (a JS object
|
|
2031
|
-
// cannot become a Go struct); other casts convert to T
|
|
2652
|
+
// cannot become a Go struct); other casts convert to T; `as const` is erased
|
|
2032
2653
|
function getAsExpressionType(node) {
|
|
2654
|
+
if (isTypeReferenceNode(node.type) &&
|
|
2655
|
+
isIdentifier(node.type.typeName) &&
|
|
2656
|
+
node.type.typeName.text === 'const') {
|
|
2657
|
+
return inferExpressionType(node.expression) ?? 'interface{}';
|
|
2658
|
+
}
|
|
2033
2659
|
const target = getType(node.type);
|
|
2034
2660
|
if (isDynamicValue(node.expression) && (isStructGoType(target) || target.startsWith('*'))) {
|
|
2035
2661
|
return 'interface{}';
|
|
@@ -2090,8 +2716,44 @@ function visitMapLiteral(node, recordType) {
|
|
|
2090
2716
|
}
|
|
2091
2717
|
return orderedMapLiteral(keyType, valueType, entries);
|
|
2092
2718
|
}
|
|
2719
|
+
// Field name → Go type entries of a struct type string: struct{ a T; b U }
|
|
2720
|
+
function parseStructFields(structType) {
|
|
2721
|
+
if (!structType.startsWith('struct{'))
|
|
2722
|
+
return [];
|
|
2723
|
+
const body = structType.slice(7, structType.lastIndexOf('}'));
|
|
2724
|
+
const out = [];
|
|
2725
|
+
let depth = 0;
|
|
2726
|
+
let start = 0;
|
|
2727
|
+
for (let i = 0; i <= body.length; i++) {
|
|
2728
|
+
const ch = i < body.length ? body[i] : ';';
|
|
2729
|
+
if (ch === '{' || ch === '(' || ch === '[')
|
|
2730
|
+
depth++;
|
|
2731
|
+
else if (ch === '}' || ch === ')' || ch === ']')
|
|
2732
|
+
depth--;
|
|
2733
|
+
else if (ch === ';' && depth === 0) {
|
|
2734
|
+
const entry = body.slice(start, i).trim();
|
|
2735
|
+
start = i + 1;
|
|
2736
|
+
if (!entry)
|
|
2737
|
+
continue;
|
|
2738
|
+
const space = entry.indexOf(' ');
|
|
2739
|
+
if (space > 0)
|
|
2740
|
+
out.push([entry.slice(0, space), entry.slice(space + 1).trim()]);
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
return out;
|
|
2744
|
+
}
|
|
2745
|
+
// Go func type of an object-literal method or getter
|
|
2746
|
+
function methodFuncType(member) {
|
|
2747
|
+
const params = (member.parameters ?? []).map((p) => getParameterGoType(p)).join(', ');
|
|
2748
|
+
const ret = member.type
|
|
2749
|
+
? getType(member.type)
|
|
2750
|
+
: inferFunctionBodyReturnType(member) ?? '';
|
|
2751
|
+
return `func(${params})${ret ? ` ${ret}` : ''}`;
|
|
2752
|
+
}
|
|
2093
2753
|
// Object literal with no contextual type → anonymous struct with inferred fields,
|
|
2094
|
-
// or a map when the variable holding it is indexed dynamically (obj[key])
|
|
2754
|
+
// or a map when the variable holding it is indexed dynamically (obj[key]).
|
|
2755
|
+
// Methods and getters become function fields whose closures capture the
|
|
2756
|
+
// instance; spreads copy the source struct's fields.
|
|
2095
2757
|
function visitAnonymousStructLiteral(node) {
|
|
2096
2758
|
if (isDictionaryLiteral(node)) {
|
|
2097
2759
|
const valueType = getDictionaryValueType(node);
|
|
@@ -2100,21 +2762,82 @@ function visitAnonymousStructLiteral(node) {
|
|
|
2100
2762
|
.map((p) => `${mapKeyText(p.name)}, ${visit(p.initializer)}`);
|
|
2101
2763
|
return orderedMapLiteral('string', valueType, entries);
|
|
2102
2764
|
}
|
|
2103
|
-
const fields =
|
|
2765
|
+
const fields = new Map();
|
|
2104
2766
|
const values = [];
|
|
2767
|
+
const steps = [];
|
|
2768
|
+
let sawSpread = false;
|
|
2105
2769
|
for (const p of node.properties ?? []) {
|
|
2106
|
-
if (
|
|
2107
|
-
const
|
|
2108
|
-
|
|
2109
|
-
|
|
2770
|
+
if (p.kind === 'SpreadAssignment') {
|
|
2771
|
+
const srcType = inferExpressionType(p.expression);
|
|
2772
|
+
const srcFields = srcType?.includes('struct{')
|
|
2773
|
+
? parseStructFields(srcType.replace(/^\*/, ''))
|
|
2774
|
+
: [];
|
|
2775
|
+
for (const entry of srcFields) {
|
|
2776
|
+
if (!fields.has(entry[0]))
|
|
2777
|
+
fields.set(entry[0], entry[1]);
|
|
2778
|
+
}
|
|
2779
|
+
if (!sawSpread) {
|
|
2780
|
+
sawSpread = true;
|
|
2781
|
+
steps.push(`__obj := *(${visit(p.expression)})`);
|
|
2782
|
+
}
|
|
2783
|
+
else {
|
|
2784
|
+
steps.push(srcFields
|
|
2785
|
+
.map((entry) => `__obj.${entry[0]} = (${visit(p.expression)}).${entry[0]}`)
|
|
2786
|
+
.join(';\n\t\t'));
|
|
2787
|
+
}
|
|
2788
|
+
continue;
|
|
2110
2789
|
}
|
|
2111
|
-
|
|
2790
|
+
if ((isMethodDeclaration(p) || isGetAccessor(p)) && isIdentifier(p.name)) {
|
|
2112
2791
|
const field = goFieldName(p.name.text);
|
|
2113
|
-
fields.
|
|
2114
|
-
|
|
2792
|
+
fields.set(field, methodFuncType(p));
|
|
2793
|
+
const prevName = thisOverrideName;
|
|
2794
|
+
const prevFn = thisOverrideFn;
|
|
2795
|
+
thisOverrideName = '__obj';
|
|
2796
|
+
thisOverrideFn = p;
|
|
2797
|
+
const paramInfo = getFunctionParametersInfo(p.parameters ?? []);
|
|
2798
|
+
const retType = methodFuncType(p).slice(methodFuncType(p).indexOf(')') + 1);
|
|
2799
|
+
const body = visit(p.body, { prefixBlockContent: paramInfo.prefixBlockContent });
|
|
2800
|
+
thisOverrideName = prevName;
|
|
2801
|
+
thisOverrideFn = prevFn;
|
|
2802
|
+
steps.push(`__obj.${field} = func(${paramInfo.signature})${retType} ${body.trimEnd()}`);
|
|
2803
|
+
continue;
|
|
2804
|
+
}
|
|
2805
|
+
if (isSetAccessor(p))
|
|
2806
|
+
continue;
|
|
2807
|
+
const isProp = isPropertyAssignment(p);
|
|
2808
|
+
if (!isProp && !isShorthandPropertyAssignment(p))
|
|
2809
|
+
continue;
|
|
2810
|
+
const field = goFieldName(p.name.text);
|
|
2811
|
+
const valueNode = isProp ? p.initializer : p.name;
|
|
2812
|
+
if (sawSpread) {
|
|
2813
|
+
steps.push(`__obj.${field} = ${toGoValueOfType(valueNode, fields.get(field))}`);
|
|
2814
|
+
}
|
|
2815
|
+
else {
|
|
2816
|
+
fields.set(field, toFieldGoType(inferExpressionType(valueNode)));
|
|
2817
|
+
values.push(`${field}: ${visit(valueNode)}`);
|
|
2818
|
+
}
|
|
2819
|
+
if (sawSpread && !fields.has(field)) {
|
|
2820
|
+
fields.set(field, toFieldGoType(inferExpressionType(valueNode)));
|
|
2115
2821
|
}
|
|
2116
2822
|
}
|
|
2117
|
-
|
|
2823
|
+
const structType = `struct{ ${structFieldsBody(fields)} }`;
|
|
2824
|
+
if (steps.length === 0) {
|
|
2825
|
+
return `&${structType}${compositeBody(values)}`;
|
|
2826
|
+
}
|
|
2827
|
+
const self = sawSpread ? '__obj' : getTempName('obj');
|
|
2828
|
+
const lines = [];
|
|
2829
|
+
if (sawSpread) {
|
|
2830
|
+
for (const step of steps)
|
|
2831
|
+
lines.push(step);
|
|
2832
|
+
lines.push('return &__obj');
|
|
2833
|
+
}
|
|
2834
|
+
else {
|
|
2835
|
+
lines.push(`${self} := &${structType}${compositeBody(values)}`);
|
|
2836
|
+
for (const step of steps)
|
|
2837
|
+
lines.push(step.replace(/__obj\./g, `${self}.`));
|
|
2838
|
+
lines.push(`return ${self}`);
|
|
2839
|
+
}
|
|
2840
|
+
return `func() *${structType} {\n\t\t${lines.join(';\n\t\t')}\n\t}()`;
|
|
2118
2841
|
}
|
|
2119
2842
|
// Follows type aliases and strips null/undefined from unions
|
|
2120
2843
|
function resolveTypeNode(typeNode, depth = 0) {
|
|
@@ -2151,6 +2874,17 @@ function getEnclosingFunction(node) {
|
|
|
2151
2874
|
}
|
|
2152
2875
|
return undefined;
|
|
2153
2876
|
}
|
|
2877
|
+
// Name of the class declaration enclosing this node, if any
|
|
2878
|
+
function getEnclosingClassName(node) {
|
|
2879
|
+
let current = node;
|
|
2880
|
+
while (current) {
|
|
2881
|
+
if (isClassDeclaration(current) && current.name && isIdentifier(current.name)) {
|
|
2882
|
+
return current.name.text;
|
|
2883
|
+
}
|
|
2884
|
+
current = current.parent;
|
|
2885
|
+
}
|
|
2886
|
+
return undefined;
|
|
2887
|
+
}
|
|
2154
2888
|
// Declared return type of a function, unwrapping Promise<T> for async functions
|
|
2155
2889
|
function getReturnTypeNode(fn) {
|
|
2156
2890
|
const typeNode = fn?.type ?? (fn ? getContextualFunctionType(fn)?.type : undefined);
|
|
@@ -2282,32 +3016,64 @@ function getContextualTypeNode(node) {
|
|
|
2282
3016
|
// variable/parameter/return type), else inferred from its first typed element
|
|
2283
3017
|
function getArrayLiteralElementType(node) {
|
|
2284
3018
|
const contextual = resolveTypeNode(getContextualTypeNode(node));
|
|
2285
|
-
if (isArrayTypeNode(contextual))
|
|
2286
|
-
|
|
3019
|
+
if (isArrayTypeNode(contextual)) {
|
|
3020
|
+
const elem = getType(contextual.elementType);
|
|
3021
|
+
// An unbound type parameter (call-site []T) resolves from the elements
|
|
3022
|
+
if (!isUnboundGoType(elem))
|
|
3023
|
+
return elem;
|
|
3024
|
+
}
|
|
2287
3025
|
if (contextual?.kind === 'TupleType')
|
|
2288
3026
|
return getTupleElementType(contextual);
|
|
2289
3027
|
if (isTypeReferenceNode(contextual) &&
|
|
2290
3028
|
isIdentifier(contextual.typeName) &&
|
|
2291
3029
|
contextual.typeName.text === 'Array' &&
|
|
2292
3030
|
contextual.typeArguments?.length === 1) {
|
|
2293
|
-
|
|
3031
|
+
const elem = getType(contextual.typeArguments[0]);
|
|
3032
|
+
if (!isUnboundGoType(elem))
|
|
3033
|
+
return elem;
|
|
2294
3034
|
}
|
|
2295
3035
|
for (const element of node.elements ?? []) {
|
|
2296
3036
|
if (isSpreadElement(element)) {
|
|
2297
3037
|
const spreadType = inferExpressionType(element.expression);
|
|
2298
3038
|
if (spreadType?.startsWith('[]'))
|
|
2299
3039
|
return spreadType.slice(2);
|
|
3040
|
+
if (spreadType === 'string')
|
|
3041
|
+
return 'string';
|
|
2300
3042
|
if (spreadType?.startsWith('*TnMap[') && extractMapValueType(spreadType) === 'struct{}') {
|
|
2301
3043
|
return extractMapKeyType(spreadType);
|
|
2302
3044
|
}
|
|
2303
3045
|
continue;
|
|
2304
3046
|
}
|
|
2305
3047
|
const elementType = inferExpressionType(element);
|
|
2306
|
-
if (elementType && elementType !== 'nil')
|
|
3048
|
+
if (elementType && elementType !== 'nil' && !isUnboundGoType(elementType))
|
|
2307
3049
|
return elementType;
|
|
2308
3050
|
}
|
|
2309
3051
|
return 'interface{}';
|
|
2310
3052
|
}
|
|
3053
|
+
// A Go type that is a generic type parameter of the enclosing function (T)
|
|
3054
|
+
// rather than a concrete type: unresolvable at the call site
|
|
3055
|
+
function isUnboundGoType(goType) {
|
|
3056
|
+
if (!goType)
|
|
3057
|
+
return false;
|
|
3058
|
+
// Only a bare identifier can be a type parameter; composite types are concrete
|
|
3059
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(goType))
|
|
3060
|
+
return false;
|
|
3061
|
+
if (['float64', 'string', 'bool', 'int', 'interface{}', 'byte', 'rune'].includes(goType))
|
|
3062
|
+
return false;
|
|
3063
|
+
return (!classNames.has(goType) &&
|
|
3064
|
+
!interfacePropertyTypes.has(goType) &&
|
|
3065
|
+
!enumNames.has(goType) &&
|
|
3066
|
+
!typeAliases.has(goType) &&
|
|
3067
|
+
!declaredInterfaces.has(goType));
|
|
3068
|
+
}
|
|
3069
|
+
// A field-name → type map rendered as a Go struct type body
|
|
3070
|
+
function structFieldsBody(fields) {
|
|
3071
|
+
const parts = [];
|
|
3072
|
+
fields.forEach((fieldType, fieldName) => {
|
|
3073
|
+
parts.push(`${fieldName} ${fieldType}`);
|
|
3074
|
+
});
|
|
3075
|
+
return parts.join('; ');
|
|
3076
|
+
}
|
|
2311
3077
|
// Loop bodies must be blocks in Go; `prefix` is emitted before the body's statements
|
|
2312
3078
|
function visitLoopBody(statement, prefix = '') {
|
|
2313
3079
|
if (isBlock(statement))
|
|
@@ -2353,12 +3119,16 @@ function getTypeText(typeNode) {
|
|
|
2353
3119
|
function toGoStringLiteral(value) {
|
|
2354
3120
|
return JSON.stringify(value);
|
|
2355
3121
|
}
|
|
2356
|
-
// A spread source as a Go slice; Sets spread their elements (map keys)
|
|
3122
|
+
// A spread source as a Go slice; Sets spread their elements (map keys) and
|
|
3123
|
+
// strings spread into []string of runes (like JS [...str])
|
|
2357
3124
|
function visitSpreadSource(expr) {
|
|
2358
3125
|
const sourceType = inferExpressionType(expr);
|
|
2359
3126
|
if (sourceType?.startsWith('*TnMap[') && extractMapValueType(sourceType) === 'struct{}') {
|
|
2360
3127
|
return `${visit(expr)}.Keys()`;
|
|
2361
3128
|
}
|
|
3129
|
+
if (sourceType === 'string') {
|
|
3130
|
+
return `func() []string { __parts := []string{}; for _, __r := range ${visit(expr)} { __parts = append(__parts, string(__r)) }; return __parts }()`;
|
|
3131
|
+
}
|
|
2362
3132
|
return visit(expr);
|
|
2363
3133
|
}
|
|
2364
3134
|
function visitSpreadArrayLiteral(node, elemType) {
|
|
@@ -2412,6 +3182,17 @@ function visitTemplateExpression(node) {
|
|
|
2412
3182
|
}
|
|
2413
3183
|
return parts.join(' + ');
|
|
2414
3184
|
}
|
|
3185
|
+
// An expression-bodied arrow whose callee emits Go statements (no value):
|
|
3186
|
+
// console.log(...), assert(...)
|
|
3187
|
+
function isVoidStatementCall(expr) {
|
|
3188
|
+
if (!isCallExpression(expr))
|
|
3189
|
+
return false;
|
|
3190
|
+
if (isIdentifier(expr.expression))
|
|
3191
|
+
return expr.expression.text === 'assert';
|
|
3192
|
+
return (isPropertyAccessExpression(expr.expression) &&
|
|
3193
|
+
isIdentifier(expr.expression.expression) &&
|
|
3194
|
+
expr.expression.expression.text === 'console');
|
|
3195
|
+
}
|
|
2415
3196
|
function hasQuestionDot(node) {
|
|
2416
3197
|
return ('questionDotToken' in node &&
|
|
2417
3198
|
!!node.questionDotToken);
|
|
@@ -2446,12 +3227,12 @@ function prescanVariableDeclarations(block) {
|
|
|
2446
3227
|
for (const decl of stmt.declarationList.declarations) {
|
|
2447
3228
|
if (isIdentifier(decl.name) && !variableGoTypes.has(decl.name.text)) {
|
|
2448
3229
|
if (decl.type) {
|
|
2449
|
-
|
|
3230
|
+
setVarGoType(decl.name.text, getType(decl.type));
|
|
2450
3231
|
}
|
|
2451
3232
|
else if (decl.initializer) {
|
|
2452
3233
|
const inferredType = inferExpressionType(decl.initializer);
|
|
2453
3234
|
if (inferredType)
|
|
2454
|
-
|
|
3235
|
+
setVarGoType(decl.name.text, inferredType);
|
|
2455
3236
|
}
|
|
2456
3237
|
}
|
|
2457
3238
|
}
|
|
@@ -2492,6 +3273,11 @@ function inferArrowFunctionGoType(node) {
|
|
|
2492
3273
|
return `func(${params})${retType ? ` ${retType}` : ''}`;
|
|
2493
3274
|
}
|
|
2494
3275
|
function inferExpressionType(expr) {
|
|
3276
|
+
if (isAwaitExpression(expr)) {
|
|
3277
|
+
// await unwraps the promise channel
|
|
3278
|
+
const inner = inferExpressionType(expr.expression);
|
|
3279
|
+
return inner?.startsWith('chan ') ? inner.slice(5) || 'struct{}' : 'interface{}';
|
|
3280
|
+
}
|
|
2495
3281
|
if (isArrowFunction(expr) || isFunctionExpression(expr)) {
|
|
2496
3282
|
return inferArrowFunctionGoType(expr);
|
|
2497
3283
|
}
|
|
@@ -2512,10 +3298,17 @@ function inferExpressionType(expr) {
|
|
|
2512
3298
|
}
|
|
2513
3299
|
if (isNumericLiteral(expr))
|
|
2514
3300
|
return 'float64';
|
|
3301
|
+
if (expr.kind === 'BigIntLiteral')
|
|
3302
|
+
return '*tnbig.Int';
|
|
2515
3303
|
if (expr.kind === 'TypeOfExpression')
|
|
2516
3304
|
return 'string';
|
|
2517
|
-
if (isRegularExpressionLiteral(expr))
|
|
3305
|
+
if (isRegularExpressionLiteral(expr)) {
|
|
3306
|
+
const lastSlash = expr.text.lastIndexOf('/');
|
|
3307
|
+
if (needsStatefulRegex(expr.text.substring(1, lastSlash), expr.text.substring(lastSlash + 1))) {
|
|
3308
|
+
return '*TnRegex';
|
|
3309
|
+
}
|
|
2518
3310
|
return '*regexp.Regexp';
|
|
3311
|
+
}
|
|
2519
3312
|
if (isNewExpression(expr) && isIdentifier(expr.expression) && expr.expression.text === 'RegExp') {
|
|
2520
3313
|
return '*regexp.Regexp';
|
|
2521
3314
|
}
|
|
@@ -2526,7 +3319,10 @@ function inferExpressionType(expr) {
|
|
|
2526
3319
|
if (isIdentifier(expr) && expr.text === 'undefined')
|
|
2527
3320
|
return 'nil';
|
|
2528
3321
|
if (isIdentifier(expr)) {
|
|
2529
|
-
|
|
3322
|
+
let varType = variableGoTypes.get(expr.text);
|
|
3323
|
+
// Array references are read as the slice they point to
|
|
3324
|
+
if (varType?.startsWith('*[]'))
|
|
3325
|
+
varType = varType.slice(1);
|
|
2530
3326
|
return narrowedVariables.has(expr.text) && varType?.startsWith('*') ? varType.slice(1) : varType;
|
|
2531
3327
|
}
|
|
2532
3328
|
if (isArrayLiteralExpression(expr)) {
|
|
@@ -2536,7 +3332,20 @@ function inferExpressionType(expr) {
|
|
|
2536
3332
|
return getObjectLiteralGoType(expr);
|
|
2537
3333
|
if (isNewExpression(expr) && isIdentifier(expr.expression)) {
|
|
2538
3334
|
const ctorName = expr.expression.text;
|
|
3335
|
+
if (classNames.has(ctorName))
|
|
3336
|
+
return `*${ctorName}`;
|
|
3337
|
+
if (ctorName === 'Error')
|
|
3338
|
+
return '*TnError';
|
|
3339
|
+
if (ctorName === 'Promise' && (expr.typeArguments ?? []).length > 0) {
|
|
3340
|
+
return `chan ${getType(expr.typeArguments[0]) || 'struct{}'}`;
|
|
3341
|
+
}
|
|
2539
3342
|
const typeArguments = getCollectionTypeArguments(expr);
|
|
3343
|
+
if (ctorName === 'Array' && (expr.typeArguments ?? []).length === 1) {
|
|
3344
|
+
return `[]${getType(expr.typeArguments[0])}`;
|
|
3345
|
+
}
|
|
3346
|
+
if (ctorName === 'Date') {
|
|
3347
|
+
return 'time.Time';
|
|
3348
|
+
}
|
|
2540
3349
|
if (ctorName === 'Map' && typeArguments.length === 2) {
|
|
2541
3350
|
return mapGoType(getType(typeArguments[0]), getType(typeArguments[1]));
|
|
2542
3351
|
}
|
|
@@ -2565,6 +3374,10 @@ function inferExpressionType(expr) {
|
|
|
2565
3374
|
expr.name.text === 'argv') {
|
|
2566
3375
|
return '[]string';
|
|
2567
3376
|
}
|
|
3377
|
+
// import.meta.url is a string
|
|
3378
|
+
if (isPropertyAccessExpression(expr) && expr.expression.kind === 'MetaProperty' && expr.name.text === 'url') {
|
|
3379
|
+
return 'string';
|
|
3380
|
+
}
|
|
2568
3381
|
if (isIdentifier(expr) && (expr.text === 'NaN' || expr.text === 'Infinity'))
|
|
2569
3382
|
return 'float64';
|
|
2570
3383
|
if (isPropertyAccessExpression(expr) && isIdentifier(expr.expression) && expr.expression.text === 'Number') {
|
|
@@ -2583,6 +3396,23 @@ function inferExpressionType(expr) {
|
|
|
2583
3396
|
: isPropertyAccessExpression(expr.expression) && isIdentifier(expr.expression.expression)
|
|
2584
3397
|
? `${expr.expression.expression.text}.${expr.expression.name.text}`
|
|
2585
3398
|
: undefined;
|
|
3399
|
+
// Promise.all([...]) resolves to a slice over a channel
|
|
3400
|
+
if (isPropertyAccessExpression(expr.expression) &&
|
|
3401
|
+
isIdentifier(expr.expression.expression) &&
|
|
3402
|
+
expr.expression.expression.text === 'Promise' &&
|
|
3403
|
+
expr.expression.name.text === 'all') {
|
|
3404
|
+
const elem = promiseAllElementType(expr);
|
|
3405
|
+
if (elem)
|
|
3406
|
+
return `chan []${elem}`;
|
|
3407
|
+
return 'chan []interface{}';
|
|
3408
|
+
}
|
|
3409
|
+
// Generator functions return a channel of yielded values
|
|
3410
|
+
if (isIdentifier(expr.expression) &&
|
|
3411
|
+
generatorFunctionNames.has(expr.expression.text)) {
|
|
3412
|
+
const fn = declaredFunctions.get(expr.expression.text);
|
|
3413
|
+
if (fn)
|
|
3414
|
+
return `chan ${getGeneratorYieldType(fn)}`;
|
|
3415
|
+
}
|
|
2586
3416
|
const nodeResultType = callee ? nodeCallResultTypes.get(callee) : undefined;
|
|
2587
3417
|
if (callee && BUILTIN_CALL_TYPES[callee])
|
|
2588
3418
|
return BUILTIN_CALL_TYPES[callee];
|
|
@@ -2617,6 +3447,13 @@ function inferExpressionType(expr) {
|
|
|
2617
3447
|
if ((isPropertyAccessExpression(expr) || isElementAccessExpression(expr)) && isDynamicValue(expr.expression)) {
|
|
2618
3448
|
return 'interface{}';
|
|
2619
3449
|
}
|
|
3450
|
+
if (isCallExpression(expr) &&
|
|
3451
|
+
isPropertyAccessExpression(expr.expression) &&
|
|
3452
|
+
isIdentifier(expr.expression.expression) &&
|
|
3453
|
+
expr.expression.expression.text === 'Array' &&
|
|
3454
|
+
expr.expression.name.text === 'from') {
|
|
3455
|
+
return '[]interface{}';
|
|
3456
|
+
}
|
|
2620
3457
|
if (isCallExpression(expr) && isObjectKeysOfDynamic(expr))
|
|
2621
3458
|
return '[]string';
|
|
2622
3459
|
if (isCallExpression(expr) &&
|
|
@@ -2651,6 +3488,9 @@ function inferExpressionType(expr) {
|
|
|
2651
3488
|
return nullableType.slice(1);
|
|
2652
3489
|
}
|
|
2653
3490
|
if (isIdentifier(expr.expression) && enumNames.has(expr.expression.text)) {
|
|
3491
|
+
// Numeric enums are float64 constants; string enums keep their type
|
|
3492
|
+
if (enumBaseTypes.get(expr.expression.text) === 'float64')
|
|
3493
|
+
return 'float64';
|
|
2654
3494
|
const enumType = getSafeName(expr.expression.text);
|
|
2655
3495
|
return enumType;
|
|
2656
3496
|
}
|
|
@@ -2713,12 +3553,29 @@ function inferExpressionType(expr) {
|
|
|
2713
3553
|
if (stringMethodType)
|
|
2714
3554
|
return stringMethodType;
|
|
2715
3555
|
}
|
|
2716
|
-
if (ownerType === '*regexp.Regexp') {
|
|
2717
|
-
if (methodName === 'exec')
|
|
3556
|
+
if (ownerType === '*regexp.Regexp' || ownerType === '*TnRegex') {
|
|
3557
|
+
if (methodName === 'exec') {
|
|
3558
|
+
// Named-group patterns return a match object with a .groups record
|
|
3559
|
+
const receiver = expr.expression.expression;
|
|
3560
|
+
if (isRegularExpressionLiteral(receiver) && /\(\?<[A-Za-z_]/.test(receiver.text ?? '')) {
|
|
3561
|
+
useHelper('namedGroups');
|
|
3562
|
+
return '*TnMatch';
|
|
3563
|
+
}
|
|
2718
3564
|
return '[]string';
|
|
3565
|
+
}
|
|
2719
3566
|
if (methodName === 'test')
|
|
2720
3567
|
return 'bool';
|
|
2721
3568
|
}
|
|
3569
|
+
if (ownerType === 'time.Time') {
|
|
3570
|
+
if (methodName === 'getTime' || methodName === 'valueOf' || methodName === 'getMilliseconds' ||
|
|
3571
|
+
methodName === 'getSeconds' || methodName === 'getMinutes' || methodName === 'getHours' ||
|
|
3572
|
+
methodName === 'getDate' || methodName === 'getDay' || methodName === 'getMonth' ||
|
|
3573
|
+
methodName === 'getFullYear')
|
|
3574
|
+
return 'float64';
|
|
3575
|
+
if (methodName === 'toISOString' || methodName === 'toJSON' || methodName === 'toString' ||
|
|
3576
|
+
methodName === 'toDateString' || methodName === 'toTimeString')
|
|
3577
|
+
return 'string';
|
|
3578
|
+
}
|
|
2722
3579
|
if (ownerType && ownerType.startsWith('*TnMap[')) {
|
|
2723
3580
|
if (methodName === 'has')
|
|
2724
3581
|
return 'bool';
|
|
@@ -2747,6 +3604,10 @@ function inferExpressionType(expr) {
|
|
|
2747
3604
|
return elementType;
|
|
2748
3605
|
if (methodName === 'flat' && elementType.startsWith('[]'))
|
|
2749
3606
|
return elementType;
|
|
3607
|
+
if (methodName === 'flat' || methodName === 'flatMap')
|
|
3608
|
+
return `[]${elementType}`;
|
|
3609
|
+
if (methodName === 'entries')
|
|
3610
|
+
return '[][]interface{}';
|
|
2750
3611
|
if (methodName === 'find')
|
|
2751
3612
|
return elementType;
|
|
2752
3613
|
if (methodName === 'join')
|
|
@@ -2803,6 +3664,11 @@ function inferExpressionType(expr) {
|
|
|
2803
3664
|
if (COMPARISON_OPERATORS.has(kind) || kind === 'InKeyword' || kind === 'InstanceOfKeyword') {
|
|
2804
3665
|
return 'bool';
|
|
2805
3666
|
}
|
|
3667
|
+
// BigInt operands stay bigint
|
|
3668
|
+
if (['AsteriskToken', 'SlashToken', 'PercentToken', 'AsteriskAsteriskToken', 'PlusToken', 'MinusToken'].includes(kind) &&
|
|
3669
|
+
(inferExpressionType(expr.left) === '*tnbig.Int' || inferExpressionType(expr.right) === '*tnbig.Int')) {
|
|
3670
|
+
return '*tnbig.Int';
|
|
3671
|
+
}
|
|
2806
3672
|
if (isLogicalOperator(expr.operatorToken)) {
|
|
2807
3673
|
return isLogicalValueExpression(expr) ? getLogicalValueType(expr) : 'bool';
|
|
2808
3674
|
}
|
|
@@ -2835,8 +3701,17 @@ const BUILTIN_CALL_TYPES = {
|
|
|
2835
3701
|
'Number.isNaN': 'bool',
|
|
2836
3702
|
'Number.isFinite': 'bool',
|
|
2837
3703
|
'Number.isInteger': 'bool',
|
|
2838
|
-
isNaN: 'bool'
|
|
3704
|
+
isNaN: 'bool',
|
|
3705
|
+
'Object.assign': '*TnMap[string, interface{}]',
|
|
3706
|
+
'Date.now': 'float64'
|
|
2839
3707
|
};
|
|
3708
|
+
// Element type of `await Promise.all([...])` from the first promise channel
|
|
3709
|
+
function promiseAllElementType(expr) {
|
|
3710
|
+
const arrayNode = (expr.arguments ?? [])[0];
|
|
3711
|
+
const first = arrayNode?.elements?.[0];
|
|
3712
|
+
const chanType = first ? inferExpressionType(first) : undefined;
|
|
3713
|
+
return chanType?.startsWith('chan ') ? chanType.slice(5) : undefined;
|
|
3714
|
+
}
|
|
2840
3715
|
const BUILTIN_FUNCTION_TYPES = {
|
|
2841
3716
|
parseFloat: 'float64',
|
|
2842
3717
|
parseInt: 'float64',
|
|
@@ -2868,7 +3743,8 @@ const STRING_METHOD_RETURN_TYPES = {
|
|
|
2868
3743
|
indexOf: 'float64',
|
|
2869
3744
|
lastIndexOf: 'float64',
|
|
2870
3745
|
search: 'float64',
|
|
2871
|
-
charCodeAt: 'float64'
|
|
3746
|
+
charCodeAt: 'float64',
|
|
3747
|
+
localeCompare: 'float64'
|
|
2872
3748
|
};
|
|
2873
3749
|
// Return type of a Go func type string: `func(a T) R` → `R`
|
|
2874
3750
|
function goFuncReturnType(funcType) {
|
|
@@ -3128,8 +4004,12 @@ function isNilableGoType(goType) {
|
|
|
3128
4004
|
function visitOptionalPropertyAccess(node) {
|
|
3129
4005
|
const baseExpr = visit(node.expression);
|
|
3130
4006
|
const baseType = inferExpressionType(node.expression);
|
|
3131
|
-
if (!baseType || !baseType.startsWith('*')) {
|
|
4007
|
+
if (!baseType || !baseType.startsWith('*') || baseType.startsWith('*TnMap[')) {
|
|
4008
|
+
// Record/Map members are key lookups even through optional chains
|
|
3132
4009
|
const objectType = resolveExpressionType(node.expression);
|
|
4010
|
+
if (objectType === 'Map' || objectType === 'Record') {
|
|
4011
|
+
return `${baseExpr}.Get(${toGoStringLiteral(node.name.text)})`;
|
|
4012
|
+
}
|
|
3133
4013
|
return getAcessString(baseExpr, visit(node.name), objectType);
|
|
3134
4014
|
}
|
|
3135
4015
|
const className = baseType.replace(/^\*/, '').replace(/\[.*\]$/, '');
|
|
@@ -3309,7 +4189,7 @@ function visitArrayHigherOrderCall(node) {
|
|
|
3309
4189
|
if (!isPropertyAccessExpression(node.expression))
|
|
3310
4190
|
return undefined;
|
|
3311
4191
|
const methodName = node.expression.name.text;
|
|
3312
|
-
if (!['map', 'filter', 'some', 'find', 'findIndex', 'every', 'forEach', 'reduce', 'join'].includes(methodName)) {
|
|
4192
|
+
if (!['map', 'filter', 'some', 'find', 'findIndex', 'findLast', 'findLastIndex', 'every', 'forEach', 'reduce', 'join', 'entries', 'flatMap'].includes(methodName)) {
|
|
3313
4193
|
return undefined;
|
|
3314
4194
|
}
|
|
3315
4195
|
const arrayExprNode = node.expression.expression;
|
|
@@ -3339,11 +4219,18 @@ function visitArrayHigherOrderCall(node) {
|
|
|
3339
4219
|
return undefined;
|
|
3340
4220
|
importedPackages.add('strings');
|
|
3341
4221
|
importedPackages.add('fmt');
|
|
3342
|
-
|
|
4222
|
+
// join() with no argument defaults to "," in JS
|
|
4223
|
+
const separator = (node.arguments ?? [])[0] ? visit((node.arguments ?? [])[0]) : '","';
|
|
3343
4224
|
const arrVar = getTempName('arrjoin');
|
|
3344
4225
|
const partsVar = getTempName('parts');
|
|
3345
4226
|
return `func() string { ${arrVar} := ${arrayExpr}; ${partsVar} := make([]string, len(${arrVar})); for i, v := range ${arrVar} { ${partsVar}[i] = fmt.Sprintf("%v", v) }; return strings.Join(${partsVar}, ${separator}) }()`;
|
|
3346
4227
|
}
|
|
4228
|
+
if (methodName === 'entries') {
|
|
4229
|
+
// [[index, value], ...] pairs, destructured by for...of
|
|
4230
|
+
if (!isArrayLikeGoType(ownerType))
|
|
4231
|
+
return undefined;
|
|
4232
|
+
return `func() [][]interface{} { __out := [][]interface{}{}; for __i, __v := range ${arrayExpr} { __out = append(__out, []interface{}{float64(__i), __v}) }; return __out }()`;
|
|
4233
|
+
}
|
|
3347
4234
|
const callback = (node.arguments ?? [])[0];
|
|
3348
4235
|
if (!callback) {
|
|
3349
4236
|
return undefined;
|
|
@@ -3351,6 +4238,16 @@ function visitArrayHigherOrderCall(node) {
|
|
|
3351
4238
|
const arrVar = getTempName('arrhof');
|
|
3352
4239
|
const idxVar = getTempName('i');
|
|
3353
4240
|
const itemVar = getTempName('item');
|
|
4241
|
+
if (methodName === 'flatMap') {
|
|
4242
|
+
const callbackInfo = buildArrayCallbackInfo(callback, elementType);
|
|
4243
|
+
const innerType = callbackInfo.returnType.startsWith('[]')
|
|
4244
|
+
? callbackInfo.returnType.slice(2)
|
|
4245
|
+
: elementType;
|
|
4246
|
+
const resultVar = getTempName('flatres');
|
|
4247
|
+
const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
|
|
4248
|
+
const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
|
|
4249
|
+
return `func() []${innerType} { ${arrVar} := ${arrayExpr}; ${resultVar} := []${innerType}{}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { ${resultVar} = append(${resultVar}, (${callbackCall})...) }; return ${resultVar} }()`;
|
|
4250
|
+
}
|
|
3354
4251
|
if (methodName === 'map') {
|
|
3355
4252
|
const callbackInfo = buildArrayCallbackInfo(callback, elementType);
|
|
3356
4253
|
const mappedType = callbackInfo.returnType || 'interface{}';
|
|
@@ -3377,6 +4274,14 @@ function visitArrayHigherOrderCall(node) {
|
|
|
3377
4274
|
const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
|
|
3378
4275
|
return `func() float64 { ${arrVar} := ${arrayExpr}; for ${idxVar}, ${itemVar} := range ${arrVar} { if ${callbackCall} { return float64(${idxVar}) } }; return float64(-1) }()`;
|
|
3379
4276
|
}
|
|
4277
|
+
if (methodName === 'findLastIndex' || methodName === 'findLast') {
|
|
4278
|
+
// Search from the end
|
|
4279
|
+
const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
|
|
4280
|
+
const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
|
|
4281
|
+
const retType = methodName === 'findLast' ? elementType : 'float64';
|
|
4282
|
+
const hit = methodName === 'findLast' ? itemVar : `float64(${idxVar})`;
|
|
4283
|
+
return `func() ${retType} { ${arrVar} := ${arrayExpr}; for ${idxVar} := len(${arrVar}) - 1; ${idxVar} >= 0; ${idxVar}-- { ${itemVar} := ${arrVar}[${idxVar}]; if ${callbackCall} { return ${hit} } }; ${methodName === 'findLast' ? `var __zero ${elementType}; return __zero` : 'return float64(-1)'} }()`;
|
|
4284
|
+
}
|
|
3380
4285
|
if (methodName === 'every') {
|
|
3381
4286
|
const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
|
|
3382
4287
|
const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
|
|
@@ -3513,6 +4418,21 @@ function visitEnumDeclaration(node) {
|
|
|
3513
4418
|
}
|
|
3514
4419
|
members.push(`\t${symbolName} ${enumName} = ${enumName}(${valueExpr})`);
|
|
3515
4420
|
}
|
|
4421
|
+
// Numeric enums are plain float64 constants so they compare against numbers;
|
|
4422
|
+
// string enums keep a named type
|
|
4423
|
+
if (baseType === 'float64') {
|
|
4424
|
+
return `const (\n${members
|
|
4425
|
+
.map((m) => {
|
|
4426
|
+
const name = m.slice(m.indexOf('\t') + 1, m.indexOf(' '));
|
|
4427
|
+
const value = m.slice(m.lastIndexOf('=') + 1).trim();
|
|
4428
|
+
// Strip the enum conversion wrapper: Dir(0) → 0
|
|
4429
|
+
const plain = value.startsWith(`${enumName}(`) && value.endsWith(')')
|
|
4430
|
+
? value.slice(enumName.length + 1, -1)
|
|
4431
|
+
: value;
|
|
4432
|
+
return `\t${name} float64 = ${plain}`;
|
|
4433
|
+
})
|
|
4434
|
+
.join('\n')}\n)`;
|
|
4435
|
+
}
|
|
3516
4436
|
return `type ${enumName} ${baseType}\n\nvar (\n${members.join('\n')}\n)`;
|
|
3517
4437
|
}
|
|
3518
4438
|
function getType(typeNode, getArrayType = false) {
|
|
@@ -3553,9 +4473,21 @@ function getType(typeNode, getArrayType = false) {
|
|
|
3553
4473
|
const ret = typeNode.type ? ` ${getType(typeNode.type)}` : '';
|
|
3554
4474
|
return `func(${params})${ret}`;
|
|
3555
4475
|
}
|
|
4476
|
+
// Type guard: function isCat(p): p is Cat → Go bool
|
|
4477
|
+
if (typeNode.kind === 'TypePredicate') {
|
|
4478
|
+
return 'bool';
|
|
4479
|
+
}
|
|
4480
|
+
// symbol → a unique pointer type usable as a map key
|
|
4481
|
+
if (typeNode.kind === 'SymbolKeyword') {
|
|
4482
|
+
useHelper('symbol');
|
|
4483
|
+
return '*TnSymbol';
|
|
4484
|
+
}
|
|
3556
4485
|
if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName)) {
|
|
3557
4486
|
const name = typeNode.typeName.text;
|
|
3558
4487
|
if (enumNames.has(name)) {
|
|
4488
|
+
// Numeric enums lowered to float64 constants have no named type
|
|
4489
|
+
if (enumBaseTypes.get(name) === 'float64')
|
|
4490
|
+
return 'float64';
|
|
3559
4491
|
return getSafeName(name);
|
|
3560
4492
|
}
|
|
3561
4493
|
const aliasType = getAliasType(name);
|
|
@@ -3563,14 +4495,21 @@ function getType(typeNode, getArrayType = false) {
|
|
|
3563
4495
|
return getType(aliasType, getArrayType);
|
|
3564
4496
|
}
|
|
3565
4497
|
if (name === 'Promise' && typeNode.typeArguments && typeNode.typeArguments.length > 0) {
|
|
3566
|
-
|
|
4498
|
+
const inner = getType(typeNode.typeArguments[0]);
|
|
4499
|
+
return `chan ${inner || 'struct{}'}`;
|
|
3567
4500
|
}
|
|
3568
4501
|
if (name === 'RegExp') {
|
|
3569
4502
|
return '*regexp.Regexp';
|
|
3570
4503
|
}
|
|
4504
|
+
if (name === 'Error') {
|
|
4505
|
+
return '*TnError';
|
|
4506
|
+
}
|
|
3571
4507
|
if (name === 'RegExpExecArray' || name === 'RegExpMatchArray') {
|
|
3572
4508
|
return '[]string';
|
|
3573
4509
|
}
|
|
4510
|
+
if (name === 'TemplateStringsArray' || name === 'ReadonlyArray') {
|
|
4511
|
+
return name === 'TemplateStringsArray' ? '[]string' : getType(typeNode.typeArguments?.[0]);
|
|
4512
|
+
}
|
|
3574
4513
|
if ((name === 'Map' || name === 'Record') &&
|
|
3575
4514
|
typeNode.typeArguments &&
|
|
3576
4515
|
typeNode.typeArguments.length === 2) {
|
|
@@ -3675,12 +4614,16 @@ function goTypeCategory(goType) {
|
|
|
3675
4614
|
return undefined;
|
|
3676
4615
|
if (goType.startsWith('*TnMap['))
|
|
3677
4616
|
return extractMapValueType(goType) === 'struct{}' ? 'Set' : 'Map';
|
|
4617
|
+
if (goType.startsWith('*[]'))
|
|
4618
|
+
goType = goType.slice(1);
|
|
3678
4619
|
if (goType.startsWith('[]'))
|
|
3679
4620
|
return 'array';
|
|
3680
4621
|
if (goType === 'string')
|
|
3681
4622
|
return 'string';
|
|
3682
|
-
if (goType === '*regexp.Regexp')
|
|
4623
|
+
if (goType === '*regexp.Regexp' || goType === '*TnRegex')
|
|
3683
4624
|
return 'RegExp';
|
|
4625
|
+
if (goType === 'time.Time')
|
|
4626
|
+
return 'Date';
|
|
3684
4627
|
return undefined;
|
|
3685
4628
|
}
|
|
3686
4629
|
function isNilLiteral(node) {
|
|
@@ -3691,6 +4634,11 @@ function isNilLiteral(node) {
|
|
|
3691
4634
|
return false;
|
|
3692
4635
|
}
|
|
3693
4636
|
function getAcessString(leftSide, rightSide, objectType) {
|
|
4637
|
+
if (rightSide === 'length' && objectType === 'string') {
|
|
4638
|
+
// JS .length counts UTF-16 code units; rune count matches for the BMP
|
|
4639
|
+
importedPackages.add('unicode/utf8');
|
|
4640
|
+
return `float64(utf8.RuneCountInString(${leftSide}))`;
|
|
4641
|
+
}
|
|
3694
4642
|
if (rightSide === 'length' && objectType !== 'class') {
|
|
3695
4643
|
return `float64(len(${leftSide}))`;
|
|
3696
4644
|
}
|
|
@@ -3814,6 +4762,7 @@ const callHandlers = {
|
|
|
3814
4762
|
},
|
|
3815
4763
|
'JSON.stringify': (_caller, args) => {
|
|
3816
4764
|
useHelper('jsonStringify');
|
|
4765
|
+
useHelper('dynamic');
|
|
3817
4766
|
return `TnJSONStringify(${args[0]}, ${args.length >= 3 ? '" "' : '""'})`;
|
|
3818
4767
|
},
|
|
3819
4768
|
'JSON.parse': (_caller, args) => {
|
|
@@ -3824,6 +4773,17 @@ const callHandlers = {
|
|
|
3824
4773
|
'Object.values': (_caller, args) => `${args[0]}.Values()`,
|
|
3825
4774
|
// Outside for...of (which iterates the map directly): [key, value] pairs
|
|
3826
4775
|
'Object.entries': (_caller, args) => `${args[0]}.Entries()`,
|
|
4776
|
+
// Object.assign copies sources into a fresh ordered map (mutating structs is
|
|
4777
|
+
// not observable the way it is in JS)
|
|
4778
|
+
'Object.assign': (_caller, args) => {
|
|
4779
|
+
useHelper('objectAssign');
|
|
4780
|
+
return `TnObjectAssign(${args.join(', ')})`;
|
|
4781
|
+
},
|
|
4782
|
+
// Promise.all: drain every promise channel, collect the results
|
|
4783
|
+
'Promise.all': (_caller, args) => {
|
|
4784
|
+
useHelper('promiseAll');
|
|
4785
|
+
return `TnPromiseAll(${args[0] ?? '[]chan interface{}{}'})`;
|
|
4786
|
+
},
|
|
3827
4787
|
'Math.log': (_caller, args) => {
|
|
3828
4788
|
importedPackages.add('math');
|
|
3829
4789
|
return `math.Log(${args[0]})`;
|
|
@@ -3855,6 +4815,10 @@ const callHandlers = {
|
|
|
3855
4815
|
'Math.sign': (_caller, args) => {
|
|
3856
4816
|
return `func() float64 { if ${args[0]} > 0 { return 1 }; if ${args[0]} < 0 { return -1 }; return 0 }()`;
|
|
3857
4817
|
},
|
|
4818
|
+
'Date.now': () => {
|
|
4819
|
+
importedPackages.add('time');
|
|
4820
|
+
return 'float64(time.Now().UnixMilli())';
|
|
4821
|
+
},
|
|
3858
4822
|
'console.error': (_caller, args) => {
|
|
3859
4823
|
importedPackages.add('fmt');
|
|
3860
4824
|
importedPackages.add('os');
|
|
@@ -3873,6 +4837,12 @@ const callHandlers = {
|
|
|
3873
4837
|
importedPackages.add('fmt');
|
|
3874
4838
|
return `fmt.Sprintf("%v", ${args[0]})`;
|
|
3875
4839
|
},
|
|
4840
|
+
'String.fromCharCode': (_caller, args) => `string(rune(int(${args[0] ?? '0'})))`,
|
|
4841
|
+
// Symbol(description) — unique values for record keys
|
|
4842
|
+
Symbol: (_caller, args) => {
|
|
4843
|
+
useHelper('symbol');
|
|
4844
|
+
return `TnNewSymbol(${args[0] ?? '""'})`;
|
|
4845
|
+
},
|
|
3876
4846
|
'Number': (_caller, args) => {
|
|
3877
4847
|
useHelper('dynamic');
|
|
3878
4848
|
return `TnNumber(${args[0]})`;
|
|
@@ -3901,6 +4871,10 @@ const stringMethodHandlers = {
|
|
|
3901
4871
|
split: (obj, args) => {
|
|
3902
4872
|
if (args[0]?.startsWith('regexp.MustCompile('))
|
|
3903
4873
|
return `${args[0]}.Split(${obj}, -1)`;
|
|
4874
|
+
if (isTnRegexArg(args[0])) {
|
|
4875
|
+
useHelper('fancyRegex');
|
|
4876
|
+
return `${args[0]}.SplitStr(${obj}, -1)`;
|
|
4877
|
+
}
|
|
3904
4878
|
importedPackages.add('strings');
|
|
3905
4879
|
return `strings.Split(${obj}, ${args[0]})`;
|
|
3906
4880
|
},
|
|
@@ -3928,6 +4902,11 @@ const stringMethodHandlers = {
|
|
|
3928
4902
|
importedPackages.add('strings');
|
|
3929
4903
|
return `float64(strings.Index(${obj}, ${args[0]}))`;
|
|
3930
4904
|
},
|
|
4905
|
+
charCodeAt: (obj, args) => `float64(${obj}[int(${args[0]})])`,
|
|
4906
|
+
localeCompare: (obj, args) => {
|
|
4907
|
+
importedPackages.add('strings');
|
|
4908
|
+
return `float64(strings.Compare(${obj}, ${args[0]}))`;
|
|
4909
|
+
},
|
|
3931
4910
|
includes: (obj, args) => {
|
|
3932
4911
|
importedPackages.add('strings');
|
|
3933
4912
|
return `strings.Contains(${obj}, ${args[0]})`;
|
|
@@ -3974,10 +4953,28 @@ const stringMethodHandlers = {
|
|
|
3974
4953
|
const pad = args[1] ?? '" "';
|
|
3975
4954
|
return `func() string { __s := ${obj}; __n := int(${args[0]}) - len(__s); if __n > 0 { __s = __s + strings.Repeat(${pad}, __n)[:__n] }; return __s }()`;
|
|
3976
4955
|
},
|
|
3977
|
-
match: (obj, args) =>
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
4956
|
+
match: (obj, args) => {
|
|
4957
|
+
if (isTnRegexArg(args[0])) {
|
|
4958
|
+
useHelper('fancyRegex');
|
|
4959
|
+
return `${args[0]}.FindSub(${obj})`;
|
|
4960
|
+
}
|
|
4961
|
+
return `${toGoRegexp(args[0])}.FindStringSubmatch(${obj})`;
|
|
4962
|
+
},
|
|
4963
|
+
matchAll: (obj, args) => {
|
|
4964
|
+
if (isTnRegexArg(args[0])) {
|
|
4965
|
+
useHelper('fancyRegex');
|
|
4966
|
+
return `${args[0]}.FindAllSub(${obj})`;
|
|
4967
|
+
}
|
|
4968
|
+
return `${toGoRegexp(args[0])}.FindAllStringSubmatch(${obj}, -1)`;
|
|
4969
|
+
},
|
|
4970
|
+
search: (obj, args) => {
|
|
4971
|
+
if (isTnRegexArg(args[0])) {
|
|
4972
|
+
useHelper('fancyRegex');
|
|
4973
|
+
return `func() float64 { __loc := ${args[0]}.FindIndexSub(${obj}); if __loc == nil { return -1 }; return float64(__loc[0]) }()`;
|
|
4974
|
+
}
|
|
4975
|
+
return `func() float64 { __loc := ${toGoRegexp(args[0])}.FindStringIndex(${obj}); if __loc == nil { return -1 }; return float64(__loc[0]) }()`;
|
|
4976
|
+
},
|
|
4977
|
+
lastIndexOf: (obj, args) => {
|
|
3981
4978
|
importedPackages.add('strings');
|
|
3982
4979
|
return `float64(strings.LastIndex(${obj}, ${args[0]}))`;
|
|
3983
4980
|
},
|
|
@@ -3990,8 +4987,36 @@ const stringMethodHandlers = {
|
|
|
3990
4987
|
}
|
|
3991
4988
|
};
|
|
3992
4989
|
const regexpMethodHandlers = {
|
|
3993
|
-
test: (obj, args) =>
|
|
3994
|
-
|
|
4990
|
+
test: (obj, args) => currentReceiverGoType === '*TnRegex'
|
|
4991
|
+
? `${obj}.Test(${args[0]})`
|
|
4992
|
+
: `${obj}.MatchString(${args[0]})`,
|
|
4993
|
+
exec: (obj, args) => {
|
|
4994
|
+
if (currentReceiverGoType === '*TnRegex')
|
|
4995
|
+
return `${obj}.Find(${args[0]})`;
|
|
4996
|
+
// Named groups come back as a match object with a .groups record
|
|
4997
|
+
if (obj.includes('(?P<')) {
|
|
4998
|
+
useHelper('namedGroups');
|
|
4999
|
+
return `TnRegexExecNamed(${obj}, ${args[0]})`;
|
|
5000
|
+
}
|
|
5001
|
+
return `${obj}.FindStringSubmatch(${args[0]})`;
|
|
5002
|
+
}
|
|
5003
|
+
};
|
|
5004
|
+
const dateMethodHandlers = {
|
|
5005
|
+
getTime: (obj) => `float64((${obj}).UnixMilli())`,
|
|
5006
|
+
getMilliseconds: (obj) => `float64((${obj}).Millisecond())`,
|
|
5007
|
+
getSeconds: (obj) => `float64((${obj}).Second())`,
|
|
5008
|
+
getMinutes: (obj) => `float64((${obj}).Minute())`,
|
|
5009
|
+
getHours: (obj) => `float64((${obj}).Hour())`,
|
|
5010
|
+
getDate: (obj) => `float64((${obj}).Day())`,
|
|
5011
|
+
getDay: (obj) => `float64(int((${obj}).Weekday()))`,
|
|
5012
|
+
getMonth: (obj) => `float64(int((${obj}).Month()) - 1)`,
|
|
5013
|
+
getFullYear: (obj) => `float64((${obj}).Year())`,
|
|
5014
|
+
toISOString: (obj) => `(${obj}).UTC().Format("2006-01-01T15:04:05.000Z07:00")`,
|
|
5015
|
+
toJSON: (obj) => `(${obj}).UTC().Format("2006-01-01T15:04:05.000Z07:00")`,
|
|
5016
|
+
toString: (obj) => `(${obj}).UTC().Format("Mon Jan 02 2006 15:04:05 GMT+0000 (UTC)")`,
|
|
5017
|
+
toDateString: (obj) => `(${obj}).UTC().Format("Mon Jan 02 2006")`,
|
|
5018
|
+
toTimeString: (obj) => `(${obj}).UTC().Format("15:04:05 GMT+0000 (UTC)")`,
|
|
5019
|
+
valueOf: (obj) => `float64((${obj}).UnixMilli())`
|
|
3995
5020
|
};
|
|
3996
5021
|
const arrayMethodHandlers = {
|
|
3997
5022
|
push: (obj, args) => `${obj} = append(${obj}, ${args.join(', ')})`,
|
|
@@ -4012,7 +5037,7 @@ const arrayMethodHandlers = {
|
|
|
4012
5037
|
unshift: (obj, args) => `${obj} = append([]${receiverElementType()}{${args.join(', ')}}, ${obj}...)`,
|
|
4013
5038
|
join: (obj, args) => {
|
|
4014
5039
|
importedPackages.add('strings');
|
|
4015
|
-
return `strings.Join(${obj}, ${args[0] ?? '""'})`;
|
|
5040
|
+
return `strings.Join(${obj}, ${args[0] ?? '","'})`;
|
|
4016
5041
|
},
|
|
4017
5042
|
slice: (obj, args) => {
|
|
4018
5043
|
if (args.length >= 2)
|
|
@@ -4021,7 +5046,29 @@ const arrayMethodHandlers = {
|
|
|
4021
5046
|
},
|
|
4022
5047
|
reverse: (obj) => {
|
|
4023
5048
|
importedPackages.add('slices');
|
|
4024
|
-
|
|
5049
|
+
const arrayType = currentReceiverGoType?.startsWith('[]') ? currentReceiverGoType : '[]interface{}';
|
|
5050
|
+
return `func() ${arrayType} { __s := ${obj}; slices.Reverse(__s); return __s }()`;
|
|
5051
|
+
},
|
|
5052
|
+
// Removes `deleteCount` elements at `start`, returns them; extra args are inserted
|
|
5053
|
+
splice: (obj, args) => {
|
|
5054
|
+
const elementType = receiverElementType();
|
|
5055
|
+
if (args.length === 0)
|
|
5056
|
+
return `[]${elementType}{}`;
|
|
5057
|
+
const start = `${sliceIndex(obj, args[0])}`;
|
|
5058
|
+
if (args.length === 1) {
|
|
5059
|
+
return `func() []${elementType} { __rm := append([]${elementType}{}, ${obj}[${start}:]...); ${obj} = ${obj}[:${start}]; return __rm }()`;
|
|
5060
|
+
}
|
|
5061
|
+
const insert = args.slice(2).join(', ');
|
|
5062
|
+
return `func() []${elementType} { __rm := append([]${elementType}{}, ${obj}[${start}:(int(${args[1]}))+(${start})]...); __tail := append([]${elementType}{}, ${obj}[(int(${args[1]}))+(${start}):]...); ${obj} = append(${obj}[:${start}], ${insert}); ${obj} = append(${obj}, __tail...); return __rm }()`;
|
|
5063
|
+
},
|
|
5064
|
+
fill: (obj, args) => {
|
|
5065
|
+
const elementType = receiverElementType();
|
|
5066
|
+
const value = args[0] ?? '0';
|
|
5067
|
+
return `func() []${elementType} { __s := ${obj}; for __i := range __s { __s[__i] = ${value} }; return __s }()`;
|
|
5068
|
+
},
|
|
5069
|
+
lastIndexOf: (obj, args) => {
|
|
5070
|
+
const elementType = receiverElementType();
|
|
5071
|
+
return `func() float64 { __x := ${args[0]}; for __i := len(${obj}) - 1; __i >= 0; __i-- { if ${elementEqualsFor(elementType, `${obj}[__i]`, '__x')} { return float64(__i) } }; return float64(-1) }()`;
|
|
4025
5072
|
},
|
|
4026
5073
|
// Sorts in place and returns the array; without a comparator, compares as strings (like JS)
|
|
4027
5074
|
sort: (obj, args) => {
|
|
@@ -4056,9 +5103,10 @@ const arrayMethodHandlers = {
|
|
|
4056
5103
|
function receiverElementType() {
|
|
4057
5104
|
return currentReceiverGoType?.startsWith('[]') ? currentReceiverGoType.slice(2) : 'interface{}';
|
|
4058
5105
|
}
|
|
4059
|
-
// Go expressions that can be assigned to (variables, fields, index expressions
|
|
5106
|
+
// Go expressions that can be assigned to (variables, fields, index expressions,
|
|
5107
|
+
// dereferenced array references)
|
|
4060
5108
|
function isAddressable(code) {
|
|
4061
|
-
return /^[\w.]+(\[[^\]]*\])*$/.test(code);
|
|
5109
|
+
return /^[\w.]+(\[[^\]]*\])*$/.test(code) || /^\(\*[\w.]+\)$/.test(code);
|
|
4062
5110
|
}
|
|
4063
5111
|
// A regex argument as a *regexp.Regexp: compiled literals pass through, pattern
|
|
4064
5112
|
// strings are compiled
|
|
@@ -4076,14 +5124,16 @@ function sliceIndex(obj, index) {
|
|
|
4076
5124
|
return `min(int(${index}), len(${obj}))`;
|
|
4077
5125
|
}
|
|
4078
5126
|
// Equality of two array elements of the current receiver's element type
|
|
4079
|
-
function
|
|
4080
|
-
const elementType = receiverElementType();
|
|
5127
|
+
function elementEqualsFor(elementType, a, b) {
|
|
4081
5128
|
if (elementType === 'interface{}' || elementType.startsWith('*TnMap[') || elementType.startsWith('[]')) {
|
|
4082
5129
|
useHelper('dynamic');
|
|
4083
5130
|
return `TnSame(${a}, ${b})`;
|
|
4084
5131
|
}
|
|
4085
5132
|
return `${a} == ${b}`;
|
|
4086
5133
|
}
|
|
5134
|
+
function elementEquals(a, b) {
|
|
5135
|
+
return elementEqualsFor(receiverElementType(), a, b);
|
|
5136
|
+
}
|
|
4087
5137
|
const mapMethodHandlers = {
|
|
4088
5138
|
set: (obj, args) => `${obj}.Set(${args[0]}, ${args[1]})`,
|
|
4089
5139
|
get: (obj, args) => `${obj}.Get(${args[0]})`,
|
|
@@ -4138,6 +5188,9 @@ function getDynamicCallHandler(caller, objectType) {
|
|
|
4138
5188
|
else if (objectType === 'RegExp') {
|
|
4139
5189
|
handler = regexpMethodHandlers[methodName];
|
|
4140
5190
|
}
|
|
5191
|
+
else if (objectType === 'Date') {
|
|
5192
|
+
handler = dateMethodHandlers[methodName];
|
|
5193
|
+
}
|
|
4141
5194
|
else if (objectType === 'Map') {
|
|
4142
5195
|
handler = mapMethodHandlers[methodName];
|
|
4143
5196
|
}
|
|
@@ -4341,11 +5394,13 @@ function jsReplacementToGo(replacement) {
|
|
|
4341
5394
|
function visitRegexReplace(node) {
|
|
4342
5395
|
if (!isRegexReplaceCall(node))
|
|
4343
5396
|
return undefined;
|
|
4344
|
-
const
|
|
5397
|
+
const pattern = node.arguments[0];
|
|
5398
|
+
const replacement = node.arguments[1];
|
|
4345
5399
|
const isGlobal = node.expression.name.text === 'replaceAll' ||
|
|
4346
5400
|
(isRegularExpressionLiteral(pattern) &&
|
|
4347
5401
|
pattern.text.substring(pattern.text.lastIndexOf('/') + 1).includes('g'));
|
|
4348
5402
|
const target = toGoValueOfType(node.expression.expression, 'string');
|
|
5403
|
+
const reIsWrapper = inferExpressionType(pattern) === '*TnRegex';
|
|
4349
5404
|
const re = visit(pattern);
|
|
4350
5405
|
importedPackages.add('regexp');
|
|
4351
5406
|
if (isArrowFunction(replacement) || isFunctionExpression(replacement)) {
|
|
@@ -4354,9 +5409,19 @@ function visitRegexReplace(node) {
|
|
|
4354
5409
|
const groupArgs = (replacement.parameters ?? [])
|
|
4355
5410
|
.map((_p, index) => `TnGroup(__m, ${index})`)
|
|
4356
5411
|
.join(', ');
|
|
5412
|
+
if (reIsWrapper) {
|
|
5413
|
+
useHelper('fancyRegex');
|
|
5414
|
+
return `TnRegexReplaceFuncW(${re}, ${target}, func(__m []string) string { return (${callback})(${groupArgs}) }, ${isGlobal})`;
|
|
5415
|
+
}
|
|
4357
5416
|
return `TnRegexReplaceFunc(${re}, ${target}, func(__m []string) string { return (${callback})(${groupArgs}) }, ${isGlobal})`;
|
|
4358
5417
|
}
|
|
4359
5418
|
const goReplacement = jsReplacementToGo(replacement);
|
|
5419
|
+
if (reIsWrapper) {
|
|
5420
|
+
useHelper('fancyRegex');
|
|
5421
|
+
return isGlobal
|
|
5422
|
+
? `TnRegexWrapReplaceAll(${re}, ${target}, ${goReplacement})`
|
|
5423
|
+
: `TnRegexWrapReplaceFirst(${re}, ${target}, ${goReplacement})`;
|
|
5424
|
+
}
|
|
4360
5425
|
if (isGlobal)
|
|
4361
5426
|
return `${re}.ReplaceAllString(${target}, ${goReplacement})`;
|
|
4362
5427
|
useHelper('regexReplaceFirst');
|
|
@@ -4372,6 +5437,28 @@ function jsRegexFlagsToGo(flags) {
|
|
|
4372
5437
|
goFlags += 's';
|
|
4373
5438
|
return goFlags ? `(?${goFlags})` : '';
|
|
4374
5439
|
}
|
|
5440
|
+
// Escapes a JS regex pattern for a Go string literal and rewrites JS named
|
|
5441
|
+
// groups to RE2 syntax: (?<name> → (?P<name>
|
|
5442
|
+
function translateJsRegexPattern(pattern) {
|
|
5443
|
+
return pattern
|
|
5444
|
+
.replace(/\\/g, '\\\\')
|
|
5445
|
+
.replace(/"/g, '\\"')
|
|
5446
|
+
.replace(/\(\?<([A-Za-z_][A-Za-z0-9_]*)>/g, '(?P<$1>');
|
|
5447
|
+
}
|
|
5448
|
+
// Patterns the plain RE2 path cannot serve: /g state or RE2-unsupported syntax.
|
|
5449
|
+
// Named groups (?<name> are fine (translated to (?P<name>), so strip them
|
|
5450
|
+
// before testing for lookarounds (?= (?! (?<= (?<!
|
|
5451
|
+
function needsStatefulRegex(pattern, flags) {
|
|
5452
|
+
if (flags.includes('g'))
|
|
5453
|
+
return true;
|
|
5454
|
+
const stripped = pattern.replace(/\(\?<[A-Za-z_][A-Za-z0-9_]*>/g, '');
|
|
5455
|
+
// (?= (?! and any remaining (?< (lookbehind) need the backtracker
|
|
5456
|
+
return /\\[1-9]|\(\?[=!<]/.test(stripped);
|
|
5457
|
+
}
|
|
5458
|
+
// Whether a visited regex argument is the stateful wrapper type
|
|
5459
|
+
function isTnRegexArg(arg) {
|
|
5460
|
+
return arg.startsWith('TnRegexCompile(') || variableGoTypes.get(arg) === '*TnRegex';
|
|
5461
|
+
}
|
|
4375
5462
|
function getTypeParameters(typeParameters) {
|
|
4376
5463
|
if (!typeParameters || typeParameters.length === 0)
|
|
4377
5464
|
return '';
|
|
@@ -4407,9 +5494,20 @@ function getParameterGoType(param) {
|
|
|
4407
5494
|
return '...interface{}';
|
|
4408
5495
|
}
|
|
4409
5496
|
if (param.type) {
|
|
4410
|
-
|
|
5497
|
+
let explicitType = getType(param.type);
|
|
4411
5498
|
if (explicitType === ':')
|
|
4412
5499
|
return 'interface{}';
|
|
5500
|
+
// Array parameters are references (mutations are visible to the caller) —
|
|
5501
|
+
// except unbound type parameters (Go generics), template-tag arrays, and
|
|
5502
|
+
// dynamic []interface{} (runtime helper arrays)
|
|
5503
|
+
if (explicitType.startsWith('[]') && explicitType !== '[]interface{}' && !param.dotDotDotToken) {
|
|
5504
|
+
const isTaggedTemplateArray = isTypeReferenceNode(param.type) &&
|
|
5505
|
+
isIdentifier(param.type.typeName) &&
|
|
5506
|
+
param.type.typeName.text === 'TemplateStringsArray';
|
|
5507
|
+
if (!isUnboundGoType(explicitType.slice(2)) && !isTaggedTemplateArray) {
|
|
5508
|
+
explicitType = `*${explicitType}`;
|
|
5509
|
+
}
|
|
5510
|
+
}
|
|
4413
5511
|
return param.questionToken ? makeNullableType(explicitType) : explicitType;
|
|
4414
5512
|
}
|
|
4415
5513
|
if (param.initializer) {
|
|
@@ -4439,7 +5537,19 @@ function registerParameterType(param) {
|
|
|
4439
5537
|
return;
|
|
4440
5538
|
const name = param.name.text;
|
|
4441
5539
|
const goType = getParameterGoType(param);
|
|
4442
|
-
|
|
5540
|
+
if (goType.startsWith('...')) {
|
|
5541
|
+
// Rest parameters own a fresh array; plain slice storage is fine
|
|
5542
|
+
variableGoTypes.set(name, `[]${goType.slice(3)}`);
|
|
5543
|
+
referenceArrays.delete(name);
|
|
5544
|
+
}
|
|
5545
|
+
else {
|
|
5546
|
+
// The signature type already carries the reference wrapper when needed
|
|
5547
|
+
variableGoTypes.set(name, goType);
|
|
5548
|
+
if (goType.startsWith('*[]'))
|
|
5549
|
+
referenceArrays.add(name);
|
|
5550
|
+
else
|
|
5551
|
+
referenceArrays.delete(name);
|
|
5552
|
+
}
|
|
4443
5553
|
const category = param.type ? getTypeCategory(param.type) : undefined;
|
|
4444
5554
|
if (category)
|
|
4445
5555
|
variableTypes.set(name, category);
|
|
@@ -4530,7 +5640,7 @@ function withContextualParameters(fn, info) {
|
|
|
4530
5640
|
// Destructured parameters ({ a, b }: T / [x, y]: T[]) get a temporary name
|
|
4531
5641
|
function getParameterName(param, parameters) {
|
|
4532
5642
|
if (isIdentifier(param.name))
|
|
4533
|
-
return
|
|
5643
|
+
return getSafeName(param.name.text);
|
|
4534
5644
|
return `__param${parameters.indexOf(param)}`;
|
|
4535
5645
|
}
|
|
4536
5646
|
// Unpacks destructured parameters at the start of the function body
|
|
@@ -4589,9 +5699,12 @@ function getFunctionParametersInfo(parameters) {
|
|
|
4589
5699
|
signatureParts.push('__defaultArgs ...interface{}');
|
|
4590
5700
|
const prefixBlockContent = defaultedParams
|
|
4591
5701
|
.map((param, index) => {
|
|
4592
|
-
const paramName =
|
|
5702
|
+
const paramName = getSafeName(param.name.text);
|
|
4593
5703
|
const paramType = getParameterGoType(param);
|
|
4594
|
-
|
|
5704
|
+
// Array-reference parameters need the default boxed to *[]T
|
|
5705
|
+
const defaultValue = paramType.startsWith('*[]')
|
|
5706
|
+
? toGoValueOfType(param.initializer, paramType)
|
|
5707
|
+
: visit(param.initializer);
|
|
4595
5708
|
if (paramType === 'interface{}') {
|
|
4596
5709
|
return `var ${paramName} interface{}\n\t\tif len(__defaultArgs) > ${index} {\n\t\t\t${paramName} = __defaultArgs[${index}]\n\t\t} else {\n\t\t\t${paramName} = ${defaultValue}\n\t\t}\n\t\t`;
|
|
4597
5710
|
}
|
|
@@ -4613,7 +5726,14 @@ function getSafeName(name) {
|
|
|
4613
5726
|
return renamedFunctions.get(name);
|
|
4614
5727
|
}
|
|
4615
5728
|
function getPromiseChannelType(node) {
|
|
4616
|
-
|
|
5729
|
+
// An explicit new Promise<T> / async return type wins
|
|
5730
|
+
if (isNewExpression(node) &&
|
|
5731
|
+
isIdentifier(node.expression) &&
|
|
5732
|
+
node.expression.text === 'Promise' &&
|
|
5733
|
+
(node.typeArguments ?? []).length > 0) {
|
|
5734
|
+
return getType(node.typeArguments[0]) || 'struct{}';
|
|
5735
|
+
}
|
|
5736
|
+
let parent = node;
|
|
4617
5737
|
while (parent) {
|
|
4618
5738
|
if (isFunctionDeclaration(parent) ||
|
|
4619
5739
|
isMethodDeclaration(parent) ||
|
|
@@ -4624,7 +5744,7 @@ function getPromiseChannelType(node) {
|
|
|
4624
5744
|
if (parent.type.typeName.text === 'Promise' &&
|
|
4625
5745
|
parent.type.typeArguments &&
|
|
4626
5746
|
parent.type.typeArguments.length > 0) {
|
|
4627
|
-
return getType(parent.type.typeArguments[0]);
|
|
5747
|
+
return getType(parent.type.typeArguments[0]) || 'struct{}';
|
|
4628
5748
|
}
|
|
4629
5749
|
}
|
|
4630
5750
|
break;
|
|
@@ -4633,6 +5753,38 @@ function getPromiseChannelType(node) {
|
|
|
4633
5753
|
}
|
|
4634
5754
|
return 'interface{}';
|
|
4635
5755
|
}
|
|
5756
|
+
// A function whose body runs in a goroutine and communicates over a channel:
|
|
5757
|
+
// async functions (return sends) and generators (yield sends)
|
|
5758
|
+
function emitChannelFunction(node, isAsync) {
|
|
5759
|
+
const name = getSafeName(visit(node.name, { inline: true }));
|
|
5760
|
+
const typeParams = getTypeParameters(node.typeParameters);
|
|
5761
|
+
const parameterInfo = withContextualParameters(node, getFunctionParametersInfo(node.parameters ?? []));
|
|
5762
|
+
if (node.body && isBlock(node.body)) {
|
|
5763
|
+
prescanVariableDeclarations(node.body);
|
|
5764
|
+
}
|
|
5765
|
+
const chanType = isAsync ? getPromiseChannelType(node) : getGeneratorYieldType(node);
|
|
5766
|
+
const channelVar = '__ch';
|
|
5767
|
+
const prevChannel = asyncChannelVar;
|
|
5768
|
+
const prevFn = asyncFunctionNode;
|
|
5769
|
+
asyncChannelVar = channelVar;
|
|
5770
|
+
asyncFunctionNode = node;
|
|
5771
|
+
const body = visit(node.body, { prefixBlockContent: parameterInfo.prefixBlockContent });
|
|
5772
|
+
asyncChannelVar = prevChannel;
|
|
5773
|
+
asyncFunctionNode = prevFn;
|
|
5774
|
+
return (`func ${name}${typeParams}(${parameterInfo.signature}) chan ${chanType} {\n\t${channelVar} := make(chan ${chanType})\n\tgo func() {\n\t\tdefer close(${channelVar})\n\t\t${body.trim()}\n\t}()\n\treturn ${channelVar}\n}`);
|
|
5775
|
+
}
|
|
5776
|
+
// The type a generator yields: from its first yield expression's operand
|
|
5777
|
+
function getGeneratorYieldType(fn) {
|
|
5778
|
+
let found;
|
|
5779
|
+
containsNode(fn, (n) => {
|
|
5780
|
+
if (n.kind === 'YieldExpression' && found === undefined) {
|
|
5781
|
+
found = n.expression ? inferExpressionType(n.expression) : undefined;
|
|
5782
|
+
return true;
|
|
5783
|
+
}
|
|
5784
|
+
return false;
|
|
5785
|
+
});
|
|
5786
|
+
return found && found !== 'nil' ? found : 'interface{}';
|
|
5787
|
+
}
|
|
4636
5788
|
function visitPromiseReturn(node, options) {
|
|
4637
5789
|
const callback = node.arguments?.[0];
|
|
4638
5790
|
if (!callback || (!isArrowFunction(callback) && !isFunctionExpression(callback))) {
|
|
@@ -4713,6 +5865,13 @@ function visitNewSet(node) {
|
|
|
4713
5865
|
elementType = getArrayLiteralElementType(node.arguments[0]);
|
|
4714
5866
|
}
|
|
4715
5867
|
const args = node.arguments ?? [];
|
|
5868
|
+
// new Set(otherSet/values()) — copy at runtime
|
|
5869
|
+
if (args.length > 0 && !isArrayLiteralExpression(args[0])) {
|
|
5870
|
+
useHelper('orderedMap');
|
|
5871
|
+
const src = getTempName('setsrc');
|
|
5872
|
+
const item = getTempName('item');
|
|
5873
|
+
return `func() *TnMap[${elementType}, struct{}] { ${src} := ${visit(args[0])}; __s := TnNewMap[${elementType}, struct{}](); for _, ${item} := range ${src} { __s.Set(${item}, struct{}{}) }; return __s }()`;
|
|
5874
|
+
}
|
|
4716
5875
|
const values = args.length > 0 && isArrayLiteralExpression(args[0])
|
|
4717
5876
|
? args[0].elements.map((el) => `${visit(el)}, struct{}{}`)
|
|
4718
5877
|
: [];
|
|
@@ -4788,11 +5947,11 @@ function includeLocalImport(code, dir, goFileName) {
|
|
|
4788
5947
|
.filter((pkg) => {
|
|
4789
5948
|
if (!helperProvidedPackages.has(pkg))
|
|
4790
5949
|
return true;
|
|
4791
|
-
const
|
|
4792
|
-
return stripGoStrings(fileCode).includes(`${
|
|
5950
|
+
const pkgName = goImportAliases[pkg] ?? pkg.split('/').pop();
|
|
5951
|
+
return stripGoStrings(fileCode).includes(`${pkgName}.`);
|
|
4793
5952
|
})
|
|
4794
5953
|
.sort()
|
|
4795
|
-
.map((pkg) =>
|
|
5954
|
+
.map((pkg) => goImportLine(pkg))
|
|
4796
5955
|
.join('\n');
|
|
4797
5956
|
const parts = ['package main'];
|
|
4798
5957
|
if (fileImports)
|
|
@@ -4868,10 +6027,26 @@ function needPkg(pkg) {
|
|
|
4868
6027
|
}
|
|
4869
6028
|
// Marks a Go helper as used; also registers the packages the helper needs,
|
|
4870
6029
|
// since the import list is serialized before helpers are emitted.
|
|
6030
|
+
const helperRequires = {
|
|
6031
|
+
objectAssign: ['orderedMap'],
|
|
6032
|
+
promiseAll: ['orderedMap', 'asyncWait'],
|
|
6033
|
+
namedGroups: ['orderedMap'],
|
|
6034
|
+
fancyRegex: ['orderedMap'],
|
|
6035
|
+
dynamic: ['error'],
|
|
6036
|
+
moduleUrl: ['pathToFileURL'],
|
|
6037
|
+
timerWait: [],
|
|
6038
|
+
asyncWait: []
|
|
6039
|
+
};
|
|
4871
6040
|
function useHelper(id) {
|
|
4872
6041
|
if (usedHelpers.has(id))
|
|
4873
6042
|
return;
|
|
4874
6043
|
usedHelpers.add(id);
|
|
6044
|
+
// The match object type the named-groups helper returns
|
|
6045
|
+
if (id === 'namedGroups') {
|
|
6046
|
+
interfacePropertyTypes.set('TnMatch', new Map([['groups', '*TnMap[string, string]']]));
|
|
6047
|
+
}
|
|
6048
|
+
for (const dep of helperRequires[id] ?? [])
|
|
6049
|
+
useHelper(dep);
|
|
4875
6050
|
for (const pkg of helperPackages[id] ?? []) {
|
|
4876
6051
|
importedPackages.add(pkg);
|
|
4877
6052
|
helperProvidedPackages.add(pkg);
|
|
@@ -5027,7 +6202,8 @@ const nodeModuleMappings = {
|
|
|
5027
6202
|
const helperPackages = {
|
|
5028
6203
|
regexReplaceFirst: ['regexp'],
|
|
5029
6204
|
orderedMap: ['iter', 'encoding/json', 'bytes', 'fmt'],
|
|
5030
|
-
jsonStringify: ['encoding/json', 'bytes', 'strings'],
|
|
6205
|
+
jsonStringify: ['encoding/json', 'bytes', 'strings', 'unsafe'],
|
|
6206
|
+
objectAssign: ['reflect', 'unsafe'],
|
|
5031
6207
|
typeOf: ['reflect'],
|
|
5032
6208
|
dynamic: ['math', 'strings', 'fmt', 'sort', 'reflect', 'strconv', 'regexp'],
|
|
5033
6209
|
regexReplaceFunc: ['regexp'],
|
|
@@ -5041,6 +6217,15 @@ const helperPackages = {
|
|
|
5041
6217
|
fileURLToPath: ['net/url', 'path/filepath', 'strings'],
|
|
5042
6218
|
pathToFileURL: ['net/url', 'path/filepath'],
|
|
5043
6219
|
osPlatform: ['runtime'],
|
|
6220
|
+
error: [],
|
|
6221
|
+
bigint: ['math/big'],
|
|
6222
|
+
symbol: [],
|
|
6223
|
+
fancyRegex: ['regexp', 'strings', 'strconv', 'fmt'],
|
|
6224
|
+
namedGroups: ['regexp'],
|
|
6225
|
+
moduleUrl: ['os'],
|
|
6226
|
+
asyncWait: ['sync'],
|
|
6227
|
+
timerWait: ['sync', 'time'],
|
|
6228
|
+
promiseAll: [],
|
|
5044
6229
|
exec: ['os/exec', 'bytes', 'runtime'],
|
|
5045
6230
|
execSync: ['os/exec', 'bytes', 'runtime'],
|
|
5046
6231
|
runInherit: ['os/exec', 'os'],
|
|
@@ -5066,6 +6251,10 @@ func TnGet(obj interface{}, key string) interface{} {
|
|
|
5066
6251
|
if key == "length" {
|
|
5067
6252
|
return float64(len(o))
|
|
5068
6253
|
}
|
|
6254
|
+
case *TnError:
|
|
6255
|
+
if key == "message" {
|
|
6256
|
+
return o.message
|
|
6257
|
+
}
|
|
5069
6258
|
}
|
|
5070
6259
|
return nil
|
|
5071
6260
|
}
|
|
@@ -5284,6 +6473,19 @@ func TnHas(obj interface{}, key interface{}) bool {
|
|
|
5284
6473
|
return found
|
|
5285
6474
|
}
|
|
5286
6475
|
}
|
|
6476
|
+
// Struct objects: the in operator checks for a field
|
|
6477
|
+
rv := reflect.ValueOf(obj)
|
|
6478
|
+
if rv.Kind() == reflect.Pointer {
|
|
6479
|
+
if rv.IsNil() {
|
|
6480
|
+
return false
|
|
6481
|
+
}
|
|
6482
|
+
rv = rv.Elem()
|
|
6483
|
+
}
|
|
6484
|
+
if rv.Kind() == reflect.Struct {
|
|
6485
|
+
if k, ok := key.(string); ok {
|
|
6486
|
+
return rv.FieldByName(k).IsValid()
|
|
6487
|
+
}
|
|
6488
|
+
}
|
|
5287
6489
|
return false
|
|
5288
6490
|
}
|
|
5289
6491
|
|
|
@@ -5397,7 +6599,25 @@ func TnTruthy(v interface{}) bool {
|
|
|
5397
6599
|
case string:
|
|
5398
6600
|
return t != ""
|
|
5399
6601
|
}
|
|
6602
|
+
rv := reflect.ValueOf(v)
|
|
6603
|
+
switch rv.Kind() {
|
|
6604
|
+
case reflect.Pointer, reflect.Interface, reflect.Map, reflect.Slice:
|
|
6605
|
+
return !rv.IsNil()
|
|
6606
|
+
}
|
|
5400
6607
|
return true
|
|
6608
|
+
}
|
|
6609
|
+
|
|
6610
|
+
// nil or a typed nil pointer inside an interface (undefined vs defined)
|
|
6611
|
+
func TnIsNil(v interface{}) bool {
|
|
6612
|
+
if v == nil {
|
|
6613
|
+
return true
|
|
6614
|
+
}
|
|
6615
|
+
rv := reflect.ValueOf(v)
|
|
6616
|
+
switch rv.Kind() {
|
|
6617
|
+
case reflect.Pointer, reflect.Interface, reflect.Map, reflect.Slice, reflect.Func, reflect.Chan:
|
|
6618
|
+
return rv.IsNil()
|
|
6619
|
+
}
|
|
6620
|
+
return false
|
|
5401
6621
|
}`,
|
|
5402
6622
|
typeOf: `func TnTypeOf(v interface{}) string {
|
|
5403
6623
|
switch v.(type) {
|
|
@@ -5415,13 +6635,124 @@ func TnTruthy(v interface{}) bool {
|
|
|
5415
6635
|
}
|
|
5416
6636
|
return "object"
|
|
5417
6637
|
}`,
|
|
5418
|
-
jsonStringify:
|
|
6638
|
+
jsonStringify: `// JSON.stringify including unexported struct fields (Go's encoder skips them)
|
|
6639
|
+
func TnJSONStringify(v interface{}, indent string) string {
|
|
5419
6640
|
var buf bytes.Buffer
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
6641
|
+
tnJSONWrite(&buf, reflect.ValueOf(v), indent, 0)
|
|
6642
|
+
return buf.String()
|
|
6643
|
+
}
|
|
6644
|
+
|
|
6645
|
+
func tnJSONNewline(buf *bytes.Buffer, indent string, depth int) {
|
|
6646
|
+
if indent == "" {
|
|
6647
|
+
return
|
|
6648
|
+
}
|
|
6649
|
+
buf.WriteByte('\\n')
|
|
6650
|
+
for i := 0; i < depth; i++ {
|
|
6651
|
+
buf.WriteString(indent)
|
|
6652
|
+
}
|
|
6653
|
+
}
|
|
6654
|
+
|
|
6655
|
+
func tnJSONWrite(buf *bytes.Buffer, rv reflect.Value, indent string, depth int) {
|
|
6656
|
+
if !rv.IsValid() {
|
|
6657
|
+
buf.WriteString("null")
|
|
6658
|
+
return
|
|
6659
|
+
}
|
|
6660
|
+
switch rv.Kind() {
|
|
6661
|
+
case reflect.Pointer, reflect.Interface:
|
|
6662
|
+
if rv.IsNil() {
|
|
6663
|
+
buf.WriteString("null")
|
|
6664
|
+
return
|
|
6665
|
+
}
|
|
6666
|
+
tnJSONWrite(buf, rv.Elem(), indent, depth)
|
|
6667
|
+
case reflect.Bool:
|
|
6668
|
+
if rv.Bool() {
|
|
6669
|
+
buf.WriteString("true")
|
|
6670
|
+
} else {
|
|
6671
|
+
buf.WriteString("false")
|
|
6672
|
+
}
|
|
6673
|
+
case reflect.Float32, reflect.Float64:
|
|
6674
|
+
buf.WriteString(TnNumStr(rv.Float()))
|
|
6675
|
+
case reflect.String:
|
|
6676
|
+
data, _ := json.Marshal(rv.String())
|
|
6677
|
+
buf.Write(data)
|
|
6678
|
+
case reflect.Slice, reflect.Array:
|
|
6679
|
+
if rv.Kind() == reflect.Slice && rv.IsNil() {
|
|
6680
|
+
buf.WriteString("null")
|
|
6681
|
+
return
|
|
6682
|
+
}
|
|
6683
|
+
buf.WriteByte('[')
|
|
6684
|
+
for i := 0; i < rv.Len(); i++ {
|
|
6685
|
+
if i > 0 {
|
|
6686
|
+
buf.WriteByte(',')
|
|
6687
|
+
tnJSONNewline(buf, indent, depth+1)
|
|
6688
|
+
}
|
|
6689
|
+
tnJSONWrite(buf, rv.Index(i), indent, depth+1)
|
|
6690
|
+
}
|
|
6691
|
+
buf.WriteByte(']')
|
|
6692
|
+
case reflect.Map:
|
|
6693
|
+
buf.WriteByte('{')
|
|
6694
|
+
keys := make([]string, 0, rv.Len())
|
|
6695
|
+
byKey := map[string]reflect.Value{}
|
|
6696
|
+
for _, k := range rv.MapKeys() {
|
|
6697
|
+
ks := fmt.Sprint(k.Interface())
|
|
6698
|
+
keys = append(keys, ks)
|
|
6699
|
+
byKey[ks] = rv.MapIndex(k)
|
|
6700
|
+
}
|
|
6701
|
+
sort.Strings(keys)
|
|
6702
|
+
for i, ks := range keys {
|
|
6703
|
+
if i > 0 {
|
|
6704
|
+
buf.WriteByte(',')
|
|
6705
|
+
tnJSONNewline(buf, indent, depth+1)
|
|
6706
|
+
}
|
|
6707
|
+
kd, _ := json.Marshal(ks)
|
|
6708
|
+
buf.Write(kd)
|
|
6709
|
+
buf.WriteByte(':')
|
|
6710
|
+
tnJSONWrite(buf, byKey[ks], indent, depth+1)
|
|
6711
|
+
}
|
|
6712
|
+
buf.WriteByte('}')
|
|
6713
|
+
case reflect.Struct:
|
|
6714
|
+
if m, ok := rv.Interface().(json.Marshaler); ok {
|
|
6715
|
+
data, err := m.MarshalJSON()
|
|
6716
|
+
if err == nil {
|
|
6717
|
+
buf.Write(data)
|
|
6718
|
+
return
|
|
6719
|
+
}
|
|
6720
|
+
}
|
|
6721
|
+
buf.WriteByte('{')
|
|
6722
|
+
rt := rv.Type()
|
|
6723
|
+
first := true
|
|
6724
|
+
for i := 0; i < rt.NumField(); i++ {
|
|
6725
|
+
f := rt.Field(i)
|
|
6726
|
+
value := rv.Field(i)
|
|
6727
|
+
if value.Kind() == reflect.Func {
|
|
6728
|
+
continue
|
|
6729
|
+
}
|
|
6730
|
+
if !value.CanInterface() {
|
|
6731
|
+
// Unexported field: read it through a pointer to bypass the flag
|
|
6732
|
+
if !value.CanAddr() {
|
|
6733
|
+
continue
|
|
6734
|
+
}
|
|
6735
|
+
value = reflect.NewAt(value.Type(), unsafe.Pointer(value.UnsafeAddr())).Elem()
|
|
6736
|
+
}
|
|
6737
|
+
if !first {
|
|
6738
|
+
buf.WriteByte(',')
|
|
6739
|
+
tnJSONNewline(buf, indent, depth+1)
|
|
6740
|
+
}
|
|
6741
|
+
first = false
|
|
6742
|
+
kd, _ := json.Marshal(f.Name)
|
|
6743
|
+
buf.Write(kd)
|
|
6744
|
+
buf.WriteByte(':')
|
|
6745
|
+
tnJSONWrite(buf, value, indent, depth+1)
|
|
6746
|
+
}
|
|
6747
|
+
buf.WriteByte('}')
|
|
6748
|
+
default:
|
|
6749
|
+
data, err := json.Marshal(rv.Interface())
|
|
6750
|
+
if err != nil {
|
|
6751
|
+
buf.WriteString("null")
|
|
6752
|
+
return
|
|
6753
|
+
}
|
|
6754
|
+
buf.Write(data)
|
|
6755
|
+
}
|
|
5425
6756
|
}`,
|
|
5426
6757
|
orderedMap: `// TnMap: JS Map/Set/object semantics — keys iterate in insertion order
|
|
5427
6758
|
type TnMap[K comparable, V any] struct {
|
|
@@ -5554,6 +6885,902 @@ func (m *TnMap[K, V]) MarshalJSON() ([]byte, error) {
|
|
|
5554
6885
|
return s
|
|
5555
6886
|
}
|
|
5556
6887
|
return s[:loc[0]] + string(re.ExpandString(nil, repl, s, loc)) + s[loc[1]:]
|
|
6888
|
+
}`,
|
|
6889
|
+
namedGroups: `// TnMatch: the result of exec on a regex with named groups
|
|
6890
|
+
type TnMatch struct {
|
|
6891
|
+
groups *TnMap[string, string]
|
|
6892
|
+
}
|
|
6893
|
+
|
|
6894
|
+
func TnRegexExecNamed(re *regexp.Regexp, s string) *TnMatch {
|
|
6895
|
+
m := re.FindStringSubmatch(s)
|
|
6896
|
+
if m == nil {
|
|
6897
|
+
return nil
|
|
6898
|
+
}
|
|
6899
|
+
res := &TnMatch{groups: TnNewMap[string, string]()}
|
|
6900
|
+
for i, name := range re.SubexpNames() {
|
|
6901
|
+
if name != "" && i < len(m) {
|
|
6902
|
+
res.groups.Set(name, m[i])
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
return res
|
|
6906
|
+
}`,
|
|
6907
|
+
fancyRegex: `// TnRegex: a regex with JS semantics the plain RE2 path cannot serve —
|
|
6908
|
+
// /g state (lastIndex) and backreference/lookaround patterns (a small
|
|
6909
|
+
// backtracking engine).
|
|
6910
|
+
type TnRegex struct {
|
|
6911
|
+
re *regexp.Regexp
|
|
6912
|
+
bt *tnBt
|
|
6913
|
+
lastIndex int
|
|
6914
|
+
global bool
|
|
6915
|
+
}
|
|
6916
|
+
|
|
6917
|
+
func TnRegexCompile(pattern string, global bool) *TnRegex {
|
|
6918
|
+
if re, err := regexp.Compile(pattern); err == nil {
|
|
6919
|
+
return &TnRegex{re: re, global: global}
|
|
6920
|
+
}
|
|
6921
|
+
return &TnRegex{bt: tnBtCompile(pattern), global: global}
|
|
6922
|
+
}
|
|
6923
|
+
|
|
6924
|
+
func (r *TnRegex) Test(s string) bool {
|
|
6925
|
+
groups, _ := r.find(s)
|
|
6926
|
+
return groups != nil
|
|
6927
|
+
}
|
|
6928
|
+
|
|
6929
|
+
func (r *TnRegex) Find(s string) []string {
|
|
6930
|
+
groups, _ := r.find(s)
|
|
6931
|
+
return groups
|
|
6932
|
+
}
|
|
6933
|
+
|
|
6934
|
+
// First match only, without advancing state (string .match)
|
|
6935
|
+
func (r *TnRegex) FindSub(s string) []string {
|
|
6936
|
+
savedG, savedI := r.global, r.lastIndex
|
|
6937
|
+
r.global, r.lastIndex = false, 0
|
|
6938
|
+
groups, _ := r.find(s)
|
|
6939
|
+
r.global, r.lastIndex = savedG, savedI
|
|
6940
|
+
return groups
|
|
6941
|
+
}
|
|
6942
|
+
|
|
6943
|
+
func (r *TnRegex) FindAllSub(s string) [][]string {
|
|
6944
|
+
savedG, savedI := r.global, r.lastIndex
|
|
6945
|
+
r.global, r.lastIndex = true, 0
|
|
6946
|
+
var out [][]string
|
|
6947
|
+
for {
|
|
6948
|
+
groups, _ := r.find(s)
|
|
6949
|
+
if groups == nil {
|
|
6950
|
+
break
|
|
6951
|
+
}
|
|
6952
|
+
out = append(out, groups)
|
|
6953
|
+
}
|
|
6954
|
+
r.global, r.lastIndex = savedG, savedI
|
|
6955
|
+
return out
|
|
6956
|
+
}
|
|
6957
|
+
|
|
6958
|
+
func (r *TnRegex) FindIndexSub(s string) []int {
|
|
6959
|
+
savedG, savedI := r.global, r.lastIndex
|
|
6960
|
+
r.global, r.lastIndex = false, 0
|
|
6961
|
+
_, loc := r.find(s)
|
|
6962
|
+
r.global, r.lastIndex = savedG, savedI
|
|
6963
|
+
if loc == nil {
|
|
6964
|
+
return nil
|
|
6965
|
+
}
|
|
6966
|
+
return loc[:2]
|
|
6967
|
+
}
|
|
6968
|
+
|
|
6969
|
+
func (r *TnRegex) SplitStr(s string, n int) []string {
|
|
6970
|
+
if r.bt == nil {
|
|
6971
|
+
return r.re.Split(s, n)
|
|
6972
|
+
}
|
|
6973
|
+
savedG, savedI := r.global, r.lastIndex
|
|
6974
|
+
r.global, r.lastIndex = true, 0
|
|
6975
|
+
parts := []string{}
|
|
6976
|
+
last := 0
|
|
6977
|
+
for {
|
|
6978
|
+
_, loc := r.find(s)
|
|
6979
|
+
if loc == nil || (n > 0 && len(parts) >= n-1) {
|
|
6980
|
+
break
|
|
6981
|
+
}
|
|
6982
|
+
parts = append(parts, s[last:loc[0]])
|
|
6983
|
+
last = loc[1]
|
|
6984
|
+
if loc[1] == loc[0] && loc[1] < len(s) {
|
|
6985
|
+
parts = append(parts, string(s[loc[1]]))
|
|
6986
|
+
last = loc[1] + 1
|
|
6987
|
+
}
|
|
6988
|
+
}
|
|
6989
|
+
r.global, r.lastIndex = savedG, savedI
|
|
6990
|
+
return append(parts, s[last:])
|
|
6991
|
+
}
|
|
6992
|
+
|
|
6993
|
+
func TnRegexWrapReplaceAll(r *TnRegex, s string, repl string) string {
|
|
6994
|
+
return r.replace(s, repl, true)
|
|
6995
|
+
}
|
|
6996
|
+
|
|
6997
|
+
func TnRegexWrapReplaceFirst(r *TnRegex, s string, repl string) string {
|
|
6998
|
+
return r.replace(s, repl, false)
|
|
6999
|
+
}
|
|
7000
|
+
|
|
7001
|
+
func TnRegexReplaceFuncW(r *TnRegex, s string, fn func([]string) string, all bool) string {
|
|
7002
|
+
savedG, savedI := r.global, r.lastIndex
|
|
7003
|
+
r.global, r.lastIndex = true, 0
|
|
7004
|
+
out := ""
|
|
7005
|
+
last := 0
|
|
7006
|
+
for {
|
|
7007
|
+
groups, loc := r.find(s)
|
|
7008
|
+
if groups == nil {
|
|
7009
|
+
break
|
|
7010
|
+
}
|
|
7011
|
+
out += s[last:loc[0]] + fn(groups)
|
|
7012
|
+
last = loc[1]
|
|
7013
|
+
if !all {
|
|
7014
|
+
break
|
|
7015
|
+
}
|
|
7016
|
+
}
|
|
7017
|
+
r.global, r.lastIndex = savedG, savedI
|
|
7018
|
+
return out + s[last:]
|
|
7019
|
+
}
|
|
7020
|
+
|
|
7021
|
+
func (r *TnRegex) replace(s string, repl string, all bool) string {
|
|
7022
|
+
savedG, savedI := r.global, r.lastIndex
|
|
7023
|
+
r.global, r.lastIndex = true, 0
|
|
7024
|
+
out := ""
|
|
7025
|
+
last := 0
|
|
7026
|
+
for {
|
|
7027
|
+
groups, loc := r.find(s)
|
|
7028
|
+
if groups == nil {
|
|
7029
|
+
break
|
|
7030
|
+
}
|
|
7031
|
+
out += s[last:loc[0]] + tnExpandGroups(repl, groups)
|
|
7032
|
+
last = loc[1]
|
|
7033
|
+
if !all {
|
|
7034
|
+
break
|
|
7035
|
+
}
|
|
7036
|
+
if loc[1] == loc[0] && loc[1] < len(s) {
|
|
7037
|
+
out += string(s[loc[1]])
|
|
7038
|
+
last = loc[1] + 1
|
|
7039
|
+
}
|
|
7040
|
+
}
|
|
7041
|
+
r.global, r.lastIndex = savedG, savedI
|
|
7042
|
+
return out + s[last:]
|
|
7043
|
+
}
|
|
7044
|
+
|
|
7045
|
+
// Replaces the numeric group placeholders in the replacement with captures
|
|
7046
|
+
func tnExpandGroups(repl string, groups []string) string {
|
|
7047
|
+
out := ""
|
|
7048
|
+
for i := 0; i < len(repl); i++ {
|
|
7049
|
+
if repl[i] == '$' && i+1 < len(repl) && repl[i+1] == '{' {
|
|
7050
|
+
if j := strings.IndexByte(repl[i+2:], '}'); j >= 0 {
|
|
7051
|
+
if n, err := strconv.Atoi(repl[i+2 : i+2+j]); err == nil && n < len(groups) {
|
|
7052
|
+
out += groups[n]
|
|
7053
|
+
i = i + 2 + j
|
|
7054
|
+
continue
|
|
7055
|
+
}
|
|
7056
|
+
}
|
|
7057
|
+
}
|
|
7058
|
+
out += string(repl[i])
|
|
7059
|
+
}
|
|
7060
|
+
return out
|
|
7061
|
+
}
|
|
7062
|
+
|
|
7063
|
+
// find: leftmost match at or after lastIndex (which /g advances in runes)
|
|
7064
|
+
func (r *TnRegex) find(s string) ([]string, []int) {
|
|
7065
|
+
runs := []rune(s)
|
|
7066
|
+
if r.bt != nil {
|
|
7067
|
+
start := 0
|
|
7068
|
+
if r.global {
|
|
7069
|
+
if r.lastIndex > len(runs) {
|
|
7070
|
+
r.lastIndex = 0
|
|
7071
|
+
return nil, nil
|
|
7072
|
+
}
|
|
7073
|
+
start = r.lastIndex
|
|
7074
|
+
}
|
|
7075
|
+
offs := tnByteOffsets(runs)
|
|
7076
|
+
for pos := start; pos <= len(runs); pos++ {
|
|
7077
|
+
caps := make([]int, (r.bt.ngroups+1)*2)
|
|
7078
|
+
for i := range caps {
|
|
7079
|
+
caps[i] = -1
|
|
7080
|
+
}
|
|
7081
|
+
m := &tnBtMatchState{runs: runs, caps: caps}
|
|
7082
|
+
end := -1
|
|
7083
|
+
if m.match(r.bt.root, pos, func(e int) bool { end = e; return true }) {
|
|
7084
|
+
caps[0], caps[1] = pos, end
|
|
7085
|
+
groups := make([]string, r.bt.ngroups+1)
|
|
7086
|
+
loc := make([]int, (r.bt.ngroups+1)*2)
|
|
7087
|
+
for i := 0; i <= r.bt.ngroups; i++ {
|
|
7088
|
+
si, ei := caps[2*i], caps[2*i+1]
|
|
7089
|
+
if si >= 0 && ei >= si {
|
|
7090
|
+
groups[i] = string(runs[si:ei])
|
|
7091
|
+
loc[2*i], loc[2*i+1] = offs[si], offs[ei]
|
|
7092
|
+
} else {
|
|
7093
|
+
loc[2*i], loc[2*i+1] = -1, -1
|
|
7094
|
+
}
|
|
7095
|
+
}
|
|
7096
|
+
if r.global {
|
|
7097
|
+
next := end
|
|
7098
|
+
if next == pos {
|
|
7099
|
+
next++
|
|
7100
|
+
}
|
|
7101
|
+
r.lastIndex = next
|
|
7102
|
+
}
|
|
7103
|
+
return groups, loc
|
|
7104
|
+
}
|
|
7105
|
+
}
|
|
7106
|
+
if r.global {
|
|
7107
|
+
r.lastIndex = 0
|
|
7108
|
+
}
|
|
7109
|
+
return nil, nil
|
|
7110
|
+
}
|
|
7111
|
+
if !r.global {
|
|
7112
|
+
loc := r.re.FindStringSubmatchIndex(s)
|
|
7113
|
+
if loc == nil {
|
|
7114
|
+
return nil, nil
|
|
7115
|
+
}
|
|
7116
|
+
groups := make([]string, len(loc)/2)
|
|
7117
|
+
for i := range groups {
|
|
7118
|
+
if loc[2*i] >= 0 {
|
|
7119
|
+
groups[i] = s[loc[2*i]:loc[2*i+1]]
|
|
7120
|
+
}
|
|
7121
|
+
}
|
|
7122
|
+
return groups, loc
|
|
7123
|
+
}
|
|
7124
|
+
if r.lastIndex >= len(runs) {
|
|
7125
|
+
r.lastIndex = 0
|
|
7126
|
+
return nil, nil
|
|
7127
|
+
}
|
|
7128
|
+
byteStart := 0
|
|
7129
|
+
for i := 0; i < r.lastIndex; i++ {
|
|
7130
|
+
byteStart += len(string(runs[i]))
|
|
7131
|
+
}
|
|
7132
|
+
loc := r.re.FindStringSubmatchIndex(s[byteStart:])
|
|
7133
|
+
if loc == nil {
|
|
7134
|
+
r.lastIndex = 0
|
|
7135
|
+
return nil, nil
|
|
7136
|
+
}
|
|
7137
|
+
for i := range loc {
|
|
7138
|
+
if loc[i] >= 0 {
|
|
7139
|
+
loc[i] += byteStart
|
|
7140
|
+
}
|
|
7141
|
+
}
|
|
7142
|
+
groups := make([]string, len(loc)/2)
|
|
7143
|
+
for i := range groups {
|
|
7144
|
+
if loc[2*i] >= 0 {
|
|
7145
|
+
groups[i] = s[loc[2*i]:loc[2*i+1]]
|
|
7146
|
+
}
|
|
7147
|
+
}
|
|
7148
|
+
endRune := r.lastIndex
|
|
7149
|
+
bo := byteStart
|
|
7150
|
+
for ri := r.lastIndex; ri < len(runs) && bo < loc[1]; ri++ {
|
|
7151
|
+
bo += len(string(runs[ri]))
|
|
7152
|
+
endRune = ri + 1
|
|
7153
|
+
}
|
|
7154
|
+
if endRune == r.lastIndex {
|
|
7155
|
+
endRune++
|
|
7156
|
+
}
|
|
7157
|
+
r.lastIndex = endRune
|
|
7158
|
+
return groups, loc
|
|
7159
|
+
}
|
|
7160
|
+
|
|
7161
|
+
func tnByteOffsets(runs []rune) []int {
|
|
7162
|
+
offs := make([]int, len(runs)+1)
|
|
7163
|
+
bo := 0
|
|
7164
|
+
for i, r := range runs {
|
|
7165
|
+
offs[i] = bo
|
|
7166
|
+
bo += len(string(r))
|
|
7167
|
+
}
|
|
7168
|
+
offs[len(runs)] = bo
|
|
7169
|
+
return offs
|
|
7170
|
+
}
|
|
7171
|
+
|
|
7172
|
+
// ---- Backtracking regex engine for patterns RE2 rejects ----
|
|
7173
|
+
|
|
7174
|
+
type tnBtNode struct {
|
|
7175
|
+
op string
|
|
7176
|
+
ch rune
|
|
7177
|
+
cls func(rune) bool
|
|
7178
|
+
negCls bool
|
|
7179
|
+
seq []*tnBtNode
|
|
7180
|
+
branches [][]*tnBtNode
|
|
7181
|
+
child *tnBtNode
|
|
7182
|
+
min, max int
|
|
7183
|
+
greedy bool
|
|
7184
|
+
anchor byte
|
|
7185
|
+
group int
|
|
7186
|
+
behind bool
|
|
7187
|
+
negative bool
|
|
7188
|
+
}
|
|
7189
|
+
|
|
7190
|
+
type tnBt struct {
|
|
7191
|
+
root *tnBtNode
|
|
7192
|
+
ngroups int
|
|
7193
|
+
}
|
|
7194
|
+
|
|
7195
|
+
type tnBtParser struct {
|
|
7196
|
+
src []rune
|
|
7197
|
+
pos int
|
|
7198
|
+
ngrp int
|
|
7199
|
+
}
|
|
7200
|
+
|
|
7201
|
+
func tnBtCompile(pattern string) *tnBt {
|
|
7202
|
+
p := &tnBtParser{src: []rune(pattern)}
|
|
7203
|
+
root, err := p.parseAlt()
|
|
7204
|
+
if err != nil || p.pos < len(p.src) {
|
|
7205
|
+
return nil
|
|
7206
|
+
}
|
|
7207
|
+
return &tnBt{root: root, ngroups: p.ngrp}
|
|
7208
|
+
}
|
|
7209
|
+
|
|
7210
|
+
func (p *tnBtParser) peek() (rune, bool) {
|
|
7211
|
+
if p.pos < len(p.src) {
|
|
7212
|
+
return p.src[p.pos], true
|
|
7213
|
+
}
|
|
7214
|
+
return 0, false
|
|
7215
|
+
}
|
|
7216
|
+
|
|
7217
|
+
func (p *tnBtParser) parseAlt() (*tnBtNode, error) {
|
|
7218
|
+
var branches [][]*tnBtNode
|
|
7219
|
+
for {
|
|
7220
|
+
seq, err := p.parseSeq()
|
|
7221
|
+
if err != nil {
|
|
7222
|
+
return nil, err
|
|
7223
|
+
}
|
|
7224
|
+
branches = append(branches, seq)
|
|
7225
|
+
if ch, ok := p.peek(); ok && ch == '|' {
|
|
7226
|
+
p.pos++
|
|
7227
|
+
continue
|
|
7228
|
+
}
|
|
7229
|
+
break
|
|
7230
|
+
}
|
|
7231
|
+
if len(branches) == 1 {
|
|
7232
|
+
return &tnBtNode{op: "cat", seq: branches[0]}, nil
|
|
7233
|
+
}
|
|
7234
|
+
return &tnBtNode{op: "alt", branches: branches}, nil
|
|
7235
|
+
}
|
|
7236
|
+
|
|
7237
|
+
func (p *tnBtParser) parseSeq() ([]*tnBtNode, error) {
|
|
7238
|
+
var seq []*tnBtNode
|
|
7239
|
+
for {
|
|
7240
|
+
ch, ok := p.peek()
|
|
7241
|
+
if !ok || ch == '|' || ch == ')' {
|
|
7242
|
+
break
|
|
7243
|
+
}
|
|
7244
|
+
atom, err := p.parseAtom()
|
|
7245
|
+
if err != nil {
|
|
7246
|
+
return nil, err
|
|
7247
|
+
}
|
|
7248
|
+
if atom == nil {
|
|
7249
|
+
continue
|
|
7250
|
+
}
|
|
7251
|
+
node, err := p.parseQuantifier(atom)
|
|
7252
|
+
if err != nil {
|
|
7253
|
+
return nil, err
|
|
7254
|
+
}
|
|
7255
|
+
seq = append(seq, node)
|
|
7256
|
+
}
|
|
7257
|
+
return seq, nil
|
|
7258
|
+
}
|
|
7259
|
+
|
|
7260
|
+
func (p *tnBtParser) parseQuantifier(atom *tnBtNode) (*tnBtNode, error) {
|
|
7261
|
+
ch, ok := p.peek()
|
|
7262
|
+
if !ok {
|
|
7263
|
+
return atom, nil
|
|
7264
|
+
}
|
|
7265
|
+
lo, hi := 0, 0
|
|
7266
|
+
switch ch {
|
|
7267
|
+
case '*':
|
|
7268
|
+
lo, hi = 0, -1
|
|
7269
|
+
case '+':
|
|
7270
|
+
lo, hi = 1, -1
|
|
7271
|
+
case '?':
|
|
7272
|
+
lo, hi = 0, 1
|
|
7273
|
+
case '{':
|
|
7274
|
+
save := p.pos
|
|
7275
|
+
p.pos++
|
|
7276
|
+
parsedLo, parsedHi, ok2 := p.parseBraces()
|
|
7277
|
+
if !ok2 {
|
|
7278
|
+
p.pos = save
|
|
7279
|
+
return atom, nil
|
|
7280
|
+
}
|
|
7281
|
+
lo, hi = parsedLo, parsedHi
|
|
7282
|
+
default:
|
|
7283
|
+
return atom, nil
|
|
7284
|
+
}
|
|
7285
|
+
p.pos++
|
|
7286
|
+
greedy := true
|
|
7287
|
+
if c, ok2 := p.peek(); ok2 && c == '?' {
|
|
7288
|
+
greedy = false
|
|
7289
|
+
p.pos++
|
|
7290
|
+
}
|
|
7291
|
+
return &tnBtNode{op: "rep", child: atom, min: lo, max: hi, greedy: greedy}, nil
|
|
7292
|
+
}
|
|
7293
|
+
|
|
7294
|
+
// {m}, {m,}, {m,n} — returns ok=false when the brace is a literal
|
|
7295
|
+
func (p *tnBtParser) parseBraces() (int, int, bool) {
|
|
7296
|
+
lo := 0
|
|
7297
|
+
digits := 0
|
|
7298
|
+
for {
|
|
7299
|
+
c, ok := p.peek()
|
|
7300
|
+
if !ok || c < '0' || c > '9' {
|
|
7301
|
+
break
|
|
7302
|
+
}
|
|
7303
|
+
lo = lo*10 + int(c-'0')
|
|
7304
|
+
digits++
|
|
7305
|
+
p.pos++
|
|
7306
|
+
}
|
|
7307
|
+
if digits == 0 {
|
|
7308
|
+
return 0, 0, false
|
|
7309
|
+
}
|
|
7310
|
+
hi := lo
|
|
7311
|
+
if c, ok := p.peek(); ok && c == ',' {
|
|
7312
|
+
p.pos++
|
|
7313
|
+
hi = -1
|
|
7314
|
+
hv := 0
|
|
7315
|
+
hd := 0
|
|
7316
|
+
for {
|
|
7317
|
+
c2, ok2 := p.peek()
|
|
7318
|
+
if !ok2 || c2 < '0' || c2 > '9' {
|
|
7319
|
+
break
|
|
7320
|
+
}
|
|
7321
|
+
hv = hv*10 + int(c2-'0')
|
|
7322
|
+
hd++
|
|
7323
|
+
p.pos++
|
|
7324
|
+
}
|
|
7325
|
+
if hd > 0 {
|
|
7326
|
+
hi = hv
|
|
7327
|
+
}
|
|
7328
|
+
}
|
|
7329
|
+
if c, ok := p.peek(); !ok || c != '}' {
|
|
7330
|
+
return 0, 0, false
|
|
7331
|
+
}
|
|
7332
|
+
return lo, hi, true
|
|
7333
|
+
}
|
|
7334
|
+
|
|
7335
|
+
func tnCharClass(escape rune) func(rune) bool {
|
|
7336
|
+
switch escape {
|
|
7337
|
+
case 'd':
|
|
7338
|
+
return func(r rune) bool { return r >= '0' && r <= '9' }
|
|
7339
|
+
case 'D':
|
|
7340
|
+
return func(r rune) bool { return !(r >= '0' && r <= '9') }
|
|
7341
|
+
case 'w':
|
|
7342
|
+
return func(r rune) bool {
|
|
7343
|
+
return r == '_' || (r >= '0' && r <= '9') || (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
|
|
7344
|
+
}
|
|
7345
|
+
case 'W':
|
|
7346
|
+
return func(r rune) bool {
|
|
7347
|
+
return !(r == '_' || (r >= '0' && r <= '9') || (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z'))
|
|
7348
|
+
}
|
|
7349
|
+
case 's':
|
|
7350
|
+
return func(r rune) bool { return r == ' ' || r == '\\t' || r == '\\n' || r == '\\r' || r == '\\f' || r == '\\v' }
|
|
7351
|
+
case 'S':
|
|
7352
|
+
return func(r rune) bool { return !(r == ' ' || r == '\\t' || r == '\\n' || r == '\\r' || r == '\\f' || r == '\\v') }
|
|
7353
|
+
}
|
|
7354
|
+
return nil
|
|
7355
|
+
}
|
|
7356
|
+
|
|
7357
|
+
func (p *tnBtParser) parseAtom() (*tnBtNode, error) {
|
|
7358
|
+
ch, ok := p.peek()
|
|
7359
|
+
if !ok {
|
|
7360
|
+
return nil, fmt.Errorf("unexpected end")
|
|
7361
|
+
}
|
|
7362
|
+
p.pos++
|
|
7363
|
+
switch ch {
|
|
7364
|
+
case '(':
|
|
7365
|
+
kind := "group"
|
|
7366
|
+
negative := false
|
|
7367
|
+
behind := false
|
|
7368
|
+
if c, ok2 := p.peek(); ok2 && c == '?' {
|
|
7369
|
+
p.pos++
|
|
7370
|
+
c2, _ := p.peek()
|
|
7371
|
+
switch c2 {
|
|
7372
|
+
case ':':
|
|
7373
|
+
p.pos++
|
|
7374
|
+
kind = "noncap"
|
|
7375
|
+
case '=':
|
|
7376
|
+
p.pos++
|
|
7377
|
+
kind = "look"
|
|
7378
|
+
case '!':
|
|
7379
|
+
p.pos++
|
|
7380
|
+
kind = "look"
|
|
7381
|
+
negative = true
|
|
7382
|
+
case '<':
|
|
7383
|
+
p.pos++
|
|
7384
|
+
c3, ok3 := p.peek()
|
|
7385
|
+
if !ok3 {
|
|
7386
|
+
return nil, fmt.Errorf("bad (?<")
|
|
7387
|
+
}
|
|
7388
|
+
if c3 == '=' || c3 == '!' {
|
|
7389
|
+
p.pos++
|
|
7390
|
+
kind = "look"
|
|
7391
|
+
behind = true
|
|
7392
|
+
negative = c3 == '!'
|
|
7393
|
+
} else {
|
|
7394
|
+
for {
|
|
7395
|
+
cn, okn := p.peek()
|
|
7396
|
+
if !okn {
|
|
7397
|
+
return nil, fmt.Errorf("unterminated group name")
|
|
7398
|
+
}
|
|
7399
|
+
p.pos++
|
|
7400
|
+
if cn == '>' {
|
|
7401
|
+
break
|
|
7402
|
+
}
|
|
7403
|
+
}
|
|
7404
|
+
}
|
|
7405
|
+
case 'P':
|
|
7406
|
+
p.pos++
|
|
7407
|
+
if c3, ok3 := p.peek(); ok3 && c3 == '<' {
|
|
7408
|
+
p.pos++
|
|
7409
|
+
for {
|
|
7410
|
+
cn, okn := p.peek()
|
|
7411
|
+
if !okn {
|
|
7412
|
+
return nil, fmt.Errorf("unterminated group name")
|
|
7413
|
+
}
|
|
7414
|
+
p.pos++
|
|
7415
|
+
if cn == '>' {
|
|
7416
|
+
break
|
|
7417
|
+
}
|
|
7418
|
+
}
|
|
7419
|
+
} else {
|
|
7420
|
+
return nil, fmt.Errorf("bad (?P")
|
|
7421
|
+
}
|
|
7422
|
+
default:
|
|
7423
|
+
return nil, fmt.Errorf("unsupported group")
|
|
7424
|
+
}
|
|
7425
|
+
}
|
|
7426
|
+
body, err := p.parseAlt()
|
|
7427
|
+
if err != nil {
|
|
7428
|
+
return nil, err
|
|
7429
|
+
}
|
|
7430
|
+
if c, ok2 := p.peek(); !ok2 || c != ')' {
|
|
7431
|
+
return nil, fmt.Errorf("missing )")
|
|
7432
|
+
}
|
|
7433
|
+
p.pos++
|
|
7434
|
+
switch kind {
|
|
7435
|
+
case "noncap":
|
|
7436
|
+
return body, nil
|
|
7437
|
+
case "look":
|
|
7438
|
+
return &tnBtNode{op: "look", child: body, behind: behind, negative: negative}, nil
|
|
7439
|
+
default:
|
|
7440
|
+
p.ngrp++
|
|
7441
|
+
return &tnBtNode{op: "group", group: p.ngrp, child: body}, nil
|
|
7442
|
+
}
|
|
7443
|
+
case '[':
|
|
7444
|
+
return p.parseClass()
|
|
7445
|
+
case '.':
|
|
7446
|
+
return &tnBtNode{op: "any"}, nil
|
|
7447
|
+
case '^':
|
|
7448
|
+
return &tnBtNode{op: "anchor", anchor: '^'}, nil
|
|
7449
|
+
case '$':
|
|
7450
|
+
return &tnBtNode{op: "anchor", anchor: '$'}, nil
|
|
7451
|
+
case '\\\\':
|
|
7452
|
+
return p.parseEscape()
|
|
7453
|
+
default:
|
|
7454
|
+
return &tnBtNode{op: "char", ch: ch}, nil
|
|
7455
|
+
}
|
|
7456
|
+
}
|
|
7457
|
+
|
|
7458
|
+
func (p *tnBtParser) parseEscape() (*tnBtNode, error) {
|
|
7459
|
+
c, ok := p.peek()
|
|
7460
|
+
if !ok {
|
|
7461
|
+
return nil, fmt.Errorf("trailing backslash")
|
|
7462
|
+
}
|
|
7463
|
+
p.pos++
|
|
7464
|
+
switch c {
|
|
7465
|
+
case 'b':
|
|
7466
|
+
return &tnBtNode{op: "anchor", anchor: 'b'}, nil
|
|
7467
|
+
case 'B':
|
|
7468
|
+
return &tnBtNode{op: "anchor", anchor: 'B'}, nil
|
|
7469
|
+
case 'n':
|
|
7470
|
+
return &tnBtNode{op: "char", ch: '\\n'}, nil
|
|
7471
|
+
case 't':
|
|
7472
|
+
return &tnBtNode{op: "char", ch: '\\t'}, nil
|
|
7473
|
+
case 'r':
|
|
7474
|
+
return &tnBtNode{op: "char", ch: '\\r'}, nil
|
|
7475
|
+
case 'f':
|
|
7476
|
+
return &tnBtNode{op: "char", ch: '\\f'}, nil
|
|
7477
|
+
case 'v':
|
|
7478
|
+
return &tnBtNode{op: "char", ch: '\\v'}, nil
|
|
7479
|
+
case '0':
|
|
7480
|
+
return &tnBtNode{op: "char", ch: 0}, nil
|
|
7481
|
+
case '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
|
7482
|
+
return &tnBtNode{op: "backref", group: int(c - '0')}, nil
|
|
7483
|
+
default:
|
|
7484
|
+
if cls := tnCharClass(c); cls != nil {
|
|
7485
|
+
return &tnBtNode{op: "class", cls: cls}, nil
|
|
7486
|
+
}
|
|
7487
|
+
return &tnBtNode{op: "char", ch: c}, nil
|
|
7488
|
+
}
|
|
7489
|
+
}
|
|
7490
|
+
|
|
7491
|
+
func (p *tnBtParser) parseClass() (*tnBtNode, error) {
|
|
7492
|
+
negate := false
|
|
7493
|
+
if c, ok := p.peek(); ok && c == '^' {
|
|
7494
|
+
negate = true
|
|
7495
|
+
p.pos++
|
|
7496
|
+
}
|
|
7497
|
+
var items []func(rune) bool
|
|
7498
|
+
first := true
|
|
7499
|
+
for {
|
|
7500
|
+
c, ok := p.peek()
|
|
7501
|
+
if !ok {
|
|
7502
|
+
return nil, fmt.Errorf("unterminated class")
|
|
7503
|
+
}
|
|
7504
|
+
if c == ']' && !first {
|
|
7505
|
+
p.pos++
|
|
7506
|
+
break
|
|
7507
|
+
}
|
|
7508
|
+
first = false
|
|
7509
|
+
p.pos++
|
|
7510
|
+
var lo rune
|
|
7511
|
+
if c == '\\\\' {
|
|
7512
|
+
c2, ok2 := p.peek()
|
|
7513
|
+
if !ok2 {
|
|
7514
|
+
return nil, fmt.Errorf("trailing backslash in class")
|
|
7515
|
+
}
|
|
7516
|
+
p.pos++
|
|
7517
|
+
if cls := tnCharClass(c2); cls != nil {
|
|
7518
|
+
items = append(items, cls)
|
|
7519
|
+
continue
|
|
7520
|
+
}
|
|
7521
|
+
switch c2 {
|
|
7522
|
+
case 'n':
|
|
7523
|
+
lo = '\\n'
|
|
7524
|
+
case 't':
|
|
7525
|
+
lo = '\\t'
|
|
7526
|
+
case 'r':
|
|
7527
|
+
lo = '\\r'
|
|
7528
|
+
default:
|
|
7529
|
+
lo = c2
|
|
7530
|
+
}
|
|
7531
|
+
} else {
|
|
7532
|
+
lo = c
|
|
7533
|
+
}
|
|
7534
|
+
// range?
|
|
7535
|
+
if c3, ok3 := p.peek(); ok3 && c3 == '-' {
|
|
7536
|
+
if c4, ok4 := p.peekAt(1); ok4 && c4 != ']' {
|
|
7537
|
+
p.pos++ // consume '-'
|
|
7538
|
+
hiChar, _ := p.peek()
|
|
7539
|
+
p.pos++
|
|
7540
|
+
hi := hiChar
|
|
7541
|
+
if hiChar == '\\\\' {
|
|
7542
|
+
hc, ok5 := p.peek()
|
|
7543
|
+
if !ok5 {
|
|
7544
|
+
return nil, fmt.Errorf("trailing backslash in class")
|
|
7545
|
+
}
|
|
7546
|
+
p.pos++
|
|
7547
|
+
hi = hc
|
|
7548
|
+
}
|
|
7549
|
+
l, h := lo, hi
|
|
7550
|
+
items = append(items, func(r rune) bool { return r >= l && r <= h })
|
|
7551
|
+
continue
|
|
7552
|
+
}
|
|
7553
|
+
}
|
|
7554
|
+
l := lo
|
|
7555
|
+
items = append(items, func(r rune) bool { return r == l })
|
|
7556
|
+
}
|
|
7557
|
+
matchers := items
|
|
7558
|
+
return &tnBtNode{op: "class", cls: func(r rune) bool {
|
|
7559
|
+
for _, m := range matchers {
|
|
7560
|
+
if m(r) {
|
|
7561
|
+
return true
|
|
7562
|
+
}
|
|
7563
|
+
}
|
|
7564
|
+
return false
|
|
7565
|
+
}, negCls: negate}, nil
|
|
7566
|
+
}
|
|
7567
|
+
|
|
7568
|
+
func (p *tnBtParser) peekAt(offset int) (rune, bool) {
|
|
7569
|
+
if p.pos+offset < len(p.src) {
|
|
7570
|
+
return p.src[p.pos+offset], true
|
|
7571
|
+
}
|
|
7572
|
+
return 0, false
|
|
7573
|
+
}
|
|
7574
|
+
|
|
7575
|
+
type tnBtMatchState struct {
|
|
7576
|
+
runs []rune
|
|
7577
|
+
caps []int
|
|
7578
|
+
}
|
|
7579
|
+
|
|
7580
|
+
func (m *tnBtMatchState) seq(nodes []*tnBtNode, pos int, k func(int) bool) bool {
|
|
7581
|
+
if len(nodes) == 0 {
|
|
7582
|
+
return k(pos)
|
|
7583
|
+
}
|
|
7584
|
+
return m.match(nodes[0], pos, func(p2 int) bool {
|
|
7585
|
+
return m.seq(nodes[1:], p2, k)
|
|
7586
|
+
})
|
|
7587
|
+
}
|
|
7588
|
+
|
|
7589
|
+
func tnBtWord(r rune) bool {
|
|
7590
|
+
return r == '_' || (r >= '0' && r <= '9') || (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
|
|
7591
|
+
}
|
|
7592
|
+
|
|
7593
|
+
func (m *tnBtMatchState) match(node *tnBtNode, pos int, k func(int) bool) bool {
|
|
7594
|
+
switch node.op {
|
|
7595
|
+
case "char":
|
|
7596
|
+
if pos < len(m.runs) && m.runs[pos] == node.ch {
|
|
7597
|
+
return k(pos + 1)
|
|
7598
|
+
}
|
|
7599
|
+
return false
|
|
7600
|
+
case "any":
|
|
7601
|
+
if pos < len(m.runs) && m.runs[pos] != '\\n' {
|
|
7602
|
+
return k(pos + 1)
|
|
7603
|
+
}
|
|
7604
|
+
return false
|
|
7605
|
+
case "class":
|
|
7606
|
+
if pos < len(m.runs) && node.cls(m.runs[pos]) != node.negCls {
|
|
7607
|
+
return k(pos + 1)
|
|
7608
|
+
}
|
|
7609
|
+
return false
|
|
7610
|
+
case "anchor":
|
|
7611
|
+
switch node.anchor {
|
|
7612
|
+
case '^':
|
|
7613
|
+
if pos == 0 {
|
|
7614
|
+
return k(pos)
|
|
7615
|
+
}
|
|
7616
|
+
case '$':
|
|
7617
|
+
if pos == len(m.runs) {
|
|
7618
|
+
return k(pos)
|
|
7619
|
+
}
|
|
7620
|
+
case 'b':
|
|
7621
|
+
before := pos > 0 && tnBtWord(m.runs[pos-1])
|
|
7622
|
+
after := pos < len(m.runs) && tnBtWord(m.runs[pos])
|
|
7623
|
+
if before != after {
|
|
7624
|
+
return k(pos)
|
|
7625
|
+
}
|
|
7626
|
+
case 'B':
|
|
7627
|
+
before := pos > 0 && tnBtWord(m.runs[pos-1])
|
|
7628
|
+
after := pos < len(m.runs) && tnBtWord(m.runs[pos])
|
|
7629
|
+
if before == after {
|
|
7630
|
+
return k(pos)
|
|
7631
|
+
}
|
|
7632
|
+
}
|
|
7633
|
+
return false
|
|
7634
|
+
case "cat":
|
|
7635
|
+
return m.seq(node.seq, pos, k)
|
|
7636
|
+
case "alt":
|
|
7637
|
+
for _, br := range node.branches {
|
|
7638
|
+
if m.seq(br, pos, k) {
|
|
7639
|
+
return true
|
|
7640
|
+
}
|
|
7641
|
+
}
|
|
7642
|
+
return false
|
|
7643
|
+
case "group":
|
|
7644
|
+
si, ei := node.group*2, node.group*2+1
|
|
7645
|
+
savedS, savedE := m.caps[si], m.caps[ei]
|
|
7646
|
+
m.caps[si] = pos
|
|
7647
|
+
matched := m.match(node.child, pos, func(end int) bool {
|
|
7648
|
+
ps, pe := m.caps[si], m.caps[ei]
|
|
7649
|
+
m.caps[ei] = end
|
|
7650
|
+
if k(end) {
|
|
7651
|
+
return true
|
|
7652
|
+
}
|
|
7653
|
+
m.caps[si], m.caps[ei] = ps, pe
|
|
7654
|
+
return false
|
|
7655
|
+
})
|
|
7656
|
+
if !matched {
|
|
7657
|
+
m.caps[si], m.caps[ei] = savedS, savedE
|
|
7658
|
+
}
|
|
7659
|
+
return matched
|
|
7660
|
+
case "backref":
|
|
7661
|
+
s, e := m.caps[node.group*2], m.caps[node.group*2+1]
|
|
7662
|
+
if s < 0 || e < 0 {
|
|
7663
|
+
return k(pos)
|
|
7664
|
+
}
|
|
7665
|
+
n := e - s
|
|
7666
|
+
if pos+n > len(m.runs) {
|
|
7667
|
+
return false
|
|
7668
|
+
}
|
|
7669
|
+
for i := 0; i < n; i++ {
|
|
7670
|
+
if m.runs[pos+i] != m.runs[s+i] {
|
|
7671
|
+
return false
|
|
7672
|
+
}
|
|
7673
|
+
}
|
|
7674
|
+
return k(pos + n)
|
|
7675
|
+
case "look":
|
|
7676
|
+
if !node.behind {
|
|
7677
|
+
matched := false
|
|
7678
|
+
saved := append([]int(nil), m.caps...)
|
|
7679
|
+
if m.match(node.child, pos, func(int) bool { return true }) {
|
|
7680
|
+
matched = true
|
|
7681
|
+
}
|
|
7682
|
+
m.caps = saved
|
|
7683
|
+
if matched != node.negative {
|
|
7684
|
+
return k(pos)
|
|
7685
|
+
}
|
|
7686
|
+
return false
|
|
7687
|
+
}
|
|
7688
|
+
matched := false
|
|
7689
|
+
saved := append([]int(nil), m.caps...)
|
|
7690
|
+
for start := 0; start <= pos && !matched; start++ {
|
|
7691
|
+
if m.match(node.child, start, func(end int) bool { return end == pos }) {
|
|
7692
|
+
matched = true
|
|
7693
|
+
}
|
|
7694
|
+
}
|
|
7695
|
+
m.caps = saved
|
|
7696
|
+
if matched != node.negative {
|
|
7697
|
+
return k(pos)
|
|
7698
|
+
}
|
|
7699
|
+
return false
|
|
7700
|
+
case "rep":
|
|
7701
|
+
return m.rep(node, pos, 0, k)
|
|
7702
|
+
}
|
|
7703
|
+
return false
|
|
7704
|
+
}
|
|
7705
|
+
|
|
7706
|
+
func (m *tnBtMatchState) rep(node *tnBtNode, pos, count int, k func(int) bool) bool {
|
|
7707
|
+
if count < node.min {
|
|
7708
|
+
return m.match(node.child, pos, func(p2 int) bool {
|
|
7709
|
+
return m.rep(node, p2, count+1, k)
|
|
7710
|
+
})
|
|
7711
|
+
}
|
|
7712
|
+
tryMore := func() bool {
|
|
7713
|
+
if node.max >= 0 && count >= node.max {
|
|
7714
|
+
return false
|
|
7715
|
+
}
|
|
7716
|
+
return m.match(node.child, pos, func(p2 int) bool {
|
|
7717
|
+
if p2 == pos {
|
|
7718
|
+
return false
|
|
7719
|
+
}
|
|
7720
|
+
return m.rep(node, p2, count+1, k)
|
|
7721
|
+
})
|
|
7722
|
+
}
|
|
7723
|
+
if node.greedy {
|
|
7724
|
+
if tryMore() {
|
|
7725
|
+
return true
|
|
7726
|
+
}
|
|
7727
|
+
return k(pos)
|
|
7728
|
+
}
|
|
7729
|
+
if k(pos) {
|
|
7730
|
+
return true
|
|
7731
|
+
}
|
|
7732
|
+
return tryMore()
|
|
7733
|
+
}`,
|
|
7734
|
+
objectAssign: `// Object.assign(target, ...sources): copies every source's properties
|
|
7735
|
+
// (struct fields, maps, dynamic objects) into a fresh ordered map
|
|
7736
|
+
func TnObjectAssign(target interface{}, sources ...interface{}) *TnMap[string, interface{}] {
|
|
7737
|
+
result := TnNewMap[string, interface{}]()
|
|
7738
|
+
tnAssignInto(result, target)
|
|
7739
|
+
for _, src := range sources {
|
|
7740
|
+
tnAssignInto(result, src)
|
|
7741
|
+
}
|
|
7742
|
+
return result
|
|
7743
|
+
}
|
|
7744
|
+
|
|
7745
|
+
func tnAssignInto(dst *TnMap[string, interface{}], src interface{}) {
|
|
7746
|
+
if src == nil {
|
|
7747
|
+
return
|
|
7748
|
+
}
|
|
7749
|
+
switch s := src.(type) {
|
|
7750
|
+
case map[string]interface{}:
|
|
7751
|
+
for k, v := range s {
|
|
7752
|
+
dst.Set(k, v)
|
|
7753
|
+
}
|
|
7754
|
+
return
|
|
7755
|
+
case *TnMap[string, interface{}]:
|
|
7756
|
+
for _, k := range s.Keys() {
|
|
7757
|
+
dst.Set(k, s.Get(k))
|
|
7758
|
+
}
|
|
7759
|
+
return
|
|
7760
|
+
}
|
|
7761
|
+
rv := reflect.ValueOf(src)
|
|
7762
|
+
if rv.Kind() == reflect.Pointer {
|
|
7763
|
+
if rv.IsNil() {
|
|
7764
|
+
return
|
|
7765
|
+
}
|
|
7766
|
+
rv = rv.Elem()
|
|
7767
|
+
}
|
|
7768
|
+
if rv.Kind() == reflect.Struct {
|
|
7769
|
+
rt := rv.Type()
|
|
7770
|
+
for i := 0; i < rt.NumField(); i++ {
|
|
7771
|
+
if rv.Field(i).Kind() == reflect.Func {
|
|
7772
|
+
continue
|
|
7773
|
+
}
|
|
7774
|
+
if !rv.Field(i).CanInterface() {
|
|
7775
|
+
if !rv.Field(i).CanAddr() {
|
|
7776
|
+
continue
|
|
7777
|
+
}
|
|
7778
|
+
dst.Set(rt.Field(i).Name, reflect.NewAt(rv.Field(i).Type(), unsafe.Pointer(rv.Field(i).UnsafeAddr())).Elem().Interface())
|
|
7779
|
+
continue
|
|
7780
|
+
}
|
|
7781
|
+
dst.Set(rt.Field(i).Name, rv.Field(i).Interface())
|
|
7782
|
+
}
|
|
7783
|
+
}
|
|
5557
7784
|
}`,
|
|
5558
7785
|
regexReplaceFunc: `func TnRegexReplaceFunc(re *regexp.Regexp, s string, fn func([]string) string, all bool) string {
|
|
5559
7786
|
limit := 1
|
|
@@ -5644,7 +7871,7 @@ func TnGroup(groups []string, i int) string {
|
|
|
5644
7871
|
}`,
|
|
5645
7872
|
fileURLToPath: `func TnFileURLToPath(u string) string {
|
|
5646
7873
|
trimmed := strings.TrimPrefix(u, "file://")
|
|
5647
|
-
if unescaped, err :=
|
|
7874
|
+
if unescaped, err := tnurl.PathUnescape(trimmed); err == nil {
|
|
5648
7875
|
trimmed = unescaped
|
|
5649
7876
|
}
|
|
5650
7877
|
return filepath.FromSlash(trimmed)
|
|
@@ -5654,7 +7881,15 @@ func TnGroup(groups []string, i int) string {
|
|
|
5654
7881
|
if err != nil {
|
|
5655
7882
|
panic(err)
|
|
5656
7883
|
}
|
|
5657
|
-
return "file://" +
|
|
7884
|
+
return "file://" + tnurl.PathEscape(filepath.ToSlash(abs))
|
|
7885
|
+
}`,
|
|
7886
|
+
moduleUrl: `// import.meta.url: the file URL of the running program
|
|
7887
|
+
func TnModuleURL() string {
|
|
7888
|
+
exe, err := os.Executable()
|
|
7889
|
+
if err != nil {
|
|
7890
|
+
panic(err)
|
|
7891
|
+
}
|
|
7892
|
+
return TnPathToFileURL(exe)
|
|
5658
7893
|
}`,
|
|
5659
7894
|
osPlatform: `func TnOsPlatform() string {
|
|
5660
7895
|
if runtime.GOOS == "windows" {
|
|
@@ -5664,6 +7899,46 @@ func TnGroup(groups []string, i int) string {
|
|
|
5664
7899
|
return "darwin"
|
|
5665
7900
|
}
|
|
5666
7901
|
return runtime.GOOS
|
|
7902
|
+
}`,
|
|
7903
|
+
error: `// TnError: JS Error values (thrown and caught) with a message
|
|
7904
|
+
type TnError struct {
|
|
7905
|
+
message string
|
|
7906
|
+
}
|
|
7907
|
+
|
|
7908
|
+
func (e *TnError) Error() string { return e.message }
|
|
7909
|
+
|
|
7910
|
+
func TnNewError(message string) *TnError {
|
|
7911
|
+
return &TnError{message: message}
|
|
7912
|
+
}`,
|
|
7913
|
+
asyncWait: `// Pending async work (promise callbacks, detached async calls) is drained
|
|
7914
|
+
// before the process exits
|
|
7915
|
+
var __tnWaitGroup sync.WaitGroup`,
|
|
7916
|
+
bigint: `func TnBigInt(s string) *tnbig.Int {
|
|
7917
|
+
v, _ := new(tnbig.Int).SetString(s, 10)
|
|
7918
|
+
return v
|
|
7919
|
+
}`,
|
|
7920
|
+
symbol: `// TnSymbol: JS symbol values (unique by identity)
|
|
7921
|
+
type TnSymbol struct {
|
|
7922
|
+
description string
|
|
7923
|
+
}
|
|
7924
|
+
|
|
7925
|
+
func TnNewSymbol(description string) *TnSymbol {
|
|
7926
|
+
return &TnSymbol{description: description}
|
|
7927
|
+
}`,
|
|
7928
|
+
timerWait: `// Pending setTimeout timers are waited on before the process exits
|
|
7929
|
+
var __tnTimers sync.WaitGroup`,
|
|
7930
|
+
promiseAll: `// Promise.all: every channel drained in order, results collected
|
|
7931
|
+
func TnPromiseAll[T any](chs []chan T) chan []T {
|
|
7932
|
+
out := make(chan []T)
|
|
7933
|
+
go func() {
|
|
7934
|
+
results := make([]T, len(chs))
|
|
7935
|
+
for i, ch := range chs {
|
|
7936
|
+
results[i] = <-ch
|
|
7937
|
+
}
|
|
7938
|
+
out <- results
|
|
7939
|
+
close(out)
|
|
7940
|
+
}()
|
|
7941
|
+
return out
|
|
5667
7942
|
}`,
|
|
5668
7943
|
exec: `type tnExecResult struct {
|
|
5669
7944
|
stdout string
|
|
@@ -5887,6 +8162,16 @@ function visitImportDeclaration(node) {
|
|
|
5887
8162
|
}
|
|
5888
8163
|
return '';
|
|
5889
8164
|
}
|
|
8165
|
+
// Go packages imported under a fixed alias to avoid collisions with user
|
|
8166
|
+
// variables (a variable named `big` would shadow the package name)
|
|
8167
|
+
const goImportAliases = {
|
|
8168
|
+
'math/big': 'tnbig',
|
|
8169
|
+
'net/url': 'tnurl'
|
|
8170
|
+
};
|
|
8171
|
+
function goImportLine(pkg) {
|
|
8172
|
+
const alias = goImportAliases[pkg];
|
|
8173
|
+
return alias ? `import ${alias} "${pkg}"` : `import "${pkg}"`;
|
|
8174
|
+
}
|
|
5890
8175
|
// Records the type of a variable introduced by a loop or binding
|
|
5891
8176
|
function registerLocalVariable(name, goType) {
|
|
5892
8177
|
if (!name || name === '_')
|
|
@@ -5895,6 +8180,8 @@ function registerLocalVariable(name, goType) {
|
|
|
5895
8180
|
variableClassNames.delete(name);
|
|
5896
8181
|
variableTypeNodes.delete(name);
|
|
5897
8182
|
narrowedVariables.delete(name);
|
|
8183
|
+
// Loop and binding variables hold plain values in our lowering
|
|
8184
|
+
referenceArrays.delete(name);
|
|
5898
8185
|
if (goType)
|
|
5899
8186
|
variableGoTypes.set(name, goType);
|
|
5900
8187
|
else
|
|
@@ -5904,6 +8191,32 @@ function registerLocalVariable(name, goType) {
|
|
|
5904
8191
|
function visitForOfSequence(node, iterExpr, iterType) {
|
|
5905
8192
|
if (iterType === 'interface{}')
|
|
5906
8193
|
iterType = '[]interface{}';
|
|
8194
|
+
// An iterable class instance: range its Symbol_iterator channel
|
|
8195
|
+
if (iterType?.startsWith('*')) {
|
|
8196
|
+
const iterClass = iterType.slice(1).replace(/\[.*\]$/, '');
|
|
8197
|
+
const yieldType = classIteratorMethods.get(iterClass);
|
|
8198
|
+
if (yieldType) {
|
|
8199
|
+
const declaration = isVariableDeclarationList(node.initializer)
|
|
8200
|
+
? node.initializer.declarations[0]
|
|
8201
|
+
: undefined;
|
|
8202
|
+
if (declaration && isIdentifier(declaration.name)) {
|
|
8203
|
+
registerLocalVariable(declaration.name.text, yieldType);
|
|
8204
|
+
return `for ${visit(declaration.name)} := range ${iterExpr}.Symbol_iterator()${visitLoopBody(node.statement)}`;
|
|
8205
|
+
}
|
|
8206
|
+
return `for _ = range ${iterExpr}.Symbol_iterator()${visitLoopBody(node.statement)}`;
|
|
8207
|
+
}
|
|
8208
|
+
}
|
|
8209
|
+
if (iterType?.startsWith('chan ')) {
|
|
8210
|
+
const elemType = iterType.slice(5) || 'interface{}';
|
|
8211
|
+
const declaration = isVariableDeclarationList(node.initializer)
|
|
8212
|
+
? node.initializer.declarations[0]
|
|
8213
|
+
: undefined;
|
|
8214
|
+
if (declaration && isIdentifier(declaration.name)) {
|
|
8215
|
+
registerLocalVariable(declaration.name.text, elemType);
|
|
8216
|
+
return `for ${visit(declaration.name)} := range ${iterExpr}${visitLoopBody(node.statement)}`;
|
|
8217
|
+
}
|
|
8218
|
+
return `for _ = range ${iterExpr}${visitLoopBody(node.statement)}`;
|
|
8219
|
+
}
|
|
5907
8220
|
const elementType = iterType === 'string' ? 'string' : iterType?.startsWith('[]') ? iterType.slice(2) : undefined;
|
|
5908
8221
|
const declaration = isVariableDeclarationList(node.initializer)
|
|
5909
8222
|
? node.initializer.declarations[0]
|
|
@@ -6019,6 +8332,9 @@ function visitTryStatement(node, options) {
|
|
|
6019
8332
|
if (node.catchClause) {
|
|
6020
8333
|
const varDecl = node.catchClause.variableDeclaration;
|
|
6021
8334
|
const catchVar = varDecl ? visit(varDecl.name) : '_r';
|
|
8335
|
+
if (varDecl && isIdentifier(varDecl.name)) {
|
|
8336
|
+
registerLocalVariable(varDecl.name.text, 'interface{}');
|
|
8337
|
+
}
|
|
6022
8338
|
const catchBody = (node.catchClause.block.statements ?? []).map((s) => visit(s)).join('\t');
|
|
6023
8339
|
deferreds.push(`defer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\t${catchVar} := r\n\t\t\t\t_ = ${catchVar}\n\t\t\t\t${catchBody}\t\t\t}\n\t\t\t}()`);
|
|
6024
8340
|
}
|