goscript 0.0.32 → 0.0.34
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/compiler/analysis.go +2 -2
- package/compiler/assignment.go +26 -0
- package/compiler/builtin_test.go +2 -0
- package/compiler/compiler.go +12 -2
- package/compiler/compiler_test.go +0 -53
- package/compiler/expr-call.go +229 -2
- package/compiler/expr.go +66 -1
- package/compiler/lit.go +1 -1
- package/compiler/spec.go +6 -0
- package/compiler/stmt-assign.go +106 -90
- package/compiler/stmt-for.go +78 -1
- package/compiler/stmt-range.go +333 -461
- package/compiler/stmt.go +20 -0
- package/compiler/type.go +11 -8
- package/dist/gs/builtin/builtin.d.ts +7 -0
- package/dist/gs/builtin/builtin.js +30 -0
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/map.d.ts +4 -4
- package/dist/gs/builtin/map.js +12 -6
- package/dist/gs/builtin/map.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +7 -7
- package/dist/gs/builtin/slice.js +19 -9
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/maps/index.d.ts +2 -0
- package/dist/gs/maps/index.js +3 -0
- package/dist/gs/maps/index.js.map +1 -0
- package/dist/gs/maps/iter.gs.d.ts +7 -0
- package/dist/gs/maps/iter.gs.js +65 -0
- package/dist/gs/maps/iter.gs.js.map +1 -0
- package/dist/gs/maps/maps.gs.d.ts +7 -0
- package/dist/gs/maps/maps.gs.js +79 -0
- package/dist/gs/maps/maps.gs.js.map +1 -0
- package/dist/gs/slices/slices.d.ts +6 -0
- package/dist/gs/slices/slices.js +8 -0
- package/dist/gs/slices/slices.js.map +1 -1
- package/gs/builtin/builtin.ts +38 -0
- package/gs/builtin/map.ts +10 -9
- package/gs/builtin/slice.ts +23 -11
- package/gs/maps/index.ts +2 -0
- package/gs/maps/iter.gs.ts +71 -0
- package/gs/maps/maps.gs.ts +87 -0
- package/gs/slices/slices.ts +9 -0
- package/package.json +1 -1
package/compiler/stmt-assign.go
CHANGED
|
@@ -276,122 +276,106 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
// writeMapLookupWithExists handles the map comma-ok idiom: value, exists := myMap[key]
|
|
279
|
-
//
|
|
279
|
+
// Uses array destructuring with the tuple-returning $.mapGet function
|
|
280
280
|
writeMapLookupWithExists := func(lhs []ast.Expr, indexExpr *ast.IndexExpr, tok token.Token) error {
|
|
281
281
|
// First check that we have exactly two LHS expressions (value and exists)
|
|
282
282
|
if len(lhs) != 2 {
|
|
283
283
|
return fmt.Errorf("map comma-ok idiom requires exactly 2 variables on LHS, got %d", len(lhs))
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
// Check for blank identifiers
|
|
286
|
+
// Check for blank identifiers
|
|
287
287
|
valueIsBlank := false
|
|
288
288
|
existsIsBlank := false
|
|
289
|
-
var valueName string
|
|
290
|
-
var existsName string
|
|
291
289
|
|
|
292
|
-
if valIdent, ok := lhs[0].(*ast.Ident); ok {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
} else {
|
|
299
|
-
return fmt.Errorf("unhandled LHS expression type for value in map comma-ok: %T", lhs[0])
|
|
290
|
+
if valIdent, ok := lhs[0].(*ast.Ident); ok && valIdent.Name == "_" {
|
|
291
|
+
valueIsBlank = true
|
|
292
|
+
}
|
|
293
|
+
if existsIdent, ok := lhs[1].(*ast.Ident); ok && existsIdent.Name == "_" {
|
|
294
|
+
existsIsBlank = true
|
|
300
295
|
}
|
|
301
296
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
} else {
|
|
306
|
-
existsName = existsIdent.Name
|
|
307
|
-
}
|
|
297
|
+
// Use array destructuring with mapGet tuple return
|
|
298
|
+
if tok == token.DEFINE {
|
|
299
|
+
c.tsw.WriteLiterally("let ")
|
|
308
300
|
} else {
|
|
309
|
-
|
|
301
|
+
// Add semicolon before destructuring assignment to prevent TypeScript
|
|
302
|
+
// from interpreting it as array access on the previous line
|
|
303
|
+
c.tsw.WriteLiterally(";")
|
|
310
304
|
}
|
|
311
305
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
c.tsw.WriteLine("")
|
|
319
|
-
}
|
|
320
|
-
if !existsIsBlank {
|
|
321
|
-
c.tsw.WriteLiterally("let ")
|
|
322
|
-
c.tsw.WriteLiterally(existsName)
|
|
323
|
-
c.tsw.WriteLiterally(": boolean") // exists is always boolean
|
|
324
|
-
c.tsw.WriteLine("")
|
|
306
|
+
c.tsw.WriteLiterally("[")
|
|
307
|
+
|
|
308
|
+
// Write LHS variables, handling blanks
|
|
309
|
+
if !valueIsBlank {
|
|
310
|
+
if err := c.WriteValueExpr(lhs[0]); err != nil {
|
|
311
|
+
return err
|
|
325
312
|
}
|
|
326
313
|
}
|
|
314
|
+
// Note: for blank identifiers, we just omit the variable name entirely
|
|
315
|
+
|
|
316
|
+
c.tsw.WriteLiterally(", ")
|
|
327
317
|
|
|
328
|
-
// Assign 'exists'
|
|
329
318
|
if !existsIsBlank {
|
|
330
|
-
c.
|
|
331
|
-
c.tsw.WriteLiterally(" = ")
|
|
332
|
-
c.tsw.WriteLiterally("$.mapHas(")
|
|
333
|
-
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
334
|
-
return err
|
|
335
|
-
}
|
|
336
|
-
c.tsw.WriteLiterally(", ")
|
|
337
|
-
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
319
|
+
if err := c.WriteValueExpr(lhs[1]); err != nil {
|
|
338
320
|
return err
|
|
339
321
|
}
|
|
340
|
-
c.tsw.WriteLiterally(")")
|
|
341
|
-
c.tsw.WriteLine("")
|
|
342
322
|
}
|
|
323
|
+
// Note: for blank identifiers, we just omit the variable name entirely
|
|
343
324
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
325
|
+
c.tsw.WriteLiterally("] = $.mapGet(")
|
|
326
|
+
|
|
327
|
+
// Write map expression
|
|
328
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil {
|
|
329
|
+
return err
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
c.tsw.WriteLiterally(", ")
|
|
333
|
+
|
|
334
|
+
// Write key expression
|
|
335
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil {
|
|
336
|
+
return err
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
c.tsw.WriteLiterally(", ")
|
|
340
|
+
|
|
341
|
+
// Write the zero value for the map's value type
|
|
342
|
+
if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
|
|
343
|
+
if mapType, isMap := tv.Type.Underlying().(*types.Map); isMap {
|
|
344
|
+
c.WriteZeroValueForType(mapType.Elem())
|
|
345
|
+
} else if typeParam, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
|
|
346
|
+
// Handle type parameter constrained to be a map type
|
|
347
|
+
constraint := typeParam.Constraint()
|
|
348
|
+
if constraint != nil {
|
|
349
|
+
underlying := constraint.Underlying()
|
|
350
|
+
if iface, isInterface := underlying.(*types.Interface); isInterface {
|
|
351
|
+
if hasMapConstraint(iface) {
|
|
352
|
+
// Get the value type from the constraint
|
|
353
|
+
mapValueType := getMapValueTypeFromConstraint(iface)
|
|
354
|
+
if mapValueType != nil {
|
|
355
|
+
c.WriteZeroValueForType(mapValueType)
|
|
356
|
+
} else {
|
|
357
|
+
c.tsw.WriteLiterally("null")
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
c.tsw.WriteLiterally("null")
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
c.tsw.WriteLiterally("null")
|
|
364
|
+
}
|
|
361
365
|
} else {
|
|
362
|
-
// Fallback zero value if type info is missing or not a map
|
|
363
366
|
c.tsw.WriteLiterally("null")
|
|
364
367
|
}
|
|
365
368
|
} else {
|
|
369
|
+
// Fallback zero value if type info is missing or not a map
|
|
366
370
|
c.tsw.WriteLiterally("null")
|
|
367
371
|
}
|
|
368
|
-
|
|
369
|
-
c.tsw.
|
|
370
|
-
} else if existsIsBlank {
|
|
371
|
-
// If both are blank, still evaluate for side effects (though .has/.get are usually pure)
|
|
372
|
-
// We add a ; otherwise TypeScript thinks we are invoking a function.
|
|
373
|
-
c.tsw.WriteLiterally(";(") // Wrap in parens to make it an expression statement
|
|
374
|
-
c.tsw.WriteLiterally("$.mapHas(")
|
|
375
|
-
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
376
|
-
return err
|
|
377
|
-
}
|
|
378
|
-
c.tsw.WriteLiterally(", ")
|
|
379
|
-
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
380
|
-
return err
|
|
381
|
-
}
|
|
382
|
-
c.tsw.WriteLiterally("), ") // Evaluate .has
|
|
383
|
-
c.tsw.WriteLiterally("$.mapGet(")
|
|
384
|
-
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
385
|
-
return err
|
|
386
|
-
}
|
|
387
|
-
c.tsw.WriteLiterally(", ")
|
|
388
|
-
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
389
|
-
return err
|
|
390
|
-
}
|
|
391
|
-
c.tsw.WriteLiterally(", null))") // Evaluate .get with null as default
|
|
392
|
-
c.tsw.WriteLine("")
|
|
372
|
+
} else {
|
|
373
|
+
c.tsw.WriteLiterally("null")
|
|
393
374
|
}
|
|
394
375
|
|
|
376
|
+
c.tsw.WriteLiterally(")")
|
|
377
|
+
c.tsw.WriteLine("")
|
|
378
|
+
|
|
395
379
|
return nil
|
|
396
380
|
}
|
|
397
381
|
|
|
@@ -417,6 +401,8 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
|
417
401
|
}
|
|
418
402
|
return nil
|
|
419
403
|
}
|
|
404
|
+
// Handle general function calls that return multiple values
|
|
405
|
+
return writeMultiVarAssignFromCall(exp.Lhs, callExpr, exp.Tok)
|
|
420
406
|
}
|
|
421
407
|
|
|
422
408
|
if typeAssertExpr, ok := rhsExpr.(*ast.TypeAssertExpr); ok {
|
|
@@ -428,10 +414,22 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
|
428
414
|
if c.pkg != nil && c.pkg.TypesInfo != nil {
|
|
429
415
|
tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]
|
|
430
416
|
if ok {
|
|
431
|
-
// Check if it's a map type
|
|
417
|
+
// Check if it's a concrete map type
|
|
432
418
|
if _, isMap := tv.Type.Underlying().(*types.Map); isMap {
|
|
433
419
|
return writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
|
|
434
420
|
}
|
|
421
|
+
// Check if it's a type parameter constrained to be a map type
|
|
422
|
+
if typeParam, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
|
|
423
|
+
constraint := typeParam.Constraint()
|
|
424
|
+
if constraint != nil {
|
|
425
|
+
underlying := constraint.Underlying()
|
|
426
|
+
if iface, isInterface := underlying.(*types.Interface); isInterface {
|
|
427
|
+
if hasMapConstraint(iface) {
|
|
428
|
+
return writeMapLookupWithExists(exp.Lhs, indexExpr, exp.Tok)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
435
433
|
}
|
|
436
434
|
}
|
|
437
435
|
}
|
|
@@ -441,8 +439,6 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
|
441
439
|
return c.writeChannelReceiveWithOk(exp.Lhs, unaryExpr, exp.Tok)
|
|
442
440
|
}
|
|
443
441
|
// If LHS count is not 2, fall through to error or other handling
|
|
444
|
-
} else if callExpr, ok := rhsExpr.(*ast.CallExpr); ok {
|
|
445
|
-
return writeMultiVarAssignFromCall(exp.Lhs, callExpr, exp.Tok)
|
|
446
442
|
}
|
|
447
443
|
// If none of the specific multi-assign patterns match, fall through to the error check below
|
|
448
444
|
}
|
|
@@ -463,7 +459,27 @@ func (c *GoToTSCompiler) WriteStmtAssign(exp *ast.AssignStmt) error {
|
|
|
463
459
|
|
|
464
460
|
// Ensure LHS and RHS have the same length for valid Go code in these cases
|
|
465
461
|
if len(exp.Lhs) != len(exp.Rhs) {
|
|
466
|
-
|
|
462
|
+
// Special case: allow multiple LHS with single RHS if RHS can produce multiple values
|
|
463
|
+
// This handles cases like: x, y := getValue() where getValue() returns multiple values
|
|
464
|
+
// or other expressions that can produce multiple values
|
|
465
|
+
if len(exp.Rhs) == 1 {
|
|
466
|
+
// Allow single RHS expressions that can produce multiple values:
|
|
467
|
+
// - Function calls that return multiple values
|
|
468
|
+
// - Type assertions with comma-ok
|
|
469
|
+
// - Map lookups with comma-ok
|
|
470
|
+
// - Channel receives with comma-ok
|
|
471
|
+
// The Go type checker should have already verified this is valid
|
|
472
|
+
rhsExpr := exp.Rhs[0]
|
|
473
|
+
switch rhsExpr.(type) {
|
|
474
|
+
case *ast.CallExpr, *ast.TypeAssertExpr, *ast.IndexExpr, *ast.UnaryExpr:
|
|
475
|
+
// These expression types can potentially produce multiple values
|
|
476
|
+
// Let the general assignment logic handle them
|
|
477
|
+
default:
|
|
478
|
+
return fmt.Errorf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs))
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
return fmt.Errorf("invalid assignment statement: LHS count (%d) != RHS count (%d)", len(exp.Lhs), len(exp.Rhs))
|
|
482
|
+
}
|
|
467
483
|
}
|
|
468
484
|
|
|
469
485
|
// Handle multi-variable assignment (e.g., swaps) using writeAssignmentCore
|
package/compiler/stmt-for.go
CHANGED
|
@@ -4,6 +4,7 @@ import (
|
|
|
4
4
|
"fmt"
|
|
5
5
|
"go/ast"
|
|
6
6
|
"go/token"
|
|
7
|
+
"go/types"
|
|
7
8
|
|
|
8
9
|
"github.com/pkg/errors"
|
|
9
10
|
)
|
|
@@ -64,7 +65,25 @@ func (c *GoToTSCompiler) WriteStmtForInit(stmt ast.Stmt) error {
|
|
|
64
65
|
case *ast.AssignStmt:
|
|
65
66
|
// Handle assignment in init (e.g., i := 0 or i = 0)
|
|
66
67
|
// 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)
|
|
68
|
+
if s.Tok == token.DEFINE && len(s.Lhs) > 1 && len(s.Rhs) == 1 {
|
|
69
|
+
// Handle the case where we have multiple LHS variables but only one RHS expression
|
|
70
|
+
// Use array destructuring for all cases including map comma-ok idiom
|
|
71
|
+
rhsExpr := s.Rhs[0]
|
|
72
|
+
c.tsw.WriteLiterally("let [")
|
|
73
|
+
for i, lhs := range s.Lhs {
|
|
74
|
+
if i > 0 {
|
|
75
|
+
c.tsw.WriteLiterally(", ")
|
|
76
|
+
}
|
|
77
|
+
if err := c.WriteValueExpr(lhs); err != nil {
|
|
78
|
+
return err
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
c.tsw.WriteLiterally("] = ")
|
|
82
|
+
if err := c.writeDestructuringValue(rhsExpr); err != nil {
|
|
83
|
+
return err
|
|
84
|
+
}
|
|
85
|
+
return nil
|
|
86
|
+
} else if s.Tok == token.DEFINE && len(s.Lhs) > 1 && len(s.Rhs) > 0 {
|
|
68
87
|
// For loop initialization with multiple variables (e.g., let i = 0, j = 10)
|
|
69
88
|
c.tsw.WriteLiterally("let ")
|
|
70
89
|
|
|
@@ -121,6 +140,64 @@ func (c *GoToTSCompiler) WriteStmtForInit(stmt ast.Stmt) error {
|
|
|
121
140
|
}
|
|
122
141
|
}
|
|
123
142
|
|
|
143
|
+
// writeDestructuringValue writes a value expression in a destructuring context.
|
|
144
|
+
// For map index expressions, it generates the full tuple without [0] indexing.
|
|
145
|
+
func (c *GoToTSCompiler) writeDestructuringValue(expr ast.Expr) error {
|
|
146
|
+
// Check if this is a map index expression that should return a tuple
|
|
147
|
+
if indexExpr, ok := expr.(*ast.IndexExpr); ok {
|
|
148
|
+
if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
|
|
149
|
+
underlyingType := tv.Type.Underlying()
|
|
150
|
+
// Check if it's a map type
|
|
151
|
+
if mapType, isMap := underlyingType.(*types.Map); isMap {
|
|
152
|
+
c.tsw.WriteLiterally("$.mapGet(")
|
|
153
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil {
|
|
154
|
+
return err
|
|
155
|
+
}
|
|
156
|
+
c.tsw.WriteLiterally(", ")
|
|
157
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil {
|
|
158
|
+
return err
|
|
159
|
+
}
|
|
160
|
+
c.tsw.WriteLiterally(", ")
|
|
161
|
+
// Write the zero value as the default value for mapGet
|
|
162
|
+
c.WriteZeroValueForType(mapType.Elem())
|
|
163
|
+
c.tsw.WriteLiterally(")") // Don't add [0] for destructuring
|
|
164
|
+
return nil
|
|
165
|
+
}
|
|
166
|
+
// Check if it's a type parameter constrained to be a map type
|
|
167
|
+
if typeParam, isTypeParam := tv.Type.(*types.TypeParam); isTypeParam {
|
|
168
|
+
constraint := typeParam.Constraint()
|
|
169
|
+
if constraint != nil {
|
|
170
|
+
underlying := constraint.Underlying()
|
|
171
|
+
if iface, isInterface := underlying.(*types.Interface); isInterface {
|
|
172
|
+
if hasMapConstraint(iface) {
|
|
173
|
+
c.tsw.WriteLiterally("$.mapGet(")
|
|
174
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil {
|
|
175
|
+
return err
|
|
176
|
+
}
|
|
177
|
+
c.tsw.WriteLiterally(", ")
|
|
178
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil {
|
|
179
|
+
return err
|
|
180
|
+
}
|
|
181
|
+
c.tsw.WriteLiterally(", ")
|
|
182
|
+
// Generate the zero value as the default value for mapGet
|
|
183
|
+
mapValueType := getMapValueTypeFromConstraint(iface)
|
|
184
|
+
if mapValueType != nil {
|
|
185
|
+
c.WriteZeroValueForType(mapValueType)
|
|
186
|
+
} else {
|
|
187
|
+
c.tsw.WriteLiterally("null")
|
|
188
|
+
}
|
|
189
|
+
c.tsw.WriteLiterally(")") // Don't add [0] for destructuring
|
|
190
|
+
return nil
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// For non-map expressions, use the regular WriteValueExpr
|
|
198
|
+
return c.WriteValueExpr(expr)
|
|
199
|
+
}
|
|
200
|
+
|
|
124
201
|
// WriteStmtForPost translates the post-iteration part of a Go `for` loop header
|
|
125
202
|
// (e.g., `i++` or `i, j = i+1, j-1` in `for ...; i++`) into its TypeScript
|
|
126
203
|
// equivalent.
|