From e31fb3c1726ab40edd7da39d09d71bc1aa11a54b Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 1 Oct 2019 09:27:34 -0400 Subject: [PATCH] wip: Sequence & Conditional expressions for AST --- packages/compiler-core/src/ast.ts | 65 ++++++++++++++++--- packages/compiler-core/src/codegen.ts | 54 +++++++++++++-- packages/compiler-core/src/parse.ts | 14 ++-- packages/compiler-core/src/transform.ts | 12 ++-- .../src/transforms/optimizeText.ts | 6 +- .../compiler-core/src/transforms/vSlot.ts | 6 +- 6 files changed, 124 insertions(+), 33 deletions(-) diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index eb7ad3791..b894be857 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -28,7 +28,9 @@ export const enum NodeTypes { JS_OBJECT_EXPRESSION, JS_PROPERTY, JS_ARRAY_EXPRESSION, - JS_SLOT_FUNCTION + JS_SLOT_FUNCTION, + JS_SEQUENCE_EXPRESSION, + JS_CONDITIONAL_EXPRESSION } export const enum ElementTypes { @@ -61,7 +63,7 @@ export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode -export type ChildNode = +export type TemplateChildNode = | ElementNode | InterpolationNode | CompoundExpressionNode @@ -72,7 +74,7 @@ export type ChildNode = export interface RootNode extends Node { type: NodeTypes.ROOT - children: ChildNode[] + children: TemplateChildNode[] imports: string[] statements: string[] hoists: JSChildNode[] @@ -85,7 +87,7 @@ export interface ElementNode extends Node { tagType: ElementTypes isSelfClosing: boolean props: Array - children: ChildNode[] + children: TemplateChildNode[] codegenNode: CallExpression | undefined } @@ -140,12 +142,13 @@ export interface CompoundExpressionNode extends Node { export interface IfNode extends Node { type: NodeTypes.IF branches: IfBranchNode[] + codegenNode: JSChildNode | undefined } export interface IfBranchNode extends Node { type: NodeTypes.IF_BRANCH condition: ExpressionNode | undefined // else - children: ChildNode[] + children: TemplateChildNode[] } export interface ForNode extends Node { @@ -154,7 +157,7 @@ export interface ForNode extends Node { valueAlias: ExpressionNode | undefined keyAlias: ExpressionNode | undefined objectIndexAlias: ExpressionNode | undefined - children: ChildNode[] + children: TemplateChildNode[] } // We also include a number of JavaScript AST nodes for code generation. @@ -166,11 +169,13 @@ export type JSChildNode = | ArrayExpression | ExpressionNode | SlotFunctionExpression + | ConditionalExpression + | SequenceExpression export interface CallExpression extends Node { type: NodeTypes.JS_CALL_EXPRESSION callee: string - arguments: (string | JSChildNode | ChildNode[])[] + arguments: (string | JSChildNode | TemplateChildNode[])[] } export interface ObjectExpression extends Node { @@ -192,7 +197,19 @@ export interface ArrayExpression extends Node { export interface SlotFunctionExpression extends Node { type: NodeTypes.JS_SLOT_FUNCTION params: ExpressionNode | undefined - returns: ChildNode[] + returns: TemplateChildNode[] +} + +export interface SequenceExpression extends Node { + type: NodeTypes.JS_SEQUENCE_EXPRESSION + expressions: JSChildNode[] +} + +export interface ConditionalExpression extends Node { + type: NodeTypes.JS_CONDITIONAL_EXPRESSION + test: ExpressionNode + consequent: JSChildNode + alternate: JSChildNode } export function createArrayExpression( @@ -282,7 +299,7 @@ export function createCallExpression( export function createFunctionExpression( params: ExpressionNode | undefined, - returns: ChildNode[], + returns: TemplateChildNode[], loc: SourceLocation ): SlotFunctionExpression { return { @@ -292,3 +309,33 @@ export function createFunctionExpression( loc } } + +// sequence and conditional expressions are never associated with template nodes, +// so their source locations are just a stub. +const locStub: SourceLocation = { + source: '', + start: { line: 1, column: 1, offset: 0 }, + end: { line: 1, column: 1, offset: 0 } +} + +export function createSequenceExpression(expressions: JSChildNode[]) { + return { + type: NodeTypes.JS_SEQUENCE_EXPRESSION, + expressions, + loc: locStub + } +} + +export function createConditionalExpression( + test: ExpressionNode, + consequent: JSChildNode, + alternate: JSChildNode +) { + return { + type: NodeTypes.JS_CONDITIONAL_EXPRESSION, + test, + consequent, + alternate, + loc: locStub + } +} diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 1b3f53c82..0d01772ad 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -1,6 +1,6 @@ import { RootNode, - ChildNode, + TemplateChildNode, ElementNode, IfNode, ForNode, @@ -19,7 +19,9 @@ import { CompoundExpressionNode, SimpleExpressionNode, ElementTypes, - SlotFunctionExpression + SlotFunctionExpression, + SequenceExpression, + ConditionalExpression } from './ast' import { SourceMapGenerator, RawSourceMap } from 'source-map' import { @@ -35,7 +37,7 @@ import { COMMENT } from './runtimeConstants' -type CodegenNode = ChildNode | JSChildNode +type CodegenNode = TemplateChildNode | JSChildNode export interface CodegenOptions { // - Module mode will generate ES module import statements for helpers @@ -269,7 +271,7 @@ function genHoists(hoists: JSChildNode[], context: CodegenContext) { // - expression // - outlet, which always produces an array function genChildren( - children: ChildNode[], + children: TemplateChildNode[], context: CodegenContext, allowSingle: boolean = false ) { @@ -294,7 +296,7 @@ function genChildren( } function genNodeListAsArray( - nodes: (string | CodegenNode | ChildNode[])[], + nodes: (string | CodegenNode | TemplateChildNode[])[], context: CodegenContext ) { const multilines = @@ -312,7 +314,7 @@ function genNodeListAsArray( } function genNodeList( - nodes: (string | CodegenNode | ChildNode[])[], + nodes: (string | CodegenNode | TemplateChildNode[])[], context: CodegenContext, multilines: boolean = false ) { @@ -375,6 +377,12 @@ function genNode(node: CodegenNode, context: CodegenContext) { case NodeTypes.JS_SLOT_FUNCTION: genSlotFunction(node, context) break + case NodeTypes.JS_SEQUENCE_EXPRESSION: + genSequenceExpression(node, context) + break + case NodeTypes.JS_CONDITIONAL_EXPRESSION: + genConditionalExpression(node, context) + break /* istanbul ignore next */ default: if (__DEV__) { @@ -593,3 +601,37 @@ function genSlotFunction( // pre-normalized slots should always return arrays genNodeListAsArray(node.returns, context) } + +function genConditionalExpression( + node: ConditionalExpression, + context: CodegenContext +) { + const { test, consequent, alternate } = node + const { push, indent, deindent, newline } = context + if (test.type === NodeTypes.SIMPLE_EXPRESSION) { + const needsQuote = !isSimpleIdentifier(test.content) + needsQuote && push(`(`) + genExpression(test, context) + needsQuote && push(`)`) + } else { + genCompoundExpression(test, context) + } + indent() + context.indentLevel++ + push(`? `) + genNode(consequent, context) + context.indentLevel-- + newline() + push(`: `) + genNode(alternate, context) + deindent(true /* without newline */) +} + +function genSequenceExpression( + node: SequenceExpression, + context: CodegenContext +) { + context.push(`(`) + genNodeList(node.expressions, context) + context.push(`)`) +} diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 128f70034..bc4f5258f 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -23,7 +23,7 @@ import { RootNode, SourceLocation, TextNode, - ChildNode, + TemplateChildNode, InterpolationNode } from './ast' @@ -115,15 +115,15 @@ function parseChildren( context: ParserContext, mode: TextModes, ancestors: ElementNode[] -): ChildNode[] { +): TemplateChildNode[] { const parent = last(ancestors) const ns = parent ? parent.ns : Namespaces.HTML - const nodes: ChildNode[] = [] + const nodes: TemplateChildNode[] = [] while (!isEnd(context, mode, ancestors)) { __DEV__ && assert(context.source.length > 0) const s = context.source - let node: ChildNode | ChildNode[] | undefined = undefined + let node: TemplateChildNode | TemplateChildNode[] | undefined = undefined if (startsWith(s, context.options.delimiters[0])) { // '{{' @@ -197,8 +197,8 @@ function parseChildren( function pushNode( context: ParserContext, - nodes: ChildNode[], - node: ChildNode + nodes: TemplateChildNode[], + node: TemplateChildNode ): void { // ignore comments in production /* istanbul ignore next */ @@ -234,7 +234,7 @@ function pushNode( function parseCDATA( context: ParserContext, ancestors: ElementNode[] -): ChildNode[] { +): TemplateChildNode[] { __DEV__ && assert(last(ancestors) == null || last(ancestors)!.ns !== Namespaces.HTML) __DEV__ && assert(startsWith(context.source, ' void | (() => void) | (() => void)[] @@ -59,10 +59,10 @@ export interface TransformContext extends Required { identifiers: { [name: string]: number | undefined } parent: ParentNode | null childIndex: number - currentNode: RootNode | ChildNode | null + currentNode: RootNode | TemplateChildNode | null helper(name: string): string - replaceNode(node: ChildNode): void - removeNode(node?: ChildNode): void + replaceNode(node: TemplateChildNode): void + removeNode(node?: TemplateChildNode): void onNodeRemoved: () => void addIdentifiers(exp: ExpressionNode): void removeIdentifiers(exp: ExpressionNode): void @@ -207,7 +207,7 @@ export function traverseChildren( } export function traverseNode( - node: RootNode | ChildNode, + node: RootNode | TemplateChildNode, context: TransformContext ) { // apply transform plugins diff --git a/packages/compiler-core/src/transforms/optimizeText.ts b/packages/compiler-core/src/transforms/optimizeText.ts index 09e637eeb..93bd15b7f 100644 --- a/packages/compiler-core/src/transforms/optimizeText.ts +++ b/packages/compiler-core/src/transforms/optimizeText.ts @@ -1,13 +1,15 @@ import { NodeTransform } from '../transform' import { NodeTypes, - ChildNode, + TemplateChildNode, TextNode, InterpolationNode, CompoundExpressionNode } from '../ast' -const isText = (node: ChildNode): node is TextNode | InterpolationNode => +const isText = ( + node: TemplateChildNode +): node is TextNode | InterpolationNode => node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT // Merge adjacent text nodes and expressions into a single expression diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index eb9fd12bb..92ab4a6b6 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -10,7 +10,7 @@ import { ElementTypes, ExpressionNode, Property, - ChildNode, + TemplateChildNode, SourceLocation } from '../ast' import { TransformContext, NodeTransform } from '../transform' @@ -67,7 +67,7 @@ export function buildSlots( // 2. Iterate through children and check for template slots //