goscript 0.0.21 → 0.0.22

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.
@@ -0,0 +1,178 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ "go/token"
7
+
8
+ "github.com/pkg/errors"
9
+ )
10
+
11
+ // WriteStmtFor translates a Go `for` statement (`ast.ForStmt`) into a
12
+ // TypeScript `for` loop.
13
+ // The structure is `for (init_ts; cond_ts; post_ts) { body_ts }`.
14
+ // - The initialization part (`exp.Init`) is translated using `WriteStmtForInit`.
15
+ // - The condition part (`exp.Cond`) is translated using `WriteValueExpr`. If nil,
16
+ // the condition part in TypeScript is empty (resulting in an infinite loop
17
+ // unless broken out of).
18
+ // - The post-iteration part (`exp.Post`) is translated using `WriteStmtForPost`.
19
+ // - The loop body (`exp.Body`) is translated as a block statement using `WriteStmtBlock`.
20
+ //
21
+ // This function covers standard Go `for` loops (three-part loops, condition-only
22
+ // loops, and infinite loops). `for...range` loops are handled by `WriteStmtRange`.
23
+ func (c *GoToTSCompiler) WriteStmtFor(exp *ast.ForStmt) error {
24
+ c.tsw.WriteLiterally("for (")
25
+ if exp.Init != nil {
26
+ if err := c.WriteStmtForInit(exp.Init); err != nil { // Use WriteStmtForInit
27
+ return fmt.Errorf("failed to write for loop initialization: %w", err)
28
+ }
29
+ }
30
+ c.tsw.WriteLiterally("; ")
31
+ if exp.Cond != nil {
32
+ if err := c.WriteValueExpr(exp.Cond); err != nil { // Condition is a value
33
+ return fmt.Errorf("failed to write for loop condition: %w", err)
34
+ }
35
+ }
36
+ c.tsw.WriteLiterally("; ")
37
+ if exp.Post != nil {
38
+ if err := c.WriteStmtForPost(exp.Post); err != nil { // Use WriteStmtForPost
39
+ return fmt.Errorf("failed to write for loop post statement: %w", err)
40
+ }
41
+ }
42
+ c.tsw.WriteLiterally(") ")
43
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
44
+ return fmt.Errorf("failed to write for loop body: %w", err)
45
+ }
46
+ return nil
47
+ }
48
+
49
+ // WriteStmtForInit translates the initialization part of a Go `for` loop header
50
+ // (e.g., `i := 0` or `i = 0` in `for i := 0; ...`) into its TypeScript equivalent.
51
+ // - If `stmt` is an `ast.AssignStmt`:
52
+ // - For short variable declarations (`:=`) with multiple variables (e.g., `i, j := 0, 10`),
53
+ // it generates `let i = 0, j = 10`. Each LHS variable is paired with its
54
+ // corresponding RHS value; if RHS values are insufficient, remaining LHS
55
+ // variables are initialized with their zero value using `WriteZeroValue`.
56
+ // - For other assignments (single variable `:=`, or regular `=`), it uses
57
+ // `writeAssignmentCore`. If it's `:=`, `let` is prepended.
58
+ // - If `stmt` is an `ast.ExprStmt` (less common in `for` inits), it translates
59
+ // the expression using `WriteValueExpr`.
60
+ //
61
+ // Unhandled statement types in the init part result in a comment.
62
+ func (c *GoToTSCompiler) WriteStmtForInit(stmt ast.Stmt) error {
63
+ switch s := stmt.(type) {
64
+ case *ast.AssignStmt:
65
+ // Handle assignment in init (e.g., i := 0 or i = 0)
66
+ // For TypeScript for-loop init, we need to handle multi-variable declarations differently
67
+ if s.Tok == token.DEFINE && len(s.Lhs) > 1 && len(s.Rhs) > 0 {
68
+ // For loop initialization with multiple variables (e.g., let i = 0, j = 10)
69
+ c.tsw.WriteLiterally("let ")
70
+
71
+ // Handle each LHS variable with its corresponding RHS value
72
+ for i, lhs := range s.Lhs {
73
+ if i > 0 {
74
+ c.tsw.WriteLiterally(", ")
75
+ }
76
+
77
+ // Write the LHS variable name
78
+ if err := c.WriteValueExpr(lhs); err != nil {
79
+ return err
80
+ }
81
+
82
+ // Write the corresponding RHS, or a default if not enough RHS values
83
+ c.tsw.WriteLiterally(" = ")
84
+ if i < len(s.Rhs) {
85
+ // If there's a corresponding RHS value
86
+ if err := c.WriteValueExpr(s.Rhs[i]); err != nil {
87
+ return err
88
+ }
89
+ } else {
90
+ // No corresponding RHS
91
+ return errors.Errorf("no corresponding rhs to lhs: %v", s)
92
+ }
93
+ }
94
+ } else {
95
+ // Regular single variable or assignment (not declaration)
96
+ if s.Tok == token.DEFINE {
97
+ c.tsw.WriteLiterally("let ")
98
+ }
99
+ // Use existing assignment core logic
100
+ if err := c.writeAssignmentCore(s.Lhs, s.Rhs, s.Tok, false); err != nil {
101
+ return err
102
+ }
103
+ }
104
+ return nil
105
+ case *ast.ExprStmt:
106
+ // Handle expression statement in init
107
+ return c.WriteValueExpr(s.X)
108
+ default:
109
+ return errors.Errorf("unhandled for loop init statement: %T", stmt)
110
+ }
111
+ }
112
+
113
+ // WriteStmtForPost translates the post-iteration part of a Go `for` loop header
114
+ // (e.g., `i++` or `i, j = i+1, j-1` in `for ...; i++`) into its TypeScript
115
+ // equivalent.
116
+ // - If `stmt` is an `ast.IncDecStmt` (e.g., `i++`), it writes `i_ts++`.
117
+ // - If `stmt` is an `ast.AssignStmt`:
118
+ // - For multiple variable assignments (e.g., `i, j = i+1, j-1`), it generates
119
+ // TypeScript array destructuring: `[i_ts, j_ts] = [i_ts+1, j_ts-1]`.
120
+ // - For single variable assignments, it uses `writeAssignmentCore`.
121
+ // - If `stmt` is an `ast.ExprStmt` (less common), it translates the expression
122
+ // using `WriteValueExpr`.
123
+ //
124
+ // Unhandled statement types in the post part result in a comment.
125
+ func (c *GoToTSCompiler) WriteStmtForPost(stmt ast.Stmt) error {
126
+ switch s := stmt.(type) {
127
+ case *ast.IncDecStmt:
128
+ // Handle increment/decrement (e.g., i++)
129
+ if err := c.WriteValueExpr(s.X); err != nil { // The expression (e.g., i)
130
+ return err
131
+ }
132
+ tokStr, ok := TokenToTs(s.Tok)
133
+ if !ok {
134
+ return errors.Errorf("unknown incdec token: %v", s.Tok)
135
+ }
136
+ c.tsw.WriteLiterally(tokStr) // The token (e.g., ++)
137
+ return nil
138
+ case *ast.AssignStmt:
139
+ // For multiple variable assignment in post like i, j = i+1, j-1
140
+ // we need to use destructuring in TypeScript like [i, j] = [i+1, j-1]
141
+ if len(s.Lhs) > 1 && len(s.Rhs) > 0 {
142
+ // Write LHS as array destructuring
143
+ c.tsw.WriteLiterally("[")
144
+ for i, lhs := range s.Lhs {
145
+ if i > 0 {
146
+ c.tsw.WriteLiterally(", ")
147
+ }
148
+ if err := c.WriteValueExpr(lhs); err != nil {
149
+ return err
150
+ }
151
+ }
152
+ c.tsw.WriteLiterally("] = [")
153
+
154
+ // Write RHS as array
155
+ for i, rhs := range s.Rhs {
156
+ if i > 0 {
157
+ c.tsw.WriteLiterally(", ")
158
+ }
159
+ if err := c.WriteValueExpr(rhs); err != nil {
160
+ return err
161
+ }
162
+ }
163
+ c.tsw.WriteLiterally("]")
164
+ } else {
165
+ // Regular single variable assignment
166
+ // No declaration handling needed in for loop post statements
167
+ if err := c.writeAssignmentCore(s.Lhs, s.Rhs, s.Tok, false); err != nil {
168
+ return err
169
+ }
170
+ }
171
+ return nil
172
+ case *ast.ExprStmt:
173
+ // Handle expression statement in post
174
+ return c.WriteValueExpr(s.X)
175
+ default:
176
+ return errors.Errorf("unhandled for loop post statement: %T", stmt)
177
+ }
178
+ }
@@ -0,0 +1,237 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ "go/types"
7
+
8
+ "github.com/pkg/errors"
9
+ )
10
+
11
+ // WriteStmtRange translates a Go `for...range` statement (`ast.RangeStmt`)
12
+ // into an equivalent TypeScript loop. The translation depends on the type of
13
+ // the expression being ranged over (`exp.X`), determined using `go/types` info.
14
+ //
15
+ // - **Maps (`*types.Map`):**
16
+ // `for k, v := range myMap` becomes `for (const [k_ts, v_ts] of myMap_ts.entries()) { const k = k_ts; const v = v_ts; ...body... }`.
17
+ // If only `k` or `v` (or neither) is used, the corresponding TypeScript const declaration is adjusted.
18
+ //
19
+ // - **Strings (`*types.Basic` with `IsString` info):**
20
+ // `for i, r := range myString` becomes:
21
+ // `const _runes = $.stringToRunes(myString_ts);`
22
+ // `for (let i_ts = 0; i_ts < _runes.length; i_ts++) { const r_ts = _runes[i_ts]; ...body... }`.
23
+ // The index variable `i_ts` uses the Go key variable name if provided (and not `_`).
24
+ // The rune variable `r_ts` uses the Go value variable name.
25
+ //
26
+ // - **Integers (`*types.Basic` with `IsInteger` info, Go 1.22+):**
27
+ // `for i := range N` becomes `for (let i_ts = 0; i_ts < N_ts; i_ts++) { ...body... }`.
28
+ // `for i, v := range N` becomes `for (let i_ts = 0; i_ts < N_ts; i_ts++) { const v_ts = i_ts; ...body... }`.
29
+ //
30
+ // - **Arrays (`*types.Array`) and Slices (`*types.Slice`):**
31
+ // - If both key (index) and value are used (`for i, val := range arr`):
32
+ // `for (let i_ts = 0; i_ts < arr_ts.length; i_ts++) { const val_ts = arr_ts[i_ts]; ...body... }`.
33
+ // - If only the key (index) is used (`for i := range arr`):
34
+ // `for (let i_ts = 0; i_ts < arr_ts.length; i_ts++) { ...body... }`.
35
+ // - If only the value is used (`for _, val := range arr`):
36
+ // `for (const v_ts of arr_ts) { const val_ts = v_ts; ...body... }`.
37
+ // - If neither is used (e.g., `for range arr`), a simple index loop `for (let _i = 0; ...)` is generated.
38
+ // The index variable `i_ts` uses the Go key variable name if provided.
39
+ //
40
+ // Loop variables (`exp.Key`, `exp.Value`) are declared as `const` inside the loop
41
+ // body if they are not blank identifiers (`_`). The loop body (`exp.Body`) is
42
+ // translated using `WriteStmtBlock` (or `WriteStmt` for array/slice with key and value).
43
+ // If the ranged type is not supported, a comment is written, and an error is returned.
44
+ func (c *GoToTSCompiler) WriteStmtRange(exp *ast.RangeStmt) error {
45
+ // Get the type of the iterable expression
46
+ iterType := c.pkg.TypesInfo.TypeOf(exp.X)
47
+ underlying := iterType.Underlying()
48
+
49
+ // Handle map types
50
+ if _, ok := underlying.(*types.Map); ok {
51
+ // Use for-of with entries() for proper Map iteration
52
+ c.tsw.WriteLiterally("for (const [k, v] of ")
53
+ if err := c.WriteValueExpr(exp.X); err != nil {
54
+ return fmt.Errorf("failed to write range loop map expression: %w", err)
55
+ }
56
+ c.tsw.WriteLiterally(".entries()) {")
57
+ c.tsw.Indent(1)
58
+ c.tsw.WriteLine("")
59
+ // If a key variable is provided and is not blank, declare it as a constant
60
+ if exp.Key != nil {
61
+ if ident, ok := exp.Key.(*ast.Ident); ok && ident.Name != "_" {
62
+ c.tsw.WriteLiterally("const ")
63
+ c.WriteIdent(ident, false)
64
+ c.tsw.WriteLiterally(" = k")
65
+ c.tsw.WriteLine("")
66
+ }
67
+ }
68
+ // If a value variable is provided and is not blank, use the value from entries()
69
+ if exp.Value != nil {
70
+ if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
71
+ c.tsw.WriteLiterally("const ")
72
+ c.WriteIdent(ident, false)
73
+ c.tsw.WriteLiterally(" = v")
74
+ c.tsw.WriteLine("")
75
+ }
76
+ }
77
+ // Write the loop body
78
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
79
+ return fmt.Errorf("failed to write range loop map body: %w", err)
80
+ }
81
+ c.tsw.Indent(-1)
82
+ c.tsw.WriteLine("}")
83
+ return nil
84
+ }
85
+
86
+ // Handle basic types (string, integer)
87
+ if basic, ok := underlying.(*types.Basic); ok {
88
+ if basic.Info()&types.IsString != 0 {
89
+ // Add a scope to avoid collision of _runes variable
90
+ c.tsw.WriteLine("{")
91
+ c.tsw.Indent(1)
92
+
93
+ // Convert the string to runes using $.stringToRunes
94
+ c.tsw.WriteLiterally("const _runes = $.stringToRunes(")
95
+ if err := c.WriteValueExpr(exp.X); err != nil {
96
+ return fmt.Errorf("failed to write range loop string conversion expression: %w", err)
97
+ }
98
+ c.tsw.WriteLiterally(")")
99
+ c.tsw.WriteLine("")
100
+
101
+ // Determine the index variable name for the generated loop
102
+ indexVarName := "i" // Default name
103
+ if exp.Key != nil {
104
+ if keyIdent, ok := exp.Key.(*ast.Ident); ok && keyIdent.Name != "_" {
105
+ indexVarName = keyIdent.Name
106
+ }
107
+ }
108
+ c.tsw.WriteLiterallyf("for (let %s = 0; %s < _runes.length; %s++) {", indexVarName, indexVarName, indexVarName)
109
+ c.tsw.Indent(1)
110
+ c.tsw.WriteLine("")
111
+ // Declare value if provided and not blank
112
+ if exp.Value != nil {
113
+ if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
114
+ c.tsw.WriteLiterally("const ")
115
+ c.WriteIdent(ident, false)
116
+ c.tsw.WriteLiterally(" = _runes[i]") // TODO: should be indexVarName?
117
+ c.tsw.WriteLine("")
118
+ }
119
+ }
120
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
121
+ return fmt.Errorf("failed to write range loop string body: %w", err)
122
+ }
123
+ c.tsw.Indent(-1)
124
+ c.tsw.WriteLine("}")
125
+
126
+ // outer }
127
+ c.tsw.Indent(-1)
128
+ c.tsw.WriteLine("}")
129
+ return nil
130
+ } else if basic.Info()&types.IsInteger != 0 {
131
+ // Handle ranging over an integer (Go 1.22+)
132
+ // Determine the index variable name for the generated loop
133
+ indexVarName := "_i" // Default name
134
+ if exp.Key != nil {
135
+ if keyIdent, ok := exp.Key.(*ast.Ident); ok && keyIdent.Name != "_" {
136
+ indexVarName = keyIdent.Name
137
+ }
138
+ }
139
+
140
+ c.tsw.WriteLiterallyf("for (let %s = 0; %s < ", indexVarName, indexVarName)
141
+ if err := c.WriteValueExpr(exp.X); err != nil { // This is N
142
+ return fmt.Errorf("failed to write range loop integer expression: %w", err)
143
+ }
144
+ c.tsw.WriteLiterallyf("; %s++) {", indexVarName)
145
+ c.tsw.Indent(1)
146
+ c.tsw.WriteLine("")
147
+
148
+ // The value variable is not allowed ranging over an integer.
149
+ if exp.Value != nil {
150
+ return errors.Errorf("ranging over an integer supports key variable only (not value variable): %v", exp)
151
+ }
152
+
153
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
154
+ return fmt.Errorf("failed to write range loop integer body: %w", err)
155
+ }
156
+ c.tsw.Indent(-1)
157
+ c.tsw.WriteLine("}")
158
+ return nil
159
+ }
160
+ }
161
+
162
+ // Handle array and slice types
163
+ _, isSlice := underlying.(*types.Slice)
164
+ _, isArray := underlying.(*types.Array)
165
+ if isArray || isSlice {
166
+ // Determine the index variable name for the generated loop
167
+ indexVarName := "i" // Default name
168
+ if exp.Key != nil {
169
+ if keyIdent, ok := exp.Key.(*ast.Ident); ok && keyIdent.Name != "_" {
170
+ indexVarName = keyIdent.Name
171
+ }
172
+ }
173
+ // If both key and value are provided, use an index loop and assign both
174
+ if exp.Key != nil && exp.Value != nil {
175
+ c.tsw.WriteLiterallyf("for (let %s = 0; %s < $.len(", indexVarName, indexVarName)
176
+ if err := c.WriteValueExpr(exp.X); err != nil { // Write the expression for the iterable
177
+ return fmt.Errorf("failed to write range loop array/slice expression (key and value): %w", err)
178
+ }
179
+ c.tsw.WriteLiterallyf("); %s++) {", indexVarName)
180
+ c.tsw.Indent(1)
181
+ c.tsw.WriteLine("")
182
+ // Declare value if not blank
183
+ if ident, ok := exp.Value.(*ast.Ident); ok && ident.Name != "_" {
184
+ c.tsw.WriteLiterally("const ")
185
+ c.WriteIdent(ident, false)
186
+ c.tsw.WriteLiterally(" = ")
187
+ if err := c.WriteValueExpr(exp.X); err != nil {
188
+ return fmt.Errorf("failed to write range loop array/slice value expression: %w", err)
189
+ }
190
+ c.tsw.WriteLiterallyf("![%s]", indexVarName) // Use indexVarName with not-null assert
191
+ c.tsw.WriteLine("")
192
+ }
193
+ if err := c.WriteStmt(exp.Body); err != nil {
194
+ return fmt.Errorf("failed to write range loop array/slice body (key and value): %w", err)
195
+ }
196
+ c.tsw.Indent(-1)
197
+ c.tsw.WriteLine("}")
198
+ return nil
199
+ } else if exp.Key != nil && exp.Value == nil { // Only key provided
200
+ c.tsw.WriteLiterallyf("for (let %s = 0; %s < $.len(", indexVarName, indexVarName)
201
+ // Write the expression for the iterable
202
+ if err := c.WriteValueExpr(exp.X); err != nil {
203
+ return fmt.Errorf("failed to write expression for the iterable: %w", err)
204
+ }
205
+ c.tsw.WriteLiterallyf("); %s++) {", indexVarName)
206
+ c.tsw.Indent(1)
207
+ c.tsw.WriteLine("")
208
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
209
+ return fmt.Errorf("failed to write range loop array/slice body (only key): %w", err)
210
+ }
211
+ c.tsw.Indent(-1)
212
+ c.tsw.WriteLine("}")
213
+ return nil
214
+ } else if exp.Key == nil && exp.Value != nil { // Only value provided
215
+ // I think this is impossible. See for_range_value_only test.
216
+ return errors.Errorf("unexpected value without key in for range expression: %v", exp)
217
+ } else {
218
+ // Fallback: simple index loop without declaring range variables, use _i
219
+ indexVarName := "_i"
220
+ c.tsw.WriteLiterallyf("for (let %s = 0; %s < $.len(", indexVarName, indexVarName)
221
+ if err := c.WriteValueExpr(exp.X); err != nil {
222
+ return fmt.Errorf("failed to write range loop array/slice length expression (fallback): %w", err)
223
+ }
224
+ c.tsw.WriteLiterallyf("); %s++) {", indexVarName)
225
+ c.tsw.Indent(1)
226
+ c.tsw.WriteLine("")
227
+ if err := c.WriteStmtBlock(exp.Body, false); err != nil {
228
+ return fmt.Errorf("failed to write range loop array/slice body (fallback): %w", err)
229
+ }
230
+ c.tsw.Indent(-1)
231
+ c.tsw.WriteLine("}")
232
+ return nil
233
+ }
234
+ }
235
+
236
+ return errors.Errorf("unsupported range loop type: %T for expression %v", underlying, exp)
237
+ }