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.
@@ -0,0 +1,226 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ "go/token"
7
+ "go/types"
8
+ )
9
+
10
+ // WriteValueSpec translates a Go value specification (`ast.ValueSpec`),
11
+ // which represents `var` or `const` declarations, into TypeScript `let`
12
+ // declarations.
13
+ //
14
+ // For single variable declarations (`var x T = val` or `var x = val` or `var x T`):
15
+ // - It determines if the variable `x` needs to be boxed (e.g., if its address is taken)
16
+ // using `c.analysis.NeedsBoxed(obj)`.
17
+ // - If boxed: `let x: $.Box<T_ts> = $.box(initializer_ts_or_zero_ts);`
18
+ // The type annotation is `$.Box<T_ts>`, and the initializer is wrapped in `$.box()`.
19
+ // - If not boxed: `let x: T_ts = initializer_ts_or_zero_ts;`
20
+ // The type annotation is `T_ts`. If the initializer is `&unboxedVar`, it becomes `$.box(unboxedVar_ts)`.
21
+ // If the RHS is a struct value, `.clone()` is applied to maintain Go's value semantics.
22
+ // - If no initializer is provided, the TypeScript zero value (from `WriteZeroValueForType`)
23
+ // is used.
24
+ // - Type `T` (or `T_ts`) is obtained from `obj.Type()` and translated via `WriteGoType`.
25
+ //
26
+ // For multiple variable declarations (`var a, b = val1, val2` or `a, b := val1, val2`):
27
+ // - It uses TypeScript array destructuring: `let [a, b] = [val1_ts, val2_ts];`.
28
+ // - If initialized from a single multi-return function call (`a, b := func()`),
29
+ // it becomes `let [a, b] = func_ts();`.
30
+ // - If no initializers are provided, it defaults to `let [a,b] = []` (with a TODO
31
+ // to assign correct individual zero values).
32
+ //
33
+ // Documentation comments associated with the `ValueSpec` are preserved.
34
+ func (c *GoToTSCompiler) WriteValueSpec(a *ast.ValueSpec) error {
35
+ if a.Doc != nil {
36
+ c.WriteDoc(a.Doc)
37
+ }
38
+ if a.Comment != nil {
39
+ c.WriteDoc(a.Comment)
40
+ }
41
+
42
+ // Handle single variable declaration
43
+ if len(a.Names) == 1 {
44
+ name := a.Names[0]
45
+ obj := c.pkg.TypesInfo.Defs[name]
46
+ if obj == nil {
47
+ return fmt.Errorf("could not resolve type: %v", name)
48
+ }
49
+
50
+ goType := obj.Type()
51
+ needsBox := c.analysis.NeedsBoxed(obj) // Check if address is taken
52
+
53
+ hasInitializer := len(a.Values) > 0
54
+ var initializerExpr ast.Expr
55
+ if hasInitializer {
56
+ initializerExpr = a.Values[0]
57
+ }
58
+
59
+ // Check if the initializer will result in an $.arrayToSlice call in TypeScript
60
+ isSliceConversion := false
61
+ if hasInitializer {
62
+ // Case 1: Direct call to $.arrayToSlice in Go source (less common for typical array literals)
63
+ if callExpr, isCallExpr := initializerExpr.(*ast.CallExpr); isCallExpr {
64
+ if selExpr, isSelExpr := callExpr.Fun.(*ast.SelectorExpr); isSelExpr {
65
+ if pkgIdent, isPkgIdent := selExpr.X.(*ast.Ident); isPkgIdent && pkgIdent.Name == "$" {
66
+ if selExpr.Sel.Name == "arrayToSlice" {
67
+ isSliceConversion = true
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ // Case 2: Go array or slice literal, which will be compiled to $.arrayToSlice
74
+ // We also check if the original Go type is actually a slice or array.
75
+ if !isSliceConversion { // Only check if not already determined by Case 1
76
+ if _, isCompositeLit := initializerExpr.(*ast.CompositeLit); isCompositeLit {
77
+ switch goType.Underlying().(type) {
78
+ case *types.Slice, *types.Array:
79
+ isSliceConversion = true
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ // Start declaration
86
+ c.tsw.WriteLiterally("let ")
87
+ c.tsw.WriteLiterally(name.Name)
88
+
89
+ if !isSliceConversion {
90
+ c.tsw.WriteLiterally(": ")
91
+ // Write type annotation
92
+ if needsBox {
93
+ // If boxed, the variable holds Box<OriginalGoType>
94
+ c.tsw.WriteLiterally("$.Box<")
95
+ c.WriteGoType(goType, GoTypeContextGeneral) // Write the original Go type T
96
+ c.tsw.WriteLiterally(">")
97
+ } else {
98
+ // If not boxed, the variable holds the translated Go type directly
99
+ c.WriteGoType(goType, GoTypeContextGeneral)
100
+ }
101
+ }
102
+
103
+ // Write initializer
104
+ c.tsw.WriteLiterally(" = ")
105
+
106
+ // Special case for nil pointer to struct type: (*struct{})(nil)
107
+ if hasInitializer {
108
+ if callExpr, isCallExpr := initializerExpr.(*ast.CallExpr); isCallExpr {
109
+ if starExpr, isStarExpr := callExpr.Fun.(*ast.StarExpr); isStarExpr {
110
+ if _, isStructType := starExpr.X.(*ast.StructType); isStructType {
111
+ // Check if the argument is nil
112
+ if len(callExpr.Args) == 1 {
113
+ if nilIdent, isIdent := callExpr.Args[0].(*ast.Ident); isIdent && nilIdent.Name == "nil" {
114
+ c.tsw.WriteLiterally("null")
115
+ c.tsw.WriteLine("") // Ensure newline after null
116
+ return nil
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ }
123
+
124
+ if needsBox {
125
+ // Boxed variable: let v: Box<T> = $.box(init_or_zero);
126
+ c.tsw.WriteLiterally("$.box(")
127
+ if hasInitializer {
128
+ // Write the compiled initializer expression normally
129
+ if err := c.WriteValueExpr(initializerExpr); err != nil {
130
+ return err
131
+ }
132
+ } else {
133
+ // No initializer, box the zero value
134
+ c.WriteZeroValueForType(goType)
135
+ }
136
+ c.tsw.WriteLiterally(")")
137
+ } else {
138
+ // Unboxed variable: let v: T = init_or_zero;
139
+ if hasInitializer {
140
+ // Handle &v initializer specifically for unboxed variables
141
+ if unaryExpr, isUnary := initializerExpr.(*ast.UnaryExpr); isUnary && unaryExpr.Op == token.AND {
142
+ // Initializer is &v
143
+ // Check if v is boxed
144
+ needsBoxOperand := false
145
+ unaryExprXIdent, ok := unaryExpr.X.(*ast.Ident)
146
+ if ok {
147
+ innerObj := c.pkg.TypesInfo.Uses[unaryExprXIdent]
148
+ needsBoxOperand = innerObj != nil && c.analysis.NeedsBoxed(innerObj)
149
+ }
150
+
151
+ // If v is boxed, assign the box itself (v)
152
+ // If v is not boxed, assign $.box(v)
153
+ if needsBoxOperand {
154
+ // special handling: do not write .value here.
155
+ c.WriteIdent(unaryExprXIdent, false)
156
+ } else {
157
+ // &unboxedVar -> $.box(unboxedVar)
158
+ c.tsw.WriteLiterally("$.box(")
159
+ if err := c.WriteValueExpr(unaryExpr.X); err != nil { // Write 'v'
160
+ return err
161
+ }
162
+ c.tsw.WriteLiterally(")")
163
+ }
164
+ } else {
165
+ // Regular initializer, clone if needed
166
+ if shouldApplyClone(c.pkg, initializerExpr) {
167
+ if err := c.WriteValueExpr(initializerExpr); err != nil {
168
+ return err
169
+ }
170
+ c.tsw.WriteLiterally(".clone()")
171
+ } else {
172
+ if err := c.WriteValueExpr(initializerExpr); err != nil {
173
+ return err
174
+ }
175
+ }
176
+ }
177
+ } else {
178
+ // No initializer, use the zero value directly
179
+ c.WriteZeroValueForType(goType)
180
+ }
181
+ }
182
+ c.tsw.WriteLine("") // Finish the declaration line
183
+ return nil
184
+ }
185
+
186
+ // --- Multi-variable declaration (existing logic seems okay, but less common for pointers) ---
187
+ c.tsw.WriteLiterally("let ")
188
+ c.tsw.WriteLiterally("[") // Use array destructuring for multi-assign
189
+ for i, name := range a.Names {
190
+ if i != 0 {
191
+ c.tsw.WriteLiterally(", ")
192
+ }
193
+ c.tsw.WriteLiterally(name.Name)
194
+ // TODO: Add type annotations for multi-var declarations if possible/needed
195
+ }
196
+ c.tsw.WriteLiterally("]")
197
+ if len(a.Values) > 0 {
198
+ // TODO: handle other kinds of assignment += -= etc.
199
+ c.tsw.WriteLiterally(" = ")
200
+ if len(a.Values) == 1 && len(a.Names) > 1 {
201
+ // Assign from a single multi-return value
202
+ if err := c.WriteValueExpr(a.Values[0]); err != nil {
203
+ return err
204
+ }
205
+ } else {
206
+ // Assign from multiple values
207
+ c.tsw.WriteLiterally("[")
208
+ for i, val := range a.Values {
209
+ if i != 0 {
210
+ c.tsw.WriteLiterally(", ")
211
+ }
212
+ if err := c.WriteValueExpr(val); err != nil { // Initializers are values
213
+ return err
214
+ }
215
+ }
216
+ c.tsw.WriteLiterally("]")
217
+ }
218
+ } else {
219
+ // No initializer, assign default values (complex for multi-assign)
220
+ // For simplicity, assign default array based on context (needs improvement)
221
+ c.tsw.WriteLiterally(" = []") // Placeholder
222
+ // TODO: Assign correct zero values based on types
223
+ }
224
+ c.tsw.WriteLine("") // Use WriteLine instead of WriteLine(";")
225
+ return nil
226
+ }
@@ -0,0 +1,272 @@
1
+ package compiler
2
+
3
+ import (
4
+ "fmt"
5
+ "go/ast"
6
+ "go/types"
7
+ "strings"
8
+
9
+ // types provides type information for Go types.
10
+ "github.com/pkg/errors"
11
+ )
12
+
13
+ // WriteSpec is a dispatcher function that translates a Go specification node
14
+ // (`ast.Spec`) into its TypeScript equivalent. It handles different types of
15
+ // specifications found within `GenDecl` (general declarations):
16
+ // - `ast.ImportSpec` (import declarations): Delegates to `WriteImportSpec`.
17
+ // - `ast.ValueSpec` (variable or constant declarations): Delegates to `WriteValueSpec`.
18
+ // - `ast.TypeSpec` (type definitions like structs, interfaces): Delegates to `WriteTypeSpec`.
19
+ // If an unknown specification type is encountered, it returns an error.
20
+ func (c *GoToTSCompiler) WriteSpec(a ast.Spec) error {
21
+ switch d := a.(type) {
22
+ case *ast.ImportSpec:
23
+ c.WriteImportSpec(d)
24
+ case *ast.ValueSpec:
25
+ if err := c.WriteValueSpec(d); err != nil {
26
+ return err
27
+ }
28
+ case *ast.TypeSpec:
29
+ if err := c.WriteTypeSpec(d); err != nil {
30
+ return err
31
+ }
32
+ default:
33
+ return fmt.Errorf("unknown spec type: %T", a)
34
+ }
35
+ return nil
36
+ }
37
+
38
+ func (c *GoToTSCompiler) getEmbeddedFieldKeyName(fieldType types.Type) string {
39
+ trueType := fieldType
40
+ if ptr, isPtr := trueType.(*types.Pointer); isPtr {
41
+ trueType = ptr.Elem()
42
+ }
43
+
44
+ if named, isNamed := trueType.(*types.Named); isNamed {
45
+ return named.Obj().Name()
46
+ } else {
47
+ // Fallback for unnamed embedded types, though less common for structs
48
+ fieldKeyName := strings.Title(trueType.String()) // Simple heuristic
49
+ if dotIndex := strings.LastIndex(fieldKeyName, "."); dotIndex != -1 {
50
+ fieldKeyName = fieldKeyName[dotIndex+1:]
51
+ }
52
+ return fieldKeyName
53
+ }
54
+ }
55
+
56
+ func (c *GoToTSCompiler) writeGetterSetter(fieldName string, fieldType types.Type, doc, comment *ast.CommentGroup) {
57
+ fieldTypeStr := c.getTypeString(fieldType)
58
+
59
+ // Generate getter
60
+ if doc != nil {
61
+ c.WriteDoc(doc)
62
+ }
63
+ if comment != nil {
64
+ c.WriteDoc(comment)
65
+ }
66
+ c.tsw.WriteLinef("public get %s(): %s {", fieldName, fieldTypeStr)
67
+ c.tsw.Indent(1)
68
+ c.tsw.WriteLinef("return this._fields.%s.value", fieldName)
69
+ c.tsw.Indent(-1)
70
+ c.tsw.WriteLine("}")
71
+
72
+ // Generate setter (no comments)
73
+ c.tsw.WriteLinef("public set %s(value: %s) {", fieldName, fieldTypeStr)
74
+ c.tsw.Indent(1)
75
+ c.tsw.WriteLinef("this._fields.%s.value = value", fieldName)
76
+ c.tsw.Indent(-1)
77
+ c.tsw.WriteLine("}")
78
+ c.tsw.WriteLine("")
79
+ }
80
+
81
+ func (c *GoToTSCompiler) writeBoxedFieldInitializer(fieldName string, fieldType types.Type, isEmbedded bool) {
82
+ c.tsw.WriteLiterally(fieldName)
83
+ c.tsw.WriteLiterally(": $.box(")
84
+
85
+ if isEmbedded {
86
+ if _, isPtr := fieldType.(*types.Pointer); isPtr {
87
+ c.tsw.WriteLiterallyf("init?.%s ?? null", fieldName)
88
+ } else {
89
+ typeForNew := fieldName
90
+ c.tsw.WriteLiterallyf("new %s(init?.%s)", typeForNew, fieldName)
91
+ }
92
+ } else {
93
+ isStructValueType := false
94
+ var structTypeNameForClone string
95
+ if named, ok := fieldType.(*types.Named); ok {
96
+ if _, isStruct := named.Underlying().(*types.Struct); isStruct {
97
+ isStructValueType = true
98
+ structTypeNameForClone = named.Obj().Name()
99
+ }
100
+ }
101
+
102
+ if isStructValueType {
103
+ c.tsw.WriteLiterallyf("init?.%s?.clone() ?? new %s()", fieldName, structTypeNameForClone)
104
+ } else {
105
+ c.tsw.WriteLiterallyf("init?.%s ?? ", fieldName)
106
+ c.WriteZeroValueForType(fieldType)
107
+ }
108
+ }
109
+
110
+ c.tsw.WriteLiterally(")")
111
+ }
112
+
113
+ func (c *GoToTSCompiler) writeClonedFieldInitializer(fieldName string, fieldType types.Type, isEmbedded bool) {
114
+ c.tsw.WriteLiterally(fieldName)
115
+ c.tsw.WriteLiterally(": $.box(")
116
+
117
+ if isEmbedded {
118
+ isPointerToStruct := false
119
+ trueType := fieldType
120
+ if ptr, isPtr := trueType.(*types.Pointer); isPtr {
121
+ trueType = ptr.Elem()
122
+ isPointerToStruct = true
123
+ }
124
+
125
+ if named, isNamed := trueType.(*types.Named); isNamed {
126
+ _, isUnderlyingStruct := named.Underlying().(*types.Struct)
127
+ if isUnderlyingStruct && !isPointerToStruct { // Is a value struct
128
+ c.tsw.WriteLiterallyf("this._fields.%s.value.clone()", fieldName)
129
+ } else { // Is a pointer to a struct, or not a struct
130
+ c.tsw.WriteLiterallyf("this._fields.%s.value", fieldName)
131
+ }
132
+ } else {
133
+ c.tsw.WriteLiterallyf("this._fields.%s.value", fieldName)
134
+ }
135
+ } else {
136
+ isValueTypeStruct := false
137
+ if named, ok := fieldType.(*types.Named); ok {
138
+ if _, isStruct := named.Underlying().(*types.Struct); isStruct {
139
+ isValueTypeStruct = true
140
+ }
141
+ }
142
+
143
+ if isValueTypeStruct {
144
+ c.tsw.WriteLiterallyf("this._fields.%s.value?.clone() ?? null", fieldName)
145
+ } else {
146
+ c.tsw.WriteLiterallyf("this._fields.%s.value", fieldName)
147
+ }
148
+ }
149
+
150
+ c.tsw.WriteLiterally(")")
151
+ }
152
+
153
+ // WriteTypeSpec writes the type specification to the output.
154
+ func (c *GoToTSCompiler) WriteTypeSpec(a *ast.TypeSpec) error {
155
+ if a.Doc != nil {
156
+ c.WriteDoc(a.Doc)
157
+ }
158
+ if a.Comment != nil {
159
+ c.WriteDoc(a.Comment)
160
+ }
161
+
162
+ switch t := a.Type.(type) {
163
+ case *ast.StructType:
164
+ return c.WriteStructTypeSpec(a, t)
165
+ case *ast.InterfaceType:
166
+ return c.WriteInterfaceTypeSpec(a, t)
167
+ default:
168
+ // type alias
169
+ c.tsw.WriteLiterally("type ")
170
+ if err := c.WriteValueExpr(a.Name); err != nil {
171
+ return err
172
+ }
173
+ c.tsw.WriteLiterally(" = ")
174
+ c.WriteTypeExpr(a.Type) // The aliased type
175
+ c.tsw.WriteLine(";")
176
+ }
177
+ return nil
178
+ }
179
+
180
+ // WriteInterfaceTypeSpec writes the TypeScript type for a Go interface type.
181
+ func (c *GoToTSCompiler) WriteInterfaceTypeSpec(a *ast.TypeSpec, t *ast.InterfaceType) error {
182
+ c.tsw.WriteLiterally("type ")
183
+ if err := c.WriteValueExpr(a.Name); err != nil {
184
+ return err
185
+ }
186
+ c.tsw.WriteLiterally(" = ")
187
+ // Get the types.Interface from the ast.InterfaceType.
188
+ // For an interface definition like `type MyInterface interface { M() }`,
189
+ // 't' is the *ast.InterfaceType representing `interface { M() }`.
190
+ // TypesInfo.TypeOf(t) will give the *types.Interface.
191
+ goType := c.pkg.TypesInfo.TypeOf(t)
192
+ if goType == nil {
193
+ return errors.Errorf("could not get type for interface AST node for %s", a.Name.Name)
194
+ }
195
+ ifaceType, ok := goType.(*types.Interface)
196
+ if !ok {
197
+ return errors.Errorf("expected *types.Interface, got %T for %s when processing interface literal", goType, a.Name.Name)
198
+ }
199
+ c.WriteInterfaceType(ifaceType, t) // Pass the *ast.InterfaceType for comment fetching
200
+ c.tsw.WriteLine("")
201
+
202
+ // Add code to register the interface with the runtime system
203
+ interfaceName := a.Name.Name
204
+ c.tsw.WriteLine("")
205
+ c.tsw.WriteLinef("$.registerInterfaceType(")
206
+ c.tsw.WriteLinef(" '%s',", interfaceName)
207
+ c.tsw.WriteLinef(" null, // Zero value for interface is null")
208
+
209
+ // Collect methods for the interface type
210
+ var interfaceMethods []*types.Func
211
+ if ifaceType != nil { // ifaceType is *types.Interface
212
+ for i := range ifaceType.NumExplicitMethods() {
213
+ interfaceMethods = append(interfaceMethods, ifaceType.ExplicitMethod(i))
214
+ }
215
+ // TODO: Handle embedded interface methods if necessary for full signature collection.
216
+ // For now, explicit methods are covered.
217
+ }
218
+ c.tsw.WriteLiterally(" [")
219
+ c.writeMethodSignatures(interfaceMethods)
220
+ c.tsw.WriteLiterally("]")
221
+ c.tsw.WriteLine("")
222
+
223
+ c.tsw.WriteLinef(");")
224
+
225
+ return nil
226
+ }
227
+
228
+ // WriteImportSpec translates a Go import specification (`ast.ImportSpec`)
229
+ // into a TypeScript import statement.
230
+ //
231
+ // It extracts the Go import path (e.g., `"path/to/pkg"`) and determines the
232
+ // import alias/name for TypeScript. If the Go import has an explicit name
233
+ // (e.g., `alias "path/to/pkg"`), that alias is used. Otherwise, the package
234
+ // name is derived from the Go path.
235
+ //
236
+ // The Go path is then translated to a TypeScript module path using
237
+ // `translateGoPathToTypescriptPath`.
238
+ //
239
+ // Finally, it writes a TypeScript import statement like `import * as alias from "typescript/path/to/pkg";`
240
+ // and records the import details in `c.analysis.Imports` for later use (e.g.,
241
+ // resolving qualified identifiers).
242
+ func (c *GoToTSCompiler) WriteImportSpec(a *ast.ImportSpec) {
243
+ if a.Doc != nil {
244
+ c.WriteDoc(a.Doc)
245
+ }
246
+ if a.Comment != nil {
247
+ c.WriteDoc(a.Comment)
248
+ }
249
+
250
+ goPath := a.Path.Value[1 : len(a.Path.Value)-1]
251
+ impName := packageNameFromGoPath(goPath)
252
+ if a.Name != nil && a.Name.Name != "" {
253
+ impName = a.Name.Name
254
+ }
255
+
256
+ // All Go package imports are mapped to the @goscript/ scope.
257
+ // The TypeScript compiler will resolve these using tsconfig paths to either
258
+ // handwritten versions (in .goscript-assets) or transpiled versions (in goscript).
259
+ var tsImportPath string
260
+ if goPath == "github.com/aperturerobotics/goscript/builtin" {
261
+ tsImportPath = "@goscript/builtin/builtin.js"
262
+ } else {
263
+ tsImportPath = "@goscript/" + goPath
264
+ }
265
+
266
+ c.analysis.Imports[impName] = &fileImport{
267
+ importPath: tsImportPath,
268
+ importVars: make(map[string]struct{}),
269
+ }
270
+
271
+ c.tsw.WriteImport(impName, tsImportPath+"/index.js")
272
+ }