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 Moxie types.
116 export function zeroValue(typeId) {
117 switch (typeId) {
118 case 'bool': return false;
119 case 'int64': case 'uint64':
120 return 0n;
121 case 'int': case 'int8': case 'int16': case 'int32':
122 case 'uint': case 'uint8': case 'uint16': case 'uint32':
123 case 'float32': case 'float64':
124 return 0;
125 default:
126 return null;
127 }
128 }
129
130 // Println — maps to fmt.Println for the runtime.
131 export function println(...args) {
132 console.log(args.map(a => {
133 if (typeof a === 'string') return a;
134 if (typeof a === 'boolean') return a ? 'true' : 'false';
135 if (a === null || a === undefined) return '<nil>';
136 return String(a);
137 }).join(' '));
138 }
139
140 // Print without newline.
141 export function print(...args) {
142 const text = args.map(String).join(' ');
143 console.log(text);
144 }
145
146 // Program exit.
147 export function exit(code) {
148 throw new Error('exit: ' + code);
149 }
150
151 // Nil check helper.
152 export function nilCheck(ptr, op) {
153 if (ptr === null || ptr === undefined) {
154 panic('runtime error: invalid memory address or nil pointer dereference');
155 }
156 }
157
158 // Bounds check helper.
159 export function boundsCheck(index, length) {
160 if (index < 0 || index >= length) {
161 panic(`runtime error: index out of range [${index}] with length ${length}`);
162 }
163 }
164
165 // Slice bounds check.
166 export function sliceBoundsCheck(low, high, max) {
167 if (low < 0 || high < low || max < high) {
168 panic(`runtime error: slice bounds out of range [${low}:${high}:${max}]`);
169 }
170 }
171