jz 0.4.0 → 0.5.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.
- package/README.md +275 -255
- package/cli.js +8 -51
- package/index.js +166 -31
- package/{src/host.js → interop.js} +291 -88
- package/module/array.js +181 -71
- package/module/collection.js +222 -8
- package/module/console.js +1 -1
- package/module/core.js +251 -118
- package/module/date.js +9 -5
- package/module/function.js +11 -1
- package/module/json.js +361 -55
- package/module/math.js +551 -128
- package/module/number.js +390 -227
- package/module/object.js +252 -34
- package/module/regex.js +123 -16
- package/module/schema.js +15 -1
- package/module/string.js +540 -96
- package/module/timer.js +1 -1
- package/module/typedarray.js +674 -57
- package/package.json +10 -7
- package/src/abi/array.js +89 -0
- package/src/abi/index.js +35 -0
- package/src/abi/number.js +137 -0
- package/src/abi/object.js +57 -0
- package/src/abi/string.js +529 -0
- package/src/analyze.js +1870 -485
- package/src/assemble.js +27 -12
- package/src/autoload.js +13 -1
- package/src/compile.js +446 -179
- package/src/ctx.js +85 -18
- package/src/emit.js +662 -196
- package/src/infer.js +548 -0
- package/src/ir.js +291 -23
- package/src/jzify.js +786 -94
- package/src/narrow.js +433 -251
- package/src/optimize.js +126 -122
- package/src/plan.js +703 -34
- package/src/prepare.js +822 -150
- package/src/resolve.js +85 -0
- package/src/vectorize.js +99 -18
- package/src/ast.js +0 -160
- package/src/auto-config.js +0 -120
- package/src/fuse.js +0 -159
package/module/regex.js
CHANGED
|
@@ -10,14 +10,31 @@
|
|
|
10
10
|
import { typed, asF64, asI64, UNDEF_NAN, mkPtrIR, temp, tempI32 } from '../src/ir.js'
|
|
11
11
|
import { emit } from '../src/emit.js'
|
|
12
12
|
import { err, inc, PTR, LAYOUT } from '../src/ctx.js'
|
|
13
|
+
import { includeModule } from '../src/autoload.js'
|
|
13
14
|
|
|
14
15
|
// Build IR that constructs a match array: [full, cap1, cap2, ...]
|
|
15
16
|
// strLocal, msLocal, meLocal are local names (i32 for ms/me, f64 for str).
|
|
16
17
|
// Captures read from globals $__re_g${i}_start / _end. -1 → undefined.
|
|
17
|
-
const buildMatchArr = (strLocal, msLocal, meLocal, nGroups) => {
|
|
18
|
+
const buildMatchArr = (strLocal, msLocal, meLocal, nGroups, groupNames = []) => {
|
|
18
19
|
const N = nGroups + 1
|
|
19
20
|
inc('__alloc', '__mkptr', '__str_slice')
|
|
20
21
|
const arr = tempI32('mka')
|
|
22
|
+
const arrPtr = temp('mkap')
|
|
23
|
+
const captures = []
|
|
24
|
+
const named = []
|
|
25
|
+
for (let i = 1; i <= nGroups; i++) {
|
|
26
|
+
captures[i] = [tempI32('mkgs'), tempI32('mkge')]
|
|
27
|
+
if (groupNames[i]) named.push([i, groupNames[i]])
|
|
28
|
+
}
|
|
29
|
+
if (named.length) {
|
|
30
|
+
includeModule('collection')
|
|
31
|
+
inc('__hash_new_small', '__hash_set', '__dyn_set')
|
|
32
|
+
}
|
|
33
|
+
const captureValue = i => ['if', ['result', 'f64'],
|
|
34
|
+
['i32.lt_s', ['local.get', `$${captures[i][0]}`], ['i32.const', 0]],
|
|
35
|
+
['then', ['f64.const', `nan:${UNDEF_NAN}`]],
|
|
36
|
+
['else', ['call', '$__str_slice', ['i64.reinterpret_f64', ['local.get', `$${strLocal}`]],
|
|
37
|
+
['local.get', `$${captures[i][0]}`], ['local.get', `$${captures[i][1]}`]]]]
|
|
21
38
|
const stmts = [
|
|
22
39
|
['local.set', `$${arr}`, ['call', '$__alloc', ['i32.const', 8 + N * 8]]],
|
|
23
40
|
['i32.store', ['local.get', `$${arr}`], ['i32.const', N]],
|
|
@@ -26,15 +43,31 @@ const buildMatchArr = (strLocal, msLocal, meLocal, nGroups) => {
|
|
|
26
43
|
['call', '$__str_slice', ['i64.reinterpret_f64', ['local.get', `$${strLocal}`]],
|
|
27
44
|
['local.get', `$${msLocal}`], ['local.get', `$${meLocal}`]]],
|
|
28
45
|
]
|
|
46
|
+
for (let i = 1; i <= nGroups; i++) {
|
|
47
|
+
stmts.push(['local.set', `$${captures[i][0]}`, ['global.get', `$__re_g${i}_start`]])
|
|
48
|
+
stmts.push(['local.set', `$${captures[i][1]}`, ['global.get', `$__re_g${i}_end`]])
|
|
49
|
+
}
|
|
29
50
|
for (let i = 1; i <= nGroups; i++) {
|
|
30
51
|
stmts.push(['f64.store', ['i32.add', ['local.get', `$${arr}`], ['i32.const', 8 + i * 8]],
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
52
|
+
captureValue(i)])
|
|
53
|
+
}
|
|
54
|
+
stmts.push(['local.set', `$${arrPtr}`, mkPtrIR(PTR.ARRAY, 0, ['i32.add', ['local.get', `$${arr}`], ['i32.const', 8]])])
|
|
55
|
+
if (named.length) {
|
|
56
|
+
const groups = temp('mkg')
|
|
57
|
+
stmts.push(['local.set', `$${groups}`, ['call', '$__hash_new_small']])
|
|
58
|
+
for (const [i, name] of named) {
|
|
59
|
+
stmts.push(['local.set', `$${groups}`,
|
|
60
|
+
['f64.reinterpret_i64', ['call', '$__hash_set',
|
|
61
|
+
['i64.reinterpret_f64', ['local.get', `$${groups}`]],
|
|
62
|
+
asI64(emit(['str', name])),
|
|
63
|
+
['i64.reinterpret_f64', captureValue(i)]]]])
|
|
64
|
+
}
|
|
65
|
+
stmts.push(['drop', ['call', '$__dyn_set',
|
|
66
|
+
['i64.reinterpret_f64', ['local.get', `$${arrPtr}`]],
|
|
67
|
+
asI64(emit(['str', 'groups'])),
|
|
68
|
+
['i64.reinterpret_f64', ['local.get', `$${groups}`]]]])
|
|
36
69
|
}
|
|
37
|
-
stmts.push(
|
|
70
|
+
stmts.push(['local.get', `$${arrPtr}`])
|
|
38
71
|
return ['block', ['result', 'f64'], ...stmts]
|
|
39
72
|
}
|
|
40
73
|
|
|
@@ -43,9 +76,9 @@ const buildMatchArr = (strLocal, msLocal, meLocal, nGroups) => {
|
|
|
43
76
|
const PIPE = 124, STAR = 42, PLUS = 43, QUEST = 63, DOT = 46,
|
|
44
77
|
LBRACK = 91, RBRACK = 93, LPAREN = 40, RPAREN = 41,
|
|
45
78
|
LBRACE = 123, RBRACE = 125, CARET = 94, DOLLAR = 36,
|
|
46
|
-
BSLASH = 92, DASH = 45, COLON = 58, EQUAL = 61, EXCL = 33, LT = 60
|
|
79
|
+
BSLASH = 92, DASH = 45, COLON = 58, EQUAL = 61, EXCL = 33, LT = 60, GT = 62
|
|
47
80
|
|
|
48
|
-
let src, idx, groupNum
|
|
81
|
+
let src, idx, groupNum, groupNames
|
|
49
82
|
|
|
50
83
|
const cur = () => src.charCodeAt(idx),
|
|
51
84
|
peek = () => src[idx],
|
|
@@ -55,12 +88,13 @@ const cur = () => src.charCodeAt(idx),
|
|
|
55
88
|
|
|
56
89
|
/** Parse regex pattern → AST */
|
|
57
90
|
export const parseRegex = (pattern, flags = '') => {
|
|
58
|
-
src = pattern; idx = 0; groupNum = 0
|
|
91
|
+
src = pattern; idx = 0; groupNum = 0; groupNames = []
|
|
59
92
|
let ast = parseAlt()
|
|
60
93
|
if (!eof()) perr('Unexpected ' + peek())
|
|
61
94
|
if (typeof ast === 'string') ast = ['seq', ast]
|
|
62
95
|
if (flags) ast.flags = flags
|
|
63
96
|
ast.groups = groupNum
|
|
97
|
+
if (groupNames.length) ast.groupNames = groupNames
|
|
64
98
|
return ast
|
|
65
99
|
}
|
|
66
100
|
|
|
@@ -85,7 +119,7 @@ const parseQuantified = () => {
|
|
|
85
119
|
if (c === STAR) { skip(); node = ['*', node] }
|
|
86
120
|
else if (c === PLUS) { skip(); node = ['+', node] }
|
|
87
121
|
else if (c === QUEST) { skip(); node = ['?', node] }
|
|
88
|
-
else if (c === LBRACE) { node = parseRepeat(node) }
|
|
122
|
+
else if (c === LBRACE && isRepeatStart()) { node = parseRepeat(node) }
|
|
89
123
|
else break
|
|
90
124
|
if (cur() === QUEST) { skip(); node[0] += '?' }
|
|
91
125
|
}
|
|
@@ -100,6 +134,17 @@ const parseRepeat = node => {
|
|
|
100
134
|
return ['{}', node, min, max]
|
|
101
135
|
}
|
|
102
136
|
|
|
137
|
+
const isRepeatStart = () => {
|
|
138
|
+
let i = idx + 1
|
|
139
|
+
if (src.charCodeAt(i) < 48 || src.charCodeAt(i) > 57) return false
|
|
140
|
+
while (src.charCodeAt(i) >= 48 && src.charCodeAt(i) <= 57) i++
|
|
141
|
+
if (src.charCodeAt(i) === RBRACE) return true
|
|
142
|
+
if (src.charCodeAt(i) !== 44) return false
|
|
143
|
+
i++
|
|
144
|
+
while (src.charCodeAt(i) >= 48 && src.charCodeAt(i) <= 57) i++
|
|
145
|
+
return src.charCodeAt(i) === RBRACE
|
|
146
|
+
}
|
|
147
|
+
|
|
103
148
|
const parseNum = () => {
|
|
104
149
|
let n = 0
|
|
105
150
|
while (cur() >= 48 && cur() <= 57) { n = n * 10 + (cur() - 48); skip() }
|
|
@@ -143,6 +188,7 @@ const parseEscape = () => {
|
|
|
143
188
|
skip()
|
|
144
189
|
const c = peek()
|
|
145
190
|
if (c >= '1' && c <= '9') { skip(); return ['\\' + c] }
|
|
191
|
+
if (c === 'k' && src.charCodeAt(idx + 1) === LT) perr('Named backreference unsupported')
|
|
146
192
|
if ('dDwWsS'.includes(c)) { skip(); return ['\\' + c] }
|
|
147
193
|
if (c === 'b' || c === 'B') { skip(); return ['\\' + c] }
|
|
148
194
|
return parseEscapeChar()
|
|
@@ -161,7 +207,7 @@ const parseEscapeChar = () => {
|
|
|
161
207
|
|
|
162
208
|
const parseGroup = () => {
|
|
163
209
|
skip()
|
|
164
|
-
let type = '()', groupId = null
|
|
210
|
+
let type = '()', groupId = null, groupName = null
|
|
165
211
|
if (cur() === QUEST) {
|
|
166
212
|
skip(); const c = cur()
|
|
167
213
|
if (c === COLON) { skip(); type = '(?:)' }
|
|
@@ -171,14 +217,34 @@ const parseGroup = () => {
|
|
|
171
217
|
skip(); const c2 = cur()
|
|
172
218
|
if (c2 === EQUAL) { skip(); type = '(?<=)' }
|
|
173
219
|
else if (c2 === EXCL) { skip(); type = '(?<!)' }
|
|
174
|
-
else
|
|
220
|
+
else { groupName = parseGroupName(); groupId = ++groupNum }
|
|
175
221
|
} else perr('Invalid group syntax')
|
|
176
222
|
} else groupId = ++groupNum
|
|
177
223
|
const inner = parseAlt()
|
|
178
224
|
cur() === RPAREN || perr('Unclosed ('); skip()
|
|
225
|
+
if (groupName) groupNames[groupId] = groupName
|
|
179
226
|
return groupId ? [type, inner, groupId] : [type, inner]
|
|
180
227
|
}
|
|
181
228
|
|
|
229
|
+
const isGroupNameStart = c =>
|
|
230
|
+
(c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 36 || c === 95
|
|
231
|
+
|
|
232
|
+
const isGroupNameContinue = c => isGroupNameStart(c) || (c >= 48 && c <= 57)
|
|
233
|
+
|
|
234
|
+
const parseGroupName = () => {
|
|
235
|
+
const start = idx
|
|
236
|
+
isGroupNameStart(cur()) || perr('Invalid group name')
|
|
237
|
+
skip()
|
|
238
|
+
while (!eof() && cur() !== GT) {
|
|
239
|
+
isGroupNameContinue(cur()) || perr('Invalid group name')
|
|
240
|
+
skip()
|
|
241
|
+
}
|
|
242
|
+
cur() === GT || perr('Unclosed group name')
|
|
243
|
+
const name = src.slice(start, idx)
|
|
244
|
+
skip()
|
|
245
|
+
return name
|
|
246
|
+
}
|
|
247
|
+
|
|
182
248
|
|
|
183
249
|
// === WAT Codegen ===
|
|
184
250
|
|
|
@@ -652,7 +718,7 @@ export default (ctx) => {
|
|
|
652
718
|
__str_to_buf: ['__str_byteLen', '__char_at'],
|
|
653
719
|
})
|
|
654
720
|
|
|
655
|
-
ctx.runtime.regex = { count: 0, vars: new Map(), compiled: new Map(), groups: new Map() }
|
|
721
|
+
ctx.runtime.regex = { count: 0, vars: new Map(), compiled: new Map(), groups: new Map(), groupNames: new Map() }
|
|
656
722
|
|
|
657
723
|
// SSO → heap normalizer: returns data offset (i32) for direct byte access.
|
|
658
724
|
// Heap STRING: aux bit SSO_BIT is 0 → offset already points at bytes.
|
|
@@ -689,6 +755,7 @@ export default (ctx) => {
|
|
|
689
755
|
}
|
|
690
756
|
}
|
|
691
757
|
ctx.runtime.regex.groups.set(id, ast.groups || 0)
|
|
758
|
+
ctx.runtime.regex.groupNames.set(id, ast.groupNames || [])
|
|
692
759
|
ctx.core.stdlib[funcName] = compileRegex(ast, funcName)
|
|
693
760
|
|
|
694
761
|
// Search wrapper: tries match at each position, returns (match_start, match_end) via locals
|
|
@@ -749,6 +816,7 @@ export default (ctx) => {
|
|
|
749
816
|
const id = resolveRegex(obj)
|
|
750
817
|
if (id == null) err('regex.exec requires a known regex')
|
|
751
818
|
const nGroups = ctx.runtime.regex.groups.get(id) || 0
|
|
819
|
+
const groupNames = ctx.runtime.regex.groupNames.get(id) || []
|
|
752
820
|
const s = temp('re'), ms = tempI32('rems'), me = tempI32('reme')
|
|
753
821
|
return typed(['block', ['result', 'f64'],
|
|
754
822
|
['local.set', `$${s}`, asF64(emit(str))],
|
|
@@ -756,9 +824,47 @@ export default (ctx) => {
|
|
|
756
824
|
['call', `$__regex_search_${id}`, ['i64.reinterpret_f64', ['local.get', `$${s}`]]]]],
|
|
757
825
|
['if', ['result', 'f64'], ['i32.lt_s', ['local.get', `$${ms}`], ['i32.const', 0]],
|
|
758
826
|
['then', ['f64.const', 0]],
|
|
759
|
-
['else', buildMatchArr(s, ms, me, nGroups)]]], 'f64')
|
|
827
|
+
['else', buildMatchArr(s, ms, me, nGroups, groupNames)]]], 'f64')
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// === Regex instance properties ===
|
|
831
|
+
// A regex value is a compile-time id; pattern + flags live in the literal AST
|
|
832
|
+
// (`['//', pattern, flags]`) or in the regex-var table. Every property below
|
|
833
|
+
// resolves entirely at compile time. Routed by the `.regex:<prop>` dispatch
|
|
834
|
+
// added to core's `.` handler — registered with arity ≤ 1 (receiver only).
|
|
835
|
+
|
|
836
|
+
/** Resolve a regex-typed operand to its `['//', pattern, flags]` literal AST. */
|
|
837
|
+
const regexAstOf = (obj) => {
|
|
838
|
+
if (Array.isArray(obj) && obj[0] === '//') return obj
|
|
839
|
+
if (typeof obj === 'string' && ctx.runtime.regex.vars.has(obj)) return ctx.runtime.regex.vars.get(obj)
|
|
840
|
+
return null
|
|
841
|
+
}
|
|
842
|
+
const flagsOf = (obj) => { const a = regexAstOf(obj); return (a && a[2]) || '' }
|
|
843
|
+
|
|
844
|
+
// RegExp.prototype.source — the pattern text. A literal stores it verbatim
|
|
845
|
+
// (already grammar-escaped), so `/A/.source` is the 6-char "A".
|
|
846
|
+
// An empty pattern serializes to "(?:)" so the result re-parses to a regex.
|
|
847
|
+
ctx.core.emit['.regex:source'] = (obj) => {
|
|
848
|
+
const a = regexAstOf(obj)
|
|
849
|
+
return emit(['str', (a && a[1]) || '(?:)'])
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// RegExp.prototype.flags — flag characters in canonical order (sec-get-regexp.prototype.flags).
|
|
853
|
+
const FLAG_ORDER = 'dgimsvy'
|
|
854
|
+
ctx.core.emit['.regex:flags'] = (obj) => {
|
|
855
|
+
const f = flagsOf(obj)
|
|
856
|
+
return emit(['str', [...FLAG_ORDER].filter(c => f.includes(c)).join('')])
|
|
760
857
|
}
|
|
761
858
|
|
|
859
|
+
// Individual flag accessors → 1/0 (jz carries booleans as f64).
|
|
860
|
+
for (const [prop, ch] of [
|
|
861
|
+
['global', 'g'], ['ignoreCase', 'i'], ['multiline', 'm'], ['dotAll', 's'],
|
|
862
|
+
['unicode', 'u'], ['sticky', 'y'], ['hasIndices', 'd'], ['unicodeSets', 'v'],
|
|
863
|
+
]) ctx.core.emit[`.regex:${prop}`] = (obj) => typed(['f64.const', flagsOf(obj).includes(ch) ? 1 : 0], 'f64')
|
|
864
|
+
|
|
865
|
+
// lastIndex — jz regexes are stateless; a freshly-evaluated regex reads 0.
|
|
866
|
+
ctx.core.emit['.regex:lastIndex'] = () => typed(['f64.const', 0], 'f64')
|
|
867
|
+
|
|
762
868
|
// str.search(/re/) → first match position or -1
|
|
763
869
|
ctx.core.emit['.string:search'] = (str, search) => {
|
|
764
870
|
const id = resolveRegex(search)
|
|
@@ -796,6 +902,7 @@ export default (ctx) => {
|
|
|
796
902
|
['i32.add', ['local.get', `$${idx}`], ['call', '$__str_byteLen', ['i64.reinterpret_f64', ['local.get', `$${q}`]]]]]]]]]], 'f64')
|
|
797
903
|
}
|
|
798
904
|
const nGroups = ctx.runtime.regex.groups.get(id) || 0
|
|
905
|
+
const groupNames = ctx.runtime.regex.groupNames.get(id) || []
|
|
799
906
|
const s = temp('sm'), ms = tempI32('smms'), me = tempI32('smme')
|
|
800
907
|
return typed(['block', ['result', 'f64'],
|
|
801
908
|
['local.set', `$${s}`, asF64(emit(str))],
|
|
@@ -803,7 +910,7 @@ export default (ctx) => {
|
|
|
803
910
|
['call', `$__regex_search_${id}`, ['i64.reinterpret_f64', ['local.get', `$${s}`]]]]],
|
|
804
911
|
['if', ['result', 'f64'], ['i32.lt_s', ['local.get', `$${ms}`], ['i32.const', 0]],
|
|
805
912
|
['then', ['f64.const', 0]],
|
|
806
|
-
['else', buildMatchArr(s, ms, me, nGroups)]]], 'f64')
|
|
913
|
+
['else', buildMatchArr(s, ms, me, nGroups, groupNames)]]], 'f64')
|
|
807
914
|
}
|
|
808
915
|
|
|
809
916
|
// str.replace(/re/, repl) → replaced string
|
package/module/schema.js
CHANGED
|
@@ -22,7 +22,9 @@ export function initSchema(ctx) {
|
|
|
22
22
|
ctx.schema._byProp = byProp
|
|
23
23
|
|
|
24
24
|
ctx.schema.register = (props) => {
|
|
25
|
-
|
|
25
|
+
// Length prefix disambiguates [] from [''] (both join to '') and any
|
|
26
|
+
// shorter prop list from a longer one whose extra entries are empty.
|
|
27
|
+
const key = props.length + '\x01' + props.join('\x01')
|
|
26
28
|
const existing = byKey.get(key)
|
|
27
29
|
if (existing != null) return existing
|
|
28
30
|
const id = ctx.schema.list.push(props) - 1
|
|
@@ -100,4 +102,16 @@ export function initSchema(ctx) {
|
|
|
100
102
|
const idx = ctx.schema.list[id]?.indexOf(prop)
|
|
101
103
|
return idx >= 0 ? (ctx.schema.slotTypes.get(id)?.[idx] ?? null) : null
|
|
102
104
|
}
|
|
105
|
+
|
|
106
|
+
/** Resolve per-slot intCertain: returns true iff every observed write to
|
|
107
|
+
* `varName.prop` is integer-shaped. Precise path only — requires `varName`
|
|
108
|
+
* to have a bound `schemaId`. Consumers (Math.floor elision, toNumF64 skip,
|
|
109
|
+
* intIndexIR) treat `false`/`null` identically (no narrowing). */
|
|
110
|
+
ctx.schema.slotIntCertainAt = (varName, prop) => {
|
|
111
|
+
const id = ctx.schema.idOf(varName)
|
|
112
|
+
if (id == null) return false
|
|
113
|
+
const idx = ctx.schema.list[id]?.indexOf(prop)
|
|
114
|
+
if (idx < 0) return false
|
|
115
|
+
return ctx.schema.slotIntCertain.get(id)?.[idx] === true
|
|
116
|
+
}
|
|
103
117
|
}
|