From 152e3ebc4434aec02a12516353af501d267ae142 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 10 Aug 2015 15:50:38 -0700 Subject: [PATCH] Moved initial transform flag aggregation to binder --- src/compiler/binder.ts | 37 ++- src/compiler/factory.ts | 2 +- src/compiler/parser.ts | 18 +- src/compiler/transform.ts | 457 +++++++++++++++++++------------------- 4 files changed, 274 insertions(+), 240 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index c99ba2c175e..2c432c6890e 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -97,6 +97,8 @@ namespace ts { let symbolCount = 0; let Symbol = objectAllocator.getSymbolConstructor(); let classifiableNames: Map = {}; + let transformFlags: TransformFlags; + let skipTransformFlagAggregation: boolean; if (!file.locals) { bind(file); @@ -782,16 +784,45 @@ namespace ts { // However, not all symbols will end up in any of these tables. 'Anonymous' symbols // (like TypeLiterals for example) will not be put in any table. bindWorker(node); - + // Then we recurse into the children of the node to bind them as well. For certain // symbols we do specialized work when we recurse. For example, we'll keep track of // the current 'container' node when it changes. This helps us know which symbol table // a local should go into for example. - bindChildren(node); + aggregateTransformFlagsIfNeededAnd(bindChildren, node); inStrictMode = savedInStrictMode; } - + + function aggregateTransformFlagsIfNeededAnd(cbNode: (node: Node) => T, node: Node): T { + return node.transformFlags !== undefined + ? skipTransformFlagAggregationAnd(cbNode, node) + : aggregateTransformFlagsAnd(cbNode, node); + } + + function skipTransformFlagAggregationAnd(cbNode: (node: Node) => T, node: Node): T { + if (!skipTransformFlagAggregation) { + skipTransformFlagAggregation = true; + let result = cbNode(node); + skipTransformFlagAggregation = false; + return result; + } + + return cbNode(node); + } + + function aggregateTransformFlagsAnd(cbNode: (node: Node) => T, node: Node): T { + if (!skipTransformFlagAggregation) { + let saveTransformFlags = transformFlags; + transformFlags = 0; + let result = cbNode(node); + transformFlags = saveTransformFlags | transform.computeTransformFlagsForNode(node, transformFlags); + return result; + } + + return cbNode(node); + } + function updateStrictMode(node: Node) { switch (node.kind) { case SyntaxKind.SourceFile: diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index b2605b11dd5..9613aaa788a 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -10,7 +10,7 @@ namespace ts { export function createNode(kind: SyntaxKind): T { return factory.createNode(kind); } - + // @internal export namespace factory { export function setNodeFlags(node: T, flags: NodeFlags): T { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index cf61d36e96b..c69cc5a2542 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -997,8 +997,6 @@ namespace ts { node.parserContextFlags |= ParserContextFlags.ThisNodeHasError; } - transform.aggregateTransformFlags(node); - return node; } @@ -1945,6 +1943,9 @@ namespace ts { } } + // Types are always TypeScript only, so we can shortcut transform flag aggregation by + // explicitly marking this node as TypeScript + node.transformFlags = TransformFlags.ThisNodeIsTypeScript; return finishNode(node); } @@ -1972,6 +1973,12 @@ namespace ts { if (modifiers) { node.flags |= modifiers.flags; node.modifiers = modifiers; + + if (node.flags & NodeFlags.Ambient) { + // Ambient declarations are always TypeScript only, so we can shortcut transform flag aggregation by + // explicitly marking this node as TypeScript + node.transformFlags = TransformFlags.ThisNodeIsTypeScript; + } } } @@ -2465,7 +2472,12 @@ namespace ts { function parseType(): TypeNode { // The rules about 'yield' only apply to actual code/expression contexts. They don't // apply to 'type' contexts. So we disable these parameters here before moving on. - return doOutsideOfContext(ParserContextFlags.TypeExcludesFlags, parseTypeWorker); + let node = doOutsideOfContext(ParserContextFlags.TypeExcludesFlags, parseTypeWorker); + + // Types are always TypeScript only, so we can shortcut transform flag aggregation by + // explicitly marking this node as TypeScript + node.transformFlags = TransformFlags.ThisNodeIsTypeScript; + return node; } function parseTypeWorker(): TypeNode { diff --git a/src/compiler/transform.ts b/src/compiler/transform.ts index 568cf1d839e..77f4289e6e9 100644 --- a/src/compiler/transform.ts +++ b/src/compiler/transform.ts @@ -23,34 +23,57 @@ namespace ts.transform { return; } - transformFlags = node.transformFlags; - if (transformFlags === undefined) { - transformFlags = 0; - aggregateTransformFlagsForNode(node); - node.transformFlags = transformFlags; - } + aggregateTransformFlagsForThisNodeAndSubtree(node); + } + + function aggregateTransformFlagsForThisNodeAndSubtree(node: Node) { + if (node.transformFlags === undefined) { + if (node.flags & NodeFlags.Ambient) { + // Ambient nodes are marked as TypeScript early to prevent an unnecessary walk of the tree + return node.transformFlags = TransformFlags.ThisNodeIsTypeScript; + } - transformFlags &= ~(TransformFlags.NodeExcludes | node.excludeTransformFlags); + let transformFlagsOfChildren = aggregateTransformFlagsOfChildren(node); + return computeTransformFlagsForNode(node, transformFlagsOfChildren); + } + + return node.transformFlags & ~node.excludeTransformFlags; + } + + function aggregateTransformFlagsOfChildren(node: Node) { + let saveTransformFlags = transformFlags; + transformFlags = 0; + + forEachChild(node, aggregateTransformFlagsForChildNode); + + let transformFlagsOfChildren = transformFlags; + transformFlags = saveTransformFlags; + + return transformFlagsOfChildren & ~TransformFlags.NodeExcludes; } function aggregateTransformFlagsForChildNode(child: Node) { - let saveTransformFlags = transformFlags; - transformFlags = 0; - aggregateTransformFlags(child); - transformFlags = saveTransformFlags | transformFlags; + transformFlags |= aggregateTransformFlagsForThisNodeAndSubtree(child); } - function aggregateTransformFlagsForNode(node: Node) { - forEachChild(node, aggregateTransformFlagsForChildNode); - + /** + * Computes the transform flags for a node, given the transform flags of its subtree + * @param node The node to analyze + * @param subtreeFlags Transform flags computed for this node's subtree + */ + export function computeTransformFlagsForNode(node: Node, subtreeFlags: TransformFlags) { let start = new Date().getTime(); - - if (node.flags & NodeFlags.Ambient) { - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - } + computeTransformFlagsForNodeWorker(node, subtreeFlags); + aggregateTime += new Date().getTime() - start; + return node.transformFlags & ~node.excludeTransformFlags; + } + + function computeTransformFlagsForNodeWorker(node: Node, subtreeFlags: TransformFlags) { + let transformFlags: TransformFlags; // Mark transformations needed for each node - switch (node.kind) { + let kind = node.kind; + switch (kind) { case SyntaxKind.PublicKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: @@ -58,235 +81,62 @@ namespace ts.transform { case SyntaxKind.DeclareKeyword: case SyntaxKind.AsyncKeyword: case SyntaxKind.ConstKeyword: - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - break; - - case SyntaxKind.ModuleDeclaration: - node.excludeTransformFlags = TransformFlags.ModuleScopeExcludes; - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - break; + return node.transformFlags = TransformFlags.ThisNodeIsTypeScript; + + case SyntaxKind.AwaitExpression: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ImportEqualsDeclaration: + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsTypeScript; + case SyntaxKind.ImportDeclaration: + case SyntaxKind.ExportDeclaration: case SyntaxKind.ComputedPropertyName: case SyntaxKind.TemplateExpression: case SyntaxKind.NoSubstitutionTemplateLiteral: - transformFlags |= TransformFlags.ThisNodeIsES6; - break; - - // case SyntaxKind.NumericLiteral: - // let sourceFile = getSourceFileOfNode(node); - // let firstChar = sourceFile.text.charCodeAt(node.pos); - // if (firstChar === CharacterCodes.b - // || firstChar === CharacterCodes.B - // || firstChar === CharacterCodes.o - // || firstChar === CharacterCodes.O) { - // markTransform(TransformFlags.ThisNodeIsES6); - // } - // break; - - case SyntaxKind.Parameter: - if ((node).questionToken) { - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - } - if ((node).flags & NodeFlags.AccessibilityModifier) { - transformFlags |= TransformFlags.ThisNodeIsTypeScriptClassSyntaxExtension; - } - if ((node).initializer) { - transformFlags |= TransformFlags.ThisNodeIsES6ParameterInitializer; - } - if ((node).dotDotDotToken) { - transformFlags |= TransformFlags.ThisNodeIsES6RestParameter; - } - break; - - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.CallExpression: - node.excludeTransformFlags = TransformFlags.CallOrArrayLiteralExcludes; - break; + case SyntaxKind.TaggedTemplateExpression: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.ForOfStatement: + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsES6; case SyntaxKind.YieldExpression: - transformFlags |= TransformFlags.ThisNodeIsES6Yield; - break; - - case SyntaxKind.AwaitExpression: - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - break; - - case SyntaxKind.BinaryExpression: - if (isDestructuringAssignment(node)) { - transformFlags |= TransformFlags.ThisNodeIsES6; - } - - break; - - case SyntaxKind.TaggedTemplateExpression: - transformFlags |= TransformFlags.ThisNodeIsES6; - break; + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsES6Yield; case SyntaxKind.ThisKeyword: - transformFlags |= TransformFlags.ThisNodeIsThisKeyword; - break; + return node.transformFlags = TransformFlags.ThisNodeIsThisKeyword; case SyntaxKind.SpreadElementExpression: - transformFlags |= TransformFlags.ThisNodeIsES6SpreadElement; - break; - - case SyntaxKind.ShorthandPropertyAssignment: - transformFlags |= TransformFlags.ThisNodeIsES6; - break; - - case SyntaxKind.ArrowFunction: - node.excludeTransformFlags = TransformFlags.ArrowFunctionScopeExcludes; - transformFlags |= TransformFlags.ThisNodeIsES6; - if (transformFlags & TransformFlags.SubtreeContainsLexicalThis) { - transformFlags |= TransformFlags.ThisNodeCapturesLexicalThis; - } - if (node.flags & NodeFlags.Async) { - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - } - break; - - case SyntaxKind.FunctionExpression: - node.excludeTransformFlags = TransformFlags.FunctionScopeExcludes; - if ((node).asteriskToken - || transformFlags & TransformFlags.SubtreeContainsES6ParameterOrCapturedThis) { - transformFlags |= TransformFlags.ThisNodeIsES6; - } - if (node.flags & NodeFlags.Async) { - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - } - break; - - case SyntaxKind.FunctionDeclaration: - node.excludeTransformFlags = TransformFlags.FunctionScopeExcludes; - transformFlags |= TransformFlags.ThisNodeIsHoistedDeclaration; - if (!(node).body - || node.flags & NodeFlags.Async) { - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - } - if ((node).asteriskToken - || node.flags & NodeFlags.Export - || transformFlags & TransformFlags.SubtreeContainsES6ParameterOrCapturedThis) { - transformFlags |= TransformFlags.ThisNodeIsES6; - } - break; - - case SyntaxKind.ForOfStatement: - transformFlags |= TransformFlags.ThisNodeIsES6; - break; + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsES6SpreadElement; case SyntaxKind.BreakStatement: case SyntaxKind.ContinueStatement: case SyntaxKind.ReturnStatement: - transformFlags |= TransformFlags.ThisNodeIsCompletionStatement; - break; + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsCompletionStatement; case SyntaxKind.ObjectBindingPattern: case SyntaxKind.ArrayBindingPattern: - transformFlags |= TransformFlags.ThisNodeIsES6VariableBindingPattern; - break; - - case SyntaxKind.VariableDeclarationList: - transformFlags |= TransformFlags.ThisNodeIsHoistedDeclaration; - if (node.flags & (NodeFlags.Let | NodeFlags.Const)) { - transformFlags |= TransformFlags.ThisNodeIsES6LetOrConst; - } - break; - - case SyntaxKind.VariableStatement: - if (node.flags & NodeFlags.Export) { - transformFlags |= TransformFlags.ThisNodeIsES6; - } - break; + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsES6VariableBindingPattern; case SyntaxKind.Decorator: - transformFlags |= TransformFlags.ThisNodeIsTypeScriptDecorator; - break; + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsTypeScriptDecorator; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - node.excludeTransformFlags = TransformFlags.ClassScopeExcludes; - transformFlags |= TransformFlags.ThisNodeIsES6; - if (transformFlags & TransformFlags.ContainsTypeScriptClassSyntaxExtension) { - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - } - break; - - case SyntaxKind.HeritageClause: - if ((node).token !== SyntaxKind.ExtendsKeyword) { - transformFlags |= TransformFlags.ThisNodeIsTypeScriptClassSyntaxExtension; - } - break; - - case SyntaxKind.ExpressionWithTypeArguments: - if ((node).typeArguments) { - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - } - break; + case SyntaxKind.ModuleDeclaration: + return (node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsTypeScript) + & ~(node.excludeTransformFlags = TransformFlags.ModuleScopeExcludes); + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.CallExpression: + return (node.transformFlags = subtreeFlags) + & ~(node.excludeTransformFlags = TransformFlags.CallOrArrayLiteralExcludes); + case SyntaxKind.PropertyDeclaration: - transformFlags |= TransformFlags.ThisNodeIsTypeScriptClassSyntaxExtension; - break; + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsTypeScriptClassSyntaxExtension; - case SyntaxKind.Constructor: - node.excludeTransformFlags = TransformFlags.FunctionScopeExcludes; - if (!(node).body) { - transformFlags |= TransformFlags.ThisNodeIsTypeScript; + case SyntaxKind.BinaryExpression: + if (isDestructuringAssignment(node)) { + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsES6; } break; - case SyntaxKind.MethodDeclaration: - node.excludeTransformFlags = TransformFlags.FunctionScopeExcludes; - if (!(node).body) { - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - } - - transformFlags |= TransformFlags.ThisNodeIsES6; - - if (transformFlags & TransformFlags.ContainsTypeScriptDecorator - && isComputedPropertyName((node).name)) { - transformFlags |= TransformFlags.ThisNodeIsTypeScriptClassSyntaxExtension; - } - - if (node.flags & NodeFlags.Async) { - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - } - - break; - - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - node.excludeTransformFlags = TransformFlags.FunctionScopeExcludes; - if (transformFlags & TransformFlags.ContainsTypeScriptDecorator - && isComputedPropertyName((node).name)) { - transformFlags |= TransformFlags.ThisNodeIsTypeScriptClassSyntaxExtension; - } - break; - - case SyntaxKind.EnumDeclaration: - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - break; - - case SyntaxKind.ImportEqualsDeclaration: - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - break; - - case SyntaxKind.ImportDeclaration: - transformFlags |= TransformFlags.ThisNodeIsES6; - break; - - case SyntaxKind.ExportAssignment: - if ((node).isExportEquals) { - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - } - else { - transformFlags |= TransformFlags.ThisNodeIsES6; - } - break; - - case SyntaxKind.ExportDeclaration: - transformFlags |= TransformFlags.ThisNodeIsES6; - break; - case SyntaxKind.AnyKeyword: case SyntaxKind.NumberKeyword: case SyntaxKind.StringKeyword: @@ -298,22 +148,163 @@ namespace ts.transform { case SyntaxKind.IndexSignature: case SyntaxKind.MethodSignature: case SyntaxKind.PropertySignature: - transformFlags |= TransformFlags.ThisNodeIsTypeScript; - node.excludeTransformFlags = TransformFlags.TypeExcludes; - break; - - default: - if (SyntaxKind.FirstTypeNode <= node.kind && node.kind <= SyntaxKind.LastTypeNode) { + return (node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsTypeScript) + & ~(node.excludeTransformFlags = TransformFlags.TypeExcludes); + + case SyntaxKind.Parameter: + if ((node).questionToken) { transformFlags |= TransformFlags.ThisNodeIsTypeScript; - node.excludeTransformFlags = TransformFlags.TypeExcludes; + } + + if ((node).flags & NodeFlags.AccessibilityModifier) { + transformFlags |= TransformFlags.ThisNodeIsTypeScriptClassSyntaxExtension; + } + + if ((node).initializer) { + transformFlags |= TransformFlags.ThisNodeIsES6ParameterInitializer; + } + + if ((node).dotDotDotToken) { + transformFlags |= TransformFlags.ThisNodeIsES6RestParameter; + } + + return node.transformFlags = subtreeFlags | transformFlags; + + case SyntaxKind.ArrowFunction: + transformFlags = TransformFlags.ThisNodeIsES6; + if (subtreeFlags & TransformFlags.SubtreeContainsLexicalThis) { + transformFlags |= TransformFlags.ThisNodeCapturesLexicalThis; + } + if (node.flags & NodeFlags.Async) { + transformFlags |= TransformFlags.ThisNodeIsTypeScript; + } + + return (node.transformFlags = subtreeFlags | transformFlags) + & ~(node.excludeTransformFlags = TransformFlags.ArrowFunctionScopeExcludes); + + case SyntaxKind.FunctionExpression: + if ((node).asteriskToken + || transformFlags & TransformFlags.SubtreeContainsES6ParameterOrCapturedThis) { + transformFlags |= TransformFlags.ThisNodeIsES6; + } + + if (node.flags & NodeFlags.Async) { + transformFlags |= TransformFlags.ThisNodeIsTypeScript; + } + + return (node.transformFlags = subtreeFlags | transformFlags) + & ~(node.excludeTransformFlags = TransformFlags.FunctionScopeExcludes); + + case SyntaxKind.FunctionDeclaration: + if (!(node).body) { + return node.transformFlags = TransformFlags.ThisNodeIsTypeScript; + } + + transformFlags = TransformFlags.ThisNodeIsHoistedDeclaration; + if (node.flags & NodeFlags.Async) { + transformFlags |= TransformFlags.ThisNodeIsTypeScript; + } + + if ((node).asteriskToken + || node.flags & NodeFlags.Export + || subtreeFlags & TransformFlags.SubtreeContainsES6ParameterOrCapturedThis) { + transformFlags |= TransformFlags.ThisNodeIsES6; + } + + return (node.transformFlags = subtreeFlags | transformFlags) + & ~(node.excludeTransformFlags = TransformFlags.FunctionScopeExcludes); + + case SyntaxKind.VariableDeclarationList: + transformFlags = TransformFlags.ThisNodeIsHoistedDeclaration; + if (node.flags & (NodeFlags.Let | NodeFlags.Const)) { + transformFlags |= TransformFlags.ThisNodeIsES6LetOrConst; + } + + return node.transformFlags = subtreeFlags | transformFlags; + + case SyntaxKind.VariableStatement: + if (node.flags & NodeFlags.Export) { + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsES6; } break; - - } - node.transformFlags = transformFlags; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + transformFlags = TransformFlags.ThisNodeIsES6; + if (subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntaxExtension) { + transformFlags |= TransformFlags.ThisNodeIsTypeScript; + } + + return (node.transformFlags = subtreeFlags | transformFlags) + & ~(node.excludeTransformFlags = TransformFlags.ClassScopeExcludes); + + case SyntaxKind.HeritageClause: + if ((node).token !== SyntaxKind.ExtendsKeyword) { + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsTypeScriptClassSyntaxExtension; + } + + break; + + case SyntaxKind.ExpressionWithTypeArguments: + if ((node).typeArguments) { + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsTypeScript; + } + + break; + + case SyntaxKind.Constructor: + if (!(node).body) { + return node.transformFlags = TransformFlags.ThisNodeIsTypeScript; + } + + return (node.transformFlags = subtreeFlags | transformFlags) + & ~(node.excludeTransformFlags = TransformFlags.FunctionScopeExcludes); + + case SyntaxKind.MethodDeclaration: + if (!(node).body) { + return node.transformFlags = TransformFlags.ThisNodeIsTypeScript; + } + + transformFlags = TransformFlags.ThisNodeIsES6; + if (node.flags & NodeFlags.Async) { + transformFlags |= TransformFlags.ThisNodeIsTypeScript; + } + + if (subtreeFlags & TransformFlags.ContainsTypeScriptDecorator + && (node).name.kind === SyntaxKind.ComputedPropertyName) { + transformFlags |= TransformFlags.ThisNodeIsTypeScriptClassSyntaxExtension; + } + + return (node.transformFlags = subtreeFlags | transformFlags) + & ~(node.excludeTransformFlags = TransformFlags.FunctionScopeExcludes); + + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + if (subtreeFlags & TransformFlags.ContainsTypeScriptDecorator + && (node).name.kind === SyntaxKind.ComputedPropertyName) { + transformFlags |= TransformFlags.ThisNodeIsTypeScriptClassSyntaxExtension; + } + + return (node.transformFlags = subtreeFlags | transformFlags) + & ~(node.excludeTransformFlags = TransformFlags.FunctionScopeExcludes); + + case SyntaxKind.ExportAssignment: + if ((node).isExportEquals) { + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsTypeScript; + } + + return node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsES6; + + default: + if (SyntaxKind.FirstTypeNode <= kind && kind <= SyntaxKind.LastTypeNode) { + return (node.transformFlags = subtreeFlags | TransformFlags.ThisNodeIsTypeScript) + & ~(node.excludeTransformFlags = TransformFlags.TypeExcludes); + } + + break; + } - aggregateTime += new Date().getTime() - start; + return node.transformFlags = subtreeFlags; } export class VisitorContext {