goscript 0.0.8 → 0.0.11

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.
Files changed (78) hide show
  1. package/builtin/builtin.ts +277 -1
  2. package/cmd/goscript/cmd_compile.go +8 -6
  3. package/compiler/compile.go +28 -11
  4. package/compiler/compile_decls.go +12 -0
  5. package/compiler/compile_expr.go +178 -27
  6. package/compiler/compile_spec.go +285 -5
  7. package/compiler/compile_stmt.go +160 -49
  8. package/compiler/writer.go +2 -2
  9. package/dist/builtin/builtin.d.ts +112 -1
  10. package/dist/builtin/builtin.js +199 -0
  11. package/dist/builtin/builtin.js.map +1 -1
  12. package/dist/compliance/tests/array_literal/array_literal.gs.js +1 -0
  13. package/dist/compliance/tests/array_literal/array_literal.gs.js.map +1 -1
  14. package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.d.ts +1 -0
  15. package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js +82 -0
  16. package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js.map +1 -0
  17. package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js +4 -0
  18. package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js.map +1 -1
  19. package/dist/compliance/tests/copy_independence/copy_independence.gs.js +3 -1
  20. package/dist/compliance/tests/copy_independence/copy_independence.gs.js.map +1 -1
  21. package/dist/compliance/tests/defer_statement/defer_statement.gs.d.ts +1 -0
  22. package/dist/compliance/tests/defer_statement/defer_statement.gs.js +75 -0
  23. package/dist/compliance/tests/defer_statement/defer_statement.gs.js.map +1 -0
  24. package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.d.ts +1 -0
  25. package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js +37 -0
  26. package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js.map +1 -0
  27. package/dist/compliance/tests/float64/float64.gs.js +1 -0
  28. package/dist/compliance/tests/float64/float64.gs.js.map +1 -1
  29. package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.d.ts +1 -0
  30. package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js +14 -0
  31. package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js.map +1 -0
  32. package/dist/compliance/tests/func_literal/func_literal.gs.js.map +1 -1
  33. package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.d.ts +2 -0
  34. package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js +4 -0
  35. package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js.map +1 -1
  36. package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.d.ts +1 -0
  37. package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js +34 -0
  38. package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js.map +1 -0
  39. package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js +8 -2
  40. package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js.map +1 -1
  41. package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js +16 -2
  42. package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js.map +1 -1
  43. package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.d.ts +1 -0
  44. package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js +51 -0
  45. package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js.map +1 -0
  46. package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js +3 -1
  47. package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js.map +1 -1
  48. package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js +3 -0
  49. package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js.map +1 -1
  50. package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js +3 -0
  51. package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js.map +1 -1
  52. package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js +3 -0
  53. package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js.map +1 -1
  54. package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js +3 -0
  55. package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js.map +1 -1
  56. package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js +3 -0
  57. package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js.map +1 -1
  58. package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js +3 -0
  59. package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js.map +1 -1
  60. package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js +3 -1
  61. package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js.map +1 -1
  62. package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js +1 -0
  63. package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js.map +1 -1
  64. package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js +3 -1
  65. package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js.map +1 -1
  66. package/dist/compliance/tests/slices/slices.gs.js +280 -7
  67. package/dist/compliance/tests/slices/slices.gs.js.map +1 -1
  68. package/dist/compliance/tests/string_rune_conversion/string_rune_conversion.gs.js.map +1 -1
  69. package/dist/compliance/tests/struct_embedding/struct_embedding.gs.d.ts +1 -0
  70. package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js +48 -0
  71. package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js.map +1 -0
  72. package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js +3 -0
  73. package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js.map +1 -1
  74. package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js +3 -0
  75. package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js.map +1 -1
  76. package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js +3 -1
  77. package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js.map +1 -1
  78. package/package.json +4 -3
@@ -13,6 +13,34 @@ export const makeSlice = <T>(
13
13
  return slice
14
14
  }
15
15
 
16
+ /**
17
+ * Creates a new slice header that shares the backing array.
18
+ * Arguments mirror Go semantics; omitted indices are undefined.
19
+ *
20
+ * @param arr The original slice/array produced by makeSlice or another slice
21
+ * @param low Starting index (defaults to 0)
22
+ * @param high Ending index (defaults to arr.length)
23
+ * @param max Capacity limit (defaults to original capacity)
24
+ */
25
+ export const slice = <T>(
26
+ arr: Array<T> & { __capacity?: number },
27
+ low?: number,
28
+ high?: number,
29
+ max?: number,
30
+ ): Array<T> & { __capacity?: number } => {
31
+ const start = low ?? 0
32
+ const origLen = arr.length
33
+ const origCap = arr.__capacity !== undefined ? arr.__capacity : origLen
34
+ const end = high !== undefined ? high : origLen
35
+ const newCap = max !== undefined ? max - start : origCap - start
36
+
37
+ const newArr = arr.slice(start, end) as Array<T> & {
38
+ __capacity?: number
39
+ }
40
+ newArr.__capacity = newCap
41
+ return newArr
42
+ }
43
+
16
44
  /**
17
45
  * Creates a new map (TypeScript Map).
18
46
  * @returns A new TypeScript Map.
@@ -46,6 +74,13 @@ export const cap = <T>(slice: Array<T> & { __capacity?: number }): number => {
46
74
  return slice.__capacity !== undefined ? slice.__capacity : slice.length
47
75
  }
48
76
 
77
+ /**
78
+ * Represents the Go error type (interface).
79
+ */
80
+ export type Error = {
81
+ Error(): string
82
+ } | null
83
+
49
84
  /**
50
85
  * Converts a string to an array of Unicode code points (runes).
51
86
  * @param str The input string.
@@ -106,11 +141,195 @@ export const mapHas = <K, V>(map: Map<K, V>, key: K): boolean => {
106
141
  * @param elements The elements to append.
107
142
  * @returns The modified slice (TypeScript array).
108
143
  */
109
- export const append = <T>(slice: Array<T>, ...elements: T[]): Array<T> => {
144
+ export const append = <T>(
145
+ slice: Array<T> & { __capacity?: number },
146
+ ...elements: T[]
147
+ ): Array<T> & { __capacity?: number } => {
110
148
  slice.push(...elements)
149
+ if (slice.__capacity !== undefined && slice.length > slice.__capacity) {
150
+ slice.__capacity = slice.length
151
+ }
111
152
  return slice
112
153
  }
113
154
 
155
+ /**
156
+ * Represents the kinds of Go types that can be registered at runtime.
157
+ */
158
+ export enum TypeKind {
159
+ Struct = 'struct',
160
+ Interface = 'interface',
161
+ Basic = 'basic',
162
+ Pointer = 'pointer',
163
+ Slice = 'slice',
164
+ Map = 'map',
165
+ Channel = 'channel',
166
+ Function = 'function',
167
+ }
168
+
169
+ /**
170
+ * Represents type information for a Go type in the runtime.
171
+ */
172
+ export interface TypeInfo {
173
+ name: string
174
+ kind: TypeKind
175
+ zeroValue: any
176
+ // For interfaces, the set of methods
177
+ methods?: Set<string>
178
+ // For structs, the constructor
179
+ constructor?: new (...args: any[]) => any
180
+ }
181
+
182
+ // Registry to store runtime type information
183
+ const typeRegistry = new Map<string, TypeInfo>()
184
+
185
+ /**
186
+ * Registers a type with the runtime type system.
187
+ *
188
+ * @param name The name of the type.
189
+ * @param kind The kind of the type.
190
+ * @param zeroValue The zero value for the type.
191
+ * @param methods Optional set of method names for interfaces.
192
+ * @param constructor Optional constructor for structs.
193
+ * @returns The type information object for chaining.
194
+ */
195
+ export const registerType = (
196
+ name: string,
197
+ kind: TypeKind,
198
+ zeroValue: any,
199
+ methods?: Set<string>,
200
+ constructor?: new (...args: any[]) => any,
201
+ ): TypeInfo => {
202
+ const typeInfo: TypeInfo = {
203
+ name,
204
+ kind,
205
+ zeroValue,
206
+ methods,
207
+ constructor,
208
+ }
209
+ typeRegistry.set(name, typeInfo)
210
+ return typeInfo
211
+ }
212
+
213
+ /**
214
+ * Represents the result of a type assertion.
215
+ */
216
+ export interface TypeAssertResult<T> {
217
+ value: T
218
+ ok: boolean
219
+ }
220
+
221
+ /**
222
+ * Performs a type assertion at runtime.
223
+ *
224
+ * @param value The value to assert.
225
+ * @param typeName The name of the target type.
226
+ * @returns An object with the asserted value and whether the assertion succeeded.
227
+ */
228
+ export function typeAssert<T>(
229
+ value: any,
230
+ typeName: string,
231
+ ): TypeAssertResult<T> {
232
+ // Get the type information from the registry
233
+ const typeInfo = typeRegistry.get(typeName)
234
+ if (!typeInfo) {
235
+ console.warn(`Type information for '${typeName}' not found in registry.`)
236
+ return { value: null as unknown as T, ok: false }
237
+ }
238
+
239
+ // If value is null or undefined, assertion fails
240
+ if (value === null || value === undefined) {
241
+ return { value: typeInfo.zeroValue as T, ok: false }
242
+ }
243
+
244
+ // Check based on the kind of the target type
245
+ switch (typeInfo.kind) {
246
+ case TypeKind.Struct:
247
+ // For structs, use instanceof with the constructor
248
+ if (typeInfo.constructor && value instanceof typeInfo.constructor) {
249
+ return { value: value as T, ok: true }
250
+ }
251
+ break
252
+
253
+ case TypeKind.Interface:
254
+ // For interfaces, check if the value has all the required methods
255
+ if (typeInfo.methods && typeof value === 'object') {
256
+ const allMethodsPresent = Array.from(typeInfo.methods).every(
257
+ (method) => typeof (value as any)[method] === 'function',
258
+ )
259
+ if (allMethodsPresent) {
260
+ return { value: value as T, ok: true }
261
+ }
262
+ }
263
+ break
264
+
265
+ case TypeKind.Basic:
266
+ // For basic types, check if the value matches the expected JavaScript type
267
+ // This is a simple check for common basic types
268
+ const basicType = typeof value
269
+ if (
270
+ basicType === 'string' ||
271
+ basicType === 'number' ||
272
+ basicType === 'boolean'
273
+ ) {
274
+ return { value: value as T, ok: true }
275
+ }
276
+ break
277
+
278
+ case TypeKind.Pointer:
279
+ // For pointers, check if value is not null or undefined
280
+ // In Go, pointers can be nil which we represent as null/undefined in TS
281
+ if (value !== null && value !== undefined) {
282
+ return { value: value as T, ok: true }
283
+ }
284
+ break
285
+
286
+ case TypeKind.Slice:
287
+ // For slices, check if the value is an array
288
+ if (Array.isArray(value)) {
289
+ return { value: value as T, ok: true }
290
+ }
291
+ break
292
+
293
+ case TypeKind.Map:
294
+ // For maps, check if the value is a Map
295
+ if (value instanceof Map) {
296
+ return { value: value as T, ok: true }
297
+ }
298
+ break
299
+
300
+ case TypeKind.Channel:
301
+ // For channels, check if the value has the required Channel interface methods
302
+ if (
303
+ typeof value === 'object' &&
304
+ value !== null &&
305
+ 'send' in value &&
306
+ 'receive' in value &&
307
+ 'close' in value &&
308
+ typeof value.send === 'function' &&
309
+ typeof value.receive === 'function' &&
310
+ typeof value.close === 'function'
311
+ ) {
312
+ return { value: value as T, ok: true }
313
+ }
314
+ break
315
+
316
+ case TypeKind.Function:
317
+ // For functions, check if the value is a function
318
+ if (typeof value === 'function') {
319
+ return { value: value as T, ok: true }
320
+ }
321
+ break
322
+
323
+ default:
324
+ console.warn(
325
+ `Type assertion for kind '${typeInfo.kind}' not implemented.`,
326
+ )
327
+ }
328
+
329
+ // Assertion failed
330
+ return { value: typeInfo.zeroValue as T, ok: false }
331
+ }
332
+
114
333
  /**
115
334
  * Represents the result of a channel receive operation with 'ok' value
116
335
  */
@@ -535,3 +754,60 @@ export const makeChannel = <T>(
535
754
  ): Channel<T> => {
536
755
  return new BufferedChannel<T>(bufferSize, zeroValue)
537
756
  }
757
+
758
+ /**
759
+ * DisposableStack manages synchronous disposable resources, mimicking Go's defer behavior.
760
+ * Functions added via `defer` are executed in LIFO order when the stack is disposed.
761
+ * Implements the `Disposable` interface for use with `using` declarations.
762
+ */
763
+ export class DisposableStack implements Disposable {
764
+ #stack: (() => void)[] = [];
765
+
766
+ /**
767
+ * Adds a function to be executed when the stack is disposed.
768
+ * @param fn The function to defer.
769
+ */
770
+ defer(fn: () => void): void { this.#stack.push(fn); }
771
+
772
+ /**
773
+ * Disposes of the resources in the stack by executing the deferred functions
774
+ * in Last-In, First-Out (LIFO) order.
775
+ * If a deferred function throws an error, disposal stops, and the error is rethrown,
776
+ * similar to Go's panic behavior during defer execution.
777
+ */
778
+ [Symbol.dispose](): void {
779
+ // Emulate Go: if a deferred throws, stop and rethrow
780
+ while (this.#stack.length) {
781
+ const fn = this.#stack.pop()!;
782
+ fn();
783
+ }
784
+ }
785
+ }
786
+
787
+ /**
788
+ * AsyncDisposableStack manages asynchronous disposable resources, mimicking Go's defer behavior.
789
+ * Functions added via `defer` are executed sequentially in LIFO order when the stack is disposed.
790
+ * Implements the `AsyncDisposable` interface for use with `await using` declarations.
791
+ */
792
+ export class AsyncDisposableStack implements AsyncDisposable {
793
+ #stack: (() => Promise<void> | void)[] = [];
794
+
795
+ /**
796
+ * Adds a synchronous or asynchronous function to be executed when the stack is disposed.
797
+ * @param fn The function to defer. Can return void or a Promise<void>.
798
+ */
799
+ defer(fn: () => Promise<void> | void): void {
800
+ this.#stack.push(fn);
801
+ }
802
+
803
+ /**
804
+ * Asynchronously disposes of the resources in the stack by executing the deferred functions
805
+ * sequentially in Last-In, First-Out (LIFO) order. It awaits each function if it returns a promise.
806
+ */
807
+ async [Symbol.asyncDispose](): Promise<void> {
808
+ // Execute in LIFO order, awaiting each potentially async function
809
+ for (let i = this.#stack.length - 1; i >= 0; --i) {
810
+ await this.#stack[i]();
811
+ }
812
+ }
813
+ }
@@ -12,7 +12,7 @@ import (
12
12
  var (
13
13
  cliCompiler *compiler.Compiler
14
14
  cliCompilerConfig compiler.Config
15
- cliCompilerPkg string
15
+ cliCompilerPkg cli.StringSlice
16
16
  )
17
17
 
18
18
  // CompileCommands are commands related to compiling code.
@@ -29,9 +29,10 @@ var CompileCommands = []*cli.Command{{
29
29
  return
30
30
  },
31
31
  Flags: []cli.Flag{
32
- &cli.StringFlag{
32
+ &cli.StringSliceFlag{
33
33
  Name: "package",
34
- Usage: "the package to compile",
34
+ Usage: "the package(s) to compile",
35
+ Aliases: []string{"p", "packages"},
35
36
  Destination: &cliCompilerPkg,
36
37
  },
37
38
  &cli.StringFlag{
@@ -51,9 +52,10 @@ var CompileCommands = []*cli.Command{{
51
52
 
52
53
  // compilePackage tries to compile the package.
53
54
  func compilePackage(c *cli.Context) error {
54
- if cliCompilerPkg == "" {
55
- return errors.New("package must be specified")
55
+ pkgs := cliCompilerPkg.Value()
56
+ if len(pkgs) == 0 {
57
+ return errors.New("package(s) must be specified")
56
58
  }
57
59
 
58
- return cliCompiler.CompilePackages(context.Background(), cliCompilerPkg)
60
+ return cliCompiler.CompilePackages(context.Background(), pkgs...)
59
61
  }
@@ -12,11 +12,13 @@ import (
12
12
 
13
13
  // GoToTSCompiler compiles Go code to TypeScript code.
14
14
  type GoToTSCompiler struct {
15
- tsw *TSCodeWriter
16
- imports map[string]*fileImport
17
- pkg *packages.Package
18
- cmap ast.CommentMap
19
- asyncFuncs map[string]bool // Track which functions are async
15
+ tsw *TSCodeWriter
16
+ imports map[string]*fileImport
17
+ pkg *packages.Package
18
+ cmap ast.CommentMap
19
+ asyncFuncs map[string]bool // Track which functions are async
20
+ nextBlockNeedsDefer bool // Track if the next block should have a "using" statement
21
+ inAsyncFunction bool // Track if we're inside an async function
20
22
 
21
23
  tempVarCounter int // Counter for generating unique temporary variable names
22
24
  }
@@ -68,15 +70,30 @@ func (c *GoToTSCompiler) WriteGoType(typ types.Type) {
68
70
  }
69
71
  }
70
72
 
73
+ // scanForDefer checks if a block contains any defer statements (recursively).
74
+ func (c *GoToTSCompiler) scanForDefer(block *ast.BlockStmt) bool {
75
+ hasDefer := false
76
+ ast.Inspect(block, func(n ast.Node) bool {
77
+ if _, ok := n.(*ast.DeferStmt); ok {
78
+ hasDefer = true
79
+ return false // Stop traversal
80
+ }
81
+ return true // Continue traversal
82
+ })
83
+ return hasDefer
84
+ }
85
+
71
86
  // NewGoToTSCompiler builds a new GoToTSCompiler
72
87
  func NewGoToTSCompiler(tsw *TSCodeWriter, pkg *packages.Package, cmap ast.CommentMap) *GoToTSCompiler {
73
88
  return &GoToTSCompiler{
74
- tsw: tsw,
75
- imports: make(map[string]*fileImport),
76
- pkg: pkg,
77
- cmap: cmap,
78
- asyncFuncs: make(map[string]bool),
79
- tempVarCounter: 0, // Initialize counter
89
+ tsw: tsw,
90
+ imports: make(map[string]*fileImport),
91
+ pkg: pkg,
92
+ cmap: cmap,
93
+ asyncFuncs: make(map[string]bool),
94
+ nextBlockNeedsDefer: false,
95
+ inAsyncFunction: false,
96
+ tempVarCounter: 0, // Initialize counter
80
97
  }
81
98
  }
82
99
 
@@ -65,8 +65,20 @@ func (c *GoToTSCompiler) WriteFuncDeclAsFunction(decl *ast.FuncDecl) error {
65
65
  // WriteFuncType needs to be aware if the function is async
66
66
  c.WriteFuncType(decl.Type, isAsync) // Write signature (params, return type)
67
67
  c.tsw.WriteLiterally(" ")
68
+
69
+ // Check if function body has defer statements
70
+ c.nextBlockNeedsDefer = c.scanForDefer(decl.Body)
71
+
72
+ // Save previous async state and set current state based on isAsync
73
+ previousAsyncState := c.inAsyncFunction
74
+ c.inAsyncFunction = isAsync
75
+
68
76
  if err := c.WriteStmt(decl.Body); err != nil {
77
+ c.inAsyncFunction = previousAsyncState // Restore state before returning error
69
78
  return fmt.Errorf("failed to write function body: %w", err)
70
79
  }
80
+
81
+ // Restore previous async state
82
+ c.inAsyncFunction = previousAsyncState
71
83
  return nil
72
84
  }
@@ -7,6 +7,7 @@ import (
7
7
  "go/token"
8
8
  gtypes "go/types"
9
9
  "strconv"
10
+ "strings"
10
11
 
11
12
  gstypes "github.com/paralin/goscript/compiler/types"
12
13
  )
@@ -72,6 +73,9 @@ func (c *GoToTSCompiler) WriteValueExpr(a ast.Expr) error {
72
73
  return c.WriteCompositeLitValue(exp)
73
74
  case *ast.KeyValueExpr:
74
75
  return c.WriteKeyValueExprValue(exp)
76
+ case *ast.TypeAssertExpr:
77
+ // Handle type assertion in an expression context
78
+ return c.WriteTypeAssertExpr(exp)
75
79
  case *ast.IndexExpr:
76
80
  // Handle map access: use Map.get() instead of brackets for reading values
77
81
  if tv, ok := c.pkg.TypesInfo.Types[exp.X]; ok {
@@ -104,6 +108,43 @@ func (c *GoToTSCompiler) WriteValueExpr(a ast.Expr) error {
104
108
  }
105
109
  c.tsw.WriteLiterally("]")
106
110
  return nil
111
+ case *ast.SliceExpr:
112
+ // Translate Go slice expression to goscript.slice(x, low, high, max)
113
+ c.tsw.WriteLiterally("goscript.slice(")
114
+ if err := c.WriteValueExpr(exp.X); err != nil {
115
+ return err
116
+ }
117
+ // low argument
118
+ c.tsw.WriteLiterally(", ")
119
+ if exp.Low != nil {
120
+ if err := c.WriteValueExpr(exp.Low); err != nil {
121
+ return err
122
+ }
123
+ } else {
124
+ c.tsw.WriteLiterally("undefined")
125
+ }
126
+ // high argument
127
+ c.tsw.WriteLiterally(", ")
128
+ if exp.High != nil {
129
+ if err := c.WriteValueExpr(exp.High); err != nil {
130
+ return err
131
+ }
132
+ } else {
133
+ c.tsw.WriteLiterally("undefined")
134
+ }
135
+ // max argument (only for full slice expressions)
136
+ if exp.Slice3 {
137
+ c.tsw.WriteLiterally(", ")
138
+ if exp.Max != nil {
139
+ if err := c.WriteValueExpr(exp.Max); err != nil {
140
+ return err
141
+ }
142
+ } else {
143
+ c.tsw.WriteLiterally("undefined")
144
+ }
145
+ }
146
+ c.tsw.WriteLiterally(")")
147
+ return nil
107
148
  case *ast.ParenExpr:
108
149
  // Translate (X) to (X)
109
150
  c.tsw.WriteLiterally("(")
@@ -121,11 +162,51 @@ func (c *GoToTSCompiler) WriteValueExpr(a ast.Expr) error {
121
162
  }
122
163
  }
123
164
 
165
+ // WriteTypeAssertExpr writes a type assertion expression.
166
+ func (c *GoToTSCompiler) WriteTypeAssertExpr(exp *ast.TypeAssertExpr) error {
167
+ // Get the type name string for the asserted type
168
+ typeName := c.getTypeNameString(exp.Type)
169
+
170
+ // Generate a call to goscript.typeAssert
171
+ c.tsw.WriteLiterally("goscript.typeAssert<")
172
+ c.WriteTypeExpr(exp.Type) // Write the asserted type for the generic
173
+ c.tsw.WriteLiterally(">(")
174
+ if err := c.WriteValueExpr(exp.X); err != nil { // The interface expression
175
+ return fmt.Errorf("failed to write interface expression in type assertion expression: %w", err)
176
+ }
177
+ c.tsw.WriteLiterally(", ")
178
+ c.tsw.WriteLiterally(fmt.Sprintf("'%s'", typeName))
179
+ c.tsw.WriteLiterally(").value") // Access the value field directly in expression context
180
+ return nil
181
+ }
182
+
183
+ // getTypeNameString returns the string representation of a type name
184
+ func (c *GoToTSCompiler) getTypeNameString(typeExpr ast.Expr) string {
185
+ switch t := typeExpr.(type) {
186
+ case *ast.Ident:
187
+ return t.Name
188
+ case *ast.SelectorExpr:
189
+ // For imported types like pkg.Type
190
+ if ident, ok := t.X.(*ast.Ident); ok {
191
+ return fmt.Sprintf("%s.%s", ident.Name, t.Sel.Name)
192
+ }
193
+ }
194
+ // Default case, use a placeholder for complex types
195
+ return "unknown"
196
+ }
197
+
124
198
  // --- Exported Node-Specific Writers ---
125
199
 
126
200
  // WriteIdentType writes an identifier used as a type.
127
201
  func (c *GoToTSCompiler) WriteIdentType(exp *ast.Ident) {
128
202
  name := exp.Name
203
+
204
+ // Special case for the built-in error interface
205
+ if name == "error" {
206
+ c.tsw.WriteLiterally("goscript.Error")
207
+ return
208
+ }
209
+
129
210
  if tsname, ok := gstypes.GoBuiltinToTypescript(name); ok {
130
211
  name = tsname
131
212
  } else {
@@ -150,9 +231,13 @@ func (c *GoToTSCompiler) WriteIdentType(exp *ast.Ident) {
150
231
  c.tsw.WriteLiterally(name)
151
232
  }
152
233
 
153
- // WriteIdentValue writes an identifier used as a value (variable, function name).
234
+ // WriteIdentValue writes an identifier used as a value (variable, function name, nil).
154
235
  func (c *GoToTSCompiler) WriteIdentValue(exp *ast.Ident) {
155
- c.tsw.WriteLiterally(exp.Name)
236
+ if exp.Name == "nil" {
237
+ c.tsw.WriteLiterally("null")
238
+ } else {
239
+ c.tsw.WriteLiterally(exp.Name)
240
+ }
156
241
  }
157
242
 
158
243
  // WriteSelectorExprType writes a selector expression used as a type (e.g., pkg.Type).
@@ -205,32 +290,47 @@ func (c *GoToTSCompiler) WriteStructType(exp *ast.StructType) {
205
290
 
206
291
  // WriteInterfaceType writes an interface type definition.
207
292
  func (c *GoToTSCompiler) WriteInterfaceType(exp *ast.InterfaceType) {
208
- if exp.Methods == nil || exp.Methods.NumFields() == 0 {
209
- c.tsw.WriteLiterally("{}")
210
- return
211
- }
212
- c.tsw.WriteLine("{")
213
- c.tsw.Indent(1)
214
- for _, method := range exp.Methods.List {
215
- if len(method.Names) > 0 {
216
- // Keep original Go casing for method names
217
- methodName := method.Names[0]
218
- c.WriteIdentValue(methodName)
219
-
220
- // Method signature is a FuncType
221
- if funcType, ok := method.Type.(*ast.FuncType); ok {
222
- c.WriteFuncType(funcType, false) // Interface method signatures are not async
293
+ var embeddedInterfaces []string
294
+ var methods []*ast.Field
295
+
296
+ if exp.Methods != nil {
297
+ for _, method := range exp.Methods.List {
298
+ if len(method.Names) > 0 {
299
+ // Named method
300
+ methods = append(methods, method)
223
301
  } else {
224
- // Should not happen for valid interfaces, but handle defensively
225
- c.tsw.WriteCommentInline("unexpected method type")
302
+ // Embedded interface - collect the type name using type info
303
+ if tv, ok := c.pkg.TypesInfo.Types[method.Type]; ok && tv.Type != nil {
304
+ if namedType, ok := tv.Type.(*gtypes.Named); ok {
305
+ embeddedInterfaces = append(embeddedInterfaces, namedType.Obj().Name())
306
+ } else {
307
+ c.tsw.WriteCommentLine(fmt.Sprintf("// Unhandled embedded interface type: %T", tv.Type))
308
+ }
309
+ } else {
310
+ c.tsw.WriteCommentLine("// Could not resolve embedded interface type")
311
+ }
226
312
  }
227
- c.tsw.WriteLine(";")
228
- } else {
229
- // Embedded interface - write the type name
230
- c.WriteTypeExpr(method.Type)
231
- c.tsw.WriteLine("; // Embedded interface - requires manual merging or mixin in TS")
232
313
  }
233
314
  }
315
+
316
+ // If there are embedded interfaces, write the extends clause
317
+ if len(embeddedInterfaces) > 0 {
318
+ c.tsw.WriteLiterally(" extends ")
319
+ c.tsw.WriteLiterally(strings.Join(embeddedInterfaces, ", "))
320
+ }
321
+
322
+ // Write the interface body on the same line
323
+ c.tsw.WriteLiterally("{") // Removed leading space
324
+ c.tsw.WriteLine("") // Newline after opening brace
325
+ c.tsw.Indent(1)
326
+
327
+ // Write named methods
328
+ for _, method := range methods {
329
+ if err := c.WriteInterfaceMethodSignature(method); err != nil {
330
+ c.tsw.WriteCommentInline(fmt.Sprintf("error writing interface method signature: %v", err))
331
+ }
332
+ }
333
+
234
334
  c.tsw.Indent(-1)
235
335
  c.tsw.WriteLine("}")
236
336
  }
@@ -370,7 +470,20 @@ func (c *GoToTSCompiler) WriteCallExpr(exp *ast.CallExpr) error {
370
470
 
371
471
  // Handle slice creation
372
472
  if _, ok := exp.Args[0].(*ast.ArrayType); ok {
373
- c.tsw.WriteLiterally("goscript.makeSlice(")
473
+ // Get the slice type information
474
+ sliceType := c.pkg.TypesInfo.TypeOf(exp.Args[0])
475
+ if sliceType == nil {
476
+ return errors.New("could not get type information for slice in make call")
477
+ }
478
+ goElemType, ok := sliceType.Underlying().(*gtypes.Slice)
479
+ if !ok {
480
+ return errors.New("expected slice type for make call")
481
+ }
482
+
483
+ c.tsw.WriteLiterally("goscript.makeSlice<")
484
+ c.WriteGoType(goElemType.Elem()) // Write the element type
485
+ c.tsw.WriteLiterally(">(")
486
+
374
487
  if len(exp.Args) >= 2 {
375
488
  if err := c.WriteValueExpr(exp.Args[1]); err != nil { // Length
376
489
  return err
@@ -790,12 +903,50 @@ func (c *GoToTSCompiler) WriteFuncLitValue(exp *ast.FuncLit) error {
790
903
  // Write arrow function: (params) => { body }
791
904
  c.tsw.WriteLiterally("(")
792
905
  c.WriteFieldList(exp.Type.Params, true) // true = arguments
793
- c.tsw.WriteLiterally(") => ")
906
+ c.tsw.WriteLiterally(")")
907
+
908
+ // Handle return type for function literals
909
+ if exp.Type.Results != nil && len(exp.Type.Results.List) > 0 {
910
+ c.tsw.WriteLiterally(": ")
911
+ if isAsync {
912
+ c.tsw.WriteLiterally("Promise<")
913
+ }
914
+ if len(exp.Type.Results.List) == 1 && len(exp.Type.Results.List[0].Names) == 0 {
915
+ c.WriteTypeExpr(exp.Type.Results.List[0].Type)
916
+ } else {
917
+ c.tsw.WriteLiterally("[")
918
+ for i, field := range exp.Type.Results.List {
919
+ if i > 0 {
920
+ c.tsw.WriteLiterally(", ")
921
+ }
922
+ c.WriteTypeExpr(field.Type)
923
+ }
924
+ c.tsw.WriteLiterally("]")
925
+ }
926
+ if isAsync {
927
+ c.tsw.WriteLiterally(">")
928
+ }
929
+ } else {
930
+ if isAsync {
931
+ c.tsw.WriteLiterally(": Promise<void>")
932
+ } else {
933
+ c.tsw.WriteLiterally(": void")
934
+ }
935
+ }
936
+
937
+ c.tsw.WriteLiterally(" => ")
938
+
939
+ // Save previous async state and set current state based on isAsync
940
+ previousAsyncState := c.inAsyncFunction
941
+ c.inAsyncFunction = isAsync
794
942
 
795
943
  // Write function body
796
944
  if err := c.WriteStmt(exp.Body); err != nil {
945
+ c.inAsyncFunction = previousAsyncState // Restore state before returning error
797
946
  return fmt.Errorf("failed to write function literal body: %w", err)
798
947
  }
799
-
948
+
949
+ // Restore previous async state
950
+ c.inAsyncFunction = previousAsyncState
800
951
  return nil
801
952
  }