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.
- package/builtin/builtin.ts +277 -1
- package/cmd/goscript/cmd_compile.go +8 -6
- package/compiler/compile.go +28 -11
- package/compiler/compile_decls.go +12 -0
- package/compiler/compile_expr.go +178 -27
- package/compiler/compile_spec.go +285 -5
- package/compiler/compile_stmt.go +160 -49
- package/compiler/writer.go +2 -2
- package/dist/builtin/builtin.d.ts +112 -1
- package/dist/builtin/builtin.js +199 -0
- package/dist/builtin/builtin.js.map +1 -1
- package/dist/compliance/tests/array_literal/array_literal.gs.js +1 -0
- package/dist/compliance/tests/array_literal/array_literal.gs.js.map +1 -1
- package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.d.ts +1 -0
- package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js +82 -0
- package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js.map +1 -0
- package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js +4 -0
- package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js.map +1 -1
- package/dist/compliance/tests/copy_independence/copy_independence.gs.js +3 -1
- package/dist/compliance/tests/copy_independence/copy_independence.gs.js.map +1 -1
- package/dist/compliance/tests/defer_statement/defer_statement.gs.d.ts +1 -0
- package/dist/compliance/tests/defer_statement/defer_statement.gs.js +75 -0
- package/dist/compliance/tests/defer_statement/defer_statement.gs.js.map +1 -0
- package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.d.ts +1 -0
- package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js +37 -0
- package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js.map +1 -0
- package/dist/compliance/tests/float64/float64.gs.js +1 -0
- package/dist/compliance/tests/float64/float64.gs.js.map +1 -1
- package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.d.ts +1 -0
- package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js +14 -0
- package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js.map +1 -0
- package/dist/compliance/tests/func_literal/func_literal.gs.js.map +1 -1
- package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.d.ts +2 -0
- package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js +4 -0
- package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js.map +1 -1
- package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.d.ts +1 -0
- package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js +34 -0
- package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js.map +1 -0
- package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js +8 -2
- package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js.map +1 -1
- package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js +16 -2
- package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js.map +1 -1
- package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.d.ts +1 -0
- package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js +51 -0
- package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js.map +1 -0
- package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js +3 -1
- package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js.map +1 -1
- package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js +3 -0
- package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js.map +1 -1
- package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js +3 -0
- package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js.map +1 -1
- package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js +3 -0
- package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js.map +1 -1
- package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js +3 -0
- package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js.map +1 -1
- package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js +3 -0
- package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js.map +1 -1
- package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js +3 -0
- package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js.map +1 -1
- package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js +3 -1
- package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js.map +1 -1
- package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js +1 -0
- package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js.map +1 -1
- package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js +3 -1
- package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js.map +1 -1
- package/dist/compliance/tests/slices/slices.gs.js +280 -7
- package/dist/compliance/tests/slices/slices.gs.js.map +1 -1
- package/dist/compliance/tests/string_rune_conversion/string_rune_conversion.gs.js.map +1 -1
- package/dist/compliance/tests/struct_embedding/struct_embedding.gs.d.ts +1 -0
- package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js +48 -0
- package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js.map +1 -0
- package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js +3 -0
- package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js.map +1 -1
- package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js +3 -0
- package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js.map +1 -1
- package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js +3 -1
- package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js.map +1 -1
- package/package.json +4 -3
package/builtin/builtin.ts
CHANGED
|
@@ -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>(
|
|
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
|
|
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.
|
|
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
|
-
|
|
55
|
-
|
|
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(),
|
|
60
|
+
return cliCompiler.CompilePackages(context.Background(), pkgs...)
|
|
59
61
|
}
|
package/compiler/compile.go
CHANGED
|
@@ -12,11 +12,13 @@ import (
|
|
|
12
12
|
|
|
13
13
|
// GoToTSCompiler compiles Go code to TypeScript code.
|
|
14
14
|
type GoToTSCompiler struct {
|
|
15
|
-
tsw
|
|
16
|
-
imports
|
|
17
|
-
pkg
|
|
18
|
-
cmap
|
|
19
|
-
asyncFuncs
|
|
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:
|
|
75
|
-
imports:
|
|
76
|
-
pkg:
|
|
77
|
-
cmap:
|
|
78
|
-
asyncFuncs:
|
|
79
|
-
|
|
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
|
}
|
package/compiler/compile_expr.go
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
//
|
|
225
|
-
c.
|
|
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
|
-
|
|
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
|
}
|