feat: codegen context

This commit is contained in:
三咲智子 Kevin Deng 2023-12-01 05:18:20 +08:00
parent 5957c18a0b
commit 0b765bcea3
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
2 changed files with 100 additions and 83 deletions

View File

@ -4,31 +4,56 @@ import {
type RootIRNode, type RootIRNode,
IRNodeTypes, IRNodeTypes,
OperationNode, OperationNode,
VaporHelper,
} from './ir' } from './ir'
// remove when stable // remove when stable
function checkNever(x: never): void {} function checkNever(x: never): void {}
export interface CodegenContext {
options: CodegenOptions
helpers: Set<string>
vaporHelpers: Set<string>
helper(name: string): string
vaporHelper(name: string): string
}
function createCodegenContext(ir: RootIRNode, options: CodegenOptions) {
const { helpers, vaporHelpers } = ir
return {
options,
helpers,
vaporHelpers,
helper(name: string) {
helpers.add(name)
return name
},
vaporHelper(name: VaporHelper) {
vaporHelpers.add(name)
return name
},
}
}
// IR -> JS codegen // IR -> JS codegen
export function generate( export function generate(
ir: RootIRNode, ir: RootIRNode,
options: CodegenOptions = {}, options: CodegenOptions = {},
): CodegenResult { ): CodegenResult {
const ctx = createCodegenContext(ir, options)
const { vaporHelper, helpers, vaporHelpers } = ctx
let code = '' let code = ''
let preamble = '' let preamble = ''
const { helpers, vaporHelpers } = ir
ir.template.forEach((template, i) => { ir.template.forEach((template, i) => {
if (template.type === IRNodeTypes.TEMPLATE_FACTORY) { if (template.type === IRNodeTypes.TEMPLATE_FACTORY) {
preamble += `const t${i} = template(${JSON.stringify( preamble += `const t${i} = ${vaporHelper('template')}(${JSON.stringify(
template.template, template.template,
)})\n` )})\n`
vaporHelpers.add('template')
} else { } else {
// fragment // fragment
code += `const t0 = fragment()\n` code += `const t0 = ${vaporHelper('fragment')}()\n`
vaporHelpers.add('fragment')
} }
}) })
@ -37,18 +62,18 @@ export function generate(
const children = genChildren(ir.dynamic.children) const children = genChildren(ir.dynamic.children)
if (children) { if (children) {
code += `const ${children} = children(n${ir.dynamic.id})\n` code += `const ${children} = ${vaporHelper('children')}(n${
vaporHelpers.add('children') ir.dynamic.id
})\n`
} }
for (const operation of ir.operation) { for (const operation of ir.operation) {
code += genOperation(operation) code += genOperation(operation, ctx)
} }
for (const [_expr, operations] of Object.entries(ir.effect)) { for (const [_expr, operations] of Object.entries(ir.effect)) {
let scope = `effect(() => {\n` let scope = `${vaporHelper('effect')}(() => {\n`
vaporHelpers.add('effect')
for (const operation of operations) { for (const operation of operations) {
scope += genOperation(operation) scope += genOperation(operation, ctx)
} }
scope += '})\n' scope += '})\n'
code += scope code += scope
@ -69,7 +94,7 @@ export function generate(
if (isSetupInlined) { if (isSetupInlined) {
code = `(() => {\n${code}\n})();` code = `(() => {\n${code}\n})();`
} else { } else {
code = `${preamble}export function ${functionName}() {\n${code}\n}` code = `${preamble}export function ${functionName}(_ctx) {\n${code}\n}`
} }
return { return {
@ -77,78 +102,67 @@ export function generate(
ast: ir as any, ast: ir as any,
preamble, preamble,
} }
}
function genOperation(oper: OperationNode) { function genOperation(oper: OperationNode, { vaporHelper }: CodegenContext) {
let code = '' // TODO: cache old value
switch (oper.type) {
// TODO: cache old value case IRNodeTypes.SET_PROP: {
switch (oper.type) { return `${vaporHelper('setAttr')}(n${oper.element}, ${JSON.stringify(
case IRNodeTypes.SET_PROP: { oper.name,
code = `setAttr(n${oper.element}, ${JSON.stringify( )}, undefined, ${oper.value})\n`
oper.name,
)}, undefined, ${oper.value})\n`
vaporHelpers.add('setAttr')
break
}
case IRNodeTypes.SET_TEXT: {
code = `setText(n${oper.element}, undefined, ${oper.value})\n`
vaporHelpers.add('setText')
break
}
case IRNodeTypes.SET_EVENT: {
let value = oper.value
if (oper.modifiers.length) {
value = `withModifiers(${value}, ${genArrayExpression(
oper.modifiers,
)})`
vaporHelpers.add('withModifiers')
}
code = `on(n${oper.element}, ${JSON.stringify(oper.name)}, ${value})\n`
vaporHelpers.add('on')
break
}
case IRNodeTypes.SET_HTML: {
code = `setHtml(n${oper.element}, undefined, ${oper.value})\n`
vaporHelpers.add('setHtml')
break
}
case IRNodeTypes.CREATE_TEXT_NODE: {
code = `const n${oper.id} = createTextNode(${oper.value})\n`
vaporHelpers.add('createTextNode')
break
}
case IRNodeTypes.INSERT_NODE: {
const elements = ([] as number[]).concat(oper.element)
let element = elements.map((el) => `n${el}`).join(', ')
if (elements.length > 1) element = `[${element}]`
code = `insert(${element}, n${oper.parent}${`, n${oper.anchor}`})\n`
vaporHelpers.add('insert')
break
}
case IRNodeTypes.PREPEND_NODE: {
code = `prepend(n${oper.parent}, ${oper.elements
.map((el) => `n${el}`)
.join(', ')})\n`
vaporHelpers.add('prepend')
break
}
case IRNodeTypes.APPEND_NODE: {
code = `append(n${oper.parent}, ${oper.elements
.map((el) => `n${el}`)
.join(', ')})\n`
vaporHelpers.add('append')
break
}
default:
checkNever(oper)
} }
return code case IRNodeTypes.SET_TEXT: {
return `${vaporHelper('setText')}(n${oper.element}, undefined, ${
oper.value
})\n`
}
case IRNodeTypes.SET_EVENT: {
let value = oper.value
if (oper.modifiers.length) {
value = `${vaporHelper('withModifiers')}(${value}, ${genArrayExpression(
oper.modifiers,
)})`
}
return `${vaporHelper('on')}(n${oper.element}, ${JSON.stringify(
oper.name,
)}, ${value})\n`
}
case IRNodeTypes.SET_HTML: {
return `${vaporHelper('setHtml')}(n${oper.element}, undefined, ${
oper.value
})\n`
}
case IRNodeTypes.CREATE_TEXT_NODE: {
return `const n${oper.id} = ${vaporHelper('createTextNode')}(${
oper.value
})\n`
}
case IRNodeTypes.INSERT_NODE: {
const elements = ([] as number[]).concat(oper.element)
let element = elements.map((el) => `n${el}`).join(', ')
if (elements.length > 1) element = `[${element}]`
return `${vaporHelper('insert')}(${element}, n${
oper.parent
}${`, n${oper.anchor}`})\n`
}
case IRNodeTypes.PREPEND_NODE: {
return `${vaporHelper('prepend')}(n${oper.parent}, ${oper.elements
.map((el) => `n${el}`)
.join(', ')})\n`
}
case IRNodeTypes.APPEND_NODE: {
return `${vaporHelper('append')}(n${oper.parent}, ${oper.elements
.map((el) => `n${el}`)
.join(', ')})\n`
}
default:
checkNever(oper)
} }
} }

View File

@ -21,6 +21,9 @@ export interface IRNode {
loc: SourceLocation loc: SourceLocation
} }
// TODO refactor
export type VaporHelper = keyof typeof import('../../runtime-vapor/src')
export interface RootIRNode extends IRNode { export interface RootIRNode extends IRNode {
type: IRNodeTypes.ROOT type: IRNodeTypes.ROOT
template: Array<TemplateFactoryIRNode | FragmentFactoryIRNode> template: Array<TemplateFactoryIRNode | FragmentFactoryIRNode>
@ -29,7 +32,7 @@ export interface RootIRNode extends IRNode {
effect: Record<string /* expr */, OperationNode[]> effect: Record<string /* expr */, OperationNode[]>
operation: OperationNode[] operation: OperationNode[]
helpers: Set<string> helpers: Set<string>
vaporHelpers: Set<string> vaporHelpers: Set<VaporHelper>
} }
export interface TemplateFactoryIRNode extends IRNode { export interface TemplateFactoryIRNode extends IRNode {