goscript 0.0.84 → 0.1.0

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 (188) hide show
  1. package/README.md +13 -1
  2. package/cmd/goscript/cmd_compile.go +70 -69
  3. package/cmd/goscript/cmd_compile_test.go +79 -0
  4. package/cmd/goscript/main.go +10 -5
  5. package/compiler/compile-request.go +218 -0
  6. package/compiler/compiler.go +16 -1336
  7. package/compiler/compliance_test.go +196 -0
  8. package/compiler/config.go +6 -13
  9. package/compiler/diagnostic.go +70 -0
  10. package/compiler/index.test.ts +28 -28
  11. package/compiler/index.ts +40 -72
  12. package/compiler/lowered-program.go +132 -0
  13. package/compiler/lowering.go +3576 -0
  14. package/compiler/override-registry.go +422 -0
  15. package/compiler/override-registry_test.go +207 -0
  16. package/compiler/package-graph.go +231 -0
  17. package/compiler/package-graph_test.go +281 -0
  18. package/compiler/result.go +13 -0
  19. package/compiler/runtime-contract.go +279 -0
  20. package/compiler/runtime-contract_test.go +90 -0
  21. package/compiler/semantic-model-types.go +110 -0
  22. package/compiler/semantic-model.go +922 -0
  23. package/compiler/semantic-model_test.go +416 -0
  24. package/compiler/service.go +133 -0
  25. package/compiler/skeleton_test.go +1145 -0
  26. package/compiler/typescript-emitter.go +663 -0
  27. package/compiler/wasm/compile.go +2 -3
  28. package/compiler/wasm/compile_test.go +29 -0
  29. package/compiler/wasm_api.go +10 -159
  30. package/dist/compiler/index.d.ts +1 -3
  31. package/dist/compiler/index.js +31 -55
  32. package/dist/compiler/index.js.map +1 -1
  33. package/dist/gs/builtin/builtin.d.ts +13 -0
  34. package/dist/gs/builtin/builtin.js +23 -0
  35. package/dist/gs/builtin/builtin.js.map +1 -1
  36. package/dist/gs/builtin/channel.d.ts +3 -3
  37. package/dist/gs/builtin/channel.js.map +1 -1
  38. package/dist/gs/builtin/hostio.d.ts +15 -1
  39. package/dist/gs/builtin/hostio.js +134 -49
  40. package/dist/gs/builtin/hostio.js.map +1 -1
  41. package/dist/gs/builtin/index.d.ts +1 -0
  42. package/dist/gs/builtin/index.js +1 -0
  43. package/dist/gs/builtin/index.js.map +1 -1
  44. package/dist/gs/builtin/slice.d.ts +1 -1
  45. package/dist/gs/builtin/slice.js.map +1 -1
  46. package/dist/gs/builtin/type.d.ts +11 -0
  47. package/dist/gs/builtin/type.js +55 -1
  48. package/dist/gs/builtin/type.js.map +1 -1
  49. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  50. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  51. package/dist/gs/bytes/reader.gs.js.map +1 -1
  52. package/dist/gs/context/context.js.map +1 -1
  53. package/dist/gs/crypto/rand/index.d.ts +5 -0
  54. package/dist/gs/crypto/rand/index.js +77 -0
  55. package/dist/gs/crypto/rand/index.js.map +1 -0
  56. package/dist/gs/encoding/json/index.d.ts +3 -0
  57. package/dist/gs/encoding/json/index.js +160 -0
  58. package/dist/gs/encoding/json/index.js.map +1 -0
  59. package/dist/gs/fmt/fmt.js.map +1 -1
  60. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -1
  61. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +1 -1
  62. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  63. package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
  64. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  65. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  66. package/dist/gs/go/scanner/index.d.ts +29 -0
  67. package/dist/gs/go/scanner/index.js +120 -0
  68. package/dist/gs/go/scanner/index.js.map +1 -0
  69. package/dist/gs/go/token/index.d.ts +31 -0
  70. package/dist/gs/go/token/index.js +82 -0
  71. package/dist/gs/go/token/index.js.map +1 -0
  72. package/dist/gs/internal/abi/index.js.map +1 -1
  73. package/dist/gs/io/fs/fs.js.map +1 -1
  74. package/dist/gs/io/fs/readdir.js.map +1 -1
  75. package/dist/gs/io/fs/readfile.js.map +1 -1
  76. package/dist/gs/io/fs/stat.js.map +1 -1
  77. package/dist/gs/io/fs/sub.js.map +1 -1
  78. package/dist/gs/io/io.js.map +1 -1
  79. package/dist/gs/os/dir_unix.gs.js.map +1 -1
  80. package/dist/gs/os/error.gs.js +2 -4
  81. package/dist/gs/os/error.gs.js.map +1 -1
  82. package/dist/gs/os/exec.gs.js.map +1 -1
  83. package/dist/gs/os/exec_posix.gs.js.map +1 -1
  84. package/dist/gs/os/rawconn_js.gs.js.map +1 -1
  85. package/dist/gs/os/root_js.gs.js.map +1 -1
  86. package/dist/gs/os/tempfile.gs.js +66 -9
  87. package/dist/gs/os/tempfile.gs.js.map +1 -1
  88. package/dist/gs/os/types.gs.js.map +1 -1
  89. package/dist/gs/os/types_js.gs.js +9 -9
  90. package/dist/gs/os/types_js.gs.js.map +1 -1
  91. package/dist/gs/os/types_unix.gs.js.map +1 -1
  92. package/dist/gs/path/filepath/match.js.map +1 -1
  93. package/dist/gs/path/match.js.map +1 -1
  94. package/dist/gs/path/path.js.map +1 -1
  95. package/dist/gs/reflect/index.d.ts +2 -2
  96. package/dist/gs/reflect/index.js +1 -1
  97. package/dist/gs/reflect/index.js.map +1 -1
  98. package/dist/gs/reflect/map.js.map +1 -1
  99. package/dist/gs/reflect/type.d.ts +2 -1
  100. package/dist/gs/reflect/type.js +85 -14
  101. package/dist/gs/reflect/type.js.map +1 -1
  102. package/dist/gs/reflect/types.js.map +1 -1
  103. package/dist/gs/reflect/visiblefields.js.map +1 -1
  104. package/dist/gs/runtime/runtime.js.map +1 -1
  105. package/dist/gs/sort/sort.gs.js.map +1 -1
  106. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  107. package/dist/gs/strconv/quote.gs.js.map +1 -1
  108. package/dist/gs/strings/builder.js.map +1 -1
  109. package/dist/gs/strings/reader.js.map +1 -1
  110. package/dist/gs/strings/replace.js.map +1 -1
  111. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  112. package/dist/gs/sync/atomic/value.gs.js.map +1 -1
  113. package/dist/gs/sync/sync.d.ts +1 -0
  114. package/dist/gs/sync/sync.js +12 -0
  115. package/dist/gs/sync/sync.js.map +1 -1
  116. package/dist/gs/time/time.js.map +1 -1
  117. package/dist/gs/unicode/unicode.js.map +1 -1
  118. package/go.mod +2 -2
  119. package/gs/builtin/builtin.ts +27 -0
  120. package/gs/builtin/hostio.test.ts +177 -0
  121. package/gs/builtin/hostio.ts +171 -56
  122. package/gs/builtin/index.ts +1 -0
  123. package/gs/builtin/runtime-contract.test.ts +230 -0
  124. package/gs/builtin/type.ts +84 -1
  125. package/gs/crypto/rand/index.test.ts +32 -0
  126. package/gs/crypto/rand/index.ts +90 -0
  127. package/gs/crypto/rand/meta.json +5 -0
  128. package/gs/encoding/json/index.test.ts +65 -0
  129. package/gs/encoding/json/index.ts +186 -0
  130. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +23 -0
  131. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +3 -1
  132. package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/meta.json +3 -1
  133. package/gs/go/scanner/index.test.ts +50 -0
  134. package/gs/go/scanner/index.ts +157 -0
  135. package/gs/go/token/index.test.ts +21 -0
  136. package/gs/go/token/index.ts +120 -0
  137. package/gs/os/file_unix_js.test.ts +50 -0
  138. package/gs/os/meta.json +1 -2
  139. package/gs/os/tempfile.gs.test.ts +85 -0
  140. package/gs/os/tempfile.gs.ts +71 -11
  141. package/gs/os/types_js.gs.ts +9 -9
  142. package/gs/reflect/index.ts +1 -1
  143. package/gs/reflect/type.ts +106 -17
  144. package/gs/reflect/typefor.test.ts +75 -0
  145. package/gs/sync/sync.test.ts +24 -0
  146. package/gs/sync/sync.ts +12 -0
  147. package/package.json +13 -13
  148. package/compiler/analysis.go +0 -3475
  149. package/compiler/analysis_test.go +0 -338
  150. package/compiler/assignment.go +0 -580
  151. package/compiler/builtin_test.go +0 -92
  152. package/compiler/code-writer.go +0 -115
  153. package/compiler/compiler_test.go +0 -149
  154. package/compiler/composite-lit.go +0 -779
  155. package/compiler/config_test.go +0 -62
  156. package/compiler/constraint.go +0 -86
  157. package/compiler/decl.go +0 -801
  158. package/compiler/expr-call-async.go +0 -188
  159. package/compiler/expr-call-builtins.go +0 -208
  160. package/compiler/expr-call-helpers.go +0 -382
  161. package/compiler/expr-call-make.go +0 -318
  162. package/compiler/expr-call-type-conversion.go +0 -520
  163. package/compiler/expr-call.go +0 -413
  164. package/compiler/expr-selector.go +0 -343
  165. package/compiler/expr-star.go +0 -82
  166. package/compiler/expr-type.go +0 -442
  167. package/compiler/expr-value.go +0 -89
  168. package/compiler/expr.go +0 -773
  169. package/compiler/field.go +0 -183
  170. package/compiler/gs_dependencies_test.go +0 -298
  171. package/compiler/lit.go +0 -322
  172. package/compiler/output.go +0 -72
  173. package/compiler/primitive.go +0 -149
  174. package/compiler/protobuf.go +0 -697
  175. package/compiler/sanitize.go +0 -100
  176. package/compiler/spec-struct.go +0 -995
  177. package/compiler/spec-value.go +0 -540
  178. package/compiler/spec.go +0 -725
  179. package/compiler/stmt-assign.go +0 -664
  180. package/compiler/stmt-for.go +0 -266
  181. package/compiler/stmt-range.go +0 -475
  182. package/compiler/stmt-select.go +0 -262
  183. package/compiler/stmt-type-switch.go +0 -147
  184. package/compiler/stmt.go +0 -1308
  185. package/compiler/type-assert.go +0 -386
  186. package/compiler/type-info.go +0 -156
  187. package/compiler/type-utils.go +0 -207
  188. package/compiler/type.go +0 -892
@@ -0,0 +1,663 @@
1
+ package compiler
2
+
3
+ import (
4
+ "context"
5
+ "os"
6
+ "path/filepath"
7
+ "slices"
8
+ "strconv"
9
+ "strings"
10
+ )
11
+
12
+ // TypeScriptEmitOwner owns deterministic TypeScript file emission.
13
+ type TypeScriptEmitOwner struct {
14
+ runtimeOwner *RuntimeContractOwner
15
+ }
16
+
17
+ // NewTypeScriptEmitOwner creates the TypeScript emit owner.
18
+ func NewTypeScriptEmitOwner(runtimeOwners ...*RuntimeContractOwner) *TypeScriptEmitOwner {
19
+ runtimeOwner := NewRuntimeContractOwner()
20
+ if len(runtimeOwners) != 0 && runtimeOwners[0] != nil {
21
+ runtimeOwner = runtimeOwners[0]
22
+ }
23
+ return &TypeScriptEmitOwner{runtimeOwner: runtimeOwner}
24
+ }
25
+
26
+ // Emit writes a lowered program to the configured TypeScript output tree.
27
+ func (o *TypeScriptEmitOwner) Emit(
28
+ ctx context.Context,
29
+ req *CompileRequest,
30
+ program *LoweredProgram,
31
+ ) ([]string, []Diagnostic) {
32
+ if err := ctx.Err(); err != nil {
33
+ return nil, []Diagnostic{{
34
+ Severity: DiagnosticSeverityError,
35
+ Code: "goscript/context:canceled",
36
+ Message: err.Error(),
37
+ }}
38
+ }
39
+ if req == nil {
40
+ return nil, []Diagnostic{{
41
+ Severity: DiagnosticSeverityError,
42
+ Code: "goscript/emitter:no-request",
43
+ Message: "TypeScript emission requires a compile request",
44
+ }}
45
+ }
46
+ if program == nil {
47
+ return nil, []Diagnostic{{
48
+ Severity: DiagnosticSeverityError,
49
+ Code: "goscript/emitter:no-program",
50
+ Message: "TypeScript emission requires a lowered program",
51
+ }}
52
+ }
53
+
54
+ var compiled []string
55
+ var diagnostics []Diagnostic
56
+ for _, pkg := range program.packages {
57
+ if err := ctx.Err(); err != nil {
58
+ diagnostics = append(diagnostics, Diagnostic{
59
+ Severity: DiagnosticSeverityError,
60
+ Code: "goscript/context:canceled",
61
+ Message: err.Error(),
62
+ })
63
+ break
64
+ }
65
+ pkgDir := filepath.Join(req.OutputPath, "@goscript", filepath.FromSlash(pkg.pkgPath))
66
+ if err := os.MkdirAll(pkgDir, 0o755); err != nil {
67
+ diagnostics = append(diagnostics, emitError("create package output", pkg.pkgPath, err))
68
+ continue
69
+ }
70
+ for _, file := range pkg.files {
71
+ path := filepath.Join(pkgDir, file.outputName)
72
+ if err := os.WriteFile(path, []byte(o.renderLoweredFile(pkg, file)), 0o644); err != nil {
73
+ diagnostics = append(diagnostics, emitError("write TypeScript file", path, err))
74
+ }
75
+ }
76
+ if err := os.WriteFile(filepath.Join(pkgDir, "index.ts"), []byte(renderIndex(pkg)), 0o644); err != nil {
77
+ diagnostics = append(diagnostics, emitError("write package index", pkg.pkgPath, err))
78
+ continue
79
+ }
80
+ compiled = append(compiled, pkg.pkgPath)
81
+ }
82
+ return compiled, diagnostics
83
+ }
84
+
85
+ func (o *TypeScriptEmitOwner) renderLoweredFile(pkg *loweredPackage, file *loweredFile) string {
86
+ var b strings.Builder
87
+ if file.sourcePath != "" {
88
+ b.WriteString("// Generated file based on ")
89
+ b.WriteString(filepath.Base(file.sourcePath))
90
+ b.WriteString("\n// Updated when compliance tests are re-run, DO NOT EDIT!\n\n")
91
+ }
92
+ for idx, imp := range file.imports {
93
+ if idx != 0 {
94
+ b.WriteString("\n")
95
+ }
96
+ b.WriteString("import * as ")
97
+ b.WriteString(imp.alias)
98
+ b.WriteString(" from \"")
99
+ b.WriteString(imp.source)
100
+ b.WriteString("\"\n")
101
+ }
102
+ if len(file.imports) != 0 {
103
+ b.WriteString("\n")
104
+ }
105
+ for idx, decl := range file.decls {
106
+ if idx != 0 {
107
+ b.WriteString("\n")
108
+ }
109
+ if decl.structType != nil {
110
+ renderStruct(&b, decl.structType, o.runtimeOwner)
111
+ continue
112
+ }
113
+ if decl.function != nil {
114
+ renderFunction(&b, decl.function)
115
+ if pkg.name == "main" && decl.function.name == "main" {
116
+ b.WriteString("\n\nif (")
117
+ b.WriteString(o.runtimeOwner.QualifiedHelper(RuntimeHelperIsMainScript))
118
+ b.WriteString("(import.meta)) {\n")
119
+ b.WriteString("\tawait main()\n")
120
+ b.WriteString("}\n")
121
+ }
122
+ continue
123
+ }
124
+ b.WriteString(decl.code)
125
+ b.WriteString("\n")
126
+ }
127
+ return b.String()
128
+ }
129
+
130
+ func renderStruct(b *strings.Builder, structType *loweredStruct, runtimeOwner *RuntimeContractOwner) {
131
+ varRef := runtimeOwner.QualifiedHelper(RuntimeHelperVarRef)
132
+ markStructValue := runtimeOwner.QualifiedHelper(RuntimeHelperMarkAsStructValue)
133
+ registerStructType := runtimeOwner.QualifiedHelper(RuntimeHelperRegisterStructType)
134
+ if structType.exported {
135
+ b.WriteString("export ")
136
+ }
137
+ b.WriteString("class ")
138
+ b.WriteString(structType.name)
139
+ b.WriteString(" {\n")
140
+ for _, field := range structType.fields {
141
+ writeLineComment(b, "\t", field.doc)
142
+ b.WriteString("\tpublic get ")
143
+ b.WriteString(field.name)
144
+ b.WriteString("(): ")
145
+ b.WriteString(field.typ)
146
+ b.WriteString(" {\n\t\treturn this._fields.")
147
+ b.WriteString(field.name)
148
+ b.WriteString(".value\n\t}\n")
149
+ b.WriteString("\tpublic set ")
150
+ b.WriteString(field.name)
151
+ b.WriteString("(value: ")
152
+ b.WriteString(field.typ)
153
+ b.WriteString(") {\n\t\tthis._fields.")
154
+ b.WriteString(field.name)
155
+ b.WriteString(".value = value\n\t}\n\n")
156
+ }
157
+ b.WriteString("\tpublic _fields: {\n")
158
+ for _, field := range structType.fields {
159
+ b.WriteString("\t\t")
160
+ b.WriteString(field.name)
161
+ b.WriteString(": $.VarRef<")
162
+ b.WriteString(field.typ)
163
+ b.WriteString(">\n")
164
+ }
165
+ b.WriteString("\t}\n\n")
166
+ b.WriteString("\tconstructor(init?: Partial<{")
167
+ for idx, field := range structType.fields {
168
+ if idx != 0 {
169
+ b.WriteString(", ")
170
+ }
171
+ b.WriteString(field.name)
172
+ b.WriteString("?: ")
173
+ b.WriteString(field.typ)
174
+ }
175
+ b.WriteString("}>) {\n\t\tthis._fields = {\n")
176
+ for idx, field := range structType.fields {
177
+ b.WriteString("\t\t\t")
178
+ b.WriteString(field.name)
179
+ b.WriteString(": ")
180
+ b.WriteString(varRef)
181
+ b.WriteString("(")
182
+ if field.structValue {
183
+ b.WriteString("init?.")
184
+ b.WriteString(field.name)
185
+ b.WriteString(" ? ")
186
+ b.WriteString(markStructValue)
187
+ b.WriteString("(init.")
188
+ b.WriteString(field.name)
189
+ b.WriteString(".clone()) : ")
190
+ b.WriteString(field.zero)
191
+ } else {
192
+ b.WriteString("init?.")
193
+ b.WriteString(field.name)
194
+ b.WriteString(" ?? ")
195
+ b.WriteString(field.zero)
196
+ }
197
+ b.WriteString(")")
198
+ if idx != len(structType.fields)-1 {
199
+ b.WriteString(",")
200
+ }
201
+ b.WriteString("\n")
202
+ }
203
+ b.WriteString("\t\t}\n\t}\n\n")
204
+ b.WriteString("\tpublic clone(): ")
205
+ b.WriteString(structType.name)
206
+ b.WriteString(" {\n\t\tconst cloned = new ")
207
+ b.WriteString(structType.name)
208
+ b.WriteString("()\n\t\tcloned._fields = {\n")
209
+ for idx, field := range structType.fields {
210
+ b.WriteString("\t\t\t")
211
+ b.WriteString(field.name)
212
+ b.WriteString(": ")
213
+ b.WriteString(varRef)
214
+ b.WriteString("(")
215
+ if field.structValue {
216
+ b.WriteString(markStructValue)
217
+ b.WriteString("(this._fields.")
218
+ b.WriteString(field.name)
219
+ b.WriteString(".value.clone())")
220
+ } else {
221
+ b.WriteString("this._fields.")
222
+ b.WriteString(field.name)
223
+ b.WriteString(".value")
224
+ }
225
+ b.WriteString(")")
226
+ if idx != len(structType.fields)-1 {
227
+ b.WriteString(",")
228
+ }
229
+ b.WriteString("\n")
230
+ }
231
+ b.WriteString("\t\t}\n\t\treturn ")
232
+ b.WriteString(markStructValue)
233
+ b.WriteString("(cloned)\n\t}\n")
234
+ for _, method := range structType.methods {
235
+ b.WriteString("\n")
236
+ renderMethod(b, &method)
237
+ }
238
+ b.WriteString("\n\tstatic __typeInfo = ")
239
+ b.WriteString(registerStructType)
240
+ b.WriteString("(\n")
241
+ b.WriteString("\t\t")
242
+ b.WriteString(strconvQuote(structType.typeName))
243
+ b.WriteString(",\n\t\tnew ")
244
+ b.WriteString(structType.name)
245
+ b.WriteString("(),\n\t\t[")
246
+ for idx, method := range structType.methods {
247
+ if idx != 0 {
248
+ b.WriteString(", ")
249
+ }
250
+ b.WriteString("{ name: ")
251
+ b.WriteString(strconvQuote(method.name))
252
+ b.WriteString(", args: [], returns: [] }")
253
+ }
254
+ b.WriteString("],\n\t\t")
255
+ b.WriteString(structType.name)
256
+ b.WriteString(",\n\t\t{")
257
+ for idx, field := range structType.fields {
258
+ if idx != 0 {
259
+ b.WriteString(", ")
260
+ }
261
+ b.WriteString(strconvQuote(field.name))
262
+ b.WriteString(": ")
263
+ b.WriteString(runtimeStructFieldInfoExpr(field.runtimeType, field.tag))
264
+ }
265
+ b.WriteString("}\n\t)\n")
266
+ b.WriteString("}\n")
267
+ }
268
+
269
+ func writeLineComment(b *strings.Builder, indent string, comment string) {
270
+ comment = strings.TrimSpace(comment)
271
+ if comment == "" {
272
+ return
273
+ }
274
+ for line := range strings.SplitSeq(comment, "\n") {
275
+ line = strings.TrimRight(line, "\r")
276
+ if strings.TrimSpace(line) == "" {
277
+ b.WriteString(indent)
278
+ b.WriteString("//\n")
279
+ continue
280
+ }
281
+ b.WriteString(indent)
282
+ b.WriteString("// ")
283
+ b.WriteString(line)
284
+ b.WriteString("\n")
285
+ }
286
+ }
287
+
288
+ func renderFunction(b *strings.Builder, fn *loweredFunction) {
289
+ if fn.exported {
290
+ b.WriteString("export ")
291
+ }
292
+ if fn.async {
293
+ b.WriteString("async ")
294
+ }
295
+ b.WriteString("function ")
296
+ b.WriteString(fn.name)
297
+ b.WriteString("(")
298
+ for idx, param := range fn.params {
299
+ if idx != 0 {
300
+ b.WriteString(", ")
301
+ }
302
+ b.WriteString(param.name)
303
+ b.WriteString(": ")
304
+ b.WriteString(param.typ)
305
+ }
306
+ b.WriteString("): ")
307
+ b.WriteString(fn.result)
308
+ b.WriteString(" {\n")
309
+ if fn.receiverAlias != "" && fn.receiverAlias != "_" {
310
+ writeIndent(b, 1)
311
+ b.WriteString("const ")
312
+ b.WriteString(fn.receiverAlias)
313
+ b.WriteString(" = this\n")
314
+ }
315
+ renderDeferStack(b, fn.deferState, 1)
316
+ renderStmts(b, fn.body, 1)
317
+ b.WriteString("}\n")
318
+ }
319
+
320
+ func renderMethod(b *strings.Builder, fn *loweredFunction) {
321
+ writeIndent(b, 1)
322
+ b.WriteString("public ")
323
+ if fn.async {
324
+ b.WriteString("async ")
325
+ }
326
+ b.WriteString(fn.name)
327
+ b.WriteString("(")
328
+ for idx, param := range fn.params {
329
+ if idx != 0 {
330
+ b.WriteString(", ")
331
+ }
332
+ b.WriteString(param.name)
333
+ b.WriteString(": ")
334
+ b.WriteString(param.typ)
335
+ }
336
+ b.WriteString("): ")
337
+ b.WriteString(fn.result)
338
+ b.WriteString(" {\n")
339
+ if fn.receiverAlias != "" && fn.receiverAlias != "_" {
340
+ writeIndent(b, 2)
341
+ b.WriteString("const ")
342
+ b.WriteString(fn.receiverAlias)
343
+ b.WriteString(" = this\n")
344
+ }
345
+ renderDeferStack(b, fn.deferState, 2)
346
+ renderStmts(b, fn.body, 2)
347
+ writeIndent(b, 1)
348
+ b.WriteString("}\n")
349
+ }
350
+
351
+ func renderDeferStack(b *strings.Builder, state *loweredDeferState, indent int) {
352
+ if state == nil || !state.used {
353
+ return
354
+ }
355
+ writeIndent(b, indent)
356
+ if state.async {
357
+ b.WriteString("await using __defer = new $.AsyncDisposableStack()\n")
358
+ return
359
+ }
360
+ b.WriteString("using __defer = new $.DisposableStack()\n")
361
+ }
362
+
363
+ func renderStmts(b *strings.Builder, stmts []loweredStmt, indent int) {
364
+ for _, stmt := range stmts {
365
+ renderLeadingLines(b, stmt.leading, indent)
366
+ if stmt.rangeFunc != nil {
367
+ renderRangeFunc(b, stmt.rangeFunc, indent)
368
+ continue
369
+ }
370
+ if stmt.switchStmt != nil {
371
+ renderSwitch(b, stmt.switchStmt, indent)
372
+ continue
373
+ }
374
+ if stmt.selectStmt != nil {
375
+ renderSelect(b, stmt.selectStmt, indent)
376
+ continue
377
+ }
378
+ if stmt.typeSwitch != nil {
379
+ renderTypeSwitch(b, stmt.typeSwitch, indent)
380
+ continue
381
+ }
382
+ writeIndent(b, indent)
383
+ if stmt.text == "" && len(stmt.children) != 0 {
384
+ b.WriteString("{\n")
385
+ renderStmts(b, stmt.children, indent+1)
386
+ writeIndent(b, indent)
387
+ b.WriteString("}\n")
388
+ continue
389
+ }
390
+ writeIndentedText(b, stmt.text, indent)
391
+ if len(stmt.children) == 0 {
392
+ b.WriteString("\n")
393
+ continue
394
+ }
395
+ b.WriteString(" {\n")
396
+ renderStmts(b, stmt.children, indent+1)
397
+ writeIndent(b, indent)
398
+ b.WriteString("}")
399
+ if len(stmt.elseBody) == 0 {
400
+ b.WriteString("\n")
401
+ continue
402
+ }
403
+ b.WriteString(" else {\n")
404
+ renderStmts(b, stmt.elseBody, indent+1)
405
+ writeIndent(b, indent)
406
+ b.WriteString("}\n")
407
+ }
408
+ }
409
+
410
+ func renderLeadingLines(b *strings.Builder, lines []string, indent int) {
411
+ for _, line := range lines {
412
+ if strings.TrimSpace(line) == "" {
413
+ b.WriteString("\n")
414
+ continue
415
+ }
416
+ writeIndent(b, indent)
417
+ b.WriteString(line)
418
+ b.WriteString("\n")
419
+ }
420
+ }
421
+
422
+ func writeIndentedText(b *strings.Builder, text string, indent int) {
423
+ lines := strings.Split(text, "\n")
424
+ for idx, line := range lines {
425
+ if idx != 0 {
426
+ b.WriteString("\n")
427
+ if line != "" {
428
+ writeIndent(b, indent)
429
+ }
430
+ }
431
+ b.WriteString(line)
432
+ }
433
+ }
434
+
435
+ func renderSwitch(b *strings.Builder, stmt *loweredSwitch, indent int) {
436
+ writeIndent(b, indent)
437
+ b.WriteString("switch (")
438
+ b.WriteString(stmt.value)
439
+ b.WriteString(") {\n")
440
+ for _, switchCase := range stmt.cases {
441
+ for _, value := range switchCase.values {
442
+ writeIndent(b, indent+1)
443
+ b.WriteString("case ")
444
+ b.WriteString(value)
445
+ b.WriteString(":\n")
446
+ }
447
+ renderSwitchBody(b, switchCase.body, indent+1)
448
+ }
449
+ if len(stmt.defaultBody) != 0 {
450
+ writeIndent(b, indent+1)
451
+ b.WriteString("default:\n")
452
+ renderSwitchBody(b, stmt.defaultBody, indent+1)
453
+ }
454
+ writeIndent(b, indent)
455
+ b.WriteString("}\n")
456
+ }
457
+
458
+ func renderSwitchBody(b *strings.Builder, body []loweredStmt, indent int) {
459
+ writeIndent(b, indent)
460
+ b.WriteString("{\n")
461
+ renderStmts(b, body, indent+1)
462
+ writeIndent(b, indent+1)
463
+ b.WriteString("break\n")
464
+ writeIndent(b, indent)
465
+ b.WriteString("}\n")
466
+ }
467
+
468
+ func renderRangeFunc(b *strings.Builder, stmt *loweredRangeFunc, indent int) {
469
+ writeIndent(b, indent)
470
+ b.WriteString(";(() => {\n")
471
+ writeIndent(b, indent+1)
472
+ b.WriteString(stmt.value)
473
+ b.WriteString("!((")
474
+ b.WriteString(strings.Join(stmt.params, ", "))
475
+ b.WriteString(") => {\n")
476
+ renderStmts(b, stmt.body, indent+2)
477
+ writeIndent(b, indent+2)
478
+ b.WriteString("return true\n")
479
+ writeIndent(b, indent+1)
480
+ b.WriteString("})\n")
481
+ writeIndent(b, indent)
482
+ b.WriteString("})()\n")
483
+ }
484
+
485
+ func renderSelect(b *strings.Builder, stmt *loweredSelect, indent int) {
486
+ writeIndent(b, indent)
487
+ b.WriteString("const [")
488
+ b.WriteString(stmt.hasReturn)
489
+ b.WriteString(", ")
490
+ b.WriteString(stmt.value)
491
+ b.WriteString("] = await $.selectStatement<any, ")
492
+ b.WriteString(stmt.resultType)
493
+ b.WriteString(">([\n")
494
+ for idx, switchCase := range stmt.cases {
495
+ renderSelectCase(b, switchCase, indent+1)
496
+ if idx != len(stmt.cases)-1 {
497
+ b.WriteString(",")
498
+ }
499
+ b.WriteString("\n")
500
+ }
501
+ writeIndent(b, indent)
502
+ b.WriteString("], ")
503
+ hasDefault := "false"
504
+ if stmt.hasDefault {
505
+ hasDefault = "true"
506
+ }
507
+ b.WriteString(hasDefault)
508
+ b.WriteString(")\n")
509
+ writeIndent(b, indent)
510
+ b.WriteString("if (")
511
+ b.WriteString(stmt.hasReturn)
512
+ b.WriteString(") {\n")
513
+ writeIndent(b, indent+1)
514
+ b.WriteString("return ")
515
+ b.WriteString(stmt.value)
516
+ b.WriteString("\n")
517
+ writeIndent(b, indent)
518
+ b.WriteString("}\n")
519
+ }
520
+
521
+ func renderSelectCase(b *strings.Builder, switchCase loweredSelectCase, indent int) {
522
+ writeIndent(b, indent)
523
+ b.WriteString("{\n")
524
+ writeIndent(b, indent+1)
525
+ b.WriteString("id: ")
526
+ b.WriteString(strconv.Itoa(switchCase.id))
527
+ b.WriteString(",\n")
528
+ writeIndent(b, indent+1)
529
+ b.WriteString("isSend: ")
530
+ isSend := "false"
531
+ if switchCase.isSend {
532
+ isSend = "true"
533
+ }
534
+ b.WriteString(isSend)
535
+ b.WriteString(",\n")
536
+ writeIndent(b, indent+1)
537
+ b.WriteString("channel: ")
538
+ b.WriteString(switchCase.channel)
539
+ b.WriteString(",\n")
540
+ if switchCase.isSend {
541
+ writeIndent(b, indent+1)
542
+ b.WriteString("value: ")
543
+ b.WriteString(switchCase.value)
544
+ b.WriteString(",\n")
545
+ }
546
+ writeIndent(b, indent+1)
547
+ b.WriteString("onSelected: async (result) => {\n")
548
+ renderStmts(b, switchCase.prelude, indent+2)
549
+ renderStmts(b, switchCase.body, indent+2)
550
+ writeIndent(b, indent+1)
551
+ b.WriteString("}\n")
552
+ writeIndent(b, indent)
553
+ b.WriteString("}")
554
+ }
555
+
556
+ func renderTypeSwitch(b *strings.Builder, stmt *loweredTypeSwitch, indent int) {
557
+ writeIndent(b, indent)
558
+ b.WriteString("$.typeSwitch(\n")
559
+ writeIndent(b, indent+1)
560
+ b.WriteString(stmt.value)
561
+ b.WriteString(",\n")
562
+ writeIndent(b, indent+1)
563
+ b.WriteString("[\n")
564
+ for caseIdx, switchCase := range stmt.cases {
565
+ writeIndent(b, indent+2)
566
+ b.WriteString("{\n")
567
+ writeIndent(b, indent+3)
568
+ b.WriteString("types: [")
569
+ for idx, typ := range switchCase.types {
570
+ if idx != 0 {
571
+ b.WriteString(", ")
572
+ }
573
+ b.WriteString(typ)
574
+ }
575
+ b.WriteString("],\n")
576
+ writeIndent(b, indent+3)
577
+ b.WriteString("body: ")
578
+ renderTypeSwitchBody(b, stmt.varName, "", switchCase.body, indent+3)
579
+ writeIndent(b, indent+2)
580
+ b.WriteString("}")
581
+ if caseIdx != len(stmt.cases)-1 {
582
+ b.WriteString(",")
583
+ }
584
+ b.WriteString("\n")
585
+ }
586
+ writeIndent(b, indent+1)
587
+ b.WriteString("]")
588
+ if len(stmt.defaultBody) != 0 {
589
+ b.WriteString(",\n")
590
+ writeIndent(b, indent+1)
591
+ renderTypeSwitchBody(b, stmt.varName, stmt.value, stmt.defaultBody, indent+1)
592
+ }
593
+ if len(stmt.defaultBody) == 0 {
594
+ b.WriteString("\n")
595
+ }
596
+ writeIndent(b, indent)
597
+ b.WriteString(")\n")
598
+ }
599
+
600
+ func renderTypeSwitchBody(
601
+ b *strings.Builder,
602
+ varName string,
603
+ defaultValue string,
604
+ body []loweredStmt,
605
+ indent int,
606
+ ) {
607
+ header := "() => {\n"
608
+ if varName != "" && defaultValue == "" {
609
+ header = "(" + varName + ") => {\n"
610
+ }
611
+ b.WriteString(header)
612
+ if varName != "" && defaultValue != "" {
613
+ writeIndent(b, indent+1)
614
+ b.WriteString("let ")
615
+ b.WriteString(varName)
616
+ b.WriteString(" = ")
617
+ b.WriteString(defaultValue)
618
+ b.WriteString("\n")
619
+ }
620
+ renderStmts(b, body, indent+1)
621
+ writeIndent(b, indent)
622
+ b.WriteString("}\n")
623
+ }
624
+
625
+ func renderIndex(pkg *loweredPackage) string {
626
+ var lines []string
627
+ for _, file := range pkg.files {
628
+ exports := slices.Clone(file.exports)
629
+ slices.Sort(exports)
630
+ if len(exports) != 0 {
631
+ lines = append(lines, "export { "+strings.Join(exports, ", ")+" } from \"./"+file.outputName+"\"")
632
+ }
633
+ typeExports := slices.Clone(file.typeExports)
634
+ slices.Sort(typeExports)
635
+ if len(typeExports) != 0 {
636
+ lines = append(lines, "export type { "+strings.Join(typeExports, ", ")+" } from \"./"+file.outputName+"\"")
637
+ }
638
+ }
639
+ slices.Sort(lines)
640
+ if len(lines) == 0 {
641
+ return ""
642
+ }
643
+ return strings.Join(lines, "\n") + "\n"
644
+ }
645
+
646
+ func writeIndent(b *strings.Builder, indent int) {
647
+ for range indent {
648
+ b.WriteString("\t")
649
+ }
650
+ }
651
+
652
+ func strconvQuote(value string) string {
653
+ return strconv.Quote(value)
654
+ }
655
+
656
+ func emitError(action string, subject string, err error) Diagnostic {
657
+ return Diagnostic{
658
+ Severity: DiagnosticSeverityError,
659
+ Code: "goscript/emitter:write",
660
+ Message: "failed to " + action,
661
+ Detail: subject + ": " + err.Error(),
662
+ }
663
+ }
@@ -1,12 +1,11 @@
1
- // Package wasm provides a WASM-friendly API for compiling Go source code to TypeScript.
1
+ // Package wasm provides the WASM-friendly compiler adapter surface.
2
2
  package wasm
3
3
 
4
4
  import (
5
5
  "github.com/aperturerobotics/goscript/compiler"
6
6
  )
7
7
 
8
- // CompileSource compiles Go source code to TypeScript.
9
- // It takes the source code as a string and returns the generated TypeScript.
8
+ // CompileSource returns the v2 browser source-compilation diagnostic.
10
9
  func CompileSource(source string, packageName string) (string, error) {
11
10
  return compiler.CompileSourceToTypeScript(source, packageName)
12
11
  }
@@ -0,0 +1,29 @@
1
+ package wasm
2
+
3
+ import (
4
+ "errors"
5
+ "testing"
6
+
7
+ "github.com/aperturerobotics/goscript/compiler"
8
+ )
9
+
10
+ func TestCompileSourceReportsUnsupportedSingleFile(t *testing.T) {
11
+ _, err := CompileSource("package main\nfunc main() {}\n", "main")
12
+ if err == nil {
13
+ t.Fatal("expected compile error")
14
+ }
15
+ var compileErr *compiler.CompileError
16
+ if !errors.As(err, &compileErr) {
17
+ t.Fatalf("expected CompileError, got %T: %v", err, err)
18
+ }
19
+ if len(compileErr.Diagnostics) != 1 {
20
+ t.Fatalf("expected one diagnostic, got %d", len(compileErr.Diagnostics))
21
+ }
22
+ diag := compileErr.Diagnostics[0]
23
+ if diag.Code != "goscript/wasm:single-file-unsupported" {
24
+ t.Fatalf("expected single-file diagnostic, got %s", diag.Code)
25
+ }
26
+ if diag.Severity != compiler.DiagnosticSeverityError {
27
+ t.Fatalf("expected error severity, got %s", diag.Severity)
28
+ }
29
+ }