goscript 0.0.83 → 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 (197) 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 +27 -7
  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 +86 -0
  39. package/dist/gs/builtin/hostio.js +266 -0
  40. package/dist/gs/builtin/hostio.js.map +1 -0
  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/print.d.ts +8 -0
  45. package/dist/gs/builtin/print.js +111 -0
  46. package/dist/gs/builtin/print.js.map +1 -0
  47. package/dist/gs/builtin/slice.d.ts +1 -1
  48. package/dist/gs/builtin/slice.js.map +1 -1
  49. package/dist/gs/builtin/type.d.ts +11 -0
  50. package/dist/gs/builtin/type.js +55 -1
  51. package/dist/gs/builtin/type.js.map +1 -1
  52. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  53. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  54. package/dist/gs/bytes/reader.gs.js.map +1 -1
  55. package/dist/gs/context/context.js.map +1 -1
  56. package/dist/gs/crypto/rand/index.d.ts +5 -0
  57. package/dist/gs/crypto/rand/index.js +77 -0
  58. package/dist/gs/crypto/rand/index.js.map +1 -0
  59. package/dist/gs/encoding/json/index.d.ts +3 -0
  60. package/dist/gs/encoding/json/index.js +160 -0
  61. package/dist/gs/encoding/json/index.js.map +1 -0
  62. package/dist/gs/fmt/fmt.js +2 -22
  63. package/dist/gs/fmt/fmt.js.map +1 -1
  64. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -1
  65. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +1 -1
  66. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  67. package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
  68. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  69. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  70. package/dist/gs/go/scanner/index.d.ts +29 -0
  71. package/dist/gs/go/scanner/index.js +120 -0
  72. package/dist/gs/go/scanner/index.js.map +1 -0
  73. package/dist/gs/go/token/index.d.ts +31 -0
  74. package/dist/gs/go/token/index.js +82 -0
  75. package/dist/gs/go/token/index.js.map +1 -0
  76. package/dist/gs/internal/abi/index.js.map +1 -1
  77. package/dist/gs/io/fs/fs.js.map +1 -1
  78. package/dist/gs/io/fs/readdir.js.map +1 -1
  79. package/dist/gs/io/fs/readfile.js.map +1 -1
  80. package/dist/gs/io/fs/stat.js.map +1 -1
  81. package/dist/gs/io/fs/sub.js.map +1 -1
  82. package/dist/gs/io/io.js.map +1 -1
  83. package/dist/gs/os/dir_unix.gs.js.map +1 -1
  84. package/dist/gs/os/error.gs.js +2 -4
  85. package/dist/gs/os/error.gs.js.map +1 -1
  86. package/dist/gs/os/exec.gs.js.map +1 -1
  87. package/dist/gs/os/exec_posix.gs.js.map +1 -1
  88. package/dist/gs/os/rawconn_js.gs.js.map +1 -1
  89. package/dist/gs/os/root_js.gs.js.map +1 -1
  90. package/dist/gs/os/tempfile.gs.js +66 -9
  91. package/dist/gs/os/tempfile.gs.js.map +1 -1
  92. package/dist/gs/os/types.gs.js.map +1 -1
  93. package/dist/gs/os/types_js.gs.d.ts +2 -51
  94. package/dist/gs/os/types_js.gs.js +67 -105
  95. package/dist/gs/os/types_js.gs.js.map +1 -1
  96. package/dist/gs/os/types_unix.gs.js.map +1 -1
  97. package/dist/gs/path/filepath/match.js.map +1 -1
  98. package/dist/gs/path/match.js.map +1 -1
  99. package/dist/gs/path/path.js.map +1 -1
  100. package/dist/gs/reflect/index.d.ts +2 -2
  101. package/dist/gs/reflect/index.js +1 -1
  102. package/dist/gs/reflect/index.js.map +1 -1
  103. package/dist/gs/reflect/map.js.map +1 -1
  104. package/dist/gs/reflect/type.d.ts +2 -1
  105. package/dist/gs/reflect/type.js +85 -14
  106. package/dist/gs/reflect/type.js.map +1 -1
  107. package/dist/gs/reflect/types.js.map +1 -1
  108. package/dist/gs/reflect/visiblefields.js.map +1 -1
  109. package/dist/gs/runtime/runtime.js.map +1 -1
  110. package/dist/gs/sort/sort.gs.js.map +1 -1
  111. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  112. package/dist/gs/strconv/quote.gs.js.map +1 -1
  113. package/dist/gs/strings/builder.js.map +1 -1
  114. package/dist/gs/strings/reader.js.map +1 -1
  115. package/dist/gs/strings/replace.js.map +1 -1
  116. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  117. package/dist/gs/sync/atomic/value.gs.js.map +1 -1
  118. package/dist/gs/sync/sync.d.ts +1 -0
  119. package/dist/gs/sync/sync.js +12 -0
  120. package/dist/gs/sync/sync.js.map +1 -1
  121. package/dist/gs/time/time.js.map +1 -1
  122. package/dist/gs/unicode/unicode.js.map +1 -1
  123. package/go.mod +2 -2
  124. package/gs/builtin/builtin.ts +31 -6
  125. package/gs/builtin/hostio.test.ts +246 -0
  126. package/gs/builtin/hostio.ts +413 -0
  127. package/gs/builtin/index.ts +1 -0
  128. package/gs/builtin/print.test.ts +48 -0
  129. package/gs/builtin/print.ts +154 -0
  130. package/gs/builtin/runtime-contract.test.ts +230 -0
  131. package/gs/builtin/type.ts +84 -1
  132. package/gs/crypto/rand/index.test.ts +32 -0
  133. package/gs/crypto/rand/index.ts +90 -0
  134. package/gs/crypto/rand/meta.json +5 -0
  135. package/gs/encoding/json/index.test.ts +65 -0
  136. package/gs/encoding/json/index.ts +186 -0
  137. package/gs/fmt/fmt.test.ts +41 -30
  138. package/gs/fmt/fmt.ts +2 -22
  139. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +23 -0
  140. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +3 -1
  141. package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/meta.json +3 -1
  142. package/gs/go/scanner/index.test.ts +50 -0
  143. package/gs/go/scanner/index.ts +157 -0
  144. package/gs/go/token/index.test.ts +21 -0
  145. package/gs/go/token/index.ts +120 -0
  146. package/gs/os/file_unix_js.test.ts +103 -0
  147. package/gs/os/meta.json +1 -2
  148. package/gs/os/tempfile.gs.test.ts +85 -0
  149. package/gs/os/tempfile.gs.ts +71 -11
  150. package/gs/os/types_js.gs.ts +74 -153
  151. package/gs/reflect/index.ts +1 -1
  152. package/gs/reflect/type.ts +106 -17
  153. package/gs/reflect/typefor.test.ts +75 -0
  154. package/gs/sync/sync.test.ts +24 -0
  155. package/gs/sync/sync.ts +12 -0
  156. package/package.json +13 -13
  157. package/compiler/analysis.go +0 -3475
  158. package/compiler/analysis_test.go +0 -338
  159. package/compiler/assignment.go +0 -580
  160. package/compiler/builtin_test.go +0 -92
  161. package/compiler/code-writer.go +0 -115
  162. package/compiler/compiler_test.go +0 -149
  163. package/compiler/composite-lit.go +0 -779
  164. package/compiler/config_test.go +0 -62
  165. package/compiler/constraint.go +0 -86
  166. package/compiler/decl.go +0 -801
  167. package/compiler/expr-call-async.go +0 -188
  168. package/compiler/expr-call-builtins.go +0 -208
  169. package/compiler/expr-call-helpers.go +0 -382
  170. package/compiler/expr-call-make.go +0 -318
  171. package/compiler/expr-call-type-conversion.go +0 -520
  172. package/compiler/expr-call.go +0 -413
  173. package/compiler/expr-selector.go +0 -343
  174. package/compiler/expr-star.go +0 -82
  175. package/compiler/expr-type.go +0 -442
  176. package/compiler/expr-value.go +0 -89
  177. package/compiler/expr.go +0 -773
  178. package/compiler/field.go +0 -183
  179. package/compiler/gs_dependencies_test.go +0 -298
  180. package/compiler/lit.go +0 -322
  181. package/compiler/output.go +0 -72
  182. package/compiler/primitive.go +0 -149
  183. package/compiler/protobuf.go +0 -697
  184. package/compiler/sanitize.go +0 -100
  185. package/compiler/spec-struct.go +0 -995
  186. package/compiler/spec-value.go +0 -540
  187. package/compiler/spec.go +0 -725
  188. package/compiler/stmt-assign.go +0 -664
  189. package/compiler/stmt-for.go +0 -266
  190. package/compiler/stmt-range.go +0 -475
  191. package/compiler/stmt-select.go +0 -262
  192. package/compiler/stmt-type-switch.go +0 -147
  193. package/compiler/stmt.go +0 -1308
  194. package/compiler/type-assert.go +0 -386
  195. package/compiler/type-info.go +0 -156
  196. package/compiler/type-utils.go +0 -207
  197. 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
+ }