@ball-lang/compiler 1.3.9 → 1.4.1
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/dist/compiler.d.ts +1 -0
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +189 -1
- package/dist/compiler.js.map +1 -1
- package/package.json +1 -1
- package/src/compiler.ts +183 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ball-lang/compiler",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Ball → TypeScript compiler. Consumes a Ball protobuf Program and emits idiomatic TypeScript via ts-morph. The canonical TS compiler for Ball — lives in TS land so TS syntax knowledge doesn't leak into other languages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/src/compiler.ts
CHANGED
|
@@ -123,13 +123,33 @@ export class BallCompiler {
|
|
|
123
123
|
|
|
124
124
|
// Collect ALL non-base modules (entry + user library modules).
|
|
125
125
|
const userModules: Module[] = [];
|
|
126
|
+
let usesStdMemory = false;
|
|
126
127
|
for (const mod of this.program.modules ?? []) {
|
|
127
128
|
const fns = mod.functions ?? [];
|
|
128
129
|
const allBase = fns.length > 0 && fns.every((f: FunctionDef) => f.isBase);
|
|
129
|
-
if (allBase)
|
|
130
|
+
if (allBase) {
|
|
131
|
+
if (mod.name === "std_memory") usesStdMemory = true;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
130
134
|
userModules.push(mod);
|
|
131
135
|
}
|
|
132
136
|
|
|
137
|
+
// ── Linear memory runtime preamble ──
|
|
138
|
+
// If the program imports `std_memory` (linear memory simulation), inject
|
|
139
|
+
// the runtime variables backing the `ByteData`/`Endian` shims already
|
|
140
|
+
// defined in the (always-included) runtime preamble. Mirrors the Dart
|
|
141
|
+
// compiler's conditional injection (dart/compiler/lib/compiler.dart,
|
|
142
|
+
// "Linear memory runtime preamble") — only emitted when actually used.
|
|
143
|
+
if (usesStdMemory) {
|
|
144
|
+
sf.addStatements(
|
|
145
|
+
"// Ball linear memory runtime\n" +
|
|
146
|
+
"const _ballMemory = new ByteData(65536);\n" +
|
|
147
|
+
"let _ballHeapPtr = 0;\n" +
|
|
148
|
+
"const _ballStackFrames: number[] = [];\n" +
|
|
149
|
+
"let _ballStackPtr = 65536;\n",
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
133
153
|
// Seed the function-name + typeDef lookup tables from ALL user modules.
|
|
134
154
|
this.allFunctionNames = new Set(
|
|
135
155
|
userModules.flatMap((m) => (m.functions ?? []).map((f: FunctionDef) => f.name)),
|
|
@@ -4192,6 +4212,7 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
4192
4212
|
|
|
4193
4213
|
private compileStdCall(call: FunctionCall): string {
|
|
4194
4214
|
const fn = call.function;
|
|
4215
|
+
if (call.module === "std_memory") return this.compileMemoryCall(call);
|
|
4195
4216
|
const f = fieldMap(call.input?.messageCreation?.fields ?? []);
|
|
4196
4217
|
const fg = (...names: string[]) => {
|
|
4197
4218
|
for (const n of names) { const v = f.get(n); if (v !== undefined) return v; }
|
|
@@ -5041,6 +5062,167 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
5041
5062
|
}
|
|
5042
5063
|
}
|
|
5043
5064
|
|
|
5065
|
+
// ── std_memory → TS linear-memory compilation ──────────────────────
|
|
5066
|
+
//
|
|
5067
|
+
// Mirrors the Dart compiler's `_compileMemoryCall` (dart/compiler/lib/
|
|
5068
|
+
// compiler.dart): lowers std_memory base calls to a `ByteData`-backed
|
|
5069
|
+
// linear memory simulation. The `ByteData`/`Endian` shims live in the
|
|
5070
|
+
// runtime preamble (preamble.ts, "dart:typed_data shims"); `compile()`
|
|
5071
|
+
// conditionally emits the `_ballMemory`/`_ballHeapPtr`/`_ballStackFrames`/
|
|
5072
|
+
// `_ballStackPtr` runtime variables only when the program actually
|
|
5073
|
+
// imports `std_memory` (see `usesStdMemory` in `compile()`).
|
|
5074
|
+
//
|
|
5075
|
+
// Every std_memory base function declared in dart/shared/lib/std_memory.dart
|
|
5076
|
+
// MUST have a case below. An unhandled function throws a compile-time
|
|
5077
|
+
// Error naming it — never falls through to a bare/undefined identifier.
|
|
5078
|
+
private compileMemoryCall(call: FunctionCall): string {
|
|
5079
|
+
const f = fieldMap(call.input?.messageCreation?.fields ?? []);
|
|
5080
|
+
const addrExpr = () => {
|
|
5081
|
+
const a = f.get("address") ?? f.get("dest") ?? f.get("a");
|
|
5082
|
+
return a ? this.expr(a) : "0";
|
|
5083
|
+
};
|
|
5084
|
+
const valExpr = () => {
|
|
5085
|
+
const v = f.get("value");
|
|
5086
|
+
return v ? this.expr(v) : "0";
|
|
5087
|
+
};
|
|
5088
|
+
// i64/u64 memory cells are backed by ByteData's bigint-typed
|
|
5089
|
+
// getInt64/getUint64/setInt64/setUint64 (see preamble.ts). Ball int
|
|
5090
|
+
// literals below Number.MAX_SAFE_INTEGER compile to plain JS `number`
|
|
5091
|
+
// literals (compileLiteral), so writing them straight into a
|
|
5092
|
+
// bigint-typed setter would throw "Cannot mix BigInt and other types"
|
|
5093
|
+
// at runtime — coerce explicitly.
|
|
5094
|
+
const valExprBigInt = () => `BigInt(${valExpr()})`;
|
|
5095
|
+
const ptrArith = (op: "+" | "-") => {
|
|
5096
|
+
const addr = f.get("address");
|
|
5097
|
+
const offset = f.get("offset");
|
|
5098
|
+
const elemSize = f.get("element_size");
|
|
5099
|
+
const a = addr ? this.expr(addr) : "0";
|
|
5100
|
+
const o = offset ? this.expr(offset) : "0";
|
|
5101
|
+
const es = elemSize ? this.expr(elemSize) : "1";
|
|
5102
|
+
return `(${a} ${op} (${o} * ${es}))`;
|
|
5103
|
+
};
|
|
5104
|
+
|
|
5105
|
+
switch (call.function) {
|
|
5106
|
+
// ── Allocation ──
|
|
5107
|
+
case "memory_alloc": {
|
|
5108
|
+
const size = f.get("size");
|
|
5109
|
+
const sizeStr = size ? this.expr(size) : "0";
|
|
5110
|
+
return `(() => { const __addr = _ballHeapPtr; _ballHeapPtr += ${sizeStr}; return __addr; })()`;
|
|
5111
|
+
}
|
|
5112
|
+
case "memory_free":
|
|
5113
|
+
return `(/* free(${addrExpr()}) — noop in TS */ undefined)`;
|
|
5114
|
+
case "memory_realloc": {
|
|
5115
|
+
// Bump-allocates the new block, then copies the old block's bytes
|
|
5116
|
+
// in (mirrors the Dart compiler's `_memRealloc`; see its doc
|
|
5117
|
+
// comment re: realloc semantics and issue #141).
|
|
5118
|
+
const addr = f.get("address") ?? f.get("value");
|
|
5119
|
+
const addrStr = addr ? this.expr(addr) : "0";
|
|
5120
|
+
const size = f.get("new_size") ?? f.get("size");
|
|
5121
|
+
const sizeStr = size ? this.expr(size) : "0";
|
|
5122
|
+
return `(() => { const __old = ${addrStr}; const __size = ${sizeStr}; ` +
|
|
5123
|
+
`const __addr = _ballHeapPtr; _ballHeapPtr += __size; ` +
|
|
5124
|
+
`let __n = __size; ` +
|
|
5125
|
+
`if (__old + __n > _ballMemory.lengthInBytes) { __n = _ballMemory.lengthInBytes - __old; } ` +
|
|
5126
|
+
`if (__addr + __n > _ballMemory.lengthInBytes) { __n = _ballMemory.lengthInBytes - __addr; } ` +
|
|
5127
|
+
`for (let __i = 0; __i < __n; __i++) { _ballMemory.setUint8(__addr + __i, _ballMemory.getUint8(__old + __i)); } ` +
|
|
5128
|
+
`return __addr; })()`;
|
|
5129
|
+
}
|
|
5130
|
+
// ── Typed reads (little-endian) ──
|
|
5131
|
+
case "memory_read_i8": return `_ballMemory.getInt8(${addrExpr()})`;
|
|
5132
|
+
case "memory_read_u8": return `_ballMemory.getUint8(${addrExpr()})`;
|
|
5133
|
+
case "memory_read_i16": return `_ballMemory.getInt16(${addrExpr()}, Endian.little)`;
|
|
5134
|
+
case "memory_read_u16": return `_ballMemory.getUint16(${addrExpr()}, Endian.little)`;
|
|
5135
|
+
case "memory_read_i32": return `_ballMemory.getInt32(${addrExpr()}, Endian.little)`;
|
|
5136
|
+
case "memory_read_u32": return `_ballMemory.getUint32(${addrExpr()}, Endian.little)`;
|
|
5137
|
+
case "memory_read_i64": return `_ballMemory.getInt64(${addrExpr()}, Endian.little)`;
|
|
5138
|
+
case "memory_read_u64": return `_ballMemory.getUint64(${addrExpr()}, Endian.little)`;
|
|
5139
|
+
case "memory_read_f32": return `_ballMemory.getFloat32(${addrExpr()}, Endian.little)`;
|
|
5140
|
+
case "memory_read_f64": return `_ballMemory.getFloat64(${addrExpr()}, Endian.little)`;
|
|
5141
|
+
// ── Typed writes (little-endian) ──
|
|
5142
|
+
case "memory_write_i8": return `_ballMemory.setInt8(${addrExpr()}, ${valExpr()})`;
|
|
5143
|
+
case "memory_write_u8": return `_ballMemory.setUint8(${addrExpr()}, ${valExpr()})`;
|
|
5144
|
+
case "memory_write_i16": return `_ballMemory.setInt16(${addrExpr()}, ${valExpr()}, Endian.little)`;
|
|
5145
|
+
case "memory_write_u16": return `_ballMemory.setUint16(${addrExpr()}, ${valExpr()}, Endian.little)`;
|
|
5146
|
+
case "memory_write_i32": return `_ballMemory.setInt32(${addrExpr()}, ${valExpr()}, Endian.little)`;
|
|
5147
|
+
case "memory_write_u32": return `_ballMemory.setUint32(${addrExpr()}, ${valExpr()}, Endian.little)`;
|
|
5148
|
+
case "memory_write_i64": return `_ballMemory.setInt64(${addrExpr()}, ${valExprBigInt()}, Endian.little)`;
|
|
5149
|
+
case "memory_write_u64": return `_ballMemory.setUint64(${addrExpr()}, ${valExprBigInt()}, Endian.little)`;
|
|
5150
|
+
case "memory_write_f32": return `_ballMemory.setFloat32(${addrExpr()}, ${valExpr()}, Endian.little)`;
|
|
5151
|
+
case "memory_write_f64": return `_ballMemory.setFloat64(${addrExpr()}, ${valExpr()}, Endian.little)`;
|
|
5152
|
+
// ── Bulk operations ──
|
|
5153
|
+
case "memory_copy": {
|
|
5154
|
+
const dest = f.get("dest"), src = f.get("src"), size = f.get("size");
|
|
5155
|
+
const d = dest ? this.expr(dest) : "0";
|
|
5156
|
+
const s = src ? this.expr(src) : "0";
|
|
5157
|
+
const n = size ? this.expr(size) : "0";
|
|
5158
|
+
return `(() => { for (let __i = 0; __i < ${n}; __i++) _ballMemory.setUint8(${d} + __i, _ballMemory.getUint8(${s} + __i)); })()`;
|
|
5159
|
+
}
|
|
5160
|
+
case "memory_set": {
|
|
5161
|
+
const addr = f.get("address"), val = f.get("value"), size = f.get("size");
|
|
5162
|
+
const a = addr ? this.expr(addr) : "0";
|
|
5163
|
+
const v = val ? this.expr(val) : "0";
|
|
5164
|
+
const n = size ? this.expr(size) : "0";
|
|
5165
|
+
return `(() => { for (let __i = 0; __i < ${n}; __i++) _ballMemory.setUint8(${a} + __i, ${v}); })()`;
|
|
5166
|
+
}
|
|
5167
|
+
case "memory_compare": {
|
|
5168
|
+
const a = f.get("a"), b = f.get("b"), size = f.get("size");
|
|
5169
|
+
const aStr = a ? this.expr(a) : "0";
|
|
5170
|
+
const bStr = b ? this.expr(b) : "0";
|
|
5171
|
+
const n = size ? this.expr(size) : "0";
|
|
5172
|
+
return `(() => { for (let __i = 0; __i < ${n}; __i++) { const __d = _ballMemory.getUint8(${aStr} + __i) - _ballMemory.getUint8(${bStr} + __i); if (__d !== 0) return __d; } return 0; })()`;
|
|
5173
|
+
}
|
|
5174
|
+
// ── Pointer arithmetic ──
|
|
5175
|
+
case "ptr_add": return ptrArith("+");
|
|
5176
|
+
case "ptr_sub": return ptrArith("-");
|
|
5177
|
+
case "ptr_diff": {
|
|
5178
|
+
const a = f.get("address"), b = f.get("offset"), elemSize = f.get("element_size");
|
|
5179
|
+
const aStr = a ? this.expr(a) : "0";
|
|
5180
|
+
const bStr = b ? this.expr(b) : "0";
|
|
5181
|
+
const es = elemSize ? this.expr(elemSize) : "1";
|
|
5182
|
+
return `Math.trunc((${aStr} - ${bStr}) / ${es})`;
|
|
5183
|
+
}
|
|
5184
|
+
// ── Stack frame ──
|
|
5185
|
+
case "stack_alloc": {
|
|
5186
|
+
const size = f.get("size");
|
|
5187
|
+
const sizeStr = size ? this.expr(size) : "0";
|
|
5188
|
+
return `(() => { _ballStackPtr -= ${sizeStr}; return _ballStackPtr; })()`;
|
|
5189
|
+
}
|
|
5190
|
+
case "stack_push_frame": return `_ballStackFrames.push(_ballStackPtr)`;
|
|
5191
|
+
case "stack_pop_frame": return `(_ballStackPtr = _ballStackFrames.pop()!)`;
|
|
5192
|
+
// ── Sizeof ──
|
|
5193
|
+
case "memory_sizeof": {
|
|
5194
|
+
const typeName = f.get("type_name")?.literal?.stringValue ?? "int";
|
|
5195
|
+
switch (typeName) {
|
|
5196
|
+
case "int8": case "uint8": case "char": case "bool": return "1";
|
|
5197
|
+
case "int16": case "uint16": case "short": return "2";
|
|
5198
|
+
case "int32": case "uint32": case "int": case "float": return "4";
|
|
5199
|
+
case "int64": case "uint64": case "long": case "double": case "long long": return "8";
|
|
5200
|
+
case "void": return "1";
|
|
5201
|
+
default: return "8"; // default pointer-size
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
// ── Address-of / deref (should be resolved by normalizer) ──
|
|
5205
|
+
case "address_of": return `(/* address_of: ${valExpr()} */ undefined)`;
|
|
5206
|
+
case "deref": {
|
|
5207
|
+
const ptr = f.get("pointer");
|
|
5208
|
+
const ptrStr = ptr ? this.expr(ptr) : "0";
|
|
5209
|
+
// Default: read as 64-bit int (pointer-sized).
|
|
5210
|
+
return `_ballMemory.getInt64(${ptrStr}, Endian.little)`;
|
|
5211
|
+
}
|
|
5212
|
+
// ── Null pointer ──
|
|
5213
|
+
case "nullptr": return "0";
|
|
5214
|
+
// ── Info ──
|
|
5215
|
+
case "memory_heap_size": return `_ballMemory.lengthInBytes`;
|
|
5216
|
+
case "memory_stack_size": return `(_ballMemory.lengthInBytes - _ballStackPtr)`;
|
|
5217
|
+
default:
|
|
5218
|
+
// Fail loud (repo CLAUDE.md): never emit a bare/undefined identifier
|
|
5219
|
+
// for an unimplemented std_memory function.
|
|
5220
|
+
throw new Error(
|
|
5221
|
+
`TS compiler: std_memory.${call.function} is not implemented (compileMemoryCall)`,
|
|
5222
|
+
);
|
|
5223
|
+
}
|
|
5224
|
+
}
|
|
5225
|
+
|
|
5044
5226
|
private typeRefMetaToString(ref: any): string {
|
|
5045
5227
|
let s: string = ref?.name ?? '';
|
|
5046
5228
|
const args = ref?.type_args;
|