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,23 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { CompareEqualVT } from './index.js'
4
+
5
+ class TestValue {
6
+ constructor(private readonly value: string) {}
7
+
8
+ EqualVT(other: TestValue): boolean {
9
+ return this.value == other.value
10
+ }
11
+ }
12
+
13
+ describe('protobuf-go-lite EqualVT helpers', () => {
14
+ it('accepts compiler-emitted runtime type arguments', () => {
15
+ const equal = CompareEqualVT<TestValue>({
16
+ T: { zero: () => null },
17
+ })
18
+
19
+ expect(equal(new TestValue('a'), new TestValue('a'))).toBe(true)
20
+ expect(equal(new TestValue('a'), new TestValue('b'))).toBe(false)
21
+ expect(equal(null, null)).toBe(true)
22
+ })
23
+ })
@@ -9,6 +9,8 @@ export function IsEqualVT<T extends EqualVT<T>>(t1: T | null, t2: T | null): boo
9
9
  return t1.EqualVT(t2)
10
10
  }
11
11
 
12
- export function CompareEqualVT<T extends EqualVT<T>>(): (t1: T | null, t2: T | null) => boolean {
12
+ export function CompareEqualVT<T extends EqualVT<T>>(
13
+ _typeArgs?: unknown,
14
+ ): (t1: T | null, t2: T | null) => boolean {
13
15
  return (t1, t2) => IsEqualVT(t1, t2)
14
16
  }
@@ -1,5 +1,7 @@
1
1
  {
2
- "dependencies": [],
2
+ "dependencies": [
3
+ "github.com/aperturerobotics/wasivm/wazero/kernel/runtime"
4
+ ],
3
5
  "asyncMethods": {
4
6
  "Runtime.Compile": true,
5
7
  "Runtime.Close": true,
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import * as $ from '@goscript/builtin/index.js'
4
+ import * as token from '@goscript/go/token/index.js'
5
+
6
+ import {
7
+ ErrorList_Add,
8
+ ErrorList_Error,
9
+ ErrorList_Len,
10
+ ErrorList_RemoveMultiples,
11
+ type ErrorList,
12
+ } from './index.js'
13
+
14
+ describe('go/scanner override', () => {
15
+ it('adds and formats scanner errors', () => {
16
+ const list: $.VarRef<ErrorList> = $.varRef(null)
17
+
18
+ ErrorList_Add(
19
+ list,
20
+ $.markAsStructValue(
21
+ new token.Position({
22
+ Filename: 'test.go',
23
+ Line: 1,
24
+ Column: 1,
25
+ }),
26
+ ),
27
+ 'test error',
28
+ )
29
+
30
+ expect(ErrorList_Len(list.value)).toBe(1)
31
+ expect(ErrorList_Error(list.value)).toBe('test.go:1:1: test error')
32
+ })
33
+
34
+ it('removes repeated line errors', () => {
35
+ const list: $.VarRef<ErrorList> = $.varRef(null)
36
+ const pos = $.markAsStructValue(
37
+ new token.Position({
38
+ Filename: 'test.go',
39
+ Line: 1,
40
+ Column: 1,
41
+ }),
42
+ )
43
+
44
+ ErrorList_Add(list, pos, 'first')
45
+ ErrorList_Add(list, pos, 'second')
46
+ ErrorList_RemoveMultiples(list)
47
+
48
+ expect(ErrorList_Len(list.value)).toBe(1)
49
+ })
50
+ })
@@ -0,0 +1,157 @@
1
+ import * as $ from '@goscript/builtin/index.js'
2
+ import * as token from '@goscript/go/token/index.js'
3
+
4
+ export class Error {
5
+ public get Pos(): token.Position {
6
+ return this._fields.Pos.value
7
+ }
8
+
9
+ public set Pos(value: token.Position) {
10
+ this._fields.Pos.value = value
11
+ }
12
+
13
+ public get Msg(): string {
14
+ return this._fields.Msg.value
15
+ }
16
+
17
+ public set Msg(value: string) {
18
+ this._fields.Msg.value = value
19
+ }
20
+
21
+ public _fields: {
22
+ Pos: $.VarRef<token.Position>
23
+ Msg: $.VarRef<string>
24
+ }
25
+
26
+ constructor(init?: Partial<{ Pos: token.Position; Msg: string }>) {
27
+ this._fields = {
28
+ Pos: $.varRef(init?.Pos ?? new token.Position()),
29
+ Msg: $.varRef(init?.Msg ?? ''),
30
+ }
31
+ }
32
+
33
+ public clone(): Error {
34
+ return $.markAsStructValue(
35
+ new Error({
36
+ Pos: this.Pos.clone(),
37
+ Msg: this.Msg,
38
+ }),
39
+ )
40
+ }
41
+
42
+ public Error(): string {
43
+ return Error_Error(this)
44
+ }
45
+
46
+ static __typeInfo = $.registerStructType(
47
+ 'go/scanner.Error',
48
+ new Error(),
49
+ [
50
+ {
51
+ name: 'Error',
52
+ args: [],
53
+ returns: [{ type: { kind: $.TypeKind.Basic, name: 'string' } }],
54
+ },
55
+ ],
56
+ Error,
57
+ {
58
+ Pos: { type: 'go/token.Position' },
59
+ Msg: { kind: $.TypeKind.Basic, name: 'string' },
60
+ },
61
+ )
62
+ }
63
+
64
+ export type ErrorList = $.Slice<Error | null>
65
+
66
+ export const ScanComments = 1
67
+
68
+ export function Error_Error(err: Error): string {
69
+ if (!err.Pos.IsValid()) {
70
+ return err.Msg
71
+ }
72
+ return `${err.Pos.String()}: ${err.Msg}`
73
+ }
74
+
75
+ export function ErrorList_Add(
76
+ list: $.VarRef<ErrorList>,
77
+ pos: token.Position,
78
+ msg: string,
79
+ ): void {
80
+ list.value = $.append(
81
+ list.value,
82
+ $.markAsStructValue(new Error({ Pos: pos, Msg: msg })),
83
+ )
84
+ }
85
+
86
+ export function ErrorList_Len(list: ErrorList): number {
87
+ return $.len(list)
88
+ }
89
+
90
+ export function ErrorList_Less(list: ErrorList, i: number, j: number): boolean {
91
+ const left = list![i]!
92
+ const right = list![j]!
93
+ if (left.Pos.Filename !== right.Pos.Filename) {
94
+ return left.Pos.Filename < right.Pos.Filename
95
+ }
96
+ if (left.Pos.Line !== right.Pos.Line) {
97
+ return left.Pos.Line < right.Pos.Line
98
+ }
99
+ if (left.Pos.Column !== right.Pos.Column) {
100
+ return left.Pos.Column < right.Pos.Column
101
+ }
102
+ return left.Msg < right.Msg
103
+ }
104
+
105
+ export function ErrorList_Swap(list: ErrorList, i: number, j: number): void {
106
+ const first = list![i]
107
+ list![i] = list![j]
108
+ list![j] = first
109
+ }
110
+
111
+ export function ErrorList_Sort(list: ErrorList): void {
112
+ list?.sort((left, right) => {
113
+ if (left === null || right === null) {
114
+ if (left === right) {
115
+ return 0
116
+ }
117
+ if (left === null) {
118
+ return -1
119
+ }
120
+ return 1
121
+ }
122
+ if (ErrorList_Less([left, right], 0, 1)) {
123
+ return -1
124
+ }
125
+ if (ErrorList_Less([right, left], 0, 1)) {
126
+ return 1
127
+ }
128
+ return 0
129
+ })
130
+ }
131
+
132
+ export function ErrorList_RemoveMultiples(list: $.VarRef<ErrorList>): void {
133
+ ErrorList_Sort(list.value)
134
+ const seen = new Set<string>()
135
+ list.value = $.asArray(list.value).filter((err) => {
136
+ if (err === null) {
137
+ return false
138
+ }
139
+ const key = `${err.Pos.Filename}:${err.Pos.Line}`
140
+ if (seen.has(key)) {
141
+ return false
142
+ }
143
+ seen.add(key)
144
+ return true
145
+ })
146
+ }
147
+
148
+ export function ErrorList_Error(list: ErrorList): string {
149
+ const errors = $.asArray(list).filter((err) => err !== null)
150
+ if (errors.length === 0) {
151
+ return 'no errors'
152
+ }
153
+ if (errors.length === 1) {
154
+ return errors[0]!.Error()
155
+ }
156
+ return `${errors[0]!.Error()} (and ${errors.length - 1} more errors)`
157
+ }
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import * as $ from '@goscript/builtin/index.js'
4
+
5
+ import { Position, Position_IsValid, Position_String } from './index.js'
6
+
7
+ describe('go/token override', () => {
8
+ it('models Position values', () => {
9
+ const pos = $.markAsStructValue(
10
+ new Position({
11
+ Filename: 'test.go',
12
+ Line: 3,
13
+ Column: 9,
14
+ }),
15
+ )
16
+
17
+ expect(Position_IsValid(pos)).toBe(true)
18
+ expect(Position_String(pos)).toBe('test.go:3:9')
19
+ expect(pos.clone().String()).toBe('test.go:3:9')
20
+ })
21
+ })
@@ -0,0 +1,120 @@
1
+ import * as $ from '@goscript/builtin/index.js'
2
+
3
+ export type Pos = number
4
+
5
+ export const NoPos: Pos = 0
6
+
7
+ export class Position {
8
+ public get Filename(): string {
9
+ return this._fields.Filename.value
10
+ }
11
+
12
+ public set Filename(value: string) {
13
+ this._fields.Filename.value = value
14
+ }
15
+
16
+ public get Offset(): number {
17
+ return this._fields.Offset.value
18
+ }
19
+
20
+ public set Offset(value: number) {
21
+ this._fields.Offset.value = value
22
+ }
23
+
24
+ public get Line(): number {
25
+ return this._fields.Line.value
26
+ }
27
+
28
+ public set Line(value: number) {
29
+ this._fields.Line.value = value
30
+ }
31
+
32
+ public get Column(): number {
33
+ return this._fields.Column.value
34
+ }
35
+
36
+ public set Column(value: number) {
37
+ this._fields.Column.value = value
38
+ }
39
+
40
+ public _fields: {
41
+ Filename: $.VarRef<string>
42
+ Offset: $.VarRef<number>
43
+ Line: $.VarRef<number>
44
+ Column: $.VarRef<number>
45
+ }
46
+
47
+ constructor(
48
+ init?: Partial<{
49
+ Filename: string
50
+ Offset: number
51
+ Line: number
52
+ Column: number
53
+ }>,
54
+ ) {
55
+ this._fields = {
56
+ Filename: $.varRef(init?.Filename ?? ''),
57
+ Offset: $.varRef(init?.Offset ?? 0),
58
+ Line: $.varRef(init?.Line ?? 0),
59
+ Column: $.varRef(init?.Column ?? 0),
60
+ }
61
+ }
62
+
63
+ public clone(): Position {
64
+ return $.markAsStructValue(
65
+ new Position({
66
+ Filename: this.Filename,
67
+ Offset: this.Offset,
68
+ Line: this.Line,
69
+ Column: this.Column,
70
+ }),
71
+ )
72
+ }
73
+
74
+ public IsValid(): boolean {
75
+ return Position_IsValid(this)
76
+ }
77
+
78
+ public String(): string {
79
+ return Position_String(this)
80
+ }
81
+
82
+ static __typeInfo = $.registerStructType(
83
+ 'go/token.Position',
84
+ new Position(),
85
+ [
86
+ {
87
+ name: 'IsValid',
88
+ args: [],
89
+ returns: [{ type: { kind: $.TypeKind.Basic, name: 'bool' } }],
90
+ },
91
+ {
92
+ name: 'String',
93
+ args: [],
94
+ returns: [{ type: { kind: $.TypeKind.Basic, name: 'string' } }],
95
+ },
96
+ ],
97
+ Position,
98
+ {
99
+ Filename: { kind: $.TypeKind.Basic, name: 'string' },
100
+ Offset: { kind: $.TypeKind.Basic, name: 'int' },
101
+ Line: { kind: $.TypeKind.Basic, name: 'int' },
102
+ Column: { kind: $.TypeKind.Basic, name: 'int' },
103
+ },
104
+ )
105
+ }
106
+
107
+ export function Position_IsValid(pos: Position): boolean {
108
+ return pos.Line > 0
109
+ }
110
+
111
+ export function Position_String(pos: Position): string {
112
+ if (!Position_IsValid(pos)) {
113
+ return '-'
114
+ }
115
+ const filename = pos.Filename === '' ? '<input>' : pos.Filename
116
+ if (pos.Column > 0) {
117
+ return `${filename}:${pos.Line}:${pos.Column}`
118
+ }
119
+ return `${filename}:${pos.Line}`
120
+ }
@@ -115,6 +115,56 @@ describe('os stdio', () => {
115
115
  expect(writeSync).toHaveBeenCalledTimes(1)
116
116
  })
117
117
 
118
+ it('falls back to process builtin fs when Deno lacks sync stdio', () => {
119
+ const readSync = vi.fn(
120
+ (
121
+ _fd: number,
122
+ buffer: Uint8Array,
123
+ _offset?: number,
124
+ _length?: number,
125
+ _position?: number | null,
126
+ ) => {
127
+ buffer.set([5, 6], 0)
128
+ return 2
129
+ },
130
+ )
131
+ const writeSync = vi.fn(
132
+ (
133
+ _fd: number,
134
+ _buffer: Uint8Array,
135
+ _offset?: number,
136
+ length?: number,
137
+ _position?: number | null,
138
+ ) => length ?? 0,
139
+ )
140
+
141
+ ;(globalThis as any).Deno = {
142
+ stdin: {},
143
+ stdout: {},
144
+ }
145
+ ;(globalThis as any).process = {
146
+ getBuiltinModule: vi.fn(() => ({
147
+ readSync,
148
+ writeSync,
149
+ })),
150
+ }
151
+ resetHostRuntimeForTests()
152
+
153
+ const input = NewFile(0, 'stdin')
154
+ const output = NewFile(1, 'stdout')
155
+ const readBuf = new Uint8Array(2)
156
+
157
+ const [readN, readErr] = input!.Read(readBuf)
158
+ expect(readN).toBe(2)
159
+ expect(readErr).toBeNull()
160
+ expect(Array.from(readBuf)).toEqual([5, 6])
161
+
162
+ const [writeN, writeErr] = output!.Write(new Uint8Array([7, 8, 9]))
163
+ expect(writeN).toBe(3)
164
+ expect(writeErr).toBeNull()
165
+ expect(writeSync).toHaveBeenCalledTimes(1)
166
+ })
167
+
118
168
  it('returns EOF on zero-byte reads and ErrClosed after Close', () => {
119
169
  const stdinReadSync = vi.fn(() => 0)
120
170
 
package/gs/os/meta.json CHANGED
@@ -3,7 +3,6 @@
3
3
  "errors",
4
4
  "internal/byteorder",
5
5
  "internal/goarch",
6
- "internal/poll",
7
6
  "io",
8
7
  "io/fs",
9
8
  "runtime",
@@ -12,4 +11,4 @@
12
11
  "time",
13
12
  "unsafe"
14
13
  ]
15
- }
14
+ }
@@ -0,0 +1,85 @@
1
+ import { basename, join } from 'node:path'
2
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
3
+ import { tmpdir } from 'node:os'
4
+ import { afterEach, describe, expect, it, vi } from 'vitest'
5
+
6
+ import { ErrUnimplemented } from './error.gs.js'
7
+ import { CreateTemp, MkdirTemp } from './tempfile.gs.js'
8
+
9
+ const tempRoots: string[] = []
10
+
11
+ afterEach(() => {
12
+ vi.restoreAllMocks()
13
+ vi.unstubAllGlobals()
14
+ for (const root of tempRoots.splice(0)) {
15
+ rmSync(root, { force: true, recursive: true })
16
+ }
17
+ })
18
+
19
+ function makeTempRoot(): string {
20
+ const root = mkdtempSync(join(tmpdir(), 'goscript-os-temp-'))
21
+ tempRoots.push(root)
22
+ return root
23
+ }
24
+
25
+ function stubRandomBytes(bytes: number[]): void {
26
+ vi.stubGlobal('crypto', {
27
+ getRandomValues: vi.fn((dst: Uint8Array) => {
28
+ for (let i = 0; i < dst.length; i++) {
29
+ dst[i] = bytes[i] ?? 0
30
+ }
31
+ return dst
32
+ }),
33
+ })
34
+ }
35
+
36
+ function forbidMathRandom(): void {
37
+ vi.spyOn(Math, 'random').mockImplementation(() => {
38
+ throw new Error('os temp names must not use Math.random')
39
+ })
40
+ }
41
+
42
+ describe('os temp files', () => {
43
+ it('uses Web Crypto bytes for temp names', () => {
44
+ const root = makeTempRoot()
45
+ stubRandomBytes([0x78, 0x56, 0x34, 0x12])
46
+ forbidMathRandom()
47
+
48
+ const [dir, dirErr] = MkdirTemp(root, 'dir-*.tmp')
49
+ expect(dirErr).toBeNull()
50
+ expect(basename(dir)).toBe('dir-305419896.tmp')
51
+ expect(existsSync(dir)).toBe(true)
52
+
53
+ const [file, fileErr] = CreateTemp(root, 'file-*.tmp')
54
+ expect(fileErr).toBeNull()
55
+ expect(file).not.toBeNull()
56
+ expect(basename(file!.Name())).toBe('file-305419896.tmp')
57
+ expect(existsSync(file!.Name())).toBe(true)
58
+ file!.Close()
59
+ })
60
+
61
+ it('does not truncate an existing path after a random collision', () => {
62
+ const root = makeTempRoot()
63
+ const existing = join(root, 'file-305419896.tmp')
64
+ writeFileSync(existing, 'keep')
65
+ stubRandomBytes([0x78, 0x56, 0x34, 0x12])
66
+ forbidMathRandom()
67
+
68
+ const [file, err] = CreateTemp(root, 'file-*.tmp')
69
+
70
+ expect(file).toBeNull()
71
+ expect(err).not.toBeNull()
72
+ expect(readFileSync(existing, 'utf8')).toBe('keep')
73
+ })
74
+
75
+ it('reports ErrUnimplemented without a secure random source', () => {
76
+ const root = makeTempRoot()
77
+ vi.stubGlobal('crypto', {})
78
+ forbidMathRandom()
79
+
80
+ const [dir, err] = MkdirTemp(root, 'dir-*')
81
+
82
+ expect(dir).toBe('')
83
+ expect(err).toBe(ErrUnimplemented)
84
+ })
85
+ })
@@ -1,8 +1,8 @@
1
1
  import * as $ from "@goscript/builtin/index.js";
2
2
  import { ErrUnimplemented } from "./error.gs.js";
3
3
  import { TempDir } from "./file_constants_js.gs.js";
4
- import { Create, Mkdir } from "./file_js.gs.js";
5
- import { File } from "./types_js.gs.js";
4
+ import { Mkdir } from "./file_js.gs.js";
5
+ import { createHostFile, File, getDeno, getNodeFS, newHostError } from "./types_js.gs.js";
6
6
 
7
7
  export function joinPath(dir: string, file: string): string {
8
8
  if (dir === "" || dir === ".") {
@@ -20,33 +20,93 @@ export function joinPath(dir: string, file: string): string {
20
20
  return dir + "/" + file
21
21
  }
22
22
 
23
- function nextTempPath(dir: string, pattern: string): string {
23
+ function nextTempPath(dir: string, pattern: string): [string, $.GoError] {
24
24
  const baseDir = dir === "" ? TempDir() : dir
25
- const suffix = Math.random().toString(36).slice(2, 10) + Date.now().toString(36)
26
- const name = pattern.includes("*") ? pattern.replace("*", suffix) : pattern + suffix
27
- return joinPath(baseDir, name)
25
+ const [suffix, err] = nextRandom()
26
+ if (err !== null) {
27
+ return ["", err]
28
+ }
29
+ const star = pattern.lastIndexOf("*")
30
+ const name =
31
+ star >= 0
32
+ ? pattern.slice(0, star) + suffix + pattern.slice(star + 1)
33
+ : pattern + suffix
34
+ return [joinPath(baseDir, name), null]
35
+ }
36
+
37
+ function nextRandom(): [string, $.GoError] {
38
+ const crypto = globalThis.crypto
39
+ if (!crypto || typeof crypto.getRandomValues !== "function") {
40
+ return ["", ErrUnimplemented]
41
+ }
42
+ const bytes = new Uint8Array(4)
43
+ crypto.getRandomValues(bytes)
44
+ const value =
45
+ bytes[0] |
46
+ (bytes[1] << 8) |
47
+ (bytes[2] << 16) |
48
+ (bytes[3] << 24)
49
+ return [(value >>> 0).toString(10), null]
50
+ }
51
+
52
+ function createTempFile(path: string): [File | null, $.GoError] {
53
+ const denoObj = getDeno()
54
+ if (denoObj?.openSync) {
55
+ try {
56
+ const handle = denoObj.openSync(path, {
57
+ createNew: true,
58
+ mode: 0o600,
59
+ read: true,
60
+ write: true,
61
+ })
62
+ return [createHostFile(path, handle?.rid ?? -1, handle), null]
63
+ } catch (err) {
64
+ return [null, newHostError(err)]
65
+ }
66
+ }
67
+
68
+ const nodeFS = getNodeFS()
69
+ if (nodeFS?.openSync) {
70
+ try {
71
+ return [createHostFile(path, nodeFS.openSync(path, "wx+", 0o600)), null]
72
+ } catch (err) {
73
+ return [null, newHostError(err)]
74
+ }
75
+ }
76
+
77
+ return [null, ErrUnimplemented]
28
78
  }
29
79
 
30
80
  export function CreateTemp(dir: string, pattern: string): [File | null, $.GoError] {
31
81
  const template = pattern === "" ? "tmp-*" : pattern
82
+ let lastErr: $.GoError = ErrUnimplemented
32
83
  for (let i = 0; i < 16; i++) {
33
- const path = nextTempPath(dir, template)
34
- const [file, err] = Create(path)
84
+ const [path, randErr] = nextTempPath(dir, template)
85
+ if (randErr !== null) {
86
+ return [null, randErr]
87
+ }
88
+ const [file, err] = createTempFile(path)
35
89
  if (err === null) {
36
90
  return [file, null]
37
91
  }
92
+ lastErr = err
38
93
  }
39
- return [null, ErrUnimplemented]
94
+ return [null, lastErr]
40
95
  }
41
96
 
42
97
  export function MkdirTemp(dir: string, pattern: string): [string, $.GoError] {
43
98
  const template = pattern === "" ? "tmp-*" : pattern
99
+ let lastErr: $.GoError = ErrUnimplemented
44
100
  for (let i = 0; i < 16; i++) {
45
- const path = nextTempPath(dir, template)
101
+ const [path, randErr] = nextTempPath(dir, template)
102
+ if (randErr !== null) {
103
+ return ["", randErr]
104
+ }
46
105
  const err = Mkdir(path, 0o700)
47
106
  if (err === null) {
48
107
  return [path, null]
49
108
  }
109
+ lastErr = err
50
110
  }
51
- return ["", ErrUnimplemented]
111
+ return ["", lastErr]
52
112
  }