goscript 0.0.21 → 0.0.23
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/cmd/goscript/cmd_compile.go +2 -2
- package/compiler/analysis.go +229 -51
- package/compiler/assignment.go +412 -0
- package/compiler/compiler.go +185 -5885
- package/compiler/compiler_test.go +40 -8
- package/compiler/composite-lit.go +552 -0
- package/compiler/config.go +3 -0
- package/compiler/decl.go +259 -0
- package/compiler/expr-call.go +479 -0
- package/compiler/expr-selector.go +125 -0
- package/compiler/expr-star.go +90 -0
- package/compiler/expr-type.go +309 -0
- package/compiler/expr-value.go +89 -0
- package/compiler/expr.go +591 -0
- package/compiler/field.go +169 -0
- package/compiler/lit.go +131 -0
- package/compiler/primitive.go +148 -0
- package/compiler/{write-type-spec.go → spec-struct.go} +211 -204
- package/compiler/spec-value.go +226 -0
- package/compiler/spec.go +272 -0
- package/compiler/stmt-assign.go +439 -0
- package/compiler/stmt-for.go +178 -0
- package/compiler/stmt-range.go +235 -0
- package/compiler/stmt-select.go +211 -0
- package/compiler/stmt-type-switch.go +147 -0
- package/compiler/stmt.go +792 -0
- package/compiler/type-assert.go +209 -0
- package/compiler/type-info.go +141 -0
- package/compiler/type.go +618 -0
- package/go.mod +2 -1
- package/go.sum +4 -2
- package/package.json +6 -6
- package/builtin/builtin.go +0 -11
- package/builtin/builtin.ts +0 -2114
- package/dist/builtin/builtin.d.ts +0 -495
- package/dist/builtin/builtin.js +0 -1490
- package/dist/builtin/builtin.js.map +0 -1
- /package/compiler/{writer.go → code-writer.go} +0 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
package compiler
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"go/ast"
|
|
6
|
+
"go/token"
|
|
7
|
+
"go/types"
|
|
8
|
+
"strings"
|
|
9
|
+
|
|
10
|
+
"github.com/pkg/errors"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
// WriteStmtAssign translates a Go assignment statement (`ast.AssignStmt`) into
|
|
14
|
+
// its TypeScript equivalent. It handles various forms of Go assignments:
|
|
15
|
+
//
|
|
16
|
+
// 1. **Multi-variable assignment from a single function call** (e.g., `a, b := fn()`):
|
|
17
|
+
// - Uses `writeMultiVarAssignFromCall` to generate `let [a, b] = fn_ts();`.
|
|
18
|
+
//
|
|
19
|
+
// 2. **Type assertion with comma-ok** (e.g., `val, ok := expr.(Type)`):
|
|
20
|
+
// - Uses `writeTypeAssertion` to generate `let { value: val, ok: ok } = $.typeAssert<Type_ts>(expr_ts, 'TypeName');`.
|
|
21
|
+
//
|
|
22
|
+
// 3. **Map lookup with comma-ok** (e.g., `val, ok := myMap[key]`):
|
|
23
|
+
// - Uses `writeMapLookupWithExists` to generate separate assignments for `val`
|
|
24
|
+
// (using `myMap_ts.get(key_ts) ?? zeroValue`) and `ok` (using `myMap_ts.has(key_ts)`).
|
|
25
|
+
//
|
|
26
|
+
// 4. **Channel receive with comma-ok** (e.g., `val, ok := <-ch`):
|
|
27
|
+
// - Uses `writeChannelReceiveWithOk` to generate `let { value: val, ok: ok } = await ch_ts.receiveWithOk();`.
|
|
28
|
+
//
|
|
29
|
+
// 5. **Discarded channel receive** (e.g., `<-ch` on RHS, no LHS vars):
|
|
30
|
+
// - Translates to `await ch_ts.receive();`.
|
|
31
|
+
//
|
|
32
|
+
// 6. **Single assignment** (e.g., `x = y`, `x := y`, `*p = y`, `x[i] = y`):
|
|
33
|
+
// - Uses `writeAssignmentCore` which handles:
|
|
34
|
+
// - Blank identifier `_` on LHS (evaluates RHS for side effects).
|
|
35
|
+
// - Assignment to dereferenced pointer `*p = val` -> `p_ts!.value = val_ts`.
|
|
36
|
+
// - Short declaration `x := y`: `let x = y_ts;`. If `x` is boxed, `let x: $.Box<T> = $.box(y_ts);`.
|
|
37
|
+
// - Regular assignment `x = y`, including compound assignments like `x += y`.
|
|
38
|
+
// - Assignment to map index `m[k] = v` using `$.mapSet`.
|
|
39
|
+
// - Struct value assignment `s1 = s2` becomes `s1 = s2.clone()` if `s2` is a struct.
|
|
40
|
+
//
|
|
41
|
+
// 7. **Multi-variable assignment with multiple RHS values** (e.g., `a, b = x, y`):
|
|
42
|
+
// - Uses `writeAssignmentCore` to generate `[a,b] = [x_ts, y_ts];` (or `let [a,b] = ...` for `:=`).
|
|
43
|
+
//
|
|
44
|
+
// The function ensures that the number of LHS and RHS expressions matches for
|
|
45
|
+
// most cases, erroring if they don't, except for specifically handled patterns
|
|
46
|
+
// like multi-assign from single call or discarded channel receive.
|
|
47
|
+
// It correctly applies `let` for `:=` (define) tokens and handles boxing and
|
|
48
|
+
// cloning semantics based on type information and analysis.
|
|
49
|
+
func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
50
|
+
// writeMultiVarAssignFromCall handles multi-variable assignment from a single function call.
|
|
51
|
+
writeMultiVarAssignFromCall := func(lhs []ast.Expr, callExpr *ast.CallExpr, tok token.Token) error {
|
|
52
|
+
// For token.DEFINE (:=), we need to check if any of the variables are already declared
|
|
53
|
+
// In Go, := can be used for redeclaration if at least one variable is new
|
|
54
|
+
if tok == token.DEFINE {
|
|
55
|
+
// For token.DEFINE (:=), we need to handle variable declarations differently
|
|
56
|
+
// In Go, := can redeclare existing variables if at least one variable is new
|
|
57
|
+
|
|
58
|
+
// First, identify which variables are new vs existing
|
|
59
|
+
newVars := make([]bool, len(lhs))
|
|
60
|
+
anyNewVars := false
|
|
61
|
+
allNewVars := true
|
|
62
|
+
|
|
63
|
+
// For multi-variable assignments with :=, we need to determine which variables
|
|
64
|
+
// are already in scope and which are new declarations
|
|
65
|
+
for i, lhsExpr := range lhs {
|
|
66
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" {
|
|
67
|
+
// In Go, variables declared with := can be redeclared if at least one is new
|
|
68
|
+
// For TypeScript, we need to separately declare new variables
|
|
69
|
+
|
|
70
|
+
// Check if this variable is already in scope
|
|
71
|
+
// - If the variable is used elsewhere before this point, it's existing
|
|
72
|
+
// - Otherwise, it's a new variable being declared
|
|
73
|
+
isNew := true
|
|
74
|
+
|
|
75
|
+
// Check if the variable is used elsewhere in the code
|
|
76
|
+
if obj := c.pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
77
|
+
// If it's in Uses, it's referenced elsewhere, so it exists
|
|
78
|
+
isNew = false
|
|
79
|
+
allNewVars = false
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
newVars[i] = isNew
|
|
83
|
+
if isNew {
|
|
84
|
+
anyNewVars = true
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get function return types if available
|
|
90
|
+
var resultTypes []*types.Var
|
|
91
|
+
if callExpr.Fun != nil {
|
|
92
|
+
if funcType, ok := c.pkg.TypesInfo.TypeOf(callExpr.Fun).Underlying().(*types.Signature); ok {
|
|
93
|
+
if funcType.Results() != nil && funcType.Results().Len() > 0 {
|
|
94
|
+
for i := 0; i < funcType.Results().Len(); i++ {
|
|
95
|
+
resultTypes = append(resultTypes, funcType.Results().At(i))
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if allNewVars && anyNewVars {
|
|
102
|
+
c.tsw.WriteLiterally("let [")
|
|
103
|
+
|
|
104
|
+
for i, lhsExpr := range lhs {
|
|
105
|
+
if i != 0 {
|
|
106
|
+
c.tsw.WriteLiterally(", ")
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok {
|
|
110
|
+
if ident.Name == "_" {
|
|
111
|
+
// For underscore variables, use empty slots in destructuring pattern
|
|
112
|
+
} else {
|
|
113
|
+
c.WriteIdent(ident, false)
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
c.WriteValueExpr(lhsExpr)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
c.tsw.WriteLiterally("] = ")
|
|
120
|
+
c.WriteValueExpr(callExpr)
|
|
121
|
+
c.tsw.WriteLine("")
|
|
122
|
+
return nil
|
|
123
|
+
} else if anyNewVars {
|
|
124
|
+
// If only some variables are new, declare them separately before the assignment
|
|
125
|
+
// Declare each new variable with appropriate type
|
|
126
|
+
for i, lhsExpr := range lhs {
|
|
127
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name != "_" && newVars[i] {
|
|
128
|
+
c.tsw.WriteLiterally("let ")
|
|
129
|
+
c.WriteIdent(ident, false)
|
|
130
|
+
|
|
131
|
+
// Add type annotation if we have type information
|
|
132
|
+
if i < len(resultTypes) {
|
|
133
|
+
c.tsw.WriteLiterally(": ")
|
|
134
|
+
c.WriteGoType(resultTypes[i].Type(), GoTypeContextGeneral)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
c.tsw.WriteLine("")
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// First, collect all the selector expressions to identify variables that need to be initialized
|
|
144
|
+
hasSelectors := false
|
|
145
|
+
for _, lhsExpr := range lhs {
|
|
146
|
+
if _, ok := lhsExpr.(*ast.SelectorExpr); ok {
|
|
147
|
+
hasSelectors = true
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// If we have selector expressions, we need to ensure variables are initialized
|
|
153
|
+
// before the destructuring assignment
|
|
154
|
+
if hasSelectors {
|
|
155
|
+
c.tsw.WriteLiterally("{")
|
|
156
|
+
c.tsw.WriteLine("")
|
|
157
|
+
|
|
158
|
+
// Write a temporary variable to hold the function call result
|
|
159
|
+
c.tsw.WriteLiterally(" const _tmp = ")
|
|
160
|
+
if err := c.WriteValueExpr(callExpr); err != nil {
|
|
161
|
+
return fmt.Errorf("failed to write RHS call expression in assignment: %w", err)
|
|
162
|
+
}
|
|
163
|
+
c.tsw.WriteLine("")
|
|
164
|
+
|
|
165
|
+
for i, lhsExpr := range lhs {
|
|
166
|
+
// Skip underscore variables
|
|
167
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok && ident.Name == "_" {
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Write the LHS with indentation
|
|
172
|
+
c.tsw.WriteLiterally(" ")
|
|
173
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok {
|
|
174
|
+
c.WriteIdent(ident, false)
|
|
175
|
+
} else if selectorExpr, ok := lhsExpr.(*ast.SelectorExpr); ok {
|
|
176
|
+
if err := c.WriteValueExpr(selectorExpr); err != nil {
|
|
177
|
+
return fmt.Errorf("failed to write selector expression in LHS: %w", err)
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
return errors.Errorf("unhandled LHS expression in assignment: %T", lhsExpr)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Write the assignment
|
|
184
|
+
c.tsw.WriteLiterallyf(" = _tmp[%d]", i)
|
|
185
|
+
// Always add a newline after each assignment
|
|
186
|
+
c.tsw.WriteLine("")
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Close the block scope
|
|
190
|
+
c.tsw.WriteLiterally("}")
|
|
191
|
+
c.tsw.WriteLine("")
|
|
192
|
+
|
|
193
|
+
return nil
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// For simple cases without selector expressions, use array destructuring
|
|
197
|
+
c.tsw.WriteLiterally("[")
|
|
198
|
+
|
|
199
|
+
for i, lhsExpr := range lhs {
|
|
200
|
+
if i != 0 {
|
|
201
|
+
c.tsw.WriteLiterally(", ")
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if ident, ok := lhsExpr.(*ast.Ident); ok {
|
|
205
|
+
// For underscore variables, use empty slots in destructuring pattern
|
|
206
|
+
if ident.Name != "_" {
|
|
207
|
+
c.WriteIdent(ident, false)
|
|
208
|
+
}
|
|
209
|
+
} else if selectorExpr, ok := lhsExpr.(*ast.SelectorExpr); ok {
|
|
210
|
+
// Handle selector expressions (e.g., a.b) by using WriteValueExpr
|
|
211
|
+
if err := c.WriteValueExpr(selectorExpr); err != nil {
|
|
212
|
+
return fmt.Errorf("failed to write selector expression in LHS: %w", err)
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
// Should not happen for valid Go code in this context, but handle defensively
|
|
216
|
+
return errors.Errorf("unhandled LHS expression in destructuring: %T", lhsExpr)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
c.tsw.WriteLiterally("] = ")
|
|
220
|
+
|
|
221
|
+
// Write the right-hand side (the function call)
|
|
222
|
+
if err := c.WriteValueExpr(callExpr); err != nil {
|
|
223
|
+
return fmt.Errorf("failed to write RHS call expression in assignment: %w", err)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
c.tsw.WriteLine("")
|
|
227
|
+
return nil
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// writeMapLookupWithExists handles the map comma-ok idiom: value, exists := myMap[key]
|
|
231
|
+
// Note: We don't use WriteIndexExpr here because we need to handle .has() and .get() separately
|
|
232
|
+
writeMapLookupWithExists := func(lhs []ast.Expr, indexExpr *ast.IndexExpr, tok token.Token) error {
|
|
233
|
+
// First check that we have exactly two LHS expressions (value and exists)
|
|
234
|
+
if len(lhs) != 2 {
|
|
235
|
+
return fmt.Errorf("map comma-ok idiom requires exactly 2 variables on LHS, got %d", len(lhs))
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check for blank identifiers and get variable names
|
|
239
|
+
valueIsBlank := false
|
|
240
|
+
existsIsBlank := false
|
|
241
|
+
var valueName string
|
|
242
|
+
var existsName string
|
|
243
|
+
|
|
244
|
+
if valIdent, ok := lhs[0].(*ast.Ident); ok {
|
|
245
|
+
if valIdent.Name == "_" {
|
|
246
|
+
valueIsBlank = true
|
|
247
|
+
} else {
|
|
248
|
+
valueName = valIdent.Name
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
return fmt.Errorf("unhandled LHS expression type for value in map comma-ok: %T", lhs[0])
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if existsIdent, ok := lhs[1].(*ast.Ident); ok {
|
|
255
|
+
if existsIdent.Name == "_" {
|
|
256
|
+
existsIsBlank = true
|
|
257
|
+
} else {
|
|
258
|
+
existsName = existsIdent.Name
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
return fmt.Errorf("unhandled LHS expression type for exists in map comma-ok: %T", lhs[1])
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Declare variables if using := and not blank
|
|
265
|
+
if tok == token.DEFINE {
|
|
266
|
+
if !valueIsBlank {
|
|
267
|
+
c.tsw.WriteLiterally("let ")
|
|
268
|
+
c.tsw.WriteLiterally(valueName)
|
|
269
|
+
// TODO: Add type annotation based on map value type
|
|
270
|
+
c.tsw.WriteLine("")
|
|
271
|
+
}
|
|
272
|
+
if !existsIsBlank {
|
|
273
|
+
c.tsw.WriteLiterally("let ")
|
|
274
|
+
c.tsw.WriteLiterally(existsName)
|
|
275
|
+
c.tsw.WriteLiterally(": boolean") // exists is always boolean
|
|
276
|
+
c.tsw.WriteLine("")
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Assign 'exists'
|
|
281
|
+
if !existsIsBlank {
|
|
282
|
+
c.tsw.WriteLiterally(existsName)
|
|
283
|
+
c.tsw.WriteLiterally(" = ")
|
|
284
|
+
c.tsw.WriteLiterally("$.mapHas(")
|
|
285
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
286
|
+
return err
|
|
287
|
+
}
|
|
288
|
+
c.tsw.WriteLiterally(", ")
|
|
289
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
290
|
+
return err
|
|
291
|
+
}
|
|
292
|
+
c.tsw.WriteLiterally(")")
|
|
293
|
+
c.tsw.WriteLine("")
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Assign 'value'
|
|
297
|
+
if !valueIsBlank {
|
|
298
|
+
c.tsw.WriteLiterally(valueName)
|
|
299
|
+
c.tsw.WriteLiterally(" = ")
|
|
300
|
+
c.tsw.WriteLiterally("$.mapGet(")
|
|
301
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
302
|
+
return err
|
|
303
|
+
}
|
|
304
|
+
c.tsw.WriteLiterally(", ")
|
|
305
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
306
|
+
return err
|
|
307
|
+
}
|
|
308
|
+
c.tsw.WriteLiterally(", ")
|
|
309
|
+
// Write the zero value for the map's value type
|
|
310
|
+
if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
|
|
311
|
+
if mapType, isMap := tv.Type.Underlying().(*types.Map); isMap {
|
|
312
|
+
c.WriteZeroValueForType(mapType.Elem())
|
|
313
|
+
} else {
|
|
314
|
+
// Fallback zero value if type info is missing or not a map
|
|
315
|
+
c.tsw.WriteLiterally("null")
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
c.tsw.WriteLiterally("null")
|
|
319
|
+
}
|
|
320
|
+
c.tsw.WriteLiterally(")")
|
|
321
|
+
c.tsw.WriteLine("")
|
|
322
|
+
} else if existsIsBlank {
|
|
323
|
+
// If both are blank, still evaluate for side effects (though .has/.get are usually pure)
|
|
324
|
+
// We add a ; otherwise TypeScript thinks we are invoking a function.
|
|
325
|
+
c.tsw.WriteLiterally(";(") // Wrap in parens to make it an expression statement
|
|
326
|
+
c.tsw.WriteLiterally("$.mapHas(")
|
|
327
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
328
|
+
return err
|
|
329
|
+
}
|
|
330
|
+
c.tsw.WriteLiterally(", ")
|
|
331
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
332
|
+
return err
|
|
333
|
+
}
|
|
334
|
+
c.tsw.WriteLiterally("), ") // Evaluate .has
|
|
335
|
+
c.tsw.WriteLiterally("$.mapGet(")
|
|
336
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
337
|
+
return err
|
|
338
|
+
}
|
|
339
|
+
c.tsw.WriteLiterally(", ")
|
|
340
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
341
|
+
return err
|
|
342
|
+
}
|
|
343
|
+
c.tsw.WriteLiterally(", null))") // Evaluate .get with null as default
|
|
344
|
+
c.tsw.WriteLine("")
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return nil
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Handle multi-variable assignment from a single expression.
|
|
351
|
+
if len(exp.Lhs) > 1 && len(exp.Rhs) == 1 {
|
|
352
|
+
rhsExpr := exp.Rhs[0]
|
|
353
|
+
if typeAssertExpr, ok := rhsExpr.(*ast.TypeAssertExpr); ok {
|
|
354
|
+
return c.writeTypeAssert(exp.Lhs, typeAssertExpr, exp.Tok)
|
|
355
|
+
} else if indexExpr, ok := rhsExpr.(*ast.IndexExpr); ok {
|
|
356
|
+
// Check if this is a map lookup (comma-ok idiom)
|
|
357
|
+
if len(exp.Lhs) == 2 {
|
|
358
|
+
// Get the type of the indexed expression
|
|
359
|
+
if c.pkg != nil && c.pkg.TypesInfo != nil {
|
|
360
|
+
tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]
|
|
361
|
+
if ok {
|
|
362
|
+
// Check if it's a map type
|
|
363
|
+
if _, isMap := tv.Type.Underlying().(*types.Map); isMap {
|
|
364
|
+
return writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
} else if unaryExpr, ok := rhsExpr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.ARROW {
|
|
370
|
+
// Handle val, ok := <-channel
|
|
371
|
+
if len(exp.Lhs) == 2 {
|
|
372
|
+
return c.writeChannelReceiveWithOk(exp.Lhs, unaryExpr, exp.Tok)
|
|
373
|
+
}
|
|
374
|
+
// If LHS count is not 2, fall through to error or other handling
|
|
375
|
+
} else if callExpr, ok := rhsExpr.(*ast.CallExpr); ok {
|
|
376
|
+
return writeMultiVarAssignFromCall(exp.Lhs, callExpr, exp.Tok)
|
|
377
|
+
}
|
|
378
|
+
// If none of the specific multi-assign patterns match, fall through to the error check below
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Ensure LHS and RHS have the same length for valid Go code in these cases
|
|
382
|
+
if len(exp.Lhs) != len(exp.Rhs) {
|
|
383
|
+
return fmt.Errorf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs))
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Handle multi-variable assignment (e.g., swaps) using writeAssignmentCore
|
|
387
|
+
if len(exp.Lhs) > 1 {
|
|
388
|
+
// Need to handle := for multi-variable declarations
|
|
389
|
+
if exp.Tok == token.DEFINE {
|
|
390
|
+
c.tsw.WriteLiterally("let ") // Use let for multi-variable declarations
|
|
391
|
+
}
|
|
392
|
+
// For multi-variable assignments, we've already added the "let" if needed
|
|
393
|
+
if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, false); err != nil {
|
|
394
|
+
return err
|
|
395
|
+
}
|
|
396
|
+
// Handle potential inline comment for multi-variable assignment
|
|
397
|
+
c.writeInlineComment(exp)
|
|
398
|
+
c.tsw.WriteLine("") // Add newline after the statement
|
|
399
|
+
return nil
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Handle single assignment using writeAssignmentCore
|
|
403
|
+
if len(exp.Lhs) == 1 {
|
|
404
|
+
addDeclaration := exp.Tok == token.DEFINE
|
|
405
|
+
if err := c.writeAssignmentCore(exp.Lhs, exp.Rhs, exp.Tok, addDeclaration); err != nil {
|
|
406
|
+
return err
|
|
407
|
+
}
|
|
408
|
+
// Handle potential inline comment for single assignment
|
|
409
|
+
c.writeInlineComment(exp)
|
|
410
|
+
c.tsw.WriteLine("") // Add newline after the statement
|
|
411
|
+
return nil
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Should not reach here if LHS/RHS counts are valid and handled
|
|
415
|
+
return fmt.Errorf("unhandled assignment case")
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// writeInlineComment checks for and writes any inline comments associated with the given AST node.
|
|
419
|
+
// It is intended to be called immediately after writing the main statement/expression.
|
|
420
|
+
func (c *GoToTSCompiler) writeInlineComment(node ast.Node) {
|
|
421
|
+
if c.pkg == nil || c.pkg.Fset == nil || !node.End().IsValid() {
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
file := c.pkg.Fset.File(node.End())
|
|
426
|
+
if file == nil {
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
endLine := file.Line(node.End())
|
|
431
|
+
// Check comments associated *directly* with the node
|
|
432
|
+
for _, cg := range c.analysis.Cmap[node] {
|
|
433
|
+
if cg.Pos().IsValid() && file.Line(cg.Pos()) == endLine && cg.Pos() > node.End() {
|
|
434
|
+
commentText := strings.TrimSpace(strings.TrimPrefix(cg.Text(), "//"))
|
|
435
|
+
c.tsw.WriteLiterally(" // " + commentText)
|
|
436
|
+
return // Only write the first inline comment found
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
@@ -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
|
+
}
|