goscript 0.0.10 → 0.0.13

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 (103) hide show
  1. package/README.md +3 -3
  2. package/builtin/builtin.go +11 -0
  3. package/builtin/builtin.ts +170 -65
  4. package/cmd/goscript/cmd_compile.go +28 -8
  5. package/compiler/compile.go +42 -34
  6. package/compiler/compile_decls.go +12 -0
  7. package/compiler/compile_expr.go +135 -8
  8. package/compiler/compile_field.go +22 -1
  9. package/compiler/compile_spec.go +141 -4
  10. package/compiler/compile_stmt.go +124 -43
  11. package/compiler/compiler.go +1 -0
  12. package/compiler/config.go +2 -0
  13. package/compiler/file_compiler.go +1 -1
  14. package/compiler/index.test.ts +1 -1
  15. package/compiler/output_path.go +1 -1
  16. package/compiler/types/tokens.go +1 -0
  17. package/compiler/writer.go +1 -1
  18. package/dist/builtin/builtin.d.ts +63 -1
  19. package/dist/builtin/builtin.js +81 -0
  20. package/dist/builtin/builtin.js.map +1 -1
  21. package/dist/compliance/tests/async_basic/async_basic.gs.js +1 -1
  22. package/dist/compliance/tests/async_basic/async_basic.gs.js.map +1 -1
  23. package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.d.ts +1 -0
  24. package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js +82 -0
  25. package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js.map +1 -0
  26. package/dist/compliance/tests/channel_basic/channel_basic.gs.js +1 -1
  27. package/dist/compliance/tests/channel_basic/channel_basic.gs.js.map +1 -1
  28. package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js +1 -1
  29. package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js.map +1 -1
  30. package/dist/compliance/tests/copy_independence/copy_independence.gs.js +1 -1
  31. package/dist/compliance/tests/copy_independence/copy_independence.gs.js.map +1 -1
  32. package/dist/compliance/tests/defer_statement/defer_statement.gs.d.ts +1 -0
  33. package/dist/compliance/tests/defer_statement/defer_statement.gs.js +75 -0
  34. package/dist/compliance/tests/defer_statement/defer_statement.gs.js.map +1 -0
  35. package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js +3 -3
  36. package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js.map +1 -1
  37. package/dist/compliance/tests/flag_bitwise_op/flag_bitwise_op.gs.d.ts +1 -0
  38. package/dist/compliance/tests/flag_bitwise_op/flag_bitwise_op.gs.js +29 -0
  39. package/dist/compliance/tests/flag_bitwise_op/flag_bitwise_op.gs.js.map +1 -0
  40. package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.d.ts +1 -0
  41. package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js +14 -0
  42. package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js.map +1 -0
  43. package/dist/compliance/tests/for_range/for_range.gs.js +1 -1
  44. package/dist/compliance/tests/for_range/for_range.gs.js.map +1 -1
  45. package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.d.ts +1 -1
  46. package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js +1 -1
  47. package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js.map +1 -1
  48. package/dist/compliance/tests/interface_method_comments/interface_method_comments.gs.d.ts +1 -0
  49. package/dist/compliance/tests/interface_method_comments/interface_method_comments.gs.js +12 -0
  50. package/dist/compliance/tests/interface_method_comments/interface_method_comments.gs.js.map +1 -0
  51. package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js +2 -2
  52. package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js.map +1 -1
  53. package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js +3 -3
  54. package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js.map +1 -1
  55. package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js +13 -3
  56. package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js.map +1 -1
  57. package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.d.ts +1 -0
  58. package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js +51 -0
  59. package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js.map +1 -0
  60. package/dist/compliance/tests/map_support/map_support.gs.js +1 -1
  61. package/dist/compliance/tests/map_support/map_support.gs.js.map +1 -1
  62. package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js +1 -1
  63. package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js.map +1 -1
  64. package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js +1 -1
  65. package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js.map +1 -1
  66. package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js +1 -1
  67. package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js.map +1 -1
  68. package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js +1 -1
  69. package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js.map +1 -1
  70. package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js +1 -1
  71. package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js.map +1 -1
  72. package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js +1 -1
  73. package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js.map +1 -1
  74. package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js +1 -1
  75. package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js.map +1 -1
  76. package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js +1 -1
  77. package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js.map +1 -1
  78. package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js +1 -1
  79. package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js.map +1 -1
  80. package/dist/compliance/tests/select_send_on_full_buffered_channel_with_default/select_send_on_full_buffered_channel_with_default.gs.js +2 -1
  81. package/dist/compliance/tests/select_send_on_full_buffered_channel_with_default/select_send_on_full_buffered_channel_with_default.gs.js.map +1 -1
  82. package/dist/compliance/tests/select_statement/select_statement.gs.js +2 -4
  83. package/dist/compliance/tests/select_statement/select_statement.gs.js.map +1 -1
  84. package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js +1 -1
  85. package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js.map +1 -1
  86. package/dist/compliance/tests/slices/slices.gs.js +281 -8
  87. package/dist/compliance/tests/slices/slices.gs.js.map +1 -1
  88. package/dist/compliance/tests/string_conversion/string_conversion.gs.d.ts +1 -0
  89. package/dist/compliance/tests/string_conversion/string_conversion.gs.js +41 -0
  90. package/dist/compliance/tests/string_conversion/string_conversion.gs.js.map +1 -0
  91. package/dist/compliance/tests/struct_embedding/struct_embedding.gs.d.ts +1 -0
  92. package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js +48 -0
  93. package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js.map +1 -0
  94. package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js +1 -1
  95. package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js.map +1 -1
  96. package/dist/compliance/tests/struct_pointer_interface_fields/struct_pointer_interface_fields.gs.d.ts +1 -0
  97. package/dist/compliance/tests/struct_pointer_interface_fields/struct_pointer_interface_fields.gs.js +26 -0
  98. package/dist/compliance/tests/struct_pointer_interface_fields/struct_pointer_interface_fields.gs.js.map +1 -0
  99. package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js +1 -1
  100. package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js.map +1 -1
  101. package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js +1 -1
  102. package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js.map +1 -1
  103. package/package.json +4 -3
package/README.md CHANGED
@@ -306,7 +306,7 @@ func main() {
306
306
  Generated with `goscript compile .`:
307
307
 
308
308
  ```typescript
309
- import * as goscript from "@go/builtin"
309
+ import * as goscript from "@goscript/builtin"
310
310
 
311
311
  class MyStruct {
312
312
  // MyInt is a public integer field, initialized to zero.
@@ -460,10 +460,10 @@ export async function main(): Promise<void> {
460
460
 
461
461
  Code is compiled with `GOARCH=js` and uses a 32-bit environment similar to wasm.
462
462
 
463
- All Go import paths are prefixed with `@go/` and can be imported in TypeScript:
463
+ All Go import paths are prefixed with `@goscript/` and can be imported in TypeScript:
464
464
 
465
465
  ```typescript
466
- import { MyAsyncFunction, MyStruct } from '@go/github.com/myorg/mypackage';
466
+ import { MyAsyncFunction, MyStruct } from '@goscript/github.com/myorg/mypackage';
467
467
 
468
468
  // Example of importing and using a compiled Go async function
469
469
  async function runGoCode() {
@@ -0,0 +1,11 @@
1
+ package builtin
2
+
3
+ import (
4
+ _ "embed"
5
+ )
6
+
7
+ // BuiltinTs contains the contents of the builtin.ts file which provides
8
+ // runtime support for GoScript compiled code.
9
+ //
10
+ //go:embed builtin.ts
11
+ var BuiltinTs string
@@ -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.
@@ -50,8 +78,8 @@ export const cap = <T>(slice: Array<T> & { __capacity?: number }): number => {
50
78
  * Represents the Go error type (interface).
51
79
  */
52
80
  export type Error = {
53
- Error(): string;
54
- } | null;
81
+ Error(): string
82
+ } | null
55
83
 
56
84
  /**
57
85
  * Converts a string to an array of Unicode code points (runes).
@@ -62,6 +90,15 @@ export const stringToRunes = (str: string): number[] => {
62
90
  return Array.from(str).map((c) => c.codePointAt(0) || 0)
63
91
  }
64
92
 
93
+ /**
94
+ * Converts an array of Unicode code points (runes) to a string.
95
+ * @param runes The input array of numbers representing Unicode code points.
96
+ * @returns The resulting string.
97
+ */
98
+ export const runesToString = (runes: number[]): string => {
99
+ return String.fromCharCode(...runes);
100
+ };
101
+
65
102
  /**
66
103
  * Gets a value from a map, with a default value if the key doesn't exist.
67
104
  * @param map The map to get from.
@@ -113,8 +150,14 @@ export const mapHas = <K, V>(map: Map<K, V>, key: K): boolean => {
113
150
  * @param elements The elements to append.
114
151
  * @returns The modified slice (TypeScript array).
115
152
  */
116
- export const append = <T>(slice: Array<T>, ...elements: T[]): Array<T> => {
153
+ export const append = <T>(
154
+ slice: Array<T> & { __capacity?: number },
155
+ ...elements: T[]
156
+ ): Array<T> & { __capacity?: number } => {
117
157
  slice.push(...elements)
158
+ if (slice.__capacity !== undefined && slice.length > slice.__capacity) {
159
+ slice.__capacity = slice.length
160
+ }
118
161
  return slice
119
162
  }
120
163
 
@@ -122,35 +165,35 @@ export const append = <T>(slice: Array<T>, ...elements: T[]): Array<T> => {
122
165
  * Represents the kinds of Go types that can be registered at runtime.
123
166
  */
124
167
  export enum TypeKind {
125
- Struct = "struct",
126
- Interface = "interface",
127
- Basic = "basic",
128
- Pointer = "pointer",
129
- Slice = "slice",
130
- Map = "map",
131
- Channel = "channel",
132
- Function = "function",
168
+ Struct = 'struct',
169
+ Interface = 'interface',
170
+ Basic = 'basic',
171
+ Pointer = 'pointer',
172
+ Slice = 'slice',
173
+ Map = 'map',
174
+ Channel = 'channel',
175
+ Function = 'function',
133
176
  }
134
177
 
135
178
  /**
136
179
  * Represents type information for a Go type in the runtime.
137
180
  */
138
181
  export interface TypeInfo {
139
- name: string;
140
- kind: TypeKind;
141
- zeroValue: any;
182
+ name: string
183
+ kind: TypeKind
184
+ zeroValue: any
142
185
  // For interfaces, the set of methods
143
- methods?: Set<string>;
186
+ methods?: Set<string>
144
187
  // For structs, the constructor
145
- constructor?: new (...args: any[]) => any;
188
+ constructor?: new (...args: any[]) => any
146
189
  }
147
190
 
148
191
  // Registry to store runtime type information
149
- const typeRegistry = new Map<string, TypeInfo>();
192
+ const typeRegistry = new Map<string, TypeInfo>()
150
193
 
151
194
  /**
152
195
  * Registers a type with the runtime type system.
153
- *
196
+ *
154
197
  * @param name The name of the type.
155
198
  * @param kind The kind of the type.
156
199
  * @param zeroValue The zero value for the type.
@@ -163,7 +206,7 @@ export const registerType = (
163
206
  kind: TypeKind,
164
207
  zeroValue: any,
165
208
  methods?: Set<string>,
166
- constructor?: new (...args: any[]) => any
209
+ constructor?: new (...args: any[]) => any,
167
210
  ): TypeInfo => {
168
211
  const typeInfo: TypeInfo = {
169
212
  name,
@@ -171,37 +214,40 @@ export const registerType = (
171
214
  zeroValue,
172
215
  methods,
173
216
  constructor,
174
- };
175
- typeRegistry.set(name, typeInfo);
176
- return typeInfo;
177
- };
217
+ }
218
+ typeRegistry.set(name, typeInfo)
219
+ return typeInfo
220
+ }
178
221
 
179
222
  /**
180
223
  * Represents the result of a type assertion.
181
224
  */
182
225
  export interface TypeAssertResult<T> {
183
- value: T;
184
- ok: boolean;
226
+ value: T
227
+ ok: boolean
185
228
  }
186
229
 
187
230
  /**
188
231
  * Performs a type assertion at runtime.
189
- *
232
+ *
190
233
  * @param value The value to assert.
191
234
  * @param typeName The name of the target type.
192
235
  * @returns An object with the asserted value and whether the assertion succeeded.
193
236
  */
194
- export function typeAssert<T>(value: any, typeName: string): TypeAssertResult<T> {
237
+ export function typeAssert<T>(
238
+ value: any,
239
+ typeName: string,
240
+ ): TypeAssertResult<T> {
195
241
  // Get the type information from the registry
196
- const typeInfo = typeRegistry.get(typeName);
242
+ const typeInfo = typeRegistry.get(typeName)
197
243
  if (!typeInfo) {
198
- console.warn(`Type information for '${typeName}' not found in registry.`);
199
- return { value: null as unknown as T, ok: false };
244
+ console.warn(`Type information for '${typeName}' not found in registry.`)
245
+ return { value: null as unknown as T, ok: false }
200
246
  }
201
247
 
202
248
  // If value is null or undefined, assertion fails
203
249
  if (value === null || value === undefined) {
204
- return { value: typeInfo.zeroValue as T, ok: false };
250
+ return { value: typeInfo.zeroValue as T, ok: false }
205
251
  }
206
252
 
207
253
  // Check based on the kind of the target type
@@ -209,86 +255,88 @@ export function typeAssert<T>(value: any, typeName: string): TypeAssertResult<T>
209
255
  case TypeKind.Struct:
210
256
  // For structs, use instanceof with the constructor
211
257
  if (typeInfo.constructor && value instanceof typeInfo.constructor) {
212
- return { value: value as T, ok: true };
258
+ return { value: value as T, ok: true }
213
259
  }
214
- break;
215
-
260
+ break
261
+
216
262
  case TypeKind.Interface:
217
263
  // For interfaces, check if the value has all the required methods
218
264
  if (typeInfo.methods && typeof value === 'object') {
219
265
  const allMethodsPresent = Array.from(typeInfo.methods).every(
220
- (method) => typeof (value as any)[method] === 'function'
221
- );
266
+ (method) => typeof (value as any)[method] === 'function',
267
+ )
222
268
  if (allMethodsPresent) {
223
- return { value: value as T, ok: true };
269
+ return { value: value as T, ok: true }
224
270
  }
225
271
  }
226
- break;
227
-
272
+ break
273
+
228
274
  case TypeKind.Basic:
229
275
  // For basic types, check if the value matches the expected JavaScript type
230
276
  // This is a simple check for common basic types
231
- const basicType = typeof value;
277
+ const basicType = typeof value
232
278
  if (
233
- basicType === 'string' ||
234
- basicType === 'number' ||
279
+ basicType === 'string' ||
280
+ basicType === 'number' ||
235
281
  basicType === 'boolean'
236
282
  ) {
237
- return { value: value as T, ok: true };
283
+ return { value: value as T, ok: true }
238
284
  }
239
- break;
240
-
285
+ break
286
+
241
287
  case TypeKind.Pointer:
242
288
  // For pointers, check if value is not null or undefined
243
289
  // In Go, pointers can be nil which we represent as null/undefined in TS
244
290
  if (value !== null && value !== undefined) {
245
- return { value: value as T, ok: true };
291
+ return { value: value as T, ok: true }
246
292
  }
247
- break;
248
-
293
+ break
294
+
249
295
  case TypeKind.Slice:
250
296
  // For slices, check if the value is an array
251
297
  if (Array.isArray(value)) {
252
- return { value: value as T, ok: true };
298
+ return { value: value as T, ok: true }
253
299
  }
254
- break;
255
-
300
+ break
301
+
256
302
  case TypeKind.Map:
257
303
  // For maps, check if the value is a Map
258
304
  if (value instanceof Map) {
259
- return { value: value as T, ok: true };
305
+ return { value: value as T, ok: true }
260
306
  }
261
- break;
262
-
307
+ break
308
+
263
309
  case TypeKind.Channel:
264
310
  // For channels, check if the value has the required Channel interface methods
265
311
  if (
266
- typeof value === 'object' &&
312
+ typeof value === 'object' &&
267
313
  value !== null &&
268
- 'send' in value &&
269
- 'receive' in value &&
314
+ 'send' in value &&
315
+ 'receive' in value &&
270
316
  'close' in value &&
271
317
  typeof value.send === 'function' &&
272
318
  typeof value.receive === 'function' &&
273
319
  typeof value.close === 'function'
274
320
  ) {
275
- return { value: value as T, ok: true };
321
+ return { value: value as T, ok: true }
276
322
  }
277
- break;
278
-
323
+ break
324
+
279
325
  case TypeKind.Function:
280
326
  // For functions, check if the value is a function
281
327
  if (typeof value === 'function') {
282
- return { value: value as T, ok: true };
328
+ return { value: value as T, ok: true }
283
329
  }
284
- break;
285
-
330
+ break
331
+
286
332
  default:
287
- console.warn(`Type assertion for kind '${typeInfo.kind}' not implemented.`);
333
+ console.warn(
334
+ `Type assertion for kind '${typeInfo.kind}' not implemented.`,
335
+ )
288
336
  }
289
337
 
290
338
  // Assertion failed
291
- return { value: typeInfo.zeroValue as T, ok: false };
339
+ return { value: typeInfo.zeroValue as T, ok: false }
292
340
  }
293
341
 
294
342
  /**
@@ -715,3 +763,60 @@ export const makeChannel = <T>(
715
763
  ): Channel<T> => {
716
764
  return new BufferedChannel<T>(bufferSize, zeroValue)
717
765
  }
766
+
767
+ /**
768
+ * DisposableStack manages synchronous disposable resources, mimicking Go's defer behavior.
769
+ * Functions added via `defer` are executed in LIFO order when the stack is disposed.
770
+ * Implements the `Disposable` interface for use with `using` declarations.
771
+ */
772
+ export class DisposableStack implements Disposable {
773
+ #stack: (() => void)[] = [];
774
+
775
+ /**
776
+ * Adds a function to be executed when the stack is disposed.
777
+ * @param fn The function to defer.
778
+ */
779
+ defer(fn: () => void): void { this.#stack.push(fn); }
780
+
781
+ /**
782
+ * Disposes of the resources in the stack by executing the deferred functions
783
+ * in Last-In, First-Out (LIFO) order.
784
+ * If a deferred function throws an error, disposal stops, and the error is rethrown,
785
+ * similar to Go's panic behavior during defer execution.
786
+ */
787
+ [Symbol.dispose](): void {
788
+ // Emulate Go: if a deferred throws, stop and rethrow
789
+ while (this.#stack.length) {
790
+ const fn = this.#stack.pop()!;
791
+ fn();
792
+ }
793
+ }
794
+ }
795
+
796
+ /**
797
+ * AsyncDisposableStack manages asynchronous disposable resources, mimicking Go's defer behavior.
798
+ * Functions added via `defer` are executed sequentially in LIFO order when the stack is disposed.
799
+ * Implements the `AsyncDisposable` interface for use with `await using` declarations.
800
+ */
801
+ export class AsyncDisposableStack implements AsyncDisposable {
802
+ #stack: (() => Promise<void> | void)[] = [];
803
+
804
+ /**
805
+ * Adds a synchronous or asynchronous function to be executed when the stack is disposed.
806
+ * @param fn The function to defer. Can return void or a Promise<void>.
807
+ */
808
+ defer(fn: () => Promise<void> | void): void {
809
+ this.#stack.push(fn);
810
+ }
811
+
812
+ /**
813
+ * Asynchronously disposes of the resources in the stack by executing the deferred functions
814
+ * sequentially in Last-In, First-Out (LIFO) order. It awaits each function if it returns a promise.
815
+ */
816
+ async [Symbol.asyncDispose](): Promise<void> {
817
+ // Execute in LIFO order, awaiting each potentially async function
818
+ for (let i = this.#stack.length - 1; i >= 0; --i) {
819
+ await this.#stack[i]();
820
+ }
821
+ }
822
+ }
@@ -2,17 +2,22 @@ package main
2
2
 
3
3
  import (
4
4
  "context"
5
+ "slices"
5
6
 
6
7
  "github.com/aperturerobotics/cli"
7
8
  "github.com/paralin/goscript/compiler"
8
9
  "github.com/pkg/errors"
9
10
  "github.com/sirupsen/logrus"
11
+
12
+ // _ ensure we include the builtin package
13
+ _ "github.com/paralin/goscript/builtin"
10
14
  )
11
15
 
12
16
  var (
13
- cliCompiler *compiler.Compiler
14
- cliCompilerConfig compiler.Config
15
- cliCompilerPkg string
17
+ cliCompiler *compiler.Compiler
18
+ cliCompilerConfig compiler.Config
19
+ cliCompilerPkg cli.StringSlice
20
+ cliCompilerBuildFlags cli.StringSlice
16
21
  )
17
22
 
18
23
  // CompileCommands are commands related to compiling code.
@@ -29,9 +34,11 @@ var CompileCommands = []*cli.Command{{
29
34
  return
30
35
  },
31
36
  Flags: []cli.Flag{
32
- &cli.StringFlag{
37
+ &cli.StringSliceFlag{
33
38
  Name: "package",
34
- Usage: "the package to compile",
39
+ Usage: "the package(s) to compile",
40
+ Aliases: []string{"p", "packages"},
41
+ EnvVars: []string{"GOSCRIPT_PACKAGES"},
35
42
  Destination: &cliCompilerPkg,
36
43
  },
37
44
  &cli.StringFlag{
@@ -39,21 +46,34 @@ var CompileCommands = []*cli.Command{{
39
46
  Usage: "the output typescript path to use",
40
47
  Destination: &cliCompilerConfig.OutputPathRoot,
41
48
  Value: "./output",
49
+ EnvVars: []string{"GOSCRIPT_OUTPUT"},
42
50
  },
43
51
  &cli.StringFlag{
44
52
  Name: "dir",
45
53
  Usage: "the working directory to use for the compiler (default: current directory)",
46
54
  Destination: &cliCompilerConfig.Dir,
47
55
  Value: "",
56
+ EnvVars: []string{"GOSCRIPT_DIR"},
57
+ },
58
+ &cli.StringSliceFlag{
59
+ Name: "build-flags",
60
+ Aliases: []string{"b", "buildflags", "build-flag", "buildflag"},
61
+ Usage: "Go build flags (tags) to use during analysis",
62
+ Destination: &cliCompilerBuildFlags,
63
+ EnvVars: []string{"GOSCRIPT_BUILD_FLAGS"},
48
64
  },
49
65
  },
50
66
  }}
51
67
 
52
68
  // compilePackage tries to compile the package.
53
69
  func compilePackage(c *cli.Context) error {
54
- if cliCompilerPkg == "" {
55
- return errors.New("package must be specified")
70
+ pkgs := cliCompilerPkg.Value()
71
+ if len(pkgs) == 0 {
72
+ return errors.New("package(s) must be specified")
56
73
  }
57
74
 
58
- return cliCompiler.CompilePackages(context.Background(), cliCompilerPkg)
75
+ // build flags
76
+ cliCompilerConfig.BuildFlags = slices.Clone(cliCompilerBuildFlags.Value())
77
+
78
+ return cliCompiler.CompilePackages(context.Background(), pkgs...)
59
79
  }
@@ -4,7 +4,7 @@ import (
4
4
  "fmt"
5
5
  "go/ast"
6
6
  "go/token"
7
- "go/types"
7
+ gtypes "go/types"
8
8
 
9
9
  gstypes "github.com/paralin/goscript/compiler/types"
10
10
  "golang.org/x/tools/go/packages"
@@ -12,19 +12,19 @@ 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
20
-
21
- tempVarCounter int // Counter for generating unique temporary variable names
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
22
22
  }
23
23
 
24
24
  // WriteGoType writes a Go type as a TypeScript type.
25
- func (c *GoToTSCompiler) WriteGoType(typ types.Type) {
25
+ func (c *GoToTSCompiler) WriteGoType(typ gtypes.Type) {
26
26
  switch t := typ.(type) {
27
- case *types.Basic:
27
+ case *gtypes.Basic:
28
28
  // Handle basic types (int, string, etc.)
29
29
  name := t.Name()
30
30
  if tsType, ok := gstypes.GoBuiltinToTypescript(name); ok {
@@ -32,31 +32,31 @@ func (c *GoToTSCompiler) WriteGoType(typ types.Type) {
32
32
  } else {
33
33
  c.tsw.WriteLiterally(name)
34
34
  }
35
- case *types.Named:
35
+ case *gtypes.Named:
36
36
  // Handle named types (custom types)
37
37
  c.tsw.WriteLiterally(t.Obj().Name())
38
- case *types.Pointer:
38
+ case *gtypes.Pointer:
39
39
  // Handle pointer types (*T becomes T | null)
40
40
  c.WriteGoType(t.Elem())
41
41
  c.tsw.WriteLiterally(" | null")
42
- case *types.Slice, *types.Array:
42
+ case *gtypes.Slice, *gtypes.Array:
43
43
  // Handle array/slice types ([]T or [N]T becomes T[])
44
- var elemType types.Type
45
- if slice, ok := t.(*types.Slice); ok {
44
+ var elemType gtypes.Type
45
+ if slice, ok := t.(*gtypes.Slice); ok {
46
46
  elemType = slice.Elem()
47
- } else if array, ok := t.(*types.Array); ok {
47
+ } else if array, ok := t.(*gtypes.Array); ok {
48
48
  elemType = array.Elem()
49
49
  }
50
50
  c.WriteGoType(elemType)
51
51
  c.tsw.WriteLiterally("[]")
52
- case *types.Map:
52
+ case *gtypes.Map:
53
53
  // Handle map types (map[K]V becomes Map<K, V>)
54
54
  c.tsw.WriteLiterally("Map<")
55
55
  c.WriteGoType(t.Key())
56
56
  c.tsw.WriteLiterally(", ")
57
57
  c.WriteGoType(t.Elem())
58
58
  c.tsw.WriteLiterally(">")
59
- case *types.Chan:
59
+ case *gtypes.Chan:
60
60
  // Handle channel types (chan T becomes goscript.Channel<T>)
61
61
  c.tsw.WriteLiterally("goscript.Channel<")
62
62
  c.WriteGoType(t.Elem())
@@ -68,24 +68,32 @@ func (c *GoToTSCompiler) WriteGoType(typ types.Type) {
68
68
  }
69
69
  }
70
70
 
71
+ // scanForDefer checks if a block contains any defer statements (recursively).
72
+ func (c *GoToTSCompiler) scanForDefer(block *ast.BlockStmt) bool {
73
+ hasDefer := false
74
+ ast.Inspect(block, func(n ast.Node) bool {
75
+ if _, ok := n.(*ast.DeferStmt); ok {
76
+ hasDefer = true
77
+ return false // Stop traversal
78
+ }
79
+ return true // Continue traversal
80
+ })
81
+ return hasDefer
82
+ }
83
+
71
84
  // NewGoToTSCompiler builds a new GoToTSCompiler
72
85
  func NewGoToTSCompiler(tsw *TSCodeWriter, pkg *packages.Package, cmap ast.CommentMap) *GoToTSCompiler {
73
86
  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
87
+ tsw: tsw,
88
+ imports: make(map[string]*fileImport),
89
+ pkg: pkg,
90
+ cmap: cmap,
91
+ asyncFuncs: make(map[string]bool),
92
+ nextBlockNeedsDefer: false,
93
+ inAsyncFunction: false,
80
94
  }
81
95
  }
82
96
 
83
- // newTempVar generates a unique temporary variable name.
84
- func (c *GoToTSCompiler) newTempVar() string {
85
- c.tempVarCounter++
86
- return fmt.Sprintf("_tempVar%d", c.tempVarCounter)
87
- }
88
-
89
97
  // isAsyncFunc determines if a function is asynchronous
90
98
  // A function is async if it contains channel operations or calls other async functions
91
99
  func (c *GoToTSCompiler) isAsyncFunc(name string) bool {
@@ -128,7 +136,7 @@ func (c *GoToTSCompiler) containsAsyncOperations(node ast.Node) bool {
128
136
  // Handles array types recursively.
129
137
  func (c *GoToTSCompiler) WriteZeroValueForType(typ any) {
130
138
  switch t := typ.(type) {
131
- case *types.Array:
139
+ case *gtypes.Array:
132
140
  c.tsw.WriteLiterally("[")
133
141
  for i := 0; i < int(t.Len()); i++ {
134
142
  if i > 0 {
@@ -153,11 +161,11 @@ func (c *GoToTSCompiler) WriteZeroValueForType(typ any) {
153
161
  c.WriteZeroValueForType(t.Elt)
154
162
  }
155
163
  c.tsw.WriteLiterally("]")
156
- case *types.Basic:
164
+ case *gtypes.Basic:
157
165
  switch t.Kind() {
158
- case types.Bool:
166
+ case gtypes.Bool:
159
167
  c.tsw.WriteLiterally("false")
160
- case types.String:
168
+ case gtypes.String:
161
169
  c.tsw.WriteLiterally(`""`)
162
170
  default:
163
171
  c.tsw.WriteLiterally("0")
@@ -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
  }