goscript 0.0.13 → 0.0.16
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/LICENSE +2 -1
- package/README.md +163 -45
- package/builtin/builtin.ts +1169 -178
- package/cmd/goscript/cmd_compile.go +2 -2
- package/compiler/analysis.go +726 -0
- package/compiler/compiler.go +5701 -4
- package/compiler/compiler_test.go +104 -0
- package/compiler/config.go +3 -3
- package/compiler/config_test.go +89 -0
- package/compiler/index.ts +1 -1
- package/compiler/output.go +26 -0
- package/compiler/write-type-spec.go +506 -0
- package/compiler/writer.go +11 -0
- package/dist/builtin/builtin.d.ts +204 -67
- package/dist/builtin/builtin.js +846 -144
- package/dist/builtin/builtin.js.map +1 -1
- package/dist/compiler/index.d.ts +1 -1
- package/go.mod +5 -6
- package/go.sum +6 -11
- package/package.json +21 -5
- package/compiler/compile.go +0 -190
- package/compiler/compile_comment.go +0 -41
- package/compiler/compile_decls.go +0 -84
- package/compiler/compile_expr.go +0 -1022
- package/compiler/compile_field.go +0 -110
- package/compiler/compile_spec.go +0 -566
- package/compiler/compile_stmt.go +0 -1616
- package/compiler/context.go +0 -9
- package/compiler/file_compiler.go +0 -80
- package/compiler/output_path.go +0 -31
- package/compiler/pkg_compiler.go +0 -72
- package/compiler/types/tokens.go +0 -66
- package/compiler/types/types.go +0 -46
- package/dist/compliance/tests/array_literal/array_literal.gs.d.ts +0 -1
- package/dist/compliance/tests/array_literal/array_literal.gs.js +0 -15
- package/dist/compliance/tests/array_literal/array_literal.gs.js.map +0 -1
- package/dist/compliance/tests/async_basic/async_basic.gs.d.ts +0 -1
- package/dist/compliance/tests/async_basic/async_basic.gs.js +0 -24
- package/dist/compliance/tests/async_basic/async_basic.gs.js.map +0 -1
- package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.d.ts +0 -1
- package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js +0 -82
- package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js.map +0 -1
- package/dist/compliance/tests/basic_arithmetic/basic_arithmetic.gs.d.ts +0 -1
- package/dist/compliance/tests/basic_arithmetic/basic_arithmetic.gs.js +0 -16
- package/dist/compliance/tests/basic_arithmetic/basic_arithmetic.gs.js.map +0 -1
- package/dist/compliance/tests/boolean_logic/boolean_logic.gs.d.ts +0 -1
- package/dist/compliance/tests/boolean_logic/boolean_logic.gs.js +0 -14
- package/dist/compliance/tests/boolean_logic/boolean_logic.gs.js.map +0 -1
- package/dist/compliance/tests/channel_basic/channel_basic.gs.d.ts +0 -1
- package/dist/compliance/tests/channel_basic/channel_basic.gs.js +0 -14
- package/dist/compliance/tests/channel_basic/channel_basic.gs.js.map +0 -1
- package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.d.ts +0 -1
- package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js +0 -27
- package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js.map +0 -1
- package/dist/compliance/tests/constants/constants.gs.d.ts +0 -1
- package/dist/compliance/tests/constants/constants.gs.js +0 -16
- package/dist/compliance/tests/constants/constants.gs.js.map +0 -1
- package/dist/compliance/tests/copy_independence/copy_independence.gs.d.ts +0 -1
- package/dist/compliance/tests/copy_independence/copy_independence.gs.js +0 -33
- package/dist/compliance/tests/copy_independence/copy_independence.gs.js.map +0 -1
- package/dist/compliance/tests/defer_statement/defer_statement.gs.d.ts +0 -1
- package/dist/compliance/tests/defer_statement/defer_statement.gs.js +0 -75
- package/dist/compliance/tests/defer_statement/defer_statement.gs.js.map +0 -1
- package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.d.ts +0 -1
- package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js +0 -37
- package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js.map +0 -1
- package/dist/compliance/tests/flag_bitwise_op/flag_bitwise_op.gs.d.ts +0 -1
- package/dist/compliance/tests/flag_bitwise_op/flag_bitwise_op.gs.js +0 -29
- package/dist/compliance/tests/flag_bitwise_op/flag_bitwise_op.gs.js.map +0 -1
- package/dist/compliance/tests/float64/float64.gs.d.ts +0 -1
- package/dist/compliance/tests/float64/float64.gs.js +0 -24
- package/dist/compliance/tests/float64/float64.gs.js.map +0 -1
- package/dist/compliance/tests/for_loop_basic/for_loop_basic.gs.d.ts +0 -1
- package/dist/compliance/tests/for_loop_basic/for_loop_basic.gs.js +0 -10
- package/dist/compliance/tests/for_loop_basic/for_loop_basic.gs.js.map +0 -1
- package/dist/compliance/tests/for_loop_condition_only/for_loop_condition_only.gs.d.ts +0 -1
- package/dist/compliance/tests/for_loop_condition_only/for_loop_condition_only.gs.js +0 -11
- package/dist/compliance/tests/for_loop_condition_only/for_loop_condition_only.gs.js.map +0 -1
- package/dist/compliance/tests/for_loop_condition_only/main.gs.d.ts +0 -1
- package/dist/compliance/tests/for_loop_condition_only/main.gs.js +0 -10
- package/dist/compliance/tests/for_loop_condition_only/main.gs.js.map +0 -1
- package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.d.ts +0 -1
- package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js +0 -14
- package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js.map +0 -1
- package/dist/compliance/tests/for_range/for_range.gs.d.ts +0 -1
- package/dist/compliance/tests/for_range/for_range.gs.js +0 -39
- package/dist/compliance/tests/for_range/for_range.gs.js.map +0 -1
- package/dist/compliance/tests/for_range_index_use/for_range_index_use.gs.d.ts +0 -1
- package/dist/compliance/tests/for_range_index_use/for_range_index_use.gs.js +0 -15
- package/dist/compliance/tests/for_range_index_use/for_range_index_use.gs.js.map +0 -1
- package/dist/compliance/tests/func_literal/func_literal.gs.d.ts +0 -1
- package/dist/compliance/tests/func_literal/func_literal.gs.js +0 -10
- package/dist/compliance/tests/func_literal/func_literal.gs.js.map +0 -1
- package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.d.ts +0 -12
- package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js +0 -30
- package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js.map +0 -1
- package/dist/compliance/tests/if_statement/if_statement.gs.d.ts +0 -1
- package/dist/compliance/tests/if_statement/if_statement.gs.js +0 -13
- package/dist/compliance/tests/if_statement/if_statement.gs.js.map +0 -1
- package/dist/compliance/tests/interface_method_comments/interface_method_comments.gs.d.ts +0 -1
- package/dist/compliance/tests/interface_method_comments/interface_method_comments.gs.js +0 -12
- package/dist/compliance/tests/interface_method_comments/interface_method_comments.gs.js.map +0 -1
- package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.d.ts +0 -1
- package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js +0 -34
- package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js.map +0 -1
- package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.d.ts +0 -1
- package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js +0 -32
- package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js.map +0 -1
- package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.d.ts +0 -1
- package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js +0 -40
- package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js.map +0 -1
- package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.d.ts +0 -1
- package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js +0 -51
- package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js.map +0 -1
- package/dist/compliance/tests/map_support/map_support.gs.d.ts +0 -1
- package/dist/compliance/tests/map_support/map_support.gs.js +0 -88
- package/dist/compliance/tests/map_support/map_support.gs.js.map +0 -1
- package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.d.ts +0 -1
- package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js +0 -24
- package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js.map +0 -1
- package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.d.ts +0 -1
- package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js +0 -33
- package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js.map +0 -1
- package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.d.ts +0 -1
- package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js +0 -22
- package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js.map +0 -1
- package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.d.ts +0 -1
- package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js +0 -33
- package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js.map +0 -1
- package/dist/compliance/tests/multiple_return_values/multiple_return_values.gs.d.ts +0 -1
- package/dist/compliance/tests/multiple_return_values/multiple_return_values.gs.js +0 -17
- package/dist/compliance/tests/multiple_return_values/multiple_return_values.gs.js.map +0 -1
- package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.d.ts +0 -1
- package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js +0 -29
- package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js.map +0 -1
- package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.d.ts +0 -1
- package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js +0 -27
- package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js.map +0 -1
- package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.d.ts +0 -1
- package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js +0 -22
- package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js.map +0 -1
- package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.d.ts +0 -1
- package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js +0 -20
- package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js.map +0 -1
- package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.d.ts +0 -1
- package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js +0 -28
- package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js.map +0 -1
- package/dist/compliance/tests/select_send_on_full_buffered_channel_with_default/select_send_on_full_buffered_channel_with_default.gs.d.ts +0 -1
- package/dist/compliance/tests/select_send_on_full_buffered_channel_with_default/select_send_on_full_buffered_channel_with_default.gs.js +0 -30
- package/dist/compliance/tests/select_send_on_full_buffered_channel_with_default/select_send_on_full_buffered_channel_with_default.gs.js.map +0 -1
- package/dist/compliance/tests/select_statement/select_statement.gs.d.ts +0 -1
- package/dist/compliance/tests/select_statement/select_statement.gs.js +0 -207
- package/dist/compliance/tests/select_statement/select_statement.gs.js.map +0 -1
- package/dist/compliance/tests/simple/simple.gs.d.ts +0 -1
- package/dist/compliance/tests/simple/simple.gs.js +0 -6
- package/dist/compliance/tests/simple/simple.gs.js.map +0 -1
- package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.d.ts +0 -1
- package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js +0 -24
- package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js.map +0 -1
- package/dist/compliance/tests/slices/slices.gs.d.ts +0 -1
- package/dist/compliance/tests/slices/slices.gs.js +0 -294
- package/dist/compliance/tests/slices/slices.gs.js.map +0 -1
- package/dist/compliance/tests/string_conversion/string_conversion.gs.d.ts +0 -1
- package/dist/compliance/tests/string_conversion/string_conversion.gs.js +0 -41
- package/dist/compliance/tests/string_conversion/string_conversion.gs.js.map +0 -1
- package/dist/compliance/tests/string_rune_conversion/string_rune_conversion.gs.d.ts +0 -1
- package/dist/compliance/tests/string_rune_conversion/string_rune_conversion.gs.js +0 -17
- package/dist/compliance/tests/string_rune_conversion/string_rune_conversion.gs.js.map +0 -1
- package/dist/compliance/tests/struct_embedding/struct_embedding.gs.d.ts +0 -1
- package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js +0 -48
- package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js.map +0 -1
- package/dist/compliance/tests/struct_field_access/struct_field_access.gs.d.ts +0 -1
- package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js +0 -19
- package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js.map +0 -1
- package/dist/compliance/tests/struct_pointer_interface_fields/struct_pointer_interface_fields.gs.d.ts +0 -1
- package/dist/compliance/tests/struct_pointer_interface_fields/struct_pointer_interface_fields.gs.js +0 -26
- package/dist/compliance/tests/struct_pointer_interface_fields/struct_pointer_interface_fields.gs.js.map +0 -1
- package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.d.ts +0 -1
- package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js +0 -30
- package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js.map +0 -1
- package/dist/compliance/tests/switch_statement/switch_statement.gs.d.ts +0 -1
- package/dist/compliance/tests/switch_statement/switch_statement.gs.js +0 -76
- package/dist/compliance/tests/switch_statement/switch_statement.gs.js.map +0 -1
- package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.d.ts +0 -1
- package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js +0 -31
- package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js.map +0 -1
package/compiler/compile_stmt.go
DELETED
|
@@ -1,1616 +0,0 @@
|
|
|
1
|
-
package compiler
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"fmt"
|
|
5
|
-
"go/ast"
|
|
6
|
-
"go/token"
|
|
7
|
-
gtypes "go/types"
|
|
8
|
-
"strings"
|
|
9
|
-
|
|
10
|
-
gstypes "github.com/paralin/goscript/compiler/types"
|
|
11
|
-
"github.com/sanity-io/litter"
|
|
12
|
-
"golang.org/x/tools/go/packages"
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
// WriteStmt writes a statement to the output.
|
|
16
|
-
func (c *GoToTSCompiler) WriteStmt(a ast.Stmt) error {
|
|
17
|
-
switch exp := a.(type) {
|
|
18
|
-
case *ast.BlockStmt:
|
|
19
|
-
// WriteStmtBlock does not currently return an error, assuming it's safe for now.
|
|
20
|
-
if err := c.WriteStmtBlock(exp, false); err != nil {
|
|
21
|
-
return fmt.Errorf("failed to write block statement: %w", err)
|
|
22
|
-
}
|
|
23
|
-
case *ast.AssignStmt:
|
|
24
|
-
if err := c.WriteStmtAssign(exp); err != nil {
|
|
25
|
-
return fmt.Errorf("failed to write assignment statement: %w", err)
|
|
26
|
-
}
|
|
27
|
-
case *ast.ReturnStmt:
|
|
28
|
-
if err := c.WriteStmtReturn(exp); err != nil {
|
|
29
|
-
return fmt.Errorf("failed to write return statement: %w", err)
|
|
30
|
-
}
|
|
31
|
-
case *ast.DeferStmt:
|
|
32
|
-
if err := c.WriteDeferStmt(exp); err != nil {
|
|
33
|
-
return fmt.Errorf("failed to write defer statement: %w", err)
|
|
34
|
-
}
|
|
35
|
-
case *ast.IfStmt:
|
|
36
|
-
if err := c.WriteStmtIf(exp); err != nil {
|
|
37
|
-
return fmt.Errorf("failed to write if statement: %w", err)
|
|
38
|
-
}
|
|
39
|
-
case *ast.ExprStmt:
|
|
40
|
-
if err := c.WriteStmtExpr(exp); err != nil {
|
|
41
|
-
return fmt.Errorf("failed to write expression statement: %w", err)
|
|
42
|
-
}
|
|
43
|
-
case *ast.DeclStmt:
|
|
44
|
-
// Handle declarations within a statement list (e.g., short variable declarations)
|
|
45
|
-
// This typically contains a GenDecl
|
|
46
|
-
if genDecl, ok := exp.Decl.(*ast.GenDecl); ok {
|
|
47
|
-
for _, spec := range genDecl.Specs {
|
|
48
|
-
// Value specs within a declaration statement
|
|
49
|
-
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
|
|
50
|
-
// WriteValueSpec does not currently return an error, assuming it's safe for now.
|
|
51
|
-
if err := c.WriteValueSpec(valueSpec); err != nil {
|
|
52
|
-
return fmt.Errorf("failed to write value spec in declaration statement: %w", err)
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unhandled spec in DeclStmt: %T", spec))
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unhandled declaration type in DeclStmt: %T", exp.Decl))
|
|
60
|
-
}
|
|
61
|
-
case *ast.ForStmt:
|
|
62
|
-
// WriteStmtFor does not currently return an error, assuming it's safe for now.
|
|
63
|
-
if err := c.WriteStmtFor(exp); err != nil {
|
|
64
|
-
return fmt.Errorf("failed to write for statement: %w", err)
|
|
65
|
-
}
|
|
66
|
-
case *ast.RangeStmt:
|
|
67
|
-
// Generate TS for for…range loops, log if something goes wrong
|
|
68
|
-
if err := c.WriteStmtRange(exp); err != nil {
|
|
69
|
-
return fmt.Errorf("failed to write range statement: %w", err)
|
|
70
|
-
}
|
|
71
|
-
case *ast.SwitchStmt:
|
|
72
|
-
// WriteStmtSwitch does not currently return an error, assuming it's safe for now.
|
|
73
|
-
if err := c.WriteStmtSwitch(exp); err != nil {
|
|
74
|
-
return fmt.Errorf("failed to write switch statement: %w", err)
|
|
75
|
-
}
|
|
76
|
-
case *ast.IncDecStmt:
|
|
77
|
-
// Handle increment/decrement (e.g., i++ or i--)
|
|
78
|
-
if err := c.WriteValueExpr(exp.X); err != nil { // The expression (e.g., i)
|
|
79
|
-
return fmt.Errorf("failed to write increment/decrement expression: %w", err)
|
|
80
|
-
}
|
|
81
|
-
tokStr, ok := gstypes.TokenToTs(exp.Tok)
|
|
82
|
-
if !ok {
|
|
83
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unknown incdec token: %s", exp.Tok.String()))
|
|
84
|
-
} else {
|
|
85
|
-
c.tsw.WriteLiterally(tokStr) // The token (e.g., ++)
|
|
86
|
-
}
|
|
87
|
-
c.tsw.WriteLine("")
|
|
88
|
-
case *ast.SendStmt:
|
|
89
|
-
if err := c.WriteStmtSend(exp); err != nil {
|
|
90
|
-
return fmt.Errorf("failed to write send statement: %w", err)
|
|
91
|
-
}
|
|
92
|
-
case *ast.GoStmt:
|
|
93
|
-
// Handle goroutine statement
|
|
94
|
-
// Translate 'go func() { ... }()' to 'queueMicrotask(() => { ... compiled body ... })'
|
|
95
|
-
|
|
96
|
-
// The call expression's function is the function literal
|
|
97
|
-
callExpr := exp.Call
|
|
98
|
-
if funcLit, ok := callExpr.Fun.(*ast.FuncLit); ok {
|
|
99
|
-
// Check if the function literal body contains async operations
|
|
100
|
-
isAsync := c.containsAsyncOperations(funcLit.Body)
|
|
101
|
-
|
|
102
|
-
if isAsync {
|
|
103
|
-
c.tsw.WriteLiterally("queueMicrotask(async () => {")
|
|
104
|
-
} else {
|
|
105
|
-
c.tsw.WriteLiterally("queueMicrotask(() => {")
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
c.tsw.Indent(1)
|
|
109
|
-
c.tsw.WriteLine("")
|
|
110
|
-
|
|
111
|
-
// Compile the function literal's body directly
|
|
112
|
-
if err := c.WriteStmt(funcLit.Body); err != nil {
|
|
113
|
-
return fmt.Errorf("failed to write goroutine function literal body: %w", err)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
c.tsw.Indent(-1)
|
|
117
|
-
c.tsw.WriteLine("})") // Close the queueMicrotask callback and the statement
|
|
118
|
-
|
|
119
|
-
} else {
|
|
120
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unhandled goroutine function type: %T", callExpr.Fun))
|
|
121
|
-
// Fallback: try to compile the call expression directly, though it might not work
|
|
122
|
-
if err := c.WriteValueExpr(exp.Call); err != nil {
|
|
123
|
-
return fmt.Errorf("failed to write fallback goroutine call expression: %w", err)
|
|
124
|
-
}
|
|
125
|
-
c.tsw.WriteLine("") // Ensure a newline even on fallback
|
|
126
|
-
}
|
|
127
|
-
case *ast.SelectStmt:
|
|
128
|
-
// Handle select statement
|
|
129
|
-
if err := c.WriteStmtSelect(exp); err != nil {
|
|
130
|
-
return fmt.Errorf("failed to write select statement: %w", err)
|
|
131
|
-
}
|
|
132
|
-
case *ast.BranchStmt:
|
|
133
|
-
// Handle break and continue statements
|
|
134
|
-
switch exp.Tok {
|
|
135
|
-
case token.BREAK:
|
|
136
|
-
c.tsw.WriteLine("break") // No semicolon needed
|
|
137
|
-
case token.CONTINUE:
|
|
138
|
-
c.tsw.WriteLine("continue") // No semicolon needed
|
|
139
|
-
default:
|
|
140
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unhandled branch statement token: %s", exp.Tok.String()))
|
|
141
|
-
}
|
|
142
|
-
default:
|
|
143
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unknown statement: %s\n", litter.Sdump(a)))
|
|
144
|
-
}
|
|
145
|
-
return nil
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// WriteDeferStmt writes a defer statement using TypeScript's DisposableStack.
|
|
149
|
-
func (c *GoToTSCompiler) WriteDeferStmt(exp *ast.DeferStmt) error {
|
|
150
|
-
// Check if deferred call contains async operations
|
|
151
|
-
hasAsyncOps := c.containsAsyncOperations(exp.Call)
|
|
152
|
-
|
|
153
|
-
// Choose the right stack variable and async modifier
|
|
154
|
-
stackVar := "cleanup"
|
|
155
|
-
asyncPrefix := ""
|
|
156
|
-
|
|
157
|
-
if c.inAsyncFunction {
|
|
158
|
-
stackVar = "__defer"
|
|
159
|
-
if hasAsyncOps {
|
|
160
|
-
asyncPrefix = "async "
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
c.tsw.WriteLiterally(fmt.Sprintf("%s.defer(%s() => {", stackVar, asyncPrefix))
|
|
165
|
-
c.tsw.Indent(1)
|
|
166
|
-
c.tsw.WriteLine("")
|
|
167
|
-
|
|
168
|
-
// Write the deferred call or inline the body when it's an immediately-invoked
|
|
169
|
-
// function literal (defer func(){ ... }()).
|
|
170
|
-
if funcLit, ok := exp.Call.Fun.(*ast.FuncLit); ok && len(exp.Call.Args) == 0 {
|
|
171
|
-
// Inline the function literal's body to avoid nested arrow invocation.
|
|
172
|
-
prevAsyncState := c.inAsyncFunction
|
|
173
|
-
c.inAsyncFunction = hasAsyncOps
|
|
174
|
-
|
|
175
|
-
for _, stmt := range funcLit.Body.List {
|
|
176
|
-
if err := c.WriteStmt(stmt); err != nil {
|
|
177
|
-
c.inAsyncFunction = prevAsyncState
|
|
178
|
-
return fmt.Errorf("failed to write statement in deferred function body: %w", err)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
c.inAsyncFunction = prevAsyncState
|
|
183
|
-
} else {
|
|
184
|
-
// Fallback: write the call expression as-is.
|
|
185
|
-
if err := c.WriteValueExpr(exp.Call); err != nil {
|
|
186
|
-
return fmt.Errorf("failed to write deferred call: %w", err)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
c.tsw.Indent(-1)
|
|
191
|
-
c.tsw.WriteLine("});")
|
|
192
|
-
|
|
193
|
-
return nil
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// WriteStmtSelect writes a select statement.
|
|
197
|
-
func (c *GoToTSCompiler) WriteStmtSelect(exp *ast.SelectStmt) error {
|
|
198
|
-
// This is our implementation of the select statement, which will use Promise.race
|
|
199
|
-
// to achieve the same semantics as Go's select statement.
|
|
200
|
-
|
|
201
|
-
// Variable to track whether we have a default case
|
|
202
|
-
hasDefault := false
|
|
203
|
-
|
|
204
|
-
// Start the selectStatement call and the array literal
|
|
205
|
-
c.tsw.WriteLiterally("await goscript.selectStatement(")
|
|
206
|
-
c.tsw.WriteLine("[") // Put bracket on new line
|
|
207
|
-
c.tsw.Indent(1)
|
|
208
|
-
|
|
209
|
-
// For each case clause, generate a SelectCase object directly into the array literal
|
|
210
|
-
for i, stmt := range exp.Body.List {
|
|
211
|
-
if commClause, ok := stmt.(*ast.CommClause); ok {
|
|
212
|
-
if commClause.Comm == nil {
|
|
213
|
-
// This is a default case
|
|
214
|
-
hasDefault = true
|
|
215
|
-
// Add a SelectCase object for the default case with a special ID
|
|
216
|
-
c.tsw.WriteLiterally("{") // Start object literal
|
|
217
|
-
c.tsw.Indent(1)
|
|
218
|
-
c.tsw.WriteLine("")
|
|
219
|
-
c.tsw.WriteLiterally("id: -1,") // Special ID for default case
|
|
220
|
-
c.tsw.WriteLine("")
|
|
221
|
-
c.tsw.WriteLiterally("isSend: false,") // Default case is neither send nor receive, but needs a value
|
|
222
|
-
c.tsw.WriteLine("")
|
|
223
|
-
c.tsw.WriteLiterally("channel: null,") // No channel for default case
|
|
224
|
-
c.tsw.WriteLine("")
|
|
225
|
-
c.tsw.WriteLiterally("onSelected: async (result) => {") // Mark as async because case body might contain await
|
|
226
|
-
c.tsw.Indent(1)
|
|
227
|
-
c.tsw.WriteLine("")
|
|
228
|
-
// Write the case body
|
|
229
|
-
for _, bodyStmt := range commClause.Body {
|
|
230
|
-
if err := c.WriteStmt(bodyStmt); err != nil {
|
|
231
|
-
return fmt.Errorf("failed to write statement in select default case body (onSelected): %w", err)
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
c.tsw.Indent(-1)
|
|
235
|
-
c.tsw.WriteLine("}") // Close onSelected handler
|
|
236
|
-
c.tsw.Indent(-1)
|
|
237
|
-
c.tsw.WriteLiterally("},") // Close SelectCase object and add comma
|
|
238
|
-
c.tsw.WriteLine("")
|
|
239
|
-
|
|
240
|
-
continue
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Generate a unique ID for this case
|
|
244
|
-
caseID := i
|
|
245
|
-
|
|
246
|
-
// Start writing the SelectCase object
|
|
247
|
-
c.tsw.WriteLiterally("{") // Start object literal
|
|
248
|
-
c.tsw.Indent(1)
|
|
249
|
-
c.tsw.WriteLine("")
|
|
250
|
-
c.tsw.WriteLiterally(fmt.Sprintf("id: %d,", caseID))
|
|
251
|
-
c.tsw.WriteLine("")
|
|
252
|
-
|
|
253
|
-
// Handle different types of comm statements
|
|
254
|
-
switch comm := commClause.Comm.(type) {
|
|
255
|
-
case *ast.AssignStmt:
|
|
256
|
-
// This is a receive operation with assignment: case v := <-ch: or case v, ok := <-ch:
|
|
257
|
-
if len(comm.Rhs) == 1 {
|
|
258
|
-
if unaryExpr, ok := comm.Rhs[0].(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
|
|
259
|
-
// It's a receive operation
|
|
260
|
-
c.tsw.WriteLiterally("isSend: false,")
|
|
261
|
-
c.tsw.WriteLine("")
|
|
262
|
-
c.tsw.WriteLiterally("channel: ")
|
|
263
|
-
if err := c.WriteValueExpr(unaryExpr.X); err != nil { // The channel expression
|
|
264
|
-
return fmt.Errorf("failed to write channel expression in select receive case: %w", err)
|
|
265
|
-
}
|
|
266
|
-
c.tsw.WriteLiterally(",")
|
|
267
|
-
c.tsw.WriteLine("")
|
|
268
|
-
} else {
|
|
269
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unhandled RHS in select assignment case: %T", comm.Rhs[0]))
|
|
270
|
-
}
|
|
271
|
-
} else {
|
|
272
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unhandled RHS count in select assignment case: %d", len(comm.Rhs)))
|
|
273
|
-
}
|
|
274
|
-
case *ast.ExprStmt:
|
|
275
|
-
// This is a simple receive: case <-ch:
|
|
276
|
-
if unaryExpr, ok := comm.X.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
|
|
277
|
-
c.tsw.WriteLiterally("isSend: false,")
|
|
278
|
-
c.tsw.WriteLine("")
|
|
279
|
-
c.tsw.WriteLiterally("channel: ")
|
|
280
|
-
if err := c.WriteValueExpr(unaryExpr.X); err != nil { // The channel expression
|
|
281
|
-
return fmt.Errorf("failed to write channel expression in select receive case: %w", err)
|
|
282
|
-
}
|
|
283
|
-
c.tsw.WriteLiterally(",")
|
|
284
|
-
c.tsw.WriteLine("")
|
|
285
|
-
} else {
|
|
286
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unhandled expression in select case: %T", comm.X))
|
|
287
|
-
}
|
|
288
|
-
case *ast.SendStmt:
|
|
289
|
-
// This is a send operation: case ch <- v:
|
|
290
|
-
c.tsw.WriteLiterally("isSend: true,")
|
|
291
|
-
c.tsw.WriteLine("")
|
|
292
|
-
c.tsw.WriteLiterally("channel: ")
|
|
293
|
-
if err := c.WriteValueExpr(comm.Chan); err != nil { // The channel expression
|
|
294
|
-
return fmt.Errorf("failed to write channel expression in select send case: %w", err)
|
|
295
|
-
}
|
|
296
|
-
c.tsw.WriteLiterally(",")
|
|
297
|
-
c.tsw.WriteLine("")
|
|
298
|
-
c.tsw.WriteLiterally("value: ")
|
|
299
|
-
if err := c.WriteValueExpr(comm.Value); err != nil { // The value expression
|
|
300
|
-
return fmt.Errorf("failed to write value expression in select send case: %w", err)
|
|
301
|
-
}
|
|
302
|
-
c.tsw.WriteLiterally(",")
|
|
303
|
-
c.tsw.WriteLine("")
|
|
304
|
-
default:
|
|
305
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unhandled comm statement in select case: %T", comm))
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Add the onSelected handler to execute the case body after the select resolves
|
|
309
|
-
c.tsw.WriteLiterally("onSelected: async (result) => {") // Mark as async because case body might contain await
|
|
310
|
-
c.tsw.Indent(1)
|
|
311
|
-
c.tsw.WriteLine("")
|
|
312
|
-
|
|
313
|
-
// Handle assignment for channel receives if needed (inside the onSelected handler)
|
|
314
|
-
if assignStmt, ok := commClause.Comm.(*ast.AssignStmt); ok {
|
|
315
|
-
// This is a receive operation with assignment
|
|
316
|
-
if len(assignStmt.Lhs) == 1 {
|
|
317
|
-
// Simple receive: case v := <-ch:
|
|
318
|
-
valIdent, ok := assignStmt.Lhs[0].(*ast.Ident)
|
|
319
|
-
if ok && valIdent.Name != "_" { // Check for blank identifier
|
|
320
|
-
c.tsw.WriteLiterally("const ")
|
|
321
|
-
c.WriteIdentValue(valIdent)
|
|
322
|
-
c.tsw.WriteLiterally(" = result.value")
|
|
323
|
-
c.tsw.WriteLine("")
|
|
324
|
-
}
|
|
325
|
-
} else if len(assignStmt.Lhs) == 2 {
|
|
326
|
-
// Receive with ok: case v, ok := <-ch:
|
|
327
|
-
valIdent, valOk := assignStmt.Lhs[0].(*ast.Ident)
|
|
328
|
-
okIdent, okOk := assignStmt.Lhs[1].(*ast.Ident)
|
|
329
|
-
|
|
330
|
-
if valOk && valIdent.Name != "_" {
|
|
331
|
-
c.tsw.WriteLiterally("const ")
|
|
332
|
-
c.WriteIdentValue(valIdent)
|
|
333
|
-
c.tsw.WriteLiterally(" = result.value")
|
|
334
|
-
c.tsw.WriteLine("")
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if okOk && okIdent.Name != "_" {
|
|
338
|
-
c.tsw.WriteLiterally("const ")
|
|
339
|
-
c.WriteIdentValue(okIdent)
|
|
340
|
-
c.tsw.WriteLiterally(" = result.ok")
|
|
341
|
-
c.tsw.WriteLine("")
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
// Note: Simple receive (case <-ch:) and send (case ch <- v:) don't require assignment here,
|
|
346
|
-
// as the operation was already performed by selectReceive/selectSend and the result is in 'result'.
|
|
347
|
-
|
|
348
|
-
// Write the case body
|
|
349
|
-
for _, bodyStmt := range commClause.Body {
|
|
350
|
-
if err := c.WriteStmt(bodyStmt); err != nil {
|
|
351
|
-
return fmt.Errorf("failed to write statement in select case body (onSelected): %w", err)
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
c.tsw.Indent(-1)
|
|
356
|
-
c.tsw.WriteLine("}") // Close onSelected handler
|
|
357
|
-
c.tsw.Indent(-1)
|
|
358
|
-
c.tsw.WriteLiterally("},") // Close SelectCase object and add comma
|
|
359
|
-
c.tsw.WriteLine("")
|
|
360
|
-
|
|
361
|
-
} else {
|
|
362
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unknown statement in select body: %T", stmt))
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Close the array literal and the selectStatement call
|
|
367
|
-
c.tsw.Indent(-1)
|
|
368
|
-
c.tsw.WriteLiterally("], ")
|
|
369
|
-
c.tsw.WriteLiterally(fmt.Sprintf("%t", hasDefault))
|
|
370
|
-
c.tsw.WriteLiterally(")")
|
|
371
|
-
c.tsw.WriteLine("")
|
|
372
|
-
|
|
373
|
-
return nil
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// WriteStmtSwitch writes a switch statement.
|
|
377
|
-
func (c *GoToTSCompiler) WriteStmtSwitch(exp *ast.SwitchStmt) error {
|
|
378
|
-
// Handle optional initialization statement
|
|
379
|
-
if exp.Init != nil {
|
|
380
|
-
c.tsw.WriteLiterally("{")
|
|
381
|
-
c.tsw.Indent(1)
|
|
382
|
-
if err := c.WriteStmt(exp.Init); err != nil {
|
|
383
|
-
return fmt.Errorf("failed to write switch initialization statement: %w", err)
|
|
384
|
-
}
|
|
385
|
-
defer func() {
|
|
386
|
-
c.tsw.Indent(-1)
|
|
387
|
-
c.tsw.WriteLiterally("}")
|
|
388
|
-
}()
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
c.tsw.WriteLiterally("switch (")
|
|
392
|
-
// Handle the switch tag (the expression being switched on)
|
|
393
|
-
if exp.Tag != nil {
|
|
394
|
-
if err := c.WriteValueExpr(exp.Tag); err != nil {
|
|
395
|
-
return fmt.Errorf("failed to write switch tag expression: %w", err)
|
|
396
|
-
}
|
|
397
|
-
} else {
|
|
398
|
-
c.tsw.WriteLiterally("true") // Write 'true' for switch without expression
|
|
399
|
-
}
|
|
400
|
-
c.tsw.WriteLiterally(") {")
|
|
401
|
-
c.tsw.WriteLine("")
|
|
402
|
-
c.tsw.Indent(1)
|
|
403
|
-
|
|
404
|
-
// Handle case clauses
|
|
405
|
-
for _, stmt := range exp.Body.List {
|
|
406
|
-
if caseClause, ok := stmt.(*ast.CaseClause); ok {
|
|
407
|
-
// WriteCaseClause does not currently return an error, assuming it's safe for now.
|
|
408
|
-
if err := c.WriteCaseClause(caseClause); err != nil {
|
|
409
|
-
return fmt.Errorf("failed to write case clause in switch statement: %w", err)
|
|
410
|
-
}
|
|
411
|
-
} else {
|
|
412
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unhandled statement in switch body: %T", stmt))
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
c.tsw.Indent(-1)
|
|
417
|
-
c.tsw.WriteLine("}")
|
|
418
|
-
return nil
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// WriteCaseClause writes a case clause within a switch statement.
|
|
422
|
-
func (c *GoToTSCompiler) WriteCaseClause(exp *ast.CaseClause) error {
|
|
423
|
-
if exp.List == nil {
|
|
424
|
-
// Default case
|
|
425
|
-
c.tsw.WriteLiterally("default:")
|
|
426
|
-
c.tsw.WriteLine("")
|
|
427
|
-
} else {
|
|
428
|
-
// Case with expressions
|
|
429
|
-
c.tsw.WriteLiterally("case ")
|
|
430
|
-
for i, expr := range exp.List {
|
|
431
|
-
if i > 0 {
|
|
432
|
-
c.tsw.WriteLiterally(", ") // Although Go doesn't support multiple expressions per case like this,
|
|
433
|
-
} // TypeScript does, so we'll write it this way for now.
|
|
434
|
-
if err := c.WriteValueExpr(expr); err != nil {
|
|
435
|
-
return fmt.Errorf("failed to write case clause expression: %w", err)
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
c.tsw.WriteLiterally(":")
|
|
439
|
-
c.tsw.WriteLine("")
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
c.tsw.Indent(1)
|
|
443
|
-
// Write the body of the case clause
|
|
444
|
-
for _, stmt := range exp.Body {
|
|
445
|
-
if err := c.WriteStmt(stmt); err != nil {
|
|
446
|
-
return fmt.Errorf("failed to write statement in case clause body: %w", err)
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
// Add break statement (Go's switch has implicit breaks)
|
|
450
|
-
c.tsw.WriteLine("break") // Remove semicolon
|
|
451
|
-
c.tsw.Indent(-1)
|
|
452
|
-
return nil
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// Overload for backward compatibility
|
|
456
|
-
func (c *GoToTSCompiler) WriteStmtCompat(a ast.Stmt) error {
|
|
457
|
-
// This function is for backward compatibility and simply calls WriteStmt.
|
|
458
|
-
// It should propagate any error returned by WriteStmt.
|
|
459
|
-
return c.WriteStmt(a)
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// WriteStmtIf writes an if statement.
|
|
463
|
-
func (s *GoToTSCompiler) WriteStmtIf(exp *ast.IfStmt) error {
|
|
464
|
-
if exp.Init != nil {
|
|
465
|
-
s.tsw.WriteLiterally("{")
|
|
466
|
-
s.tsw.Indent(1)
|
|
467
|
-
|
|
468
|
-
if err := s.WriteStmt(exp.Init); err != nil {
|
|
469
|
-
return err
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
defer func() {
|
|
473
|
-
s.tsw.Indent(-1)
|
|
474
|
-
s.tsw.WriteLiterally("}")
|
|
475
|
-
}()
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
s.tsw.WriteLiterally("if (")
|
|
479
|
-
if err := s.WriteValueExpr(exp.Cond); err != nil { // Condition is a value
|
|
480
|
-
return err
|
|
481
|
-
}
|
|
482
|
-
s.tsw.WriteLiterally(") ")
|
|
483
|
-
|
|
484
|
-
if exp.Body != nil {
|
|
485
|
-
if err := s.WriteStmtBlock(exp.Body, exp.Else != nil); err != nil {
|
|
486
|
-
return fmt.Errorf("failed to write if body block statement: %w", err)
|
|
487
|
-
}
|
|
488
|
-
} else {
|
|
489
|
-
// Handle nil body case using WriteStmtBlock with an empty block
|
|
490
|
-
if err := s.WriteStmtBlock(&ast.BlockStmt{}, exp.Else != nil); err != nil {
|
|
491
|
-
return fmt.Errorf("failed to write empty block statement in if statement: %w", err)
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// handle else branch
|
|
496
|
-
if exp.Else != nil {
|
|
497
|
-
s.tsw.WriteLiterally(" else ")
|
|
498
|
-
switch elseStmt := exp.Else.(type) {
|
|
499
|
-
case *ast.BlockStmt:
|
|
500
|
-
// Always pass false for suppressNewline here
|
|
501
|
-
if err := s.WriteStmtBlock(elseStmt, false); err != nil {
|
|
502
|
-
return fmt.Errorf("failed to write else block statement in if statement: %w", err)
|
|
503
|
-
}
|
|
504
|
-
case *ast.IfStmt:
|
|
505
|
-
// Recursive call handles its own block formatting
|
|
506
|
-
if err := s.WriteStmtIf(elseStmt); err != nil {
|
|
507
|
-
return fmt.Errorf("failed to write else if statement in if statement: %w", err)
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
return nil
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// WriteStmtReturn writes a return statement.
|
|
515
|
-
func (c *GoToTSCompiler) WriteStmtReturn(exp *ast.ReturnStmt) error {
|
|
516
|
-
c.tsw.WriteLiterally("return ")
|
|
517
|
-
if len(exp.Results) > 1 {
|
|
518
|
-
c.tsw.WriteLiterally("[")
|
|
519
|
-
}
|
|
520
|
-
for i, res := range exp.Results {
|
|
521
|
-
if i != 0 {
|
|
522
|
-
c.tsw.WriteLiterally(", ")
|
|
523
|
-
}
|
|
524
|
-
if err := c.WriteValueExpr(res); err != nil { // Return results are values
|
|
525
|
-
return err
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
if len(exp.Results) > 1 {
|
|
529
|
-
c.tsw.WriteLiterally("]")
|
|
530
|
-
}
|
|
531
|
-
c.tsw.WriteLine("") // Remove semicolon
|
|
532
|
-
return nil
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// WriteStmtBlock writes a block statement, preserving comments and blank lines.
|
|
536
|
-
func (c *GoToTSCompiler) WriteStmtBlock(exp *ast.BlockStmt, suppressNewline bool) error {
|
|
537
|
-
if exp == nil {
|
|
538
|
-
c.tsw.WriteLiterally("{}")
|
|
539
|
-
if !suppressNewline {
|
|
540
|
-
c.tsw.WriteLine("")
|
|
541
|
-
}
|
|
542
|
-
return nil
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// Opening brace
|
|
546
|
-
c.tsw.WriteLine("{")
|
|
547
|
-
c.tsw.Indent(1)
|
|
548
|
-
|
|
549
|
-
// Add "using" statement if needed
|
|
550
|
-
if c.nextBlockNeedsDefer {
|
|
551
|
-
if c.inAsyncFunction {
|
|
552
|
-
c.tsw.WriteLine("await using __defer = new goscript.AsyncDisposableStack();")
|
|
553
|
-
} else {
|
|
554
|
-
c.tsw.WriteLine("using cleanup = new goscript.DisposableStack();")
|
|
555
|
-
}
|
|
556
|
-
c.nextBlockNeedsDefer = false
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Prepare line info
|
|
560
|
-
var file *token.File
|
|
561
|
-
if c.pkg != nil && c.pkg.Fset != nil && exp.Lbrace.IsValid() {
|
|
562
|
-
file = c.pkg.Fset.File(exp.Lbrace)
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// writeBlank emits a single blank line if gap > 1
|
|
566
|
-
writeBlank := func(prev, curr int) {
|
|
567
|
-
if file != nil && prev > 0 && curr > prev+1 {
|
|
568
|
-
c.tsw.WriteLine("")
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// Track last printed line, start at opening brace
|
|
573
|
-
lastLine := 0
|
|
574
|
-
if file != nil {
|
|
575
|
-
lastLine = file.Line(exp.Lbrace)
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// 1. For each statement: write its leading comments, blank space, then the stmt
|
|
579
|
-
for _, stmt := range exp.List {
|
|
580
|
-
// Get statement's end line and position for inline comment check
|
|
581
|
-
stmtEndLine := 0
|
|
582
|
-
stmtEndPos := token.NoPos
|
|
583
|
-
if file != nil && stmt.End().IsValid() {
|
|
584
|
-
stmtEndLine = file.Line(stmt.End())
|
|
585
|
-
stmtEndPos = stmt.End()
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// Process leading comments for stmt
|
|
589
|
-
comments := c.cmap.Filter(stmt).Comments()
|
|
590
|
-
for _, cg := range comments {
|
|
591
|
-
// Check if this comment group is an inline comment for the current statement
|
|
592
|
-
isInlineComment := false
|
|
593
|
-
if file != nil && cg.Pos().IsValid() && stmtEndPos.IsValid() {
|
|
594
|
-
commentStartLine := file.Line(cg.Pos())
|
|
595
|
-
// Inline if starts on same line as stmt end AND starts after stmt end position
|
|
596
|
-
if commentStartLine == stmtEndLine && cg.Pos() > stmtEndPos {
|
|
597
|
-
isInlineComment = true
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// If it's NOT an inline comment for this statement, write it here
|
|
602
|
-
if !isInlineComment {
|
|
603
|
-
start := 0
|
|
604
|
-
if file != nil && cg.Pos().IsValid() {
|
|
605
|
-
start = file.Line(cg.Pos())
|
|
606
|
-
}
|
|
607
|
-
writeBlank(lastLine, start)
|
|
608
|
-
// WriteDoc does not currently return an error, assuming it's safe for now.
|
|
609
|
-
c.WriteDoc(cg) // WriteDoc will handle the actual comment text
|
|
610
|
-
if file != nil && cg.End().IsValid() {
|
|
611
|
-
lastLine = file.Line(cg.End())
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
// If it IS an inline comment, skip it. The statement writer will handle it.
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// the statement itself
|
|
618
|
-
stmtStart := 0
|
|
619
|
-
if file != nil && stmt.Pos().IsValid() {
|
|
620
|
-
stmtStart = file.Line(stmt.Pos())
|
|
621
|
-
}
|
|
622
|
-
writeBlank(lastLine, stmtStart)
|
|
623
|
-
// Call the specific statement writer (e.g., WriteStmtAssign).
|
|
624
|
-
// It is responsible for handling its own inline comment.
|
|
625
|
-
if err := c.WriteStmt(stmt); err != nil {
|
|
626
|
-
return fmt.Errorf("failed to write statement in block: %w", err)
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
if file != nil && stmt.End().IsValid() {
|
|
630
|
-
// Update lastLine based on the statement's end, *including* potential inline comment handled by WriteStmt*
|
|
631
|
-
lastLine = file.Line(stmt.End())
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// 2. Trailing comments on the block (after last stmt, before closing brace)
|
|
636
|
-
trailing := c.cmap.Filter(exp).Comments()
|
|
637
|
-
for _, cg := range trailing {
|
|
638
|
-
start := 0
|
|
639
|
-
if file != nil && cg.Pos().IsValid() {
|
|
640
|
-
start = file.Line(cg.Pos())
|
|
641
|
-
}
|
|
642
|
-
// only emit if it follows the last content
|
|
643
|
-
if start > lastLine {
|
|
644
|
-
writeBlank(lastLine, start)
|
|
645
|
-
// WriteDoc does not currently return an error, assuming it's safe for now.
|
|
646
|
-
c.WriteDoc(cg)
|
|
647
|
-
if file != nil && cg.End().IsValid() {
|
|
648
|
-
lastLine = file.Line(cg.End())
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// 3. Blank lines before closing brace
|
|
654
|
-
closing := 0
|
|
655
|
-
if file != nil && exp.Rbrace.IsValid() {
|
|
656
|
-
closing = file.Line(exp.Rbrace)
|
|
657
|
-
}
|
|
658
|
-
writeBlank(lastLine, closing)
|
|
659
|
-
|
|
660
|
-
// Closing brace
|
|
661
|
-
c.tsw.Indent(-1)
|
|
662
|
-
c.tsw.WriteLiterally("}")
|
|
663
|
-
|
|
664
|
-
if !suppressNewline {
|
|
665
|
-
c.tsw.WriteLine("")
|
|
666
|
-
}
|
|
667
|
-
return nil
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// writeAssignmentCore writes the core LHS, operator, and RHS of an assignment.
|
|
671
|
-
// It does NOT handle blank identifiers, 'let' keyword, or trailing semicolons/comments/newlines.
|
|
672
|
-
func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Token) error {
|
|
673
|
-
// Special handling for integer division assignment (/=)
|
|
674
|
-
if tok == token.QUO_ASSIGN && len(lhs) == 1 && len(rhs) == 1 {
|
|
675
|
-
lhsType := c.pkg.TypesInfo.TypeOf(lhs[0])
|
|
676
|
-
rhsType := c.pkg.TypesInfo.TypeOf(rhs[0])
|
|
677
|
-
|
|
678
|
-
if lhsType != nil && rhsType != nil {
|
|
679
|
-
lhsBasic, lhsIsBasic := lhsType.Underlying().(*gtypes.Basic)
|
|
680
|
-
rhsBasic, rhsIsBasic := rhsType.Underlying().(*gtypes.Basic)
|
|
681
|
-
|
|
682
|
-
if lhsIsBasic && rhsIsBasic && (lhsBasic.Info()>ypes.IsInteger != 0) && (rhsBasic.Info()>ypes.IsInteger != 0) {
|
|
683
|
-
// Integer division assignment: lhs = Math.floor(lhs / rhs)
|
|
684
|
-
if err := c.WriteValueExpr(lhs[0]); err != nil {
|
|
685
|
-
return err
|
|
686
|
-
}
|
|
687
|
-
c.tsw.WriteLiterally(" = Math.floor(")
|
|
688
|
-
if err := c.WriteValueExpr(lhs[0]); err != nil { // Write LHS again for the division
|
|
689
|
-
return err
|
|
690
|
-
}
|
|
691
|
-
c.tsw.WriteLiterally(" / ")
|
|
692
|
-
if err := c.WriteValueExpr(rhs[0]); err != nil {
|
|
693
|
-
return err
|
|
694
|
-
}
|
|
695
|
-
c.tsw.WriteLiterally(")")
|
|
696
|
-
return nil // Handled integer division assignment
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// --- Original logic for other assignments ---
|
|
702
|
-
isMapIndexLHS := false // Track if the first LHS is a map index
|
|
703
|
-
for i, l := range lhs {
|
|
704
|
-
if i != 0 {
|
|
705
|
-
c.tsw.WriteLiterally(", ")
|
|
706
|
-
}
|
|
707
|
-
// Handle map indexing assignment specially
|
|
708
|
-
currentIsMapIndex := false
|
|
709
|
-
if indexExpr, ok := l.(*ast.IndexExpr); ok {
|
|
710
|
-
if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
|
|
711
|
-
if _, isMap := tv.Type.Underlying().(*gtypes.Map); isMap {
|
|
712
|
-
currentIsMapIndex = true
|
|
713
|
-
if i == 0 {
|
|
714
|
-
isMapIndexLHS = true
|
|
715
|
-
}
|
|
716
|
-
// Use mapSet helper
|
|
717
|
-
c.tsw.WriteLiterally("goscript.mapSet(")
|
|
718
|
-
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
719
|
-
return err
|
|
720
|
-
}
|
|
721
|
-
c.tsw.WriteLiterally(", ")
|
|
722
|
-
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
723
|
-
return err
|
|
724
|
-
}
|
|
725
|
-
c.tsw.WriteLiterally(", ")
|
|
726
|
-
// Value will be added after operator and RHS
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
if !currentIsMapIndex {
|
|
732
|
-
if err := c.WriteValueExpr(l); err != nil { // LHS is a value
|
|
733
|
-
return err
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// Only write the assignment operator for regular variables, not for map assignments handled by mapSet
|
|
739
|
-
if isMapIndexLHS && len(lhs) == 1 { // Only skip operator if it's a single map assignment
|
|
740
|
-
// Continue, we've already written part of the mapSet() function call
|
|
741
|
-
} else {
|
|
742
|
-
c.tsw.WriteLiterally(" ")
|
|
743
|
-
tokStr, ok := gstypes.TokenToTs(tok) // Use explicit gstypes alias
|
|
744
|
-
if !ok {
|
|
745
|
-
c.tsw.WriteLiterally("?= ")
|
|
746
|
-
c.tsw.WriteCommentLine("Unknown token " + tok.String())
|
|
747
|
-
} else {
|
|
748
|
-
c.tsw.WriteLiterally(tokStr)
|
|
749
|
-
}
|
|
750
|
-
c.tsw.WriteLiterally(" ")
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
for i, r := range rhs {
|
|
754
|
-
if i != 0 {
|
|
755
|
-
c.tsw.WriteLiterally(", ")
|
|
756
|
-
}
|
|
757
|
-
// Check if we should apply clone for value-type semantics
|
|
758
|
-
if shouldApplyClone(c.pkg, r) {
|
|
759
|
-
if err := c.WriteValueExpr(r); err != nil { // RHS is a value
|
|
760
|
-
return err
|
|
761
|
-
}
|
|
762
|
-
c.tsw.WriteLiterally(".clone()")
|
|
763
|
-
} else {
|
|
764
|
-
if err := c.WriteValueExpr(r); err != nil { // RHS is a value
|
|
765
|
-
return err
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
// If the LHS was a single map index, close the mapSet call
|
|
770
|
-
if isMapIndexLHS && len(lhs) == 1 {
|
|
771
|
-
c.tsw.WriteLiterally(")")
|
|
772
|
-
}
|
|
773
|
-
return nil
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
// writeChannelReceiveWithOk handles the val, ok := <-channel assignment pattern.
|
|
777
|
-
func (c *GoToTSCompiler) writeChannelReceiveWithOk(lhs []ast.Expr, unaryExpr *ast.UnaryExpr, tok token.Token) error {
|
|
778
|
-
// Ensure LHS has exactly two expressions
|
|
779
|
-
if len(lhs) != 2 {
|
|
780
|
-
return fmt.Errorf("internal error: writeChannelReceiveWithOk called with %d LHS expressions", len(lhs))
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// Get variable names, handling blank identifiers
|
|
784
|
-
valueIsBlank := false
|
|
785
|
-
okIsBlank := false
|
|
786
|
-
var valueName string
|
|
787
|
-
var okName string
|
|
788
|
-
|
|
789
|
-
if valIdent, ok := lhs[0].(*ast.Ident); ok {
|
|
790
|
-
if valIdent.Name == "_" {
|
|
791
|
-
valueIsBlank = true
|
|
792
|
-
} else {
|
|
793
|
-
valueName = valIdent.Name
|
|
794
|
-
}
|
|
795
|
-
} else {
|
|
796
|
-
return fmt.Errorf("unhandled LHS expression type for value in channel receive: %T", lhs[0])
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
if okIdent, ok := lhs[1].(*ast.Ident); ok {
|
|
800
|
-
if okIdent.Name == "_" {
|
|
801
|
-
okIsBlank = true
|
|
802
|
-
} else {
|
|
803
|
-
okName = okIdent.Name
|
|
804
|
-
}
|
|
805
|
-
} else {
|
|
806
|
-
return fmt.Errorf("unhandled LHS expression type for ok in channel receive: %T", lhs[1])
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// Generate destructuring assignment/declaration for val, ok := <-channel
|
|
810
|
-
keyword := ""
|
|
811
|
-
if tok == token.DEFINE {
|
|
812
|
-
keyword = "const " // Use const for destructuring declaration
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// Build the destructuring pattern, handling blank identifiers correctly for TS
|
|
816
|
-
patternParts := []string{}
|
|
817
|
-
if !valueIsBlank {
|
|
818
|
-
// Map the 'value' field to the Go variable name
|
|
819
|
-
patternParts = append(patternParts, fmt.Sprintf("value: %s", valueName))
|
|
820
|
-
} else {
|
|
821
|
-
// If both are blank, just await the call and return
|
|
822
|
-
if okIsBlank {
|
|
823
|
-
c.tsw.WriteLiterally("await ")
|
|
824
|
-
if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
|
|
825
|
-
return fmt.Errorf("failed to write channel expression in receive: %w", err)
|
|
826
|
-
}
|
|
827
|
-
c.tsw.WriteLiterally(".receiveWithOk()")
|
|
828
|
-
c.tsw.WriteLine("")
|
|
829
|
-
return nil // Nothing to assign
|
|
830
|
-
}
|
|
831
|
-
// Only value is blank, need to map 'ok' property
|
|
832
|
-
patternParts = append(patternParts, fmt.Sprintf("ok: %s", okName))
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
if !okIsBlank && !valueIsBlank { // Both are present
|
|
836
|
-
patternParts = append(patternParts, fmt.Sprintf("ok: %s", okName))
|
|
837
|
-
} else if !okIsBlank && valueIsBlank { // Only ok is present (already handled above)
|
|
838
|
-
// No need to add ok again
|
|
839
|
-
}
|
|
840
|
-
// If both are blank, patternParts remains empty, handled earlier.
|
|
841
|
-
|
|
842
|
-
destructuringPattern := fmt.Sprintf("{ %s }", strings.Join(patternParts, ", "))
|
|
843
|
-
|
|
844
|
-
// Write the destructuring assignment/declaration
|
|
845
|
-
c.tsw.WriteLiterally(keyword) // "const " or ""
|
|
846
|
-
c.tsw.WriteLiterally(destructuringPattern)
|
|
847
|
-
c.tsw.WriteLiterally(" = await ")
|
|
848
|
-
if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
|
|
849
|
-
return fmt.Errorf("failed to write channel expression in receive: %w", err)
|
|
850
|
-
}
|
|
851
|
-
c.tsw.WriteLiterally(".receiveWithOk()")
|
|
852
|
-
c.tsw.WriteLine("")
|
|
853
|
-
|
|
854
|
-
return nil
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
858
|
-
// writeTypeAssertion handles multi-variable assignment from a type assertion.
|
|
859
|
-
|
|
860
|
-
// writeMultiVarAssignFromCall handles multi-variable assignment from a single function call.
|
|
861
|
-
writeMultiVarAssignFromCall := func(lhs []ast.Expr, callExpr *ast.CallExpr, tok token.Token) error {
|
|
862
|
-
// Determine if 'let' or 'const' is needed for :=
|
|
863
|
-
if tok == token.DEFINE {
|
|
864
|
-
// For simplicity, use 'let' for := in multi-variable assignments.
|
|
865
|
-
// More advanced analysis might be needed to determine if const is possible.
|
|
866
|
-
c.tsw.WriteLiterally("let ")
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// Write the left-hand side as a destructuring pattern
|
|
870
|
-
c.tsw.WriteLiterally("[")
|
|
871
|
-
for i, lhsExpr := range lhs {
|
|
872
|
-
if i != 0 {
|
|
873
|
-
c.tsw.WriteLiterally(", ")
|
|
874
|
-
}
|
|
875
|
-
// Write the variable name, omitting '_' for blank identifier
|
|
876
|
-
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" {
|
|
877
|
-
c.WriteIdentValue(ident)
|
|
878
|
-
} else if !ok {
|
|
879
|
-
// Should not happen for valid Go code in this context, but handle defensively
|
|
880
|
-
c.tsw.WriteCommentInline(fmt.Sprintf("unhandled LHS expression in destructuring: %T", lhsExpr))
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
c.tsw.WriteLiterally("] = ")
|
|
884
|
-
|
|
885
|
-
// Write the right-hand side (the function call)
|
|
886
|
-
if err := c.WriteValueExpr(callExpr); err != nil {
|
|
887
|
-
return fmt.Errorf("failed to write RHS call expression in assignment: %w", err)
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
c.tsw.WriteLine("") // Remove semicolon
|
|
891
|
-
return nil
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// writeSingleAssign handles a single assignment pair, including blank identifiers and short declarations.
|
|
895
|
-
writeSingleAssign := func(lhsExpr, rhsExpr ast.Expr, tok token.Token, isLast bool) error {
|
|
896
|
-
// Check for blank identifier on the left-hand side
|
|
897
|
-
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name == "_" {
|
|
898
|
-
// Blank identifier: Skip generating output for this assignment.
|
|
899
|
-
// Note: This assumes RHS side effects are not required in the generated TS.
|
|
900
|
-
return nil
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
// Not a blank identifier: generate an assignment statement.
|
|
904
|
-
|
|
905
|
-
// Handle short variable declaration (:=)
|
|
906
|
-
if tok == token.DEFINE {
|
|
907
|
-
// Use 'let' for the first declaration of a variable.
|
|
908
|
-
// More sophisticated analysis might be needed to determine if it's truly the first declaration
|
|
909
|
-
// in the current scope, but for now, assume := implies a new variable.
|
|
910
|
-
c.tsw.WriteLiterally("let ")
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
// Write the core assignment (LHS = RHS)
|
|
914
|
-
// Pass single-element slices for LHS and RHS to writeAssignmentCore
|
|
915
|
-
if err := c.writeAssignmentCore([]ast.Expr{lhsExpr}, []ast.Expr{rhsExpr}, tok); err != nil {
|
|
916
|
-
return fmt.Errorf("failed to write core assignment: %w", err)
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
// Handle trailing inline comment for this specific assignment line
|
|
920
|
-
// This is more complex with multi-variable assignments. For simplicity,
|
|
921
|
-
// we'll associate any comment immediately following the *entire* Go statement
|
|
922
|
-
// with the *last* generated TypeScript assignment line.
|
|
923
|
-
// A more accurate approach would require mapping comments to specific LHS/RHS pairs.
|
|
924
|
-
if isLast {
|
|
925
|
-
if c.pkg != nil && c.pkg.Fset != nil && exp.End().IsValid() {
|
|
926
|
-
if file := c.pkg.Fset.File(exp.End()); file != nil {
|
|
927
|
-
endLine := file.Line(exp.End())
|
|
928
|
-
// Check comments associated *directly* with the AssignStmt node
|
|
929
|
-
for _, cg := range c.cmap[exp] {
|
|
930
|
-
if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > exp.End() {
|
|
931
|
-
commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
|
|
932
|
-
c.tsw.WriteLiterally(" // " + commentText)
|
|
933
|
-
break // Assume only one inline comment per statement
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
// Write the newline to finish the statement line
|
|
941
|
-
c.tsw.WriteLine("") // Remove semicolon
|
|
942
|
-
return nil
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// writeMapLookupWithExists handles the map comma-ok idiom: value, exists := myMap[key]
|
|
946
|
-
writeMapLookupWithExists := func(lhs []ast.Expr, indexExpr *ast.IndexExpr, tok token.Token) error {
|
|
947
|
-
// First check that we have exactly two LHS expressions (value and exists)
|
|
948
|
-
if len(lhs) != 2 {
|
|
949
|
-
return fmt.Errorf("map comma-ok idiom requires exactly 2 variables on LHS, got %d", len(lhs))
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
// Check for blank identifiers and get variable names
|
|
953
|
-
valueIsBlank := false
|
|
954
|
-
existsIsBlank := false
|
|
955
|
-
var valueName string
|
|
956
|
-
var existsName string
|
|
957
|
-
|
|
958
|
-
if valIdent, ok := lhs[0].(*ast.Ident); ok {
|
|
959
|
-
if valIdent.Name == "_" {
|
|
960
|
-
valueIsBlank = true
|
|
961
|
-
} else {
|
|
962
|
-
valueName = valIdent.Name
|
|
963
|
-
}
|
|
964
|
-
} else {
|
|
965
|
-
return fmt.Errorf("unhandled LHS expression type for value in map comma-ok: %T", lhs[0])
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
if existsIdent, ok := lhs[1].(*ast.Ident); ok {
|
|
969
|
-
if existsIdent.Name == "_" {
|
|
970
|
-
existsIsBlank = true
|
|
971
|
-
} else {
|
|
972
|
-
existsName = existsIdent.Name
|
|
973
|
-
}
|
|
974
|
-
} else {
|
|
975
|
-
return fmt.Errorf("unhandled LHS expression type for exists in map comma-ok: %T", lhs[1])
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
// Declare variables if using := and not blank
|
|
979
|
-
if tok == token.DEFINE {
|
|
980
|
-
if !valueIsBlank {
|
|
981
|
-
c.tsw.WriteLiterally("let ")
|
|
982
|
-
c.tsw.WriteLiterally(valueName)
|
|
983
|
-
c.tsw.WriteLine("")
|
|
984
|
-
}
|
|
985
|
-
if !existsIsBlank {
|
|
986
|
-
c.tsw.WriteLiterally("let ")
|
|
987
|
-
c.tsw.WriteLiterally(existsName)
|
|
988
|
-
c.tsw.WriteLine("")
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
// Assign 'exists'
|
|
993
|
-
if !existsIsBlank {
|
|
994
|
-
c.tsw.WriteLiterally(existsName)
|
|
995
|
-
c.tsw.WriteLiterally(" = ")
|
|
996
|
-
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
997
|
-
return err
|
|
998
|
-
}
|
|
999
|
-
c.tsw.WriteLiterally(".has(")
|
|
1000
|
-
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
1001
|
-
return err
|
|
1002
|
-
}
|
|
1003
|
-
c.tsw.WriteLiterally(")")
|
|
1004
|
-
c.tsw.WriteLine("")
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
// Assign 'value'
|
|
1008
|
-
if !valueIsBlank {
|
|
1009
|
-
c.tsw.WriteLiterally(valueName)
|
|
1010
|
-
c.tsw.WriteLiterally(" = ")
|
|
1011
|
-
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
1012
|
-
return err
|
|
1013
|
-
}
|
|
1014
|
-
c.tsw.WriteLiterally(".get(")
|
|
1015
|
-
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
1016
|
-
return err
|
|
1017
|
-
}
|
|
1018
|
-
c.tsw.WriteLiterally(") ?? ")
|
|
1019
|
-
// Write the zero value for the map's value type
|
|
1020
|
-
if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
|
|
1021
|
-
if mapType, isMap := tv.Type.Underlying().(*gtypes.Map); isMap {
|
|
1022
|
-
c.WriteZeroValueForType(mapType.Elem())
|
|
1023
|
-
} else {
|
|
1024
|
-
// Fallback zero value if type info is missing or not a map
|
|
1025
|
-
c.tsw.WriteLiterally("null")
|
|
1026
|
-
}
|
|
1027
|
-
} else {
|
|
1028
|
-
c.tsw.WriteLiterally("null")
|
|
1029
|
-
}
|
|
1030
|
-
c.tsw.WriteLine("")
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
return nil
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// Handle multi-variable assignment from a single expression.
|
|
1037
|
-
if len(exp.Lhs) > 1 && len(exp.Rhs) == 1 {
|
|
1038
|
-
rhsExpr := exp.Rhs[0]
|
|
1039
|
-
if typeAssertExpr, ok := rhsExpr.(*ast.TypeAssertExpr); ok {
|
|
1040
|
-
return c.writeTypeAssertion(exp.Lhs, typeAssertExpr, exp.Tok)
|
|
1041
|
-
} else if indexExpr, ok := rhsExpr.(*ast.IndexExpr); ok {
|
|
1042
|
-
// Check if this is a map lookup (comma-ok idiom)
|
|
1043
|
-
if len(exp.Lhs) == 2 {
|
|
1044
|
-
// Get the type of the indexed expression
|
|
1045
|
-
if c.pkg != nil && c.pkg.TypesInfo != nil {
|
|
1046
|
-
tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]
|
|
1047
|
-
if ok {
|
|
1048
|
-
// Check if it's a map type
|
|
1049
|
-
if _, isMap := tv.Type.Underlying().(*gtypes.Map); isMap {
|
|
1050
|
-
return writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
} else if unaryExpr, ok := rhsExpr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
|
|
1056
|
-
// Handle val, ok := <-channel
|
|
1057
|
-
if len(exp.Lhs) == 2 {
|
|
1058
|
-
return c.writeChannelReceiveWithOk(exp.Lhs, unaryExpr, exp.Tok)
|
|
1059
|
-
}
|
|
1060
|
-
// If LHS count is not 2, fall through to error or other handling
|
|
1061
|
-
} else if callExpr, ok := rhsExpr.(*ast.CallExpr); ok {
|
|
1062
|
-
return writeMultiVarAssignFromCall(exp.Lhs, callExpr, exp.Tok)
|
|
1063
|
-
}
|
|
1064
|
-
// If none of the specific multi-assign patterns match, fall through to the error check below
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
// Handle all other assignment cases (one-to-one, multiple RHS expressions, etc.)
|
|
1068
|
-
// Ensure LHS and RHS have the same length for valid Go code in these cases
|
|
1069
|
-
if len(exp.Lhs) != len(exp.Rhs) {
|
|
1070
|
-
// Allow single RHS channel receive to be discarded (case <-ch:)
|
|
1071
|
-
if len(exp.Lhs) == 0 && len(exp.Rhs) == 1 {
|
|
1072
|
-
if unaryExpr, ok := exp.Rhs[0].(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
|
|
1073
|
-
// Translate <-ch to await ch.receive() and discard result
|
|
1074
|
-
c.tsw.WriteLiterally("await ")
|
|
1075
|
-
if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
|
|
1076
|
-
return fmt.Errorf("failed to write channel expression in discarded receive: %w", err)
|
|
1077
|
-
}
|
|
1078
|
-
c.tsw.WriteLiterally(".receive()") // Use receive() as receiveWithOk() result isn't needed
|
|
1079
|
-
c.tsw.WriteLine("")
|
|
1080
|
-
return nil
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs)))
|
|
1085
|
-
return fmt.Errorf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs))
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
// Process each assignment pair using the core writer
|
|
1089
|
-
for i := range exp.Lhs {
|
|
1090
|
-
if err := writeSingleAssign(exp.Lhs[i], exp.Rhs[i], exp.Tok, i == len(exp.Lhs)-1); err != nil {
|
|
1091
|
-
return err
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
return nil
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
// Note: delegates to WriteZeroValueForType for unified zero‐value mapping.
|
|
1099
|
-
|
|
1100
|
-
// shouldApplyClone determines if .clone() should be applied to the RHS of an assignment
|
|
1101
|
-
// to emulate Go's value semantics for structs.
|
|
1102
|
-
// This requires type information.
|
|
1103
|
-
func shouldApplyClone(pkg *packages.Package, rhs ast.Expr) bool {
|
|
1104
|
-
if pkg == nil || pkg.TypesInfo == nil {
|
|
1105
|
-
// Cannot determine type without type info, default to no clone
|
|
1106
|
-
return false
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
// Get the type of the RHS expression
|
|
1110
|
-
tv, found := pkg.TypesInfo.Types[rhs]
|
|
1111
|
-
if !found || tv.Type == nil {
|
|
1112
|
-
// Type information not found, default to no clone
|
|
1113
|
-
return false
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
// Optimization: If the RHS is a composite literal itself, we don't need to clone immediately.
|
|
1117
|
-
if _, isCompositeLit := rhs.(*ast.CompositeLit); isCompositeLit {
|
|
1118
|
-
// Check if it's a struct *value* literal (not a pointer like &T{})
|
|
1119
|
-
// We rely on the type check below to confirm it's a struct.
|
|
1120
|
-
// If it IS a struct composite literal, don't clone.
|
|
1121
|
-
// If it's something else (like a slice literal), the type check below will handle it.
|
|
1122
|
-
if _, isStruct := tv.Type.Underlying().(*gtypes.Struct); isStruct {
|
|
1123
|
-
return false // Don't clone direct composite literal initialization
|
|
1124
|
-
}
|
|
1125
|
-
// If it's a composite literal but not for a struct (e.g., slice), proceed with normal type check.
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
// Check if the underlying type is a struct (for non-composite-literal RHS)
|
|
1129
|
-
// Also check if it's a named type whose underlying type is a struct
|
|
1130
|
-
switch t := tv.Type.Underlying().(type) {
|
|
1131
|
-
case *gtypes.Struct:
|
|
1132
|
-
// It's a struct, apply clone
|
|
1133
|
-
return true
|
|
1134
|
-
case *gtypes.Named:
|
|
1135
|
-
// It's a named type, check its underlying type
|
|
1136
|
-
if _, ok := t.Underlying().(*gtypes.Struct); ok {
|
|
1137
|
-
return true
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
// Not a struct, do not apply clone
|
|
1142
|
-
return false
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
// WriteStmtExpr writes an expr statement.
|
|
1146
|
-
func (c *GoToTSCompiler) WriteStmtExpr(exp *ast.ExprStmt) error {
|
|
1147
|
-
// Handle simple channel receive used as a statement (<-ch)
|
|
1148
|
-
if unaryExpr, ok := exp.X.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
|
|
1149
|
-
// Translate <-ch to await ch.receive()
|
|
1150
|
-
c.tsw.WriteLiterally("await ")
|
|
1151
|
-
if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Channel expression
|
|
1152
|
-
return fmt.Errorf("failed to write channel expression in receive statement: %w", err)
|
|
1153
|
-
}
|
|
1154
|
-
c.tsw.WriteLiterally(".receive()") // Use receive() as the value is discarded
|
|
1155
|
-
c.tsw.WriteLine("")
|
|
1156
|
-
return nil
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
// Handle other expression statements
|
|
1160
|
-
if err := c.WriteValueExpr(exp.X); err != nil { // Expression statement evaluates a value
|
|
1161
|
-
return err
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
// Handle potential inline comment for ExprStmt
|
|
1165
|
-
inlineCommentWritten := false
|
|
1166
|
-
if c.pkg != nil && c.pkg.Fset != nil && exp.End().IsValid() {
|
|
1167
|
-
if file := c.pkg.Fset.File(exp.End()); file != nil {
|
|
1168
|
-
endLine := file.Line(exp.End())
|
|
1169
|
-
// Check comments associated *directly* with the ExprStmt node
|
|
1170
|
-
for _, cg := range c.cmap[exp] {
|
|
1171
|
-
if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > exp.End() {
|
|
1172
|
-
commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
|
|
1173
|
-
c.tsw.WriteLiterally(" // " + commentText)
|
|
1174
|
-
inlineCommentWritten = true
|
|
1175
|
-
break
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
// Also check comments associated with the underlying expression X
|
|
1179
|
-
// This might be necessary if the comment map links it to X instead of ExprStmt
|
|
1180
|
-
if !inlineCommentWritten {
|
|
1181
|
-
for _, cg := range c.cmap[exp.X] {
|
|
1182
|
-
if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > exp.End() {
|
|
1183
|
-
commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
|
|
1184
|
-
c.tsw.WriteLiterally(" // " + commentText)
|
|
1185
|
-
inlineCommentWritten = true //nolint:ineffassign
|
|
1186
|
-
break
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
// Add semicolon according to design doc (omit semicolons) - REMOVED semicolon
|
|
1194
|
-
c.tsw.WriteLine("") // Finish with a newline
|
|
1195
|
-
return nil
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
// WriteStmtFor writes a for statement.
|
|
1199
|
-
func (c *GoToTSCompiler) WriteStmtFor(exp *ast.ForStmt) error {
|
|
1200
|
-
c.tsw.WriteLiterally("for (")
|
|
1201
|
-
if exp.Init != nil {
|
|
1202
|
-
if err := c.WriteForInit(exp.Init); err != nil { // Use WriteForInit
|
|
1203
|
-
return fmt.Errorf("failed to write for loop initialization: %w", err)
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
c.tsw.WriteLiterally("; ")
|
|
1207
|
-
if exp.Cond != nil {
|
|
1208
|
-
if err := c.WriteValueExpr(exp.Cond); err != nil { // Condition is a value
|
|
1209
|
-
return fmt.Errorf("failed to write for loop condition: %w", err)
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
c.tsw.WriteLiterally("; ")
|
|
1213
|
-
if exp.Post != nil {
|
|
1214
|
-
if err := c.WriteForPost(exp.Post); err != nil { // Use WriteForPost
|
|
1215
|
-
return fmt.Errorf("failed to write for loop post statement: %w", err)
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
c.tsw.WriteLiterally(") ")
|
|
1219
|
-
if err := c.WriteStmtBlock(exp.Body, false); err != nil {
|
|
1220
|
-
return fmt.Errorf("failed to write for loop body: %w", err)
|
|
1221
|
-
}
|
|
1222
|
-
return nil
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
// WriteForInit writes the initialization part of a for loop header.
|
|
1226
|
-
func (c *GoToTSCompiler) WriteForInit(stmt ast.Stmt) error {
|
|
1227
|
-
switch s := stmt.(type) {
|
|
1228
|
-
case *ast.AssignStmt:
|
|
1229
|
-
// Handle assignment in init (e.g., i := 0 or i = 0)
|
|
1230
|
-
// Need to handle 'let' for :=
|
|
1231
|
-
if s.Tok == token.DEFINE {
|
|
1232
|
-
c.tsw.WriteLiterally("let ")
|
|
1233
|
-
}
|
|
1234
|
-
// Write the core assignment without trailing syntax
|
|
1235
|
-
// Blank identifiers should already be handled by filterBlankIdentifiers if needed,
|
|
1236
|
-
// but for init statements they are less common. Let's assume simple assignments for now.
|
|
1237
|
-
if err := c.writeAssignmentCore(s.Lhs, s.Rhs, s.Tok); err != nil {
|
|
1238
|
-
return err
|
|
1239
|
-
}
|
|
1240
|
-
case *ast.ExprStmt:
|
|
1241
|
-
// Handle expression statement in init (less common, but possible)
|
|
1242
|
-
if err := c.WriteValueExpr(s.X); err != nil {
|
|
1243
|
-
return err
|
|
1244
|
-
}
|
|
1245
|
-
default:
|
|
1246
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unhandled for loop init statement: %T", stmt))
|
|
1247
|
-
}
|
|
1248
|
-
return nil
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
// WriteForPost writes the post part of a for loop header.
|
|
1252
|
-
func (c *GoToTSCompiler) WriteForPost(stmt ast.Stmt) error {
|
|
1253
|
-
switch s := stmt.(type) {
|
|
1254
|
-
case *ast.IncDecStmt:
|
|
1255
|
-
// Handle increment/decrement (e.g., i++)
|
|
1256
|
-
if err := c.WriteValueExpr(s.X); err != nil { // The expression (e.g., i)
|
|
1257
|
-
return err
|
|
1258
|
-
}
|
|
1259
|
-
tokStr, ok := gstypes.TokenToTs(s.Tok)
|
|
1260
|
-
if !ok {
|
|
1261
|
-
c.tsw.WriteLiterally("/* unknown incdec token */")
|
|
1262
|
-
} else {
|
|
1263
|
-
c.tsw.WriteLiterally(tokStr) // The token (e.g., ++)
|
|
1264
|
-
}
|
|
1265
|
-
case *ast.AssignStmt:
|
|
1266
|
-
// Handle assignment in post (e.g., i = i + 1)
|
|
1267
|
-
// No 'let' keyword here
|
|
1268
|
-
// Blank identifiers should already be handled by filterBlankIdentifiers if needed.
|
|
1269
|
-
if err := c.writeAssignmentCore(s.Lhs, s.Rhs, s.Tok); err != nil {
|
|
1270
|
-
return err
|
|
1271
|
-
}
|
|
1272
|
-
case *ast.ExprStmt:
|
|
1273
|
-
// Handle expression statement in post (less common)
|
|
1274
|
-
if err := c.WriteValueExpr(s.X); err != nil {
|
|
1275
|
-
return err
|
|
1276
|
-
}
|
|
1277
|
-
default:
|
|
1278
|
-
c.tsw.WriteCommentLine(fmt.Sprintf("unhandled for loop post statement: %T", stmt))
|
|
1279
|
-
}
|
|
1280
|
-
return nil
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
// WriteZeroValue writes the TypeScript zero‐value for a Go type.
|
|
1284
|
-
func (c *GoToTSCompiler) WriteZeroValue(expr ast.Expr) {
|
|
1285
|
-
switch t := expr.(type) {
|
|
1286
|
-
case *ast.Ident:
|
|
1287
|
-
// Try to resolve identifier type
|
|
1288
|
-
if tv, found := c.pkg.TypesInfo.Types[t]; found {
|
|
1289
|
-
underlying := tv.Type.Underlying()
|
|
1290
|
-
switch u := underlying.(type) {
|
|
1291
|
-
case *gtypes.Basic: // Use gotypes alias
|
|
1292
|
-
if u.Info()>ypes.IsNumeric != 0 { // Use gotypes alias
|
|
1293
|
-
c.tsw.WriteLiterally("0")
|
|
1294
|
-
} else if u.Info()>ypes.IsString != 0 { // Use gotypes alias
|
|
1295
|
-
c.tsw.WriteLiterally(`""`)
|
|
1296
|
-
} else if u.Info()>ypes.IsBoolean != 0 { // Use gotypes alias
|
|
1297
|
-
c.tsw.WriteLiterally("false")
|
|
1298
|
-
} else {
|
|
1299
|
-
c.tsw.WriteLiterally("null // unknown basic type")
|
|
1300
|
-
}
|
|
1301
|
-
case *gtypes.Struct: // Use gotypes alias
|
|
1302
|
-
// Zero value for struct is new instance
|
|
1303
|
-
c.tsw.WriteLiterally("new ")
|
|
1304
|
-
c.WriteTypeExpr(t) // Write the type name
|
|
1305
|
-
c.tsw.WriteLiterally("()")
|
|
1306
|
-
case *gtypes.Pointer, *gtypes.Interface, *gtypes.Slice, *gtypes.Map, *gtypes.Chan, *gtypes.Signature: // Use gotypes alias
|
|
1307
|
-
// Pointers, interfaces, slices, maps, channels, functions zero value is null/undefined
|
|
1308
|
-
c.tsw.WriteLiterally("null")
|
|
1309
|
-
default:
|
|
1310
|
-
c.tsw.WriteLiterally("null // unknown underlying type")
|
|
1311
|
-
}
|
|
1312
|
-
} else {
|
|
1313
|
-
// Fallback for unresolved identifiers (basic types)
|
|
1314
|
-
switch t.Name {
|
|
1315
|
-
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "float32", "float64", "complex64", "complex128":
|
|
1316
|
-
c.tsw.WriteLiterally("0")
|
|
1317
|
-
case "string":
|
|
1318
|
-
c.tsw.WriteLiterally(`""`)
|
|
1319
|
-
case "bool":
|
|
1320
|
-
c.tsw.WriteLiterally("false")
|
|
1321
|
-
default:
|
|
1322
|
-
// Assume custom type, might be struct or interface -> null
|
|
1323
|
-
c.tsw.WriteLiterally("null // unresolved identifier")
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
case *ast.StarExpr, *ast.InterfaceType, *ast.ArrayType, *ast.MapType, *ast.ChanType, *ast.FuncType:
|
|
1327
|
-
// Pointers, interfaces, arrays, maps, channels, functions zero value is null/undefined
|
|
1328
|
-
c.tsw.WriteLiterally("null")
|
|
1329
|
-
case *ast.StructType:
|
|
1330
|
-
// Anonymous struct zero value is complex, default to null for now
|
|
1331
|
-
c.tsw.WriteLiterally("null")
|
|
1332
|
-
default:
|
|
1333
|
-
// everything else defaults to null in TS
|
|
1334
|
-
c.tsw.WriteLiterally("null")
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
// WriteStmtRange writes a for…range loop by generating equivalent TypeScript code.
|
|
1339
|
-
func (c *GoToTSCompiler) WriteStmtRange(exp *ast.RangeStmt) error {
|
|
1340
|
-
// Get the type of the iterable expression
|
|
1341
|
-
iterType := c.pkg.TypesInfo.TypeOf(exp.X)
|
|
1342
|
-
underlying := iterType.Underlying()
|
|
1343
|
-
|
|
1344
|
-
// Handle map types
|
|
1345
|
-
if _, ok := underlying.(*gtypes.Map); ok {
|
|
1346
|
-
// Use for-of with entries() for proper Map iteration
|
|
1347
|
-
c.tsw.WriteLiterally("for (const [k, v] of ")
|
|
1348
|
-
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
1349
|
-
return fmt.Errorf("failed to write range loop map expression: %w", err)
|
|
1350
|
-
}
|
|
1351
|
-
c.tsw.WriteLiterally(".entries()) {")
|
|
1352
|
-
c.tsw.Indent(1)
|
|
1353
|
-
c.tsw.WriteLine("")
|
|
1354
|
-
// If a key variable is provided and is not blank, declare it as a constant
|
|
1355
|
-
if exp.Key != nil {
|
|
1356
|
-
if ident, ok := exp.Key.(*ast.Ident); ok && ident.Name != "_" {
|
|
1357
|
-
c.tsw.WriteLiterally("const ")
|
|
1358
|
-
// WriteIdentValue does not currently return an error, assuming it's safe for now.
|
|
1359
|
-
c.WriteIdentValue(ident)
|
|
1360
|
-
c.tsw.WriteLiterally(" = k")
|
|
1361
|
-
c.tsw.WriteLine("")
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
// If a value variable is provided and is not blank, use the value from entries()
|
|
1365
|
-
if exp.Value != nil {
|
|
1366
|
-
if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
|
|
1367
|
-
c.tsw.WriteLiterally("const ")
|
|
1368
|
-
// WriteIdentValue does not currently return an error, assuming it's safe for now.
|
|
1369
|
-
c.WriteIdentValue(ident)
|
|
1370
|
-
c.tsw.WriteLiterally(" = v")
|
|
1371
|
-
c.tsw.WriteLine("")
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
// Write the loop body
|
|
1375
|
-
if err := c.WriteStmtBlock(exp.Body, false); err != nil {
|
|
1376
|
-
return fmt.Errorf("failed to write range loop map body: %w", err)
|
|
1377
|
-
}
|
|
1378
|
-
c.tsw.Indent(-1)
|
|
1379
|
-
c.tsw.WriteLine("}")
|
|
1380
|
-
return nil
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
// Handle string type by converting the string to a rune array
|
|
1384
|
-
if basic, ok := underlying.(*gtypes.Basic); ok && (basic.Info()>ypes.IsString != 0) {
|
|
1385
|
-
// Convert the string to runes using goscript.stringToRunes
|
|
1386
|
-
c.tsw.WriteLiterally("const _runes = goscript.stringToRunes(")
|
|
1387
|
-
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
1388
|
-
return fmt.Errorf("failed to write range loop string conversion expression: %w", err)
|
|
1389
|
-
}
|
|
1390
|
-
c.tsw.WriteLiterally(")")
|
|
1391
|
-
c.tsw.WriteLine("")
|
|
1392
|
-
// Determine the index variable name for the generated loop
|
|
1393
|
-
indexVarName := "i" // Default name
|
|
1394
|
-
if exp.Key != nil {
|
|
1395
|
-
if keyIdent, ok := exp.Key.(*ast.Ident); ok && keyIdent.Name != "_" {
|
|
1396
|
-
indexVarName = keyIdent.Name
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
c.tsw.WriteLiterally(fmt.Sprintf("for (let %s = 0; %s < _runes.length; %s++) {", indexVarName, indexVarName, indexVarName))
|
|
1400
|
-
c.tsw.Indent(1)
|
|
1401
|
-
c.tsw.WriteLine("")
|
|
1402
|
-
// Declare value if provided and not blank
|
|
1403
|
-
if exp.Value != nil {
|
|
1404
|
-
if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
|
|
1405
|
-
c.tsw.WriteLiterally("const ")
|
|
1406
|
-
// WriteIdentValue does not currently return an error, assuming it's safe for now.
|
|
1407
|
-
c.WriteIdentValue(ident)
|
|
1408
|
-
c.tsw.WriteLiterally(" = _runes[i]")
|
|
1409
|
-
c.tsw.WriteLine("")
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
if err := c.WriteStmtBlock(exp.Body, false); err != nil {
|
|
1413
|
-
return fmt.Errorf("failed to write range loop string body: %w", err)
|
|
1414
|
-
}
|
|
1415
|
-
c.tsw.Indent(-1)
|
|
1416
|
-
c.tsw.WriteLine("}")
|
|
1417
|
-
return nil
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
// Handle array and slice types
|
|
1421
|
-
if _, isArray := underlying.(*gtypes.Array); isArray || isSlice(underlying) {
|
|
1422
|
-
// Determine the index variable name for the generated loop
|
|
1423
|
-
indexVarName := "i" // Default name
|
|
1424
|
-
if exp.Key != nil {
|
|
1425
|
-
if keyIdent, ok := exp.Key.(*ast.Ident); ok && keyIdent.Name != "_" {
|
|
1426
|
-
indexVarName = keyIdent.Name
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
// If both key and value are provided, use an index loop and assign both
|
|
1430
|
-
if exp.Key != nil && exp.Value != nil {
|
|
1431
|
-
c.tsw.WriteLiterally(fmt.Sprintf("for (let %s = 0; %s < ", indexVarName, indexVarName))
|
|
1432
|
-
if err := c.WriteValueExpr(exp.X); err != nil { // Write the expression for the iterable
|
|
1433
|
-
return fmt.Errorf("failed to write range loop array/slice expression (key and value): %w", err)
|
|
1434
|
-
}
|
|
1435
|
-
c.tsw.WriteLiterally(fmt.Sprintf(".length; %s++) {", indexVarName))
|
|
1436
|
-
c.tsw.Indent(1)
|
|
1437
|
-
c.tsw.WriteLine("")
|
|
1438
|
-
// Declare value if not blank
|
|
1439
|
-
if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
|
|
1440
|
-
c.tsw.WriteLiterally("const ")
|
|
1441
|
-
// WriteIdentValue does not currently return an error, assuming it's safe for now.
|
|
1442
|
-
c.WriteIdentValue(ident)
|
|
1443
|
-
c.tsw.WriteLiterally(" = ")
|
|
1444
|
-
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
1445
|
-
return fmt.Errorf("failed to write range loop array/slice value expression: %w", err)
|
|
1446
|
-
}
|
|
1447
|
-
c.tsw.WriteLiterally(fmt.Sprintf("[%s]", indexVarName)) // Use indexVarName
|
|
1448
|
-
c.tsw.WriteLine("")
|
|
1449
|
-
}
|
|
1450
|
-
if err := c.WriteStmtBlock(exp.Body, false); err != nil {
|
|
1451
|
-
return fmt.Errorf("failed to write range loop array/slice body (key and value): %w", err)
|
|
1452
|
-
}
|
|
1453
|
-
c.tsw.Indent(-1)
|
|
1454
|
-
c.tsw.WriteLine("}")
|
|
1455
|
-
return nil
|
|
1456
|
-
} else if exp.Key != nil && exp.Value == nil { // Only key provided
|
|
1457
|
-
c.tsw.WriteLiterally(fmt.Sprintf("for (let %s = 0; %s < ", indexVarName, indexVarName))
|
|
1458
|
-
// Write the expression for the iterable
|
|
1459
|
-
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
1460
|
-
return fmt.Errorf("failed to write expression for the iterable: %w", err)
|
|
1461
|
-
}
|
|
1462
|
-
c.tsw.WriteLiterally(fmt.Sprintf(".length; %s++) {", indexVarName))
|
|
1463
|
-
c.tsw.Indent(1)
|
|
1464
|
-
c.tsw.WriteLine("")
|
|
1465
|
-
if err := c.WriteStmtBlock(exp.Body, false); err != nil {
|
|
1466
|
-
return fmt.Errorf("failed to write range loop array/slice body (only key): %w", err)
|
|
1467
|
-
}
|
|
1468
|
-
c.tsw.Indent(-1)
|
|
1469
|
-
c.tsw.WriteLine("}")
|
|
1470
|
-
return nil
|
|
1471
|
-
} else if exp.Key == nil && exp.Value != nil { // Only value provided; use for-of loop
|
|
1472
|
-
c.tsw.WriteLiterally("for (const v of ")
|
|
1473
|
-
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
1474
|
-
return fmt.Errorf("failed to write range loop array/slice expression (only value): %w", err)
|
|
1475
|
-
}
|
|
1476
|
-
c.tsw.WriteLiterally(") {")
|
|
1477
|
-
c.tsw.Indent(1)
|
|
1478
|
-
c.tsw.WriteLine("")
|
|
1479
|
-
if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
|
|
1480
|
-
c.tsw.WriteLiterally("const ")
|
|
1481
|
-
// WriteIdentValue does not currently return an error, assuming it's safe for now.
|
|
1482
|
-
c.WriteIdentValue(ident)
|
|
1483
|
-
c.tsw.WriteLiterally(" = v")
|
|
1484
|
-
c.tsw.WriteLine("")
|
|
1485
|
-
}
|
|
1486
|
-
if err := c.WriteStmtBlock(exp.Body, false); err != nil {
|
|
1487
|
-
return fmt.Errorf("failed to write range loop array/slice body (only value): %w", err)
|
|
1488
|
-
}
|
|
1489
|
-
c.tsw.Indent(-1)
|
|
1490
|
-
c.tsw.WriteLine("}")
|
|
1491
|
-
return nil
|
|
1492
|
-
} else {
|
|
1493
|
-
// Fallback: simple index loop without declaring range variables, use _i
|
|
1494
|
-
indexVarName := "_i"
|
|
1495
|
-
c.tsw.WriteLiterally(fmt.Sprintf("for (let %s = 0; %s < ", indexVarName, indexVarName))
|
|
1496
|
-
if err := c.WriteValueExpr(exp.X); err != nil {
|
|
1497
|
-
return fmt.Errorf("failed to write range loop array/slice length expression (fallback): %w", err)
|
|
1498
|
-
}
|
|
1499
|
-
c.tsw.WriteLiterally(fmt.Sprintf(".length; %s++) {", indexVarName))
|
|
1500
|
-
c.tsw.Indent(1)
|
|
1501
|
-
c.tsw.WriteLine("")
|
|
1502
|
-
if err := c.WriteStmtBlock(exp.Body, false); err != nil {
|
|
1503
|
-
return fmt.Errorf("failed to write range loop array/slice body (fallback): %w", err)
|
|
1504
|
-
}
|
|
1505
|
-
c.tsw.Indent(-1)
|
|
1506
|
-
c.tsw.WriteLine("}")
|
|
1507
|
-
return nil
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
// Fallback case if the ranged type is not supported.
|
|
1512
|
-
c.tsw.WriteCommentLine("unsupported range loop")
|
|
1513
|
-
return fmt.Errorf("unsupported range loop type: %T", underlying)
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
// WriteStmtSend writes a channel send statement (ch <- value).
|
|
1517
|
-
func (c *GoToTSCompiler) WriteStmtSend(exp *ast.SendStmt) error {
|
|
1518
|
-
// Translate ch <- value to await ch.send(value)
|
|
1519
|
-
c.tsw.WriteLiterally("await ")
|
|
1520
|
-
if err := c.WriteValueExpr(exp.Chan); err != nil { // The channel expression
|
|
1521
|
-
return fmt.Errorf("failed to write channel expression in send statement: %w", err)
|
|
1522
|
-
}
|
|
1523
|
-
c.tsw.WriteLiterally(".send(")
|
|
1524
|
-
if err := c.WriteValueExpr(exp.Value); err != nil { // The value expression
|
|
1525
|
-
return fmt.Errorf("failed to write value expression in send statement: %w", err)
|
|
1526
|
-
}
|
|
1527
|
-
c.tsw.WriteLiterally(")")
|
|
1528
|
-
c.tsw.WriteLine("") // Add newline after the statement
|
|
1529
|
-
return nil
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
// isSlice returns true if the underlying type is a slice.
|
|
1533
|
-
func isSlice(typ gtypes.Type) bool {
|
|
1534
|
-
_, ok := typ.(*gtypes.Slice)
|
|
1535
|
-
return ok
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
// writeTypeAssertion handles multi-variable assignment from a type assertion.
|
|
1539
|
-
func (c *GoToTSCompiler) writeTypeAssertion(lhs []ast.Expr, typeAssertExpr *ast.TypeAssertExpr, tok token.Token) error {
|
|
1540
|
-
interfaceExpr := typeAssertExpr.X
|
|
1541
|
-
assertedType := typeAssertExpr.Type
|
|
1542
|
-
|
|
1543
|
-
// Ensure LHS has exactly two expressions (value and ok)
|
|
1544
|
-
if len(lhs) != 2 {
|
|
1545
|
-
return fmt.Errorf("type assertion assignment requires exactly 2 variables on LHS, got %d", len(lhs))
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
// Get variable names, handling blank identifiers
|
|
1549
|
-
valueIsBlank := false
|
|
1550
|
-
okIsBlank := false
|
|
1551
|
-
var valueName string
|
|
1552
|
-
var okName string
|
|
1553
|
-
|
|
1554
|
-
if valIdent, ok := lhs[0].(*ast.Ident); ok {
|
|
1555
|
-
if valIdent.Name == "_" {
|
|
1556
|
-
valueIsBlank = true
|
|
1557
|
-
} else {
|
|
1558
|
-
valueName = valIdent.Name
|
|
1559
|
-
}
|
|
1560
|
-
} else {
|
|
1561
|
-
return fmt.Errorf("unhandled LHS expression type for value in type assertion: %T", lhs[0])
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
if okIdent, ok := lhs[1].(*ast.Ident); ok {
|
|
1565
|
-
if okIdent.Name == "_" {
|
|
1566
|
-
okIsBlank = true
|
|
1567
|
-
} else {
|
|
1568
|
-
okName = okIdent.Name
|
|
1569
|
-
}
|
|
1570
|
-
} else {
|
|
1571
|
-
return fmt.Errorf("unhandled LHS expression type for ok in type assertion: %T", lhs[1])
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
// Get the type name string for the asserted type
|
|
1575
|
-
typeName := c.getTypeNameString(assertedType)
|
|
1576
|
-
|
|
1577
|
-
// Generate the destructuring assignment
|
|
1578
|
-
if tok == token.DEFINE {
|
|
1579
|
-
c.tsw.WriteLiterally("let ")
|
|
1580
|
-
} else {
|
|
1581
|
-
// We must wrap in parenthesis.
|
|
1582
|
-
c.tsw.WriteLiterally("(")
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
c.tsw.WriteLiterally("{ ")
|
|
1586
|
-
// Dynamically build the destructuring pattern
|
|
1587
|
-
parts := []string{}
|
|
1588
|
-
if !valueIsBlank {
|
|
1589
|
-
parts = append(parts, fmt.Sprintf("value: %s", valueName))
|
|
1590
|
-
}
|
|
1591
|
-
if !okIsBlank {
|
|
1592
|
-
parts = append(parts, fmt.Sprintf("ok: %s", okName))
|
|
1593
|
-
}
|
|
1594
|
-
c.tsw.WriteLiterally(strings.Join(parts, ", "))
|
|
1595
|
-
c.tsw.WriteLiterally(" } = goscript.typeAssert<")
|
|
1596
|
-
|
|
1597
|
-
// Write the asserted type for the generic
|
|
1598
|
-
c.WriteTypeExpr(assertedType)
|
|
1599
|
-
c.tsw.WriteLiterally(">(")
|
|
1600
|
-
|
|
1601
|
-
// Write the interface expression
|
|
1602
|
-
if err := c.WriteValueExpr(interfaceExpr); err != nil {
|
|
1603
|
-
return fmt.Errorf("failed to write interface expression in type assertion call: %w", err)
|
|
1604
|
-
}
|
|
1605
|
-
c.tsw.WriteLiterally(", ")
|
|
1606
|
-
c.tsw.WriteLiterally(fmt.Sprintf("'%s'", typeName))
|
|
1607
|
-
c.tsw.WriteLiterally(")")
|
|
1608
|
-
|
|
1609
|
-
if tok != token.DEFINE {
|
|
1610
|
-
c.tsw.WriteLiterally(")")
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
c.tsw.WriteLine("") // Add newline after the statement
|
|
1614
|
-
|
|
1615
|
-
return nil
|
|
1616
|
-
}
|