runtime.mjs raw
1 // TinyJS Runtime — Core
2 // Provides panic/recover/defer, program initialization, and console I/O.
3
4 export class GoPanic extends Error {
5 constructor(value) {
6 super(typeof value === 'string' ? value : JSON.stringify(value));
7 this.goValue = value;
8 this.name = 'GoPanic';
9 }
10 }
11
12 // Defer stack. Each goroutine has its own.
13 export class DeferStack {
14 constructor() {
15 this.frames = [];
16 }
17
18 push(fn) {
19 this.frames.push(fn);
20 }
21
22 run() {
23 let panicked = null;
24 while (this.frames.length > 0) {
25 const fn = this.frames.pop();
26 try {
27 fn();
28 } catch (e) {
29 panicked = e;
30 }
31 }
32 if (panicked) throw panicked;
33 }
34 }
35
36 // Recover returns the panic value if called inside a deferred function
37 // during a panic. Otherwise returns null.
38 let currentPanicValue = null;
39 let recovering = false;
40
41 export function setPanic(val) {
42 currentPanicValue = val;
43 }
44
45 export function recover() {
46 if (recovering) {
47 const val = currentPanicValue;
48 currentPanicValue = null;
49 recovering = false;
50 return val;
51 }
52 return null;
53 }
54
55 // Panic halts the current goroutine. Deferred functions run first.
56 export function panic(value) {
57 throw new GoPanic(value);
58 }
59
60 // Run deferred functions, handling panic/recover.
61 export async function runDefers(deferStack) {
62 const frames = [...deferStack.frames].reverse();
63 deferStack.frames = [];
64 let lastPanic = null;
65
66 for (const fn of frames) {
67 try {
68 recovering = lastPanic !== null;
69 if (recovering) {
70 currentPanicValue = lastPanic instanceof GoPanic ? lastPanic.goValue : lastPanic;
71 }
72 const result = fn();
73 if (result instanceof Promise) await result;
74 if (recovering && currentPanicValue === null) {
75 lastPanic = null; // recovered
76 }
77 recovering = false;
78 } catch (e) {
79 recovering = false;
80 lastPanic = e;
81 }
82 }
83
84 if (lastPanic) throw lastPanic;
85 }
86
87 // Synchronous version of defer stack runner for generated code.
88 // Takes an array of deferred functions (already in push order; will pop from end).
89 export function runDeferStack(defers, panicValue) {
90 let lastPanic = panicValue || null;
91 while (defers.length) {
92 const fn = defers.pop();
93 try {
94 recovering = lastPanic !== null;
95 if (recovering) {
96 currentPanicValue = lastPanic instanceof GoPanic ? lastPanic.goValue : lastPanic;
97 }
98 fn();
99 // If recover() was called, it clears currentPanicValue to null.
100 // Check if the panic was recovered (currentPanicValue cleared by recover()).
101 if (currentPanicValue === null && lastPanic !== null) {
102 lastPanic = null; // recovered
103 }
104 recovering = false;
105 } catch (e) {
106 recovering = false;
107 lastPanic = e;
108 }
109 }
110 currentPanicValue = null;
111 recovering = false;
112 if (lastPanic) throw lastPanic;
113 }
114
115 // Zero values for Go types.
116 export function zeroValue(typeId) {
117 switch (typeId) {
118 case 'bool': return false;
119 case 'int': case 'int8': case 'int16': case 'int32': case 'int64':
120 case 'uint': case 'uint8': case 'uint16': case 'uint32': case 'uint64':
121 case 'float32': case 'float64':
122 case 'uintptr':
123 return 0;
124 case 'complex64': case 'complex128':
125 return { re: 0, im: 0 };
126 case 'string':
127 return '';
128 default:
129 return null;
130 }
131 }
132
133 // Println — maps to fmt.Println for the runtime.
134 export function println(...args) {
135 console.log(args.map(a => {
136 if (typeof a === 'string') return a;
137 if (typeof a === 'boolean') return a ? 'true' : 'false';
138 if (a === null || a === undefined) return '<nil>';
139 return String(a);
140 }).join(' '));
141 }
142
143 // Print without newline.
144 export function print(...args) {
145 const text = args.map(String).join(' ');
146 if (typeof process !== 'undefined' && process.stdout) {
147 process.stdout.write(text);
148 } else {
149 console.log(text);
150 }
151 }
152
153 // Program exit.
154 export function exit(code) {
155 if (typeof process !== 'undefined' && process.exit) {
156 process.exit(code);
157 } else {
158 throw new Error('exit: ' + code);
159 }
160 }
161
162 // Nil check helper.
163 export function nilCheck(ptr, op) {
164 if (ptr === null || ptr === undefined) {
165 panic('runtime error: invalid memory address or nil pointer dereference');
166 }
167 }
168
169 // Bounds check helper.
170 export function boundsCheck(index, length) {
171 if (index < 0 || index >= length) {
172 panic(`runtime error: index out of range [${index}] with length ${length}`);
173 }
174 }
175
176 // Slice bounds check.
177 export function sliceBoundsCheck(low, high, max) {
178 if (low < 0 || high < low || max < high) {
179 panic(`runtime error: slice bounds out of range [${low}:${high}:${max}]`);
180 }
181 }
182