*/
function deduplicateTextBindings(job) {
const seen = new Map();
for (const unit of job.units) {
for (const op of unit.update.reversed()) {
if (op.kind === OpKind.Binding && op.isTextAttribute) {
const seenForElement = seen.get(op.target) || new Set();
if (seenForElement.has(op.name)) {
if (job.compatibility === CompatibilityMode.TemplateDefinitionBuilder) {
// For most duplicated attributes, TemplateDefinitionBuilder lists all of the values in
// the consts array. However, for style and class attributes it only keeps the last one.
// We replicate that behavior here since it has actual consequences for apps with
// duplicate class or style attrs.
if (op.name === 'style' || op.name === 'class') {
OpList.remove(op);
}
}
}
seenForElement.add(op.name);
seen.set(op.target, seenForElement);
}
}
}
}
/**
* @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.dev/license
*/
/**
* Defer instructions take a configuration array, which should be collected into the component
* consts. This phase finds the config options, and creates the corresponding const array.
*/
function configureDeferInstructions(job) {
for (const unit of job.units) {
for (const op of unit.create) {
if (op.kind !== OpKind.Defer) {
continue;
}
if (op.placeholderMinimumTime !== null) {
op.placeholderConfig = new ConstCollectedExpr(literalOrArrayLiteral([op.placeholderMinimumTime]));
}
if (op.loadingMinimumTime !== null || op.loadingAfterTime !== null) {
op.loadingConfig = new ConstCollectedExpr(literalOrArrayLiteral([op.loadingMinimumTime, op.loadingAfterTime]));
}
}
}
}
/**
* @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.dev/license
*/
/**
* Some `defer` conditions can reference other elements in the template, using their local reference
* names. However, the semantics are quite different from the normal local reference system: in
* particular, we need to look at local reference names in enclosing views. This phase resolves
* all such references to actual xrefs.
*/
function resolveDeferTargetNames(job) {
const scopes = new Map();
function getScopeForView(view) {
if (scopes.has(view.xref)) {
return scopes.get(view.xref);
}
const scope = new Scope$2();
for (const op of view.create) {
// add everything that can be referenced.
if (!isElementOrContainerOp(op) || op.localRefs === null) {
continue;
}
if (!Array.isArray(op.localRefs)) {
throw new Error('LocalRefs were already processed, but were needed to resolve defer targets.');
}
for (const ref of op.localRefs) {
if (ref.target !== '') {
continue;
}
scope.targets.set(ref.name, { xref: op.xref, slot: op.handle });
}
}
scopes.set(view.xref, scope);
return scope;
}
function resolveTrigger(deferOwnerView, op, placeholderView) {
switch (op.trigger.kind) {
case DeferTriggerKind.Idle:
case DeferTriggerKind.Immediate:
case DeferTriggerKind.Timer:
return;
case DeferTriggerKind.Hover:
case DeferTriggerKind.Interaction:
case DeferTriggerKind.Viewport:
if (op.trigger.targetName === null) {
// A `null` target name indicates we should default to the first element in the
// placeholder block.
if (placeholderView === null) {
throw new Error('defer on trigger with no target name must have a placeholder block');
}
const placeholder = job.views.get(placeholderView);
if (placeholder == undefined) {
throw new Error('AssertionError: could not find placeholder view for defer on trigger');
}
for (const placeholderOp of placeholder.create) {
if (hasConsumesSlotTrait(placeholderOp) &&
(isElementOrContainerOp(placeholderOp) ||
placeholderOp.kind === OpKind.Projection)) {
op.trigger.targetXref = placeholderOp.xref;
op.trigger.targetView = placeholderView;
op.trigger.targetSlotViewSteps = -1;
op.trigger.targetSlot = placeholderOp.handle;
return;
}
}
return;
}
let view = placeholderView !== null ? job.views.get(placeholderView) : deferOwnerView;
let step = placeholderView !== null ? -1 : 0;
while (view !== null) {
const scope = getScopeForView(view);
if (scope.targets.has(op.trigger.targetName)) {
const { xref, slot } = scope.targets.get(op.trigger.targetName);
op.trigger.targetXref = xref;
op.trigger.targetView = view.xref;
op.trigger.targetSlotViewSteps = step;
op.trigger.targetSlot = slot;
return;
}
view = view.parent !== null ? job.views.get(view.parent) : null;
step++;
}
break;
default:
throw new Error(`Trigger kind ${op.trigger.kind} not handled`);
}
}
// Find the defer ops, and assign the data about their targets.
for (const unit of job.units) {
const defers = new Map();
for (const op of unit.create) {
switch (op.kind) {
case OpKind.Defer:
defers.set(op.xref, op);
break;
case OpKind.DeferOn:
const deferOp = defers.get(op.defer);
resolveTrigger(unit, op, deferOp.placeholderView);
break;
}
}
}
}
class Scope$2 {
constructor() {
this.targets = new Map();
}
}
/**
* @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.dev/license
*/
const REPLACEMENTS = new Map([
[OpKind.ElementEnd, [OpKind.ElementStart, OpKind.Element]],
[OpKind.ContainerEnd, [OpKind.ContainerStart, OpKind.Container]],
[OpKind.I18nEnd, [OpKind.I18nStart, OpKind.I18n]],
]);
/**
* Op kinds that should not prevent merging of start/end ops.
*/
const IGNORED_OP_KINDS = new Set([OpKind.Pipe]);
/**
* Replace sequences of mergable instructions (e.g. `ElementStart` and `ElementEnd`) with a
* consolidated instruction (e.g. `Element`).
*/
function collapseEmptyInstructions(job) {
for (const unit of job.units) {
for (const op of unit.create) {
// Find end ops that may be able to be merged.
const opReplacements = REPLACEMENTS.get(op.kind);
if (opReplacements === undefined) {
continue;
}
const [startKind, mergedKind] = opReplacements;
// Locate the previous (non-ignored) op.
let prevOp = op.prev;
while (prevOp !== null && IGNORED_OP_KINDS.has(prevOp.kind)) {
prevOp = prevOp.prev;
}
// If the previous op is the corresponding start op, we can megre.
if (prevOp !== null && prevOp.kind === startKind) {
// Transmute the start instruction to the merged version. This is safe as they're designed
// to be identical apart from the `kind`.
prevOp.kind = mergedKind;
// Remove the end instruction.
OpList.remove(op);
}
}
}
}
/**
* @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.dev/license
*/
/**
* Safe read expressions such as `a?.b` have different semantics in Angular templates as
* compared to JavaScript. In particular, they default to `null` instead of `undefined`. This phase
* finds all unresolved safe read expressions, and converts them into the appropriate output AST
* reads, guarded by null checks. We generate temporaries as needed, to avoid re-evaluating the same
* sub-expression multiple times.
*/
function expandSafeReads(job) {
for (const unit of job.units) {
for (const op of unit.ops()) {
transformExpressionsInOp(op, (e) => safeTransform(e, { job }), VisitorContextFlag.None);
transformExpressionsInOp(op, ternaryTransform, VisitorContextFlag.None);
}
}
}
// A lookup set of all the expression kinds that require a temporary variable to be generated.
[
InvokeFunctionExpr,
LiteralArrayExpr,
LiteralMapExpr,
SafeInvokeFunctionExpr,
PipeBindingExpr,
].map((e) => e.constructor.name);
function needsTemporaryInSafeAccess(e) {
// TODO: We probably want to use an expression visitor to recursively visit all descendents.
// However, that would potentially do a lot of extra work (because it cannot short circuit), so we
// implement the logic ourselves for now.
if (e instanceof UnaryOperatorExpr) {
return needsTemporaryInSafeAccess(e.expr);
}
else if (e instanceof BinaryOperatorExpr) {
return needsTemporaryInSafeAccess(e.lhs) || needsTemporaryInSafeAccess(e.rhs);
}
else if (e instanceof ConditionalExpr) {
if (e.falseCase && needsTemporaryInSafeAccess(e.falseCase))
return true;
return needsTemporaryInSafeAccess(e.condition) || needsTemporaryInSafeAccess(e.trueCase);
}
else if (e instanceof NotExpr) {
return needsTemporaryInSafeAccess(e.condition);
}
else if (e instanceof AssignTemporaryExpr) {
return needsTemporaryInSafeAccess(e.expr);
}
else if (e instanceof ReadPropExpr) {
return needsTemporaryInSafeAccess(e.receiver);
}
else if (e instanceof ReadKeyExpr) {
return needsTemporaryInSafeAccess(e.receiver) || needsTemporaryInSafeAccess(e.index);
}
// TODO: Switch to a method which is exhaustive of newly added expression subtypes.
return (e instanceof InvokeFunctionExpr ||
e instanceof LiteralArrayExpr ||
e instanceof LiteralMapExpr ||
e instanceof SafeInvokeFunctionExpr ||
e instanceof PipeBindingExpr);
}
function temporariesIn(e) {
const temporaries = new Set();
// TODO: Although it's not currently supported by the transform helper, we should be able to
// short-circuit exploring the tree to do less work. In particular, we don't have to penetrate
// into the subexpressions of temporary assignments.
transformExpressionsInExpression(e, (e) => {
if (e instanceof AssignTemporaryExpr) {
temporaries.add(e.xref);
}
return e;
}, VisitorContextFlag.None);
return temporaries;
}
function eliminateTemporaryAssignments(e, tmps, ctx) {
// TODO: We can be more efficient than the transform helper here. We don't need to visit any
// descendents of temporary assignments.
transformExpressionsInExpression(e, (e) => {
if (e instanceof AssignTemporaryExpr && tmps.has(e.xref)) {
const read = new ReadTemporaryExpr(e.xref);
// `TemplateDefinitionBuilder` has the (accidental?) behavior of generating assignments of
// temporary variables to themselves. This happens because some subexpression that the
// temporary refers to, possibly through nested temporaries, has a function call. We copy that
// behavior here.
return ctx.job.compatibility === CompatibilityMode.TemplateDefinitionBuilder
? new AssignTemporaryExpr(read, read.xref)
: read;
}
return e;
}, VisitorContextFlag.None);
return e;
}
/**
* Creates a safe ternary guarded by the input expression, and with a body generated by the provided
* callback on the input expression. Generates a temporary variable assignment if needed, and
* deduplicates nested temporary assignments if needed.
*/
function safeTernaryWithTemporary(guard, body, ctx) {
let result;
if (needsTemporaryInSafeAccess(guard)) {
const xref = ctx.job.allocateXrefId();
result = [new AssignTemporaryExpr(guard, xref), new ReadTemporaryExpr(xref)];
}
else {
result = [guard, guard.clone()];
// Consider an expression like `a?.[b?.c()]?.d`. The `b?.c()` will be transformed first,
// introducing a temporary assignment into the key. Then, as part of expanding the `?.d`. That
// assignment will be duplicated into both the guard and expression sides. We de-duplicate it,
// by transforming it from an assignment into a read on the expression side.
eliminateTemporaryAssignments(result[1], temporariesIn(result[0]), ctx);
}
return new SafeTernaryExpr(result[0], body(result[1]));
}
function isSafeAccessExpression(e) {
return (e instanceof SafePropertyReadExpr ||
e instanceof SafeKeyedReadExpr ||
e instanceof SafeInvokeFunctionExpr);
}
function isUnsafeAccessExpression(e) {
return (e instanceof ReadPropExpr || e instanceof ReadKeyExpr || e instanceof InvokeFunctionExpr);
}
function isAccessExpression$1(e) {
return isSafeAccessExpression(e) || isUnsafeAccessExpression(e);
}
function deepestSafeTernary(e) {
if (isAccessExpression$1(e) && e.receiver instanceof SafeTernaryExpr) {
let st = e.receiver;
while (st.expr instanceof SafeTernaryExpr) {
st = st.expr;
}
return st;
}
return null;
}
// TODO: When strict compatibility with TemplateDefinitionBuilder is not required, we can use `&&`
// instead to save some code size.
function safeTransform(e, ctx) {
if (!isAccessExpression$1(e)) {
return e;
}
const dst = deepestSafeTernary(e);
if (dst) {
if (e instanceof InvokeFunctionExpr) {
dst.expr = dst.expr.callFn(e.args);
return e.receiver;
}
if (e instanceof ReadPropExpr) {
dst.expr = dst.expr.prop(e.name);
return e.receiver;
}
if (e instanceof ReadKeyExpr) {
dst.expr = dst.expr.key(e.index);
return e.receiver;
}
if (e instanceof SafeInvokeFunctionExpr) {
dst.expr = safeTernaryWithTemporary(dst.expr, (r) => r.callFn(e.args), ctx);
return e.receiver;
}
if (e instanceof SafePropertyReadExpr) {
dst.expr = safeTernaryWithTemporary(dst.expr, (r) => r.prop(e.name), ctx);
return e.receiver;
}
if (e instanceof SafeKeyedReadExpr) {
dst.expr = safeTernaryWithTemporary(dst.expr, (r) => r.key(e.index), ctx);
return e.receiver;
}
}
else {
if (e instanceof SafeInvokeFunctionExpr) {
return safeTernaryWithTemporary(e.receiver, (r) => r.callFn(e.args), ctx);
}
if (e instanceof SafePropertyReadExpr) {
return safeTernaryWithTemporary(e.receiver, (r) => r.prop(e.name), ctx);
}
if (e instanceof SafeKeyedReadExpr) {
return safeTernaryWithTemporary(e.receiver, (r) => r.key(e.index), ctx);
}
}
return e;
}
function ternaryTransform(e) {
if (!(e instanceof SafeTernaryExpr)) {
return e;
}
return new ConditionalExpr(new BinaryOperatorExpr(BinaryOperator.Equals, e.guard, NULL_EXPR), NULL_EXPR, e.expr);
}
/**
* @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.dev/license
*/
/**
* The escape sequence used indicate message param values.
*/
const ESCAPE$1 = '\uFFFD';
/**
* Marker used to indicate an element tag.
*/
const ELEMENT_MARKER = '#';
/**
* Marker used to indicate a template tag.
*/
const TEMPLATE_MARKER = '*';
/**
* Marker used to indicate closing of an element or template tag.
*/
const TAG_CLOSE_MARKER = '/';
/**
* Marker used to indicate the sub-template context.
*/
const CONTEXT_MARKER = ':';
/**
* Marker used to indicate the start of a list of values.
*/
const LIST_START_MARKER = '[';
/**
* Marker used to indicate the end of a list of values.
*/
const LIST_END_MARKER = ']';
/**
* Delimiter used to separate multiple values in a list.
*/
const LIST_DELIMITER = '|';
/**
* Formats the param maps on extracted message ops into a maps of `Expression` objects that can be
* used in the final output.
*/
function extractI18nMessages(job) {
// Create an i18n message for each context.
// TODO: Merge the context op with the message op since they're 1:1 anyways.
const i18nMessagesByContext = new Map();
const i18nBlocks = new Map();
const i18nContexts = new Map();
for (const unit of job.units) {
for (const op of unit.create) {
switch (op.kind) {
case OpKind.I18nContext:
const i18nMessageOp = createI18nMessage(job, op);
unit.create.push(i18nMessageOp);
i18nMessagesByContext.set(op.xref, i18nMessageOp);
i18nContexts.set(op.xref, op);
break;
case OpKind.I18nStart:
i18nBlocks.set(op.xref, op);
break;
}
}
}
// Associate sub-messages for ICUs with their root message. At this point we can also remove the
// ICU start/end ops, as they are no longer needed.
let currentIcu = null;
for (const unit of job.units) {
for (const op of unit.create) {
switch (op.kind) {
case OpKind.IcuStart:
currentIcu = op;
OpList.remove(op);
// Skip any contexts not associated with an ICU.
const icuContext = i18nContexts.get(op.context);
if (icuContext.contextKind !== I18nContextKind.Icu) {
continue;
}
// Skip ICUs that share a context with their i18n message. These represent root-level
// ICUs, not sub-messages.
const i18nBlock = i18nBlocks.get(icuContext.i18nBlock);
if (i18nBlock.context === icuContext.xref) {
continue;
}
// Find the root message and push this ICUs message as a sub-message.
const rootI18nBlock = i18nBlocks.get(i18nBlock.root);
const rootMessage = i18nMessagesByContext.get(rootI18nBlock.context);
if (rootMessage === undefined) {
throw Error('AssertionError: ICU sub-message should belong to a root message.');
}
const subMessage = i18nMessagesByContext.get(icuContext.xref);
subMessage.messagePlaceholder = op.messagePlaceholder;
rootMessage.subMessages.push(subMessage.xref);
break;
case OpKind.IcuEnd:
currentIcu = null;
OpList.remove(op);
break;
case OpKind.IcuPlaceholder:
// Add ICU placeholders to the message, then remove the ICU placeholder ops.
if (currentIcu === null || currentIcu.context == null) {
throw Error('AssertionError: Unexpected ICU placeholder outside of i18n context');
}
const msg = i18nMessagesByContext.get(currentIcu.context);
msg.postprocessingParams.set(op.name, literal$1(formatIcuPlaceholder(op)));
OpList.remove(op);
break;
}
}
}
}
/**
* Create an i18n message op from an i18n context op.
*/
function createI18nMessage(job, context, messagePlaceholder) {
let formattedParams = formatParams(context.params);
const formattedPostprocessingParams = formatParams(context.postprocessingParams);
let needsPostprocessing = [...context.params.values()].some((v) => v.length > 1);
return createI18nMessageOp(job.allocateXrefId(), context.xref, context.i18nBlock, context.message, messagePlaceholder ?? null, formattedParams, formattedPostprocessingParams, needsPostprocessing);
}
/**
* Formats an ICU placeholder into a single string with expression placeholders.
*/
function formatIcuPlaceholder(op) {
if (op.strings.length !== op.expressionPlaceholders.length + 1) {
throw Error(`AssertionError: Invalid ICU placeholder with ${op.strings.length} strings and ${op.expressionPlaceholders.length} expressions`);
}
const values = op.expressionPlaceholders.map(formatValue);
return op.strings.flatMap((str, i) => [str, values[i] || '']).join('');
}
/**
* Formats a map of `I18nParamValue[]` values into a map of `Expression` values.
*/
function formatParams(params) {
const formattedParams = new Map();
for (const [placeholder, placeholderValues] of params) {
const serializedValues = formatParamValues(placeholderValues);
if (serializedValues !== null) {
formattedParams.set(placeholder, literal$1(serializedValues));
}
}
return formattedParams;
}
/**
* Formats an `I18nParamValue[]` into a string (or null for empty array).
*/
function formatParamValues(values) {
if (values.length === 0) {
return null;
}
const serializedValues = values.map((value) => formatValue(value));
return serializedValues.length === 1
? serializedValues[0]
: `${LIST_START_MARKER}${serializedValues.join(LIST_DELIMITER)}${LIST_END_MARKER}`;
}
/**
* Formats a single `I18nParamValue` into a string
*/
function formatValue(value) {
// Element tags with a structural directive use a special form that concatenates the element and
// template values.
if (value.flags & I18nParamValueFlags.ElementTag &&
value.flags & I18nParamValueFlags.TemplateTag) {
if (typeof value.value !== 'object') {
throw Error('AssertionError: Expected i18n param value to have an element and template slot');
}
const elementValue = formatValue({
...value,
value: value.value.element,
flags: value.flags & ~I18nParamValueFlags.TemplateTag,
});
const templateValue = formatValue({
...value,
value: value.value.template,
flags: value.flags & ~I18nParamValueFlags.ElementTag,
});
// TODO(mmalerba): This is likely a bug in TemplateDefinitionBuilder, we should not need to
// record the template value twice. For now I'm re-implementing the behavior here to keep the
// output consistent with TemplateDefinitionBuilder.
if (value.flags & I18nParamValueFlags.OpenTag &&
value.flags & I18nParamValueFlags.CloseTag) {
return `${templateValue}${elementValue}${templateValue}`;
}
// To match the TemplateDefinitionBuilder output, flip the order depending on whether the
// values represent a closing or opening tag (or both).
// TODO(mmalerba): Figure out if this makes a difference in terms of either functionality,
// or the resulting message ID. If not, we can remove the special-casing in the future.
return value.flags & I18nParamValueFlags.CloseTag
? `${elementValue}${templateValue}`
: `${templateValue}${elementValue}`;
}
// Self-closing tags use a special form that concatenates the start and close tag values.
if (value.flags & I18nParamValueFlags.OpenTag &&
value.flags & I18nParamValueFlags.CloseTag) {
return `${formatValue({ ...value, flags: value.flags & ~I18nParamValueFlags.CloseTag })}${formatValue({ ...value, flags: value.flags & ~I18nParamValueFlags.OpenTag })}`;
}
// If there are no special flags, just return the raw value.
if (value.flags === I18nParamValueFlags.None) {
return `${value.value}`;
}
// Encode the remaining flags as part of the value.
let tagMarker = '';
let closeMarker = '';
if (value.flags & I18nParamValueFlags.ElementTag) {
tagMarker = ELEMENT_MARKER;
}
else if (value.flags & I18nParamValueFlags.TemplateTag) {
tagMarker = TEMPLATE_MARKER;
}
if (tagMarker !== '') {
closeMarker = value.flags & I18nParamValueFlags.CloseTag ? TAG_CLOSE_MARKER : '';
}
const context = value.subTemplateIndex === null ? '' : `${CONTEXT_MARKER}${value.subTemplateIndex}`;
return `${ESCAPE$1}${closeMarker}${tagMarker}${value.value}${context}${ESCAPE$1}`;
}
/**
* @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.dev/license
*/
/**
* Generate `ir.AdvanceOp`s in between `ir.UpdateOp`s that ensure the runtime's implicit slot
* context will be advanced correctly.
*/
function generateAdvance(job) {
for (const unit of job.units) {
// First build a map of all of the declarations in the view that have assigned slots.
const slotMap = new Map();
for (const op of unit.create) {
if (!hasConsumesSlotTrait(op)) {
continue;
}
else if (op.handle.slot === null) {
throw new Error(`AssertionError: expected slots to have been allocated before generating advance() calls`);
}
slotMap.set(op.xref, op.handle.slot);
}
// Next, step through the update operations and generate `ir.AdvanceOp`s as required to ensure
// the runtime's implicit slot counter will be set to the correct slot before executing each
// update operation which depends on it.
//
// To do that, we track what the runtime's slot counter will be through the update operations.
let slotContext = 0;
for (const op of unit.update) {
let consumer = null;
if (hasDependsOnSlotContextTrait(op)) {
consumer = op;
}
else {
visitExpressionsInOp(op, (expr) => {
if (consumer === null && hasDependsOnSlotContextTrait(expr)) {
consumer = expr;
}
});
}
if (consumer === null) {
continue;
}
if (!slotMap.has(consumer.target)) {
// We expect ops that _do_ depend on the slot counter to point at declarations that exist in
// the `slotMap`.
throw new Error(`AssertionError: reference to unknown slot for target ${consumer.target}`);
}
const slot = slotMap.get(consumer.target);
// Does the slot counter need to be adjusted?
if (slotContext !== slot) {
// If so, generate an `ir.AdvanceOp` to advance the counter.
const delta = slot - slotContext;
if (delta < 0) {
throw new Error(`AssertionError: slot counter should never need to move backwards`);
}
OpList.insertBefore(createAdvanceOp(delta, consumer.sourceSpan), op);
slotContext = slot;
}
}
}
}
/**
* @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.dev/license
*/
/**
* Locate projection slots, populate the each component's `ngContentSelectors` literal field,
* populate `project` arguments, and generate the required `projectionDef` instruction for the job's
* root view.
*/
function generateProjectionDefs(job) {
// TODO: Why does TemplateDefinitionBuilder force a shared constant?
const share = job.compatibility === CompatibilityMode.TemplateDefinitionBuilder;
// Collect all selectors from this component, and its nested views. Also, assign each projection a
// unique ascending projection slot index.
const selectors = [];
let projectionSlotIndex = 0;
for (const unit of job.units) {
for (const op of unit.create) {
if (op.kind === OpKind.Projection) {
selectors.push(op.selector);
op.projectionSlotIndex = projectionSlotIndex++;
}
}
}
if (selectors.length > 0) {
// Create the projectionDef array. If we only found a single wildcard selector, then we use the
// default behavior with no arguments instead.
let defExpr = null;
if (selectors.length > 1 || selectors[0] !== '*') {
const def = selectors.map((s) => (s === '*' ? s : parseSelectorToR3Selector(s)));
defExpr = job.pool.getConstLiteral(literalOrArrayLiteral(def), share);
}
// Create the ngContentSelectors constant.
job.contentSelectors = job.pool.getConstLiteral(literalOrArrayLiteral(selectors), share);
// The projection def instruction goes at the beginning of the root view, before any
// `projection` instructions.
job.root.create.prepend([createProjectionDefOp(defExpr)]);
}
}
/**
* @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.dev/license
*/
/**
* Generate a preamble sequence for each view creation block and listener function which declares
* any variables that be referenced in other operations in the block.
*
* Variables generated include:
* * a saved view context to be used to restore the current view in event listeners.
* * the context of the restored view within event listener handlers.
* * context variables from the current view as well as all parent views (including the root
* context if needed).
* * local references from elements within the current view and any lexical parents.
*
* Variables are generated here unconditionally, and may optimized away in future operations if it
* turns out their values (and any side effects) are unused.
*/
function generateVariables(job) {
recursivelyProcessView(job.root, /* there is no parent scope for the root view */ null);
}
/**
* Process the given `ViewCompilation` and generate preambles for it and any listeners that it
* declares.
*
* @param `parentScope` a scope extracted from the parent view which captures any variables which
* should be inherited by this view. `null` if the current view is the root view.
*/
function recursivelyProcessView(view, parentScope) {
// Extract a `Scope` from this view.
const scope = getScopeForView(view, parentScope);
for (const op of view.create) {
switch (op.kind) {
case OpKind.Template:
// Descend into child embedded views.
recursivelyProcessView(view.job.views.get(op.xref), scope);
break;
case OpKind.Projection:
if (op.fallbackView !== null) {
recursivelyProcessView(view.job.views.get(op.fallbackView), scope);
}
break;
case OpKind.RepeaterCreate:
// Descend into child embedded views.
recursivelyProcessView(view.job.views.get(op.xref), scope);
if (op.emptyView) {
recursivelyProcessView(view.job.views.get(op.emptyView), scope);
}
break;
case OpKind.Listener:
case OpKind.TwoWayListener:
// Prepend variables to listener handler functions.
op.handlerOps.prepend(generateVariablesInScopeForView(view, scope, true));
break;
}
}
view.update.prepend(generateVariablesInScopeForView(view, scope, false));
}
/**
* Process a view and generate a `Scope` representing the variables available for reference within
* that view.
*/
function getScopeForView(view, parent) {
const scope = {
view: view.xref,
viewContextVariable: {
kind: SemanticVariableKind.Context,
name: null,
view: view.xref,
},
contextVariables: new Map(),
aliases: view.aliases,
references: [],
letDeclarations: [],
parent,
};
for (const identifier of view.contextVariables.keys()) {
scope.contextVariables.set(identifier, {
kind: SemanticVariableKind.Identifier,
name: null,
identifier,
local: false,
});
}
for (const op of view.create) {
switch (op.kind) {
case OpKind.ElementStart:
case OpKind.Template:
if (!Array.isArray(op.localRefs)) {
throw new Error(`AssertionError: expected localRefs to be an array`);
}
// Record available local references from this element.
for (let offset = 0; offset < op.localRefs.length; offset++) {
scope.references.push({
name: op.localRefs[offset].name,
targetId: op.xref,
targetSlot: op.handle,
offset,
variable: {
kind: SemanticVariableKind.Identifier,
name: null,
identifier: op.localRefs[offset].name,
local: false,
},
});
}
break;
case OpKind.DeclareLet:
scope.letDeclarations.push({
targetId: op.xref,
targetSlot: op.handle,
variable: {
kind: SemanticVariableKind.Identifier,
name: null,
identifier: op.declaredName,
local: false,
},
});
break;
}
}
return scope;
}
/**
* Generate declarations for all variables that are in scope for a given view.
*
* This is a recursive process, as views inherit variables available from their parent view, which
* itself may have inherited variables, etc.
*/
function generateVariablesInScopeForView(view, scope, isListener) {
const newOps = [];
if (scope.view !== view.xref) {
// Before generating variables for a parent view, we need to switch to the context of the parent
// view with a `nextContext` expression. This context switching operation itself declares a
// variable, because the context of the view may be referenced directly.
newOps.push(createVariableOp(view.job.allocateXrefId(), scope.viewContextVariable, new NextContextExpr(), VariableFlags.None));
}
// Add variables for all context variables available in this scope's view.
const scopeView = view.job.views.get(scope.view);
for (const [name, value] of scopeView.contextVariables) {
const context = new ContextExpr(scope.view);
// We either read the context, or, if the variable is CTX_REF, use the context directly.
const variable = value === CTX_REF ? context : new ReadPropExpr(context, value);
// Add the variable declaration.
newOps.push(createVariableOp(view.job.allocateXrefId(), scope.contextVariables.get(name), variable, VariableFlags.None));
}
for (const alias of scopeView.aliases) {
newOps.push(createVariableOp(view.job.allocateXrefId(), alias, alias.expression.clone(), VariableFlags.AlwaysInline));
}
// Add variables for all local references declared for elements in this scope.
for (const ref of scope.references) {
newOps.push(createVariableOp(view.job.allocateXrefId(), ref.variable, new ReferenceExpr(ref.targetId, ref.targetSlot, ref.offset), VariableFlags.None));
}
if (scope.view !== view.xref || isListener) {
for (const decl of scope.letDeclarations) {
newOps.push(createVariableOp(view.job.allocateXrefId(), decl.variable, new ContextLetReferenceExpr(decl.targetId, decl.targetSlot), VariableFlags.None));
}
}
if (scope.parent !== null) {
// Recursively add variables from the parent scope.
newOps.push(...generateVariablesInScopeForView(view, scope.parent, false));
}
return newOps;
}
/**
* @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.dev/license
*/
/**
* `ir.ConstCollectedExpr` may be present in any IR expression. This means that expression needs to
* be lifted into the component const array, and replaced with a reference to the const array at its
*
* usage site. This phase walks the IR and performs this transformation.
*/
function collectConstExpressions(job) {
for (const unit of job.units) {
for (const op of unit.ops()) {
transformExpressionsInOp(op, (expr) => {
if (!(expr instanceof ConstCollectedExpr)) {
return expr;
}
return literal$1(job.addConst(expr.expr));
}, VisitorContextFlag.None);
}
}
}
/**
* @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.dev/license
*/
const STYLE_DOT = 'style.';
const CLASS_DOT = 'class.';
const STYLE_BANG = 'style!';
const CLASS_BANG = 'class!';
const BANG_IMPORTANT = '!important';
/**
* Host bindings are compiled using a different parser entrypoint, and are parsed quite differently
* as a result. Therefore, we need to do some extra parsing for host style properties, as compared
* to non-host style properties.
* TODO: Unify host bindings and non-host bindings in the parser.
*/
function parseHostStyleProperties(job) {
for (const op of job.root.update) {
if (!(op.kind === OpKind.Binding && op.bindingKind === BindingKind.Property)) {
continue;
}
if (op.name.endsWith(BANG_IMPORTANT)) {
// Delete any `!important` suffixes from the binding name.
op.name = op.name.substring(0, op.name.length - BANG_IMPORTANT.length);
}
if (op.name.startsWith(STYLE_DOT)) {
op.bindingKind = BindingKind.StyleProperty;
op.name = op.name.substring(STYLE_DOT.length);
if (!isCssCustomProperty(op.name)) {
op.name = hyphenate$1(op.name);
}
const { property, suffix } = parseProperty(op.name);
op.name = property;
op.unit = suffix;
}
else if (op.name.startsWith(STYLE_BANG)) {
op.bindingKind = BindingKind.StyleProperty;
op.name = 'style';
}
else if (op.name.startsWith(CLASS_DOT)) {
op.bindingKind = BindingKind.ClassName;
op.name = parseProperty(op.name.substring(CLASS_DOT.length)).property;
}
else if (op.name.startsWith(CLASS_BANG)) {
op.bindingKind = BindingKind.ClassName;
op.name = parseProperty(op.name.substring(CLASS_BANG.length)).property;
}
}
}
/**
* Checks whether property name is a custom CSS property.
* See: https://www.w3.org/TR/css-variables-1
*/
function isCssCustomProperty(name) {
return name.startsWith('--');
}
function hyphenate$1(value) {
return value
.replace(/[a-z][A-Z]/g, (v) => {
return v.charAt(0) + '-' + v.charAt(1);
})
.toLowerCase();
}
function parseProperty(name) {
const overrideIndex = name.indexOf('!important');
if (overrideIndex !== -1) {
name = overrideIndex > 0 ? name.substring(0, overrideIndex) : '';
}
let suffix = null;
let property = name;
const unitIndex = name.lastIndexOf('.');
if (unitIndex > 0) {
suffix = name.slice(unitIndex + 1);
property = name.substring(0, unitIndex);
}
return { property, suffix };
}
/**
* @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.dev/license
*/
function mapLiteral(obj, quoted = false) {
return literalMap(Object.keys(obj).map((key) => ({
key,
quoted,
value: obj[key],
})));
}
/**
* @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.dev/license
*/
class IcuSerializerVisitor {
visitText(text) {
return text.value;
}
visitContainer(container) {
return container.children.map((child) => child.visit(this)).join('');
}
visitIcu(icu) {
const strCases = Object.keys(icu.cases).map((k) => `${k} {${icu.cases[k].visit(this)}}`);
const result = `{${icu.expressionPlaceholder}, ${icu.type}, ${strCases.join(' ')}}`;
return result;
}
visitTagPlaceholder(ph) {
return ph.isVoid
? this.formatPh(ph.startName)
: `${this.formatPh(ph.startName)}${ph.children.map((child) => child.visit(this)).join('')}${this.formatPh(ph.closeName)}`;
}
visitPlaceholder(ph) {
return this.formatPh(ph.name);
}
visitBlockPlaceholder(ph) {
return `${this.formatPh(ph.startName)}${ph.children.map((child) => child.visit(this)).join('')}${this.formatPh(ph.closeName)}`;
}
visitIcuPlaceholder(ph, context) {
return this.formatPh(ph.name);
}
formatPh(value) {
return `{${formatI18nPlaceholderName(value, /* useCamelCase */ false)}}`;
}
}
const serializer = new IcuSerializerVisitor();
function serializeIcuNode(icu) {
return icu.visit(serializer);
}
/**
* @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.dev/license
*/
class NodeWithI18n {
constructor(sourceSpan, i18n) {
this.sourceSpan = sourceSpan;
this.i18n = i18n;
}
}
class Text extends NodeWithI18n {
constructor(value, sourceSpan, tokens, i18n) {
super(sourceSpan, i18n);
this.value = value;
this.tokens = tokens;
}
visit(visitor, context) {
return visitor.visitText(this, context);
}
}
class Expansion extends NodeWithI18n {
constructor(switchValue, type, cases, sourceSpan, switchValueSourceSpan, i18n) {
super(sourceSpan, i18n);
this.switchValue = switchValue;
this.type = type;
this.cases = cases;
this.switchValueSourceSpan = switchValueSourceSpan;
}
visit(visitor, context) {
return visitor.visitExpansion(this, context);
}
}
class ExpansionCase {
constructor(value, expression, sourceSpan, valueSourceSpan, expSourceSpan) {
this.value = value;
this.expression = expression;
this.sourceSpan = sourceSpan;
this.valueSourceSpan = valueSourceSpan;
this.expSourceSpan = expSourceSpan;
}
visit(visitor, context) {
return visitor.visitExpansionCase(this, context);
}
}
class Attribute extends NodeWithI18n {
constructor(name, value, sourceSpan, keySpan, valueSpan, valueTokens, i18n) {
super(sourceSpan, i18n);
this.name = name;
this.value = value;
this.keySpan = keySpan;
this.valueSpan = valueSpan;
this.valueTokens = valueTokens;
}
visit(visitor, context) {
return visitor.visitAttribute(this, context);
}
}
class Element extends NodeWithI18n {
constructor(name, attrs, children, sourceSpan, startSourceSpan, endSourceSpan = null, i18n) {
super(sourceSpan, i18n);
this.name = name;
this.attrs = attrs;
this.children = children;
this.startSourceSpan = startSourceSpan;
this.endSourceSpan = endSourceSpan;
}
visit(visitor, context) {
return visitor.visitElement(this, context);
}
}
class Comment {
constructor(value, sourceSpan) {
this.value = value;
this.sourceSpan = sourceSpan;
}
visit(visitor, context) {
return visitor.visitComment(this, context);
}
}
class Block extends NodeWithI18n {
constructor(name, parameters, children, sourceSpan, nameSpan, startSourceSpan, endSourceSpan = null, i18n) {
super(sourceSpan, i18n);
this.name = name;
this.parameters = parameters;
this.children = children;
this.nameSpan = nameSpan;
this.startSourceSpan = startSourceSpan;
this.endSourceSpan = endSourceSpan;
}
visit(visitor, context) {
return visitor.visitBlock(this, context);
}
}
class BlockParameter {
constructor(expression, sourceSpan) {
this.expression = expression;
this.sourceSpan = sourceSpan;
}
visit(visitor, context) {
return visitor.visitBlockParameter(this, context);
}
}
class LetDeclaration {
constructor(name, value, sourceSpan, nameSpan, valueSpan) {
this.name = name;
this.value = value;
this.sourceSpan = sourceSpan;
this.nameSpan = nameSpan;
this.valueSpan = valueSpan;
}
visit(visitor, context) {
return visitor.visitLetDeclaration(this, context);
}
}
function visitAll(visitor, nodes, context = null) {
const result = [];
const visit = visitor.visit
? (ast) => visitor.visit(ast, context) || ast.visit(visitor, context)
: (ast) => ast.visit(visitor, context);
nodes.forEach((ast) => {
const astResult = visit(ast);
if (astResult) {
result.push(astResult);
}
});
return result;
}
/**
* @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.dev/license
*/
// Mapping between all HTML entity names and their unicode representation.
// Generated from https://html.spec.whatwg.org/multipage/entities.json by stripping
// the `&` and `;` from the keys and removing the duplicates.
// see https://www.w3.org/TR/html51/syntax.html#named-character-references
const NAMED_ENTITIES = {
'AElig': '\u00C6',
'AMP': '\u0026',
'amp': '\u0026',
'Aacute': '\u00C1',
'Abreve': '\u0102',
'Acirc': '\u00C2',
'Acy': '\u0410',
'Afr': '\uD835\uDD04',
'Agrave': '\u00C0',
'Alpha': '\u0391',
'Amacr': '\u0100',
'And': '\u2A53',
'Aogon': '\u0104',
'Aopf': '\uD835\uDD38',
'ApplyFunction': '\u2061',
'af': '\u2061',
'Aring': '\u00C5',
'angst': '\u00C5',
'Ascr': '\uD835\uDC9C',
'Assign': '\u2254',
'colone': '\u2254',
'coloneq': '\u2254',
'Atilde': '\u00C3',
'Auml': '\u00C4',
'Backslash': '\u2216',
'setminus': '\u2216',
'setmn': '\u2216',
'smallsetminus': '\u2216',
'ssetmn': '\u2216',
'Barv': '\u2AE7',
'Barwed': '\u2306',
'doublebarwedge': '\u2306',
'Bcy': '\u0411',
'Because': '\u2235',
'becaus': '\u2235',
'because': '\u2235',
'Bernoullis': '\u212C',
'Bscr': '\u212C',
'bernou': '\u212C',
'Beta': '\u0392',
'Bfr': '\uD835\uDD05',
'Bopf': '\uD835\uDD39',
'Breve': '\u02D8',
'breve': '\u02D8',
'Bumpeq': '\u224E',
'HumpDownHump': '\u224E',
'bump': '\u224E',
'CHcy': '\u0427',
'COPY': '\u00A9',
'copy': '\u00A9',
'Cacute': '\u0106',
'Cap': '\u22D2',
'CapitalDifferentialD': '\u2145',
'DD': '\u2145',
'Cayleys': '\u212D',
'Cfr': '\u212D',
'Ccaron': '\u010C',
'Ccedil': '\u00C7',
'Ccirc': '\u0108',
'Cconint': '\u2230',
'Cdot': '\u010A',
'Cedilla': '\u00B8',
'cedil': '\u00B8',
'CenterDot': '\u00B7',
'centerdot': '\u00B7',
'middot': '\u00B7',
'Chi': '\u03A7',
'CircleDot': '\u2299',
'odot': '\u2299',
'CircleMinus': '\u2296',
'ominus': '\u2296',
'CirclePlus': '\u2295',
'oplus': '\u2295',
'CircleTimes': '\u2297',
'otimes': '\u2297',
'ClockwiseContourIntegral': '\u2232',
'cwconint': '\u2232',
'CloseCurlyDoubleQuote': '\u201D',
'rdquo': '\u201D',
'rdquor': '\u201D',
'CloseCurlyQuote': '\u2019',
'rsquo': '\u2019',
'rsquor': '\u2019',
'Colon': '\u2237',
'Proportion': '\u2237',
'Colone': '\u2A74',
'Congruent': '\u2261',
'equiv': '\u2261',
'Conint': '\u222F',
'DoubleContourIntegral': '\u222F',
'ContourIntegral': '\u222E',
'conint': '\u222E',
'oint': '\u222E',
'Copf': '\u2102',
'complexes': '\u2102',
'Coproduct': '\u2210',
'coprod': '\u2210',
'CounterClockwiseContourIntegral': '\u2233',
'awconint': '\u2233',
'Cross': '\u2A2F',
'Cscr': '\uD835\uDC9E',
'Cup': '\u22D3',
'CupCap': '\u224D',
'asympeq': '\u224D',
'DDotrahd': '\u2911',
'DJcy': '\u0402',
'DScy': '\u0405',
'DZcy': '\u040F',
'Dagger': '\u2021',
'ddagger': '\u2021',
'Darr': '\u21A1',
'Dashv': '\u2AE4',
'DoubleLeftTee': '\u2AE4',
'Dcaron': '\u010E',
'Dcy': '\u0414',
'Del': '\u2207',
'nabla': '\u2207',
'Delta': '\u0394',
'Dfr': '\uD835\uDD07',
'DiacriticalAcute': '\u00B4',
'acute': '\u00B4',
'DiacriticalDot': '\u02D9',
'dot': '\u02D9',
'DiacriticalDoubleAcute': '\u02DD',
'dblac': '\u02DD',
'DiacriticalGrave': '\u0060',
'grave': '\u0060',
'DiacriticalTilde': '\u02DC',
'tilde': '\u02DC',
'Diamond': '\u22C4',
'diam': '\u22C4',
'diamond': '\u22C4',
'DifferentialD': '\u2146',
'dd': '\u2146',
'Dopf': '\uD835\uDD3B',
'Dot': '\u00A8',
'DoubleDot': '\u00A8',
'die': '\u00A8',
'uml': '\u00A8',
'DotDot': '\u20DC',
'DotEqual': '\u2250',
'doteq': '\u2250',
'esdot': '\u2250',
'DoubleDownArrow': '\u21D3',
'Downarrow': '\u21D3',
'dArr': '\u21D3',
'DoubleLeftArrow': '\u21D0',
'Leftarrow': '\u21D0',
'lArr': '\u21D0',
'DoubleLeftRightArrow': '\u21D4',
'Leftrightarrow': '\u21D4',
'hArr': '\u21D4',
'iff': '\u21D4',
'DoubleLongLeftArrow': '\u27F8',
'Longleftarrow': '\u27F8',
'xlArr': '\u27F8',
'DoubleLongLeftRightArrow': '\u27FA',
'Longleftrightarrow': '\u27FA',
'xhArr': '\u27FA',
'DoubleLongRightArrow': '\u27F9',
'Longrightarrow': '\u27F9',
'xrArr': '\u27F9',
'DoubleRightArrow': '\u21D2',
'Implies': '\u21D2',
'Rightarrow': '\u21D2',
'rArr': '\u21D2',
'DoubleRightTee': '\u22A8',
'vDash': '\u22A8',
'DoubleUpArrow': '\u21D1',
'Uparrow': '\u21D1',
'uArr': '\u21D1',
'DoubleUpDownArrow': '\u21D5',
'Updownarrow': '\u21D5',
'vArr': '\u21D5',
'DoubleVerticalBar': '\u2225',
'par': '\u2225',
'parallel': '\u2225',
'shortparallel': '\u2225',
'spar': '\u2225',
'DownArrow': '\u2193',
'ShortDownArrow': '\u2193',
'darr': '\u2193',
'downarrow': '\u2193',
'DownArrowBar': '\u2913',
'DownArrowUpArrow': '\u21F5',
'duarr': '\u21F5',
'DownBreve': '\u0311',
'DownLeftRightVector': '\u2950',
'DownLeftTeeVector': '\u295E',
'DownLeftVector': '\u21BD',
'leftharpoondown': '\u21BD',
'lhard': '\u21BD',
'DownLeftVectorBar': '\u2956',
'DownRightTeeVector': '\u295F',
'DownRightVector': '\u21C1',
'rhard': '\u21C1',
'rightharpoondown': '\u21C1',
'DownRightVectorBar': '\u2957',
'DownTee': '\u22A4',
'top': '\u22A4',
'DownTeeArrow': '\u21A7',
'mapstodown': '\u21A7',
'Dscr': '\uD835\uDC9F',
'Dstrok': '\u0110',
'ENG': '\u014A',
'ETH': '\u00D0',
'Eacute': '\u00C9',
'Ecaron': '\u011A',
'Ecirc': '\u00CA',
'Ecy': '\u042D',
'Edot': '\u0116',
'Efr': '\uD835\uDD08',
'Egrave': '\u00C8',
'Element': '\u2208',
'in': '\u2208',
'isin': '\u2208',
'isinv': '\u2208',
'Emacr': '\u0112',
'EmptySmallSquare': '\u25FB',
'EmptyVerySmallSquare': '\u25AB',
'Eogon': '\u0118',
'Eopf': '\uD835\uDD3C',
'Epsilon': '\u0395',
'Equal': '\u2A75',
'EqualTilde': '\u2242',
'eqsim': '\u2242',
'esim': '\u2242',
'Equilibrium': '\u21CC',
'rightleftharpoons': '\u21CC',
'rlhar': '\u21CC',
'Escr': '\u2130',
'expectation': '\u2130',
'Esim': '\u2A73',
'Eta': '\u0397',
'Euml': '\u00CB',
'Exists': '\u2203',
'exist': '\u2203',
'ExponentialE': '\u2147',
'ee': '\u2147',
'exponentiale': '\u2147',
'Fcy': '\u0424',
'Ffr': '\uD835\uDD09',
'FilledSmallSquare': '\u25FC',
'FilledVerySmallSquare': '\u25AA',
'blacksquare': '\u25AA',
'squarf': '\u25AA',
'squf': '\u25AA',
'Fopf': '\uD835\uDD3D',
'ForAll': '\u2200',
'forall': '\u2200',
'Fouriertrf': '\u2131',
'Fscr': '\u2131',
'GJcy': '\u0403',
'GT': '\u003E',
'gt': '\u003E',
'Gamma': '\u0393',
'Gammad': '\u03DC',
'Gbreve': '\u011E',
'Gcedil': '\u0122',
'Gcirc': '\u011C',
'Gcy': '\u0413',
'Gdot': '\u0120',
'Gfr': '\uD835\uDD0A',
'Gg': '\u22D9',
'ggg': '\u22D9',
'Gopf': '\uD835\uDD3E',
'GreaterEqual': '\u2265',
'ge': '\u2265',
'geq': '\u2265',
'GreaterEqualLess': '\u22DB',
'gel': '\u22DB',
'gtreqless': '\u22DB',
'GreaterFullEqual': '\u2267',
'gE': '\u2267',
'geqq': '\u2267',
'GreaterGreater': '\u2AA2',
'GreaterLess': '\u2277',
'gl': '\u2277',
'gtrless': '\u2277',
'GreaterSlantEqual': '\u2A7E',
'geqslant': '\u2A7E',
'ges': '\u2A7E',
'GreaterTilde': '\u2273',
'gsim': '\u2273',
'gtrsim': '\u2273',
'Gscr': '\uD835\uDCA2',
'Gt': '\u226B',
'NestedGreaterGreater': '\u226B',
'gg': '\u226B',
'HARDcy': '\u042A',
'Hacek': '\u02C7',
'caron': '\u02C7',
'Hat': '\u005E',
'Hcirc': '\u0124',
'Hfr': '\u210C',
'Poincareplane': '\u210C',
'HilbertSpace': '\u210B',
'Hscr': '\u210B',
'hamilt': '\u210B',
'Hopf': '\u210D',
'quaternions': '\u210D',
'HorizontalLine': '\u2500',
'boxh': '\u2500',
'Hstrok': '\u0126',
'HumpEqual': '\u224F',
'bumpe': '\u224F',
'bumpeq': '\u224F',
'IEcy': '\u0415',
'IJlig': '\u0132',
'IOcy': '\u0401',
'Iacute': '\u00CD',
'Icirc': '\u00CE',
'Icy': '\u0418',
'Idot': '\u0130',
'Ifr': '\u2111',
'Im': '\u2111',
'image': '\u2111',
'imagpart': '\u2111',
'Igrave': '\u00CC',
'Imacr': '\u012A',
'ImaginaryI': '\u2148',
'ii': '\u2148',
'Int': '\u222C',
'Integral': '\u222B',
'int': '\u222B',
'Intersection': '\u22C2',
'bigcap': '\u22C2',
'xcap': '\u22C2',
'InvisibleComma': '\u2063',
'ic': '\u2063',
'InvisibleTimes': '\u2062',
'it': '\u2062',
'Iogon': '\u012E',
'Iopf': '\uD835\uDD40',
'Iota': '\u0399',
'Iscr': '\u2110',
'imagline': '\u2110',
'Itilde': '\u0128',
'Iukcy': '\u0406',
'Iuml': '\u00CF',
'Jcirc': '\u0134',
'Jcy': '\u0419',
'Jfr': '\uD835\uDD0D',
'Jopf': '\uD835\uDD41',
'Jscr': '\uD835\uDCA5',
'Jsercy': '\u0408',
'Jukcy': '\u0404',
'KHcy': '\u0425',
'KJcy': '\u040C',
'Kappa': '\u039A',
'Kcedil': '\u0136',
'Kcy': '\u041A',
'Kfr': '\uD835\uDD0E',
'Kopf': '\uD835\uDD42',
'Kscr': '\uD835\uDCA6',
'LJcy': '\u0409',
'LT': '\u003C',
'lt': '\u003C',
'Lacute': '\u0139',
'Lambda': '\u039B',
'Lang': '\u27EA',
'Laplacetrf': '\u2112',
'Lscr': '\u2112',
'lagran': '\u2112',
'Larr': '\u219E',
'twoheadleftarrow': '\u219E',
'Lcaron': '\u013D',
'Lcedil': '\u013B',
'Lcy': '\u041B',
'LeftAngleBracket': '\u27E8',
'lang': '\u27E8',
'langle': '\u27E8',
'LeftArrow': '\u2190',
'ShortLeftArrow': '\u2190',
'larr': '\u2190',
'leftarrow': '\u2190',
'slarr': '\u2190',
'LeftArrowBar': '\u21E4',
'larrb': '\u21E4',
'LeftArrowRightArrow': '\u21C6',
'leftrightarrows': '\u21C6',
'lrarr': '\u21C6',
'LeftCeiling': '\u2308',
'lceil': '\u2308',
'LeftDoubleBracket': '\u27E6',
'lobrk': '\u27E6',
'LeftDownTeeVector': '\u2961',
'LeftDownVector': '\u21C3',
'dharl': '\u21C3',
'downharpoonleft': '\u21C3',
'LeftDownVectorBar': '\u2959',
'LeftFloor': '\u230A',
'lfloor': '\u230A',
'LeftRightArrow': '\u2194',
'harr': '\u2194',
'leftrightarrow': '\u2194',
'LeftRightVector': '\u294E',
'LeftTee': '\u22A3',
'dashv': '\u22A3',
'LeftTeeArrow': '\u21A4',
'mapstoleft': '\u21A4',
'LeftTeeVector': '\u295A',
'LeftTriangle': '\u22B2',
'vartriangleleft': '\u22B2',
'vltri': '\u22B2',
'LeftTriangleBar': '\u29CF',
'LeftTriangleEqual': '\u22B4',
'ltrie': '\u22B4',
'trianglelefteq': '\u22B4',
'LeftUpDownVector': '\u2951',
'LeftUpTeeVector': '\u2960',
'LeftUpVector': '\u21BF',
'uharl': '\u21BF',
'upharpoonleft': '\u21BF',
'LeftUpVectorBar': '\u2958',
'LeftVector': '\u21BC',
'leftharpoonup': '\u21BC',
'lharu': '\u21BC',
'LeftVectorBar': '\u2952',
'LessEqualGreater': '\u22DA',
'leg': '\u22DA',
'lesseqgtr': '\u22DA',
'LessFullEqual': '\u2266',
'lE': '\u2266',
'leqq': '\u2266',
'LessGreater': '\u2276',
'lessgtr': '\u2276',
'lg': '\u2276',
'LessLess': '\u2AA1',
'LessSlantEqual': '\u2A7D',
'leqslant': '\u2A7D',
'les': '\u2A7D',
'LessTilde': '\u2272',
'lesssim': '\u2272',
'lsim': '\u2272',
'Lfr': '\uD835\uDD0F',
'Ll': '\u22D8',
'Lleftarrow': '\u21DA',
'lAarr': '\u21DA',
'Lmidot': '\u013F',
'LongLeftArrow': '\u27F5',
'longleftarrow': '\u27F5',
'xlarr': '\u27F5',
'LongLeftRightArrow': '\u27F7',
'longleftrightarrow': '\u27F7',
'xharr': '\u27F7',
'LongRightArrow': '\u27F6',
'longrightarrow': '\u27F6',
'xrarr': '\u27F6',
'Lopf': '\uD835\uDD43',
'LowerLeftArrow': '\u2199',
'swarr': '\u2199',
'swarrow': '\u2199',
'LowerRightArrow': '\u2198',
'searr': '\u2198',
'searrow': '\u2198',
'Lsh': '\u21B0',
'lsh': '\u21B0',
'Lstrok': '\u0141',
'Lt': '\u226A',
'NestedLessLess': '\u226A',
'll': '\u226A',
'Map': '\u2905',
'Mcy': '\u041C',
'MediumSpace': '\u205F',
'Mellintrf': '\u2133',
'Mscr': '\u2133',
'phmmat': '\u2133',
'Mfr': '\uD835\uDD10',
'MinusPlus': '\u2213',
'mnplus': '\u2213',
'mp': '\u2213',
'Mopf': '\uD835\uDD44',
'Mu': '\u039C',
'NJcy': '\u040A',
'Nacute': '\u0143',
'Ncaron': '\u0147',
'Ncedil': '\u0145',
'Ncy': '\u041D',
'NegativeMediumSpace': '\u200B',
'NegativeThickSpace': '\u200B',
'NegativeThinSpace': '\u200B',
'NegativeVeryThinSpace': '\u200B',
'ZeroWidthSpace': '\u200B',
'NewLine': '\u000A',
'Nfr': '\uD835\uDD11',
'NoBreak': '\u2060',
'NonBreakingSpace': '\u00A0',
'nbsp': '\u00A0',
'Nopf': '\u2115',
'naturals': '\u2115',
'Not': '\u2AEC',
'NotCongruent': '\u2262',
'nequiv': '\u2262',
'NotCupCap': '\u226D',
'NotDoubleVerticalBar': '\u2226',
'npar': '\u2226',
'nparallel': '\u2226',
'nshortparallel': '\u2226',
'nspar': '\u2226',
'NotElement': '\u2209',
'notin': '\u2209',
'notinva': '\u2209',
'NotEqual': '\u2260',
'ne': '\u2260',
'NotEqualTilde': '\u2242\u0338',
'nesim': '\u2242\u0338',
'NotExists': '\u2204',
'nexist': '\u2204',
'nexists': '\u2204',
'NotGreater': '\u226F',
'ngt': '\u226F',
'ngtr': '\u226F',
'NotGreaterEqual': '\u2271',
'nge': '\u2271',
'ngeq': '\u2271',
'NotGreaterFullEqual': '\u2267\u0338',
'ngE': '\u2267\u0338',
'ngeqq': '\u2267\u0338',
'NotGreaterGreater': '\u226B\u0338',
'nGtv': '\u226B\u0338',
'NotGreaterLess': '\u2279',
'ntgl': '\u2279',
'NotGreaterSlantEqual': '\u2A7E\u0338',
'ngeqslant': '\u2A7E\u0338',
'nges': '\u2A7E\u0338',
'NotGreaterTilde': '\u2275',
'ngsim': '\u2275',
'NotHumpDownHump': '\u224E\u0338',
'nbump': '\u224E\u0338',
'NotHumpEqual': '\u224F\u0338',
'nbumpe': '\u224F\u0338',
'NotLeftTriangle': '\u22EA',
'nltri': '\u22EA',
'ntriangleleft': '\u22EA',
'NotLeftTriangleBar': '\u29CF\u0338',
'NotLeftTriangleEqual': '\u22EC',
'nltrie': '\u22EC',
'ntrianglelefteq': '\u22EC',
'NotLess': '\u226E',
'nless': '\u226E',
'nlt': '\u226E',
'NotLessEqual': '\u2270',
'nle': '\u2270',
'nleq': '\u2270',
'NotLessGreater': '\u2278',
'ntlg': '\u2278',
'NotLessLess': '\u226A\u0338',
'nLtv': '\u226A\u0338',
'NotLessSlantEqual': '\u2A7D\u0338',
'nleqslant': '\u2A7D\u0338',
'nles': '\u2A7D\u0338',
'NotLessTilde': '\u2274',
'nlsim': '\u2274',
'NotNestedGreaterGreater': '\u2AA2\u0338',
'NotNestedLessLess': '\u2AA1\u0338',
'NotPrecedes': '\u2280',
'npr': '\u2280',
'nprec': '\u2280',
'NotPrecedesEqual': '\u2AAF\u0338',
'npre': '\u2AAF\u0338',
'npreceq': '\u2AAF\u0338',
'NotPrecedesSlantEqual': '\u22E0',
'nprcue': '\u22E0',
'NotReverseElement': '\u220C',
'notni': '\u220C',
'notniva': '\u220C',
'NotRightTriangle': '\u22EB',
'nrtri': '\u22EB',
'ntriangleright': '\u22EB',
'NotRightTriangleBar': '\u29D0\u0338',
'NotRightTriangleEqual': '\u22ED',
'nrtrie': '\u22ED',
'ntrianglerighteq': '\u22ED',
'NotSquareSubset': '\u228F\u0338',
'NotSquareSubsetEqual': '\u22E2',
'nsqsube': '\u22E2',
'NotSquareSuperset': '\u2290\u0338',
'NotSquareSupersetEqual': '\u22E3',
'nsqsupe': '\u22E3',
'NotSubset': '\u2282\u20D2',
'nsubset': '\u2282\u20D2',
'vnsub': '\u2282\u20D2',
'NotSubsetEqual': '\u2288',
'nsube': '\u2288',
'nsubseteq': '\u2288',
'NotSucceeds': '\u2281',
'nsc': '\u2281',
'nsucc': '\u2281',
'NotSucceedsEqual': '\u2AB0\u0338',
'nsce': '\u2AB0\u0338',
'nsucceq': '\u2AB0\u0338',
'NotSucceedsSlantEqual': '\u22E1',
'nsccue': '\u22E1',
'NotSucceedsTilde': '\u227F\u0338',
'NotSuperset': '\u2283\u20D2',
'nsupset': '\u2283\u20D2',
'vnsup': '\u2283\u20D2',
'NotSupersetEqual': '\u2289',
'nsupe': '\u2289',
'nsupseteq': '\u2289',
'NotTilde': '\u2241',
'nsim': '\u2241',
'NotTildeEqual': '\u2244',
'nsime': '\u2244',
'nsimeq': '\u2244',
'NotTildeFullEqual': '\u2247',
'ncong': '\u2247',
'NotTildeTilde': '\u2249',
'nap': '\u2249',
'napprox': '\u2249',
'NotVerticalBar': '\u2224',
'nmid': '\u2224',
'nshortmid': '\u2224',
'nsmid': '\u2224',
'Nscr': '\uD835\uDCA9',
'Ntilde': '\u00D1',
'Nu': '\u039D',
'OElig': '\u0152',
'Oacute': '\u00D3',
'Ocirc': '\u00D4',
'Ocy': '\u041E',
'Odblac': '\u0150',
'Ofr': '\uD835\uDD12',
'Ograve': '\u00D2',
'Omacr': '\u014C',
'Omega': '\u03A9',
'ohm': '\u03A9',
'Omicron': '\u039F',
'Oopf': '\uD835\uDD46',
'OpenCurlyDoubleQuote': '\u201C',
'ldquo': '\u201C',
'OpenCurlyQuote': '\u2018',
'lsquo': '\u2018',
'Or': '\u2A54',
'Oscr': '\uD835\uDCAA',
'Oslash': '\u00D8',
'Otilde': '\u00D5',
'Otimes': '\u2A37',
'Ouml': '\u00D6',
'OverBar': '\u203E',
'oline': '\u203E',
'OverBrace': '\u23DE',
'OverBracket': '\u23B4',
'tbrk': '\u23B4',
'OverParenthesis': '\u23DC',
'PartialD': '\u2202',
'part': '\u2202',
'Pcy': '\u041F',
'Pfr': '\uD835\uDD13',
'Phi': '\u03A6',
'Pi': '\u03A0',
'PlusMinus': '\u00B1',
'plusmn': '\u00B1',
'pm': '\u00B1',
'Popf': '\u2119',
'primes': '\u2119',
'Pr': '\u2ABB',
'Precedes': '\u227A',
'pr': '\u227A',
'prec': '\u227A',
'PrecedesEqual': '\u2AAF',
'pre': '\u2AAF',
'preceq': '\u2AAF',
'PrecedesSlantEqual': '\u227C',
'prcue': '\u227C',
'preccurlyeq': '\u227C',
'PrecedesTilde': '\u227E',
'precsim': '\u227E',
'prsim': '\u227E',
'Prime': '\u2033',
'Product': '\u220F',
'prod': '\u220F',
'Proportional': '\u221D',
'prop': '\u221D',
'propto': '\u221D',
'varpropto': '\u221D',
'vprop': '\u221D',
'Pscr': '\uD835\uDCAB',
'Psi': '\u03A8',
'QUOT': '\u0022',
'quot': '\u0022',
'Qfr': '\uD835\uDD14',
'Qopf': '\u211A',
'rationals': '\u211A',
'Qscr': '\uD835\uDCAC',
'RBarr': '\u2910',
'drbkarow': '\u2910',
'REG': '\u00AE',
'circledR': '\u00AE',
'reg': '\u00AE',
'Racute': '\u0154',
'Rang': '\u27EB',
'Rarr': '\u21A0',
'twoheadrightarrow': '\u21A0',
'Rarrtl': '\u2916',
'Rcaron': '\u0158',
'Rcedil': '\u0156',
'Rcy': '\u0420',
'Re': '\u211C',
'Rfr': '\u211C',
'real': '\u211C',
'realpart': '\u211C',
'ReverseElement': '\u220B',
'SuchThat': '\u220B',
'ni': '\u220B',
'niv': '\u220B',
'ReverseEquilibrium': '\u21CB',
'leftrightharpoons': '\u21CB',
'lrhar': '\u21CB',
'ReverseUpEquilibrium': '\u296F',
'duhar': '\u296F',
'Rho': '\u03A1',
'RightAngleBracket': '\u27E9',
'rang': '\u27E9',
'rangle': '\u27E9',
'RightArrow': '\u2192',
'ShortRightArrow': '\u2192',
'rarr': '\u2192',
'rightarrow': '\u2192',
'srarr': '\u2192',
'RightArrowBar': '\u21E5',
'rarrb': '\u21E5',
'RightArrowLeftArrow': '\u21C4',
'rightleftarrows': '\u21C4',
'rlarr': '\u21C4',
'RightCeiling': '\u2309',
'rceil': '\u2309',
'RightDoubleBracket': '\u27E7',
'robrk': '\u27E7',
'RightDownTeeVector': '\u295D',
'RightDownVector': '\u21C2',
'dharr': '\u21C2',
'downharpoonright': '\u21C2',
'RightDownVectorBar': '\u2955',
'RightFloor': '\u230B',
'rfloor': '\u230B',
'RightTee': '\u22A2',
'vdash': '\u22A2',
'RightTeeArrow': '\u21A6',
'map': '\u21A6',
'mapsto': '\u21A6',
'RightTeeVector': '\u295B',
'RightTriangle': '\u22B3',
'vartriangleright': '\u22B3',
'vrtri': '\u22B3',
'RightTriangleBar': '\u29D0',
'RightTriangleEqual': '\u22B5',
'rtrie': '\u22B5',
'trianglerighteq': '\u22B5',
'RightUpDownVector': '\u294F',
'RightUpTeeVector': '\u295C',
'RightUpVector': '\u21BE',
'uharr': '\u21BE',
'upharpoonright': '\u21BE',
'RightUpVectorBar': '\u2954',
'RightVector': '\u21C0',
'rharu': '\u21C0',
'rightharpoonup': '\u21C0',
'RightVectorBar': '\u2953',
'Ropf': '\u211D',
'reals': '\u211D',
'RoundImplies': '\u2970',
'Rrightarrow': '\u21DB',
'rAarr': '\u21DB',
'Rscr': '\u211B',
'realine': '\u211B',
'Rsh': '\u21B1',
'rsh': '\u21B1',
'RuleDelayed': '\u29F4',
'SHCHcy': '\u0429',
'SHcy': '\u0428',
'SOFTcy': '\u042C',
'Sacute': '\u015A',
'Sc': '\u2ABC',
'Scaron': '\u0160',
'Scedil': '\u015E',
'Scirc': '\u015C',
'Scy': '\u0421',
'Sfr': '\uD835\uDD16',
'ShortUpArrow': '\u2191',
'UpArrow': '\u2191',
'uarr': '\u2191',
'uparrow': '\u2191',
'Sigma': '\u03A3',
'SmallCircle': '\u2218',
'compfn': '\u2218',
'Sopf': '\uD835\uDD4A',
'Sqrt': '\u221A',
'radic': '\u221A',
'Square': '\u25A1',
'squ': '\u25A1',
'square': '\u25A1',
'SquareIntersection': '\u2293',
'sqcap': '\u2293',
'SquareSubset': '\u228F',
'sqsub': '\u228F',
'sqsubset': '\u228F',
'SquareSubsetEqual': '\u2291',
'sqsube': '\u2291',
'sqsubseteq': '\u2291',
'SquareSuperset': '\u2290',
'sqsup': '\u2290',
'sqsupset': '\u2290',
'SquareSupersetEqual': '\u2292',
'sqsupe': '\u2292',
'sqsupseteq': '\u2292',
'SquareUnion': '\u2294',
'sqcup': '\u2294',
'Sscr': '\uD835\uDCAE',
'Star': '\u22C6',
'sstarf': '\u22C6',
'Sub': '\u22D0',
'Subset': '\u22D0',
'SubsetEqual': '\u2286',
'sube': '\u2286',
'subseteq': '\u2286',
'Succeeds': '\u227B',
'sc': '\u227B',
'succ': '\u227B',
'SucceedsEqual': '\u2AB0',
'sce': '\u2AB0',
'succeq': '\u2AB0',
'SucceedsSlantEqual': '\u227D',
'sccue': '\u227D',
'succcurlyeq': '\u227D',
'SucceedsTilde': '\u227F',
'scsim': '\u227F',
'succsim': '\u227F',
'Sum': '\u2211',
'sum': '\u2211',
'Sup': '\u22D1',
'Supset': '\u22D1',
'Superset': '\u2283',
'sup': '\u2283',
'supset': '\u2283',
'SupersetEqual': '\u2287',
'supe': '\u2287',
'supseteq': '\u2287',
'THORN': '\u00DE',
'TRADE': '\u2122',
'trade': '\u2122',
'TSHcy': '\u040B',
'TScy': '\u0426',
'Tab': '\u0009',
'Tau': '\u03A4',
'Tcaron': '\u0164',
'Tcedil': '\u0162',
'Tcy': '\u0422',
'Tfr': '\uD835\uDD17',
'Therefore': '\u2234',
'there4': '\u2234',
'therefore': '\u2234',
'Theta': '\u0398',
'ThickSpace': '\u205F\u200A',
'ThinSpace': '\u2009',
'thinsp': '\u2009',
'Tilde': '\u223C',
'sim': '\u223C',
'thicksim': '\u223C',
'thksim': '\u223C',
'TildeEqual': '\u2243',
'sime': '\u2243',
'simeq': '\u2243',
'TildeFullEqual': '\u2245',
'cong': '\u2245',
'TildeTilde': '\u2248',
'ap': '\u2248',
'approx': '\u2248',
'asymp': '\u2248',
'thickapprox': '\u2248',
'thkap': '\u2248',
'Topf': '\uD835\uDD4B',
'TripleDot': '\u20DB',
'tdot': '\u20DB',
'Tscr': '\uD835\uDCAF',
'Tstrok': '\u0166',
'Uacute': '\u00DA',
'Uarr': '\u219F',
'Uarrocir': '\u2949',
'Ubrcy': '\u040E',
'Ubreve': '\u016C',
'Ucirc': '\u00DB',
'Ucy': '\u0423',
'Udblac': '\u0170',
'Ufr': '\uD835\uDD18',
'Ugrave': '\u00D9',
'Umacr': '\u016A',
'UnderBar': '\u005F',
'lowbar': '\u005F',
'UnderBrace': '\u23DF',
'UnderBracket': '\u23B5',
'bbrk': '\u23B5',
'UnderParenthesis': '\u23DD',
'Union': '\u22C3',
'bigcup': '\u22C3',
'xcup': '\u22C3',
'UnionPlus': '\u228E',
'uplus': '\u228E',
'Uogon': '\u0172',
'Uopf': '\uD835\uDD4C',
'UpArrowBar': '\u2912',
'UpArrowDownArrow': '\u21C5',
'udarr': '\u21C5',
'UpDownArrow': '\u2195',
'updownarrow': '\u2195',
'varr': '\u2195',
'UpEquilibrium': '\u296E',
'udhar': '\u296E',
'UpTee': '\u22A5',
'bot': '\u22A5',
'bottom': '\u22A5',
'perp': '\u22A5',
'UpTeeArrow': '\u21A5',
'mapstoup': '\u21A5',
'UpperLeftArrow': '\u2196',
'nwarr': '\u2196',
'nwarrow': '\u2196',
'UpperRightArrow': '\u2197',
'nearr': '\u2197',
'nearrow': '\u2197',
'Upsi': '\u03D2',
'upsih': '\u03D2',
'Upsilon': '\u03A5',
'Uring': '\u016E',
'Uscr': '\uD835\uDCB0',
'Utilde': '\u0168',
'Uuml': '\u00DC',
'VDash': '\u22AB',
'Vbar': '\u2AEB',
'Vcy': '\u0412',
'Vdash': '\u22A9',
'Vdashl': '\u2AE6',
'Vee': '\u22C1',
'bigvee': '\u22C1',
'xvee': '\u22C1',
'Verbar': '\u2016',
'Vert': '\u2016',
'VerticalBar': '\u2223',
'mid': '\u2223',
'shortmid': '\u2223',
'smid': '\u2223',
'VerticalLine': '\u007C',
'verbar': '\u007C',
'vert': '\u007C',
'VerticalSeparator': '\u2758',
'VerticalTilde': '\u2240',
'wr': '\u2240',
'wreath': '\u2240',
'VeryThinSpace': '\u200A',
'hairsp': '\u200A',
'Vfr': '\uD835\uDD19',
'Vopf': '\uD835\uDD4D',
'Vscr': '\uD835\uDCB1',
'Vvdash': '\u22AA',
'Wcirc': '\u0174',
'Wedge': '\u22C0',
'bigwedge': '\u22C0',
'xwedge': '\u22C0',
'Wfr': '\uD835\uDD1A',
'Wopf': '\uD835\uDD4E',
'Wscr': '\uD835\uDCB2',
'Xfr': '\uD835\uDD1B',
'Xi': '\u039E',
'Xopf': '\uD835\uDD4F',
'Xscr': '\uD835\uDCB3',
'YAcy': '\u042F',
'YIcy': '\u0407',
'YUcy': '\u042E',
'Yacute': '\u00DD',
'Ycirc': '\u0176',
'Ycy': '\u042B',
'Yfr': '\uD835\uDD1C',
'Yopf': '\uD835\uDD50',
'Yscr': '\uD835\uDCB4',
'Yuml': '\u0178',
'ZHcy': '\u0416',
'Zacute': '\u0179',
'Zcaron': '\u017D',
'Zcy': '\u0417',
'Zdot': '\u017B',
'Zeta': '\u0396',
'Zfr': '\u2128',
'zeetrf': '\u2128',
'Zopf': '\u2124',
'integers': '\u2124',
'Zscr': '\uD835\uDCB5',
'aacute': '\u00E1',
'abreve': '\u0103',
'ac': '\u223E',
'mstpos': '\u223E',
'acE': '\u223E\u0333',
'acd': '\u223F',
'acirc': '\u00E2',
'acy': '\u0430',
'aelig': '\u00E6',
'afr': '\uD835\uDD1E',
'agrave': '\u00E0',
'alefsym': '\u2135',
'aleph': '\u2135',
'alpha': '\u03B1',
'amacr': '\u0101',
'amalg': '\u2A3F',
'and': '\u2227',
'wedge': '\u2227',
'andand': '\u2A55',
'andd': '\u2A5C',
'andslope': '\u2A58',
'andv': '\u2A5A',
'ang': '\u2220',
'angle': '\u2220',
'ange': '\u29A4',
'angmsd': '\u2221',
'measuredangle': '\u2221',
'angmsdaa': '\u29A8',
'angmsdab': '\u29A9',
'angmsdac': '\u29AA',
'angmsdad': '\u29AB',
'angmsdae': '\u29AC',
'angmsdaf': '\u29AD',
'angmsdag': '\u29AE',
'angmsdah': '\u29AF',
'angrt': '\u221F',
'angrtvb': '\u22BE',
'angrtvbd': '\u299D',
'angsph': '\u2222',
'angzarr': '\u237C',
'aogon': '\u0105',
'aopf': '\uD835\uDD52',
'apE': '\u2A70',
'apacir': '\u2A6F',
'ape': '\u224A',
'approxeq': '\u224A',
'apid': '\u224B',
'apos': '\u0027',
'aring': '\u00E5',
'ascr': '\uD835\uDCB6',
'ast': '\u002A',
'midast': '\u002A',
'atilde': '\u00E3',
'auml': '\u00E4',
'awint': '\u2A11',
'bNot': '\u2AED',
'backcong': '\u224C',
'bcong': '\u224C',
'backepsilon': '\u03F6',
'bepsi': '\u03F6',
'backprime': '\u2035',
'bprime': '\u2035',
'backsim': '\u223D',
'bsim': '\u223D',
'backsimeq': '\u22CD',
'bsime': '\u22CD',
'barvee': '\u22BD',
'barwed': '\u2305',
'barwedge': '\u2305',
'bbrktbrk': '\u23B6',
'bcy': '\u0431',
'bdquo': '\u201E',
'ldquor': '\u201E',
'bemptyv': '\u29B0',
'beta': '\u03B2',
'beth': '\u2136',
'between': '\u226C',
'twixt': '\u226C',
'bfr': '\uD835\uDD1F',
'bigcirc': '\u25EF',
'xcirc': '\u25EF',
'bigodot': '\u2A00',
'xodot': '\u2A00',
'bigoplus': '\u2A01',
'xoplus': '\u2A01',
'bigotimes': '\u2A02',
'xotime': '\u2A02',
'bigsqcup': '\u2A06',
'xsqcup': '\u2A06',
'bigstar': '\u2605',
'starf': '\u2605',
'bigtriangledown': '\u25BD',
'xdtri': '\u25BD',
'bigtriangleup': '\u25B3',
'xutri': '\u25B3',
'biguplus': '\u2A04',
'xuplus': '\u2A04',
'bkarow': '\u290D',
'rbarr': '\u290D',
'blacklozenge': '\u29EB',
'lozf': '\u29EB',
'blacktriangle': '\u25B4',
'utrif': '\u25B4',
'blacktriangledown': '\u25BE',
'dtrif': '\u25BE',
'blacktriangleleft': '\u25C2',
'ltrif': '\u25C2',
'blacktriangleright': '\u25B8',
'rtrif': '\u25B8',
'blank': '\u2423',
'blk12': '\u2592',
'blk14': '\u2591',
'blk34': '\u2593',
'block': '\u2588',
'bne': '\u003D\u20E5',
'bnequiv': '\u2261\u20E5',
'bnot': '\u2310',
'bopf': '\uD835\uDD53',
'bowtie': '\u22C8',
'boxDL': '\u2557',
'boxDR': '\u2554',
'boxDl': '\u2556',
'boxDr': '\u2553',
'boxH': '\u2550',
'boxHD': '\u2566',
'boxHU': '\u2569',
'boxHd': '\u2564',
'boxHu': '\u2567',
'boxUL': '\u255D',
'boxUR': '\u255A',
'boxUl': '\u255C',
'boxUr': '\u2559',
'boxV': '\u2551',
'boxVH': '\u256C',
'boxVL': '\u2563',
'boxVR': '\u2560',
'boxVh': '\u256B',
'boxVl': '\u2562',
'boxVr': '\u255F',
'boxbox': '\u29C9',
'boxdL': '\u2555',
'boxdR': '\u2552',
'boxdl': '\u2510',
'boxdr': '\u250C',
'boxhD': '\u2565',
'boxhU': '\u2568',
'boxhd': '\u252C',
'boxhu': '\u2534',
'boxminus': '\u229F',
'minusb': '\u229F',
'boxplus': '\u229E',
'plusb': '\u229E',
'boxtimes': '\u22A0',
'timesb': '\u22A0',
'boxuL': '\u255B',
'boxuR': '\u2558',
'boxul': '\u2518',
'boxur': '\u2514',
'boxv': '\u2502',
'boxvH': '\u256A',
'boxvL': '\u2561',
'boxvR': '\u255E',
'boxvh': '\u253C',
'boxvl': '\u2524',
'boxvr': '\u251C',
'brvbar': '\u00A6',
'bscr': '\uD835\uDCB7',
'bsemi': '\u204F',
'bsol': '\u005C',
'bsolb': '\u29C5',
'bsolhsub': '\u27C8',
'bull': '\u2022',
'bullet': '\u2022',
'bumpE': '\u2AAE',
'cacute': '\u0107',
'cap': '\u2229',
'capand': '\u2A44',
'capbrcup': '\u2A49',
'capcap': '\u2A4B',
'capcup': '\u2A47',
'capdot': '\u2A40',
'caps': '\u2229\uFE00',
'caret': '\u2041',
'ccaps': '\u2A4D',
'ccaron': '\u010D',
'ccedil': '\u00E7',
'ccirc': '\u0109',
'ccups': '\u2A4C',
'ccupssm': '\u2A50',
'cdot': '\u010B',
'cemptyv': '\u29B2',
'cent': '\u00A2',
'cfr': '\uD835\uDD20',
'chcy': '\u0447',
'check': '\u2713',
'checkmark': '\u2713',
'chi': '\u03C7',
'cir': '\u25CB',
'cirE': '\u29C3',
'circ': '\u02C6',
'circeq': '\u2257',
'cire': '\u2257',
'circlearrowleft': '\u21BA',
'olarr': '\u21BA',
'circlearrowright': '\u21BB',
'orarr': '\u21BB',
'circledS': '\u24C8',
'oS': '\u24C8',
'circledast': '\u229B',
'oast': '\u229B',
'circledcirc': '\u229A',
'ocir': '\u229A',
'circleddash': '\u229D',
'odash': '\u229D',
'cirfnint': '\u2A10',
'cirmid': '\u2AEF',
'cirscir': '\u29C2',
'clubs': '\u2663',
'clubsuit': '\u2663',
'colon': '\u003A',
'comma': '\u002C',
'commat': '\u0040',
'comp': '\u2201',
'complement': '\u2201',
'congdot': '\u2A6D',
'copf': '\uD835\uDD54',
'copysr': '\u2117',
'crarr': '\u21B5',
'cross': '\u2717',
'cscr': '\uD835\uDCB8',
'csub': '\u2ACF',
'csube': '\u2AD1',
'csup': '\u2AD0',
'csupe': '\u2AD2',
'ctdot': '\u22EF',
'cudarrl': '\u2938',
'cudarrr': '\u2935',
'cuepr': '\u22DE',
'curlyeqprec': '\u22DE',
'cuesc': '\u22DF',
'curlyeqsucc': '\u22DF',
'cularr': '\u21B6',
'curvearrowleft': '\u21B6',
'cularrp': '\u293D',
'cup': '\u222A',
'cupbrcap': '\u2A48',
'cupcap': '\u2A46',
'cupcup': '\u2A4A',
'cupdot': '\u228D',
'cupor': '\u2A45',
'cups': '\u222A\uFE00',
'curarr': '\u21B7',
'curvearrowright': '\u21B7',
'curarrm': '\u293C',
'curlyvee': '\u22CE',
'cuvee': '\u22CE',
'curlywedge': '\u22CF',
'cuwed': '\u22CF',
'curren': '\u00A4',
'cwint': '\u2231',
'cylcty': '\u232D',
'dHar': '\u2965',
'dagger': '\u2020',
'daleth': '\u2138',
'dash': '\u2010',
'hyphen': '\u2010',
'dbkarow': '\u290F',
'rBarr': '\u290F',
'dcaron': '\u010F',
'dcy': '\u0434',
'ddarr': '\u21CA',
'downdownarrows': '\u21CA',
'ddotseq': '\u2A77',
'eDDot': '\u2A77',
'deg': '\u00B0',
'delta': '\u03B4',
'demptyv': '\u29B1',
'dfisht': '\u297F',
'dfr': '\uD835\uDD21',
'diamondsuit': '\u2666',
'diams': '\u2666',
'digamma': '\u03DD',
'gammad': '\u03DD',
'disin': '\u22F2',
'div': '\u00F7',
'divide': '\u00F7',
'divideontimes': '\u22C7',
'divonx': '\u22C7',
'djcy': '\u0452',
'dlcorn': '\u231E',
'llcorner': '\u231E',
'dlcrop': '\u230D',
'dollar': '\u0024',
'dopf': '\uD835\uDD55',
'doteqdot': '\u2251',
'eDot': '\u2251',
'dotminus': '\u2238',
'minusd': '\u2238',
'dotplus': '\u2214',
'plusdo': '\u2214',
'dotsquare': '\u22A1',
'sdotb': '\u22A1',
'drcorn': '\u231F',
'lrcorner': '\u231F',
'drcrop': '\u230C',
'dscr': '\uD835\uDCB9',
'dscy': '\u0455',
'dsol': '\u29F6',
'dstrok': '\u0111',
'dtdot': '\u22F1',
'dtri': '\u25BF',
'triangledown': '\u25BF',
'dwangle': '\u29A6',
'dzcy': '\u045F',
'dzigrarr': '\u27FF',
'eacute': '\u00E9',
'easter': '\u2A6E',
'ecaron': '\u011B',
'ecir': '\u2256',
'eqcirc': '\u2256',
'ecirc': '\u00EA',
'ecolon': '\u2255',
'eqcolon': '\u2255',
'ecy': '\u044D',
'edot': '\u0117',
'efDot': '\u2252',
'fallingdotseq': '\u2252',
'efr': '\uD835\uDD22',
'eg': '\u2A9A',
'egrave': '\u00E8',
'egs': '\u2A96',
'eqslantgtr': '\u2A96',
'egsdot': '\u2A98',
'el': '\u2A99',
'elinters': '\u23E7',
'ell': '\u2113',
'els': '\u2A95',
'eqslantless': '\u2A95',
'elsdot': '\u2A97',
'emacr': '\u0113',
'empty': '\u2205',
'emptyset': '\u2205',
'emptyv': '\u2205',
'varnothing': '\u2205',
'emsp13': '\u2004',
'emsp14': '\u2005',
'emsp': '\u2003',
'eng': '\u014B',
'ensp': '\u2002',
'eogon': '\u0119',
'eopf': '\uD835\uDD56',
'epar': '\u22D5',
'eparsl': '\u29E3',
'eplus': '\u2A71',
'epsi': '\u03B5',
'epsilon': '\u03B5',
'epsiv': '\u03F5',
'straightepsilon': '\u03F5',
'varepsilon': '\u03F5',
'equals': '\u003D',
'equest': '\u225F',
'questeq': '\u225F',
'equivDD': '\u2A78',
'eqvparsl': '\u29E5',
'erDot': '\u2253',
'risingdotseq': '\u2253',
'erarr': '\u2971',
'escr': '\u212F',
'eta': '\u03B7',
'eth': '\u00F0',
'euml': '\u00EB',
'euro': '\u20AC',
'excl': '\u0021',
'fcy': '\u0444',
'female': '\u2640',
'ffilig': '\uFB03',
'fflig': '\uFB00',
'ffllig': '\uFB04',
'ffr': '\uD835\uDD23',
'filig': '\uFB01',
'fjlig': '\u0066\u006A',
'flat': '\u266D',
'fllig': '\uFB02',
'fltns': '\u25B1',
'fnof': '\u0192',
'fopf': '\uD835\uDD57',
'fork': '\u22D4',
'pitchfork': '\u22D4',
'forkv': '\u2AD9',
'fpartint': '\u2A0D',
'frac12': '\u00BD',
'half': '\u00BD',
'frac13': '\u2153',
'frac14': '\u00BC',
'frac15': '\u2155',
'frac16': '\u2159',
'frac18': '\u215B',
'frac23': '\u2154',
'frac25': '\u2156',
'frac34': '\u00BE',
'frac35': '\u2157',
'frac38': '\u215C',
'frac45': '\u2158',
'frac56': '\u215A',
'frac58': '\u215D',
'frac78': '\u215E',
'frasl': '\u2044',
'frown': '\u2322',
'sfrown': '\u2322',
'fscr': '\uD835\uDCBB',
'gEl': '\u2A8C',
'gtreqqless': '\u2A8C',
'gacute': '\u01F5',
'gamma': '\u03B3',
'gap': '\u2A86',
'gtrapprox': '\u2A86',
'gbreve': '\u011F',
'gcirc': '\u011D',
'gcy': '\u0433',
'gdot': '\u0121',
'gescc': '\u2AA9',
'gesdot': '\u2A80',
'gesdoto': '\u2A82',
'gesdotol': '\u2A84',
'gesl': '\u22DB\uFE00',
'gesles': '\u2A94',
'gfr': '\uD835\uDD24',
'gimel': '\u2137',
'gjcy': '\u0453',
'glE': '\u2A92',
'gla': '\u2AA5',
'glj': '\u2AA4',
'gnE': '\u2269',
'gneqq': '\u2269',
'gnap': '\u2A8A',
'gnapprox': '\u2A8A',
'gne': '\u2A88',
'gneq': '\u2A88',
'gnsim': '\u22E7',
'gopf': '\uD835\uDD58',
'gscr': '\u210A',
'gsime': '\u2A8E',
'gsiml': '\u2A90',
'gtcc': '\u2AA7',
'gtcir': '\u2A7A',
'gtdot': '\u22D7',
'gtrdot': '\u22D7',
'gtlPar': '\u2995',
'gtquest': '\u2A7C',
'gtrarr': '\u2978',
'gvertneqq': '\u2269\uFE00',
'gvnE': '\u2269\uFE00',
'hardcy': '\u044A',
'harrcir': '\u2948',
'harrw': '\u21AD',
'leftrightsquigarrow': '\u21AD',
'hbar': '\u210F',
'hslash': '\u210F',
'planck': '\u210F',
'plankv': '\u210F',
'hcirc': '\u0125',
'hearts': '\u2665',
'heartsuit': '\u2665',
'hellip': '\u2026',
'mldr': '\u2026',
'hercon': '\u22B9',
'hfr': '\uD835\uDD25',
'hksearow': '\u2925',
'searhk': '\u2925',
'hkswarow': '\u2926',
'swarhk': '\u2926',
'hoarr': '\u21FF',
'homtht': '\u223B',
'hookleftarrow': '\u21A9',
'larrhk': '\u21A9',
'hookrightarrow': '\u21AA',
'rarrhk': '\u21AA',
'hopf': '\uD835\uDD59',
'horbar': '\u2015',
'hscr': '\uD835\uDCBD',
'hstrok': '\u0127',
'hybull': '\u2043',
'iacute': '\u00ED',
'icirc': '\u00EE',
'icy': '\u0438',
'iecy': '\u0435',
'iexcl': '\u00A1',
'ifr': '\uD835\uDD26',
'igrave': '\u00EC',
'iiiint': '\u2A0C',
'qint': '\u2A0C',
'iiint': '\u222D',
'tint': '\u222D',
'iinfin': '\u29DC',
'iiota': '\u2129',
'ijlig': '\u0133',
'imacr': '\u012B',
'imath': '\u0131',
'inodot': '\u0131',
'imof': '\u22B7',
'imped': '\u01B5',
'incare': '\u2105',
'infin': '\u221E',
'infintie': '\u29DD',
'intcal': '\u22BA',
'intercal': '\u22BA',
'intlarhk': '\u2A17',
'intprod': '\u2A3C',
'iprod': '\u2A3C',
'iocy': '\u0451',
'iogon': '\u012F',
'iopf': '\uD835\uDD5A',
'iota': '\u03B9',
'iquest': '\u00BF',
'iscr': '\uD835\uDCBE',
'isinE': '\u22F9',
'isindot': '\u22F5',
'isins': '\u22F4',
'isinsv': '\u22F3',
'itilde': '\u0129',
'iukcy': '\u0456',
'iuml': '\u00EF',
'jcirc': '\u0135',
'jcy': '\u0439',
'jfr': '\uD835\uDD27',
'jmath': '\u0237',
'jopf': '\uD835\uDD5B',
'jscr': '\uD835\uDCBF',
'jsercy': '\u0458',
'jukcy': '\u0454',
'kappa': '\u03BA',
'kappav': '\u03F0',
'varkappa': '\u03F0',
'kcedil': '\u0137',
'kcy': '\u043A',
'kfr': '\uD835\uDD28',
'kgreen': '\u0138',
'khcy': '\u0445',
'kjcy': '\u045C',
'kopf': '\uD835\uDD5C',
'kscr': '\uD835\uDCC0',
'lAtail': '\u291B',
'lBarr': '\u290E',
'lEg': '\u2A8B',
'lesseqqgtr': '\u2A8B',
'lHar': '\u2962',
'lacute': '\u013A',
'laemptyv': '\u29B4',
'lambda': '\u03BB',
'langd': '\u2991',
'lap': '\u2A85',
'lessapprox': '\u2A85',
'laquo': '\u00AB',
'larrbfs': '\u291F',
'larrfs': '\u291D',
'larrlp': '\u21AB',
'looparrowleft': '\u21AB',
'larrpl': '\u2939',
'larrsim': '\u2973',
'larrtl': '\u21A2',
'leftarrowtail': '\u21A2',
'lat': '\u2AAB',
'latail': '\u2919',
'late': '\u2AAD',
'lates': '\u2AAD\uFE00',
'lbarr': '\u290C',
'lbbrk': '\u2772',
'lbrace': '\u007B',
'lcub': '\u007B',
'lbrack': '\u005B',
'lsqb': '\u005B',
'lbrke': '\u298B',
'lbrksld': '\u298F',
'lbrkslu': '\u298D',
'lcaron': '\u013E',
'lcedil': '\u013C',
'lcy': '\u043B',
'ldca': '\u2936',
'ldrdhar': '\u2967',
'ldrushar': '\u294B',
'ldsh': '\u21B2',
'le': '\u2264',
'leq': '\u2264',
'leftleftarrows': '\u21C7',
'llarr': '\u21C7',
'leftthreetimes': '\u22CB',
'lthree': '\u22CB',
'lescc': '\u2AA8',
'lesdot': '\u2A7F',
'lesdoto': '\u2A81',
'lesdotor': '\u2A83',
'lesg': '\u22DA\uFE00',
'lesges': '\u2A93',
'lessdot': '\u22D6',
'ltdot': '\u22D6',
'lfisht': '\u297C',
'lfr': '\uD835\uDD29',
'lgE': '\u2A91',
'lharul': '\u296A',
'lhblk': '\u2584',
'ljcy': '\u0459',
'llhard': '\u296B',
'lltri': '\u25FA',
'lmidot': '\u0140',
'lmoust': '\u23B0',
'lmoustache': '\u23B0',
'lnE': '\u2268',
'lneqq': '\u2268',
'lnap': '\u2A89',
'lnapprox': '\u2A89',
'lne': '\u2A87',
'lneq': '\u2A87',
'lnsim': '\u22E6',
'loang': '\u27EC',
'loarr': '\u21FD',
'longmapsto': '\u27FC',
'xmap': '\u27FC',
'looparrowright': '\u21AC',
'rarrlp': '\u21AC',
'lopar': '\u2985',
'lopf': '\uD835\uDD5D',
'loplus': '\u2A2D',
'lotimes': '\u2A34',
'lowast': '\u2217',
'loz': '\u25CA',
'lozenge': '\u25CA',
'lpar': '\u0028',
'lparlt': '\u2993',
'lrhard': '\u296D',
'lrm': '\u200E',
'lrtri': '\u22BF',
'lsaquo': '\u2039',
'lscr': '\uD835\uDCC1',
'lsime': '\u2A8D',
'lsimg': '\u2A8F',
'lsquor': '\u201A',
'sbquo': '\u201A',
'lstrok': '\u0142',
'ltcc': '\u2AA6',
'ltcir': '\u2A79',
'ltimes': '\u22C9',
'ltlarr': '\u2976',
'ltquest': '\u2A7B',
'ltrPar': '\u2996',
'ltri': '\u25C3',
'triangleleft': '\u25C3',
'lurdshar': '\u294A',
'luruhar': '\u2966',
'lvertneqq': '\u2268\uFE00',
'lvnE': '\u2268\uFE00',
'mDDot': '\u223A',
'macr': '\u00AF',
'strns': '\u00AF',
'male': '\u2642',
'malt': '\u2720',
'maltese': '\u2720',
'marker': '\u25AE',
'mcomma': '\u2A29',
'mcy': '\u043C',
'mdash': '\u2014',
'mfr': '\uD835\uDD2A',
'mho': '\u2127',
'micro': '\u00B5',
'midcir': '\u2AF0',
'minus': '\u2212',
'minusdu': '\u2A2A',
'mlcp': '\u2ADB',
'models': '\u22A7',
'mopf': '\uD835\uDD5E',
'mscr': '\uD835\uDCC2',
'mu': '\u03BC',
'multimap': '\u22B8',
'mumap': '\u22B8',
'nGg': '\u22D9\u0338',
'nGt': '\u226B\u20D2',
'nLeftarrow': '\u21CD',
'nlArr': '\u21CD',
'nLeftrightarrow': '\u21CE',
'nhArr': '\u21CE',
'nLl': '\u22D8\u0338',
'nLt': '\u226A\u20D2',
'nRightarrow': '\u21CF',
'nrArr': '\u21CF',
'nVDash': '\u22AF',
'nVdash': '\u22AE',
'nacute': '\u0144',
'nang': '\u2220\u20D2',
'napE': '\u2A70\u0338',
'napid': '\u224B\u0338',
'napos': '\u0149',
'natur': '\u266E',
'natural': '\u266E',
'ncap': '\u2A43',
'ncaron': '\u0148',
'ncedil': '\u0146',
'ncongdot': '\u2A6D\u0338',
'ncup': '\u2A42',
'ncy': '\u043D',
'ndash': '\u2013',
'neArr': '\u21D7',
'nearhk': '\u2924',
'nedot': '\u2250\u0338',
'nesear': '\u2928',
'toea': '\u2928',
'nfr': '\uD835\uDD2B',
'nharr': '\u21AE',
'nleftrightarrow': '\u21AE',
'nhpar': '\u2AF2',
'nis': '\u22FC',
'nisd': '\u22FA',
'njcy': '\u045A',
'nlE': '\u2266\u0338',
'nleqq': '\u2266\u0338',
'nlarr': '\u219A',
'nleftarrow': '\u219A',
'nldr': '\u2025',
'nopf': '\uD835\uDD5F',
'not': '\u00AC',
'notinE': '\u22F9\u0338',
'notindot': '\u22F5\u0338',
'notinvb': '\u22F7',
'notinvc': '\u22F6',
'notnivb': '\u22FE',
'notnivc': '\u22FD',
'nparsl': '\u2AFD\u20E5',
'npart': '\u2202\u0338',
'npolint': '\u2A14',
'nrarr': '\u219B',
'nrightarrow': '\u219B',
'nrarrc': '\u2933\u0338',
'nrarrw': '\u219D\u0338',
'nscr': '\uD835\uDCC3',
'nsub': '\u2284',
'nsubE': '\u2AC5\u0338',
'nsubseteqq': '\u2AC5\u0338',
'nsup': '\u2285',
'nsupE': '\u2AC6\u0338',
'nsupseteqq': '\u2AC6\u0338',
'ntilde': '\u00F1',
'nu': '\u03BD',
'num': '\u0023',
'numero': '\u2116',
'numsp': '\u2007',
'nvDash': '\u22AD',
'nvHarr': '\u2904',
'nvap': '\u224D\u20D2',
'nvdash': '\u22AC',
'nvge': '\u2265\u20D2',
'nvgt': '\u003E\u20D2',
'nvinfin': '\u29DE',
'nvlArr': '\u2902',
'nvle': '\u2264\u20D2',
'nvlt': '\u003C\u20D2',
'nvltrie': '\u22B4\u20D2',
'nvrArr': '\u2903',
'nvrtrie': '\u22B5\u20D2',
'nvsim': '\u223C\u20D2',
'nwArr': '\u21D6',
'nwarhk': '\u2923',
'nwnear': '\u2927',
'oacute': '\u00F3',
'ocirc': '\u00F4',
'ocy': '\u043E',
'odblac': '\u0151',
'odiv': '\u2A38',
'odsold': '\u29BC',
'oelig': '\u0153',
'ofcir': '\u29BF',
'ofr': '\uD835\uDD2C',
'ogon': '\u02DB',
'ograve': '\u00F2',
'ogt': '\u29C1',
'ohbar': '\u29B5',
'olcir': '\u29BE',
'olcross': '\u29BB',
'olt': '\u29C0',
'omacr': '\u014D',
'omega': '\u03C9',
'omicron': '\u03BF',
'omid': '\u29B6',
'oopf': '\uD835\uDD60',
'opar': '\u29B7',
'operp': '\u29B9',
'or': '\u2228',
'vee': '\u2228',
'ord': '\u2A5D',
'order': '\u2134',
'orderof': '\u2134',
'oscr': '\u2134',
'ordf': '\u00AA',
'ordm': '\u00BA',
'origof': '\u22B6',
'oror': '\u2A56',
'orslope': '\u2A57',
'orv': '\u2A5B',
'oslash': '\u00F8',
'osol': '\u2298',
'otilde': '\u00F5',
'otimesas': '\u2A36',
'ouml': '\u00F6',
'ovbar': '\u233D',
'para': '\u00B6',
'parsim': '\u2AF3',
'parsl': '\u2AFD',
'pcy': '\u043F',
'percnt': '\u0025',
'period': '\u002E',
'permil': '\u2030',
'pertenk': '\u2031',
'pfr': '\uD835\uDD2D',
'phi': '\u03C6',
'phiv': '\u03D5',
'straightphi': '\u03D5',
'varphi': '\u03D5',
'phone': '\u260E',
'pi': '\u03C0',
'piv': '\u03D6',
'varpi': '\u03D6',
'planckh': '\u210E',
'plus': '\u002B',
'plusacir': '\u2A23',
'pluscir': '\u2A22',
'plusdu': '\u2A25',
'pluse': '\u2A72',
'plussim': '\u2A26',
'plustwo': '\u2A27',
'pointint': '\u2A15',
'popf': '\uD835\uDD61',
'pound': '\u00A3',
'prE': '\u2AB3',
'prap': '\u2AB7',
'precapprox': '\u2AB7',
'precnapprox': '\u2AB9',
'prnap': '\u2AB9',
'precneqq': '\u2AB5',
'prnE': '\u2AB5',
'precnsim': '\u22E8',
'prnsim': '\u22E8',
'prime': '\u2032',
'profalar': '\u232E',
'profline': '\u2312',
'profsurf': '\u2313',
'prurel': '\u22B0',
'pscr': '\uD835\uDCC5',
'psi': '\u03C8',
'puncsp': '\u2008',
'qfr': '\uD835\uDD2E',
'qopf': '\uD835\uDD62',
'qprime': '\u2057',
'qscr': '\uD835\uDCC6',
'quatint': '\u2A16',
'quest': '\u003F',
'rAtail': '\u291C',
'rHar': '\u2964',
'race': '\u223D\u0331',
'racute': '\u0155',
'raemptyv': '\u29B3',
'rangd': '\u2992',
'range': '\u29A5',
'raquo': '\u00BB',
'rarrap': '\u2975',
'rarrbfs': '\u2920',
'rarrc': '\u2933',
'rarrfs': '\u291E',
'rarrpl': '\u2945',
'rarrsim': '\u2974',
'rarrtl': '\u21A3',
'rightarrowtail': '\u21A3',
'rarrw': '\u219D',
'rightsquigarrow': '\u219D',
'ratail': '\u291A',
'ratio': '\u2236',
'rbbrk': '\u2773',
'rbrace': '\u007D',
'rcub': '\u007D',
'rbrack': '\u005D',
'rsqb': '\u005D',
'rbrke': '\u298C',
'rbrksld': '\u298E',
'rbrkslu': '\u2990',
'rcaron': '\u0159',
'rcedil': '\u0157',
'rcy': '\u0440',
'rdca': '\u2937',
'rdldhar': '\u2969',
'rdsh': '\u21B3',
'rect': '\u25AD',
'rfisht': '\u297D',
'rfr': '\uD835\uDD2F',
'rharul': '\u296C',
'rho': '\u03C1',
'rhov': '\u03F1',
'varrho': '\u03F1',
'rightrightarrows': '\u21C9',
'rrarr': '\u21C9',
'rightthreetimes': '\u22CC',
'rthree': '\u22CC',
'ring': '\u02DA',
'rlm': '\u200F',
'rmoust': '\u23B1',
'rmoustache': '\u23B1',
'rnmid': '\u2AEE',
'roang': '\u27ED',
'roarr': '\u21FE',
'ropar': '\u2986',
'ropf': '\uD835\uDD63',
'roplus': '\u2A2E',
'rotimes': '\u2A35',
'rpar': '\u0029',
'rpargt': '\u2994',
'rppolint': '\u2A12',
'rsaquo': '\u203A',
'rscr': '\uD835\uDCC7',
'rtimes': '\u22CA',
'rtri': '\u25B9',
'triangleright': '\u25B9',
'rtriltri': '\u29CE',
'ruluhar': '\u2968',
'rx': '\u211E',
'sacute': '\u015B',
'scE': '\u2AB4',
'scap': '\u2AB8',
'succapprox': '\u2AB8',
'scaron': '\u0161',
'scedil': '\u015F',
'scirc': '\u015D',
'scnE': '\u2AB6',
'succneqq': '\u2AB6',
'scnap': '\u2ABA',
'succnapprox': '\u2ABA',
'scnsim': '\u22E9',
'succnsim': '\u22E9',
'scpolint': '\u2A13',
'scy': '\u0441',
'sdot': '\u22C5',
'sdote': '\u2A66',
'seArr': '\u21D8',
'sect': '\u00A7',
'semi': '\u003B',
'seswar': '\u2929',
'tosa': '\u2929',
'sext': '\u2736',
'sfr': '\uD835\uDD30',
'sharp': '\u266F',
'shchcy': '\u0449',
'shcy': '\u0448',
'shy': '\u00AD',
'sigma': '\u03C3',
'sigmaf': '\u03C2',
'sigmav': '\u03C2',
'varsigma': '\u03C2',
'simdot': '\u2A6A',
'simg': '\u2A9E',
'simgE': '\u2AA0',
'siml': '\u2A9D',
'simlE': '\u2A9F',
'simne': '\u2246',
'simplus': '\u2A24',
'simrarr': '\u2972',
'smashp': '\u2A33',
'smeparsl': '\u29E4',
'smile': '\u2323',
'ssmile': '\u2323',
'smt': '\u2AAA',
'smte': '\u2AAC',
'smtes': '\u2AAC\uFE00',
'softcy': '\u044C',
'sol': '\u002F',
'solb': '\u29C4',
'solbar': '\u233F',
'sopf': '\uD835\uDD64',
'spades': '\u2660',
'spadesuit': '\u2660',
'sqcaps': '\u2293\uFE00',
'sqcups': '\u2294\uFE00',
'sscr': '\uD835\uDCC8',
'star': '\u2606',
'sub': '\u2282',
'subset': '\u2282',
'subE': '\u2AC5',
'subseteqq': '\u2AC5',
'subdot': '\u2ABD',
'subedot': '\u2AC3',
'submult': '\u2AC1',
'subnE': '\u2ACB',
'subsetneqq': '\u2ACB',
'subne': '\u228A',
'subsetneq': '\u228A',
'subplus': '\u2ABF',
'subrarr': '\u2979',
'subsim': '\u2AC7',
'subsub': '\u2AD5',
'subsup': '\u2AD3',
'sung': '\u266A',
'sup1': '\u00B9',
'sup2': '\u00B2',
'sup3': '\u00B3',
'supE': '\u2AC6',
'supseteqq': '\u2AC6',
'supdot': '\u2ABE',
'supdsub': '\u2AD8',
'supedot': '\u2AC4',
'suphsol': '\u27C9',
'suphsub': '\u2AD7',
'suplarr': '\u297B',
'supmult': '\u2AC2',
'supnE': '\u2ACC',
'supsetneqq': '\u2ACC',
'supne': '\u228B',
'supsetneq': '\u228B',
'supplus': '\u2AC0',
'supsim': '\u2AC8',
'supsub': '\u2AD4',
'supsup': '\u2AD6',
'swArr': '\u21D9',
'swnwar': '\u292A',
'szlig': '\u00DF',
'target': '\u2316',
'tau': '\u03C4',
'tcaron': '\u0165',
'tcedil': '\u0163',
'tcy': '\u0442',
'telrec': '\u2315',
'tfr': '\uD835\uDD31',
'theta': '\u03B8',
'thetasym': '\u03D1',
'thetav': '\u03D1',
'vartheta': '\u03D1',
'thorn': '\u00FE',
'times': '\u00D7',
'timesbar': '\u2A31',
'timesd': '\u2A30',
'topbot': '\u2336',
'topcir': '\u2AF1',
'topf': '\uD835\uDD65',
'topfork': '\u2ADA',
'tprime': '\u2034',
'triangle': '\u25B5',
'utri': '\u25B5',
'triangleq': '\u225C',
'trie': '\u225C',
'tridot': '\u25EC',
'triminus': '\u2A3A',
'triplus': '\u2A39',
'trisb': '\u29CD',
'tritime': '\u2A3B',
'trpezium': '\u23E2',
'tscr': '\uD835\uDCC9',
'tscy': '\u0446',
'tshcy': '\u045B',
'tstrok': '\u0167',
'uHar': '\u2963',
'uacute': '\u00FA',
'ubrcy': '\u045E',
'ubreve': '\u016D',
'ucirc': '\u00FB',
'ucy': '\u0443',
'udblac': '\u0171',
'ufisht': '\u297E',
'ufr': '\uD835\uDD32',
'ugrave': '\u00F9',
'uhblk': '\u2580',
'ulcorn': '\u231C',
'ulcorner': '\u231C',
'ulcrop': '\u230F',
'ultri': '\u25F8',
'umacr': '\u016B',
'uogon': '\u0173',
'uopf': '\uD835\uDD66',
'upsi': '\u03C5',
'upsilon': '\u03C5',
'upuparrows': '\u21C8',
'uuarr': '\u21C8',
'urcorn': '\u231D',
'urcorner': '\u231D',
'urcrop': '\u230E',
'uring': '\u016F',
'urtri': '\u25F9',
'uscr': '\uD835\uDCCA',
'utdot': '\u22F0',
'utilde': '\u0169',
'uuml': '\u00FC',
'uwangle': '\u29A7',
'vBar': '\u2AE8',
'vBarv': '\u2AE9',
'vangrt': '\u299C',
'varsubsetneq': '\u228A\uFE00',
'vsubne': '\u228A\uFE00',
'varsubsetneqq': '\u2ACB\uFE00',
'vsubnE': '\u2ACB\uFE00',
'varsupsetneq': '\u228B\uFE00',
'vsupne': '\u228B\uFE00',
'varsupsetneqq': '\u2ACC\uFE00',
'vsupnE': '\u2ACC\uFE00',
'vcy': '\u0432',
'veebar': '\u22BB',
'veeeq': '\u225A',
'vellip': '\u22EE',
'vfr': '\uD835\uDD33',
'vopf': '\uD835\uDD67',
'vscr': '\uD835\uDCCB',
'vzigzag': '\u299A',
'wcirc': '\u0175',
'wedbar': '\u2A5F',
'wedgeq': '\u2259',
'weierp': '\u2118',
'wp': '\u2118',
'wfr': '\uD835\uDD34',
'wopf': '\uD835\uDD68',
'wscr': '\uD835\uDCCC',
'xfr': '\uD835\uDD35',
'xi': '\u03BE',
'xnis': '\u22FB',
'xopf': '\uD835\uDD69',
'xscr': '\uD835\uDCCD',
'yacute': '\u00FD',
'yacy': '\u044F',
'ycirc': '\u0177',
'ycy': '\u044B',
'yen': '\u00A5',
'yfr': '\uD835\uDD36',
'yicy': '\u0457',
'yopf': '\uD835\uDD6A',
'yscr': '\uD835\uDCCE',
'yucy': '\u044E',
'yuml': '\u00FF',
'zacute': '\u017A',
'zcaron': '\u017E',
'zcy': '\u0437',
'zdot': '\u017C',
'zeta': '\u03B6',
'zfr': '\uD835\uDD37',
'zhcy': '\u0436',
'zigrarr': '\u21DD',
'zopf': '\uD835\uDD6B',
'zscr': '\uD835\uDCCF',
'zwj': '\u200D',
'zwnj': '\u200C',
};
// The &ngsp; pseudo-entity is denoting a space.
// 0xE500 is a PUA (Private Use Areas) unicode character
// This is inspired by the Angular Dart implementation.
const NGSP_UNICODE = '\uE500';
NAMED_ENTITIES['ngsp'] = NGSP_UNICODE;
/**
* @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.dev/license
*/
class TokenError extends ParseError {
constructor(errorMsg, tokenType, span) {
super(span, errorMsg);
this.tokenType = tokenType;
}
}
class TokenizeResult {
constructor(tokens, errors, nonNormalizedIcuExpressions) {
this.tokens = tokens;
this.errors = errors;
this.nonNormalizedIcuExpressions = nonNormalizedIcuExpressions;
}
}
function tokenize(source, url, getTagDefinition, options = {}) {
const tokenizer = new _Tokenizer(new ParseSourceFile(source, url), getTagDefinition, options);
tokenizer.tokenize();
return new TokenizeResult(mergeTextTokens(tokenizer.tokens), tokenizer.errors, tokenizer.nonNormalizedIcuExpressions);
}
const _CR_OR_CRLF_REGEXP = /\r\n?/g;
function _unexpectedCharacterErrorMsg(charCode) {
const char = charCode === $EOF ? 'EOF' : String.fromCharCode(charCode);
return `Unexpected character "${char}"`;
}
function _unknownEntityErrorMsg(entitySrc) {
return `Unknown entity "${entitySrc}" - use the "
;" or ";" syntax`;
}
function _unparsableEntityErrorMsg(type, entityStr) {
return `Unable to parse entity "${entityStr}" - ${type} character reference entities must end with ";"`;
}
var CharacterReferenceType;
(function (CharacterReferenceType) {
CharacterReferenceType["HEX"] = "hexadecimal";
CharacterReferenceType["DEC"] = "decimal";
})(CharacterReferenceType || (CharacterReferenceType = {}));
class _ControlFlowError {
constructor(error) {
this.error = error;
}
}
// See https://www.w3.org/TR/html51/syntax.html#writing-html-documents
class _Tokenizer {
/**
* @param _file The html source file being tokenized.
* @param _getTagDefinition A function that will retrieve a tag definition for a given tag name.
* @param options Configuration of the tokenization.
*/
constructor(_file, _getTagDefinition, options) {
this._getTagDefinition = _getTagDefinition;
this._currentTokenStart = null;
this._currentTokenType = null;
this._expansionCaseStack = [];
this._inInterpolation = false;
this.tokens = [];
this.errors = [];
this.nonNormalizedIcuExpressions = [];
this._tokenizeIcu = options.tokenizeExpansionForms || false;
this._interpolationConfig = options.interpolationConfig || DEFAULT_INTERPOLATION_CONFIG;
this._leadingTriviaCodePoints =
options.leadingTriviaChars && options.leadingTriviaChars.map((c) => c.codePointAt(0) || 0);
const range = options.range || {
endPos: _file.content.length,
startPos: 0,
startLine: 0,
startCol: 0,
};
this._cursor = options.escapedString
? new EscapedCharacterCursor(_file, range)
: new PlainCharacterCursor(_file, range);
this._preserveLineEndings = options.preserveLineEndings || false;
this._i18nNormalizeLineEndingsInICUs = options.i18nNormalizeLineEndingsInICUs || false;
this._tokenizeBlocks = options.tokenizeBlocks ?? true;
this._tokenizeLet = options.tokenizeLet ?? true;
try {
this._cursor.init();
}
catch (e) {
this.handleError(e);
}
}
_processCarriageReturns(content) {
if (this._preserveLineEndings) {
return content;
}
// https://www.w3.org/TR/html51/syntax.html#preprocessing-the-input-stream
// In order to keep the original position in the source, we can not
// pre-process it.
// Instead CRs are processed right before instantiating the tokens.
return content.replace(_CR_OR_CRLF_REGEXP, '\n');
}
tokenize() {
while (this._cursor.peek() !== $EOF) {
const start = this._cursor.clone();
try {
if (this._attemptCharCode($LT)) {
if (this._attemptCharCode($BANG)) {
if (this._attemptCharCode($LBRACKET)) {
this._consumeCdata(start);
}
else if (this._attemptCharCode($MINUS)) {
this._consumeComment(start);
}
else {
this._consumeDocType(start);
}
}
else if (this._attemptCharCode($SLASH)) {
this._consumeTagClose(start);
}
else {
this._consumeTagOpen(start);
}
}
else if (this._tokenizeLet &&
// Use `peek` instead of `attempCharCode` since we
// don't want to advance in case it's not `@let`.
this._cursor.peek() === $AT &&
!this._inInterpolation &&
this._attemptStr('@let')) {
this._consumeLetDeclaration(start);
}
else if (this._tokenizeBlocks && this._attemptCharCode($AT)) {
this._consumeBlockStart(start);
}
else if (this._tokenizeBlocks &&
!this._inInterpolation &&
!this._isInExpansionCase() &&
!this._isInExpansionForm() &&
this._attemptCharCode($RBRACE)) {
this._consumeBlockEnd(start);
}
else if (!(this._tokenizeIcu && this._tokenizeExpansionForm())) {
// In (possibly interpolated) text the end of the text is given by `isTextEnd()`, while
// the premature end of an interpolation is given by the start of a new HTML element.
this._consumeWithInterpolation(5 /* TokenType.TEXT */, 8 /* TokenType.INTERPOLATION */, () => this._isTextEnd(), () => this._isTagStart());
}
}
catch (e) {
this.handleError(e);
}
}
this._beginToken(33 /* TokenType.EOF */);
this._endToken([]);
}
_getBlockName() {
// This allows us to capture up something like `@else if`, but not `@ if`.
let spacesInNameAllowed = false;
const nameCursor = this._cursor.clone();
this._attemptCharCodeUntilFn((code) => {
if (isWhitespace(code)) {
return !spacesInNameAllowed;
}
if (isBlockNameChar(code)) {
spacesInNameAllowed = true;
return false;
}
return true;
});
return this._cursor.getChars(nameCursor).trim();
}
_consumeBlockStart(start) {
this._beginToken(24 /* TokenType.BLOCK_OPEN_START */, start);
const startToken = this._endToken([this._getBlockName()]);
if (this._cursor.peek() === $LPAREN) {
// Advance past the opening paren.
this._cursor.advance();
// Capture the parameters.
this._consumeBlockParameters();
// Allow spaces before the closing paren.
this._attemptCharCodeUntilFn(isNotWhitespace);
if (this._attemptCharCode($RPAREN)) {
// Allow spaces after the paren.
this._attemptCharCodeUntilFn(isNotWhitespace);
}
else {
startToken.type = 28 /* TokenType.INCOMPLETE_BLOCK_OPEN */;
return;
}
}
if (this._attemptCharCode($LBRACE)) {
this._beginToken(25 /* TokenType.BLOCK_OPEN_END */);
this._endToken([]);
}
else {
startToken.type = 28 /* TokenType.INCOMPLETE_BLOCK_OPEN */;
}
}
_consumeBlockEnd(start) {
this._beginToken(26 /* TokenType.BLOCK_CLOSE */, start);
this._endToken([]);
}
_consumeBlockParameters() {
// Trim the whitespace until the first parameter.
this._attemptCharCodeUntilFn(isBlockParameterChar);
while (this._cursor.peek() !== $RPAREN && this._cursor.peek() !== $EOF) {
this._beginToken(27 /* TokenType.BLOCK_PARAMETER */);
const start = this._cursor.clone();
let inQuote = null;
let openParens = 0;
// Consume the parameter until the next semicolon or brace.
// Note that we skip over semicolons/braces inside of strings.
while ((this._cursor.peek() !== $SEMICOLON && this._cursor.peek() !== $EOF) ||
inQuote !== null) {
const char = this._cursor.peek();
// Skip to the next character if it was escaped.
if (char === $BACKSLASH) {
this._cursor.advance();
}
else if (char === inQuote) {
inQuote = null;
}
else if (inQuote === null && isQuote(char)) {
inQuote = char;
}
else if (char === $LPAREN && inQuote === null) {
openParens++;
}
else if (char === $RPAREN && inQuote === null) {
if (openParens === 0) {
break;
}
else if (openParens > 0) {
openParens--;
}
}
this._cursor.advance();
}
this._endToken([this._cursor.getChars(start)]);
// Skip to the next parameter.
this._attemptCharCodeUntilFn(isBlockParameterChar);
}
}
_consumeLetDeclaration(start) {
this._beginToken(29 /* TokenType.LET_START */, start);
// Require at least one white space after the `@let`.
if (isWhitespace(this._cursor.peek())) {
this._attemptCharCodeUntilFn(isNotWhitespace);
}
else {
const token = this._endToken([this._cursor.getChars(start)]);
token.type = 32 /* TokenType.INCOMPLETE_LET */;
return;
}
const startToken = this._endToken([this._getLetDeclarationName()]);
// Skip over white space before the equals character.
this._attemptCharCodeUntilFn(isNotWhitespace);
// Expect an equals sign.
if (!this._attemptCharCode($EQ)) {
startToken.type = 32 /* TokenType.INCOMPLETE_LET */;
return;
}
// Skip spaces after the equals.
this._attemptCharCodeUntilFn((code) => isNotWhitespace(code) && !isNewLine(code));
this._consumeLetDeclarationValue();
// Terminate the `@let` with a semicolon.
const endChar = this._cursor.peek();
if (endChar === $SEMICOLON) {
this._beginToken(31 /* TokenType.LET_END */);
this._endToken([]);
this._cursor.advance();
}
else {
startToken.type = 32 /* TokenType.INCOMPLETE_LET */;
startToken.sourceSpan = this._cursor.getSpan(start);
}
}
_getLetDeclarationName() {
const nameCursor = this._cursor.clone();
let allowDigit = false;
this._attemptCharCodeUntilFn((code) => {
if (isAsciiLetter(code) ||
code === $$ ||
code === $_ ||
// `@let` names can't start with a digit, but digits are valid anywhere else in the name.
(allowDigit && isDigit(code))) {
allowDigit = true;
return false;
}
return true;
});
return this._cursor.getChars(nameCursor).trim();
}
_consumeLetDeclarationValue() {
const start = this._cursor.clone();
this._beginToken(30 /* TokenType.LET_VALUE */, start);
while (this._cursor.peek() !== $EOF) {
const char = this._cursor.peek();
// `@let` declarations terminate with a semicolon.
if (char === $SEMICOLON) {
break;
}
// If we hit a quote, skip over its content since we don't care what's inside.
if (isQuote(char)) {
this._cursor.advance();
this._attemptCharCodeUntilFn((inner) => {
if (inner === $BACKSLASH) {
this._cursor.advance();
return false;
}
return inner === char;
});
}
this._cursor.advance();
}
this._endToken([this._cursor.getChars(start)]);
}
/**
* @returns whether an ICU token has been created
* @internal
*/
_tokenizeExpansionForm() {
if (this.isExpansionFormStart()) {
this._consumeExpansionFormStart();
return true;
}
if (isExpansionCaseStart(this._cursor.peek()) && this._isInExpansionForm()) {
this._consumeExpansionCaseStart();
return true;
}
if (this._cursor.peek() === $RBRACE) {
if (this._isInExpansionCase()) {
this._consumeExpansionCaseEnd();
return true;
}
if (this._isInExpansionForm()) {
this._consumeExpansionFormEnd();
return true;
}
}
return false;
}
_beginToken(type, start = this._cursor.clone()) {
this._currentTokenStart = start;
this._currentTokenType = type;
}
_endToken(parts, end) {
if (this._currentTokenStart === null) {
throw new TokenError('Programming error - attempted to end a token when there was no start to the token', this._currentTokenType, this._cursor.getSpan(end));
}
if (this._currentTokenType === null) {
throw new TokenError('Programming error - attempted to end a token which has no token type', null, this._cursor.getSpan(this._currentTokenStart));
}
const token = {
type: this._currentTokenType,
parts,
sourceSpan: (end ?? this._cursor).getSpan(this._currentTokenStart, this._leadingTriviaCodePoints),
};
this.tokens.push(token);
this._currentTokenStart = null;
this._currentTokenType = null;
return token;
}
_createError(msg, span) {
if (this._isInExpansionForm()) {
msg += ` (Do you have an unescaped "{" in your template? Use "{{ '{' }}") to escape it.)`;
}
const error = new TokenError(msg, this._currentTokenType, span);
this._currentTokenStart = null;
this._currentTokenType = null;
return new _ControlFlowError(error);
}
handleError(e) {
if (e instanceof CursorError) {
e = this._createError(e.msg, this._cursor.getSpan(e.cursor));
}
if (e instanceof _ControlFlowError) {
this.errors.push(e.error);
}
else {
throw e;
}
}
_attemptCharCode(charCode) {
if (this._cursor.peek() === charCode) {
this._cursor.advance();
return true;
}
return false;
}
_attemptCharCodeCaseInsensitive(charCode) {
if (compareCharCodeCaseInsensitive(this._cursor.peek(), charCode)) {
this._cursor.advance();
return true;
}
return false;
}
_requireCharCode(charCode) {
const location = this._cursor.clone();
if (!this._attemptCharCode(charCode)) {
throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(location));
}
}
_attemptStr(chars) {
const len = chars.length;
if (this._cursor.charsLeft() < len) {
return false;
}
const initialPosition = this._cursor.clone();
for (let i = 0; i < len; i++) {
if (!this._attemptCharCode(chars.charCodeAt(i))) {
// If attempting to parse the string fails, we want to reset the parser
// to where it was before the attempt
this._cursor = initialPosition;
return false;
}
}
return true;
}
_attemptStrCaseInsensitive(chars) {
for (let i = 0; i < chars.length; i++) {
if (!this._attemptCharCodeCaseInsensitive(chars.charCodeAt(i))) {
return false;
}
}
return true;
}
_requireStr(chars) {
const location = this._cursor.clone();
if (!this._attemptStr(chars)) {
throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(location));
}
}
_attemptCharCodeUntilFn(predicate) {
while (!predicate(this._cursor.peek())) {
this._cursor.advance();
}
}
_requireCharCodeUntilFn(predicate, len) {
const start = this._cursor.clone();
this._attemptCharCodeUntilFn(predicate);
if (this._cursor.diff(start) < len) {
throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(start));
}
}
_attemptUntilChar(char) {
while (this._cursor.peek() !== char) {
this._cursor.advance();
}
}
_readChar() {
// Don't rely upon reading directly from `_input` as the actual char value
// may have been generated from an escape sequence.
const char = String.fromCodePoint(this._cursor.peek());
this._cursor.advance();
return char;
}
_consumeEntity(textTokenType) {
this._beginToken(9 /* TokenType.ENCODED_ENTITY */);
const start = this._cursor.clone();
this._cursor.advance();
if (this._attemptCharCode($HASH)) {
const isHex = this._attemptCharCode($x) || this._attemptCharCode($X);
const codeStart = this._cursor.clone();
this._attemptCharCodeUntilFn(isDigitEntityEnd);
if (this._cursor.peek() != $SEMICOLON) {
// Advance cursor to include the peeked character in the string provided to the error
// message.
this._cursor.advance();
const entityType = isHex ? CharacterReferenceType.HEX : CharacterReferenceType.DEC;
throw this._createError(_unparsableEntityErrorMsg(entityType, this._cursor.getChars(start)), this._cursor.getSpan());
}
const strNum = this._cursor.getChars(codeStart);
this._cursor.advance();
try {
const charCode = parseInt(strNum, isHex ? 16 : 10);
this._endToken([String.fromCharCode(charCode), this._cursor.getChars(start)]);
}
catch {
throw this._createError(_unknownEntityErrorMsg(this._cursor.getChars(start)), this._cursor.getSpan());
}
}
else {
const nameStart = this._cursor.clone();
this._attemptCharCodeUntilFn(isNamedEntityEnd);
if (this._cursor.peek() != $SEMICOLON) {
// No semicolon was found so abort the encoded entity token that was in progress, and treat
// this as a text token
this._beginToken(textTokenType, start);
this._cursor = nameStart;
this._endToken(['&']);
}
else {
const name = this._cursor.getChars(nameStart);
this._cursor.advance();
const char = NAMED_ENTITIES[name];
if (!char) {
throw this._createError(_unknownEntityErrorMsg(name), this._cursor.getSpan(start));
}
this._endToken([char, `&${name};`]);
}
}
}
_consumeRawText(consumeEntities, endMarkerPredicate) {
this._beginToken(consumeEntities ? 6 /* TokenType.ESCAPABLE_RAW_TEXT */ : 7 /* TokenType.RAW_TEXT */);
const parts = [];
while (true) {
const tagCloseStart = this._cursor.clone();
const foundEndMarker = endMarkerPredicate();
this._cursor = tagCloseStart;
if (foundEndMarker) {
break;
}
if (consumeEntities && this._cursor.peek() === $AMPERSAND) {
this._endToken([this._processCarriageReturns(parts.join(''))]);
parts.length = 0;
this._consumeEntity(6 /* TokenType.ESCAPABLE_RAW_TEXT */);
this._beginToken(6 /* TokenType.ESCAPABLE_RAW_TEXT */);
}
else {
parts.push(this._readChar());
}
}
this._endToken([this._processCarriageReturns(parts.join(''))]);
}
_consumeComment(start) {
this._beginToken(10 /* TokenType.COMMENT_START */, start);
this._requireCharCode($MINUS);
this._endToken([]);
this._consumeRawText(false, () => this._attemptStr('-->'));
this._beginToken(11 /* TokenType.COMMENT_END */);
this._requireStr('-->');
this._endToken([]);
}
_consumeCdata(start) {
this._beginToken(12 /* TokenType.CDATA_START */, start);
this._requireStr('CDATA[');
this._endToken([]);
this._consumeRawText(false, () => this._attemptStr(']]>'));
this._beginToken(13 /* TokenType.CDATA_END */);
this._requireStr(']]>');
this._endToken([]);
}
_consumeDocType(start) {
this._beginToken(18 /* TokenType.DOC_TYPE */, start);
const contentStart = this._cursor.clone();
this._attemptUntilChar($GT);
const content = this._cursor.getChars(contentStart);
this._cursor.advance();
this._endToken([content]);
}
_consumePrefixAndName() {
const nameOrPrefixStart = this._cursor.clone();
let prefix = '';
while (this._cursor.peek() !== $COLON && !isPrefixEnd(this._cursor.peek())) {
this._cursor.advance();
}
let nameStart;
if (this._cursor.peek() === $COLON) {
prefix = this._cursor.getChars(nameOrPrefixStart);
this._cursor.advance();
nameStart = this._cursor.clone();
}
else {
nameStart = nameOrPrefixStart;
}
this._requireCharCodeUntilFn(isNameEnd, prefix === '' ? 0 : 1);
const name = this._cursor.getChars(nameStart);
return [prefix, name];
}
_consumeTagOpen(start) {
let tagName;
let prefix;
let openTagToken;
try {
if (!isAsciiLetter(this._cursor.peek())) {
throw this._createError(_unexpectedCharacterErrorMsg(this._cursor.peek()), this._cursor.getSpan(start));
}
openTagToken = this._consumeTagOpenStart(start);
prefix = openTagToken.parts[0];
tagName = openTagToken.parts[1];
this._attemptCharCodeUntilFn(isNotWhitespace);
while (this._cursor.peek() !== $SLASH &&
this._cursor.peek() !== $GT &&
this._cursor.peek() !== $LT &&
this._cursor.peek() !== $EOF) {
this._consumeAttributeName();
this._attemptCharCodeUntilFn(isNotWhitespace);
if (this._attemptCharCode($EQ)) {
this._attemptCharCodeUntilFn(isNotWhitespace);
this._consumeAttributeValue();
}
this._attemptCharCodeUntilFn(isNotWhitespace);
}
this._consumeTagOpenEnd();
}
catch (e) {
if (e instanceof _ControlFlowError) {
if (openTagToken) {
// We errored before we could close the opening tag, so it is incomplete.
openTagToken.type = 4 /* TokenType.INCOMPLETE_TAG_OPEN */;
}
else {
// When the start tag is invalid, assume we want a "<" as text.
// Back to back text tokens are merged at the end.
this._beginToken(5 /* TokenType.TEXT */, start);
this._endToken(['<']);
}
return;
}
throw e;
}
const contentTokenType = this._getTagDefinition(tagName).getContentType(prefix);
if (contentTokenType === TagContentType.RAW_TEXT) {
this._consumeRawTextWithTagClose(prefix, tagName, false);
}
else if (contentTokenType === TagContentType.ESCAPABLE_RAW_TEXT) {
this._consumeRawTextWithTagClose(prefix, tagName, true);
}
}
_consumeRawTextWithTagClose(prefix, tagName, consumeEntities) {
this._consumeRawText(consumeEntities, () => {
if (!this._attemptCharCode($LT))
return false;
if (!this._attemptCharCode($SLASH))
return false;
this._attemptCharCodeUntilFn(isNotWhitespace);
if (!this._attemptStrCaseInsensitive(tagName))
return false;
this._attemptCharCodeUntilFn(isNotWhitespace);
return this._attemptCharCode($GT);
});
this._beginToken(3 /* TokenType.TAG_CLOSE */);
this._requireCharCodeUntilFn((code) => code === $GT, 3);
this._cursor.advance(); // Consume the `>`
this._endToken([prefix, tagName]);
}
_consumeTagOpenStart(start) {
this._beginToken(0 /* TokenType.TAG_OPEN_START */, start);
const parts = this._consumePrefixAndName();
return this._endToken(parts);
}
_consumeAttributeName() {
const attrNameStart = this._cursor.peek();
if (attrNameStart === $SQ || attrNameStart === $DQ) {
throw this._createError(_unexpectedCharacterErrorMsg(attrNameStart), this._cursor.getSpan());
}
this._beginToken(14 /* TokenType.ATTR_NAME */);
const prefixAndName = this._consumePrefixAndName();
this._endToken(prefixAndName);
}
_consumeAttributeValue() {
if (this._cursor.peek() === $SQ || this._cursor.peek() === $DQ) {
const quoteChar = this._cursor.peek();
this._consumeQuote(quoteChar);
// In an attribute then end of the attribute value and the premature end to an interpolation
// are both triggered by the `quoteChar`.
const endPredicate = () => this._cursor.peek() === quoteChar;
this._consumeWithInterpolation(16 /* TokenType.ATTR_VALUE_TEXT */, 17 /* TokenType.ATTR_VALUE_INTERPOLATION */, endPredicate, endPredicate);
this._consumeQuote(quoteChar);
}
else {
const endPredicate = () => isNameEnd(this._cursor.peek());
this._consumeWithInterpolation(16 /* TokenType.ATTR_VALUE_TEXT */, 17 /* TokenType.ATTR_VALUE_INTERPOLATION */, endPredicate, endPredicate);
}
}
_consumeQuote(quoteChar) {
this._beginToken(15 /* TokenType.ATTR_QUOTE */);
this._requireCharCode(quoteChar);
this._endToken([String.fromCodePoint(quoteChar)]);
}
_consumeTagOpenEnd() {
const tokenType = this._attemptCharCode($SLASH)
? 2 /* TokenType.TAG_OPEN_END_VOID */
: 1 /* TokenType.TAG_OPEN_END */;
this._beginToken(tokenType);
this._requireCharCode($GT);
this._endToken([]);
}
_consumeTagClose(start) {
this._beginToken(3 /* TokenType.TAG_CLOSE */, start);
this._attemptCharCodeUntilFn(isNotWhitespace);
const prefixAndName = this._consumePrefixAndName();
this._attemptCharCodeUntilFn(isNotWhitespace);
this._requireCharCode($GT);
this._endToken(prefixAndName);
}
_consumeExpansionFormStart() {
this._beginToken(19 /* TokenType.EXPANSION_FORM_START */);
this._requireCharCode($LBRACE);
this._endToken([]);
this._expansionCaseStack.push(19 /* TokenType.EXPANSION_FORM_START */);
this._beginToken(7 /* TokenType.RAW_TEXT */);
const condition = this._readUntil($COMMA);
const normalizedCondition = this._processCarriageReturns(condition);
if (this._i18nNormalizeLineEndingsInICUs) {
// We explicitly want to normalize line endings for this text.
this._endToken([normalizedCondition]);
}
else {
// We are not normalizing line endings.
const conditionToken = this._endToken([condition]);
if (normalizedCondition !== condition) {
this.nonNormalizedIcuExpressions.push(conditionToken);
}
}
this._requireCharCode($COMMA);
this._attemptCharCodeUntilFn(isNotWhitespace);
this._beginToken(7 /* TokenType.RAW_TEXT */);
const type = this._readUntil($COMMA);
this._endToken([type]);
this._requireCharCode($COMMA);
this._attemptCharCodeUntilFn(isNotWhitespace);
}
_consumeExpansionCaseStart() {
this._beginToken(20 /* TokenType.EXPANSION_CASE_VALUE */);
const value = this._readUntil($LBRACE).trim();
this._endToken([value]);
this._attemptCharCodeUntilFn(isNotWhitespace);
this._beginToken(21 /* TokenType.EXPANSION_CASE_EXP_START */);
this._requireCharCode($LBRACE);
this._endToken([]);
this._attemptCharCodeUntilFn(isNotWhitespace);
this._expansionCaseStack.push(21 /* TokenType.EXPANSION_CASE_EXP_START */);
}
_consumeExpansionCaseEnd() {
this._beginToken(22 /* TokenType.EXPANSION_CASE_EXP_END */);
this._requireCharCode($RBRACE);
this._endToken([]);
this._attemptCharCodeUntilFn(isNotWhitespace);
this._expansionCaseStack.pop();
}
_consumeExpansionFormEnd() {
this._beginToken(23 /* TokenType.EXPANSION_FORM_END */);
this._requireCharCode($RBRACE);
this._endToken([]);
this._expansionCaseStack.pop();
}
/**
* Consume a string that may contain interpolation expressions.
*
* The first token consumed will be of `tokenType` and then there will be alternating
* `interpolationTokenType` and `tokenType` tokens until the `endPredicate()` returns true.
*
* If an interpolation token ends prematurely it will have no end marker in its `parts` array.
*
* @param textTokenType the kind of tokens to interleave around interpolation tokens.
* @param interpolationTokenType the kind of tokens that contain interpolation.
* @param endPredicate a function that should return true when we should stop consuming.
* @param endInterpolation a function that should return true if there is a premature end to an
* interpolation expression - i.e. before we get to the normal interpolation closing marker.
*/
_consumeWithInterpolation(textTokenType, interpolationTokenType, endPredicate, endInterpolation) {
this._beginToken(textTokenType);
const parts = [];
while (!endPredicate()) {
const current = this._cursor.clone();
if (this._interpolationConfig && this._attemptStr(this._interpolationConfig.start)) {
this._endToken([this._processCarriageReturns(parts.join(''))], current);
parts.length = 0;
this._consumeInterpolation(interpolationTokenType, current, endInterpolation);
this._beginToken(textTokenType);
}
else if (this._cursor.peek() === $AMPERSAND) {
this._endToken([this._processCarriageReturns(parts.join(''))]);
parts.length = 0;
this._consumeEntity(textTokenType);
this._beginToken(textTokenType);
}
else {
parts.push(this._readChar());
}
}
// It is possible that an interpolation was started but not ended inside this text token.
// Make sure that we reset the state of the lexer correctly.
this._inInterpolation = false;
this._endToken([this._processCarriageReturns(parts.join(''))]);
}
/**
* Consume a block of text that has been interpreted as an Angular interpolation.
*
* @param interpolationTokenType the type of the interpolation token to generate.
* @param interpolationStart a cursor that points to the start of this interpolation.
* @param prematureEndPredicate a function that should return true if the next characters indicate
* an end to the interpolation before its normal closing marker.
*/
_consumeInterpolation(interpolationTokenType, interpolationStart, prematureEndPredicate) {
const parts = [];
this._beginToken(interpolationTokenType, interpolationStart);
parts.push(this._interpolationConfig.start);
// Find the end of the interpolation, ignoring content inside quotes.
const expressionStart = this._cursor.clone();
let inQuote = null;
let inComment = false;
while (this._cursor.peek() !== $EOF &&
(prematureEndPredicate === null || !prematureEndPredicate())) {
const current = this._cursor.clone();
if (this._isTagStart()) {
// We are starting what looks like an HTML element in the middle of this interpolation.
// Reset the cursor to before the `<` character and end the interpolation token.
// (This is actually wrong but here for backward compatibility).
this._cursor = current;
parts.push(this._getProcessedChars(expressionStart, current));
this._endToken(parts);
return;
}
if (inQuote === null) {
if (this._attemptStr(this._interpolationConfig.end)) {
// We are not in a string, and we hit the end interpolation marker
parts.push(this._getProcessedChars(expressionStart, current));
parts.push(this._interpolationConfig.end);
this._endToken(parts);
return;
}
else if (this._attemptStr('//')) {
// Once we are in a comment we ignore any quotes
inComment = true;
}
}
const char = this._cursor.peek();
this._cursor.advance();
if (char === $BACKSLASH) {
// Skip the next character because it was escaped.
this._cursor.advance();
}
else if (char === inQuote) {
// Exiting the current quoted string
inQuote = null;
}
else if (!inComment && inQuote === null && isQuote(char)) {
// Entering a new quoted string
inQuote = char;
}
}
// We hit EOF without finding a closing interpolation marker
parts.push(this._getProcessedChars(expressionStart, this._cursor));
this._endToken(parts);
}
_getProcessedChars(start, end) {
return this._processCarriageReturns(end.getChars(start));
}
_isTextEnd() {
if (this._isTagStart() || this._cursor.peek() === $EOF) {
return true;
}
if (this._tokenizeIcu && !this._inInterpolation) {
if (this.isExpansionFormStart()) {
// start of an expansion form
return true;
}
if (this._cursor.peek() === $RBRACE && this._isInExpansionCase()) {
// end of and expansion case
return true;
}
}
if (this._tokenizeBlocks &&
!this._inInterpolation &&
!this._isInExpansion() &&
(this._cursor.peek() === $AT || this._cursor.peek() === $RBRACE)) {
return true;
}
return false;
}
/**
* Returns true if the current cursor is pointing to the start of a tag
* (opening/closing/comments/cdata/etc).
*/
_isTagStart() {
if (this._cursor.peek() === $LT) {
// We assume that `<` followed by whitespace is not the start of an HTML element.
const tmp = this._cursor.clone();
tmp.advance();
// If the next character is alphabetic, ! nor / then it is a tag start
const code = tmp.peek();
if (($a <= code && code <= $z) ||
($A <= code && code <= $Z) ||
code === $SLASH ||
code === $BANG) {
return true;
}
}
return false;
}
_readUntil(char) {
const start = this._cursor.clone();
this._attemptUntilChar(char);
return this._cursor.getChars(start);
}
_isInExpansion() {
return this._isInExpansionCase() || this._isInExpansionForm();
}
_isInExpansionCase() {
return (this._expansionCaseStack.length > 0 &&
this._expansionCaseStack[this._expansionCaseStack.length - 1] ===
21 /* TokenType.EXPANSION_CASE_EXP_START */);
}
_isInExpansionForm() {
return (this._expansionCaseStack.length > 0 &&
this._expansionCaseStack[this._expansionCaseStack.length - 1] ===
19 /* TokenType.EXPANSION_FORM_START */);
}
isExpansionFormStart() {
if (this._cursor.peek() !== $LBRACE) {
return false;
}
if (this._interpolationConfig) {
const start = this._cursor.clone();
const isInterpolation = this._attemptStr(this._interpolationConfig.start);
this._cursor = start;
return !isInterpolation;
}
return true;
}
}
function isNotWhitespace(code) {
return !isWhitespace(code) || code === $EOF;
}
function isNameEnd(code) {
return (isWhitespace(code) ||
code === $GT ||
code === $LT ||
code === $SLASH ||
code === $SQ ||
code === $DQ ||
code === $EQ ||
code === $EOF);
}
function isPrefixEnd(code) {
return ((code < $a || $z < code) &&
(code < $A || $Z < code) &&
(code < $0 || code > $9));
}
function isDigitEntityEnd(code) {
return code === $SEMICOLON || code === $EOF || !isAsciiHexDigit(code);
}
function isNamedEntityEnd(code) {
return code === $SEMICOLON || code === $EOF || !isAsciiLetter(code);
}
function isExpansionCaseStart(peek) {
return peek !== $RBRACE;
}
function compareCharCodeCaseInsensitive(code1, code2) {
return toUpperCaseCharCode(code1) === toUpperCaseCharCode(code2);
}
function toUpperCaseCharCode(code) {
return code >= $a && code <= $z ? code - $a + $A : code;
}
function isBlockNameChar(code) {
return isAsciiLetter(code) || isDigit(code) || code === $_;
}
function isBlockParameterChar(code) {
return code !== $SEMICOLON && isNotWhitespace(code);
}
function mergeTextTokens(srcTokens) {
const dstTokens = [];
let lastDstToken = undefined;
for (let i = 0; i < srcTokens.length; i++) {
const token = srcTokens[i];
if ((lastDstToken && lastDstToken.type === 5 /* TokenType.TEXT */ && token.type === 5 /* TokenType.TEXT */) ||
(lastDstToken &&
lastDstToken.type === 16 /* TokenType.ATTR_VALUE_TEXT */ &&
token.type === 16 /* TokenType.ATTR_VALUE_TEXT */)) {
lastDstToken.parts[0] += token.parts[0];
lastDstToken.sourceSpan.end = token.sourceSpan.end;
}
else {
lastDstToken = token;
dstTokens.push(lastDstToken);
}
}
return dstTokens;
}
class PlainCharacterCursor {
constructor(fileOrCursor, range) {
if (fileOrCursor instanceof PlainCharacterCursor) {
this.file = fileOrCursor.file;
this.input = fileOrCursor.input;
this.end = fileOrCursor.end;
const state = fileOrCursor.state;
// Note: avoid using `{...fileOrCursor.state}` here as that has a severe performance penalty.
// In ES5 bundles the object spread operator is translated into the `__assign` helper, which
// is not optimized by VMs as efficiently as a raw object literal. Since this constructor is
// called in tight loops, this difference matters.
this.state = {
peek: state.peek,
offset: state.offset,
line: state.line,
column: state.column,
};
}
else {
if (!range) {
throw new Error('Programming error: the range argument must be provided with a file argument.');
}
this.file = fileOrCursor;
this.input = fileOrCursor.content;
this.end = range.endPos;
this.state = {
peek: -1,
offset: range.startPos,
line: range.startLine,
column: range.startCol,
};
}
}
clone() {
return new PlainCharacterCursor(this);
}
peek() {
return this.state.peek;
}
charsLeft() {
return this.end - this.state.offset;
}
diff(other) {
return this.state.offset - other.state.offset;
}
advance() {
this.advanceState(this.state);
}
init() {
this.updatePeek(this.state);
}
getSpan(start, leadingTriviaCodePoints) {
start = start || this;
let fullStart = start;
if (leadingTriviaCodePoints) {
while (this.diff(start) > 0 && leadingTriviaCodePoints.indexOf(start.peek()) !== -1) {
if (fullStart === start) {
start = start.clone();
}
start.advance();
}
}
const startLocation = this.locationFromCursor(start);
const endLocation = this.locationFromCursor(this);
const fullStartLocation = fullStart !== start ? this.locationFromCursor(fullStart) : startLocation;
return new ParseSourceSpan(startLocation, endLocation, fullStartLocation);
}
getChars(start) {
return this.input.substring(start.state.offset, this.state.offset);
}
charAt(pos) {
return this.input.charCodeAt(pos);
}
advanceState(state) {
if (state.offset >= this.end) {
this.state = state;
throw new CursorError('Unexpected character "EOF"', this);
}
const currentChar = this.charAt(state.offset);
if (currentChar === $LF) {
state.line++;
state.column = 0;
}
else if (!isNewLine(currentChar)) {
state.column++;
}
state.offset++;
this.updatePeek(state);
}
updatePeek(state) {
state.peek = state.offset >= this.end ? $EOF : this.charAt(state.offset);
}
locationFromCursor(cursor) {
return new ParseLocation(cursor.file, cursor.state.offset, cursor.state.line, cursor.state.column);
}
}
class EscapedCharacterCursor extends PlainCharacterCursor {
constructor(fileOrCursor, range) {
if (fileOrCursor instanceof EscapedCharacterCursor) {
super(fileOrCursor);
this.internalState = { ...fileOrCursor.internalState };
}
else {
super(fileOrCursor, range);
this.internalState = this.state;
}
}
advance() {
this.state = this.internalState;
super.advance();
this.processEscapeSequence();
}
init() {
super.init();
this.processEscapeSequence();
}
clone() {
return new EscapedCharacterCursor(this);
}
getChars(start) {
const cursor = start.clone();
let chars = '';
while (cursor.internalState.offset < this.internalState.offset) {
chars += String.fromCodePoint(cursor.peek());
cursor.advance();
}
return chars;
}
/**
* Process the escape sequence that starts at the current position in the text.
*
* This method is called to ensure that `peek` has the unescaped value of escape sequences.
*/
processEscapeSequence() {
const peek = () => this.internalState.peek;
if (peek() === $BACKSLASH) {
// We have hit an escape sequence so we need the internal state to become independent
// of the external state.
this.internalState = { ...this.state };
// Move past the backslash
this.advanceState(this.internalState);
// First check for standard control char sequences
if (peek() === $n) {
this.state.peek = $LF;
}
else if (peek() === $r) {
this.state.peek = $CR;
}
else if (peek() === $v) {
this.state.peek = $VTAB;
}
else if (peek() === $t) {
this.state.peek = $TAB;
}
else if (peek() === $b) {
this.state.peek = $BSPACE;
}
else if (peek() === $f) {
this.state.peek = $FF;
}
// Now consider more complex sequences
else if (peek() === $u) {
// Unicode code-point sequence
this.advanceState(this.internalState); // advance past the `u` char
if (peek() === $LBRACE) {
// Variable length Unicode, e.g. `\x{123}`
this.advanceState(this.internalState); // advance past the `{` char
// Advance past the variable number of hex digits until we hit a `}` char
const digitStart = this.clone();
let length = 0;
while (peek() !== $RBRACE) {
this.advanceState(this.internalState);
length++;
}
this.state.peek = this.decodeHexDigits(digitStart, length);
}
else {
// Fixed length Unicode, e.g. `\u1234`
const digitStart = this.clone();
this.advanceState(this.internalState);
this.advanceState(this.internalState);
this.advanceState(this.internalState);
this.state.peek = this.decodeHexDigits(digitStart, 4);
}
}
else if (peek() === $x) {
// Hex char code, e.g. `\x2F`
this.advanceState(this.internalState); // advance past the `x` char
const digitStart = this.clone();
this.advanceState(this.internalState);
this.state.peek = this.decodeHexDigits(digitStart, 2);
}
else if (isOctalDigit(peek())) {
// Octal char code, e.g. `\012`,
let octal = '';
let length = 0;
let previous = this.clone();
while (isOctalDigit(peek()) && length < 3) {
previous = this.clone();
octal += String.fromCodePoint(peek());
this.advanceState(this.internalState);
length++;
}
this.state.peek = parseInt(octal, 8);
// Backup one char
this.internalState = previous.internalState;
}
else if (isNewLine(this.internalState.peek)) {
// Line continuation `\` followed by a new line
this.advanceState(this.internalState); // advance over the newline
this.state = this.internalState;
}
else {
// If none of the `if` blocks were executed then we just have an escaped normal character.
// In that case we just, effectively, skip the backslash from the character.
this.state.peek = this.internalState.peek;
}
}
}
decodeHexDigits(start, length) {
const hex = this.input.slice(start.internalState.offset, start.internalState.offset + length);
const charCode = parseInt(hex, 16);
if (!isNaN(charCode)) {
return charCode;
}
else {
start.state = start.internalState;
throw new CursorError('Invalid hexadecimal escape sequence', start);
}
}
}
class CursorError {
constructor(msg, cursor) {
this.msg = msg;
this.cursor = cursor;
}
}
/**
* @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.dev/license
*/
class TreeError extends ParseError {
static create(elementName, span, msg) {
return new TreeError(elementName, span, msg);
}
constructor(elementName, span, msg) {
super(span, msg);
this.elementName = elementName;
}
}
class ParseTreeResult {
constructor(rootNodes, errors) {
this.rootNodes = rootNodes;
this.errors = errors;
}
}
class Parser$1 {
constructor(getTagDefinition) {
this.getTagDefinition = getTagDefinition;
}
parse(source, url, options) {
const tokenizeResult = tokenize(source, url, this.getTagDefinition, options);
const parser = new _TreeBuilder(tokenizeResult.tokens, this.getTagDefinition);
parser.build();
return new ParseTreeResult(parser.rootNodes, tokenizeResult.errors.concat(parser.errors));
}
}
class _TreeBuilder {
constructor(tokens, getTagDefinition) {
this.tokens = tokens;
this.getTagDefinition = getTagDefinition;
this._index = -1;
this._containerStack = [];
this.rootNodes = [];
this.errors = [];
this._advance();
}
build() {
while (this._peek.type !== 33 /* TokenType.EOF */) {
if (this._peek.type === 0 /* TokenType.TAG_OPEN_START */ ||
this._peek.type === 4 /* TokenType.INCOMPLETE_TAG_OPEN */) {
this._consumeStartTag(this._advance());
}
else if (this._peek.type === 3 /* TokenType.TAG_CLOSE */) {
this._consumeEndTag(this._advance());
}
else if (this._peek.type === 12 /* TokenType.CDATA_START */) {
this._closeVoidElement();
this._consumeCdata(this._advance());
}
else if (this._peek.type === 10 /* TokenType.COMMENT_START */) {
this._closeVoidElement();
this._consumeComment(this._advance());
}
else if (this._peek.type === 5 /* TokenType.TEXT */ ||
this._peek.type === 7 /* TokenType.RAW_TEXT */ ||
this._peek.type === 6 /* TokenType.ESCAPABLE_RAW_TEXT */) {
this._closeVoidElement();
this._consumeText(this._advance());
}
else if (this._peek.type === 19 /* TokenType.EXPANSION_FORM_START */) {
this._consumeExpansion(this._advance());
}
else if (this._peek.type === 24 /* TokenType.BLOCK_OPEN_START */) {
this._closeVoidElement();
this._consumeBlockOpen(this._advance());
}
else if (this._peek.type === 26 /* TokenType.BLOCK_CLOSE */) {
this._closeVoidElement();
this._consumeBlockClose(this._advance());
}
else if (this._peek.type === 28 /* TokenType.INCOMPLETE_BLOCK_OPEN */) {
this._closeVoidElement();
this._consumeIncompleteBlock(this._advance());
}
else if (this._peek.type === 29 /* TokenType.LET_START */) {
this._closeVoidElement();
this._consumeLet(this._advance());
}
else if (this._peek.type === 32 /* TokenType.INCOMPLETE_LET */) {
this._closeVoidElement();
this._consumeIncompleteLet(this._advance());
}
else {
// Skip all other tokens...
this._advance();
}
}
for (const leftoverContainer of this._containerStack) {
// Unlike HTML elements, blocks aren't closed implicitly by the end of the file.
if (leftoverContainer instanceof Block) {
this.errors.push(TreeError.create(leftoverContainer.name, leftoverContainer.sourceSpan, `Unclosed block "${leftoverContainer.name}"`));
}
}
}
_advance() {
const prev = this._peek;
if (this._index < this.tokens.length - 1) {
// Note: there is always an EOF token at the end
this._index++;
}
this._peek = this.tokens[this._index];
return prev;
}
_advanceIf(type) {
if (this._peek.type === type) {
return this._advance();
}
return null;
}
_consumeCdata(_startToken) {
this._consumeText(this._advance());
this._advanceIf(13 /* TokenType.CDATA_END */);
}
_consumeComment(token) {
const text = this._advanceIf(7 /* TokenType.RAW_TEXT */);
const endToken = this._advanceIf(11 /* TokenType.COMMENT_END */);
const value = text != null ? text.parts[0].trim() : null;
const sourceSpan = endToken == null
? token.sourceSpan
: new ParseSourceSpan(token.sourceSpan.start, endToken.sourceSpan.end, token.sourceSpan.fullStart);
this._addToParent(new Comment(value, sourceSpan));
}
_consumeExpansion(token) {
const switchValue = this._advance();
const type = this._advance();
const cases = [];
// read =
while (this._peek.type === 20 /* TokenType.EXPANSION_CASE_VALUE */) {
const expCase = this._parseExpansionCase();
if (!expCase)
return; // error
cases.push(expCase);
}
// read the final }
if (this._peek.type !== 23 /* TokenType.EXPANSION_FORM_END */) {
this.errors.push(TreeError.create(null, this._peek.sourceSpan, `Invalid ICU message. Missing '}'.`));
return;
}
const sourceSpan = new ParseSourceSpan(token.sourceSpan.start, this._peek.sourceSpan.end, token.sourceSpan.fullStart);
this._addToParent(new Expansion(switchValue.parts[0], type.parts[0], cases, sourceSpan, switchValue.sourceSpan));
this._advance();
}
_parseExpansionCase() {
const value = this._advance();
// read {
if (this._peek.type !== 21 /* TokenType.EXPANSION_CASE_EXP_START */) {
this.errors.push(TreeError.create(null, this._peek.sourceSpan, `Invalid ICU message. Missing '{'.`));
return null;
}
// read until }
const start = this._advance();
const exp = this._collectExpansionExpTokens(start);
if (!exp)
return null;
const end = this._advance();
exp.push({ type: 33 /* TokenType.EOF */, parts: [], sourceSpan: end.sourceSpan });
// parse everything in between { and }
const expansionCaseParser = new _TreeBuilder(exp, this.getTagDefinition);
expansionCaseParser.build();
if (expansionCaseParser.errors.length > 0) {
this.errors = this.errors.concat(expansionCaseParser.errors);
return null;
}
const sourceSpan = new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end, value.sourceSpan.fullStart);
const expSourceSpan = new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end, start.sourceSpan.fullStart);
return new ExpansionCase(value.parts[0], expansionCaseParser.rootNodes, sourceSpan, value.sourceSpan, expSourceSpan);
}
_collectExpansionExpTokens(start) {
const exp = [];
const expansionFormStack = [21 /* TokenType.EXPANSION_CASE_EXP_START */];
while (true) {
if (this._peek.type === 19 /* TokenType.EXPANSION_FORM_START */ ||
this._peek.type === 21 /* TokenType.EXPANSION_CASE_EXP_START */) {
expansionFormStack.push(this._peek.type);
}
if (this._peek.type === 22 /* TokenType.EXPANSION_CASE_EXP_END */) {
if (lastOnStack(expansionFormStack, 21 /* TokenType.EXPANSION_CASE_EXP_START */)) {
expansionFormStack.pop();
if (expansionFormStack.length === 0)
return exp;
}
else {
this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
return null;
}
}
if (this._peek.type === 23 /* TokenType.EXPANSION_FORM_END */) {
if (lastOnStack(expansionFormStack, 19 /* TokenType.EXPANSION_FORM_START */)) {
expansionFormStack.pop();
}
else {
this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
return null;
}
}
if (this._peek.type === 33 /* TokenType.EOF */) {
this.errors.push(TreeError.create(null, start.sourceSpan, `Invalid ICU message. Missing '}'.`));
return null;
}
exp.push(this._advance());
}
}
_consumeText(token) {
const tokens = [token];
const startSpan = token.sourceSpan;
let text = token.parts[0];
if (text.length > 0 && text[0] === '\n') {
const parent = this._getContainer();
if (parent != null &&
parent.children.length === 0 &&
this.getTagDefinition(parent.name).ignoreFirstLf) {
text = text.substring(1);
tokens[0] = { type: token.type, sourceSpan: token.sourceSpan, parts: [text] };
}
}
while (this._peek.type === 8 /* TokenType.INTERPOLATION */ ||
this._peek.type === 5 /* TokenType.TEXT */ ||
this._peek.type === 9 /* TokenType.ENCODED_ENTITY */) {
token = this._advance();
tokens.push(token);
if (token.type === 8 /* TokenType.INTERPOLATION */) {
// For backward compatibility we decode HTML entities that appear in interpolation
// expressions. This is arguably a bug, but it could be a considerable breaking change to
// fix it. It should be addressed in a larger project to refactor the entire parser/lexer
// chain after View Engine has been removed.
text += token.parts.join('').replace(/&([^;]+);/g, decodeEntity);
}
else if (token.type === 9 /* TokenType.ENCODED_ENTITY */) {
text += token.parts[0];
}
else {
text += token.parts.join('');
}
}
if (text.length > 0) {
const endSpan = token.sourceSpan;
this._addToParent(new Text(text, new ParseSourceSpan(startSpan.start, endSpan.end, startSpan.fullStart, startSpan.details), tokens));
}
}
_closeVoidElement() {
const el = this._getContainer();
if (el instanceof Element && this.getTagDefinition(el.name).isVoid) {
this._containerStack.pop();
}
}
_consumeStartTag(startTagToken) {
const [prefix, name] = startTagToken.parts;
const attrs = [];
while (this._peek.type === 14 /* TokenType.ATTR_NAME */) {
attrs.push(this._consumeAttr(this._advance()));
}
const fullName = this._getElementFullName(prefix, name, this._getClosestParentElement());
let selfClosing = false;
// Note: There could have been a tokenizer error
// so that we don't get a token for the end tag...
if (this._peek.type === 2 /* TokenType.TAG_OPEN_END_VOID */) {
this._advance();
selfClosing = true;
const tagDef = this.getTagDefinition(fullName);
if (!(tagDef.canSelfClose || getNsPrefix(fullName) !== null || tagDef.isVoid)) {
this.errors.push(TreeError.create(fullName, startTagToken.sourceSpan, `Only void, custom and foreign elements can be self closed "${startTagToken.parts[1]}"`));
}
}
else if (this._peek.type === 1 /* TokenType.TAG_OPEN_END */) {
this._advance();
selfClosing = false;
}
const end = this._peek.sourceSpan.fullStart;
const span = new ParseSourceSpan(startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
// Create a separate `startSpan` because `span` will be modified when there is an `end` span.
const startSpan = new ParseSourceSpan(startTagToken.sourceSpan.start, end, startTagToken.sourceSpan.fullStart);
const el = new Element(fullName, attrs, [], span, startSpan, undefined);
const parentEl = this._getContainer();
this._pushContainer(el, parentEl instanceof Element &&
this.getTagDefinition(parentEl.name).isClosedByChild(el.name));
if (selfClosing) {
// Elements that are self-closed have their `endSourceSpan` set to the full span, as the
// element start tag also represents the end tag.
this._popContainer(fullName, Element, span);
}
else if (startTagToken.type === 4 /* TokenType.INCOMPLETE_TAG_OPEN */) {
// We already know the opening tag is not complete, so it is unlikely it has a corresponding
// close tag. Let's optimistically parse it as a full element and emit an error.
this._popContainer(fullName, Element, null);
this.errors.push(TreeError.create(fullName, span, `Opening tag "${fullName}" not terminated.`));
}
}
_pushContainer(node, isClosedByChild) {
if (isClosedByChild) {
this._containerStack.pop();
}
this._addToParent(node);
this._containerStack.push(node);
}
_consumeEndTag(endTagToken) {
const fullName = this._getElementFullName(endTagToken.parts[0], endTagToken.parts[1], this._getClosestParentElement());
if (this.getTagDefinition(fullName).isVoid) {
this.errors.push(TreeError.create(fullName, endTagToken.sourceSpan, `Void elements do not have end tags "${endTagToken.parts[1]}"`));
}
else if (!this._popContainer(fullName, Element, endTagToken.sourceSpan)) {
const errMsg = `Unexpected closing tag "${fullName}". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags`;
this.errors.push(TreeError.create(fullName, endTagToken.sourceSpan, errMsg));
}
}
/**
* Closes the nearest element with the tag name `fullName` in the parse tree.
* `endSourceSpan` is the span of the closing tag, or null if the element does
* not have a closing tag (for example, this happens when an incomplete
* opening tag is recovered).
*/
_popContainer(expectedName, expectedType, endSourceSpan) {
let unexpectedCloseTagDetected = false;
for (let stackIndex = this._containerStack.length - 1; stackIndex >= 0; stackIndex--) {
const node = this._containerStack[stackIndex];
if ((node.name === expectedName || expectedName === null) && node instanceof expectedType) {
// Record the parse span with the element that is being closed. Any elements that are
// removed from the element stack at this point are closed implicitly, so they won't get
// an end source span (as there is no explicit closing element).
node.endSourceSpan = endSourceSpan;
node.sourceSpan.end = endSourceSpan !== null ? endSourceSpan.end : node.sourceSpan.end;
this._containerStack.splice(stackIndex, this._containerStack.length - stackIndex);
return !unexpectedCloseTagDetected;
}
// Blocks and most elements are not self closing.
if (node instanceof Block ||
(node instanceof Element && !this.getTagDefinition(node.name).closedByParent)) {
// Note that we encountered an unexpected close tag but continue processing the element
// stack so we can assign an `endSourceSpan` if there is a corresponding start tag for this
// end tag in the stack.
unexpectedCloseTagDetected = true;
}
}
return false;
}
_consumeAttr(attrName) {
const fullName = mergeNsAndName(attrName.parts[0], attrName.parts[1]);
let attrEnd = attrName.sourceSpan.end;
// Consume any quote
if (this._peek.type === 15 /* TokenType.ATTR_QUOTE */) {
this._advance();
}
// Consume the attribute value
let value = '';
const valueTokens = [];
let valueStartSpan = undefined;
let valueEnd = undefined;
// NOTE: We need to use a new variable `nextTokenType` here to hide the actual type of
// `_peek.type` from TS. Otherwise TS will narrow the type of `_peek.type` preventing it from
// being able to consider `ATTR_VALUE_INTERPOLATION` as an option. This is because TS is not
// able to see that `_advance()` will actually mutate `_peek`.
const nextTokenType = this._peek.type;
if (nextTokenType === 16 /* TokenType.ATTR_VALUE_TEXT */) {
valueStartSpan = this._peek.sourceSpan;
valueEnd = this._peek.sourceSpan.end;
while (this._peek.type === 16 /* TokenType.ATTR_VALUE_TEXT */ ||
this._peek.type === 17 /* TokenType.ATTR_VALUE_INTERPOLATION */ ||
this._peek.type === 9 /* TokenType.ENCODED_ENTITY */) {
const valueToken = this._advance();
valueTokens.push(valueToken);
if (valueToken.type === 17 /* TokenType.ATTR_VALUE_INTERPOLATION */) {
// For backward compatibility we decode HTML entities that appear in interpolation
// expressions. This is arguably a bug, but it could be a considerable breaking change to
// fix it. It should be addressed in a larger project to refactor the entire parser/lexer
// chain after View Engine has been removed.
value += valueToken.parts.join('').replace(/&([^;]+);/g, decodeEntity);
}
else if (valueToken.type === 9 /* TokenType.ENCODED_ENTITY */) {
value += valueToken.parts[0];
}
else {
value += valueToken.parts.join('');
}
valueEnd = attrEnd = valueToken.sourceSpan.end;
}
}
// Consume any quote
if (this._peek.type === 15 /* TokenType.ATTR_QUOTE */) {
const quoteToken = this._advance();
attrEnd = quoteToken.sourceSpan.end;
}
const valueSpan = valueStartSpan &&
valueEnd &&
new ParseSourceSpan(valueStartSpan.start, valueEnd, valueStartSpan.fullStart);
return new Attribute(fullName, value, new ParseSourceSpan(attrName.sourceSpan.start, attrEnd, attrName.sourceSpan.fullStart), attrName.sourceSpan, valueSpan, valueTokens.length > 0 ? valueTokens : undefined, undefined);
}
_consumeBlockOpen(token) {
const parameters = [];
while (this._peek.type === 27 /* TokenType.BLOCK_PARAMETER */) {
const paramToken = this._advance();
parameters.push(new BlockParameter(paramToken.parts[0], paramToken.sourceSpan));
}
if (this._peek.type === 25 /* TokenType.BLOCK_OPEN_END */) {
this._advance();
}
const end = this._peek.sourceSpan.fullStart;
const span = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
// Create a separate `startSpan` because `span` will be modified when there is an `end` span.
const startSpan = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
const block = new Block(token.parts[0], parameters, [], span, token.sourceSpan, startSpan);
this._pushContainer(block, false);
}
_consumeBlockClose(token) {
if (!this._popContainer(null, Block, token.sourceSpan)) {
this.errors.push(TreeError.create(null, token.sourceSpan, `Unexpected closing block. The block may have been closed earlier. ` +
`If you meant to write the } character, you should use the "}" ` +
`HTML entity instead.`));
}
}
_consumeIncompleteBlock(token) {
const parameters = [];
while (this._peek.type === 27 /* TokenType.BLOCK_PARAMETER */) {
const paramToken = this._advance();
parameters.push(new BlockParameter(paramToken.parts[0], paramToken.sourceSpan));
}
const end = this._peek.sourceSpan.fullStart;
const span = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
// Create a separate `startSpan` because `span` will be modified when there is an `end` span.
const startSpan = new ParseSourceSpan(token.sourceSpan.start, end, token.sourceSpan.fullStart);
const block = new Block(token.parts[0], parameters, [], span, token.sourceSpan, startSpan);
this._pushContainer(block, false);
// Incomplete blocks don't have children so we close them immediately and report an error.
this._popContainer(null, Block, null);
this.errors.push(TreeError.create(token.parts[0], span, `Incomplete block "${token.parts[0]}". If you meant to write the @ character, ` +
`you should use the "@" HTML entity instead.`));
}
_consumeLet(startToken) {
const name = startToken.parts[0];
let valueToken;
let endToken;
if (this._peek.type !== 30 /* TokenType.LET_VALUE */) {
this.errors.push(TreeError.create(startToken.parts[0], startToken.sourceSpan, `Invalid @let declaration "${name}". Declaration must have a value.`));
return;
}
else {
valueToken = this._advance();
}
// Type cast is necessary here since TS narrowed the type of `peek` above.
if (this._peek.type !== 31 /* TokenType.LET_END */) {
this.errors.push(TreeError.create(startToken.parts[0], startToken.sourceSpan, `Unterminated @let declaration "${name}". Declaration must be terminated with a semicolon.`));
return;
}
else {
endToken = this._advance();
}
const end = endToken.sourceSpan.fullStart;
const span = new ParseSourceSpan(startToken.sourceSpan.start, end, startToken.sourceSpan.fullStart);
// The start token usually captures the `@let`. Construct a name span by
// offsetting the start by the length of any text before the name.
const startOffset = startToken.sourceSpan.toString().lastIndexOf(name);
const nameStart = startToken.sourceSpan.start.moveBy(startOffset);
const nameSpan = new ParseSourceSpan(nameStart, startToken.sourceSpan.end);
const node = new LetDeclaration(name, valueToken.parts[0], span, nameSpan, valueToken.sourceSpan);
this._addToParent(node);
}
_consumeIncompleteLet(token) {
// Incomplete `@let` declaration may end up with an empty name.
const name = token.parts[0] ?? '';
const nameString = name ? ` "${name}"` : '';
// If there's at least a name, we can salvage an AST node that can be used for completions.
if (name.length > 0) {
const startOffset = token.sourceSpan.toString().lastIndexOf(name);
const nameStart = token.sourceSpan.start.moveBy(startOffset);
const nameSpan = new ParseSourceSpan(nameStart, token.sourceSpan.end);
const valueSpan = new ParseSourceSpan(token.sourceSpan.start, token.sourceSpan.start.moveBy(0));
const node = new LetDeclaration(name, '', token.sourceSpan, nameSpan, valueSpan);
this._addToParent(node);
}
this.errors.push(TreeError.create(token.parts[0], token.sourceSpan, `Incomplete @let declaration${nameString}. ` +
`@let declarations must be written as \`@let = ;\``));
}
_getContainer() {
return this._containerStack.length > 0
? this._containerStack[this._containerStack.length - 1]
: null;
}
_getClosestParentElement() {
for (let i = this._containerStack.length - 1; i > -1; i--) {
if (this._containerStack[i] instanceof Element) {
return this._containerStack[i];
}
}
return null;
}
_addToParent(node) {
const parent = this._getContainer();
if (parent === null) {
this.rootNodes.push(node);
}
else {
parent.children.push(node);
}
}
_getElementFullName(prefix, localName, parentElement) {
if (prefix === '') {
prefix = this.getTagDefinition(localName).implicitNamespacePrefix || '';
if (prefix === '' && parentElement != null) {
const parentTagName = splitNsName(parentElement.name)[1];
const parentTagDefinition = this.getTagDefinition(parentTagName);
if (!parentTagDefinition.preventNamespaceInheritance) {
prefix = getNsPrefix(parentElement.name);
}
}
}
return mergeNsAndName(prefix, localName);
}
}
function lastOnStack(stack, element) {
return stack.length > 0 && stack[stack.length - 1] === element;
}
/**
* Decode the `entity` string, which we believe is the contents of an HTML entity.
*
* If the string is not actually a valid/known entity then just return the original `match` string.
*/
function decodeEntity(match, entity) {
if (NAMED_ENTITIES[entity] !== undefined) {
return NAMED_ENTITIES[entity] || match;
}
if (/^#x[a-f0-9]+$/i.test(entity)) {
return String.fromCodePoint(parseInt(entity.slice(2), 16));
}
if (/^#\d+$/.test(entity)) {
return String.fromCodePoint(parseInt(entity.slice(1), 10));
}
return match;
}
/**
* @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.dev/license
*/
const PRESERVE_WS_ATTR_NAME = 'ngPreserveWhitespaces';
const SKIP_WS_TRIM_TAGS = new Set(['pre', 'template', 'textarea', 'script', 'style']);
// Equivalent to \s with \u00a0 (non-breaking space) excluded.
// Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
const WS_CHARS = ' \f\n\r\t\v\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff';
const NO_WS_REGEXP = new RegExp(`[^${WS_CHARS}]`);
const WS_REPLACE_REGEXP = new RegExp(`[${WS_CHARS}]{2,}`, 'g');
function hasPreserveWhitespacesAttr(attrs) {
return attrs.some((attr) => attr.name === PRESERVE_WS_ATTR_NAME);
}
/**
* &ngsp; is a placeholder for non-removable space
* &ngsp; is converted to the 0xE500 PUA (Private Use Areas) unicode character
* and later on replaced by a space.
*/
function replaceNgsp(value) {
// lexer is replacing the &ngsp; pseudo-entity with NGSP_UNICODE
return value.replace(new RegExp(NGSP_UNICODE, 'g'), ' ');
}
/**
* This visitor can walk HTML parse tree and remove / trim text nodes using the following rules:
* - consider spaces, tabs and new lines as whitespace characters;
* - drop text nodes consisting of whitespace characters only;
* - for all other text nodes replace consecutive whitespace characters with one space;
* - convert &ngsp; pseudo-entity to a single space;
*
* Removal and trimming of whitespaces have positive performance impact (less code to generate
* while compiling templates, faster view creation). At the same time it can be "destructive"
* in some cases (whitespaces can influence layout). Because of the potential of breaking layout
* this visitor is not activated by default in Angular 5 and people need to explicitly opt-in for
* whitespace removal. The default option for whitespace removal will be revisited in Angular 6
* and might be changed to "on" by default.
*
* If `originalNodeMap` is provided, the transformed nodes will be mapped back to their original
* inputs. Any output nodes not in the map were not transformed. This supports correlating and
* porting information between the trimmed nodes and original nodes (such as `i18n` properties)
* such that trimming whitespace does not does not drop required information from the node.
*/
class WhitespaceVisitor {
constructor(preserveSignificantWhitespace, originalNodeMap, requireContext = true) {
this.preserveSignificantWhitespace = preserveSignificantWhitespace;
this.originalNodeMap = originalNodeMap;
this.requireContext = requireContext;
// How many ICU expansions which are currently being visited. ICUs can be nested, so this
// tracks the current depth of nesting. If this depth is greater than 0, then this visitor is
// currently processing content inside an ICU expansion.
this.icuExpansionDepth = 0;
}
visitElement(element, context) {
if (SKIP_WS_TRIM_TAGS.has(element.name) || hasPreserveWhitespacesAttr(element.attrs)) {
// don't descent into elements where we need to preserve whitespaces
// but still visit all attributes to eliminate one used as a market to preserve WS
const newElement = new Element(element.name, visitAllWithSiblings(this, element.attrs), element.children, element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
this.originalNodeMap?.set(newElement, element);
return newElement;
}
const newElement = new Element(element.name, element.attrs, visitAllWithSiblings(this, element.children), element.sourceSpan, element.startSourceSpan, element.endSourceSpan, element.i18n);
this.originalNodeMap?.set(newElement, element);
return newElement;
}
visitAttribute(attribute, context) {
return attribute.name !== PRESERVE_WS_ATTR_NAME ? attribute : null;
}
visitText(text, context) {
const isNotBlank = text.value.match(NO_WS_REGEXP);
const hasExpansionSibling = context && (context.prev instanceof Expansion || context.next instanceof Expansion);
// Do not trim whitespace within ICU expansions when preserving significant whitespace.
// Historically, ICU whitespace was never trimmed and this is really a bug. However fixing it
// would change message IDs which we can't easily do. Instead we only trim ICU whitespace within
// ICU expansions when not preserving significant whitespace, which is the new behavior where it
// most matters.
const inIcuExpansion = this.icuExpansionDepth > 0;
if (inIcuExpansion && this.preserveSignificantWhitespace)
return text;
if (isNotBlank || hasExpansionSibling) {
// Process the whitespace in the tokens of this Text node
const tokens = text.tokens.map((token) => token.type === 5 /* TokenType.TEXT */ ? createWhitespaceProcessedTextToken(token) : token);
// Fully trim message when significant whitespace is not preserved.
if (!this.preserveSignificantWhitespace && tokens.length > 0) {
// The first token should only call `.trimStart()` and the last token
// should only call `.trimEnd()`, but there might be only one token which
// needs to call both.
const firstToken = tokens[0];
tokens.splice(0, 1, trimLeadingWhitespace(firstToken, context));
const lastToken = tokens[tokens.length - 1]; // Could be the same as the first token.
tokens.splice(tokens.length - 1, 1, trimTrailingWhitespace(lastToken, context));
}
// Process the whitespace of the value of this Text node. Also trim the leading/trailing
// whitespace when we don't need to preserve significant whitespace.
const processed = processWhitespace(text.value);
const value = this.preserveSignificantWhitespace
? processed
: trimLeadingAndTrailingWhitespace(processed, context);
const result = new Text(value, text.sourceSpan, tokens, text.i18n);
this.originalNodeMap?.set(result, text);
return result;
}
return null;
}
visitComment(comment, context) {
return comment;
}
visitExpansion(expansion, context) {
this.icuExpansionDepth++;
let newExpansion;
try {
newExpansion = new Expansion(expansion.switchValue, expansion.type, visitAllWithSiblings(this, expansion.cases), expansion.sourceSpan, expansion.switchValueSourceSpan, expansion.i18n);
}
finally {
this.icuExpansionDepth--;
}
this.originalNodeMap?.set(newExpansion, expansion);
return newExpansion;
}
visitExpansionCase(expansionCase, context) {
const newExpansionCase = new ExpansionCase(expansionCase.value, visitAllWithSiblings(this, expansionCase.expression), expansionCase.sourceSpan, expansionCase.valueSourceSpan, expansionCase.expSourceSpan);
this.originalNodeMap?.set(newExpansionCase, expansionCase);
return newExpansionCase;
}
visitBlock(block, context) {
const newBlock = new Block(block.name, block.parameters, visitAllWithSiblings(this, block.children), block.sourceSpan, block.nameSpan, block.startSourceSpan, block.endSourceSpan);
this.originalNodeMap?.set(newBlock, block);
return newBlock;
}
visitBlockParameter(parameter, context) {
return parameter;
}
visitLetDeclaration(decl, context) {
return decl;
}
visit(_node, context) {
// `visitAllWithSiblings` provides context necessary for ICU messages to be handled correctly.
// Prefer that over calling `html.visitAll` directly on this visitor.
if (this.requireContext && !context) {
throw new Error(`WhitespaceVisitor requires context. Visit via \`visitAllWithSiblings\` to get this context.`);
}
return false;
}
}
function trimLeadingWhitespace(token, context) {
if (token.type !== 5 /* TokenType.TEXT */)
return token;
const isFirstTokenInTag = !context?.prev;
if (!isFirstTokenInTag)
return token;
return transformTextToken(token, (text) => text.trimStart());
}
function trimTrailingWhitespace(token, context) {
if (token.type !== 5 /* TokenType.TEXT */)
return token;
const isLastTokenInTag = !context?.next;
if (!isLastTokenInTag)
return token;
return transformTextToken(token, (text) => text.trimEnd());
}
function trimLeadingAndTrailingWhitespace(text, context) {
const isFirstTokenInTag = !context?.prev;
const isLastTokenInTag = !context?.next;
const maybeTrimmedStart = isFirstTokenInTag ? text.trimStart() : text;
const maybeTrimmed = isLastTokenInTag ? maybeTrimmedStart.trimEnd() : maybeTrimmedStart;
return maybeTrimmed;
}
function createWhitespaceProcessedTextToken({ type, parts, sourceSpan }) {
return { type, parts: [processWhitespace(parts[0])], sourceSpan };
}
function transformTextToken({ type, parts, sourceSpan }, transform) {
// `TextToken` only ever has one part as defined in its type, so we just transform the first element.
return { type, parts: [transform(parts[0])], sourceSpan };
}
function processWhitespace(text) {
return replaceNgsp(text).replace(WS_REPLACE_REGEXP, ' ');
}
function visitAllWithSiblings(visitor, nodes) {
const result = [];
nodes.forEach((ast, i) => {
const context = { prev: nodes[i - 1], next: nodes[i + 1] };
const astResult = ast.visit(visitor, context);
if (astResult) {
result.push(astResult);
}
});
return result;
}
/**
* @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.dev/license
*/
var TokenType;
(function (TokenType) {
TokenType[TokenType["Character"] = 0] = "Character";
TokenType[TokenType["Identifier"] = 1] = "Identifier";
TokenType[TokenType["PrivateIdentifier"] = 2] = "PrivateIdentifier";
TokenType[TokenType["Keyword"] = 3] = "Keyword";
TokenType[TokenType["String"] = 4] = "String";
TokenType[TokenType["Operator"] = 5] = "Operator";
TokenType[TokenType["Number"] = 6] = "Number";
TokenType[TokenType["Error"] = 7] = "Error";
})(TokenType || (TokenType = {}));
const KEYWORDS = ['var', 'let', 'as', 'null', 'undefined', 'true', 'false', 'if', 'else', 'this'];
class Lexer {
tokenize(text) {
const scanner = new _Scanner(text);
const tokens = [];
let token = scanner.scanToken();
while (token != null) {
tokens.push(token);
token = scanner.scanToken();
}
return tokens;
}
}
class Token {
constructor(index, end, type, numValue, strValue) {
this.index = index;
this.end = end;
this.type = type;
this.numValue = numValue;
this.strValue = strValue;
}
isCharacter(code) {
return this.type == TokenType.Character && this.numValue == code;
}
isNumber() {
return this.type == TokenType.Number;
}
isString() {
return this.type == TokenType.String;
}
isOperator(operator) {
return this.type == TokenType.Operator && this.strValue == operator;
}
isIdentifier() {
return this.type == TokenType.Identifier;
}
isPrivateIdentifier() {
return this.type == TokenType.PrivateIdentifier;
}
isKeyword() {
return this.type == TokenType.Keyword;
}
isKeywordLet() {
return this.type == TokenType.Keyword && this.strValue == 'let';
}
isKeywordAs() {
return this.type == TokenType.Keyword && this.strValue == 'as';
}
isKeywordNull() {
return this.type == TokenType.Keyword && this.strValue == 'null';
}
isKeywordUndefined() {
return this.type == TokenType.Keyword && this.strValue == 'undefined';
}
isKeywordTrue() {
return this.type == TokenType.Keyword && this.strValue == 'true';
}
isKeywordFalse() {
return this.type == TokenType.Keyword && this.strValue == 'false';
}
isKeywordThis() {
return this.type == TokenType.Keyword && this.strValue == 'this';
}
isError() {
return this.type == TokenType.Error;
}
toNumber() {
return this.type == TokenType.Number ? this.numValue : -1;
}
toString() {
switch (this.type) {
case TokenType.Character:
case TokenType.Identifier:
case TokenType.Keyword:
case TokenType.Operator:
case TokenType.PrivateIdentifier:
case TokenType.String:
case TokenType.Error:
return this.strValue;
case TokenType.Number:
return this.numValue.toString();
default:
return null;
}
}
}
function newCharacterToken(index, end, code) {
return new Token(index, end, TokenType.Character, code, String.fromCharCode(code));
}
function newIdentifierToken(index, end, text) {
return new Token(index, end, TokenType.Identifier, 0, text);
}
function newPrivateIdentifierToken(index, end, text) {
return new Token(index, end, TokenType.PrivateIdentifier, 0, text);
}
function newKeywordToken(index, end, text) {
return new Token(index, end, TokenType.Keyword, 0, text);
}
function newOperatorToken(index, end, text) {
return new Token(index, end, TokenType.Operator, 0, text);
}
function newStringToken(index, end, text) {
return new Token(index, end, TokenType.String, 0, text);
}
function newNumberToken(index, end, n) {
return new Token(index, end, TokenType.Number, n, '');
}
function newErrorToken(index, end, message) {
return new Token(index, end, TokenType.Error, 0, message);
}
const EOF = new Token(-1, -1, TokenType.Character, 0, '');
class _Scanner {
constructor(input) {
this.input = input;
this.peek = 0;
this.index = -1;
this.length = input.length;
this.advance();
}
advance() {
this.peek = ++this.index >= this.length ? $EOF : this.input.charCodeAt(this.index);
}
scanToken() {
const input = this.input, length = this.length;
let peek = this.peek, index = this.index;
// Skip whitespace.
while (peek <= $SPACE) {
if (++index >= length) {
peek = $EOF;
break;
}
else {
peek = input.charCodeAt(index);
}
}
this.peek = peek;
this.index = index;
if (index >= length) {
return null;
}
// Handle identifiers and numbers.
if (isIdentifierStart(peek))
return this.scanIdentifier();
if (isDigit(peek))
return this.scanNumber(index);
const start = index;
switch (peek) {
case $PERIOD:
this.advance();
return isDigit(this.peek)
? this.scanNumber(start)
: newCharacterToken(start, this.index, $PERIOD);
case $LPAREN:
case $RPAREN:
case $LBRACE:
case $RBRACE:
case $LBRACKET:
case $RBRACKET:
case $COMMA:
case $COLON:
case $SEMICOLON:
return this.scanCharacter(start, peek);
case $SQ:
case $DQ:
return this.scanString();
case $HASH:
return this.scanPrivateIdentifier();
case $PLUS:
case $MINUS:
case $STAR:
case $SLASH:
case $PERCENT:
case $CARET:
return this.scanOperator(start, String.fromCharCode(peek));
case $QUESTION:
return this.scanQuestion(start);
case $LT:
case $GT:
return this.scanComplexOperator(start, String.fromCharCode(peek), $EQ, '=');
case $BANG:
case $EQ:
return this.scanComplexOperator(start, String.fromCharCode(peek), $EQ, '=', $EQ, '=');
case $AMPERSAND:
return this.scanComplexOperator(start, '&', $AMPERSAND, '&');
case $BAR:
return this.scanComplexOperator(start, '|', $BAR, '|');
case $NBSP:
while (isWhitespace(this.peek))
this.advance();
return this.scanToken();
}
this.advance();
return this.error(`Unexpected character [${String.fromCharCode(peek)}]`, 0);
}
scanCharacter(start, code) {
this.advance();
return newCharacterToken(start, this.index, code);
}
scanOperator(start, str) {
this.advance();
return newOperatorToken(start, this.index, str);
}
/**
* Tokenize a 2/3 char long operator
*
* @param start start index in the expression
* @param one first symbol (always part of the operator)
* @param twoCode code point for the second symbol
* @param two second symbol (part of the operator when the second code point matches)
* @param threeCode code point for the third symbol
* @param three third symbol (part of the operator when provided and matches source expression)
*/
scanComplexOperator(start, one, twoCode, two, threeCode, three) {
this.advance();
let str = one;
if (this.peek == twoCode) {
this.advance();
str += two;
}
if (threeCode != null && this.peek == threeCode) {
this.advance();
str += three;
}
return newOperatorToken(start, this.index, str);
}
scanIdentifier() {
const start = this.index;
this.advance();
while (isIdentifierPart(this.peek))
this.advance();
const str = this.input.substring(start, this.index);
return KEYWORDS.indexOf(str) > -1
? newKeywordToken(start, this.index, str)
: newIdentifierToken(start, this.index, str);
}
/** Scans an ECMAScript private identifier. */
scanPrivateIdentifier() {
const start = this.index;
this.advance();
if (!isIdentifierStart(this.peek)) {
return this.error('Invalid character [#]', -1);
}
while (isIdentifierPart(this.peek))
this.advance();
const identifierName = this.input.substring(start, this.index);
return newPrivateIdentifierToken(start, this.index, identifierName);
}
scanNumber(start) {
let simple = this.index === start;
let hasSeparators = false;
this.advance(); // Skip initial digit.
while (true) {
if (isDigit(this.peek)) ;
else if (this.peek === $_) {
// Separators are only valid when they're surrounded by digits. E.g. `1_0_1` is
// valid while `_101` and `101_` are not. The separator can't be next to the decimal
// point or another separator either. Note that it's unlikely that we'll hit a case where
// the underscore is at the start, because that's a valid identifier and it will be picked
// up earlier in the parsing. We validate for it anyway just in case.
if (!isDigit(this.input.charCodeAt(this.index - 1)) ||
!isDigit(this.input.charCodeAt(this.index + 1))) {
return this.error('Invalid numeric separator', 0);
}
hasSeparators = true;
}
else if (this.peek === $PERIOD) {
simple = false;
}
else if (isExponentStart(this.peek)) {
this.advance();
if (isExponentSign(this.peek))
this.advance();
if (!isDigit(this.peek))
return this.error('Invalid exponent', -1);
simple = false;
}
else {
break;
}
this.advance();
}
let str = this.input.substring(start, this.index);
if (hasSeparators) {
str = str.replace(/_/g, '');
}
const value = simple ? parseIntAutoRadix(str) : parseFloat(str);
return newNumberToken(start, this.index, value);
}
scanString() {
const start = this.index;
const quote = this.peek;
this.advance(); // Skip initial quote.
let buffer = '';
let marker = this.index;
const input = this.input;
while (this.peek != quote) {
if (this.peek == $BACKSLASH) {
buffer += input.substring(marker, this.index);
let unescapedCode;
this.advance(); // mutates this.peek
// @ts-expect-error see microsoft/TypeScript#9998
if (this.peek == $u) {
// 4 character hex code for unicode character.
const hex = input.substring(this.index + 1, this.index + 5);
if (/^[0-9a-f]+$/i.test(hex)) {
unescapedCode = parseInt(hex, 16);
}
else {
return this.error(`Invalid unicode escape [\\u${hex}]`, 0);
}
for (let i = 0; i < 5; i++) {
this.advance();
}
}
else {
unescapedCode = unescape$1(this.peek);
this.advance();
}
buffer += String.fromCharCode(unescapedCode);
marker = this.index;
}
else if (this.peek == $EOF) {
return this.error('Unterminated quote', 0);
}
else {
this.advance();
}
}
const last = input.substring(marker, this.index);
this.advance(); // Skip terminating quote.
return newStringToken(start, this.index, buffer + last);
}
scanQuestion(start) {
this.advance();
let str = '?';
// Either `a ?? b` or 'a?.b'.
if (this.peek === $QUESTION || this.peek === $PERIOD) {
str += this.peek === $PERIOD ? '.' : '?';
this.advance();
}
return newOperatorToken(start, this.index, str);
}
error(message, offset) {
const position = this.index + offset;
return newErrorToken(position, this.index, `Lexer Error: ${message} at column ${position} in expression [${this.input}]`);
}
}
function isIdentifierStart(code) {
return (($a <= code && code <= $z) ||
($A <= code && code <= $Z) ||
code == $_ ||
code == $$);
}
function isIdentifierPart(code) {
return isAsciiLetter(code) || isDigit(code) || code == $_ || code == $$;
}
function isExponentStart(code) {
return code == $e || code == $E;
}
function isExponentSign(code) {
return code == $MINUS || code == $PLUS;
}
function unescape$1(code) {
switch (code) {
case $n:
return $LF;
case $f:
return $FF;
case $r:
return $CR;
case $t:
return $TAB;
case $v:
return $VTAB;
default:
return code;
}
}
function parseIntAutoRadix(text) {
const result = parseInt(text);
if (isNaN(result)) {
throw new Error('Invalid integer literal when parsing ' + text);
}
return result;
}
/**
* @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.dev/license
*/
class SplitInterpolation {
constructor(strings, expressions, offsets) {
this.strings = strings;
this.expressions = expressions;
this.offsets = offsets;
}
}
class TemplateBindingParseResult {
constructor(templateBindings, warnings, errors) {
this.templateBindings = templateBindings;
this.warnings = warnings;
this.errors = errors;
}
}
class Parser {
constructor(_lexer) {
this._lexer = _lexer;
this.errors = [];
}
parseAction(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
this._checkNoInterpolation(input, location, interpolationConfig);
const sourceToLex = this._stripComments(input);
const tokens = this._lexer.tokenize(sourceToLex);
const ast = new _ParseAST(input, location, absoluteOffset, tokens, 1 /* ParseFlags.Action */, this.errors, 0).parseChain();
return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
}
parseBinding(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
const ast = this._parseBindingAst(input, location, absoluteOffset, interpolationConfig);
return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
}
checkSimpleExpression(ast) {
const checker = new SimpleExpressionChecker();
ast.visit(checker);
return checker.errors;
}
// Host bindings parsed here
parseSimpleBinding(input, location, absoluteOffset, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
const ast = this._parseBindingAst(input, location, absoluteOffset, interpolationConfig);
const errors = this.checkSimpleExpression(ast);
if (errors.length > 0) {
this._reportError(`Host binding expression cannot contain ${errors.join(' ')}`, input, location);
}
return new ASTWithSource(ast, input, location, absoluteOffset, this.errors);
}
_reportError(message, input, errLocation, ctxLocation) {
this.errors.push(new ParserError(message, input, errLocation, ctxLocation));
}
_parseBindingAst(input, location, absoluteOffset, interpolationConfig) {
this._checkNoInterpolation(input, location, interpolationConfig);
const sourceToLex = this._stripComments(input);
const tokens = this._lexer.tokenize(sourceToLex);
return new _ParseAST(input, location, absoluteOffset, tokens, 0 /* ParseFlags.None */, this.errors, 0).parseChain();
}
/**
* Parse microsyntax template expression and return a list of bindings or
* parsing errors in case the given expression is invalid.
*
* For example,
* ```
*
* ^ ^ absoluteValueOffset for `templateValue`
* absoluteKeyOffset for `templateKey`
* ```
* contains three bindings:
* 1. ngFor -> null
* 2. item -> NgForOfContext.$implicit
* 3. ngForOf -> items
*
* This is apparent from the de-sugared template:
* ```
*
* ```
*
* @param templateKey name of directive, without the * prefix. For example: ngIf, ngFor
* @param templateValue RHS of the microsyntax attribute
* @param templateUrl template filename if it's external, component filename if it's inline
* @param absoluteKeyOffset start of the `templateKey`
* @param absoluteValueOffset start of the `templateValue`
*/
parseTemplateBindings(templateKey, templateValue, templateUrl, absoluteKeyOffset, absoluteValueOffset) {
const tokens = this._lexer.tokenize(templateValue);
const parser = new _ParseAST(templateValue, templateUrl, absoluteValueOffset, tokens, 0 /* ParseFlags.None */, this.errors, 0 /* relative offset */);
return parser.parseTemplateBindings({
source: templateKey,
span: new AbsoluteSourceSpan$1(absoluteKeyOffset, absoluteKeyOffset + templateKey.length),
});
}
parseInterpolation(input, location, absoluteOffset, interpolatedTokens, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
const { strings, expressions, offsets } = this.splitInterpolation(input, location, interpolatedTokens, interpolationConfig);
if (expressions.length === 0)
return null;
const expressionNodes = [];
for (let i = 0; i < expressions.length; ++i) {
const expressionText = expressions[i].text;
const sourceToLex = this._stripComments(expressionText);
const tokens = this._lexer.tokenize(sourceToLex);
const ast = new _ParseAST(input, location, absoluteOffset, tokens, 0 /* ParseFlags.None */, this.errors, offsets[i]).parseChain();
expressionNodes.push(ast);
}
return this.createInterpolationAst(strings.map((s) => s.text), expressionNodes, input, location, absoluteOffset);
}
/**
* Similar to `parseInterpolation`, but treats the provided string as a single expression
* element that would normally appear within the interpolation prefix and suffix (`{{` and `}}`).
* This is used for parsing the switch expression in ICUs.
*/
parseInterpolationExpression(expression, location, absoluteOffset) {
const sourceToLex = this._stripComments(expression);
const tokens = this._lexer.tokenize(sourceToLex);
const ast = new _ParseAST(expression, location, absoluteOffset, tokens, 0 /* ParseFlags.None */, this.errors, 0).parseChain();
const strings = ['', '']; // The prefix and suffix strings are both empty
return this.createInterpolationAst(strings, [ast], expression, location, absoluteOffset);
}
createInterpolationAst(strings, expressions, input, location, absoluteOffset) {
const span = new ParseSpan(0, input.length);
const interpolation = new Interpolation$1(span, span.toAbsolute(absoluteOffset), strings, expressions);
return new ASTWithSource(interpolation, input, location, absoluteOffset, this.errors);
}
/**
* Splits a string of text into "raw" text segments and expressions present in interpolations in
* the string.
* Returns `null` if there are no interpolations, otherwise a
* `SplitInterpolation` with splits that look like
* ...
*/
splitInterpolation(input, location, interpolatedTokens, interpolationConfig = DEFAULT_INTERPOLATION_CONFIG) {
const strings = [];
const expressions = [];
const offsets = [];
const inputToTemplateIndexMap = interpolatedTokens
? getIndexMapForOriginalTemplate(interpolatedTokens)
: null;
let i = 0;
let atInterpolation = false;
let extendLastString = false;
let { start: interpStart, end: interpEnd } = interpolationConfig;
while (i < input.length) {
if (!atInterpolation) {
// parse until starting {{
const start = i;
i = input.indexOf(interpStart, i);
if (i === -1) {
i = input.length;
}
const text = input.substring(start, i);
strings.push({ text, start, end: i });
atInterpolation = true;
}
else {
// parse from starting {{ to ending }} while ignoring content inside quotes.
const fullStart = i;
const exprStart = fullStart + interpStart.length;
const exprEnd = this._getInterpolationEndIndex(input, interpEnd, exprStart);
if (exprEnd === -1) {
// Could not find the end of the interpolation; do not parse an expression.
// Instead we should extend the content on the last raw string.
atInterpolation = false;
extendLastString = true;
break;
}
const fullEnd = exprEnd + interpEnd.length;
const text = input.substring(exprStart, exprEnd);
if (text.trim().length === 0) {
this._reportError('Blank expressions are not allowed in interpolated strings', input, `at column ${i} in`, location);
}
expressions.push({ text, start: fullStart, end: fullEnd });
const startInOriginalTemplate = inputToTemplateIndexMap?.get(fullStart) ?? fullStart;
const offset = startInOriginalTemplate + interpStart.length;
offsets.push(offset);
i = fullEnd;
atInterpolation = false;
}
}
if (!atInterpolation) {
// If we are now at a text section, add the remaining content as a raw string.
if (extendLastString) {
const piece = strings[strings.length - 1];
piece.text += input.substring(i);
piece.end = input.length;
}
else {
strings.push({ text: input.substring(i), start: i, end: input.length });
}
}
return new SplitInterpolation(strings, expressions, offsets);
}
wrapLiteralPrimitive(input, location, absoluteOffset) {
const span = new ParseSpan(0, input == null ? 0 : input.length);
return new ASTWithSource(new LiteralPrimitive(span, span.toAbsolute(absoluteOffset), input), input, location, absoluteOffset, this.errors);
}
_stripComments(input) {
const i = this._commentStart(input);
return i != null ? input.substring(0, i) : input;
}
_commentStart(input) {
let outerQuote = null;
for (let i = 0; i < input.length - 1; i++) {
const char = input.charCodeAt(i);
const nextChar = input.charCodeAt(i + 1);
if (char === $SLASH && nextChar == $SLASH && outerQuote == null)
return i;
if (outerQuote === char) {
outerQuote = null;
}
else if (outerQuote == null && isQuote(char)) {
outerQuote = char;
}
}
return null;
}
_checkNoInterpolation(input, location, { start, end }) {
let startIndex = -1;
let endIndex = -1;
for (const charIndex of this._forEachUnquotedChar(input, 0)) {
if (startIndex === -1) {
if (input.startsWith(start)) {
startIndex = charIndex;
}
}
else {
endIndex = this._getInterpolationEndIndex(input, end, charIndex);
if (endIndex > -1) {
break;
}
}
}
if (startIndex > -1 && endIndex > -1) {
this._reportError(`Got interpolation (${start}${end}) where expression was expected`, input, `at column ${startIndex} in`, location);
}
}
/**
* Finds the index of the end of an interpolation expression
* while ignoring comments and quoted content.
*/
_getInterpolationEndIndex(input, expressionEnd, start) {
for (const charIndex of this._forEachUnquotedChar(input, start)) {
if (input.startsWith(expressionEnd, charIndex)) {
return charIndex;
}
// Nothing else in the expression matters after we've
// hit a comment so look directly for the end token.
if (input.startsWith('//', charIndex)) {
return input.indexOf(expressionEnd, charIndex);
}
}
return -1;
}
/**
* Generator used to iterate over the character indexes of a string that are outside of quotes.
* @param input String to loop through.
* @param start Index within the string at which to start.
*/
*_forEachUnquotedChar(input, start) {
let currentQuote = null;
let escapeCount = 0;
for (let i = start; i < input.length; i++) {
const char = input[i];
// Skip the characters inside quotes. Note that we only care about the outer-most
// quotes matching up and we need to account for escape characters.
if (isQuote(input.charCodeAt(i)) &&
(currentQuote === null || currentQuote === char) &&
escapeCount % 2 === 0) {
currentQuote = currentQuote === null ? char : null;
}
else if (currentQuote === null) {
yield i;
}
escapeCount = char === '\\' ? escapeCount + 1 : 0;
}
}
}
/** Describes a stateful context an expression parser is in. */
var ParseContextFlags;
(function (ParseContextFlags) {
ParseContextFlags[ParseContextFlags["None"] = 0] = "None";
/**
* A Writable context is one in which a value may be written to an lvalue.
* For example, after we see a property access, we may expect a write to the
* property via the "=" operator.
* prop
* ^ possible "=" after
*/
ParseContextFlags[ParseContextFlags["Writable"] = 1] = "Writable";
})(ParseContextFlags || (ParseContextFlags = {}));
class _ParseAST {
constructor(input, location, absoluteOffset, tokens, parseFlags, errors, offset) {
this.input = input;
this.location = location;
this.absoluteOffset = absoluteOffset;
this.tokens = tokens;
this.parseFlags = parseFlags;
this.errors = errors;
this.offset = offset;
this.rparensExpected = 0;
this.rbracketsExpected = 0;
this.rbracesExpected = 0;
this.context = ParseContextFlags.None;
// Cache of expression start and input indeces to the absolute source span they map to, used to
// prevent creating superfluous source spans in `sourceSpan`.
// A serial of the expression start and input index is used for mapping because both are stateful
// and may change for subsequent expressions visited by the parser.
this.sourceSpanCache = new Map();
this.index = 0;
}
peek(offset) {
const i = this.index + offset;
return i < this.tokens.length ? this.tokens[i] : EOF;
}
get next() {
return this.peek(0);
}
/** Whether all the parser input has been processed. */
get atEOF() {
return this.index >= this.tokens.length;
}
/**
* Index of the next token to be processed, or the end of the last token if all have been
* processed.
*/
get inputIndex() {
return this.atEOF ? this.currentEndIndex : this.next.index + this.offset;
}
/**
* End index of the last processed token, or the start of the first token if none have been
* processed.
*/
get currentEndIndex() {
if (this.index > 0) {
const curToken = this.peek(-1);
return curToken.end + this.offset;
}
// No tokens have been processed yet; return the next token's start or the length of the input
// if there is no token.
if (this.tokens.length === 0) {
return this.input.length + this.offset;
}
return this.next.index + this.offset;
}
/**
* Returns the absolute offset of the start of the current token.
*/
get currentAbsoluteOffset() {
return this.absoluteOffset + this.inputIndex;
}
/**
* Retrieve a `ParseSpan` from `start` to the current position (or to `artificialEndIndex` if
* provided).
*
* @param start Position from which the `ParseSpan` will start.
* @param artificialEndIndex Optional ending index to be used if provided (and if greater than the
* natural ending index)
*/
span(start, artificialEndIndex) {
let endIndex = this.currentEndIndex;
if (artificialEndIndex !== undefined && artificialEndIndex > this.currentEndIndex) {
endIndex = artificialEndIndex;
}
// In some unusual parsing scenarios (like when certain tokens are missing and an `EmptyExpr` is
// being created), the current token may already be advanced beyond the `currentEndIndex`. This
// appears to be a deep-seated parser bug.
//
// As a workaround for now, swap the start and end indices to ensure a valid `ParseSpan`.
// TODO(alxhub): fix the bug upstream in the parser state, and remove this workaround.
if (start > endIndex) {
const tmp = endIndex;
endIndex = start;
start = tmp;
}
return new ParseSpan(start, endIndex);
}
sourceSpan(start, artificialEndIndex) {
const serial = `${start}@${this.inputIndex}:${artificialEndIndex}`;
if (!this.sourceSpanCache.has(serial)) {
this.sourceSpanCache.set(serial, this.span(start, artificialEndIndex).toAbsolute(this.absoluteOffset));
}
return this.sourceSpanCache.get(serial);
}
advance() {
this.index++;
}
/**
* Executes a callback in the provided context.
*/
withContext(context, cb) {
this.context |= context;
const ret = cb();
this.context ^= context;
return ret;
}
consumeOptionalCharacter(code) {
if (this.next.isCharacter(code)) {
this.advance();
return true;
}
else {
return false;
}
}
peekKeywordLet() {
return this.next.isKeywordLet();
}
peekKeywordAs() {
return this.next.isKeywordAs();
}
/**
* Consumes an expected character, otherwise emits an error about the missing expected character
* and skips over the token stream until reaching a recoverable point.
*
* See `this.error` and `this.skip` for more details.
*/
expectCharacter(code) {
if (this.consumeOptionalCharacter(code))
return;
this.error(`Missing expected ${String.fromCharCode(code)}`);
}
consumeOptionalOperator(op) {
if (this.next.isOperator(op)) {
this.advance();
return true;
}
else {
return false;
}
}
expectOperator(operator) {
if (this.consumeOptionalOperator(operator))
return;
this.error(`Missing expected operator ${operator}`);
}
prettyPrintToken(tok) {
return tok === EOF ? 'end of input' : `token ${tok}`;
}
expectIdentifierOrKeyword() {
const n = this.next;
if (!n.isIdentifier() && !n.isKeyword()) {
if (n.isPrivateIdentifier()) {
this._reportErrorForPrivateIdentifier(n, 'expected identifier or keyword');
}
else {
this.error(`Unexpected ${this.prettyPrintToken(n)}, expected identifier or keyword`);
}
return null;
}
this.advance();
return n.toString();
}
expectIdentifierOrKeywordOrString() {
const n = this.next;
if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) {
if (n.isPrivateIdentifier()) {
this._reportErrorForPrivateIdentifier(n, 'expected identifier, keyword or string');
}
else {
this.error(`Unexpected ${this.prettyPrintToken(n)}, expected identifier, keyword, or string`);
}
return '';
}
this.advance();
return n.toString();
}
parseChain() {
const exprs = [];
const start = this.inputIndex;
while (this.index < this.tokens.length) {
const expr = this.parsePipe();
exprs.push(expr);
if (this.consumeOptionalCharacter($SEMICOLON)) {
if (!(this.parseFlags & 1 /* ParseFlags.Action */)) {
this.error('Binding expression cannot contain chained expression');
}
while (this.consumeOptionalCharacter($SEMICOLON)) { } // read all semicolons
}
else if (this.index < this.tokens.length) {
const errorIndex = this.index;
this.error(`Unexpected token '${this.next}'`);
// The `error` call above will skip ahead to the next recovery point in an attempt to
// recover part of the expression, but that might be the token we started from which will
// lead to an infinite loop. If that's the case, break the loop assuming that we can't
// parse further.
if (this.index === errorIndex) {
break;
}
}
}
if (exprs.length === 0) {
// We have no expressions so create an empty expression that spans the entire input length
const artificialStart = this.offset;
const artificialEnd = this.offset + this.input.length;
return new EmptyExpr$1(this.span(artificialStart, artificialEnd), this.sourceSpan(artificialStart, artificialEnd));
}
if (exprs.length == 1)
return exprs[0];
return new Chain(this.span(start), this.sourceSpan(start), exprs);
}
parsePipe() {
const start = this.inputIndex;
let result = this.parseExpression();
if (this.consumeOptionalOperator('|')) {
if (this.parseFlags & 1 /* ParseFlags.Action */) {
this.error(`Cannot have a pipe in an action expression`);
}
do {
const nameStart = this.inputIndex;
let nameId = this.expectIdentifierOrKeyword();
let nameSpan;
let fullSpanEnd = undefined;
if (nameId !== null) {
nameSpan = this.sourceSpan(nameStart);
}
else {
// No valid identifier was found, so we'll assume an empty pipe name ('').
nameId = '';
// However, there may have been whitespace present between the pipe character and the next
// token in the sequence (or the end of input). We want to track this whitespace so that
// the `BindingPipe` we produce covers not just the pipe character, but any trailing
// whitespace beyond it. Another way of thinking about this is that the zero-length name
// is assumed to be at the end of any whitespace beyond the pipe character.
//
// Therefore, we push the end of the `ParseSpan` for this pipe all the way up to the
// beginning of the next token, or until the end of input if the next token is EOF.
fullSpanEnd = this.next.index !== -1 ? this.next.index : this.input.length + this.offset;
// The `nameSpan` for an empty pipe name is zero-length at the end of any whitespace
// beyond the pipe character.
nameSpan = new ParseSpan(fullSpanEnd, fullSpanEnd).toAbsolute(this.absoluteOffset);
}
const args = [];
while (this.consumeOptionalCharacter($COLON)) {
args.push(this.parseExpression());
// If there are additional expressions beyond the name, then the artificial end for the
// name is no longer relevant.
}
result = new BindingPipe(this.span(start), this.sourceSpan(start, fullSpanEnd), result, nameId, args, nameSpan);
} while (this.consumeOptionalOperator('|'));
}
return result;
}
parseExpression() {
return this.parseConditional();
}
parseConditional() {
const start = this.inputIndex;
const result = this.parseLogicalOr();
if (this.consumeOptionalOperator('?')) {
const yes = this.parsePipe();
let no;
if (!this.consumeOptionalCharacter($COLON)) {
const end = this.inputIndex;
const expression = this.input.substring(start, end);
this.error(`Conditional expression ${expression} requires all 3 expressions`);
no = new EmptyExpr$1(this.span(start), this.sourceSpan(start));
}
else {
no = this.parsePipe();
}
return new Conditional(this.span(start), this.sourceSpan(start), result, yes, no);
}
else {
return result;
}
}
parseLogicalOr() {
// '||'
const start = this.inputIndex;
let result = this.parseLogicalAnd();
while (this.consumeOptionalOperator('||')) {
const right = this.parseLogicalAnd();
result = new Binary(this.span(start), this.sourceSpan(start), '||', result, right);
}
return result;
}
parseLogicalAnd() {
// '&&'
const start = this.inputIndex;
let result = this.parseNullishCoalescing();
while (this.consumeOptionalOperator('&&')) {
const right = this.parseNullishCoalescing();
result = new Binary(this.span(start), this.sourceSpan(start), '&&', result, right);
}
return result;
}
parseNullishCoalescing() {
// '??'
const start = this.inputIndex;
let result = this.parseEquality();
while (this.consumeOptionalOperator('??')) {
const right = this.parseEquality();
result = new Binary(this.span(start), this.sourceSpan(start), '??', result, right);
}
return result;
}
parseEquality() {
// '==','!=','===','!=='
const start = this.inputIndex;
let result = this.parseRelational();
while (this.next.type == TokenType.Operator) {
const operator = this.next.strValue;
switch (operator) {
case '==':
case '===':
case '!=':
case '!==':
this.advance();
const right = this.parseRelational();
result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
continue;
}
break;
}
return result;
}
parseRelational() {
// '<', '>', '<=', '>='
const start = this.inputIndex;
let result = this.parseAdditive();
while (this.next.type == TokenType.Operator) {
const operator = this.next.strValue;
switch (operator) {
case '<':
case '>':
case '<=':
case '>=':
this.advance();
const right = this.parseAdditive();
result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
continue;
}
break;
}
return result;
}
parseAdditive() {
// '+', '-'
const start = this.inputIndex;
let result = this.parseMultiplicative();
while (this.next.type == TokenType.Operator) {
const operator = this.next.strValue;
switch (operator) {
case '+':
case '-':
this.advance();
let right = this.parseMultiplicative();
result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
continue;
}
break;
}
return result;
}
parseMultiplicative() {
// '*', '%', '/'
const start = this.inputIndex;
let result = this.parsePrefix();
while (this.next.type == TokenType.Operator) {
const operator = this.next.strValue;
switch (operator) {
case '*':
case '%':
case '/':
this.advance();
let right = this.parsePrefix();
result = new Binary(this.span(start), this.sourceSpan(start), operator, result, right);
continue;
}
break;
}
return result;
}
parsePrefix() {
if (this.next.type == TokenType.Operator) {
const start = this.inputIndex;
const operator = this.next.strValue;
let result;
switch (operator) {
case '+':
this.advance();
result = this.parsePrefix();
return Unary.createPlus(this.span(start), this.sourceSpan(start), result);
case '-':
this.advance();
result = this.parsePrefix();
return Unary.createMinus(this.span(start), this.sourceSpan(start), result);
case '!':
this.advance();
result = this.parsePrefix();
return new PrefixNot(this.span(start), this.sourceSpan(start), result);
}
}
return this.parseCallChain();
}
parseCallChain() {
const start = this.inputIndex;
let result = this.parsePrimary();
while (true) {
if (this.consumeOptionalCharacter($PERIOD)) {
result = this.parseAccessMember(result, start, false);
}
else if (this.consumeOptionalOperator('?.')) {
if (this.consumeOptionalCharacter($LPAREN)) {
result = this.parseCall(result, start, true);
}
else {
result = this.consumeOptionalCharacter($LBRACKET)
? this.parseKeyedReadOrWrite(result, start, true)
: this.parseAccessMember(result, start, true);
}
}
else if (this.consumeOptionalCharacter($LBRACKET)) {
result = this.parseKeyedReadOrWrite(result, start, false);
}
else if (this.consumeOptionalCharacter($LPAREN)) {
result = this.parseCall(result, start, false);
}
else if (this.consumeOptionalOperator('!')) {
result = new NonNullAssert(this.span(start), this.sourceSpan(start), result);
}
else {
return result;
}
}
}
parsePrimary() {
const start = this.inputIndex;
if (this.consumeOptionalCharacter($LPAREN)) {
this.rparensExpected++;
const result = this.parsePipe();
this.rparensExpected--;
this.expectCharacter($RPAREN);
return result;
}
else if (this.next.isKeywordNull()) {
this.advance();
return new LiteralPrimitive(this.span(start), this.sourceSpan(start), null);
}
else if (this.next.isKeywordUndefined()) {
this.advance();
return new LiteralPrimitive(this.span(start), this.sourceSpan(start), void 0);
}
else if (this.next.isKeywordTrue()) {
this.advance();
return new LiteralPrimitive(this.span(start), this.sourceSpan(start), true);
}
else if (this.next.isKeywordFalse()) {
this.advance();
return new LiteralPrimitive(this.span(start), this.sourceSpan(start), false);
}
else if (this.next.isKeywordThis()) {
this.advance();
return new ThisReceiver(this.span(start), this.sourceSpan(start));
}
else if (this.consumeOptionalCharacter($LBRACKET)) {
this.rbracketsExpected++;
const elements = this.parseExpressionList($RBRACKET);
this.rbracketsExpected--;
this.expectCharacter($RBRACKET);
return new LiteralArray(this.span(start), this.sourceSpan(start), elements);
}
else if (this.next.isCharacter($LBRACE)) {
return this.parseLiteralMap();
}
else if (this.next.isIdentifier()) {
return this.parseAccessMember(new ImplicitReceiver(this.span(start), this.sourceSpan(start)), start, false);
}
else if (this.next.isNumber()) {
const value = this.next.toNumber();
this.advance();
return new LiteralPrimitive(this.span(start), this.sourceSpan(start), value);
}
else if (this.next.isString()) {
const literalValue = this.next.toString();
this.advance();
return new LiteralPrimitive(this.span(start), this.sourceSpan(start), literalValue);
}
else if (this.next.isPrivateIdentifier()) {
this._reportErrorForPrivateIdentifier(this.next, null);
return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
}
else if (this.index >= this.tokens.length) {
this.error(`Unexpected end of expression: ${this.input}`);
return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
}
else {
this.error(`Unexpected token ${this.next}`);
return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
}
}
parseExpressionList(terminator) {
const result = [];
do {
if (!this.next.isCharacter(terminator)) {
result.push(this.parsePipe());
}
else {
break;
}
} while (this.consumeOptionalCharacter($COMMA));
return result;
}
parseLiteralMap() {
const keys = [];
const values = [];
const start = this.inputIndex;
this.expectCharacter($LBRACE);
if (!this.consumeOptionalCharacter($RBRACE)) {
this.rbracesExpected++;
do {
const keyStart = this.inputIndex;
const quoted = this.next.isString();
const key = this.expectIdentifierOrKeywordOrString();
const literalMapKey = { key, quoted };
keys.push(literalMapKey);
// Properties with quoted keys can't use the shorthand syntax.
if (quoted) {
this.expectCharacter($COLON);
values.push(this.parsePipe());
}
else if (this.consumeOptionalCharacter($COLON)) {
values.push(this.parsePipe());
}
else {
literalMapKey.isShorthandInitialized = true;
const span = this.span(keyStart);
const sourceSpan = this.sourceSpan(keyStart);
values.push(new PropertyRead(span, sourceSpan, sourceSpan, new ImplicitReceiver(span, sourceSpan), key));
}
} while (this.consumeOptionalCharacter($COMMA) &&
!this.next.isCharacter($RBRACE));
this.rbracesExpected--;
this.expectCharacter($RBRACE);
}
return new LiteralMap(this.span(start), this.sourceSpan(start), keys, values);
}
parseAccessMember(readReceiver, start, isSafe) {
const nameStart = this.inputIndex;
const id = this.withContext(ParseContextFlags.Writable, () => {
const id = this.expectIdentifierOrKeyword() ?? '';
if (id.length === 0) {
this.error(`Expected identifier for property access`, readReceiver.span.end);
}
return id;
});
const nameSpan = this.sourceSpan(nameStart);
let receiver;
if (isSafe) {
if (this.consumeOptionalOperator('=')) {
this.error("The '?.' operator cannot be used in the assignment");
receiver = new EmptyExpr$1(this.span(start), this.sourceSpan(start));
}
else {
receiver = new SafePropertyRead(this.span(start), this.sourceSpan(start), nameSpan, readReceiver, id);
}
}
else {
if (this.consumeOptionalOperator('=')) {
if (!(this.parseFlags & 1 /* ParseFlags.Action */)) {
this.error('Bindings cannot contain assignments');
return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
}
const value = this.parseConditional();
receiver = new PropertyWrite(this.span(start), this.sourceSpan(start), nameSpan, readReceiver, id, value);
}
else {
receiver = new PropertyRead(this.span(start), this.sourceSpan(start), nameSpan, readReceiver, id);
}
}
return receiver;
}
parseCall(receiver, start, isSafe) {
const argumentStart = this.inputIndex;
this.rparensExpected++;
const args = this.parseCallArguments();
const argumentSpan = this.span(argumentStart, this.inputIndex).toAbsolute(this.absoluteOffset);
this.expectCharacter($RPAREN);
this.rparensExpected--;
const span = this.span(start);
const sourceSpan = this.sourceSpan(start);
return isSafe
? new SafeCall(span, sourceSpan, receiver, args, argumentSpan)
: new Call(span, sourceSpan, receiver, args, argumentSpan);
}
parseCallArguments() {
if (this.next.isCharacter($RPAREN))
return [];
const positionals = [];
do {
positionals.push(this.parsePipe());
} while (this.consumeOptionalCharacter($COMMA));
return positionals;
}
/**
* Parses an identifier, a keyword, a string with an optional `-` in between,
* and returns the string along with its absolute source span.
*/
expectTemplateBindingKey() {
let result = '';
let operatorFound = false;
const start = this.currentAbsoluteOffset;
do {
result += this.expectIdentifierOrKeywordOrString();
operatorFound = this.consumeOptionalOperator('-');
if (operatorFound) {
result += '-';
}
} while (operatorFound);
return {
source: result,
span: new AbsoluteSourceSpan$1(start, start + result.length),
};
}
/**
* Parse microsyntax template expression and return a list of bindings or
* parsing errors in case the given expression is invalid.
*
* For example,
* ```
*
* ```
* contains five bindings:
* 1. ngFor -> null
* 2. item -> NgForOfContext.$implicit
* 3. ngForOf -> items
* 4. i -> NgForOfContext.index
* 5. ngForTrackBy -> func
*
* For a full description of the microsyntax grammar, see
* https://gist.github.com/mhevery/d3530294cff2e4a1b3fe15ff75d08855
*
* @param templateKey name of the microsyntax directive, like ngIf, ngFor,
* without the *, along with its absolute span.
*/
parseTemplateBindings(templateKey) {
const bindings = [];
// The first binding is for the template key itself
// In *ngFor="let item of items", key = "ngFor", value = null
// In *ngIf="cond | pipe", key = "ngIf", value = "cond | pipe"
bindings.push(...this.parseDirectiveKeywordBindings(templateKey));
while (this.index < this.tokens.length) {
// If it starts with 'let', then this must be variable declaration
const letBinding = this.parseLetBinding();
if (letBinding) {
bindings.push(letBinding);
}
else {
// Two possible cases here, either `value "as" key` or
// "directive-keyword expression". We don't know which case, but both
// "value" and "directive-keyword" are template binding key, so consume
// the key first.
const key = this.expectTemplateBindingKey();
// Peek at the next token, if it is "as" then this must be variable
// declaration.
const binding = this.parseAsBinding(key);
if (binding) {
bindings.push(binding);
}
else {
// Otherwise the key must be a directive keyword, like "of". Transform
// the key to actual key. Eg. of -> ngForOf, trackBy -> ngForTrackBy
key.source =
templateKey.source + key.source.charAt(0).toUpperCase() + key.source.substring(1);
bindings.push(...this.parseDirectiveKeywordBindings(key));
}
}
this.consumeStatementTerminator();
}
return new TemplateBindingParseResult(bindings, [] /* warnings */, this.errors);
}
parseKeyedReadOrWrite(receiver, start, isSafe) {
return this.withContext(ParseContextFlags.Writable, () => {
this.rbracketsExpected++;
const key = this.parsePipe();
if (key instanceof EmptyExpr$1) {
this.error(`Key access cannot be empty`);
}
this.rbracketsExpected--;
this.expectCharacter($RBRACKET);
if (this.consumeOptionalOperator('=')) {
if (isSafe) {
this.error("The '?.' operator cannot be used in the assignment");
}
else {
const value = this.parseConditional();
return new KeyedWrite(this.span(start), this.sourceSpan(start), receiver, key, value);
}
}
else {
return isSafe
? new SafeKeyedRead(this.span(start), this.sourceSpan(start), receiver, key)
: new KeyedRead(this.span(start), this.sourceSpan(start), receiver, key);
}
return new EmptyExpr$1(this.span(start), this.sourceSpan(start));
});
}
/**
* Parse a directive keyword, followed by a mandatory expression.
* For example, "of items", "trackBy: func".
* The bindings are: ngForOf -> items, ngForTrackBy -> func
* There could be an optional "as" binding that follows the expression.
* For example,
* ```
* *ngFor="let item of items | slice:0:1 as collection".
* ^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^
* keyword bound target optional 'as' binding
* ```
*
* @param key binding key, for example, ngFor, ngIf, ngForOf, along with its
* absolute span.
*/
parseDirectiveKeywordBindings(key) {
const bindings = [];
this.consumeOptionalCharacter($COLON); // trackBy: trackByFunction
const value = this.getDirectiveBoundTarget();
let spanEnd = this.currentAbsoluteOffset;
// The binding could optionally be followed by "as". For example,
// *ngIf="cond | pipe as x". In this case, the key in the "as" binding
// is "x" and the value is the template key itself ("ngIf"). Note that the
// 'key' in the current context now becomes the "value" in the next binding.
const asBinding = this.parseAsBinding(key);
if (!asBinding) {
this.consumeStatementTerminator();
spanEnd = this.currentAbsoluteOffset;
}
const sourceSpan = new AbsoluteSourceSpan$1(key.span.start, spanEnd);
bindings.push(new ExpressionBinding(sourceSpan, key, value));
if (asBinding) {
bindings.push(asBinding);
}
return bindings;
}
/**
* Return the expression AST for the bound target of a directive keyword
* binding. For example,
* ```
* *ngIf="condition | pipe"
* ^^^^^^^^^^^^^^^^ bound target for "ngIf"
* *ngFor="let item of items"
* ^^^^^ bound target for "ngForOf"
* ```
*/
getDirectiveBoundTarget() {
if (this.next === EOF || this.peekKeywordAs() || this.peekKeywordLet()) {
return null;
}
const ast = this.parsePipe(); // example: "condition | async"
const { start, end } = ast.span;
const value = this.input.substring(start, end);
return new ASTWithSource(ast, value, this.location, this.absoluteOffset + start, this.errors);
}
/**
* Return the binding for a variable declared using `as`. Note that the order
* of the key-value pair in this declaration is reversed. For example,
* ```
* *ngFor="let item of items; index as i"
* ^^^^^ ^
* value key
* ```
*
* @param value name of the value in the declaration, "ngIf" in the example
* above, along with its absolute span.
*/
parseAsBinding(value) {
if (!this.peekKeywordAs()) {
return null;
}
this.advance(); // consume the 'as' keyword
const key = this.expectTemplateBindingKey();
this.consumeStatementTerminator();
const sourceSpan = new AbsoluteSourceSpan$1(value.span.start, this.currentAbsoluteOffset);
return new VariableBinding(sourceSpan, key, value);
}
/**
* Return the binding for a variable declared using `let`. For example,
* ```
* *ngFor="let item of items; let i=index;"
* ^^^^^^^^ ^^^^^^^^^^^
* ```
* In the first binding, `item` is bound to `NgForOfContext.$implicit`.
* In the second binding, `i` is bound to `NgForOfContext.index`.
*/
parseLetBinding() {
if (!this.peekKeywordLet()) {
return null;
}
const spanStart = this.currentAbsoluteOffset;
this.advance(); // consume the 'let' keyword
const key = this.expectTemplateBindingKey();
let value = null;
if (this.consumeOptionalOperator('=')) {
value = this.expectTemplateBindingKey();
}
this.consumeStatementTerminator();
const sourceSpan = new AbsoluteSourceSpan$1(spanStart, this.currentAbsoluteOffset);
return new VariableBinding(sourceSpan, key, value);
}
/**
* Consume the optional statement terminator: semicolon or comma.
*/
consumeStatementTerminator() {
this.consumeOptionalCharacter($SEMICOLON) || this.consumeOptionalCharacter($COMMA);
}
/**
* Records an error and skips over the token stream until reaching a recoverable point. See
* `this.skip` for more details on token skipping.
*/
error(message, index = null) {
this.errors.push(new ParserError(message, this.input, this.locationText(index), this.location));
this.skip();
}
locationText(index = null) {
if (index == null)
index = this.index;
return index < this.tokens.length
? `at column ${this.tokens[index].index + 1} in`
: `at the end of the expression`;
}
/**
* Records an error for an unexpected private identifier being discovered.
* @param token Token representing a private identifier.
* @param extraMessage Optional additional message being appended to the error.
*/
_reportErrorForPrivateIdentifier(token, extraMessage) {
let errorMessage = `Private identifiers are not supported. Unexpected private identifier: ${token}`;
if (extraMessage !== null) {
errorMessage += `, ${extraMessage}`;
}
this.error(errorMessage);
}
/**
* Error recovery should skip tokens until it encounters a recovery point.
*
* The following are treated as unconditional recovery points:
* - end of input
* - ';' (parseChain() is always the root production, and it expects a ';')
* - '|' (since pipes may be chained and each pipe expression may be treated independently)
*
* The following are conditional recovery points:
* - ')', '}', ']' if one of calling productions is expecting one of these symbols
* - This allows skip() to recover from errors such as '(a.) + 1' allowing more of the AST to
* be retained (it doesn't skip any tokens as the ')' is retained because of the '(' begins
* an '('
')' production).
* The recovery points of grouping symbols must be conditional as they must be skipped if
* none of the calling productions are not expecting the closing token else we will never
* make progress in the case of an extraneous group closing symbol (such as a stray ')').
* That is, we skip a closing symbol if we are not in a grouping production.
* - '=' in a `Writable` context
* - In this context, we are able to recover after seeing the `=` operator, which
* signals the presence of an independent rvalue expression following the `=` operator.
*
* If a production expects one of these token it increments the corresponding nesting count,
* and then decrements it just prior to checking if the token is in the input.
*/
skip() {
let n = this.next;
while (this.index < this.tokens.length &&
!n.isCharacter($SEMICOLON) &&
!n.isOperator('|') &&
(this.rparensExpected <= 0 || !n.isCharacter($RPAREN)) &&
(this.rbracesExpected <= 0 || !n.isCharacter($RBRACE)) &&
(this.rbracketsExpected <= 0 || !n.isCharacter($RBRACKET)) &&
(!(this.context & ParseContextFlags.Writable) || !n.isOperator('='))) {
if (this.next.isError()) {
this.errors.push(new ParserError(this.next.toString(), this.input, this.locationText(), this.location));
}
this.advance();
n = this.next;
}
}
}
class SimpleExpressionChecker extends RecursiveAstVisitor {
constructor() {
super(...arguments);
this.errors = [];
}
visitPipe() {
this.errors.push('pipes');
}
}
/**
* Computes the real offset in the original template for indexes in an interpolation.
*
* Because templates can have encoded HTML entities and the input passed to the parser at this stage
* of the compiler is the _decoded_ value, we need to compute the real offset using the original
* encoded values in the interpolated tokens. Note that this is only a special case handling for
* `MlParserTokenType.ENCODED_ENTITY` token types. All other interpolated tokens are expected to
* have parts which exactly match the input string for parsing the interpolation.
*
* @param interpolatedTokens The tokens for the interpolated value.
*
* @returns A map of index locations in the decoded template to indexes in the original template
*/
function getIndexMapForOriginalTemplate(interpolatedTokens) {
let offsetMap = new Map();
let consumedInOriginalTemplate = 0;
let consumedInInput = 0;
let tokenIndex = 0;
while (tokenIndex < interpolatedTokens.length) {
const currentToken = interpolatedTokens[tokenIndex];
if (currentToken.type === 9 /* MlParserTokenType.ENCODED_ENTITY */) {
const [decoded, encoded] = currentToken.parts;
consumedInOriginalTemplate += encoded.length;
consumedInInput += decoded.length;
}
else {
const lengthOfParts = currentToken.parts.reduce((sum, current) => sum + current.length, 0);
consumedInInput += lengthOfParts;
consumedInOriginalTemplate += lengthOfParts;
}
offsetMap.set(consumedInInput, consumedInOriginalTemplate);
tokenIndex++;
}
return offsetMap;
}
/**
* @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.dev/license
*/
// =================================================================================================
// =================================================================================================
// =========== S T O P - S T O P - S T O P - S T O P - S T O P - S T O P ===========
// =================================================================================================
// =================================================================================================
//
// DO NOT EDIT THIS LIST OF SECURITY SENSITIVE PROPERTIES WITHOUT A SECURITY REVIEW!
// Reach out to mprobst for details.
//
// =================================================================================================
/** Map from tagName|propertyName to SecurityContext. Properties applying to all tags use '*'. */
let _SECURITY_SCHEMA;
function SECURITY_SCHEMA() {
if (!_SECURITY_SCHEMA) {
_SECURITY_SCHEMA = {};
// Case is insignificant below, all element and attribute names are lower-cased for lookup.
registerContext(SecurityContext.HTML, ['iframe|srcdoc', '*|innerHTML', '*|outerHTML']);
registerContext(SecurityContext.STYLE, ['*|style']);
// NB: no SCRIPT contexts here, they are never allowed due to the parser stripping them.
registerContext(SecurityContext.URL, [
'*|formAction',
'area|href',
'area|ping',
'audio|src',
'a|href',
'a|ping',
'blockquote|cite',
'body|background',
'del|cite',
'form|action',
'img|src',
'input|src',
'ins|cite',
'q|cite',
'source|src',
'track|src',
'video|poster',
'video|src',
]);
registerContext(SecurityContext.RESOURCE_URL, [
'applet|code',
'applet|codebase',
'base|href',
'embed|src',
'frame|src',
'head|profile',
'html|manifest',
'iframe|src',
'link|href',
'media|src',
'object|codebase',
'object|data',
'script|src',
]);
}
return _SECURITY_SCHEMA;
}
function registerContext(ctx, specs) {
for (const spec of specs)
_SECURITY_SCHEMA[spec.toLowerCase()] = ctx;
}
/**
* The set of security-sensitive attributes of an `