X Tutup
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/compiler/src/template/pipeline/ir/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ export enum OpKind {
*/
ElementEnd,

/**
* An operation to begin an `ng-container`.
*/
ContainerStart,

/**
* An operation for an `ng-container` with no children.
*/
Container,

/**
* An operation to end an `ng-container`.
*/
ContainerEnd,

/**
* An operation to render a text node.
*/
Expand Down
6 changes: 6 additions & 0 deletions packages/compiler/src/template/pipeline/ir/src/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,9 @@ export function transformExpressionsInOp(
case OpKind.Element:
case OpKind.ElementStart:
case OpKind.ElementEnd:
case OpKind.Container:
case OpKind.ContainerStart:
case OpKind.ContainerEnd:
case OpKind.Template:
case OpKind.Text:
case OpKind.Advance:
Expand All @@ -341,6 +344,9 @@ export function transformExpressionsInExpression(
expr.rhs = transformExpressionsInExpression(expr.rhs, transform, flags);
} else if (expr instanceof o.ReadPropExpr) {
expr.receiver = transformExpressionsInExpression(expr.receiver, transform, flags);
} else if (expr instanceof o.ReadKeyExpr) {
expr.receiver = transformExpressionsInExpression(expr.receiver, transform, flags);
expr.index = transformExpressionsInExpression(expr.index, transform, flags);
} else if (expr instanceof o.InvokeFunctionExpr) {
expr.fn = transformExpressionsInExpression(expr.fn, transform, flags);
for (let i = 0; i < expr.args.length; i++) {
Expand Down
51 changes: 42 additions & 9 deletions packages/compiler/src/template/pipeline/ir/src/ops/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import type {UpdateOp} from './update';
/**
* An operation usable on the creation side of the IR.
*/
export type CreateOp = ListEndOp<CreateOp>|StatementOp<CreateOp>|ElementOp|ElementStartOp|
ElementEndOp|TemplateOp|TextOp|ListenerOp|VariableOp<CreateOp>;
export type CreateOp =
ListEndOp<CreateOp>|StatementOp<CreateOp>|ElementOp|ElementStartOp|ElementEndOp|ContainerOp|
ContainerStartOp|ContainerEndOp|TemplateOp|TextOp|ListenerOp|VariableOp<CreateOp>;

/**
* Representation of a local reference on an element.
Expand All @@ -39,8 +40,8 @@ export interface LocalRef {
* Base interface for `Element`, `ElementStart`, and `Template` operations, containing common fields
* used to represent their element-like nature.
*/
export interface ElementOpBase extends Op<CreateOp>, ConsumesSlotOpTrait {
kind: OpKind.Element|OpKind.ElementStart|OpKind.Template;
export interface ElementOrContainerOpBase extends Op<CreateOp>, ConsumesSlotOpTrait {
kind: OpKind.Element|OpKind.ElementStart|OpKind.Container|OpKind.ContainerStart|OpKind.Template;

/**
* `XrefId` allocated for this element.
Expand All @@ -49,11 +50,6 @@ export interface ElementOpBase extends Op<CreateOp>, ConsumesSlotOpTrait {
*/
xref: XrefId;

/**
* The HTML tag name for this element.
*/
tag: string;

/**
* Attributes of various kinds on this element.
*
Expand All @@ -76,6 +72,15 @@ export interface ElementOpBase extends Op<CreateOp>, ConsumesSlotOpTrait {
localRefs: LocalRef[]|ConstIndex|null;
}

export interface ElementOpBase extends ElementOrContainerOpBase {
kind: OpKind.Element|OpKind.ElementStart|OpKind.Template;

/**
* The HTML tag name for this element.
*/
tag: string;
}

/**
* Logical operation representing the start of an element in the creation IR.
*/
Expand Down Expand Up @@ -166,6 +171,34 @@ export function createElementEndOp(xref: XrefId): ElementEndOp {
};
}

/**
* Logical operation representing the start of a container in the creation IR.
*/
export interface ContainerStartOp extends ElementOrContainerOpBase {
kind: OpKind.ContainerStart;
}

/**
* Logical operation representing an empty container in the creation IR.
*/
export interface ContainerOp extends ElementOrContainerOpBase {
kind: OpKind.Container;
}

/**
* Logical operation representing the end of a container structure in the creation IR.
*
* Pairs with an `ContainerStart` operation.
*/
export interface ContainerEndOp extends Op<CreateOp> {
kind: OpKind.ContainerEnd;

/**
* The `XrefId` of the element declared via `ContainerStart`.
*/
xref: XrefId;
}

/**
* Logical operation representing a text node in the creation IR.
*/
Expand Down
29 changes: 29 additions & 0 deletions packages/compiler/src/template/pipeline/src/conversion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import * as o from '../../../output/output_ast';

export const BINARY_OPERATORS = new Map([
['&&', o.BinaryOperator.And],
['>', o.BinaryOperator.Bigger],
['>=', o.BinaryOperator.BiggerEquals],
['&', o.BinaryOperator.BitwiseAnd],
['/', o.BinaryOperator.Divide],
['==', o.BinaryOperator.Equals],
['===', o.BinaryOperator.Identical],
['<', o.BinaryOperator.Lower],
['<=', o.BinaryOperator.LowerEquals],
['-', o.BinaryOperator.Minus],
['%', o.BinaryOperator.Modulo],
['*', o.BinaryOperator.Multiply],
['!=', o.BinaryOperator.NotEquals],
['!==', o.BinaryOperator.NotIdentical],
['??', o.BinaryOperator.NullishCoalesce],
['||', o.BinaryOperator.Or],
['+', o.BinaryOperator.Plus],
]);
6 changes: 5 additions & 1 deletion packages/compiler/src/template/pipeline/src/emit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,28 @@ import {phaseResolveContexts} from './phases/resolve_contexts';
import {phaseVariableOptimization} from './phases/variable_optimization';
import {phaseChaining} from './phases/chaining';
import {phaseMergeNextContext} from './phases/next_context_merging';
import {phaseNgContainer} from './phases/ng_container';
import {phaseSaveRestoreView} from './phases/save_restore_view';

/**
* Run all transformation phases in the correct order against a `ComponentCompilation`. After this
* processing, the compilation should be in a state where it can be emitted via `emitTemplateFn`.s
*/
export function transformTemplate(cpl: ComponentCompilation): void {
phaseGenerateVariables(cpl);
phaseSaveRestoreView(cpl);
phaseResolveNames(cpl);
phaseResolveContexts(cpl);
phaseLocalRefs(cpl);
phaseEmptyElements(cpl);
phaseConstCollection(cpl);
phaseSlotAllocation(cpl);
phaseVarCounting(cpl);
phaseGenerateAdvance(cpl);
phaseNaming(cpl);
phaseVariableOptimization(cpl, {conservative: true});
phaseMergeNextContext(cpl);
phaseNgContainer(cpl);
phaseEmptyElements(cpl);
phaseReify(cpl);
phaseChaining(cpl);
}
Expand Down
54 changes: 46 additions & 8 deletions packages/compiler/src/template/pipeline/src/ingest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as t from '../../../render3/r3_ast';
import * as ir from '../ir';

import {ComponentCompilation, ViewCompilation} from './compilation';
import {BINARY_OPERATORS} from './conversion';

/**
* Process a template AST and convert it into a `ComponentCompilation` in the intermediate
Expand Down Expand Up @@ -131,8 +132,19 @@ function convertAst(ast: e.AST, cpl: ComponentCompilation): o.Expression {
}
} else if (ast instanceof e.LiteralPrimitive) {
return o.literal(ast.value);
} else if (ast instanceof e.Binary) {
const operator = BINARY_OPERATORS.get(ast.operation);
if (operator === undefined) {
throw new Error(`AssertionError: unknown binary operator ${ast.operation}`);
}
return new o.BinaryOperatorExpr(
operator, convertAst(ast.left, cpl), convertAst(ast.right, cpl));
} else if (ast instanceof e.ThisReceiver) {
return new ir.ContextExpr(cpl.root.xref);
} else if (ast instanceof e.KeyedRead) {
return new o.ReadKeyExpr(convertAst(ast.receiver, cpl), convertAst(ast.key, cpl));
} else if (ast instanceof e.Chain) {
throw new Error(`AssertionError: Chain in unknown context`);
} else {
throw new Error(`Unhandled expression type: ${ast.constructor.name}`);
}
Expand All @@ -154,7 +166,6 @@ function ingestAttributes(op: ir.ElementOpBase, element: t.Element|t.Template):
for (const output of element.outputs) {
op.attributes.add(ir.ElementAttributeKind.Binding, output.name, null);
}

if (element instanceof t.Template) {
for (const attr of element.templateAttrs) {
// TODO: what do we do about the value here?
Expand All @@ -170,12 +181,15 @@ function ingestAttributes(op: ir.ElementOpBase, element: t.Element|t.Template):
function ingestBindings(
view: ViewCompilation, op: ir.ElementOpBase, element: t.Element|t.Template): void {
if (element instanceof t.Template) {
for (const attr of element.templateAttrs) {
if (typeof attr.value === 'string') {
// TODO: do we need to handle static attribute bindings here?
} else {
view.update.push(ir.createPropertyOp(op.xref, attr.name, convertAst(attr.value, view.tpl)));
// TODO: Are ng-template inputs handled differently from element inputs?
// <ng-template dir [foo]="...">
// <item-cmp *ngFor="let item of items" [item]="item">
for (const input of [...element.templateAttrs, ...element.inputs]) {
if (!(input instanceof t.BoundAttribute)) {
continue;
}

view.update.push(ir.createPropertyOp(op.xref, input.name, convertAst(input.value, view.tpl)));
}
} else {
for (const input of element.inputs) {
Expand All @@ -184,8 +198,32 @@ function ingestBindings(

for (const output of element.outputs) {
const listenerOp = ir.createListenerOp(op.xref, output.name, op.tag);
listenerOp.handlerOps.push(
ir.createStatementOp(new o.ReturnStatement(convertAst(output.handler, view.tpl))));
// if output.handler is a chain, then push each statement from the chain separately, and
// return the last one?
let inputExprs: e.AST[];
let handler: e.AST = output.handler;
if (handler instanceof e.ASTWithSource) {
handler = handler.ast;
}

if (handler instanceof e.Chain) {
inputExprs = handler.expressions;
} else {
inputExprs = [handler];
}

if (inputExprs.length === 0) {
throw new Error('Expected listener to have non-empty expression list.');
}

const expressions = inputExprs.map(expr => convertAst(expr, view.tpl));
const returnExpr = expressions.pop()!;

for (const expr of expressions) {
const stmtOp = ir.createStatementOp<ir.UpdateOp>(new o.ExpressionStatement(expr));
listenerOp.handlerOps.push(stmtOp);
}
listenerOp.handlerOps.push(ir.createStatementOp(new o.ReturnStatement(returnExpr)));
view.create.push(listenerOp);
}
}
Expand Down
33 changes: 24 additions & 9 deletions packages/compiler/src/template/pipeline/src/instruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,21 @@ import * as ir from '../ir';

export function element(
slot: number, tag: string, constIndex: number|null, localRefIndex: number|null): ir.CreateOp {
return elementStartBase(Identifiers.element, slot, tag, constIndex, localRefIndex);
return elementOrContainerBase(Identifiers.element, slot, tag, constIndex, localRefIndex);
}


export function elementStart(
slot: number, tag: string, constIndex: number|null, localRefIndex: number|null): ir.CreateOp {
return elementStartBase(Identifiers.elementStart, slot, tag, constIndex, localRefIndex);
return elementOrContainerBase(Identifiers.elementStart, slot, tag, constIndex, localRefIndex);
}

function elementStartBase(
instruction: o.ExternalReference, slot: number, tag: string, constIndex: number|null,
function elementOrContainerBase(
instruction: o.ExternalReference, slot: number, tag: string|null, constIndex: number|null,
localRefIndex: number|null): ir.CreateOp {
const args: o.Expression[] = [
o.literal(slot),
o.literal(tag),
];
const args: o.Expression[] = [o.literal(slot)];
if (tag !== null) {
args.push(o.literal(tag));
}
if (localRefIndex !== null) {
args.push(
o.literal(constIndex), // might be null, but that's okay.
Expand All @@ -48,6 +47,22 @@ export function elementEnd(): ir.CreateOp {
return call(Identifiers.elementEnd, []);
}

export function elementContainerStart(
slot: number, constIndex: number|null, localRefIndex: number|null): ir.CreateOp {
return elementOrContainerBase(
Identifiers.elementContainerStart, slot, /* tag */ null, constIndex, localRefIndex);
}

export function elementContainer(
slot: number, constIndex: number|null, localRefIndex: number|null): ir.CreateOp {
return elementOrContainerBase(
Identifiers.elementContainer, slot, /* tag */ null, constIndex, localRefIndex);
}

export function elementContainerEnd(): ir.CreateOp {
return call(Identifiers.elementContainerEnd, []);
}

export function template(
slot: number, templateFnRef: o.Expression, decls: number, vars: number, tag: string,
constIndex: number): ir.CreateOp {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ const CHAINABLE = new Set([
R3.elementStart,
R3.elementEnd,
R3.property,
R3.elementContainerStart,
R3.elementContainerEnd,
R3.elementContainer,
]);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,29 @@
import * as ir from '../../ir';
import {ComponentCompilation} from '../compilation';

const REPLACEMENTS = new Map<ir.OpKind, [ir.OpKind, ir.OpKind]>([
[ir.OpKind.ElementEnd, [ir.OpKind.ElementStart, ir.OpKind.Element]],
[ir.OpKind.ContainerEnd, [ir.OpKind.ContainerStart, ir.OpKind.Container]],
]);

/**
* Replace sequences of `ElementStart` followed by `ElementEnd` with a condensed `Element`
* instruction.
* Replace sequences of mergable elements (e.g. `ElementStart` and `ElementEnd`) with a consolidated
* element (e.g. `Element`).
*/
export function phaseEmptyElements(cpl: ComponentCompilation): void {
for (const [_, view] of cpl.views) {
for (const op of view.create) {
if (op.kind === ir.OpKind.ElementEnd && op.prev !== null &&
op.prev.kind === ir.OpKind.ElementStart) {
// Transmute the `ElementStart` instruction to `Element`. This is safe as they're designed
const opReplacements = REPLACEMENTS.get(op.kind);
if (opReplacements === undefined) {
continue;
}
const [startKind, mergedKind] = opReplacements;
if (op.prev !== null && op.prev.kind === startKind) {
// Transmute the start instruction to the merged version. This is safe as they're designed
// to be identical apart from the `kind`.
(op.prev as unknown as ir.ElementOp).kind = ir.OpKind.Element;
(op.prev as ir.Op<ir.CreateOp>).kind = mergedKind;

// Remove the `ElementEnd` instruction.
// Remove the end instruction.
ir.OpList.remove<ir.CreateOp>(op);
}
}
Expand Down
Loading
X Tutup