@kudzujs/core 0.8.35 → 0.8.37
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/MIGRATION_ROADMAP.md +1 -1
- package/PERFORMANCE.md +50 -0
- package/README.md +2 -1
- package/RELEASES.md +64 -0
- package/docs/next-architecture/README.md +1 -1
- package/docs/next-architecture/compiler-current-architecture.md +5 -5
- package/docs/next-architecture/large-application-ai-native-roadmap.md +7 -1
- package/docs/next-architecture/versioning.md +4 -2
- package/framework/compiler/analysis/binding-index.mjs +15 -1
- package/framework/compiler/analysis/component-analysis.mjs +8 -2
- package/framework/compiler/descriptor-session.mjs +75 -30
- package/framework/compiler/ir/module-ir.mjs +141 -30
- package/framework/compiler/optimize/command-specialization.mjs +185 -14
- package/framework/compiler/source-compiler.mjs +108 -44
- package/package.json +1 -1
|
@@ -1,48 +1,159 @@
|
|
|
1
1
|
export function createModuleIR(file) {
|
|
2
|
-
return { version:
|
|
2
|
+
return { version: 2, file, symbols: [], signals: [], handlers: [], bindings: [], derived: [], effects: [], keyedBlocks: [], imports: [], clientModules: [] }
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
export function assertModuleIRReferences(moduleIR) {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export function assertModuleIRReferences(moduleIR, componentAnalysis) {
|
|
6
|
+
if (moduleIR?.version !== 2) throw new Error(`Unsupported ModuleIR version: ${JSON.stringify(moduleIR?.version)}`)
|
|
7
|
+
if (componentAnalysis && componentAnalysis.version !== 2) throw new Error(`Unsupported ComponentAnalysis version: ${JSON.stringify(componentAnalysis.version)}`)
|
|
8
|
+
const slot = (records, value, label, kind) => {
|
|
9
|
+
if (!Number.isInteger(value) || value < 0 || value >= records.length) throw new Error(`${label} references missing ${kind} slot ${JSON.stringify(value)}`)
|
|
10
|
+
return records[value]
|
|
8
11
|
}
|
|
9
|
-
|
|
12
|
+
const indexed = (name, records) => {
|
|
10
13
|
records.forEach((record, index) => {
|
|
11
|
-
if (record.slot !== index) throw new Error(
|
|
14
|
+
if (record.slot !== index) throw new Error(`${name} slot ${JSON.stringify(record.slot)} must equal its index ${index}`)
|
|
12
15
|
})
|
|
13
16
|
}
|
|
17
|
+
indexed("SymbolRef", moduleIR.symbols)
|
|
18
|
+
indexed("SignalIR", moduleIR.signals)
|
|
19
|
+
indexed("HandlerIR", moduleIR.handlers)
|
|
20
|
+
indexed("BindingIR", moduleIR.bindings)
|
|
21
|
+
indexed("DerivedIR", moduleIR.derived)
|
|
22
|
+
indexed("EffectIR", moduleIR.effects)
|
|
23
|
+
indexed("KeyedBlockIR", moduleIR.keyedBlocks)
|
|
24
|
+
indexed("ImportIR", moduleIR.imports)
|
|
25
|
+
if (componentAnalysis) {
|
|
26
|
+
indexed("Component owner", componentAnalysis.owners)
|
|
27
|
+
indexed("Component specialization", componentAnalysis.specializations)
|
|
28
|
+
for (const owner of componentAnalysis.owners) {
|
|
29
|
+
indexed(`Component owner ${owner.slot} state`, owner.states)
|
|
30
|
+
indexed(`Component owner ${owner.slot} ref`, owner.refs)
|
|
31
|
+
indexed(`Component owner ${owner.slot} ID`, owner.ids)
|
|
32
|
+
for (const setter of owner.setters) slot(owner.states, setter.signal, `Component owner ${owner.slot} setter ${JSON.stringify(setter.name)}`, "state")
|
|
33
|
+
for (const state of owner.states) if (state.owner !== undefined) ownerRef(state.owner, `Component owner ${owner.slot} state ${state.slot} external owner`)
|
|
34
|
+
}
|
|
35
|
+
for (const specialization of componentAnalysis.specializations) {
|
|
36
|
+
indexed(`Component specialization ${specialization.slot} state`, specialization.states)
|
|
37
|
+
indexed(`Component specialization ${specialization.slot} ref`, specialization.refs)
|
|
38
|
+
indexed(`Component specialization ${specialization.slot} ID`, specialization.ids)
|
|
39
|
+
if (specialization.owner !== undefined) ownerRef(specialization.owner, `Component specialization ${specialization.slot} owner`)
|
|
40
|
+
for (const prop of specialization.props ?? []) for (const signal of prop.signals ?? []) slot(moduleIR.signals, signal, `Component specialization ${specialization.slot} prop ${JSON.stringify(prop.name)}`, "SignalIR")
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function moduleSymbol(symbol, label) {
|
|
44
|
+
if (!symbol || typeof symbol.id !== "string" || typeof symbol.module !== "string" || typeof symbol.site !== "string" || typeof symbol.name !== "string") throw new Error(`${label} must be a ModuleSymbol`)
|
|
45
|
+
}
|
|
46
|
+
function ownerRef(reference, label) {
|
|
47
|
+
if (!reference || typeof reference !== "object") throw new Error(`${label} must be an OwnerRef`)
|
|
48
|
+
if (reference.kind === "component") return slot(componentAnalysis?.owners ?? [], reference.slot, label, "component owner")
|
|
49
|
+
if (reference.kind === "specialization") return slot(componentAnalysis?.specializations ?? [], reference.slot, label, "component specialization")
|
|
50
|
+
if (reference.kind === "module-symbol") return moduleSymbol(reference.symbol, label)
|
|
51
|
+
if (reference.kind !== "module") throw new Error(`${label} has invalid kind ${JSON.stringify(reference.kind)}`)
|
|
52
|
+
}
|
|
53
|
+
function stateRef(reference, label) {
|
|
54
|
+
if (!reference || typeof reference !== "object") throw new Error(`${label} must be a StateRef`)
|
|
55
|
+
if (reference.kind === "module-symbol") return moduleSymbol(reference.symbol, label)
|
|
56
|
+
if (reference.kind === "symbol") return slot(moduleIR.symbols, reference.symbol, label, "SymbolRef")
|
|
57
|
+
if (reference.kind !== "state") throw new Error(`${label} has invalid kind ${JSON.stringify(reference.kind)}`)
|
|
58
|
+
const owner = ownerRef(reference.owner, `${label} owner`)
|
|
59
|
+
const states = reference.owner.kind === "component" || reference.owner.kind === "specialization" ? owner.states : []
|
|
60
|
+
slot(states, reference.slot, label, "owner state")
|
|
61
|
+
}
|
|
62
|
+
const exports = new Map()
|
|
63
|
+
const exported = (record, label) => {
|
|
64
|
+
if (record.kind !== "module-export") return
|
|
65
|
+
if (typeof record.exportName !== "string" || !record.exportName) throw new Error(`${label} requires an export name`)
|
|
66
|
+
const previous = exports.get(record.exportName)
|
|
67
|
+
if (previous) throw new Error(`ModuleIR export ${JSON.stringify(record.exportName)} is declared by both ${previous} and ${label}`)
|
|
68
|
+
exports.set(record.exportName, label)
|
|
69
|
+
}
|
|
70
|
+
for (const signal of moduleIR.signals) stateRef(signal.reference, `SignalIR ${signal.slot}`)
|
|
14
71
|
for (const handler of moduleIR.handlers) {
|
|
15
|
-
|
|
16
|
-
|
|
72
|
+
exported(handler, `HandlerIR ${handler.slot}`)
|
|
73
|
+
for (const [index, command] of (handler.commands ?? []).entries()) slot(moduleIR.signals, command.signal, `HandlerIR ${handler.slot} command ${index}`, "SignalIR")
|
|
74
|
+
for (const [index, signal] of (handler.signals ?? []).entries()) slot(moduleIR.signals, signal.signal, `HandlerIR ${handler.slot} signal ${index}`, "SignalIR")
|
|
75
|
+
for (const [index, imported] of (handler.imports ?? []).entries()) slot(moduleIR.imports, imported, `HandlerIR ${handler.slot} import ${index}`, "ImportIR")
|
|
76
|
+
for (const [index, capture] of (handler.captures ?? []).entries()) if (capture.symbol !== undefined) slot(moduleIR.symbols, capture.symbol, `HandlerIR ${handler.slot} capture ${index}`, "SymbolRef")
|
|
77
|
+
if (handler.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, handler.keyedBlock, `HandlerIR ${handler.slot} keyed block`, "KeyedBlockIR")
|
|
17
78
|
}
|
|
18
|
-
for (const binding of moduleIR.bindings)
|
|
79
|
+
for (const binding of moduleIR.bindings) {
|
|
80
|
+
exported(binding, `BindingIR ${binding.slot}`)
|
|
81
|
+
for (const [index, signal] of (binding.signals ?? []).entries()) slot(moduleIR.signals, signal, `BindingIR ${binding.slot} signal ${index}`, "SignalIR")
|
|
82
|
+
for (const [index, imported] of (binding.imports ?? []).entries()) slot(moduleIR.imports, imported, `BindingIR ${binding.slot} import ${index}`, "ImportIR")
|
|
83
|
+
for (const [index, capture] of (binding.captures ?? []).entries()) if (capture.symbol !== undefined) slot(moduleIR.symbols, capture.symbol, `BindingIR ${binding.slot} capture ${index}`, "SymbolRef")
|
|
84
|
+
if (binding.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, binding.keyedBlock, `BindingIR ${binding.slot} keyed block`, "KeyedBlockIR")
|
|
85
|
+
}
|
|
86
|
+
for (const derived of moduleIR.derived) for (const [index, signal] of (derived.signals ?? []).entries()) slot(moduleIR.signals, signal, `DerivedIR ${derived.slot} signal ${index}`, "SignalIR")
|
|
19
87
|
for (const effect of moduleIR.effects) {
|
|
20
|
-
slot(moduleIR.handlers, effect.setup?.handler, `
|
|
21
|
-
|
|
22
|
-
|
|
88
|
+
const handler = slot(moduleIR.handlers, effect.setup?.handler, `EffectIR ${effect.slot} setup`, "HandlerIR")
|
|
89
|
+
if (handler.kind !== "module-export" || handler.role !== "effect") throw new Error(`EffectIR ${effect.slot} setup HandlerIR ${handler.slot} must have role "effect"`)
|
|
90
|
+
for (const [index, dependency] of (effect.dependencies ?? []).entries()) {
|
|
91
|
+
if (dependency.kind === "signal") slot(moduleIR.signals, dependency.signal, `EffectIR ${effect.slot} dependency ${index}`, "SignalIR")
|
|
92
|
+
else if (dependency.kind === "derived") {
|
|
93
|
+
slot(moduleIR.derived, dependency.derived, `EffectIR ${effect.slot} dependency ${index}`, "DerivedIR")
|
|
94
|
+
for (const [sourceIndex, signal] of (dependency.sources ?? []).entries()) slot(moduleIR.signals, signal, `EffectIR ${effect.slot} dependency ${index} source ${sourceIndex}`, "SignalIR")
|
|
95
|
+
} else throw new Error(`EffectIR ${effect.slot} dependency ${index} has invalid kind ${JSON.stringify(dependency.kind)}`)
|
|
96
|
+
}
|
|
97
|
+
for (const [index, signal] of (effect.subscriptions ?? []).entries()) slot(moduleIR.signals, signal, `EffectIR ${effect.slot} subscription ${index}`, "SignalIR")
|
|
98
|
+
for (const [index, signal] of (effect.dependencySignals ?? []).entries()) slot(moduleIR.signals, signal, `EffectIR ${effect.slot} dependency signal ${index}`, "SignalIR")
|
|
99
|
+
if (effect.ownership?.owner) ownerRef(effect.ownership.owner, `EffectIR ${effect.slot} ownership`)
|
|
100
|
+
if (effect.ownership?.keyedBlock !== undefined) {
|
|
101
|
+
slot(moduleIR.keyedBlocks, effect.ownership.keyedBlock, `EffectIR ${effect.slot} ownership`, "KeyedBlockIR")
|
|
102
|
+
if (handler.keyedBlock !== effect.ownership.keyedBlock) throw new Error(`EffectIR ${effect.slot} and HandlerIR ${handler.slot} must reference the same KeyedBlockIR`)
|
|
103
|
+
}
|
|
23
104
|
}
|
|
24
105
|
for (const block of moduleIR.keyedBlocks) {
|
|
25
|
-
if (block.
|
|
26
|
-
|
|
27
|
-
if (block.
|
|
106
|
+
if (block.collection?.kind === "signal") slot(moduleIR.signals, block.collection.signal, `KeyedBlockIR ${block.slot} collection`, "SignalIR")
|
|
107
|
+
else if (block.collection?.kind === "binding") slot(moduleIR.bindings, block.collection.binding, `KeyedBlockIR ${block.slot} collection`, "BindingIR")
|
|
108
|
+
else if (block.collection?.kind === "symbol") slot(moduleIR.symbols, block.collection.symbol, `KeyedBlockIR ${block.slot} collection`, "SymbolRef")
|
|
109
|
+
else if (block.collection?.kind !== "static") throw new Error(`KeyedBlockIR ${block.slot} collection has invalid kind ${JSON.stringify(block.collection?.kind)}`)
|
|
110
|
+
if (block.parent !== undefined) {
|
|
111
|
+
const parent = slot(moduleIR.keyedBlocks, block.parent, `KeyedBlockIR ${block.slot} parent`, "KeyedBlockIR")
|
|
112
|
+
if (!(parent.children ?? []).includes(block.slot)) throw new Error(`KeyedBlockIR ${block.slot} parent ${parent.slot} does not reciprocally list child ${block.slot}`)
|
|
113
|
+
}
|
|
114
|
+
for (const childSlot of block.children ?? []) {
|
|
115
|
+
const child = slot(moduleIR.keyedBlocks, childSlot, `KeyedBlockIR ${block.slot} child`, "KeyedBlockIR")
|
|
116
|
+
if (child.parent !== block.slot) throw new Error(`KeyedBlockIR ${block.slot} child ${child.slot} does not reciprocally reference parent ${block.slot}`)
|
|
117
|
+
}
|
|
118
|
+
if (new Set(block.children ?? []).size !== (block.children ?? []).length) throw new Error(`KeyedBlockIR ${block.slot} has duplicate children`)
|
|
119
|
+
if (block.selector !== undefined) slot(moduleIR.derived, block.selector, `KeyedBlockIR ${block.slot} selector`, "DerivedIR")
|
|
120
|
+
for (const [index, signal] of (block.selectorSignals ?? []).entries()) slot(moduleIR.signals, signal, `KeyedBlockIR ${block.slot} selector signal ${index}`, "SignalIR")
|
|
121
|
+
for (const [index, specialization] of (block.specializations ?? []).entries()) slot(componentAnalysis?.specializations ?? [], specialization, `KeyedBlockIR ${block.slot} specialization ${index}`, "component specialization")
|
|
122
|
+
for (const [index, row] of (block.rowStates ?? []).entries()) slot(moduleIR.signals, row.signal, `KeyedBlockIR ${block.slot} row state ${index}`, "SignalIR")
|
|
123
|
+
for (const [index, row] of (block.rowRefs ?? []).entries()) {
|
|
124
|
+
const specialization = slot(componentAnalysis?.specializations ?? [], row.specialization, `KeyedBlockIR ${block.slot} row ref ${index}`, "component specialization")
|
|
125
|
+
slot(specialization.refs, row.ref, `KeyedBlockIR ${block.slot} row ref ${index}`, "specialization ref")
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const visiting = new Set()
|
|
129
|
+
const visited = new Set()
|
|
130
|
+
const visit = (block, trail) => {
|
|
131
|
+
if (visiting.has(block.slot)) throw new Error(`KeyedBlockIR parent cycle: ${[...trail, block.slot].join(" -> ")}`)
|
|
132
|
+
if (visited.has(block.slot)) return
|
|
133
|
+
visiting.add(block.slot)
|
|
134
|
+
if (block.parent !== undefined) visit(moduleIR.keyedBlocks[block.parent], [...trail, block.slot])
|
|
135
|
+
visiting.delete(block.slot)
|
|
136
|
+
visited.add(block.slot)
|
|
28
137
|
}
|
|
138
|
+
for (const block of moduleIR.keyedBlocks) visit(block, [])
|
|
29
139
|
return moduleIR
|
|
30
140
|
}
|
|
31
141
|
|
|
32
|
-
export function
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
slots.set(key, slot)
|
|
39
|
-
moduleIR.signals.push({ slot, key, debugName: state })
|
|
142
|
+
export function registerSignal(moduleIR, reference, debugName) {
|
|
143
|
+
const key = JSON.stringify(reference)
|
|
144
|
+
let signal = moduleIR.signals.find(entry => JSON.stringify(entry.reference) === key)
|
|
145
|
+
if (!signal) {
|
|
146
|
+
signal = { slot: moduleIR.signals.length, reference, debugName }
|
|
147
|
+
moduleIR.signals.push(signal)
|
|
40
148
|
}
|
|
41
|
-
|
|
149
|
+
return signal
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function registerCommandHandler(moduleIR, commands, source) {
|
|
42
153
|
const handler = {
|
|
43
154
|
slot: moduleIR.handlers.length,
|
|
44
155
|
kind: "commands",
|
|
45
|
-
commands: commands.map(({ operation,
|
|
156
|
+
commands: commands.map(({ operation, reference, state, value, syntax }) => ({ operation, signal: registerSignal(moduleIR, reference, state).slot, value, ...(syntax ? { syntax } : {}) })),
|
|
46
157
|
...(source ? { source } : {})
|
|
47
158
|
}
|
|
48
159
|
moduleIR.handlers.push(handler)
|
|
@@ -50,31 +161,31 @@ export function registerCommandHandler(moduleIR, commands, source, scope = "modu
|
|
|
50
161
|
}
|
|
51
162
|
|
|
52
163
|
export function registerModuleHandler(moduleIR, descriptor) {
|
|
53
|
-
const handler = { slot: moduleIR.handlers.length, kind: "module-export"
|
|
164
|
+
const handler = { ...descriptor, slot: moduleIR.handlers.length, kind: "module-export" }
|
|
54
165
|
moduleIR.handlers.push(handler)
|
|
55
166
|
return handler
|
|
56
167
|
}
|
|
57
168
|
|
|
58
169
|
export function registerBinding(moduleIR, descriptor) {
|
|
59
|
-
const binding = { slot: moduleIR.bindings.length, kind: "module-export"
|
|
170
|
+
const binding = { ...descriptor, slot: moduleIR.bindings.length, kind: "module-export" }
|
|
60
171
|
moduleIR.bindings.push(binding)
|
|
61
172
|
return binding
|
|
62
173
|
}
|
|
63
174
|
|
|
64
175
|
export function registerDerived(moduleIR, descriptor) {
|
|
65
|
-
const derived = { slot: moduleIR.derived.length
|
|
176
|
+
const derived = { ...descriptor, slot: moduleIR.derived.length }
|
|
66
177
|
moduleIR.derived.push(derived)
|
|
67
178
|
return derived
|
|
68
179
|
}
|
|
69
180
|
|
|
70
181
|
export function registerEffect(moduleIR, descriptor) {
|
|
71
|
-
const effect = { slot: moduleIR.effects.length
|
|
182
|
+
const effect = { ...descriptor, slot: moduleIR.effects.length }
|
|
72
183
|
moduleIR.effects.push(effect)
|
|
73
184
|
return effect
|
|
74
185
|
}
|
|
75
186
|
|
|
76
187
|
export function registerKeyedBlock(moduleIR, descriptor) {
|
|
77
|
-
const block = { slot: moduleIR.keyedBlocks.length
|
|
188
|
+
const block = { ...descriptor, slot: moduleIR.keyedBlocks.length }
|
|
78
189
|
moduleIR.keyedBlocks.push(block)
|
|
79
190
|
return block
|
|
80
191
|
}
|
|
@@ -1,32 +1,203 @@
|
|
|
1
1
|
import ts from "typescript"
|
|
2
|
+
import { sourceNodeError } from "../ast-helpers.mjs"
|
|
2
3
|
|
|
3
4
|
export function createCommandSpecializer({ isPrimitiveLiteral }) {
|
|
4
|
-
|
|
5
|
+
const specialize = (expression, setters) => {
|
|
5
6
|
if (ts.isCallExpression(expression) && ts.isPropertyAccessExpression(expression.expression) && ts.isIdentifier(expression.expression.expression) && expression.expression.expression.text === "console" && expression.expression.name.text === "log" && expression.arguments.length === 2 && ts.isStringLiteral(expression.arguments[0]) && ts.isIdentifier(expression.arguments[1]) && [...setters.values()].includes(expression.arguments[1].text)) {
|
|
6
7
|
return { operation: "log", state: expression.arguments[1].text, value: expression.arguments[0].text }
|
|
7
8
|
}
|
|
8
|
-
|
|
9
9
|
if (!ts.isCallExpression(expression) || !ts.isIdentifier(expression.expression) || expression.arguments.length !== 1) return undefined
|
|
10
10
|
const state = setters.get(expression.expression.text)
|
|
11
11
|
if (!state) return undefined
|
|
12
|
-
|
|
13
12
|
const value = expression.arguments[0]
|
|
14
|
-
if (ts.isBinaryExpression(value) && ts.isIdentifier(value.left) && value.left.text === state
|
|
15
|
-
|
|
16
|
-
const operand = signedNumber(value.right, value.operatorToken.kind === ts.SyntaxKind.MinusToken ? "negative" : undefined)
|
|
17
|
-
return operand ? { operation: "add", state, ...operand } : undefined
|
|
18
|
-
}
|
|
19
|
-
if (ts.isArrowFunction(value) && value.parameters.length === 1 && ts.isIdentifier(value.parameters[0].name) && ts.isBinaryExpression(value.body) && ts.isIdentifier(value.body.left) && value.body.left.text === value.parameters[0].name.text && ts.isNumericLiteral(value.body.right)) {
|
|
20
|
-
if (value.body.operatorToken.kind !== ts.SyntaxKind.PlusToken && value.body.operatorToken.kind !== ts.SyntaxKind.MinusToken) return undefined
|
|
21
|
-
const operand = signedNumber(value.body.right, value.body.operatorToken.kind === ts.SyntaxKind.MinusToken ? "negative" : undefined)
|
|
22
|
-
return operand ? { operation: "add", state, ...operand } : undefined
|
|
23
|
-
}
|
|
13
|
+
if (ts.isBinaryExpression(value) && ts.isIdentifier(value.left) && value.left.text === state) return addCommand(state, value)
|
|
14
|
+
if (ts.isArrowFunction(value) && value.parameters.length === 1 && ts.isIdentifier(value.parameters[0].name) && ts.isBinaryExpression(value.body) && ts.isIdentifier(value.body.left) && value.body.left.text === value.parameters[0].name.text) return addCommand(state, value.body)
|
|
24
15
|
if (isPrimitiveLiteral(value)) {
|
|
25
16
|
const literal = primitiveValue(value)
|
|
26
17
|
return literal ? { operation: "set", state, ...literal } : undefined
|
|
27
18
|
}
|
|
28
|
-
return undefined
|
|
29
19
|
}
|
|
20
|
+
specialize.handler = (handler, setters, bindingIndex) => specializeHandler(handler, setters, bindingIndex, specialize)
|
|
21
|
+
return specialize
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function specializeHandler(handler, setters, bindingIndex, specialize) {
|
|
25
|
+
const statements = ts.isBlock(handler.body) ? handler.body.statements.filter(statement => !ts.isEmptyStatement(statement)) : []
|
|
26
|
+
if (statements.length === 2) {
|
|
27
|
+
const command = aliasCommand(statements, handler, setters, bindingIndex) ?? helperCommand(statements, handler, setters, bindingIndex, specialize)
|
|
28
|
+
if (command) return [command]
|
|
29
|
+
}
|
|
30
|
+
rejectUnsafe(statements, handler, setters, bindingIndex)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function aliasCommand([first, second], boundary, setters, bindingIndex) {
|
|
34
|
+
const declaration = oneDeclaration(first)
|
|
35
|
+
if (!declaration || !(first.declarationList.flags & ts.NodeFlags.Const) || !declaration.initializer || !ts.isExpressionStatement(second)) return undefined
|
|
36
|
+
const value = stateAdd(declaration.initializer, boundary, setters, bindingIndex)
|
|
37
|
+
const call = second.expression
|
|
38
|
+
if (!value || !directCall(call) || call.arguments.length !== 1 || !ts.isIdentifier(call.arguments[0])) return undefined
|
|
39
|
+
const state = setters.get(call.expression.text)
|
|
40
|
+
if (state !== value.state || !captured(call.expression, boundary, call.expression.text, bindingIndex) || !declaredBy(call.arguments[0], declaration.name, boundary, bindingIndex) || refs(boundary, declaration.name, boundary, bindingIndex).length !== 1) return undefined
|
|
41
|
+
return { operation: "add", state, ...value.operand }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function helperCommand([first, second], boundary, setters, bindingIndex, specialize) {
|
|
45
|
+
if (!ts.isExpressionStatement(second) || !directCall(second.expression)) return undefined
|
|
46
|
+
const call = second.expression
|
|
47
|
+
let name
|
|
48
|
+
let command
|
|
49
|
+
const declaration = oneDeclaration(first)
|
|
50
|
+
if (declaration && first.declarationList.flags & ts.NodeFlags.Const && declaration.initializer && ts.isArrowFunction(declaration.initializer) && !declaration.initializer.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword) && !declaration.initializer.parameters.length && !ts.isBlock(declaration.initializer.body) && !call.arguments.length) {
|
|
51
|
+
name = declaration.name
|
|
52
|
+
command = safeDirect(declaration.initializer.body, boundary, setters, bindingIndex, specialize)
|
|
53
|
+
} else if (ts.isFunctionDeclaration(first) && first.name && first.body && !first.asteriskToken && !first.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword) && first.parameters.length === 1 && first.body.statements.length === 1 && ts.isExpressionStatement(first.body.statements[0]) && call.arguments.length === 1 && ts.isIdentifier(call.arguments[0])) {
|
|
54
|
+
const parameter = first.parameters[0]
|
|
55
|
+
if (!ts.isIdentifier(parameter.name) || parameter.initializer || parameter.dotDotDotToken) return undefined
|
|
56
|
+
name = first.name
|
|
57
|
+
command = parameterCommand(first.body.statements[0].expression, parameter.name, first, call.arguments[0], boundary, setters, bindingIndex)
|
|
58
|
+
}
|
|
59
|
+
if (!name || !command || call.expression.text !== name.text || !declaredBy(call.expression, name, boundary, bindingIndex) || refs(boundary, name, boundary, bindingIndex).length !== 1) return undefined
|
|
60
|
+
return command
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function safeDirect(expression, boundary, setters, bindingIndex, specialize) {
|
|
64
|
+
const command = specialize(expression, setters)
|
|
65
|
+
if (!command || command.operation === "log") return undefined
|
|
66
|
+
if (!captured(expression.expression, boundary, expression.expression.text, bindingIndex)) return undefined
|
|
67
|
+
const value = expression.arguments[0]
|
|
68
|
+
return !ts.isBinaryExpression(value) || captured(value.left, boundary, command.state, bindingIndex) ? command : undefined
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parameterCommand(expression, parameter, helper, argument, boundary, setters, bindingIndex) {
|
|
72
|
+
if (!directCall(expression) || expression.arguments.length !== 1 || !ts.isBinaryExpression(expression.arguments[0])) return undefined
|
|
73
|
+
const state = setters.get(expression.expression.text)
|
|
74
|
+
const value = expression.arguments[0]
|
|
75
|
+
if (!state || !captured(expression.expression, boundary, expression.expression.text, bindingIndex) || !ts.isIdentifier(value.left) || !declaredBy(value.left, parameter, helper, bindingIndex) || !captured(argument, boundary, state, bindingIndex)) return undefined
|
|
76
|
+
const operand = addOperand(value)
|
|
77
|
+
return operand ? { operation: "add", state, ...operand } : undefined
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function rejectUnsafe(statements, boundary, setters, bindingIndex) {
|
|
81
|
+
if (!bindingIndex) return
|
|
82
|
+
for (const statement of statements) {
|
|
83
|
+
const declaration = oneDeclaration(statement)
|
|
84
|
+
if (declaration?.initializer && ts.isObjectLiteralExpression(declaration.initializer) && containsSetter(declaration.initializer, boundary, setters, bindingIndex)) {
|
|
85
|
+
const use = refs(boundary, declaration.name, boundary, bindingIndex).find(dynamic)
|
|
86
|
+
if (use) fail(use, "Semantic state helpers do not support dynamic dispatch")
|
|
87
|
+
}
|
|
88
|
+
if (declaration?.initializer && stateAdd(declaration.initializer, boundary, setters, bindingIndex)) {
|
|
89
|
+
const use = setterAliasUse(boundary, declaration.name, setters, bindingIndex)
|
|
90
|
+
if (!use) continue
|
|
91
|
+
const uses = refs(boundary, declaration.name, boundary, bindingIndex)
|
|
92
|
+
if (!(statement.declarationList.flags & ts.NodeFlags.Const) || uses.some(mutated)) fail(declaration.name, "Semantic state aliases must remain immutable")
|
|
93
|
+
fail(uses.find(reference => reference !== use) ?? use, "Semantic state aliases must be passed directly to one setter and cannot escape")
|
|
94
|
+
}
|
|
95
|
+
const helper = helperDeclaration(statement, boundary, setters, bindingIndex)
|
|
96
|
+
if (!helper) continue
|
|
97
|
+
const uses = refs(boundary, helper.name, boundary, bindingIndex)
|
|
98
|
+
if (uses.some(reference => inside(reference, helper.body))) fail(helper.name, "Semantic state helpers cannot be recursive")
|
|
99
|
+
if (helper.mutable || uses.some(mutated)) fail(helper.name, "Semantic state helpers must remain immutable")
|
|
100
|
+
if (uses.some(dynamic)) fail(uses.find(dynamic), "Semantic state helpers do not support dynamic dispatch")
|
|
101
|
+
if (uses.length !== 1 || !directIdentifierCall(uses[0])) fail(uses.find(reference => !directIdentifierCall(reference)) ?? helper.name, "Semantic state helpers must be called exactly once and cannot escape")
|
|
102
|
+
fail(helper.body, "Semantic state helpers must contain one synchronous state operation")
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function helperDeclaration(statement, boundary, setters, bindingIndex) {
|
|
107
|
+
const declaration = oneDeclaration(statement)
|
|
108
|
+
if (declaration?.initializer && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer)) && containsSetter(declaration.initializer, boundary, setters, bindingIndex)) return { name: declaration.name, body: declaration.initializer, mutable: !(statement.declarationList.flags & ts.NodeFlags.Const) }
|
|
109
|
+
if (ts.isFunctionDeclaration(statement) && statement.name && statement.body && containsSetter(statement.body, boundary, setters, bindingIndex)) return { name: statement.name, body: statement.body, mutable: false }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function stateAdd(expression, boundary, setters, bindingIndex) {
|
|
113
|
+
if (!ts.isBinaryExpression(expression) || !ts.isIdentifier(expression.left) || ![...setters.values()].includes(expression.left.text) || !captured(expression.left, boundary, expression.left.text, bindingIndex)) return undefined
|
|
114
|
+
const operand = addOperand(expression)
|
|
115
|
+
return operand ? { state: expression.left.text, operand } : undefined
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function addCommand(state, expression) {
|
|
119
|
+
const operand = addOperand(expression)
|
|
120
|
+
return operand ? { operation: "add", state, ...operand } : undefined
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function addOperand(expression) {
|
|
124
|
+
if (!ts.isBinaryExpression(expression) || !ts.isNumericLiteral(expression.right) || ![ts.SyntaxKind.PlusToken, ts.SyntaxKind.MinusToken].includes(expression.operatorToken.kind)) return undefined
|
|
125
|
+
return signedNumber(expression.right, expression.operatorToken.kind === ts.SyntaxKind.MinusToken ? "negative" : undefined)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function oneDeclaration(statement) {
|
|
129
|
+
if (!ts.isVariableStatement(statement) || statement.declarationList.declarations.length !== 1) return undefined
|
|
130
|
+
const declaration = statement.declarationList.declarations[0]
|
|
131
|
+
return ts.isIdentifier(declaration.name) ? declaration : undefined
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function directCall(node) {
|
|
135
|
+
return ts.isCallExpression(node) && !node.questionDotToken && ts.isIdentifier(node.expression)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function captured(identifier, boundary, name, bindingIndex) {
|
|
139
|
+
if (!ts.isIdentifier(identifier)) return false
|
|
140
|
+
const resolution = bindingIndex?.resolveReference(identifier, boundary)
|
|
141
|
+
return !resolution ? identifier.text === name : resolution.debugName === name && ["capture", "unresolved"].includes(resolution.kind)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function declaredBy(identifier, declaration, boundary, bindingIndex) {
|
|
145
|
+
const resolution = bindingIndex?.resolveReference(identifier, boundary)
|
|
146
|
+
return bindingIndex ? resolution?.declaration === declaration : identifier.text === declaration.text
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function refs(root, declaration, boundary, bindingIndex) {
|
|
150
|
+
const found = []
|
|
151
|
+
const visit = node => {
|
|
152
|
+
if (ts.isIdentifier(node) && declaredBy(node, declaration, boundary, bindingIndex)) found.push(node)
|
|
153
|
+
ts.forEachChild(node, visit)
|
|
154
|
+
}
|
|
155
|
+
visit(root)
|
|
156
|
+
return found
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function containsSetter(root, boundary, setters, bindingIndex) {
|
|
160
|
+
let found = false
|
|
161
|
+
const visit = node => {
|
|
162
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && setters.has(node.expression.text) && captured(node.expression, boundary, node.expression.text, bindingIndex)) found = true
|
|
163
|
+
if (!found) ts.forEachChild(node, visit)
|
|
164
|
+
}
|
|
165
|
+
visit(root)
|
|
166
|
+
return found
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function setterAliasUse(root, declaration, setters, bindingIndex) {
|
|
170
|
+
let found
|
|
171
|
+
const visit = node => {
|
|
172
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && setters.has(node.expression.text) && ts.isIdentifier(node.arguments[0]) && declaredBy(node.arguments[0], declaration, root, bindingIndex)) found = node.arguments[0]
|
|
173
|
+
if (!found) ts.forEachChild(node, visit)
|
|
174
|
+
}
|
|
175
|
+
visit(root)
|
|
176
|
+
return found
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function directIdentifierCall(identifier) {
|
|
180
|
+
return ts.isCallExpression(identifier.parent) && identifier.parent.expression === identifier && !identifier.parent.questionDotToken
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function dynamic(identifier) {
|
|
184
|
+
if (ts.isCallExpression(identifier.parent) && identifier.parent.expression === identifier) return Boolean(identifier.parent.questionDotToken)
|
|
185
|
+
for (let current = identifier.parent; current && !ts.isStatement(current); current = current.parent) if (ts.isCallExpression(current)) return true
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function mutated(identifier) {
|
|
190
|
+
const parent = identifier.parent
|
|
191
|
+
return ts.isPrefixUnaryExpression(parent) && [ts.SyntaxKind.PlusPlusToken, ts.SyntaxKind.MinusMinusToken].includes(parent.operator) || ts.isPostfixUnaryExpression(parent) || ts.isBinaryExpression(parent) && parent.left === identifier && parent.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && parent.operatorToken.kind <= ts.SyntaxKind.LastAssignment
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function inside(node, root) {
|
|
195
|
+
for (let current = node; current; current = current.parent) if (current === root) return true
|
|
196
|
+
return false
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function fail(node, message) {
|
|
200
|
+
throw sourceNodeError(node, node.getSourceFile(), message)
|
|
30
201
|
}
|
|
31
202
|
|
|
32
203
|
function primitiveValue(node) {
|