@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ball-lang/compiler",
3
- "version": "1.3.9",
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) continue;
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;