@ball-lang/compiler 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.
- package/bin/ball-ts-compile.mjs +56 -0
- package/dist/compiler.d.ts +70 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +1674 -0
- package/dist/compiler.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/preamble.d.ts +16 -0
- package/dist/preamble.d.ts.map +1 -0
- package/dist/preamble.js +474 -0
- package/dist/preamble.js.map +1 -0
- package/dist/types.d.ts +115 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +65 -0
- package/src/compiler.ts +1742 -0
- package/src/index.ts +4 -0
- package/src/preamble.ts +473 -0
- package/src/types.ts +127 -0
package/src/index.ts
ADDED
package/src/preamble.ts
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime preamble prepended to every compiled TS output.
|
|
3
|
+
*
|
|
4
|
+
* Contains:
|
|
5
|
+
* - Helpers for Dart-flavored string conversion / parsing
|
|
6
|
+
* - Polyfills for Dart-flavored Array / Map / String methods that
|
|
7
|
+
* don't exist on JS prototypes (containsKey, addAll, removeLast,
|
|
8
|
+
* isEmpty/isNotEmpty/first/last, etc.)
|
|
9
|
+
* - A `__ball_active_error` module-level binding used by `rethrow`
|
|
10
|
+
*
|
|
11
|
+
* Node strips the TS type annotations at runtime via
|
|
12
|
+
* `--experimental-strip-types`, so the `any` annotations and interface
|
|
13
|
+
* declarations below are type-check only — they have no runtime cost.
|
|
14
|
+
*/
|
|
15
|
+
export const TS_RUNTIME_PREAMBLE = String.raw`// ── Ball runtime preamble (generated by @ball-lang/compiler) ────────
|
|
16
|
+
|
|
17
|
+
function __ball_to_string(v: any): string {
|
|
18
|
+
if (v === null || v === undefined) return 'null';
|
|
19
|
+
if (typeof v === 'boolean') return v ? 'true' : 'false';
|
|
20
|
+
if (typeof v === 'number') {
|
|
21
|
+
if (Number.isInteger(v)) return v.toString();
|
|
22
|
+
const s = v.toString();
|
|
23
|
+
return s.includes('.') || s.includes('e') ? s : s + '.0';
|
|
24
|
+
}
|
|
25
|
+
if (typeof v === 'string') return v;
|
|
26
|
+
if (Array.isArray(v)) {
|
|
27
|
+
return '[' + v.map(__ball_to_string).join(', ') + ']';
|
|
28
|
+
}
|
|
29
|
+
if (v instanceof Map) {
|
|
30
|
+
const parts: string[] = [];
|
|
31
|
+
for (const [k, val] of v.entries()) {
|
|
32
|
+
parts.push(__ball_to_string(k) + ': ' + __ball_to_string(val));
|
|
33
|
+
}
|
|
34
|
+
return '{' + parts.join(', ') + '}';
|
|
35
|
+
}
|
|
36
|
+
return String(v);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function __ball_parse_int(s: string): number {
|
|
40
|
+
const trimmed = s.trim();
|
|
41
|
+
if (!/^-?\d+$/.test(trimmed)) {
|
|
42
|
+
throw new Error('FormatException: ' + s);
|
|
43
|
+
}
|
|
44
|
+
return parseInt(trimmed, 10);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function __ball_parse_double(s: string): number {
|
|
48
|
+
const n = parseFloat(s);
|
|
49
|
+
if (Number.isNaN(n)) throw new Error('FormatException: ' + s);
|
|
50
|
+
return n;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function __ball_double_to_string(n: number): string {
|
|
54
|
+
if (Number.isInteger(n)) return n.toFixed(1);
|
|
55
|
+
return n.toString();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Active exception for rethrow. Catch bodies shadow with a local.
|
|
59
|
+
let __ball_active_error: any = undefined;
|
|
60
|
+
|
|
61
|
+
// Dart type shims — provide static methods for Dart built-in types
|
|
62
|
+
// that don't exist in JS (int, double, num, bool).
|
|
63
|
+
const int = {
|
|
64
|
+
parse: (s: any) => { const n = parseInt(String(s), 10); if (isNaN(n)) throw new Error('FormatException: ' + s); return n; },
|
|
65
|
+
tryParse: (s: any) => { const n = parseInt(String(s), 10); return isNaN(n) ? null : n; },
|
|
66
|
+
};
|
|
67
|
+
const double = {
|
|
68
|
+
parse: (s: any) => { const n = parseFloat(String(s)); if (isNaN(n)) throw new Error('FormatException: ' + s); return n; },
|
|
69
|
+
tryParse: (s: any) => { const n = parseFloat(String(s)); return isNaN(n) ? null : n; },
|
|
70
|
+
infinity: Infinity,
|
|
71
|
+
nan: NaN,
|
|
72
|
+
negativeInfinity: -Infinity,
|
|
73
|
+
};
|
|
74
|
+
const num = {
|
|
75
|
+
parse: (s: any) => { const n = Number(s); if (isNaN(n)) throw new Error('FormatException: ' + s); return n; },
|
|
76
|
+
tryParse: (s: any) => { const n = Number(s); return isNaN(n) ? null : n; },
|
|
77
|
+
};
|
|
78
|
+
const bool = {
|
|
79
|
+
parse: (s: any) => { if (s === 'true') return true; if (s === 'false') return false; throw new Error('FormatException: ' + s); },
|
|
80
|
+
tryParse: (s: any) => { if (s === 'true') return true; if (s === 'false') return false; return null; },
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Sentinel for "not yet initialized" — used by the Dart engine for
|
|
84
|
+
// late-initialized variables and block-scoped flow tracking.
|
|
85
|
+
const __no_init__: unique symbol = Symbol('__no_init__');
|
|
86
|
+
|
|
87
|
+
// ── Dart \u2192 JS method-name polyfills ────────────────────────────────
|
|
88
|
+
//
|
|
89
|
+
// Idempotent: guarded so multiple preamble inclusions don't double-install.
|
|
90
|
+
(function installBallPolyfills() {
|
|
91
|
+
const mp: any = Map.prototype;
|
|
92
|
+
if (!mp.containsKey) mp.containsKey = function (k: any) { return this.has(k); };
|
|
93
|
+
if (!mp.putIfAbsent) {
|
|
94
|
+
mp.putIfAbsent = function (k: any, supplier: any) {
|
|
95
|
+
if (!this.has(k)) this.set(k, supplier());
|
|
96
|
+
return this.get(k);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (!mp.addAll) {
|
|
100
|
+
mp.addAll = function (other: any) {
|
|
101
|
+
if (other instanceof Map) {
|
|
102
|
+
for (const [k, v] of other.entries()) this.set(k, v);
|
|
103
|
+
} else if (other && typeof other === 'object') {
|
|
104
|
+
for (const k of Object.keys(other)) this.set(k, other[k]);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
Object.defineProperty(mp, 'isEmpty', {
|
|
109
|
+
configurable: true, get() { return this.size === 0; },
|
|
110
|
+
});
|
|
111
|
+
Object.defineProperty(mp, 'isNotEmpty', {
|
|
112
|
+
configurable: true, get() { return this.size !== 0; },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const ap: any = Array.prototype;
|
|
116
|
+
if (!ap.add) ap.add = function (v: any) { this.push(v); };
|
|
117
|
+
if (!ap.addAll) ap.addAll = function (iter: any) {
|
|
118
|
+
for (const v of iter) this.push(v);
|
|
119
|
+
};
|
|
120
|
+
if (!ap.removeLast) ap.removeLast = function () { return this.pop(); };
|
|
121
|
+
if (!ap.where) ap.where = Array.prototype.filter;
|
|
122
|
+
if (!ap.toList) ap.toList = function () { return this.slice(); };
|
|
123
|
+
if (!ap.toSet) ap.toSet = function () { return new Set(this); };
|
|
124
|
+
if (!ap.contains) ap.contains = function (v: any) { return this.indexOf(v) >= 0; };
|
|
125
|
+
|
|
126
|
+
// Dart Set polyfills — Set.contains → Set.has, etc.
|
|
127
|
+
const setp: any = Set.prototype;
|
|
128
|
+
if (!setp.contains) setp.contains = function (v: any) { return this.has(v); };
|
|
129
|
+
if (!setp.toList) setp.toList = function () { return [...this]; };
|
|
130
|
+
if (!setp.add) { /* Set already has .add */ }
|
|
131
|
+
if (!setp.remove) setp.remove = function (v: any) { return this.delete(v); };
|
|
132
|
+
Object.defineProperty(ap, 'isEmpty', {
|
|
133
|
+
configurable: true, get() { return this.length === 0; },
|
|
134
|
+
});
|
|
135
|
+
Object.defineProperty(ap, 'isNotEmpty', {
|
|
136
|
+
configurable: true, get() { return this.length !== 0; },
|
|
137
|
+
});
|
|
138
|
+
Object.defineProperty(ap, 'first', {
|
|
139
|
+
configurable: true, get() { return this[0]; },
|
|
140
|
+
});
|
|
141
|
+
Object.defineProperty(ap, 'last', {
|
|
142
|
+
configurable: true, get() { return this[this.length - 1]; },
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const sp: any = String.prototype;
|
|
146
|
+
Object.defineProperty(sp, 'isEmpty', {
|
|
147
|
+
configurable: true, get() { return this.length === 0; },
|
|
148
|
+
});
|
|
149
|
+
Object.defineProperty(sp, 'isNotEmpty', {
|
|
150
|
+
configurable: true, get() { return this.length !== 0; },
|
|
151
|
+
});
|
|
152
|
+
// Dart String methods not on JS String.
|
|
153
|
+
if (!sp.contains) sp.contains = function (s: any) { return this.includes(s); };
|
|
154
|
+
if (!sp.replaceFirst) sp.replaceFirst = function (from: any, to: any) {
|
|
155
|
+
return this.replace(from instanceof RegExp ? from : String(from), to);
|
|
156
|
+
};
|
|
157
|
+
if (!sp.codeUnitAt) sp.codeUnitAt = function (i: any) { return this.charCodeAt(i); };
|
|
158
|
+
if (!sp.compareTo) sp.compareTo = function (other: any) {
|
|
159
|
+
return this < other ? -1 : this > other ? 1 : 0;
|
|
160
|
+
};
|
|
161
|
+
if (!sp.allMatches) sp.allMatches = function (pattern: any, start: any) {
|
|
162
|
+
const s = typeof start === 'number' ? this.substring(start) : this;
|
|
163
|
+
if (typeof pattern === 'string') {
|
|
164
|
+
return Array.from(s.matchAll(new RegExp(pattern, 'g')));
|
|
165
|
+
}
|
|
166
|
+
const flags = pattern.flags.includes('g') ? pattern.flags : pattern.flags + 'g';
|
|
167
|
+
return Array.from(s.matchAll(new RegExp(pattern.source, flags)));
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Dart RegExp polyfills.
|
|
171
|
+
const rp: any = RegExp.prototype;
|
|
172
|
+
if (!rp.firstMatch) rp.firstMatch = function (s: any) {
|
|
173
|
+
const m = this.exec(s);
|
|
174
|
+
if (m) m.group = (i: any) => m[i];
|
|
175
|
+
return m;
|
|
176
|
+
};
|
|
177
|
+
if (!rp.allMatches) rp.allMatches = function (s: any) {
|
|
178
|
+
const flags = this.flags.includes('g') ? this.flags : this.flags + 'g';
|
|
179
|
+
return [...s.matchAll(new RegExp(this.source, flags))];
|
|
180
|
+
};
|
|
181
|
+
if (!rp.hasMatch) rp.hasMatch = function (s: any) { return this.test(s); };
|
|
182
|
+
|
|
183
|
+
// Object.prototype polyfills — used by the compiled engine when
|
|
184
|
+
// checking Ball program inputs (plain objects, not Maps).
|
|
185
|
+
const op2: any = Object.prototype;
|
|
186
|
+
if (!op2.containsKey) {
|
|
187
|
+
Object.defineProperty(op2, 'containsKey', {
|
|
188
|
+
configurable: true, writable: true, enumerable: false,
|
|
189
|
+
value: function (k: any) {
|
|
190
|
+
if (this instanceof Map) return this.has(k);
|
|
191
|
+
if (this == null || typeof this !== 'object') return false;
|
|
192
|
+
return k in this;
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
// putIfAbsent — Dart Map.putIfAbsent. Works on plain objects too.
|
|
197
|
+
if (!op2.putIfAbsent) {
|
|
198
|
+
Object.defineProperty(op2, 'putIfAbsent', {
|
|
199
|
+
configurable: true, writable: true, enumerable: false,
|
|
200
|
+
value: function (k: any, supplier: any) {
|
|
201
|
+
if (this instanceof Map) {
|
|
202
|
+
if (!this.has(k)) this.set(k, supplier());
|
|
203
|
+
return this.get(k);
|
|
204
|
+
}
|
|
205
|
+
if (!(k in this)) this[k] = supplier();
|
|
206
|
+
return this[k];
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
// remove — Dart Map.remove.
|
|
211
|
+
if (!op2.remove) {
|
|
212
|
+
Object.defineProperty(op2, 'remove', {
|
|
213
|
+
configurable: true, writable: true, enumerable: false,
|
|
214
|
+
value: function (k: any) {
|
|
215
|
+
if (this instanceof Map) { const v = this.get(k); this.delete(k); return v; }
|
|
216
|
+
const v = this[k]; delete this[k]; return v;
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
// Dart Map has .entries / .keys / .values as GETTERS (no parens).
|
|
221
|
+
// JS Map has them as METHODS (need parens). The compiled engine
|
|
222
|
+
// accesses map.entries as a getter. Shadow BOTH Map.prototype AND
|
|
223
|
+
// Object.prototype so Map and plain-object dispatch tables work.
|
|
224
|
+
const _nativeMapEntries = Map.prototype.entries;
|
|
225
|
+
const _nativeMapKeys = Map.prototype.keys;
|
|
226
|
+
const _nativeMapValues = Map.prototype.values;
|
|
227
|
+
// Shadow Map.prototype.entries with a getter (Dart uses it as a getter).
|
|
228
|
+
Object.defineProperty(Map.prototype, 'entries', {
|
|
229
|
+
configurable: true, enumerable: false,
|
|
230
|
+
get() {
|
|
231
|
+
return [..._nativeMapEntries.call(this)].map(([k, v]: any) => ({ key: k, value: v }));
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
Object.defineProperty(Map.prototype, 'keys', {
|
|
235
|
+
configurable: true, enumerable: false,
|
|
236
|
+
get() { return [..._nativeMapKeys.call(this)]; },
|
|
237
|
+
});
|
|
238
|
+
Object.defineProperty(Map.prototype, 'values', {
|
|
239
|
+
configurable: true, enumerable: false,
|
|
240
|
+
get() { return [..._nativeMapValues.call(this)]; },
|
|
241
|
+
});
|
|
242
|
+
// For plain objects — same getters on Object.prototype.
|
|
243
|
+
Object.defineProperty(op2, 'entries', {
|
|
244
|
+
configurable: true, enumerable: false,
|
|
245
|
+
get() {
|
|
246
|
+
if (this instanceof Map) return [..._nativeMapEntries.call(this)].map(([k, v]: any) => ({ key: k, value: v }));
|
|
247
|
+
if (this == null || typeof this !== 'object') return [];
|
|
248
|
+
return Object.entries(this).map(([k, v]: any) => ({ key: k, value: v }));
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
Object.defineProperty(op2, 'keys', {
|
|
252
|
+
configurable: true, enumerable: false,
|
|
253
|
+
get() {
|
|
254
|
+
if (this instanceof Map) return [..._nativeMapKeys.call(this)];
|
|
255
|
+
if (this == null || typeof this !== 'object') return [];
|
|
256
|
+
return Object.keys(this);
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
Object.defineProperty(op2, 'values', {
|
|
260
|
+
configurable: true, enumerable: false,
|
|
261
|
+
get() {
|
|
262
|
+
if (this instanceof Map) return [..._nativeMapValues.call(this)];
|
|
263
|
+
if (this == null || typeof this !== 'object') return [];
|
|
264
|
+
return Object.values(this);
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Number polyfills for Dart-style methods.
|
|
269
|
+
const np: any = Number.prototype;
|
|
270
|
+
if (!np.toInt) np.toInt = function () { return Math.trunc(this); };
|
|
271
|
+
if (!np.toDouble) np.toDouble = function () { return this + 0.0; };
|
|
272
|
+
if (!np.compareTo) np.compareTo = function (other: any) {
|
|
273
|
+
return this < other ? -1 : this > other ? 1 : 0;
|
|
274
|
+
};
|
|
275
|
+
if (!np.clamp) np.clamp = function (lo: any, hi: any) {
|
|
276
|
+
return Math.min(Math.max(this, lo), hi);
|
|
277
|
+
};
|
|
278
|
+
})();
|
|
279
|
+
|
|
280
|
+
// ── Protobuf Struct/Value compatibility ─────────────────────────
|
|
281
|
+
//
|
|
282
|
+
// Dart's protobuf runtime wraps google.protobuf.Struct as a class
|
|
283
|
+
// with .fields (Map<String, Value>) and Value as .whichKind() +
|
|
284
|
+
// .stringValue / .boolValue / .numberValue / .listValue / .structValue.
|
|
285
|
+
// In proto3 JSON, these serialize as plain objects and values.
|
|
286
|
+
//
|
|
287
|
+
// This shim makes plain JSON objects behave like Struct/Value so the
|
|
288
|
+
// compiled engine.dart can call .fields['key'].whichKind() etc.
|
|
289
|
+
//
|
|
290
|
+
// Strategy: Object.prototype gets a .fields getter that returns a
|
|
291
|
+
// Proxy wrapping the object as a Map-like. Accessing [key] on the
|
|
292
|
+
// proxy returns a Value-like wrapper with .whichKind() / typed
|
|
293
|
+
// accessors (.stringValue, .boolValue, .numberValue, .listValue,
|
|
294
|
+
// .structValue).
|
|
295
|
+
|
|
296
|
+
const structpb_Value_Kind = {
|
|
297
|
+
nullValue: 'nullValue',
|
|
298
|
+
numberValue: 'numberValue',
|
|
299
|
+
stringValue: 'stringValue',
|
|
300
|
+
boolValue: 'boolValue',
|
|
301
|
+
structValue: 'structValue',
|
|
302
|
+
listValue: 'listValue',
|
|
303
|
+
} as const;
|
|
304
|
+
|
|
305
|
+
class __BallValueWrapper {
|
|
306
|
+
private _raw: any;
|
|
307
|
+
constructor(raw: any) { this._raw = raw; }
|
|
308
|
+
whichKind(): string {
|
|
309
|
+
const v = this._raw;
|
|
310
|
+
if (v === null || v === undefined) return 'nullValue';
|
|
311
|
+
if (typeof v === 'string') return 'stringValue';
|
|
312
|
+
if (typeof v === 'boolean') return 'boolValue';
|
|
313
|
+
if (typeof v === 'number') return 'numberValue';
|
|
314
|
+
if (Array.isArray(v)) return 'listValue';
|
|
315
|
+
if (typeof v === 'object') return 'structValue';
|
|
316
|
+
return 'nullValue';
|
|
317
|
+
}
|
|
318
|
+
get stringValue(): string { return typeof this._raw === 'string' ? this._raw : String(this._raw ?? ''); }
|
|
319
|
+
get boolValue(): boolean { return !!this._raw; }
|
|
320
|
+
get numberValue(): number { return Number(this._raw); }
|
|
321
|
+
get nullValue(): null { return null; }
|
|
322
|
+
get listValue(): { values: __BallValueWrapper[] } {
|
|
323
|
+
const arr = Array.isArray(this._raw) ? this._raw : [];
|
|
324
|
+
return { values: arr.map((v: any) => new __BallValueWrapper(v)) };
|
|
325
|
+
}
|
|
326
|
+
get structValue(): { fields: Record<string, __BallValueWrapper> } {
|
|
327
|
+
const obj = (typeof this._raw === 'object' && this._raw !== null) ? this._raw : {};
|
|
328
|
+
const fields: Record<string, __BallValueWrapper> = {};
|
|
329
|
+
for (const [k, v] of Object.entries(obj)) fields[k] = new __BallValueWrapper(v);
|
|
330
|
+
return { fields };
|
|
331
|
+
}
|
|
332
|
+
// Also proxy hasXxx for sub-values.
|
|
333
|
+
hasNullValue(): boolean { return this._raw == null; }
|
|
334
|
+
hasStringValue(): boolean { return typeof this._raw === 'string'; }
|
|
335
|
+
hasBoolValue(): boolean { return typeof this._raw === 'boolean'; }
|
|
336
|
+
hasNumberValue(): boolean { return typeof this._raw === 'number'; }
|
|
337
|
+
hasListValue(): boolean { return Array.isArray(this._raw); }
|
|
338
|
+
hasStructValue(): boolean { return typeof this._raw === 'object' && this._raw !== null && !Array.isArray(this._raw); }
|
|
339
|
+
// Pass-through for when the wrapper is used in expressions.
|
|
340
|
+
toString(): string { return String(this._raw); }
|
|
341
|
+
valueOf(): any { return this._raw; }
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Struct.fields shimming is done via a metadata-specific wrapper.
|
|
345
|
+
// We do NOT add .fields to Object.prototype because it conflicts
|
|
346
|
+
// with data properties named "fields" on MessageCreation / TypeDef.
|
|
347
|
+
// Instead, the compiled engine accesses metadata.fields['key'] —
|
|
348
|
+
// in proto3 JSON, metadata IS the fields directly, so we add a
|
|
349
|
+
// .fields getter only when the object is a metadata Struct (i.e.,
|
|
350
|
+
// it has string/bool/number/array/object values and no proto-shape
|
|
351
|
+
// keys like "call"/"literal"/"block").
|
|
352
|
+
//
|
|
353
|
+
// The protoWrap normalizer in the test harness is responsible for
|
|
354
|
+
// converting metadata objects to have the right shape.
|
|
355
|
+
|
|
356
|
+
// ── Protobuf compatibility shims ────────────────────────────────
|
|
357
|
+
//
|
|
358
|
+
// The Dart encoder produces code that uses Dart's protobuf runtime
|
|
359
|
+
// API (.whichExpr(), Expression_Expr.call, .hasInput(), .toInt(), etc.)
|
|
360
|
+
// on what are really plain JSON objects at runtime. These shims make
|
|
361
|
+
// the proto-style method calls work on plain objects so the compiled
|
|
362
|
+
// engine.dart can execute on Node.
|
|
363
|
+
|
|
364
|
+
// Oneof discriminator enums — string-valued constants that match the
|
|
365
|
+
// field names the Dart protobuf codegen uses.
|
|
366
|
+
const Expression_Expr = {
|
|
367
|
+
call: 'call', literal: 'literal', reference: 'reference',
|
|
368
|
+
fieldAccess: 'fieldAccess', messageCreation: 'messageCreation',
|
|
369
|
+
block: 'block', lambda: 'lambda', notSet: 'notSet',
|
|
370
|
+
} as const;
|
|
371
|
+
|
|
372
|
+
const Literal_Value = {
|
|
373
|
+
intValue: 'intValue', doubleValue: 'doubleValue',
|
|
374
|
+
stringValue: 'stringValue', boolValue: 'boolValue',
|
|
375
|
+
listValue: 'listValue', bytesValue: 'bytesValue', notSet: 'notSet',
|
|
376
|
+
} as const;
|
|
377
|
+
|
|
378
|
+
const Statement_Stmt = {
|
|
379
|
+
let: 'let', expression: 'expression', notSet: 'notSet',
|
|
380
|
+
} as const;
|
|
381
|
+
|
|
382
|
+
const ModuleImport_Source = {
|
|
383
|
+
http: 'http', file: 'file', inline: 'inline',
|
|
384
|
+
git: 'git', registry: 'registry', notSet: 'notSet',
|
|
385
|
+
} as const;
|
|
386
|
+
|
|
387
|
+
// Object.prototype shims for .whichXxx() / .hasXxx() / .toInt() —
|
|
388
|
+
// these match the Dart protobuf generated API. Each is configurable
|
|
389
|
+
// and non-enumerable so it doesn't pollute for-in loops.
|
|
390
|
+
(function installProtoShims() {
|
|
391
|
+
const op: any = Object.prototype;
|
|
392
|
+
|
|
393
|
+
function defMethod(name: string, fn: Function) {
|
|
394
|
+
if (op[name]) return;
|
|
395
|
+
Object.defineProperty(op, name, {
|
|
396
|
+
configurable: true, writable: true, enumerable: false, value: fn,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// whichExpr / whichValue / whichStmt / whichSource — return which
|
|
401
|
+
// oneof field is set on this object.
|
|
402
|
+
defMethod('whichExpr', function (this: any) {
|
|
403
|
+
for (const k of ['call','literal','reference','fieldAccess','messageCreation','block','lambda']) {
|
|
404
|
+
if (this[k] !== undefined && this[k] !== null) return k;
|
|
405
|
+
}
|
|
406
|
+
return 'notSet';
|
|
407
|
+
});
|
|
408
|
+
defMethod('whichValue', function (this: any) {
|
|
409
|
+
for (const k of ['intValue','doubleValue','stringValue','boolValue','listValue','bytesValue']) {
|
|
410
|
+
if (this[k] !== undefined && this[k] !== null) return k;
|
|
411
|
+
}
|
|
412
|
+
return 'notSet';
|
|
413
|
+
});
|
|
414
|
+
defMethod('whichStmt', function (this: any) {
|
|
415
|
+
if (this['let'] !== undefined && this['let'] !== null) return 'let';
|
|
416
|
+
if (this['expression'] !== undefined && this['expression'] !== null) return 'expression';
|
|
417
|
+
return 'notSet';
|
|
418
|
+
});
|
|
419
|
+
defMethod('whichSource', function (this: any) {
|
|
420
|
+
for (const k of ['http','file','inline','git','registry']) {
|
|
421
|
+
if (this[k] !== undefined && this[k] !== null) return k;
|
|
422
|
+
}
|
|
423
|
+
return 'notSet';
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Presence checks — .hasXxx() returns true if the field is set.
|
|
427
|
+
for (const field of [
|
|
428
|
+
'input','body','result','metadata','value','name','module',
|
|
429
|
+
'left','right','condition','then','else','finally',
|
|
430
|
+
'subject','cases','catches','init','update','iterable',
|
|
431
|
+
'target','index','field','object','key','message',
|
|
432
|
+
'stringValue','boolValue','intValue','doubleValue','listValue',
|
|
433
|
+
'call','literal','reference','fieldAccess','messageCreation',
|
|
434
|
+
'block','lambda','let','expression','descriptor',
|
|
435
|
+
]) {
|
|
436
|
+
const methodName = 'has' + field[0].toUpperCase() + field.slice(1);
|
|
437
|
+
defMethod(methodName, function (this: any) {
|
|
438
|
+
return this[field] !== undefined && this[field] !== null;
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Proto field-name aliases — Dart's protobuf codegen renames some
|
|
443
|
+
// fields to avoid keyword collisions (field → field_2, etc.) but
|
|
444
|
+
// proto3 JSON uses the original names. Add getters so both work.
|
|
445
|
+
Object.defineProperty(op, 'field_2', {
|
|
446
|
+
configurable: true, enumerable: false,
|
|
447
|
+
get() { return this.field; },
|
|
448
|
+
set(v: any) { this.field = v; },
|
|
449
|
+
});
|
|
450
|
+
// descriptor_ → descriptor (same issue)
|
|
451
|
+
Object.defineProperty(op, 'descriptor_', {
|
|
452
|
+
configurable: true, enumerable: false,
|
|
453
|
+
get() { return this.descriptor; },
|
|
454
|
+
set(v: any) { this.descriptor = v; },
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// .toInt() — Dart's Int64/fixnum returns int from string. In proto3
|
|
458
|
+
// JSON, int64 fields are serialized as strings ("42" not 42).
|
|
459
|
+
defMethod('toInt', function (this: any) {
|
|
460
|
+
if (typeof this === 'number') return this;
|
|
461
|
+
if (typeof this === 'string') return parseInt(this, 10);
|
|
462
|
+
if (typeof this.valueOf === 'function') return parseInt(String(this.valueOf()), 10);
|
|
463
|
+
return 0;
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// .toList() on Uint8Array (bytesValue)
|
|
467
|
+
defMethod('toList', function (this: any) {
|
|
468
|
+
if (this instanceof Uint8Array) return Array.from(this);
|
|
469
|
+
if (Array.isArray(this)) return this.slice();
|
|
470
|
+
return [];
|
|
471
|
+
});
|
|
472
|
+
})();
|
|
473
|
+
`;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ball protobuf shapes consumed by the TS compiler.
|
|
3
|
+
*
|
|
4
|
+
* Matches the proto3 JSON form emitted by the Dart encoder's
|
|
5
|
+
* `Program.toProto3Json()` (see `proto/ball/v1/ball.proto`). Keep
|
|
6
|
+
* these in sync with the shapes in `@ball-lang/engine/src/index.ts`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface Program {
|
|
10
|
+
name?: string;
|
|
11
|
+
version?: string;
|
|
12
|
+
modules: Module[];
|
|
13
|
+
entryModule: string;
|
|
14
|
+
entryFunction: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface Module {
|
|
18
|
+
name: string;
|
|
19
|
+
functions: FunctionDef[];
|
|
20
|
+
typeDefs?: TypeDefinition[];
|
|
21
|
+
typeAliases?: TypeAlias[];
|
|
22
|
+
enums?: EnumDef[];
|
|
23
|
+
moduleImports?: ModuleImport[];
|
|
24
|
+
metadata?: Struct;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ModuleImport {
|
|
28
|
+
name: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface FunctionDef {
|
|
32
|
+
name: string;
|
|
33
|
+
isBase?: boolean;
|
|
34
|
+
body?: Expression;
|
|
35
|
+
outputType?: string;
|
|
36
|
+
metadata?: Struct;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface TypeDefinition {
|
|
40
|
+
name: string;
|
|
41
|
+
descriptor?: DescriptorProto;
|
|
42
|
+
description?: string;
|
|
43
|
+
metadata?: Struct;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface TypeAlias {
|
|
47
|
+
name: string;
|
|
48
|
+
targetType: string;
|
|
49
|
+
metadata?: Struct;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface EnumDef {
|
|
53
|
+
name: string;
|
|
54
|
+
values: EnumValue[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface EnumValue {
|
|
58
|
+
name: string;
|
|
59
|
+
intValue?: string | number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface DescriptorProto {
|
|
63
|
+
name: string;
|
|
64
|
+
field?: FieldDescriptor[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface FieldDescriptor {
|
|
68
|
+
name: string;
|
|
69
|
+
number?: number;
|
|
70
|
+
type?: string;
|
|
71
|
+
label?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface Expression {
|
|
75
|
+
call?: FunctionCall;
|
|
76
|
+
literal?: Literal;
|
|
77
|
+
reference?: { name: string };
|
|
78
|
+
fieldAccess?: { object: Expression; field: string };
|
|
79
|
+
messageCreation?: MessageCreation;
|
|
80
|
+
block?: Block;
|
|
81
|
+
lambda?: Lambda;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface FunctionCall {
|
|
85
|
+
module?: string;
|
|
86
|
+
function: string;
|
|
87
|
+
input?: Expression;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface Literal {
|
|
91
|
+
intValue?: string | number;
|
|
92
|
+
doubleValue?: number;
|
|
93
|
+
stringValue?: string;
|
|
94
|
+
boolValue?: boolean;
|
|
95
|
+
listValue?: { elements: Expression[] };
|
|
96
|
+
bytesValue?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface MessageCreation {
|
|
100
|
+
typeName?: string;
|
|
101
|
+
fields: FieldValuePair[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface FieldValuePair {
|
|
105
|
+
name: string;
|
|
106
|
+
value: Expression;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface Block {
|
|
110
|
+
statements: Statement[];
|
|
111
|
+
result?: Expression;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface Statement {
|
|
115
|
+
let?: { name: string; value?: Expression; metadata?: Struct };
|
|
116
|
+
expression?: Expression;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface Lambda extends FunctionDef {
|
|
120
|
+
// A Lambda is just an inline FunctionDef (body + metadata.params).
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Struct (google.protobuf.Struct JSON form) ─────────────────────────────
|
|
124
|
+
//
|
|
125
|
+
// In proto3 JSON, Struct serializes as a plain object (no `fields` wrapper).
|
|
126
|
+
// Use Struct as the metadata bag type throughout.
|
|
127
|
+
export type Struct = Record<string, unknown>;
|