jz 0.0.0 → 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.
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Object module — literals and property access.
3
+ *
4
+ * Type=6 (OBJECT): schemaId in aux, properties as sequential f64 in memory.
5
+ * Schema = compile-time known property names. Access by index via ptr module.
6
+ *
7
+ * @module object
8
+ */
9
+
10
+ import { emit, typed, asF64, valTypeOf, lookupValType, VAL, temp, tempI32, allocPtr, needsDynShadow, mkPtrIR, extractF64Bits, appendStaticSlots, slotAddr, repOf, updateRep } from '../src/compile.js'
11
+ import { ctx, err, inc, PTR } from '../src/ctx.js'
12
+
13
+
14
+ export default (ctx) => {
15
+ inc('__mkptr', '__alloc', '__alloc_hdr', '__ptr_offset', '__len', '__ptr_type')
16
+
17
+ // Object literal: {x: 1, y: 2} → allocate, fill, return pointer with schemaId
18
+ ctx.core.emit['{}'] = (...rawProps) => {
19
+ if (rawProps.length === 0)
20
+ return mkPtrIR(PTR.OBJECT, 0, ['call', '$__alloc', ['i32.const', 8]])
21
+
22
+ // Flatten comma-grouped props: [',', p1, p2] → [p1, p2]
23
+ const props = rawProps.length === 1 && Array.isArray(rawProps[0]) && rawProps[0][0] === ','
24
+ ? rawProps[0].slice(1) : rawProps
25
+
26
+ // Object spread: {...a, x: 1, ...b} — merge schemas, copy props from sources
27
+ const hasSpreads = props.some(p => Array.isArray(p) && p[0] === '...')
28
+ if (hasSpreads) return emitObjectSpread(props)
29
+
30
+ const names = [], values = []
31
+ for (const p of props) {
32
+ if (Array.isArray(p) && p[0] === ':') { names.push(p[1]); values.push(p[2]) }
33
+ }
34
+
35
+ // Use variable's merged schema if available (from Object.assign inference), else register literal schema
36
+ let schemaId = ctx.schema.register(names)
37
+ const target = ctx.schema.targetStack.at(-1)
38
+ if (target) {
39
+ const merged = ctx.schema.resolve(target)
40
+ if (merged) schemaId = ctx.schema.idOf(target)
41
+ }
42
+ const schema = ctx.schema.list[schemaId]
43
+ const t = tempI32('obj')
44
+ const ptr = temp('objp')
45
+
46
+ // R: Static data segment for objects of pure-literal property values (own-memory only).
47
+ // Even with shadow needed, we can skip alloc + N stores; just feed literal values to __dyn_set.
48
+ const shadow = needsDynShadow(target)
49
+ if (values.length >= 2 && !ctx.memory.shared) {
50
+ const emitted = values.map(emit)
51
+ // asF64 folds i32.const → f64.const so int-literal values also qualify.
52
+ const slots = emitted.map(v => extractF64Bits(asF64(v)))
53
+ if (slots.every(b => b !== null)) {
54
+ const off = appendStaticSlots(slots)
55
+ const staticPtr = mkPtrIR(PTR.OBJECT, schemaId, off)
56
+ if (!shadow) return staticPtr
57
+ inc('__dyn_set')
58
+ const body = [['local.set', `$${ptr}`, staticPtr]]
59
+ for (let i = 0; i < schema.length; i++)
60
+ body.push(['drop', ['call', '$__dyn_set', ['local.get', `$${ptr}`],
61
+ emit(['str', String(schema[i])]), asF64(emitted[i])]])
62
+ body.push(['local.get', `$${ptr}`])
63
+ return typed(['block', ['result', 'f64'], ...body], 'f64')
64
+ }
65
+ }
66
+
67
+ const body = [
68
+ ['local.set', `$${t}`, ['call', '$__alloc', ['i32.const', schema.length * 8]]],
69
+ ]
70
+ for (let i = 0; i < values.length; i++)
71
+ body.push(['f64.store', slotAddr(t, i), asF64(emit(values[i]))])
72
+ body.push(['local.set', `$${ptr}`, mkPtrIR(PTR.OBJECT, schemaId, ['local.get', `$${t}`])])
73
+ if (shadow) {
74
+ inc('__dyn_set')
75
+ for (let i = 0; i < schema.length; i++)
76
+ body.push(['drop', ['call', '$__dyn_set', ['local.get', `$${ptr}`], emit(['str', String(schema[i])]),
77
+ ['f64.load', slotAddr(t, i)]]])
78
+ }
79
+ body.push(['local.get', `$${ptr}`])
80
+
81
+ return typed(['block', ['result', 'f64'], ...body], 'f64')
82
+ }
83
+
84
+ // === Object static methods ===
85
+
86
+ ctx.core.emit['Object.keys'] = (obj) => {
87
+ const schema = resolveSchema(obj)
88
+ if (!schema) err('Object.keys requires object with known schema')
89
+ return emitStringArray(schema)
90
+ }
91
+
92
+ ctx.core.emit['Object.values'] = (obj) => {
93
+ const schema = resolveSchema(obj)
94
+ if (!schema) err('Object.values requires object with known schema')
95
+ const va = asF64(emit(obj))
96
+ const n = schema.length
97
+ const t = temp('ov'), base = tempI32('vb')
98
+ const out = allocPtr({ type: PTR.ARRAY, len: n, tag: 'oa' })
99
+ const body = [['local.set', `$${t}`, va], out.init,
100
+ ['local.set', `$${base}`, ['call', '$__ptr_offset', ['local.get', `$${t}`]]]]
101
+ for (let i = 0; i < n; i++)
102
+ body.push(['f64.store', slotAddr(out.local, i), ['f64.load', slotAddr(base, i)]])
103
+ body.push(out.ptr)
104
+ return typed(['block', ['result', 'f64'], ...body], 'f64')
105
+ }
106
+
107
+ ctx.core.emit['Object.entries'] = (obj) => {
108
+ const schema = resolveSchema(obj)
109
+ if (!schema) err('Object.entries requires object with known schema')
110
+ const va = asF64(emit(obj))
111
+ const n = schema.length
112
+ const t = temp('oe'), pair = tempI32('op'), base = tempI32('eb')
113
+ const out = allocPtr({ type: PTR.ARRAY, len: n, tag: 'oa' })
114
+ const body = [['local.set', `$${t}`, va], out.init,
115
+ ['local.set', `$${base}`, ['call', '$__ptr_offset', ['local.get', `$${t}`]]]]
116
+ for (let i = 0; i < n; i++) {
117
+ body.push(
118
+ ['local.set', `$${pair}`, ['call', '$__alloc_hdr', ['i32.const', 2], ['i32.const', 2], ['i32.const', 8]]],
119
+ ['f64.store', slotAddr(pair, 0), emit(['str', schema[i]])],
120
+ ['f64.store', slotAddr(pair, 1), ['f64.load', slotAddr(base, i)]],
121
+ ['f64.store', slotAddr(out.local, i), mkPtrIR(PTR.ARRAY, 0, ['local.get', `$${pair}`])])
122
+ }
123
+ body.push(out.ptr)
124
+ return typed(['block', ['result', 'f64'], ...body], 'f64')
125
+ }
126
+
127
+ ctx.core.emit['Object.assign'] = (target, ...sources) => {
128
+ if (typeof target === 'string') {
129
+ const vt = repOf(target)?.val
130
+ if (vt && vt !== VAL.OBJECT) {
131
+ const allProps = []
132
+ for (const src of sources) {
133
+ const s = resolveSchema(src)
134
+ if (!s) err('Object.assign: source needs known schema')
135
+ for (const p of s) if (!allProps.includes(p)) allProps.push(p)
136
+ }
137
+ const boxedSchema = ['__inner__', ...allProps]
138
+ const schemaId = ctx.schema.register(boxedSchema)
139
+ ctx.schema.vars.set(target, schemaId)
140
+ updateRep(target, { schemaId })
141
+ const t = tempI32('bx'), s = temp('bs')
142
+ const body = [
143
+ ['local.set', `$${t}`, ['call', '$__alloc', ['i32.const', boxedSchema.length * 8]]],
144
+ ['f64.store', ['local.get', `$${t}`], asF64(emit(target))],
145
+ ]
146
+ const sBase = tempI32('sb')
147
+ for (const source of sources) {
148
+ const sSchema = resolveSchema(source)
149
+ body.push(['local.set', `$${s}`, asF64(emit(source))])
150
+ body.push(['local.set', `$${sBase}`, ['call', '$__ptr_offset', ['local.get', `$${s}`]]])
151
+ for (let si = 0; si < sSchema.length; si++) {
152
+ const ti = boxedSchema.indexOf(sSchema[si])
153
+ if (ti < 0) continue
154
+ body.push(['f64.store', slotAddr(t, ti), ['f64.load', slotAddr(sBase, si)]])
155
+ }
156
+ }
157
+ body.push(['local.set', `$${target}`,
158
+ mkPtrIR(PTR.OBJECT, schemaId, ['local.get', `$${t}`])])
159
+ body.push(['local.get', `$${target}`])
160
+ return typed(['block', ['result', 'f64'], ...body], 'f64')
161
+ }
162
+ }
163
+ const tSchema = resolveSchema(target)
164
+ if (!tSchema) err('Object.assign: target needs known schema')
165
+ const t = temp('at'), s = temp('as')
166
+ const tBase = tempI32('tb'), sBase2 = tempI32('sb')
167
+ const body = [['local.set', `$${t}`, asF64(emit(target))],
168
+ ['local.set', `$${tBase}`, ['call', '$__ptr_offset', ['local.get', `$${t}`]]]]
169
+ for (const source of sources) {
170
+ const sSchema = resolveSchema(source)
171
+ if (!sSchema) err('Object.assign: source needs known schema')
172
+ body.push(['local.set', `$${s}`, asF64(emit(source))])
173
+ body.push(['local.set', `$${sBase2}`, ['call', '$__ptr_offset', ['local.get', `$${s}`]]])
174
+ for (let si = 0; si < sSchema.length; si++) {
175
+ const ti = tSchema.indexOf(sSchema[si])
176
+ if (ti < 0) continue
177
+ body.push(['f64.store', slotAddr(tBase, ti), ['f64.load', slotAddr(sBase2, si)]])
178
+ }
179
+ }
180
+ body.push(['local.get', `$${t}`])
181
+ return typed(['block', ['result', 'f64'], ...body], 'f64')
182
+ }
183
+
184
+ // Object.fromEntries(arr) → creates HASH from array of [key, value] pairs
185
+ ctx.core.emit['Object.fromEntries'] = (arr) => {
186
+ inc('__hash_new', '__hash_set')
187
+ inc('__str_hash', '__str_eq')
188
+ const va = asF64(emit(arr))
189
+ const t = temp('fe'), ptr = tempI32('fp'), len = tempI32('fl')
190
+ const i = tempI32('fi'), pair = tempI32('fv')
191
+ const id = ctx.func.uniq++
192
+ return typed(['block', ['result', 'f64'],
193
+ ['local.set', `$${t}`, ['call', '$__hash_new']],
194
+ ['local.set', `$${ptr}`, ['call', '$__ptr_offset', va]],
195
+ ['local.set', `$${len}`, ['call', '$__len', va]],
196
+ ['local.set', `$${i}`, ['i32.const', 0]],
197
+ ['block', `$brk${id}`, ['loop', `$loop${id}`,
198
+ ['br_if', `$brk${id}`, ['i32.ge_s', ['local.get', `$${i}`], ['local.get', `$${len}`]]],
199
+ // Load pair (array of 2): pair = ptr_offset(arr[i])
200
+ ['local.set', `$${pair}`, ['call', '$__ptr_offset',
201
+ ['f64.load', ['i32.add', ['local.get', `$${ptr}`], ['i32.shl', ['local.get', `$${i}`], ['i32.const', 3]]]]]],
202
+ // hash_set(result, pair[0], pair[1])
203
+ ['local.set', `$${t}`, ['call', '$__hash_set', ['local.get', `$${t}`],
204
+ ['f64.load', ['local.get', `$${pair}`]],
205
+ ['f64.load', ['i32.add', ['local.get', `$${pair}`], ['i32.const', 8]]]]],
206
+ ['local.set', `$${i}`, ['i32.add', ['local.get', `$${i}`], ['i32.const', 1]]],
207
+ ['br', `$loop${id}`]]],
208
+ ['local.get', `$${t}`]], 'f64')
209
+ }
210
+
211
+ // Object.create(proto) → shallow copy of object (same schema, copied properties)
212
+ ctx.core.emit['Object.create'] = (proto) => {
213
+ const protoType = typeof proto === 'string' ? lookupValType(proto) : valTypeOf(proto)
214
+ if (protoType === VAL.ARRAY) {
215
+ // Clone array data + link named-prop sidecar so for-in/bracket-name lookups
216
+ // keep working after Object.create (watr's ctx.local = Object.create(param) pattern).
217
+ inc('__arr_from', '__dyn_move', '__ptr_offset')
218
+ const src = temp('ocs')
219
+ const dst = temp('ocd')
220
+ return typed(['block', ['result', 'f64'],
221
+ ['local.set', `$${src}`, asF64(emit(proto))],
222
+ ['local.set', `$${dst}`, ['call', '$__arr_from', ['local.get', `$${src}`]]],
223
+ ['call', '$__dyn_move',
224
+ ['call', '$__ptr_offset', ['local.get', `$${src}`]],
225
+ ['call', '$__ptr_offset', ['local.get', `$${dst}`]]],
226
+ ['local.get', `$${dst}`]], 'f64')
227
+ }
228
+ const schema = resolveSchema(proto)
229
+ if (!schema) {
230
+ if (protoType == null) {
231
+ const value = temp('ocr')
232
+ inc('__arr_from', '__dyn_move', '__ptr_offset')
233
+ const dst2 = temp('ocd')
234
+ return typed(['block', ['result', 'f64'],
235
+ ['local.set', `$${value}`, asF64(emit(proto))],
236
+ ['if', ['result', 'f64'],
237
+ ['i32.eq', ['call', '$__ptr_type', ['local.get', `$${value}`]], ['i32.const', PTR.ARRAY]],
238
+ ['then', ['block', ['result', 'f64'],
239
+ ['local.set', `$${dst2}`, ['call', '$__arr_from', ['local.get', `$${value}`]]],
240
+ ['call', '$__dyn_move',
241
+ ['call', '$__ptr_offset', ['local.get', `$${value}`]],
242
+ ['call', '$__ptr_offset', ['local.get', `$${dst2}`]]],
243
+ ['local.get', `$${dst2}`]]],
244
+ ['else', ['local.get', `$${value}`]]]] , 'f64')
245
+ }
246
+ err('Object.create requires object with known schema')
247
+ }
248
+ const n = schema.length
249
+ const schemaId = ctx.schema.register(schema)
250
+ const t = tempI32('oc'), s = temp('os')
251
+ const srcBase = tempI32('cb')
252
+ const body = [
253
+ ['local.set', `$${s}`, asF64(emit(proto))],
254
+ ['local.set', `$${t}`, ['call', '$__alloc', ['i32.const', n * 8]]],
255
+ ['local.set', `$${srcBase}`, ['call', '$__ptr_offset', ['local.get', `$${s}`]]],
256
+ ]
257
+ // Copy all properties from proto
258
+ for (let i = 0; i < n; i++)
259
+ body.push(['f64.store', slotAddr(t, i), ['f64.load', slotAddr(srcBase, i)]])
260
+ body.push(mkPtrIR(PTR.OBJECT, schemaId, ['local.get', `$${t}`]))
261
+ return typed(['block', ['result', 'f64'], ...body], 'f64')
262
+ }
263
+ }
264
+
265
+ // --- Helpers ---
266
+
267
+ function resolveSchema(obj) {
268
+ if (typeof obj === 'string') return ctx.schema.resolve(obj)
269
+ if (Array.isArray(obj) && obj[0] === '{}')
270
+ return obj.slice(1).filter(p => Array.isArray(p) && p[0] === ':').map(p => p[1])
271
+ return null
272
+ }
273
+
274
+ /**
275
+ * Emit object literal with spread: {...a, x: 1, ...b, y: 2}
276
+ * Merges schemas from all sources, allocates result, copies in order.
277
+ */
278
+ function emitObjectSpread(props) {
279
+ // Collect merged schema: union of all spread source schemas + explicit props
280
+ const allNames = []
281
+ const addName = n => { if (!allNames.includes(n)) allNames.push(n) }
282
+ for (const p of props) {
283
+ if (Array.isArray(p) && p[0] === '...') {
284
+ const s = resolveSchema(p[1])
285
+ if (s) for (const n of s) addName(n)
286
+ } else if (Array.isArray(p) && p[0] === ':') addName(p[1])
287
+ }
288
+ // Pragmatic fallback: single spread source with no resolvable schema
289
+ // (e.g. `{ ...opts }` where opts is a parameter). Emit source directly.
290
+ // Alias rather than clone — safe for read-only use; mutation would affect source.
291
+ if (!allNames.length && props.length === 1 && Array.isArray(props[0]) && props[0][0] === '...') {
292
+ return typed(asF64(emit(props[0][1])), 'f64')
293
+ }
294
+ if (!allNames.length) err('Object spread: cannot resolve source schema')
295
+
296
+ const schemaId = ctx.schema.register(allNames)
297
+ const schema = ctx.schema.list[schemaId]
298
+ const t = tempI32('obj')
299
+ const ptr = temp('objp')
300
+ const src = tempI32('osp')
301
+
302
+ const body = [['local.set', `$${t}`, ['call', '$__alloc', ['i32.const', schema.length * 8]]]]
303
+
304
+ // Process props in order — later props override earlier (JS semantics)
305
+ let srcF
306
+ for (const p of props) {
307
+ if (Array.isArray(p) && p[0] === '...') {
308
+ const sSchema = resolveSchema(p[1])
309
+ if (!sSchema) {
310
+ // Unknown-schema source (e.g. parameter). Override each slot via runtime
311
+ // __dyn_get_or using existing value as fallback. Requires collection module.
312
+ if (!ctx.module.modules.collection) err('Object spread: source needs known schema')
313
+ inc('__dyn_get_or')
314
+ srcF ??= temp('ospf')
315
+ body.push(['local.set', `$${srcF}`, asF64(emit(p[1]))])
316
+ for (let i = 0; i < schema.length; i++) {
317
+ const slot = slotAddr(t, i)
318
+ body.push(['f64.store', slot,
319
+ ['call', '$__dyn_get_or', ['local.get', `$${srcF}`],
320
+ emit(['str', String(schema[i])]),
321
+ ['f64.load', slot]]])
322
+ }
323
+ continue
324
+ }
325
+ body.push(['local.set', `$${src}`, ['call', '$__ptr_offset', asF64(emit(p[1]))]])
326
+ for (let si = 0; si < sSchema.length; si++) {
327
+ const ti = schema.indexOf(sSchema[si])
328
+ if (ti < 0) continue
329
+ body.push(['f64.store', slotAddr(t, ti), ['f64.load', slotAddr(src, si)]])
330
+ }
331
+ } else if (Array.isArray(p) && p[0] === ':') {
332
+ const ti = schema.indexOf(p[1])
333
+ if (ti >= 0) body.push(['f64.store', slotAddr(t, ti), asF64(emit(p[2]))])
334
+ }
335
+ }
336
+
337
+ body.push(['local.set', `$${ptr}`, mkPtrIR(PTR.OBJECT, schemaId, ['local.get', `$${t}`])])
338
+ const spreadTarget = ctx.schema.targetStack.at(-1)
339
+ if (needsDynShadow(spreadTarget)) {
340
+ inc('__dyn_set')
341
+ for (let i = 0; i < schema.length; i++)
342
+ body.push(['drop', ['call', '$__dyn_set', ['local.get', `$${ptr}`], emit(['str', String(schema[i])]),
343
+ ['f64.load', slotAddr(t, i)]]])
344
+ }
345
+ body.push(['local.get', `$${ptr}`])
346
+ return typed(['block', ['result', 'f64'], ...body], 'f64')
347
+ }
348
+
349
+ function emitStringArray(names) {
350
+ const n = names.length
351
+ const out = allocPtr({ type: PTR.ARRAY, len: n, tag: 'sa' })
352
+ const body = [out.init]
353
+ for (let i = 0; i < n; i++)
354
+ body.push(['f64.store', slotAddr(out.local, i), emit(['str', names[i]])])
355
+ body.push(out.ptr)
356
+ return typed(['block', ['result', 'f64'], ...body], 'f64')
357
+ }