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