Skip to content

Commit

Permalink
perf: flyweight syntax nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
harttle committed Dec 17, 2020
1 parent 25286b4 commit 7ae19b7
Show file tree
Hide file tree
Showing 21 changed files with 109 additions and 77 deletions.
33 changes: 5 additions & 28 deletions src/ast/syntax-util.ts → src/ast/renderer-ast-factory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SyntaxKind, SyntaxNode, Block, UnaryOperator, UnaryExpression, NewExpression, VariableDefinition, ReturnStatement, BinaryOperator, If, Null, AssignmentStatement, Statement, Expression, Identifier, ExpressionStatement, BinaryExpression, Literal } from './syntax-node'
import { MapLiteral, UnaryOperator, UnaryExpression, NewExpression, VariableDefinition, ReturnStatement, BinaryOperator, If, Null, AssignmentStatement, Statement, Expression, Identifier, ExpressionStatement, BinaryExpression, Literal } from './renderer-ast-node'

export function createHTMLLiteralAppend (html: string) {
return STATMENT(BINARY(I('html'), '+=', L(html)))
Expand All @@ -25,14 +25,14 @@ export function createIfStrictEqual (lhs: Expression, rhs: Expression, statement
}

export function L (val: any) {
return new Literal(val)
return Literal.create(val)
}

export function I (name: string) {
return new Identifier(name)
return Identifier.create(name)
}

export const NULL = new Null()
export const NULL = Null.create()

export const CTX_DATA = BINARY(I('ctx'), '.', I('data'))

Expand Down Expand Up @@ -64,27 +64,4 @@ export function NEW (name: Expression, args: Expression[]) {
return new NewExpression(name, args)
}

export function isBlock (node: any): node is Block {
const blocks = [SyntaxKind.If, SyntaxKind.ElseIf, SyntaxKind.Else, SyntaxKind.Foreach, SyntaxKind.FunctionDefinition]
return isSyntaxNode(node) && blocks.includes(node.kind)
}

export function isSyntaxNode (node: any): node is SyntaxNode {
return node && Object.prototype.hasOwnProperty.call(node, 'kind')
}

export function isExpressionStatement (node: SyntaxNode): node is ExpressionStatement {
return node.kind === SyntaxKind.ExpressionStatement
}

export function isBinaryExpression (node: SyntaxNode): node is BinaryExpression {
return node.kind === SyntaxKind.BinaryExpression
}

export function isIdentifier (node: SyntaxNode): node is Identifier {
return node.kind === SyntaxKind.Identifier
}

export function isLiteral (node: SyntaxNode): node is Literal {
return node.kind === SyntaxKind.Literal
}
export const EMPTY_MAP = new MapLiteral([])
35 changes: 30 additions & 5 deletions src/ast/syntax-node.ts → src/ast/renderer-ast-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class MapLiteral implements SyntaxNode {
public readonly kind = SyntaxKind.MapLiteral
public items: [Literal | Identifier, Expression, boolean][]
constructor (
items: (Literal | Identifier | [Literal | Identifier, Expression, boolean?])[] = []
items: (Literal | Identifier | [Literal | Identifier, Expression, boolean?])[]
) {
this.items = items.map(item => Array.isArray(item)
? [item[0], item[1], !!item[2]]
Expand All @@ -90,6 +90,13 @@ export class ComponentRendererReference implements SyntaxNode {

export class Null implements SyntaxNode {
public readonly kind = SyntaxKind.Null
private static instance = new Null()

private constructor () {}

static create () {
return Null.instance
}
}
export class CreateComponentInstance implements SyntaxNode {
public readonly kind = SyntaxKind.CreateComponentInstance
Expand Down Expand Up @@ -235,10 +242,19 @@ export class FunctionCall implements SyntaxNode {
}

export class Identifier implements SyntaxNode {
private static cache: Map<string, Identifier> = new Map()
public readonly kind = SyntaxKind.Identifier
constructor (
public name: string

private constructor (
public readonly name: string
) {}

static create (name: string): Identifier {
if (!Identifier.cache.has(name)) {
Identifier.cache.set(name, new Identifier(name))
}
return Identifier.cache.get(name)!
}
}

export class ExpressionStatement implements SyntaxNode {
Expand Down Expand Up @@ -283,9 +299,18 @@ export class FunctionDefinition implements SyntaxNode {

export class Literal implements SyntaxNode {
public readonly kind = SyntaxKind.Literal
constructor (
public value: any
private static cache: Map<string, Literal> = new Map()

private constructor (
public readonly value: any
) {}

static create (name: string): Literal {
if (!Literal.cache.has(name)) {
Literal.cache.set(name, new Literal(name))
}
return Literal.cache.get(name)!
}
}

export class ReturnStatement implements SyntaxNode {
Expand Down
26 changes: 26 additions & 0 deletions src/ast/renderer-ast-util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { SyntaxKind, SyntaxNode, Block, Identifier, ExpressionStatement, BinaryExpression, Literal } from './renderer-ast-node'

export function isBlock (node: any): node is Block {
const blocks = [SyntaxKind.If, SyntaxKind.ElseIf, SyntaxKind.Else, SyntaxKind.Foreach, SyntaxKind.FunctionDefinition]
return isSyntaxNode(node) && blocks.includes(node.kind)
}

export function isSyntaxNode (node: any): node is SyntaxNode {
return node && Object.prototype.hasOwnProperty.call(node, 'kind')
}

export function isExpressionStatement (node: SyntaxNode): node is ExpressionStatement {
return node.kind === SyntaxKind.ExpressionStatement
}

export function isBinaryExpression (node: SyntaxNode): node is BinaryExpression {
return node.kind === SyntaxKind.BinaryExpression
}

export function isIdentifier (node: SyntaxNode): node is Identifier {
return node.kind === SyntaxKind.Identifier
}

export function isLiteral (node: SyntaxNode): node is Literal {
return node.kind === SyntaxKind.Literal
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Statement, SyntaxKind, Expression } from '../ast/syntax-node'
import { Statement, SyntaxKind, Expression } from '../ast/renderer-ast-node'
import { assertNever } from '../utils/lang'

export function * walk (node: Expression | Statement): Iterable<Expression | Statement> {
Expand Down
6 changes: 3 additions & 3 deletions src/compilers/anode-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { ElementCompiler } from './element-compiler'
import { getANodePropByName } from '../ast/san-ast-util'
import * as TypeGuards from '../ast/san-type-guards'
import { IDGenerator } from '../utils/id-generator'
import { HelperCall, JSONStringify, RegexpReplace, Statement, FunctionDefinition, ElseIf, Else, MapAssign, Foreach, If, MapLiteral, ComponentRendererReference, FunctionCall, Expression } from '../ast/syntax-node'
import { CTX_DATA, createHTMLExpressionAppend, createHTMLLiteralAppend, L, I, ASSIGN, STATMENT, UNARY, DEF, BINARY, RETURN } from '../ast/syntax-util'
import { HelperCall, JSONStringify, RegexpReplace, Statement, FunctionDefinition, ElseIf, Else, MapAssign, Foreach, If, MapLiteral, ComponentRendererReference, FunctionCall, Expression } from '../ast/renderer-ast-node'
import { CTX_DATA, EMPTY_MAP, createHTMLExpressionAppend, createHTMLLiteralAppend, L, I, ASSIGN, STATMENT, UNARY, DEF, BINARY, RETURN } from '../ast/renderer-ast-factory'
import { sanExpr } from '../compilers/san-expr-compiler'

/**
Expand Down Expand Up @@ -230,7 +230,7 @@ export class ANodeCompiler<T extends 'none' | 'typed'> {

const compData = this.id.next('compData')
body.push(DEF(compData, CTX_DATA))
body.push(ASSIGN(CTX_DATA, new MapAssign(new MapLiteral(), [CTX_DATA, I('data')])))
body.push(ASSIGN(CTX_DATA, new MapAssign(EMPTY_MAP, [CTX_DATA, I('data')])))

for (const child of content) body.push(...this.compile(child, false))

Expand Down
4 changes: 2 additions & 2 deletions src/compilers/element-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { autoCloseTags } from '../utils/dom-util'
import { ANodeCompiler } from './anode-compiler'
import { ExprNode, ANodeProperty, Directive, ANode } from 'san'
import { isExprNumberNode, isExprStringNode, isExprBoolNode } from '../ast/san-type-guards'
import { createIfStrictEqual, createIfNotNull, createDefaultValue, createHTMLLiteralAppend, createHTMLExpressionAppend, NULL, L, I, ASSIGN, DEF } from '../ast/syntax-util'
import { HelperCall, ArrayIncludes, Else, Foreach, If } from '../ast/syntax-node'
import { createIfStrictEqual, createIfNotNull, createDefaultValue, createHTMLLiteralAppend, createHTMLExpressionAppend, NULL, L, I, ASSIGN, DEF } from '../ast/renderer-ast-factory'
import { HelperCall, ArrayIncludes, Else, Foreach, If } from '../ast/renderer-ast-node'
import { sanExpr } from './san-expr-compiler'

const BOOL_ATTRIBUTES = ['readonly', 'disabled', 'multiple', 'checked']
Expand Down
6 changes: 3 additions & 3 deletions src/compilers/renderer-compiler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ANodeCompiler } from './anode-compiler'
import { ComponentInfo } from '../models/component-info'
import { RenderOptions } from './renderer-options'
import { FunctionDefinition, ComputedCall, Foreach, FunctionCall, MapLiteral, If, CreateComponentInstance, ImportHelper } from '../ast/syntax-node'
import { STATMENT, NEW, BINARY, ASSIGN, DEF, RETURN, createDefaultValue, L, I } from '../ast/syntax-util'
import { FunctionDefinition, ComputedCall, Foreach, FunctionCall, MapLiteral, If, CreateComponentInstance, ImportHelper } from '../ast/renderer-ast-node'
import { EMPTY_MAP, STATMENT, NEW, BINARY, ASSIGN, DEF, RETURN, createDefaultValue, L, I } from '../ast/renderer-ast-factory'
import { IDGenerator } from '../utils/id-generator'
import { mergeLiteralAdd } from '../optimizers/merge-literal-add'

Expand Down Expand Up @@ -72,7 +72,7 @@ export class RendererCompiler {
private compileContext (info: ComponentInfo) {
const refs = info.hasDynamicComponent()
? new MapLiteral([...info.childComponents.entries()].map(([key, val]) => [L(key), val.toAST()]))
: new MapLiteral()
: EMPTY_MAP
return [
DEF('instance', new CreateComponentInstance(info)),
ASSIGN(
Expand Down
14 changes: 7 additions & 7 deletions src/compilers/san-expr-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import { ExprStringNode, ExprNode, ExprTertiaryNode, ExprBinaryNode, ExprUnaryNode, ExprInterpNode, ExprAccessorNode, ExprCallNode, ExprTextNode, ExprObjectNode, ExprArrayNode } from 'san'
import * as TypeGuards from '../ast/san-type-guards'
import { _ } from '../runtime/underscore'
import { EncodeURIComponent, MapLiteral, HelperCall, ArrayLiteral, FilterCall, FunctionCall, Literal, Identifier, ConditionalExpression, BinaryExpression, UnaryExpression, Expression } from '../ast/syntax-node'
import { CTX_DATA, L, NULL } from '../ast/syntax-util'
import { EncodeURIComponent, MapLiteral, HelperCall, ArrayLiteral, FilterCall, FunctionCall, Identifier, ConditionalExpression, BinaryExpression, UnaryExpression, Expression } from '../ast/renderer-ast-node'
import { CTX_DATA, L, I, NULL } from '../ast/renderer-ast-factory'

// 输出为 HTML 并转义、输出为 HTML 不转义、非输出表达式
export type OutputType = 'html' | 'rawhtml' | 'expr'
Expand Down Expand Up @@ -55,7 +55,7 @@ export function dataAccess (accessorExpr: ExprAccessorNode, outputType: OutputTy
// 生成调用表达式代码
function callExpr (callExpr: ExprCallNode, outputType: OutputType) {
const paths = callExpr.name.paths
let fn = new BinaryExpression(new Identifier('ctx'), '.', new Identifier('instance'))
let fn = new BinaryExpression(I('ctx'), '.', I('instance'))
for (const path of paths) {
fn = new BinaryExpression(fn, '[]', sanExpr(path))
}
Expand All @@ -64,8 +64,8 @@ function callExpr (callExpr: ExprCallNode, outputType: OutputType) {

function outputCode (data: Expression, outputType: OutputType) {
if (outputType === 'expr') return data
if (outputType === 'html') return new HelperCall('output', [data, new Literal(true)])
return new HelperCall('output', [data, new Literal(false)])
if (outputType === 'html') return new HelperCall('output', [data, L(true)])
return new HelperCall('output', [data, L(false)])
}

// 生成插值代码
Expand All @@ -91,12 +91,12 @@ function interp (interpExpr: ExprInterpNode, outputType: OutputType) {
}

function str (e: ExprStringNode, output: OutputType) {
return new Literal(output === 'html' ? _.escapeHTML(e.value) : e.value)
return L(output === 'html' ? _.escapeHTML(e.value) : e.value)
}

// 生成文本片段代码
function text (textExpr: ExprTextNode, output: OutputType) {
if (!textExpr.segs.length) return new Literal('')
if (!textExpr.segs.length) return L('')
return textExpr.segs.map(seg => sanExpr(seg, output)).reduce((prev, curr) => new BinaryExpression(prev, '+', curr))
}

Expand Down
2 changes: 1 addition & 1 deletion src/models/component-info.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { SanComponentConfig, ANode } from 'san'
import { FunctionDefinition } from '../ast/syntax-node'
import { FunctionDefinition } from '../ast/renderer-ast-node'
import { parseAndNormalizeTemplate } from '../parsers/parse-template'
import type { ClassDeclaration } from 'ts-morph'
import { Node } from 'estree'
Expand Down
7 changes: 4 additions & 3 deletions src/models/component-reference.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ComponentConstructor } from 'san'
import { MapLiteral, Literal, Identifier } from '../ast/syntax-node'
import { L, I } from '../ast/renderer-ast-factory'
import { MapLiteral } from '../ast/renderer-ast-node'

/**
* 表示一个组件的引用,被引用组件可能在当前文件,也可能在外部文件。例如:
Expand Down Expand Up @@ -29,8 +30,8 @@ export class ComponentReference {
toAST () {
const { specifier, id } = this
return new MapLiteral([
[new Identifier('specifier'), new Literal(specifier)],
[new Identifier('id'), new Literal(id)]
[I('specifier'), L(specifier)],
[I('id'), L(id)]
])
}
}
Expand Down
10 changes: 6 additions & 4 deletions src/optimizers/bracket-to-dot.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { BinaryExpression, Expression, Statement } from '../ast/syntax-node'
import { isLiteral, I, isBinaryExpression } from '../ast/syntax-util'
import { walk } from '../ast/syntax-tree-walker'
import { BinaryExpression, Expression, Statement } from '../ast/renderer-ast-node'
import { isLiteral, isBinaryExpression } from '../ast/renderer-ast-util'
import { I } from '../ast/renderer-ast-factory'
import { walk } from '../ast/renderer-ast-walker'
import { isValidIdentifier } from '../utils/lang'

export function bracketToDot (node: Expression | Statement) {
for (const expr of walk(node)) {
if (isBracketNotation(expr) && isLiteral(expr.rhs) && isValidIdentifier(expr.rhs.value)) {
expr.op = '.'
// 此处要修改 readonly 属性
(expr as any).op = '.'
expr.rhs = I(expr.rhs.value)
}
}
Expand Down
17 changes: 9 additions & 8 deletions src/optimizers/merge-literal-add.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Expression, Literal, Statement, Identifier, Block, SyntaxNode } from '../ast/syntax-node'
import { isLiteral, isIdentifier, isBlock, isBinaryExpression, isExpressionStatement } from '../ast/syntax-util'
import { walk } from '../ast/syntax-tree-walker'
import { Expression, Literal, Statement, Identifier, Block, SyntaxNode } from '../ast/renderer-ast-node'
import { L } from '../ast/renderer-ast-factory'
import { isLiteral, isIdentifier, isBlock, isBinaryExpression, isExpressionStatement } from '../ast/renderer-ast-util'
import { walk } from '../ast/renderer-ast-walker'

type HTMLAddEqualLiteral = Statement & { value: { lhs: Identifier, op: '+=', rhs: Literal } }

Expand All @@ -11,17 +12,17 @@ export function mergeLiteralAdd (node: Expression | Statement): void {
}

function doMergeLiteralAdd (node: Block) {
let prevHTMLAddEqualLiteral: HTMLAddEqualLiteral | null = null
let prev: HTMLAddEqualLiteral | null = null
const filteredBody = []
for (const child of node.body) {
if (isHTMLAddEqualLiteral(child)) {
if (prevHTMLAddEqualLiteral !== null) {
prevHTMLAddEqualLiteral.value.rhs.value += child.value.rhs.value
if (prev !== null) {
prev.value.rhs = L(prev.value.rhs.value + child.value.rhs.value)
continue
}
prevHTMLAddEqualLiteral = child
prev = child
} else {
prevHTMLAddEqualLiteral = null
prev = null
}
filteredBody.push(child)
}
Expand Down
2 changes: 1 addition & 1 deletion src/target-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Compiler } from '../models/compiler'
import { tsSourceFile2js } from '../compilers/ts2js'
import { RenderOptions } from '../compilers/renderer-options'
import { CompileOptions } from './compilers/compile-options'
import { FunctionDefinition } from '../ast/syntax-node'
import { FunctionDefinition } from '../ast/renderer-ast-node'
import { bracketToDot } from '../optimizers/bracket-to-dot'

const debug = debugFactory('target-js')
Expand Down
2 changes: 1 addition & 1 deletion src/target-js/js-emitter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Literal, Foreach, FunctionDefinition, ArrayLiteral, UnaryExpression, MapLiteral, Statement, SyntaxKind, Expression, VariableDefinition } from '../ast/syntax-node'
import { Literal, Foreach, FunctionDefinition, ArrayLiteral, UnaryExpression, MapLiteral, Statement, SyntaxKind, Expression, VariableDefinition } from '../ast/renderer-ast-node'
import { Emitter } from '../utils/emitter'
import { assertNever } from '../utils/lang'

Expand Down
2 changes: 1 addition & 1 deletion test/stub/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Expression, SyntaxKind } from '../../src/ast/syntax-node'
import { Expression, SyntaxKind } from '../../src/ast/renderer-ast-node'

export function matchHTMLAddEqual (expr: Expression) {
return expect.objectContaining({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { walk } from '../../../src/ast/syntax-tree-walker'
import { walk } from '../../../src/ast/renderer-ast-walker'

describe('ast/syntax-tree-walker', () => {
it('should throw if kind not supported', () => {
Expand Down
4 changes: 2 additions & 2 deletions test/unit/compilers/element-compiler.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ElementCompiler } from '../../../src/compilers/element-compiler'
import { parseTemplate } from 'san'
import { SyntaxKind } from '../../../src/ast/syntax-node'
import { CTX_DATA } from '../../../src/ast/syntax-util'
import { SyntaxKind } from '../../../src/ast/renderer-ast-node'
import { CTX_DATA } from '../../../src/ast/renderer-ast-factory'
import { matchHTMLAddEqual } from '../../stub/util'

describe('compilers/element-compiler', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/unit/compilers/renderer-compiler.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { RendererCompiler } from '../../../src/compilers/renderer-compiler'
import { defineComponent } from 'san'
import { ComponentClassParser } from '../../../src/parsers/component-class-parser'
import { SyntaxKind } from '../../../src/ast/syntax-node'
import { SyntaxKind } from '../../../src/ast/renderer-ast-node'
import { matchHTMLAddEqual } from '../../stub/util'

describe('compilers/renderer-compiler', () => {
Expand Down
4 changes: 2 additions & 2 deletions test/unit/compilers/san-expr-compiler.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { sanExpr as expr } from '../../../src/compilers/san-expr-compiler'
import { SyntaxKind } from '../../../src/ast/syntax-node'
import { CTX_DATA } from '../../../src/ast/syntax-util'
import { SyntaxKind } from '../../../src/ast/renderer-ast-node'
import { CTX_DATA } from '../../../src/ast/renderer-ast-factory'
import { parseExpr, parseTemplate } from 'san'

describe('compilers/san-expr-compiler', () => {
Expand Down
4 changes: 2 additions & 2 deletions test/unit/optimizers/merge-literal-add.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RETURN, BINARY, STATMENT, L, I } from '../../../src/ast/syntax-util'
import { RETURN, BINARY, STATMENT, L, I } from '../../../src/ast/renderer-ast-factory'
import { mergeLiteralAdd } from '../../../src/optimizers/merge-literal-add'
import { FunctionDefinition } from '../../../src/ast/syntax-node'
import { FunctionDefinition } from '../../../src/ast/renderer-ast-node'

describe('optimizers/merge-literal-add', () => {
it('should merge to successive html+=', () => {
Expand Down
2 changes: 1 addition & 1 deletion test/unit/target-js/js-emitter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { JSEmitter } from '../../../src/target-js/js-emitter'
import { SyntaxKind } from '../../../src/ast/syntax-node'
import { SyntaxKind } from '../../../src/ast/renderer-ast-node'

describe('JSEmitter', function () {
let emitter: JSEmitter
Expand Down

0 comments on commit 7ae19b7

Please sign in to comment.