jz 0.5.1 → 0.7.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.
Files changed (86) hide show
  1. package/README.md +315 -318
  2. package/bench/README.md +369 -0
  3. package/bench/bench.svg +102 -0
  4. package/cli.js +104 -30
  5. package/dist/interop.js +1 -0
  6. package/dist/jz.js +6024 -0
  7. package/index.d.ts +126 -0
  8. package/index.js +362 -84
  9. package/interop.js +159 -186
  10. package/jz.svg +5 -0
  11. package/jzify/arguments.js +97 -0
  12. package/jzify/bundler.js +382 -0
  13. package/jzify/classes.js +328 -0
  14. package/jzify/hoist-vars.js +177 -0
  15. package/jzify/index.js +51 -0
  16. package/jzify/names.js +37 -0
  17. package/jzify/switch.js +106 -0
  18. package/jzify/transform.js +349 -0
  19. package/layout.js +184 -0
  20. package/module/array.js +377 -176
  21. package/module/collection.js +639 -145
  22. package/module/console.js +55 -43
  23. package/module/core.js +264 -153
  24. package/module/date.js +15 -3
  25. package/module/function.js +73 -6
  26. package/module/index.js +2 -1
  27. package/module/json.js +226 -61
  28. package/module/math.js +551 -187
  29. package/module/number.js +327 -60
  30. package/module/object.js +474 -184
  31. package/module/regex.js +255 -25
  32. package/module/schema.js +24 -6
  33. package/module/simd.js +117 -0
  34. package/module/string.js +621 -226
  35. package/module/symbol.js +1 -1
  36. package/module/timer.js +9 -14
  37. package/module/typedarray.js +459 -73
  38. package/package.json +58 -14
  39. package/src/abi/index.js +39 -23
  40. package/src/abi/string.js +38 -41
  41. package/src/ast.js +460 -0
  42. package/src/autoload.js +29 -24
  43. package/src/bridge.js +111 -0
  44. package/src/compile/analyze-scans.js +824 -0
  45. package/src/compile/analyze.js +1600 -0
  46. package/src/compile/emit-assign.js +411 -0
  47. package/src/compile/emit.js +3512 -0
  48. package/src/compile/flow-types.js +103 -0
  49. package/src/{compile.js → compile/index.js} +778 -128
  50. package/src/{infer.js → compile/infer.js} +62 -98
  51. package/src/compile/loop-divmod.js +155 -0
  52. package/src/{narrow.js → compile/narrow.js} +409 -106
  53. package/src/compile/peel-stencil.js +261 -0
  54. package/src/compile/plan/advise.js +370 -0
  55. package/src/compile/plan/common.js +150 -0
  56. package/src/compile/plan/index.js +122 -0
  57. package/src/compile/plan/inline.js +682 -0
  58. package/src/compile/plan/literals.js +1199 -0
  59. package/src/compile/plan/loops.js +472 -0
  60. package/src/compile/plan/scope.js +649 -0
  61. package/src/compile/program-facts.js +423 -0
  62. package/src/ctx.js +220 -64
  63. package/src/ir.js +589 -172
  64. package/src/kind-traits.js +132 -0
  65. package/src/kind.js +524 -0
  66. package/src/op-policy.js +57 -0
  67. package/src/{optimize.js → optimize/index.js} +1432 -473
  68. package/src/optimize/vectorize.js +4660 -0
  69. package/src/param-reps.js +65 -0
  70. package/src/parse.js +44 -0
  71. package/src/{prepare.js → prepare/index.js} +649 -205
  72. package/src/prepare/lift-iife.js +149 -0
  73. package/src/reps.js +116 -0
  74. package/src/resolve.js +12 -3
  75. package/src/static.js +208 -0
  76. package/src/type.js +651 -0
  77. package/src/{assemble.js → wat/assemble.js} +275 -55
  78. package/src/wat/optimize.js +3938 -0
  79. package/transform.js +21 -0
  80. package/wasi.js +47 -5
  81. package/src/analyze.js +0 -3818
  82. package/src/emit.js +0 -3040
  83. package/src/jzify.js +0 -1580
  84. package/src/plan.js +0 -2132
  85. package/src/vectorize.js +0 -1088
  86. /package/src/{codegen.js → wat/codegen.js} +0 -0
@@ -0,0 +1,97 @@
1
+ /**
2
+ * `arguments` object + destructuring-param lowering for function→arrow conversion.
3
+ * @module jzify/arguments
4
+ */
5
+
6
+ import { paramList } from '../src/ast.js'
7
+ import { isDestructurePat, prependDecls } from './hoist-vars.js'
8
+
9
+ function usesArguments(node) {
10
+ if (node === 'arguments') return true
11
+ if (!Array.isArray(node)) return false
12
+ if (node[0] === 'function') return false
13
+ // Literal node `[, value]` (op === null) — node[1] is a string/number VALUE, not an
14
+ // identifier. A string literal `'arguments'` must not read as the arguments object.
15
+ if (node[0] == null) return false
16
+ if (node[0] === '.' || node[0] === '?.') return usesArguments(node[1])
17
+ if (node[0] === ':') return usesArguments(node[2])
18
+ for (let i = 1; i < node.length; i++) if (usesArguments(node[i])) return true
19
+ return false
20
+ }
21
+
22
+ function bindsArguments(body) {
23
+ const isArgDecl = s => Array.isArray(s) && (s[0] === 'var' || s[0] === 'let' || s[0] === 'const') &&
24
+ s.slice(1).some(d => d === 'arguments' || (Array.isArray(d) && d[0] === '=' && d[1] === 'arguments'))
25
+ let n = body
26
+ if (Array.isArray(n) && n[0] === '{}') n = n[1]
27
+ if (Array.isArray(n) && n[0] === ';') return n.slice(1).some(isArgDecl)
28
+ return isArgDecl(n)
29
+ }
30
+
31
+ function renameArguments(node, to) {
32
+ if (node === 'arguments') return to
33
+ if (!Array.isArray(node)) return node
34
+ if (node[0] === 'function') return node
35
+ // Literal node `[, value]` — node[1] is a value, not an identifier; leave untouched
36
+ // so a string literal `'arguments'` survives the rename.
37
+ if (node[0] == null) return node
38
+ if (node[0] === '.' || node[0] === '?.')
39
+ return [node[0], renameArguments(node[1], to), node[2]]
40
+ if (node[0] === ':')
41
+ return [node[0], node[1], renameArguments(node[2], to)]
42
+ return node.map(n => renameArguments(n, to))
43
+ }
44
+
45
+ function prependParamDecls(decl, body) {
46
+ if (Array.isArray(body) && body[0] === '{}') {
47
+ const inner = body[1]
48
+ if (Array.isArray(inner) && inner[0] === ';') return ['{}', [';', decl, ...inner.slice(1)]]
49
+ if (inner == null) return ['{}', decl]
50
+ return ['{}', [';', decl, inner]]
51
+ }
52
+ if (Array.isArray(body) && (body[0] === ';' || body[0] === 'return')) return [';', decl, body]
53
+ return ['{}', [';', decl, ['return', body]]]
54
+ }
55
+
56
+ /** @param {ReturnType<import('./names.js').createNames>} names */
57
+ export function createArgumentsLowering(names) {
58
+ function lowerArguments(params, body) {
59
+ if (bindsArguments(body)) body = renameArguments(body, names.arg())
60
+ const paramsNeedLowering = paramList(params).some(isDestructurePat)
61
+ const usesArgsObj = usesArguments(params) || usesArguments(body)
62
+ if (!paramsNeedLowering && !usesArgsObj) return [params, body]
63
+ const name = names.arg()
64
+ const decls = []
65
+ for (const [idx, param] of paramList(params).entries()) {
66
+ if (Array.isArray(param) && param[0] === '...') {
67
+ decls.push(['=', param[1], ['()', ['.', name, 'slice'], [null, idx]]])
68
+ continue
69
+ }
70
+ if (Array.isArray(param) && param[0] === '=') {
71
+ decls.push(['=', param[1], ['??', ['[]', name, [null, idx]], renameArguments(param[2], name)]])
72
+ continue
73
+ }
74
+ decls.push(['=', param, ['[]', name, [null, idx]]])
75
+ }
76
+ const renamed = usesArgsObj ? renameArguments(body, name) : body
77
+ return [['()', ['...', name]], decls.length ? prependParamDecls(['let', ...decls], renamed) : renamed]
78
+ }
79
+
80
+ let transformRef = null
81
+ const transformPattern = (node) => {
82
+ const transform = transformRef
83
+ if (node == null || !Array.isArray(node)) return node
84
+ const op = node[0]
85
+ if (op === '=') return ['=', transformPattern(node[1]), transform(node[2])]
86
+ if (op === ':') return [':', transform(node[1]), transformPattern(node[2])]
87
+ if (op === '...') return ['...', transformPattern(node[1])]
88
+ if (op === '[]' || op === '{}' || op === ',') return [op, ...node.slice(1).map(transformPattern)]
89
+ return transform(node)
90
+ }
91
+
92
+ return {
93
+ lowerArguments,
94
+ transformPattern,
95
+ bindTransform(fn) { transformRef = fn },
96
+ }
97
+ }
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Bundler/export helper folds and object-literal idiom canonicalization.
3
+ * @module jzify/bundler
4
+ */
5
+
6
+ import {
7
+ handlerArgs, JZ_BLOCK_OPS, bindingOf, cloneNode, nodeEqual, descriptorProps,
8
+ literalString, collectBareRefs, moduleStmts, someDeep, isZeroLiteral, paramList,
9
+ } from '../src/ast.js'
10
+
11
+ export function foldStaticExportHelpers(ast) {
12
+ const body = moduleStmts(ast)
13
+ if (!body) return ast
14
+
15
+ const defPropAliases = new Set()
16
+ for (const stmt of body) {
17
+ const b = bindingOf(stmt)
18
+ if (b && isObjectDefineProperty(b[1])) defPropAliases.add(b[0])
19
+ }
20
+ if (!defPropAliases.size) return ast
21
+
22
+ const helperNames = new Set()
23
+ for (const stmt of body) {
24
+ const b = bindingOf(stmt)
25
+ if (b && Array.isArray(b[1]) && b[1][0] === '=>' && containsDefinePropertyCall(b[1], defPropAliases))
26
+ helperNames.add(b[0])
27
+ }
28
+ if (!helperNames.size) return ast
29
+
30
+ const rewrites = new Map()
31
+ const removable = new Set()
32
+ for (const stmt of body) {
33
+ const ex = staticExportCall(stmt, helperNames)
34
+ if (!ex) continue
35
+ for (const [key, value] of ex.props) rewrites.set(`${ex.target}.${key}`, value)
36
+ removable.add(stmt)
37
+ }
38
+ if (!rewrites.size) return ast
39
+
40
+ const rewritten = body
41
+ .filter(stmt => !removable.has(stmt) && !isDefPropAliasAssign(stmt, defPropAliases) && !isExportHelperAssign(stmt, helperNames))
42
+ .map(stmt => replaceStaticExportReads(stmt, rewrites))
43
+ return rewritten.length === 0 ? null : rewritten.length === 1 ? rewritten[0] : [';', ...rewritten]
44
+ }
45
+
46
+ // Esbuild's CommonJS/ESM interop helpers alias Object reflection built-ins into
47
+ // locals (`var __create = Object.create`, `var __getOwnPropNames =
48
+ // Object.getOwnPropertyNames`, ...). jz deliberately does not expose those
49
+ // built-ins as first-class function values, but the helpers are static enough to
50
+ // lower back to the supported direct calls and module reads.
51
+ export function foldStaticBundlerHelpers(ast) {
52
+ const body = moduleStmts(ast)
53
+ if (!body) return ast
54
+ const binds = body.map(bindingOf) // [name, init] | null, index-aligned with body
55
+
56
+ // Local aliases of Object reflection built-ins: name -> canonical built-in.
57
+ // esbuild's interop preamble always emits these (`var __defProp =
58
+ // Object.defineProperty`, ...); their absence proves the input is not a
59
+ // bundle, so the fold stays a strict no-op rather than guessing.
60
+ const aliases = new Map()
61
+ for (const b of binds) {
62
+ const key = b && objectBuiltinKey(b[1])
63
+ if (key) aliases.set(b[0], key)
64
+ }
65
+ if (!aliases.size) return ast
66
+
67
+ // __copyProps: an arrow driving both aliased getOwnPropertyNames + defineProperty.
68
+ const copyHelpers = new Set()
69
+ for (const b of binds)
70
+ if (b && isArrow(b[1]) &&
71
+ containsCall(b[1], c => aliases.get(c) === 'Object.getOwnPropertyNames') &&
72
+ containsCall(b[1], c => aliases.get(c) === 'Object.defineProperty'))
73
+ copyHelpers.add(b[0])
74
+
75
+ // __toESM: an arrow cloning a module behind a prototype, tagging default/__esModule.
76
+ const interopHelpers = new Set()
77
+ for (const b of binds)
78
+ if (b && isArrow(b[1]) &&
79
+ containsCall(b[1], c => aliases.get(c) === 'Object.create') &&
80
+ containsCall(b[1], c => aliases.get(c) === 'Object.getPrototypeOf') &&
81
+ containsCall(b[1], c => copyHelpers.has(c)) &&
82
+ someDeep(b[1], n => n === 'default') && someDeep(b[1], n => n === '__esModule'))
83
+ interopHelpers.add(b[0])
84
+
85
+ // Bindings produced by an interop-helper call: name -> wrapped module expression.
86
+ const interopBindings = new Map()
87
+ for (const b of binds)
88
+ if (b && Array.isArray(b[1]) && b[1][0] === '()' && interopHelpers.has(b[1][1])) {
89
+ const args = handlerArgs(b[1].slice(2))
90
+ if (args.length) interopBindings.set(b[0], args[0])
91
+ }
92
+
93
+ let out = body.map(stmt => rewriteBundlerAliases(stmt, aliases, interopBindings))
94
+ if (interopBindings.size) out = out.map(stmt => replaceInteropReads(stmt, interopBindings))
95
+ out = out.map(stmt => rewriteBundlerAliases(stmt, aliases, interopBindings)).filter(s => s != null)
96
+
97
+ // Drop synthetic alias/helper bindings nothing references after rewriting.
98
+ const synthetic = n => aliases.has(n) || copyHelpers.has(n) || interopHelpers.has(n) || interopBindings.has(n)
99
+ const live = new Set()
100
+ for (const stmt of out) {
101
+ const b = bindingOf(stmt)
102
+ if (!(b && synthetic(b[0]))) collectBareRefs(stmt, live)
103
+ }
104
+ out = out.filter(stmt => {
105
+ const b = bindingOf(stmt)
106
+ return !(b && synthetic(b[0]) && !live.has(b[0]))
107
+ })
108
+
109
+ return out.length === 0 ? null : out.length === 1 ? out[0] : [';', ...out]
110
+ }
111
+
112
+ const isArrow = node => Array.isArray(node) && node[0] === '=>'
113
+
114
+ const OBJECT_BUILTINS = new Set(['create', 'getPrototypeOf', 'getOwnPropertyNames', 'getOwnPropertyDescriptor', 'defineProperty'])
115
+
116
+ // Canonical name of the Object reflection built-in `node` references, or null.
117
+ function objectBuiltinKey(node) {
118
+ if (!Array.isArray(node) || node[0] !== '.') return null
119
+ if (node[1] === 'Object' && OBJECT_BUILTINS.has(node[2])) return 'Object.' + node[2]
120
+ return isObjectHasOwnPropertyRef(node) ? 'Object.prototype.hasOwnProperty' : null
121
+ }
122
+
123
+ // Does `node` contain a `()` call whose string callee satisfies `ok`?
124
+ const containsCall = (node, ok) =>
125
+ someDeep(node, n => Array.isArray(n) && n[0] === '()' && typeof n[1] === 'string' && ok(n[1]))
126
+
127
+ function rewriteBundlerAliases(node, aliases, interopBindings) {
128
+ if (!Array.isArray(node)) return node
129
+ const rec = n => rewriteBundlerAliases(n, aliases, interopBindings)
130
+
131
+ if (node[0] === ';') {
132
+ const out = [';']
133
+ for (let i = 1; i < node.length; i++) {
134
+ const child = rec(node[i])
135
+ if (child != null) out.push(child)
136
+ }
137
+ return out.length === 1 ? null : out.length === 2 ? out[1] : out
138
+ }
139
+ if (node[0] === '{}' && node.length === 2) {
140
+ const wasBlock = Array.isArray(node[1]) && JZ_BLOCK_OPS.has(node[1][0])
141
+ const inner = rec(node[1])
142
+ if (!wasBlock || inner == null) return ['{}', inner]
143
+ const stayed = Array.isArray(inner) && JZ_BLOCK_OPS.has(inner[0])
144
+ return ['{}', stayed ? inner : [';', inner]]
145
+ }
146
+
147
+ if (node[0] === '()') {
148
+ const callee = node[1]
149
+ const args = handlerArgs(node.slice(2))
150
+
151
+ if (typeof callee === 'string') {
152
+ const key = aliases.get(callee)
153
+ if (key === 'Object.defineProperty') {
154
+ const define = staticDefineProperty(args)
155
+ if (define !== undefined) return define
156
+ }
157
+ if (key === 'Object.getOwnPropertyNames' || key === 'Object.create') {
158
+ if (key === 'Object.create' && isGetPrototypeOfCall(args[0], aliases)) return ['{}', null]
159
+ return ['()', key, ...args.map(rec)]
160
+ }
161
+ }
162
+ // `__hasOwnProp.call(o, k)` -> `o.hasOwnProperty(k)`.
163
+ if (Array.isArray(callee) && callee[0] === '.' && callee[2] === 'call' && args.length >= 2 &&
164
+ typeof callee[1] === 'string' && aliases.get(callee[1]) === 'Object.prototype.hasOwnProperty')
165
+ return ['()', ['.', rec(args[0]), 'hasOwnProperty'], rec(args[1])]
166
+ // `(0, fn)(...)` comma-call resolving to an interop module read.
167
+ const seqCall = commaZeroCall(callee, interopBindings)
168
+ if (seqCall) return ['()', seqCall, ...args.map(rec)]
169
+ }
170
+
171
+ if (node[0] === '.' || node[0] === '?.') return [node[0], rec(node[1]), node[2]]
172
+ if (node[0] === ':') return [node[0], node[1], rec(node[2])]
173
+ return node.map((part, i) => i === 0 ? part : rec(part))
174
+ }
175
+
176
+ function replaceInteropReads(node, bindings) {
177
+ if (typeof node === 'string' && bindings.has(node)) return cloneNode(bindings.get(node))
178
+ if (!Array.isArray(node)) return node
179
+ if (node[0] === '=' && typeof node[1] === 'string') return ['=', node[1], replaceInteropReads(node[2], bindings)]
180
+ if (node[0] === 'let' || node[0] === 'const' || node[0] === 'var')
181
+ return [node[0], ...node.slice(1).map(decl =>
182
+ Array.isArray(decl) && decl[0] === '=' ? ['=', decl[1], replaceInteropReads(decl[2], bindings)] : decl)]
183
+ if ((node[0] === '.' || node[0] === '?.') && typeof node[1] === 'string' && typeof node[2] === 'string' && bindings.has(node[1])) {
184
+ const mod = cloneNode(bindings.get(node[1]))
185
+ return node[2] === 'default' ? mod : [node[0], mod, node[2]]
186
+ }
187
+ if (node[0] === ':') return [node[0], node[1], replaceInteropReads(node[2], bindings)]
188
+ return node.map((part, i) => i === 0 ? part : replaceInteropReads(part, bindings))
189
+ }
190
+
191
+ function isGetPrototypeOfCall(node, aliases) {
192
+ if (!Array.isArray(node) || node[0] !== '()') return false
193
+ const callee = node[1]
194
+ return (typeof callee === 'string' && aliases.get(callee) === 'Object.getPrototypeOf') ||
195
+ objectBuiltinKey(callee) === 'Object.getPrototypeOf'
196
+ }
197
+
198
+ function commaZeroCall(callee, bindings) {
199
+ if (!Array.isArray(callee) || callee[0] !== '()' || !Array.isArray(callee[1]) || callee[1][0] !== ',') return null
200
+ const parts = callee[1].slice(1)
201
+ if (parts.length !== 2 || !isZeroLiteral(parts[0])) return null
202
+ const fn = replaceInteropReads(parts[1], bindings)
203
+ return fn === parts[1] ? null : fn
204
+ }
205
+
206
+ // `defProp(obj, "key", descriptor)` -> `obj.key = value`; null drops `__esModule`.
207
+ function staticDefineProperty(args) {
208
+ if (args.length < 3) return undefined
209
+ const [obj, keyExpr, desc] = args
210
+ const key = literalString(keyExpr)
211
+ const props = descriptorProps(desc)
212
+ if (typeof key !== 'string' || !props) return undefined
213
+ if (key === '__esModule') return null
214
+ const prop = name => props.find(p => Array.isArray(p) && p[0] === ':' && p[1] === name)?.[2]
215
+ const value = prop('value')
216
+ if (value !== undefined) return ['=', ['.', obj, key], value]
217
+ const got = getterReturnExpr(prop('get'))
218
+ return got !== null ? ['=', ['.', obj, key], got] : undefined
219
+ }
220
+
221
+ function isObjectDefineProperty(node) {
222
+ return Array.isArray(node) && node[0] === '.' && node[1] === 'Object' && node[2] === 'defineProperty'
223
+ }
224
+
225
+ /** Unwrap an esbuild module binding to `[name, init]`. After hoistVars, a binding
226
+ * reaches this pass either split into a bare `['=', name, init]` (RHS hoisted out
227
+ * as a separate `let name;`) or kept as a single `['let', ['=', name, init]]` decl
228
+ * (arrow RHS — see the `;` handler in hoistVars). The fold keys on name/init,
229
+ * so it must see through both shapes. */
230
+ function isDefPropAliasAssign(stmt, aliases) {
231
+ const b = bindingOf(stmt)
232
+ return b != null && aliases.has(b[0]) && isObjectDefineProperty(b[1])
233
+ }
234
+
235
+ function isExportHelperAssign(stmt, helpers) {
236
+ const b = bindingOf(stmt)
237
+ return b != null && helpers.has(b[0])
238
+ }
239
+
240
+ function containsDefinePropertyCall(node, aliases) {
241
+ if (!Array.isArray(node)) return false
242
+ if (node[0] === '()' && (aliases.has(node[1]) || isObjectDefineProperty(node[1]))) return true
243
+ for (let i = 1; i < node.length; i++) if (containsDefinePropertyCall(node[i], aliases)) return true
244
+ return false
245
+ }
246
+
247
+ function staticExportCall(stmt, helpers) {
248
+ if (!Array.isArray(stmt) || stmt[0] !== '()' || !helpers.has(stmt[1])) return null
249
+ const args = handlerArgs(stmt.slice(2))
250
+ if (args.length !== 2 || typeof args[0] !== 'string') return null
251
+ const props = descriptorProps(args[1])
252
+ if (!props) return null
253
+ const out = []
254
+ for (const prop of props) {
255
+ if (!Array.isArray(prop) || prop[0] !== ':' || typeof prop[1] !== 'string') return null
256
+ const value = getterReturnExpr(prop[2])
257
+ if (!value) return null
258
+ out.push([prop[1], value])
259
+ }
260
+ return { target: args[0], props: out }
261
+ }
262
+
263
+ function getterReturnExpr(node) {
264
+ if (!Array.isArray(node) || node[0] !== '=>') return null
265
+ const params = paramList(node[1])
266
+ if (params.length !== 0) return null
267
+ const body = node[2]
268
+ if (Array.isArray(body) && body[0] === '{}' && Array.isArray(body[1]) && body[1][0] === 'return') return body[1][1]
269
+ if (Array.isArray(body) && body[0] === '{}' && Array.isArray(body[1]) && body[1][0] === ';' &&
270
+ Array.isArray(body[1][1]) && body[1][1][0] === 'return') return body[1][1][1]
271
+ if (Array.isArray(body) && body[0] === 'return') return body[1]
272
+ return body
273
+ }
274
+
275
+ function replaceStaticExportReads(node, rewrites) {
276
+ if (node == null || typeof node !== 'object' || !Array.isArray(node)) return node
277
+ if ((node[0] === '.' || node[0] === '?.') && typeof node[1] === 'string' && typeof node[2] === 'string') {
278
+ const value = rewrites.get(`${node[1]}.${node[2]}`)
279
+ if (value) return cloneNode(value)
280
+ }
281
+ if (node[0] === ':') return [node[0], node[1], replaceStaticExportReads(node[2], rewrites)]
282
+ return node.map((part, i) => i === 0 ? part : replaceStaticExportReads(part, rewrites))
283
+ }
284
+
285
+ export function canonicalizeObjectIdioms(node) {
286
+ if (node == null || typeof node !== 'object' || !Array.isArray(node)) return node
287
+
288
+ const out = node.map((part, i) => i === 0 ? part : canonicalizeObjectIdioms(part))
289
+
290
+ const toStringCall = objectPrototypeToStringCall(out)
291
+ if (toStringCall) return ['()', '__object_toString', toStringCall.obj]
292
+
293
+ const hasOwnCall = objectHasOwnPropertyCall(out)
294
+ if (hasOwnCall) return ['()', ['.', hasOwnCall.obj, 'hasOwnProperty'], hasOwnCall.key]
295
+
296
+ const mapString = arrayMapStringCallback(out)
297
+ if (mapString) return mapString
298
+
299
+ if (out[0] === '&&') {
300
+ const leftCtor = constructorIsObject(out[1])
301
+ const rightKeys = objectKeysLengthZero(out[2])
302
+ if (leftCtor && rightKeys && nodeEqual(leftCtor.obj, rightKeys.obj)) return out[2]
303
+
304
+ const leftKeys = objectKeysLengthZero(out[1])
305
+ const rightCtor = constructorIsObject(out[2])
306
+ if (leftKeys && rightCtor && nodeEqual(leftKeys.obj, rightCtor.obj)) return out[1]
307
+ }
308
+
309
+ return out
310
+ }
311
+
312
+ function arrayMapStringCallback(node) {
313
+ if (!Array.isArray(node) || node[0] !== '()') return null
314
+ const callee = node[1]
315
+ if (!Array.isArray(callee) || callee[0] !== '.' || callee[2] !== 'map') return null
316
+ const args = handlerArgs(node.slice(2))
317
+ if (args.length !== 1 || args[0] !== 'String') return null
318
+ return ['()', callee, ['=>', 'value', ['()', 'String', 'value']]]
319
+ }
320
+
321
+ function objectHasOwnPropertyCall(node) {
322
+ if (!Array.isArray(node) || node[0] !== '()') return null
323
+ const callee = node[1]
324
+ if (!Array.isArray(callee) || callee[0] !== '.' || callee[2] !== 'call') return null
325
+ if (!isObjectHasOwnPropertyRef(callee[1])) return null
326
+ const args = handlerArgs(node.slice(2))
327
+ if (args.length < 2) return null
328
+ return { obj: args[0], key: args[1] }
329
+ }
330
+
331
+ function objectPrototypeToStringCall(node) {
332
+ if (!Array.isArray(node) || node[0] !== '()') return null
333
+ const callee = node[1]
334
+ if (!Array.isArray(callee) || callee[0] !== '.' || callee[2] !== 'call') return null
335
+ if (!isObjectPrototypeToStringRef(callee[1])) return null
336
+ const args = handlerArgs(node.slice(2))
337
+ if (args.length < 1) return null
338
+ return { obj: args[0] }
339
+ }
340
+
341
+ function isObjectHasOwnPropertyRef(node) {
342
+ if (!Array.isArray(node) || node[0] !== '.' || node[2] !== 'hasOwnProperty') return false
343
+ if (node[1] === 'Object') return true
344
+ return Array.isArray(node[1]) && node[1][0] === '.' && node[1][1] === 'Object' && node[1][2] === 'prototype'
345
+ }
346
+
347
+ function isObjectPrototypeToStringRef(node) {
348
+ return Array.isArray(node) && node[0] === '.' && node[2] === 'toString' &&
349
+ Array.isArray(node[1]) && node[1][0] === '.' && node[1][1] === 'Object' && node[1][2] === 'prototype'
350
+ }
351
+
352
+ function constructorIsObject(node) {
353
+ if (!Array.isArray(node) || (node[0] !== '===' && node[0] !== '==')) return null
354
+ const left = constructorReceiver(node[1])
355
+ if (left && node[2] === 'Object') return { obj: left }
356
+ const right = constructorReceiver(node[2])
357
+ if (right && node[1] === 'Object') return { obj: right }
358
+ return null
359
+ }
360
+
361
+ function constructorReceiver(node) {
362
+ return Array.isArray(node) && node[0] === '.' && node[2] === 'constructor' ? node[1] : null
363
+ }
364
+
365
+ function objectKeysLengthZero(node) {
366
+ if (!Array.isArray(node) || (node[0] !== '===' && node[0] !== '==')) return null
367
+ const left = objectKeysLengthReceiver(node[1])
368
+ if (left && isZeroLiteral(node[2])) return { obj: left }
369
+ const right = objectKeysLengthReceiver(node[2])
370
+ if (right && isZeroLiteral(node[1])) return { obj: right }
371
+ return null
372
+ }
373
+
374
+ function objectKeysLengthReceiver(node) {
375
+ if (!Array.isArray(node) || node[0] !== '.' || node[2] !== 'length') return null
376
+ const call = node[1]
377
+ if (!Array.isArray(call) || call[0] !== '()') return null
378
+ const callee = call[1]
379
+ if (!Array.isArray(callee) || callee[0] !== '.' || callee[1] !== 'Object' || callee[2] !== 'keys') return null
380
+ const args = handlerArgs(call.slice(2))
381
+ return args.length === 1 ? args[0] : null
382
+ }