UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

2,159 lines 75.4 kB
import templateOnly from '../../component/template-only.js';
import { isPresentArray, getLast, mapPresentArray, exhausted, assert as debugAssert, assertPresentArray } from '../../../@glimmer/util/index.js';
import { WellKnownTagNames, WellKnownAttrNames, SexpOpcodes as opcodes } from '../../../@glimmer/wire-format/index.js';
import { n as node, a as api$2, g as generateSyntaxError, b as api$1, m as maybeLoc, K as KEYWORDS_TYPES, i as isKeyword, S as SourceSlice, c as normalize, l as loc } from '../../../shared-chunks/transform-resolutions-O6uYv8DS.js';
import { CurriedType as CurriedTypes } from '../../../@glimmer/vm/index.js';
import { setComponentTemplate } from '../../../@glimmer/manager/index.js';
import { templateFactory } from '../../../@glimmer/opcode-compiler/index.js';
import compileOptions from './compile-options.js';

(function (HeadKind) {
    return HeadKind.Block = "Block", HeadKind.Call = "Call", HeadKind.Element = "Element", HeadKind.AppendPath = "AppendPath", HeadKind.AppendExpr = "AppendExpr", HeadKind.Literal = "Literal", HeadKind.Modifier = "Modifier", HeadKind.DynamicComponent = "DynamicComponent", HeadKind.Comment = "Comment", HeadKind.Splat = "Splat", HeadKind.Keyword = "Keyword", HeadKind;
  })({});
  (function (VariableKind) {
    return VariableKind.Local = "Local", VariableKind.Free = "Free", VariableKind.Arg = "Arg", VariableKind.Block = "Block", VariableKind.This = "This", VariableKind;
  })({});
(function (Builder) {
    return Builder[Builder.Literal = 0] = "Literal", Builder[Builder.Comment = 1] = "Comment", Builder[Builder.Append = 2] = "Append", Builder[Builder.Modifier = 3] = "Modifier", Builder[Builder.DynamicComponent = 4] = "DynamicComponent", Builder[Builder.Get = 5] = "Get", Builder[Builder.Concat = 6] = "Concat", Builder[Builder.HasBlock = 7] = "HasBlock", Builder[Builder.HasBlockParams = 8] = "HasBlockParams", Builder;
  })({});
  (function (ExpressionKind) {
    return ExpressionKind.Literal = "Literal", ExpressionKind.Call = "Call", ExpressionKind.GetPath = "GetPath", ExpressionKind.GetVar = "GetVar", ExpressionKind.Concat = "Concat", ExpressionKind.HasBlock = "HasBlock", ExpressionKind.HasBlockParams = "HasBlockParams", ExpressionKind;
  })({});
class Template extends node("Template").fields() {}
class InElement extends node("InElement").fields() {}
class Not extends node("Not").fields() {}
class If extends node("If").fields() {}
class IfInline extends node("IfInline").fields() {}
class Each extends node("Each").fields() {}
class Let extends node("Let").fields() {}
class WithDynamicVars extends node("WithDynamicVars").fields() {}
class GetDynamicVar extends node("GetDynamicVar").fields() {}
class Log extends node("Log").fields() {}
class InvokeComponent extends node("InvokeComponent").fields() {}
class NamedBlocks extends node("NamedBlocks").fields() {}
class NamedBlock extends node("NamedBlock").fields() {}
class AppendTrustedHTML extends node("AppendTrustedHTML").fields() {}
class AppendTextNode extends node("AppendTextNode").fields() {}
class AppendComment extends node("AppendComment").fields() {}
class Component extends node("Component").fields() {}
class StaticAttr extends node("StaticAttr").fields() {}
class DynamicAttr extends node("DynamicAttr").fields() {}
class SimpleElement extends node("SimpleElement").fields() {}
class ElementParameters extends node("ElementParameters").fields() {}
class Yield extends node("Yield").fields() {}
class Debugger extends node("Debugger").fields() {}
class CallExpression extends node("CallExpression").fields() {}
class Modifier extends node("Modifier").fields() {}
class InvokeBlock extends node("InvokeBlock").fields() {}
class SplatAttr extends node("SplatAttr").fields() {}
class PathExpression extends node("PathExpression").fields() {}
class Missing extends node("Missing").fields() {}
class InterpolateExpression extends node("InterpolateExpression").fields() {}
class HasBlock extends node("HasBlock").fields() {}
class HasBlockParams extends node("HasBlockParams").fields() {}
class Curry extends node("Curry").fields() {}
class Positional extends node("Positional").fields() {}
class NamedArguments extends node("NamedArguments").fields() {}
class NamedArgument extends node("NamedArgument").fields() {}
class Args extends node("Args").fields() {}
class Tail extends node("Tail").fields() {}
class PresentList {
  constructor(list) {
    this.list = list;
  }
  toArray() {
    return this.list;
  }
  map(callback) {
    let result = mapPresentArray(this.list, callback);
    return new PresentList(result);
  }
  filter(predicate) {
    let out = [];
    for (let item of this.list) predicate(item) && out.push(item);
    return OptionalList(out);
  }
  toPresentArray() {
    return this.list;
  }
  into({
    ifPresent: ifPresent
  }) {
    return ifPresent(this);
  }
}
class EmptyList {
  list = [];
  map(_callback) {
    return new EmptyList();
  }
  filter(_predicate) {
    return new EmptyList();
  }
  toArray() {
    return this.list;
  }
  toPresentArray() {
    return null;
  }
  into({
    ifEmpty: ifEmpty
  }) {
    return ifEmpty();
  }
}

// export type OptionalList<T> = PresentList<T> | EmptyList<T>;
function OptionalList(value) {
  return isPresentArray(value) ? new PresentList(value) : new EmptyList();
}
class ResultImpl {
  static all(...results) {
    let out = [];
    for (let result of results) {
      if (result.isErr) return result.cast();
      out.push(result.value);
    }
    return Ok(out);
  }
}
const Result = ResultImpl;
class OkImpl extends ResultImpl {
  isOk = !0;
  isErr = !1;
  constructor(value) {
    super(), this.value = value;
  }
  expect(_message) {
    return this.value;
  }
  ifOk(callback) {
    return callback(this.value), this;
  }
  andThen(callback) {
    return callback(this.value);
  }
  mapOk(callback) {
    return Ok(callback(this.value));
  }
  ifErr(_callback) {
    return this;
  }
  mapErr(_callback) {
    return this;
  }
}
class ErrImpl extends ResultImpl {
  isOk = !1;
  isErr = !0;
  constructor(reason) {
    super(), this.reason = reason;
  }
  expect(message) {
    throw new Error(message || "expected an Ok, got Err");
  }
  andThen(_callback) {
    return this.cast();
  }
  mapOk(_callback) {
    return this.cast();
  }
  ifOk(_callback) {
    return this;
  }
  mapErr(callback) {
    return Err(callback(this.reason));
  }
  ifErr(callback) {
    return callback(this.reason), this;
  }
  cast() {
    return this;
  }
}
function Ok(value) {
  return new OkImpl(value);
}
function Err(reason) {
  return new ErrImpl(reason);
}
class ResultArray {
  constructor(items = []) {
    this.items = items;
  }
  add(item) {
    this.items.push(item);
  }
  toArray() {
    let err = this.items.filter(item => item instanceof ErrImpl)[0];
    return void 0 !== err ? err.cast() : Ok(this.items.map(item => item.value));
  }
  toOptionalList() {
    return this.toArray().mapOk(arr => OptionalList(arr));
  }
}
function convertPathToCallIfKeyword(path) {
  return "Path" === path.type && "Free" === path.ref.type && path.ref.name in KEYWORDS_TYPES ? new api$1.CallExpression({
    callee: path,
    args: api$1.Args.empty(path.loc),
    loc: path.loc
  }) : path;
}
const VISIT_EXPRS = new class {
  visit(node, state) {
    switch (node.type) {
      case "Literal":
        return Ok(this.Literal(node));
      case "Keyword":
        return Ok(this.Keyword(node));
      case "Interpolate":
        return this.Interpolate(node, state);
      case "Path":
        return this.PathExpression(node);
      case "Call":
        {
          let translated = CALL_KEYWORDS.translate(node, state);
          return null !== translated ? translated : this.CallExpression(node, state);
        }
    }
  }
  visitList(nodes, state) {
    return new ResultArray(nodes.map(e => VISIT_EXPRS.visit(e, state))).toOptionalList();
  }
  /**
  * Normalize paths into `hir.Path` or a `hir.Expr` that corresponds to the ref.
  *
  * TODO since keywords don't support tails anyway, distinguish PathExpression from
  * VariableReference in ASTv2.
  */
  PathExpression(path) {
    let ref = this.VariableReference(path.ref),
      {
        tail: tail
      } = path;
    if (isPresentArray(tail)) {
      let tailLoc = tail[0].loc.extend(getLast(tail).loc);
      return Ok(new PathExpression({
        loc: path.loc,
        head: ref,
        tail: new Tail({
          loc: tailLoc,
          members: tail
        })
      }));
    }
    return Ok(ref);
  }
  VariableReference(ref) {
    return ref;
  }
  Literal(literal) {
    return literal;
  }
  Keyword(keyword) {
    return keyword;
  }
  Interpolate(expr, state) {
    let parts = expr.parts.map(convertPathToCallIfKeyword);
    return VISIT_EXPRS.visitList(parts, state).mapOk(parts => new InterpolateExpression({
      loc: expr.loc,
      parts: parts
    }));
  }
  CallExpression(expr, state) {
    if ("Call" === expr.callee.type) throw new Error("unimplemented: subexpression at the head of a subexpression");
    return Result.all(VISIT_EXPRS.visit(expr.callee, state), VISIT_EXPRS.Args(expr.args, state)).mapOk(([callee, args]) => new CallExpression({
      loc: expr.loc,
      callee: callee,
      args: args
    }));
  }
  Args({
    positional: positional,
    named: named,
    loc: loc
  }, state) {
    return Result.all(this.Positional(positional, state), this.NamedArguments(named, state)).mapOk(([positional, named]) => new Args({
      loc: loc,
      positional: positional,
      named: named
    }));
  }
  Positional(positional, state) {
    return VISIT_EXPRS.visitList(positional.exprs, state).mapOk(list => new Positional({
      loc: positional.loc,
      list: list
    }));
  }
  NamedArguments(named, state) {
    let pairs = named.entries.map(arg => {
      let value = convertPathToCallIfKeyword(arg.value);
      return VISIT_EXPRS.visit(value, state).mapOk(value => new NamedArgument({
        loc: arg.loc,
        key: arg.name,
        value: value
      }));
    });
    return new ResultArray(pairs).toOptionalList().mapOk(pairs => new NamedArguments({
      loc: named.loc,
      entries: pairs
    }));
  }
}();
class KeywordImpl {
  types;
  constructor(keyword, type, delegate) {
    this.keyword = keyword, this.delegate = delegate;
    let nodes = new Set();
    for (let nodeType of KEYWORD_NODES[type]) nodes.add(nodeType);
    this.types = nodes;
  }
  match(node) {
    if (!this.types.has(node.type)) return !1;
    let path = getCalleeExpression(node);
    return null !== path && "Path" === path.type && "Free" === path.ref.type && path.ref.name === this.keyword;
  }
  translate(node, state) {
    if (this.match(node)) {
      let path = getCalleeExpression(node);
      return null !== path && "Path" === path.type && path.tail.length > 0 ? Err(generateSyntaxError(`The \`${this.keyword}\` keyword was used incorrectly. It was used as \`${path.loc.asString()}\`, but it cannot be used with additional path segments. \n\nError caused by`, node.loc)) : this.delegate.assert(node, state).andThen(param => this.delegate.translate({
        node: node,
        state: state
      }, param));
    }
    return null;
  }
}
const KEYWORD_NODES = {
  Call: ["Call"],
  Block: ["InvokeBlock"],
  Append: ["AppendContent"],
  Modifier: ["ElementModifier"]
};

/**
 * A "generic" keyword is something like `has-block`, which makes sense in the context
 * of sub-expression or append
 */
function getCalleeExpression(node) {
  switch (node.type) {
    // This covers the inside of attributes and expressions, as well as the callee
    // of call nodes
    case "Path":
      return node;
    case "AppendContent":
      return getCalleeExpression(node.value);
    case "Call":
    case "InvokeBlock":
    case "ElementModifier":
      return node.callee;
    default:
      return null;
  }
}
class Keywords {
  _keywords = [];
  _type;
  constructor(type) {
    this._type = type;
  }
  kw(name, delegate) {
    return this._keywords.push(function (keyword, type, delegate) {
      return new KeywordImpl(keyword, type, delegate);
    }(name, this._type, delegate)), this;
  }
  translate(node, state) {
    for (let keyword of this._keywords) {
      let result = keyword.translate(node, state);
      if (null !== result) return result;
    }
    let path = getCalleeExpression(node);
    if (path && "Path" === path.type && "Free" === path.ref.type && isKeyword(path.ref.name)) {
      let {
          name: name
        } = path.ref,
        usedType = this._type,
        validTypes = KEYWORDS_TYPES[name];
      if (!validTypes.includes(usedType)) return Err(generateSyntaxError(`The \`${name}\` keyword was used incorrectly. It was used as ${typesToReadableName[usedType]}, but its valid usages are:\n\n${function (name, types) {
        return types.map(type => {
          switch (type) {
            case "Append":
              return `- As an append statement, as in: {{${name}}}`;
            case "Block":
              return `- As a block statement, as in: {{#${name}}}{{/${name}}}`;
            case "Call":
              return `- As an expression, as in: (${name})`;
            case "Modifier":
              return `- As a modifier, as in: <div {{${name}}}></div>`;
            default:
              return exhausted(type);
          }
        }).join("\n\n");
      }
      /**
      * This function builds keyword definitions for a particular type of AST node (`KeywordType`).
      *
      * You can build keyword definitions for:
      *
      * - `Expr`: A `SubExpression` or `PathExpression`
      * - `Block`: A `BlockStatement`
      *   - A `BlockStatement` is a keyword candidate if its head is a
      *     `PathExpression`
      * - `Append`: An `AppendStatement`
      *
      * A node is a keyword candidate if:
      *
      * - A `PathExpression` is a keyword candidate if it has no tail, and its
      *   head expression is a `LocalVarHead` or `FreeVarHead` whose name is
      *   the keyword's name.
      * - A `SubExpression`, `AppendStatement`, or `BlockStatement` is a keyword
      *   candidate if its head is a keyword candidate.
      *
      * The keyword infrastructure guarantees that:
      *
      * - If a node is not a keyword candidate, it is never passed to any keyword's
      *   `assert` method.
      * - If a node is not the `KeywordType` for a particular keyword, it will not
      *   be passed to the keyword's `assert` method.
      *
      * `Expr` keywords are used in expression positions and should return HIR
      * expressions. `Block` and `Append` keywords are used in statement
      * positions and should return HIR statements.
      *
      * A keyword definition has two parts:
      *
      * - `match`, which determines whether an AST node matches the keyword, and can
      *   optionally return some information extracted from the AST node.
      * - `translate`, which takes a matching AST node as well as the extracted
      *   information and returns an appropriate HIR instruction.
      *
      * # Example
      *
      * This keyword:
      *
      * - turns `(hello)` into `"hello"`
      *   - as long as `hello` is not in scope
      * - makes it an error to pass any arguments (such as `(hello world)`)
      *
      * ```ts
      * keywords('SubExpr').kw('hello', {
      *   assert(node: ExprKeywordNode): Result<void> | false {
      *     // we don't want to transform `hello` as a `PathExpression`
      *     if (node.type !== 'SubExpression') {
      *       return false;
      *     }
      *
      *     // node.head would be `LocalVarHead` if `hello` was in scope
      *     if (node.head.type !== 'FreeVarHead') {
      *       return false;
      *     }
      *
      *     if (node.params.length || node.hash) {
      *       return Err(generateSyntaxError(`(hello) does not take any arguments`), node.loc);
      *     } else {
      *       return Ok();
      *     }
      *   },
      *
      *   translate(node: ASTv2.SubExpression): hir.Expression {
      *     return ASTv2.builders.literal("hello", node.loc)
      *   }
      * })
      * ```
      *
      * The keyword infrastructure checks to make sure that the node is the right
      * type before calling `assert`, so you only need to consider `SubExpression`
      * and `PathExpression` here. It also checks to make sure that the node passed
      * to `assert` has the keyword name in the right place.
      *
      * Note the important difference between returning `false` from `assert`,
      * which just means that the node didn't match, and returning `Err`, which
      * means that the node matched, but there was a keyword-specific syntax
      * error.
      */(name, validTypes)}\n\nError caused by`, node.loc));
    }
    return null;
  }
}
const typesToReadableName = {
  Append: "an append statement",
  Block: "a block statement",
  Call: "a call expression",
  Modifier: "a modifier"
};
function keywords(type) {
  return new Keywords(type);
}
function toAppend({
  assert: assert,
  translate: translate
}) {
  return {
    assert: assert,
    translate: ({
      node: node,
      state: state
    }, value) => translate({
      node: node,
      state: state
    }, value).mapOk(text => new AppendTextNode({
      text: text,
      loc: node.loc
    }))
  };
}
const CurriedTypeToReadableType = {
  [CurriedTypes.Component]: "component",
  [CurriedTypes.Helper]: "helper",
  [CurriedTypes.Modifier]: "modifier"
};
function assertCurryKeyword(curriedType) {
  return (node, state) => {
    let readableType = CurriedTypeToReadableType[curriedType],
      stringsAllowed = curriedType === CurriedTypes.Component,
      {
        args: args
      } = node,
      definition = args.nth(0);
    if (null === definition) return Err(generateSyntaxError(`(${readableType}) requires a ${readableType} definition or identifier as its first positional parameter, did not receive any parameters.`, args.loc));
    if ("Literal" === definition.type) {
      if (stringsAllowed && state.isStrict) return Err(generateSyntaxError(`(${readableType}) cannot resolve string values in strict mode templates`, node.loc));
      if (!stringsAllowed) return Err(generateSyntaxError(`(${readableType}) cannot resolve string values, you must pass a ${readableType} definition directly`, node.loc));
    }
    return args = new api$1.Args({
      positional: new api$1.PositionalArguments({
        exprs: args.positional.exprs.slice(1),
        loc: args.positional.loc
      }),
      named: args.named,
      loc: args.loc
    }), Ok({
      definition: definition,
      args: args
    });
  };
}
function translateCurryKeyword(curriedType) {
  return ({
    node: node,
    state: state
  }, {
    definition: definition,
    args: args
  }) => {
    let definitionResult = VISIT_EXPRS.visit(definition, state),
      argsResult = VISIT_EXPRS.Args(args, state);
    return Result.all(definitionResult, argsResult).mapOk(([definition, args]) => new Curry({
      loc: node.loc,
      curriedType: curriedType,
      definition: definition,
      args: args
    }));
  };
}
function curryKeyword(curriedType) {
  return {
    assert: assertCurryKeyword(curriedType),
    translate: translateCurryKeyword(curriedType)
  };
}
const getDynamicVarKeyword = {
  assert: function (node) {
    let call = "AppendContent" === node.type ? node.value : node,
      named = "Call" === call.type ? call.args.named : null,
      positionals = "Call" === call.type ? call.args.positional : null;
    if (named && !named.isEmpty()) return Err(generateSyntaxError("(-get-dynamic-vars) does not take any named arguments", node.loc));
    let varName = positionals?.nth(0);
    return varName ? positionals && positionals.size > 1 ? Err(generateSyntaxError("(-get-dynamic-vars) only receives one positional arg", node.loc)) : Ok(varName) : Err(generateSyntaxError("(-get-dynamic-vars) requires a var name to get", node.loc));
  },
  translate: function ({
    node: node,
    state: state
  }, name) {
    return VISIT_EXPRS.visit(name, state).mapOk(name => new GetDynamicVar({
      name: name,
      loc: node.loc
    }));
  }
};
function assertHasBlockKeyword(type) {
  return node => {
    let call = "AppendContent" === node.type ? node.value : node,
      named = "Call" === call.type ? call.args.named : null,
      positionals = "Call" === call.type ? call.args.positional : null;
    if (named && !named.isEmpty()) return Err(generateSyntaxError(`(${type}) does not take any named arguments`, call.loc));
    if (!positionals || positionals.isEmpty()) return Ok(SourceSlice.synthetic("default"));
    if (1 === positionals.exprs.length) {
      let positional = positionals.exprs[0];
      return api$1.isLiteral(positional, "string") ? Ok(positional.toSlice()) : Err(generateSyntaxError(`(${type}) can only receive a string literal as its first argument`, call.loc));
    }
    return Err(generateSyntaxError(`(${type}) only takes a single positional argument`, call.loc));
  };
}
function translateHasBlockKeyword(type) {
  return ({
    node: node,
    state: {
      scope: scope
    }
  }, target) => Ok("has-block" === type ? new HasBlock({
    loc: node.loc,
    target: target,
    symbol: scope.allocateBlock(target.chars)
  }) : new HasBlockParams({
    loc: node.loc,
    target: target,
    symbol: scope.allocateBlock(target.chars)
  }));
}
function hasBlockKeyword(type) {
  return {
    assert: assertHasBlockKeyword(type),
    translate: translateHasBlockKeyword(type)
  };
}
function assertIfUnlessInlineKeyword(type) {
  return originalNode => {
    let inverted = "unless" === type,
      node = "AppendContent" === originalNode.type ? originalNode.value : originalNode,
      named = "Call" === node.type ? node.args.named : null,
      positional = "Call" === node.type ? node.args.positional : null;
    if (named && !named.isEmpty()) return Err(generateSyntaxError(`(${type}) cannot receive named parameters, received ${named.entries.map(e => e.name.chars).join(", ")}`, originalNode.loc));
    let condition = positional?.nth(0);
    if (!positional || !condition) return Err(generateSyntaxError(`When used inline, (${type}) requires at least two parameters 1. the condition that determines the state of the (${type}), and 2. the value to return if the condition is ${inverted ? "false" : "true"}. Did not receive any parameters`, originalNode.loc));
    let truthy = positional.nth(1),
      falsy = positional.nth(2);
    return null === truthy ? Err(generateSyntaxError(`When used inline, (${type}) requires at least two parameters 1. the condition that determines the state of the (${type}), and 2. the value to return if the condition is ${inverted ? "false" : "true"}. Received only one parameter, the condition`, originalNode.loc)) : positional.size > 3 ? Err(generateSyntaxError(`When used inline, (${type}) can receive a maximum of three positional parameters 1. the condition that determines the state of the (${type}), 2. the value to return if the condition is ${inverted ? "false" : "true"}, and 3. the value to return if the condition is ${inverted ? "true" : "false"}. Received ${positional?.size ?? 0} parameters`, originalNode.loc)) : Ok({
      condition: condition,
      truthy: truthy,
      falsy: falsy
    });
  };
}
function translateIfUnlessInlineKeyword(type) {
  let inverted = "unless" === type;
  return ({
    node: node,
    state: state
  }, {
    condition: condition,
    truthy: truthy,
    falsy: falsy
  }) => {
    let conditionResult = VISIT_EXPRS.visit(condition, state),
      truthyResult = VISIT_EXPRS.visit(truthy, state),
      falsyResult = falsy ? VISIT_EXPRS.visit(falsy, state) : Ok(null);
    return Result.all(conditionResult, truthyResult, falsyResult).mapOk(([condition, truthy, falsy]) => (inverted && (condition = new Not({
      value: condition,
      loc: node.loc
    })), new IfInline({
      loc: node.loc,
      condition: condition,
      truthy: truthy,
      falsy: falsy
    })));
  };
}
function ifUnlessInlineKeyword(type) {
  return {
    assert: assertIfUnlessInlineKeyword(type),
    translate: translateIfUnlessInlineKeyword(type)
  };
}
const logKeyword = {
    assert: function (node) {
      let {
        args: {
          named: named,
          positional: positional
        }
      } = node;
      return named && !named.isEmpty() ? Err(generateSyntaxError("(log) does not take any named arguments", node.loc)) : Ok(positional);
    },
    translate: function ({
      node: node,
      state: state
    }, positional) {
      return VISIT_EXPRS.Positional(positional, state).mapOk(positional => new Log({
        positional: positional,
        loc: node.loc
      }));
    }
  },
  APPEND_KEYWORDS = keywords("Append").kw("has-block", toAppend(hasBlockKeyword("has-block"))).kw("has-block-params", toAppend(hasBlockKeyword("has-block-params"))).kw("-get-dynamic-var", toAppend(getDynamicVarKeyword)).kw("log", toAppend(logKeyword)).kw("if", toAppend(ifUnlessInlineKeyword("if"))).kw("unless", toAppend(ifUnlessInlineKeyword("unless"))).kw("yield", {
    assert(node) {
      let {
        args: args
      } = node;
      if (args.named.isEmpty()) return Ok({
        target: api$2.SourceSpan.synthetic("default").toSlice(),
        positional: args.positional
      });
      {
        let target = args.named.get("to");
        return args.named.size > 1 || null === target ? Err(generateSyntaxError("yield only takes a single named argument: 'to'", args.named.loc)) : api$1.isLiteral(target, "string") ? Ok({
          target: target.toSlice(),
          positional: args.positional
        }) : Err(generateSyntaxError("you can only yield to a literal string value", target.loc));
      }
    },
    translate: ({
      node: node,
      state: state
    }, {
      target: target,
      positional: positional
    }) => VISIT_EXPRS.Positional(positional, state).mapOk(positional => new Yield({
      loc: node.loc,
      target: target,
      to: state.scope.allocateBlock(target.chars),
      positional: positional
    }))
  }).kw("debugger", {
    assert(node) {
      let {
          args: args
        } = node,
        {
          positional: positional
        } = args;
      return args.isEmpty() ? Ok(void 0) : positional.isEmpty() ? Err(generateSyntaxError("debugger does not take any named arguments", node.loc)) : Err(generateSyntaxError("debugger does not take any positional arguments", node.loc));
    },
    translate: ({
      node: node,
      state: {
        scope: scope
      }
    }) => (scope.setHasDebugger(), Ok(new Debugger({
      loc: node.loc,
      scope: scope
    })))
  }).kw("component", {
    assert: assertCurryKeyword(CurriedTypes.Component),
    translate({
      node: node,
      state: state
    }, {
      definition: definition,
      args: args
    }) {
      let definitionResult = VISIT_EXPRS.visit(definition, state),
        argsResult = VISIT_EXPRS.Args(args, state);
      return Result.all(definitionResult, argsResult).mapOk(([definition, args]) => new InvokeComponent({
        loc: node.loc,
        definition: definition,
        args: args,
        blocks: null
      }));
    }
  }).kw("helper", {
    assert: assertCurryKeyword(CurriedTypes.Helper),
    translate({
      node: node,
      state: state
    }, {
      definition: definition,
      args: args
    }) {
      let definitionResult = VISIT_EXPRS.visit(definition, state),
        argsResult = VISIT_EXPRS.Args(args, state);
      return Result.all(definitionResult, argsResult).mapOk(([definition, args]) => {
        let text = new CallExpression({
          callee: definition,
          args: args,
          loc: node.loc
        });
        return new AppendTextNode({
          loc: node.loc,
          text: text
        });
      });
    }
  }),
  BLOCK_KEYWORDS = keywords("Block").kw("in-element", {
    assert(node) {
      let {
          args: args
        } = node,
        guid = args.get("guid");
      if (guid) return Err(generateSyntaxError("Cannot pass `guid` to `{{#in-element}}`", guid.loc));
      let insertBefore = args.get("insertBefore"),
        destination = args.nth(0);
      return null === destination ? Err(generateSyntaxError("{{#in-element}} requires a target element as its first positional parameter", args.loc)) : Ok({
        insertBefore: insertBefore,
        destination: destination
      });
      // TODO Better syntax checks
    },
    translate({
      node: node,
      state: state
    }, {
      insertBefore: insertBefore,
      destination: destination
    }) {
      let named = node.blocks.get("default"),
        body = VISIT_STMTS.NamedBlock(named, state),
        destinationResult = VISIT_EXPRS.visit(destination, state);
      return Result.all(body, destinationResult).andThen(([body, destination]) => insertBefore ? VISIT_EXPRS.visit(insertBefore, state).mapOk(insertBefore => ({
        body: body,
        destination: destination,
        insertBefore: insertBefore
      })) : Ok({
        body: body,
        destination: destination,
        insertBefore: new Missing({
          loc: node.callee.loc.collapse("end")
        })
      })).mapOk(({
        body: body,
        destination: destination,
        insertBefore: insertBefore
      }) => new InElement({
        loc: node.loc,
        block: body,
        insertBefore: insertBefore,
        guid: state.generateUniqueCursor(),
        destination: destination
      }));
    }
  }).kw("if", {
    assert(node) {
      let {
        args: args
      } = node;
      if (!args.named.isEmpty()) return Err(generateSyntaxError(`{{#if}} cannot receive named parameters, received ${args.named.entries.map(e => e.name.chars).join(", ")}`, node.loc));
      if (args.positional.size > 1) return Err(generateSyntaxError(`{{#if}} can only receive one positional parameter in block form, the conditional value. Received ${args.positional.size} parameters`, node.loc));
      let condition = args.nth(0);
      return null === condition ? Err(generateSyntaxError("{{#if}} requires a condition as its first positional parameter, did not receive any parameters", node.loc)) : Ok({
        condition: condition
      });
    },
    translate({
      node: node,
      state: state
    }, {
      condition: condition
    }) {
      let block = node.blocks.get("default"),
        inverse = node.blocks.get("else"),
        conditionResult = VISIT_EXPRS.visit(condition, state),
        blockResult = VISIT_STMTS.NamedBlock(block, state),
        inverseResult = inverse ? VISIT_STMTS.NamedBlock(inverse, state) : Ok(null);
      return Result.all(conditionResult, blockResult, inverseResult).mapOk(([condition, block, inverse]) => new If({
        loc: node.loc,
        condition: condition,
        block: block,
        inverse: inverse
      }));
    }
  }).kw("unless", {
    assert(node) {
      let {
        args: args
      } = node;
      if (!args.named.isEmpty()) return Err(generateSyntaxError(`{{#unless}} cannot receive named parameters, received ${args.named.entries.map(e => e.name.chars).join(", ")}`, node.loc));
      if (args.positional.size > 1) return Err(generateSyntaxError(`{{#unless}} can only receive one positional parameter in block form, the conditional value. Received ${args.positional.size} parameters`, node.loc));
      let condition = args.nth(0);
      return null === condition ? Err(generateSyntaxError("{{#unless}} requires a condition as its first positional parameter, did not receive any parameters", node.loc)) : Ok({
        condition: condition
      });
    },
    translate({
      node: node,
      state: state
    }, {
      condition: condition
    }) {
      let block = node.blocks.get("default"),
        inverse = node.blocks.get("else"),
        conditionResult = VISIT_EXPRS.visit(condition, state),
        blockResult = VISIT_STMTS.NamedBlock(block, state),
        inverseResult = inverse ? VISIT_STMTS.NamedBlock(inverse, state) : Ok(null);
      return Result.all(conditionResult, blockResult, inverseResult).mapOk(([condition, block, inverse]) => new If({
        loc: node.loc,
        condition: new Not({
          value: condition,
          loc: node.loc
        }),
        block: block,
        inverse: inverse
      }));
    }
  }).kw("each", {
    assert(node) {
      let {
        args: args
      } = node;
      if (!args.named.entries.every(e => "key" === e.name.chars)) return Err(generateSyntaxError(`{{#each}} can only receive the 'key' named parameter, received ${args.named.entries.filter(e => "key" !== e.name.chars).map(e => e.name.chars).join(", ")}`, args.named.loc));
      if (args.positional.size > 1) return Err(generateSyntaxError(`{{#each}} can only receive one positional parameter, the collection being iterated. Received ${args.positional.size} parameters`, args.positional.loc));
      let value = args.nth(0),
        key = args.get("key");
      return null === value ? Err(generateSyntaxError("{{#each}} requires an iterable value to be passed as its first positional parameter, did not receive any parameters", args.loc)) : Ok({
        value: value,
        key: key
      });
    },
    translate({
      node: node,
      state: state
    }, {
      value: value,
      key: key
    }) {
      let block = node.blocks.get("default"),
        inverse = node.blocks.get("else"),
        valueResult = VISIT_EXPRS.visit(value, state),
        keyResult = key ? VISIT_EXPRS.visit(key, state) : Ok(null),
        blockResult = VISIT_STMTS.NamedBlock(block, state),
        inverseResult = inverse ? VISIT_STMTS.NamedBlock(inverse, state) : Ok(null);
      return Result.all(valueResult, keyResult, blockResult, inverseResult).mapOk(([value, key, block, inverse]) => new Each({
        loc: node.loc,
        value: value,
        key: key,
        block: block,
        inverse: inverse
      }));
    }
  }).kw("let", {
    assert(node) {
      let {
        args: args
      } = node;
      return args.named.isEmpty() ? 0 === args.positional.size ? Err(generateSyntaxError("{{#let}} requires at least one value as its first positional parameter, did not receive any parameters", args.positional.loc)) : node.blocks.get("else") ? Err(generateSyntaxError("{{#let}} cannot receive an {{else}} block", args.positional.loc)) : Ok({
        positional: args.positional
      }) : Err(generateSyntaxError(`{{#let}} cannot receive named parameters, received ${args.named.entries.map(e => e.name.chars).join(", ")}`, args.named.loc));
    },
    translate({
      node: node,
      state: state
    }, {
      positional: positional
    }) {
      let block = node.blocks.get("default"),
        positionalResult = VISIT_EXPRS.Positional(positional, state),
        blockResult = VISIT_STMTS.NamedBlock(block, state);
      return Result.all(positionalResult, blockResult).mapOk(([positional, block]) => new Let({
        loc: node.loc,
        positional: positional,
        block: block
      }));
    }
  }).kw("-with-dynamic-vars", {
    assert: node => Ok({
      named: node.args.named
    }),
    translate({
      node: node,
      state: state
    }, {
      named: named
    }) {
      let block = node.blocks.get("default"),
        namedResult = VISIT_EXPRS.NamedArguments(named, state),
        blockResult = VISIT_STMTS.NamedBlock(block, state);
      return Result.all(namedResult, blockResult).mapOk(([named, block]) => new WithDynamicVars({
        loc: node.loc,
        named: named,
        block: block
      }));
    }
  }).kw("component", {
    assert: assertCurryKeyword(CurriedTypes.Component),
    translate({
      node: node,
      state: state
    }, {
      definition: definition,
      args: args
    }) {
      let definitionResult = VISIT_EXPRS.visit(definition, state),
        argsResult = VISIT_EXPRS.Args(args, state),
        blocksResult = VISIT_STMTS.NamedBlocks(node.blocks, state);
      return Result.all(definitionResult, argsResult, blocksResult).mapOk(([definition, args, blocks]) => new InvokeComponent({
        loc: node.loc,
        definition: definition,
        args: args,
        blocks: blocks
      }));
    }
  }),
  CALL_KEYWORDS = keywords("Call").kw("has-block", hasBlockKeyword("has-block")).kw("has-block-params", hasBlockKeyword("has-block-params")).kw("-get-dynamic-var", getDynamicVarKeyword).kw("log", logKeyword).kw("if", ifUnlessInlineKeyword("if")).kw("unless", ifUnlessInlineKeyword("unless")).kw("component", curryKeyword(CurriedTypes.Component)).kw("helper", curryKeyword(CurriedTypes.Helper)).kw("modifier", curryKeyword(CurriedTypes.Modifier)),
  MODIFIER_KEYWORDS = keywords("Modifier"),
  XLINK = "http://www.w3.org/1999/xlink",
  XML = "http://www.w3.org/XML/1998/namespace",
  XMLNS = "http://www.w3.org/2000/xmlns/",
  WHITELIST = {
    "xlink:actuate": XLINK,
    "xlink:arcrole": XLINK,
    "xlink:href": XLINK,
    "xlink:role": XLINK,
    "xlink:show": XLINK,
    "xlink:title": XLINK,
    "xlink:type": XLINK,
    "xml:base": XML,
    "xml:lang": XML,
    "xml:space": XML,
    xmlns: XMLNS,
    "xmlns:xlink": XMLNS
  },
  DEFLATE_TAG_TABLE = {
    div: WellKnownTagNames.div,
    span: WellKnownTagNames.span,
    p: WellKnownTagNames.p,
    a: WellKnownTagNames.a
  };
const DEFLATE_ATTR_TABLE = {
    class: WellKnownAttrNames.class,
    id: WellKnownAttrNames.id,
    value: WellKnownAttrNames.value,
    name: WellKnownAttrNames.name,
    type: WellKnownAttrNames.type,
    style: WellKnownAttrNames.style,
    href: WellKnownAttrNames.href
  };
function deflateAttrName(attrName) {
  return DEFLATE_ATTR_TABLE[attrName] ?? attrName;
}
class ClassifiedElement {
  delegate;
  constructor(element, delegate, state) {
    this.element = element, this.state = state, this.delegate = delegate;
  }
  toStatement() {
    return this.prepare().andThen(prepared => this.delegate.toStatement(this, prepared));
  }
  attr(attr) {
    let name = attr.name,
      rawValue = attr.value,
      namespace = (attrName = name.chars, WHITELIST[attrName] || void 0);
    var attrName;
    return api$1.isLiteral(rawValue, "string") ? Ok(new StaticAttr({
      loc: attr.loc,
      name: name,
      value: rawValue.toSlice(),
      namespace: namespace,
      kind: {
        component: this.delegate.dynamicFeatures
      }
    })) : VISIT_EXPRS.visit(convertPathToCallIfKeyword(rawValue), this.state).mapOk(value => {
      let isTrusting = attr.trusting;
      return new DynamicAttr({
        loc: attr.loc,
        name: name,
        value: value,
        namespace: namespace,
        kind: {
          trusting: isTrusting,
          component: this.delegate.dynamicFeatures
        }
      });
    });
  }
  modifier(modifier) {
    let translated = MODIFIER_KEYWORDS.translate(modifier, this.state);
    if (null !== translated) return translated;
    let head = VISIT_EXPRS.visit(modifier.callee, this.state),
      args = VISIT_EXPRS.Args(modifier.args, this.state);
    return Result.all(head, args).mapOk(([head, args]) => new Modifier({
      loc: modifier.loc,
      callee: head,
      args: args
    }));
  }
  attrs() {
    let attrs = new ResultArray(),
      args = new ResultArray(),
      typeAttr = null,
      simple = 0 === this.element.attrs.filter(attr => "SplatAttr" === attr.type).length;
    for (let attr of this.element.attrs) "SplatAttr" === attr.type ? attrs.add(Ok(new SplatAttr({
      loc: attr.loc,
      symbol: this.state.scope.allocateBlock("attrs")
    }))) : "type" === attr.name.chars && simple ? typeAttr = attr : attrs.add(this.attr(attr));
    for (let arg of this.element.componentArgs) args.add(this.delegate.arg(arg, this));
    return typeAttr && attrs.add(this.attr(typeAttr)), Result.all(args.toArray(), attrs.toArray()).mapOk(([args, attrs]) => ({
      attrs: attrs,
      args: new NamedArguments({
        loc: maybeLoc(args, api$2.SourceSpan.NON_EXISTENT),
        entries: OptionalList(args)
      })
    }));
  }
  prepare() {
    let attrs = this.attrs(),
      modifiers = new ResultArray(this.element.modifiers.map(m => this.modifier(m))).toArray();
    return Result.all(attrs, modifiers).mapOk(([result, modifiers]) => {
      let {
          attrs: attrs,
          args: args
        } = result,
        elementParams = [...attrs, ...modifiers];
      return {
        args: args,
        params: new ElementParameters({
          loc: maybeLoc(elementParams, api$2.SourceSpan.NON_EXISTENT),
          body: OptionalList(elementParams)
        })
      };
    });
  }
}
class ClassifiedComponent {
  dynamicFeatures = !0;
  constructor(tag, element) {
    this.tag = tag, this.element = element;
  }
  arg(attr, {
    state: state
  }) {
    let name = attr.name;
    return VISIT_EXPRS.visit(convertPathToCallIfKeyword(attr.value), state).mapOk(value => new NamedArgument({
      loc: attr.loc,
      key: name,
      value: value
    }));
  }
  toStatement(component, {
    args: args,
    params: params
  }) {
    let {
      element: element,
      state: state
    } = component;
    return this.blocks(state).mapOk(blocks => new Component({
      loc: element.loc,
      tag: this.tag,
      params: params,
      args: args,
      blocks: blocks
    }));
  }
  blocks(state) {
    return VISIT_STMTS.NamedBlocks(this.element.blocks, state);
  }
}
class ClassifiedSimpleElement {
  constructor(tag, element, dynamicFeatures) {
    this.tag = tag, this.element = element, this.dynamicFeatures = dynamicFeatures;
  }
  isComponent = !1;
  arg(attr) {
    return Err(generateSyntaxError(`${attr.name.chars} is not a valid attribute name. @arguments are only allowed on components, but the tag for this element (\`${this.tag.chars}\`) is a regular, non-component HTML element.`, attr.loc));
  }
  toStatement(classified, {
    params: params
  }) {
    let {
      state: state,
      element: element
    } = classified;
    return VISIT_STMTS.visitList(this.element.body, state).mapOk(body => new SimpleElement({
      loc: element.loc,
      tag: this.tag,
      params: params,
      body: body.toArray(),
      dynamicFeatures: this.dynamicFeatures
    }));
  }
}
const VISIT_STMTS = new class {
  visitList(nodes, state) {
    return new ResultArray(nodes.map(e => VISIT_STMTS.visit(e, state))).toOptionalList().mapOk(list => list.filter(s => null !== s));
  }
  visit(node, state) {
    switch (node.type) {
      case "GlimmerComment":
        return Ok(null);
      case "AppendContent":
        return this.AppendContent(node, state);
      case "HtmlText":
        return Ok(this.TextNode(node));
      case "HtmlComment":
        return Ok(this.HtmlComment(node));
      case "InvokeBlock":
        return this.InvokeBlock(node, state);
      case "InvokeComponent":
        return this.Component(node, state);
      case "SimpleElement":
        return this.SimpleElement(node, state);
    }
  }
  InvokeBlock(node, state) {
    let translated = BLOCK_KEYWORDS.translate(node, state);
    if (null !== translated) return translated;
    let head = VISIT_EXPRS.visit(node.callee, state),
      args = VISIT_EXPRS.Args(node.args, state);
    return Result.all(head, args).andThen(([head, args]) => this.NamedBlocks(node.blocks, state).mapOk(blocks => new InvokeBlock({
      loc: node.loc,
      head: head,
      args: args,
      blocks: blocks
    })));
  }
  NamedBlocks(blocks, state) {
    return new ResultArray(blocks.blocks.map(b => this.NamedBlock(b, state))).toArray().mapOk(list => new NamedBlocks({
      loc: blocks.loc,
      blocks: OptionalList(list)
    }));
  }
  NamedBlock(named, state) {
    return state.visitBlock(named.block).mapOk(body => new NamedBlock({
      loc: named.loc,
      name: named.name,
      body: body.toArray(),
      scope: named.block.scope
    }));
  }
  SimpleElement(element, state) {
    return new ClassifiedElement(element, new ClassifiedSimpleElement(element.tag, element, function ({
      attrs: attrs,
      modifiers: modifiers
    }) {
      // ElementModifier needs the special ComponentOperations
      return modifiers.length > 0 || !!attrs.filter(attr => "SplatAttr" === attr.type)[0];
      // Splattributes need the special ComponentOperations to merge into
    }(element)), state).toStatement();
  }
  Component(component, state) {
    return VISIT_EXPRS.visit(component.callee, state).andThen(callee => new ClassifiedElement(component, new ClassifiedComponent(callee, component), state).toStatement());
  }
  AppendContent(append, state) {
    let translated = APPEND_KEYWORDS.translate(append, state);
    return null !== translated ? translated : VISIT_EXPRS.visit(append.value, state).mapOk(value => append.trusting ? new AppendTrustedHTML({
      loc: append.loc,
      html: value
    }) : new AppendTextNode({
      loc: append.loc,
      text: value
    }));
  }
  TextNode(text) {
    return new AppendTextNode({
      loc: text.loc,
      text: new api$1.LiteralExpression({
        loc: text.loc,
        value: text.chars
      })
    });
  }
  HtmlComment(comment) {
    return new AppendComment({
      loc: comment.loc,
      value: comment.text
    });
  }
}();

/**
 * This is the mutable state for this compiler pass.
 */
class NormalizationState {
  _currentScope;
  _cursorCount = 0;
  constructor(block, isStrict) {
    this.isStrict = isStrict, this._currentScope = block;
  }
  generateUniqueCursor() {
    return `%cursor:${this._cursorCount++}%`;
  }
  get scope() {
    return this._currentScope;
  }
  visitBlock(block) {
    let oldBlock = this._currentScope;
    this._currentScope = block.scope;
    try {
      return VISIT_STMTS.visitList(block.body, this);
    } finally {
      this._currentScope = oldBlock;
    }
  }
}
var ResolutionType = function (ResolutionType) {
  return ResolutionType.Value = "value", ResolutionType.Component = "component", ResolutionType.Helper = "helper", ResolutionType.Modifier = "modifier", ResolutionType.ComponentOrHelper = "component or helper", ResolutionType;
}(ResolutionType || {});
class StrictModeValidationPass {
  // This is done at the end of all the keyword normalizations
  // At this point any free variables that isn't a valid keyword
  // in its context should be considered a syntax error. We
  // probably had various opportunities to do this inline in the
  // earlier passes, but this aims to produce a better syntax
  // error as we don't always have the right loc-context to do
  // so in the other spots.
  static validate(template) {
    return new this(template).validate();
  }
  constructor(template) {
    this.template = template;
  }
  validate() {
    return this.Statements(this.template.body).mapOk(() => this.template);
  }
  Statements(statements) {
    let result = Ok(null);
    for (let statement of statements) result = result.andThen(() => this.Statement(statement));
    return result;
  }
  NamedBlocks({
    blocks: blocks
  }) {
    let result = Ok(null);
    for (let block of blocks.toArray()) result = result.andThen(() => this.NamedBlock(block));
    return result;
  }
  NamedBlock(block) {
    return this.Statements(block.body);
  }
  Statement(statement) {
    switch (statement.type) {
      case "InElement":
        return this.InElement(statement);
      case "Debugger":
      case "AppendComment":
        return Ok(null);
      case "Yield":
        return this.Yield(statement);
      case "AppendTrustedHTML":
        return this.AppendTrustedHTML(statement);
      case "AppendTextNode":
        return this.AppendTextNode(statement);
      case "Component":
        return this.Component(statement);
      case "SimpleElement":
        return this.SimpleElement(statement);
      case "InvokeBlock":
        return this.InvokeBlock(statement);
      case "If":
        return this.If(statement);
      case "Each":
        return this.Each(statement);
      case "Let":
        return this.Let(statement);
      case "WithDynamicVars":
        return this.WithDynamicVars(statement);
      case "InvokeComponent":
        return this.InvokeComponent(statement);
    }
  }
  Expressions(expressions) {
    let result = Ok(null);
    for (let expression of expressions) result = result.andThen(() => this.Expression(expression));
    return result;
  }
  Expression(expression, span = expression, resolution) {
    switch (expression.type) {
      case "Literal":
      case "Keyword":
      case "Missing":
      case "This":
      case "Arg":
      case "Local":
      case "HasBlock":
      case "HasBlockParams":
      case "GetDynamicVar":
        return Ok(null);
      case "PathExpression":
        return this.Expression(expression.head, span, resolution);
      case "Free":
        return this.errorFor(expression.name, span, resolution);
      case "InterpolateExpression":
        return this.InterpolateExpression(expression, span, resolution);
      case "CallExpression":
        return this.CallExpression(expression, span, resolution ?? ResolutionType.Helper);
      case "Not":
        return this.Expression(expression.value, span, resolution);
      case "IfInline":
        return this.IfInline(expression);
      case "Curry":
        return this.Curry(expression);
      case "Log":
        return this.Log(expression);
    }
  }
  Args(args) {
    return this.Positional(args.positional).andThen(() => this.NamedArguments(args.named));
  }
  Positional(positional, span) {
    let result = Ok(null),
      expressions = positional.list.toArray();
    // For cases like {{yield foo}}, when there is only a single argument, it
    // makes for a slightly better error to report that entire span. However,
    // when there are more than one, we need to be specific.
    return result = 1 === expressions.length ? this.Expression(expressions[0], span) : this.Expressions(expressions), result;
  }
  NamedArguments({
    entries: entries
  }) {
    let result = Ok(null);
    for (let arg of entries.toArray()) result = result.andThen(() => this.NamedArgument(arg));
    return result;
  }
  NamedArgument(arg) {
    return "CallExpression" === arg.value.type ? this.Expression(arg.value, arg, ResolutionType.Helper) : this.Expression(arg.value, arg);
  }
  ElementParameters({
    body: body
  }) {
    let result = Ok(null);
    for (let param of body.toArray()) result = result.andThen(() => this.ElementParameter(param));
    return result;
  }
  ElementParameter(param) {
    switch (param.type) {
      case "StaticAttr":
      case "SplatAttr":
        return Ok(null);
      case "DynamicAttr":
        return this.DynamicAttr(param);
      case "Modifier":
        return this.Modifier(param);
    }
  }
  DynamicAttr(attr) {
    return "CallExpression" === attr.value.type ? this.Expression(attr.value, attr, ResolutionType.Helper) : this.Expression(attr.value, attr);
  }
  Modifier(modifier) {
    return this.Expression(modifier.callee, modifier, ResolutionType.Modifier).andThen(() => this.Args(modifier.args));
  }
  InElement(inElement) {
    return this.Expression(inElement.destination).andThen(() => this.Expression(inElement.insertBefore)).andThen(() => this.NamedBlock(inElement.block));
  }
  Yield(statement) {
    return this.Positional(statement.positional, statement);
  }
  AppendTrustedHTML(statement) {
    return this.Expression(statement.html, statement);
  }
  AppendTextNode(statement) {
    return "CallExpression" === statement.text.type ? this.Expression(statement.text, statement, ResolutionType.ComponentOrHelper) : this.Expression(statement.text, statement);
  }
  Component(statement) {
    return this.Expression(statement.tag, statement, ResolutionType.Component).andThen(() => this.ElementParameters(statement.params)).andThen(() => this.NamedArguments(statement.args)).andThen(() => this.NamedBlocks(statement.blocks));
  }
  SimpleElement(statement) {
    return this.ElementParameters(statement.params).andThen(() => this.Statements(statement.body));
  }
  InvokeBlock(statement) {
    return this.Expression(statement.head, statement.head, ResolutionType.Component).andThen(() => this.Args(statement.args)).andThen(() => this.NamedBlocks(statement.blocks));
  }
  If(statement) {
    return this.Expression(statement.condition, statement).andThen(() => this.NamedBlock(statement.block)).andThen(() => statement.inverse ? this.NamedBlock(statement.inverse) : Ok(null));
  }
  Each(statement) {
    return this.Expression(statement.value, statement).andThen(() => statement.key ? this.Expression(statement.key, statement) : Ok(null)).andThen(() => this.NamedBlock(statement.block)).andThen(() => statement.inverse ? this.NamedBlock(statement.inverse) : Ok(null));
  }
  Let(statement) {
    return this.Positional(statement.positional).andThen(() => this.NamedBlock(statement.block));
  }
  WithDynamicVars(statement) {
    return this.NamedArguments(statement.named).andThen(() => this.NamedBlock(statement.block));
  }
  InvokeComponent(statement) {
    return this.Expression(statement.definition, statement, ResolutionType.Component).andThen(() => this.Args(statement.args)).andThen(() => statement.blocks ? this.NamedBlocks(statement.blocks) : Ok(null));
  }
  InterpolateExpression(expression, span, resolution) {
    let expressions = expression.parts.toArray();
    return 1 === expressions.length ? this.Expression(expressions[0], span, resolution) : this.Expressions(expressions);
  }
  CallExpression(expression, span, resolution) {
    return this.Expression(expression.callee, span, resolution).andThen(() => this.Args(expression.args));
  }
  IfInline(expression) {
    return this.Expression(expression.condition).andThen(() => this.Expression(expression.truthy)).andThen(() => expression.falsy ? this.Expression(expression.falsy) : Ok(null));
  }
  Curry(expression) {
    let resolution;
    return resolution = expression.curriedType === CurriedTypes.Component ? ResolutionType.Component : expression.curriedType === CurriedTypes.Helper ? ResolutionType.Helper : ResolutionType.Modifier, this.Expression(expression.definition, expression, resolution).andThen(() => this.Args(expression.args));
  }
  Log(expression) {
    return this.Positional(expression.positional, expression);
  }
  errorFor(name, span, type = ResolutionType.Value) {
    return Err(generateSyntaxError(`Attempted to resolve a ${type} in a strict mode template, but that value was not in scope: ${name}`, loc(span)));
  }
}
const EXPR = new class {
  expr(expr) {
    switch (expr.type) {
      case "Missing":
        return;
      case "Literal":
        return this.Literal(expr);
      case "Keyword":
        return this.Keyword(expr);
      case "CallExpression":
        return this.CallExpression(expr);
      case "PathExpression":
        return this.PathExpression(expr);
      case "Arg":
        return [opcodes.GetSymbol, expr.symbol];
      case "Local":
        return this.Local(expr);
      case "This":
        return [opcodes.GetSymbol, 0];
      case "Free":
        return [expr.resolution.resolution(), expr.symbol];
      case "HasBlock":
        return this.HasBlock(expr);
      case "HasBlockParams":
        return this.HasBlockParams(expr);
      case "Curry":
        return this.Curry(expr);
      case "Not":
        return this.Not(expr);
      case "IfInline":
        return this.IfInline(expr);
      case "InterpolateExpression":
        return this.InterpolateExpression(expr);
      case "GetDynamicVar":
        return this.GetDynamicVar(expr);
      case "Log":
        return this.Log(expr);
    }
  }
  Literal({
    value: value
  }) {
    return void 0 === value ? [opcodes.Undefined] : value;
  }
  Missing() {}
  HasBlock({
    symbol: symbol
  }) {
    return [opcodes.HasBlock, [opcodes.GetSymbol, symbol]];
  }
  HasBlockParams({
    symbol: symbol
  }) {
    return [opcodes.HasBlockParams, [opcodes.GetSymbol, symbol]];
  }
  Curry({
    definition: definition,
    curriedType: curriedType,
    args: args
  }) {
    return [opcodes.Curry, EXPR.expr(definition), curriedType, EXPR.Positional(args.positional), EXPR.NamedArguments(args.named)];
  }
  Local({
    isTemplateLocal: isTemplateLocal,
    symbol: symbol
  }) {
    return [isTemplateLocal ? opcodes.GetLexicalSymbol : opcodes.GetSymbol, symbol];
  }
  Keyword({
    symbol: symbol
  }) {
    return [opcodes.GetStrictKeyword, symbol];
  }
  PathExpression({
    head: head,
    tail: tail
  }) {
    let getOp = EXPR.expr(head);
    return debugAssert(getOp[0] !== opcodes.GetStrictKeyword, "[BUG] keyword in a PathExpression"), [...getOp, EXPR.Tail(tail)];
  }
  InterpolateExpression({
    parts: parts
  }) {
    return [opcodes.Concat, parts.map(e => EXPR.expr(e)).toArray()];
  }
  CallExpression({
    callee: callee,
    args: args
  }) {
    return [opcodes.Call, EXPR.expr(callee), ...EXPR.Args(args)];
  }
  Tail({
    members: members
  }) {
    return mapPresentArray(members, member => member.chars);
  }
  Args({
    positional: positional,
    named: named
  }) {
    return [this.Positional(positional), this.NamedArguments(named)];
  }
  Positional({
    list: list
  }) {
    return list.map(l => EXPR.expr(l)).toPresentArray();
  }
  NamedArgument({
    key: key,
    value: value
  }) {
    return [key.chars, EXPR.expr(value)];
  }
  NamedArguments({
    entries: pairs
  }) {
    let list = pairs.toArray();
    if (isPresentArray(list)) {
      let names = [],
        values = [];
      for (let pair of list) {
        let [name, value] = EXPR.NamedArgument(pair);
        names.push(name), values.push(value);
      }
      return assertPresentArray(names), assertPresentArray(values), [names, values];
    }
    return null;
  }
  Not({
    value: value
  }) {
    return [opcodes.Not, EXPR.expr(value)];
  }
  IfInline({
    condition: condition,
    truthy: truthy,
    falsy: falsy
  }) {
    let expr = [opcodes.IfInline, EXPR.expr(condition), EXPR.expr(truthy)];
    return falsy && expr.push(EXPR.expr(falsy)), expr;
  }
  GetDynamicVar({
    name: name
  }) {
    return [opcodes.GetDynamicVar, EXPR.expr(name)];
  }
  Log({
    positional: positional
  }) {
    return [opcodes.Log, this.Positional(positional)];
  }
}();
class WireStatements {
  constructor(statements) {
    this.statements = statements;
  }
  toArray() {
    return this.statements;
  }
}
const CONTENT = new class {
  list(statements) {
    let out = [];
    for (let statement of statements) {
      let result = CONTENT.content(statement);
      result && result instanceof WireStatements ? out.push(...result.toArray()) : out.push(result);
    }
    return out;
  }
  content(stmt) {
    return this.visitContent(stmt);
  }
  visitContent(stmt) {
    switch (stmt.type) {
      case "Debugger":
        return [opcodes.Debugger, stmt.scope.getDebugInfo()];
      case "AppendComment":
        return this.AppendComment(stmt);
      case "AppendTextNode":
        return this.AppendTextNode(stmt);
      case "AppendTrustedHTML":
        return this.AppendTrustedHTML(stmt);
      case "Yield":
        return this.Yield(stmt);
      case "Component":
        return this.Component(stmt);
      case "SimpleElement":
        return this.SimpleElement(stmt);
      case "InElement":
        return this.InElement(stmt);
      case "InvokeBlock":
        return this.InvokeBlock(stmt);
      case "If":
        return this.If(stmt);
      case "Each":
        return this.Each(stmt);
      case "Let":
        return this.Let(stmt);
      case "WithDynamicVars":
        return this.WithDynamicVars(stmt);
      case "InvokeComponent":
        return this.InvokeComponent(stmt);
      default:
        return exhausted(stmt);
    }
  }
  Yield({
    to: to,
    positional: positional
  }) {
    return [opcodes.Yield, to, EXPR.Positional(positional)];
  }
  InElement({
    guid: guid,
    insertBefore: insertBefore,
    destination: destination,
    block: block
  }) {
    let wireBlock = CONTENT.NamedBlock(block)[1],
      wireDestination = EXPR.expr(destination),
      wireInsertBefore = EXPR.expr(insertBefore);
    // let guid = args.guid;
    return void 0 === wireInsertBefore ? [opcodes.InElement, wireBlock, guid, wireDestination] : [opcodes.InElement, wireBlock, guid, wireDestination, wireInsertBefore];
  }
  InvokeBlock({
    head: head,
    args: args,
    blocks: blocks
  }) {
    return [opcodes.Block, EXPR.expr(head), ...EXPR.Args(args), CONTENT.NamedBlocks(blocks)];
  }
  AppendTrustedHTML({
    html: html
  }) {
    return [opcodes.TrustingAppend, EXPR.expr(html)];
  }
  AppendTextNode({
    text: text
  }) {
    return [opcodes.Append, EXPR.expr(text)];
  }
  AppendComment({
    value: value
  }) {
    return [opcodes.Comment, value.chars];
  }
  SimpleElement({
    tag: tag,
    params: params,
    body: body,
    dynamicFeatures: dynamicFeatures
  }) {
    let op = dynamicFeatures ? opcodes.OpenElementWithSplat : opcodes.OpenElement;
    return new WireStatements([[op, (tagName = tag.chars, DEFLATE_TAG_TABLE[tagName] ?? tagName)], ...CONTENT.ElementParameters(params).toArray(), [opcodes.FlushElement], ...CONTENT.list(body), [opcodes.CloseElement]]);
    var tagName;
  }
  Component({
    tag: tag,
    params: params,
    args: args,
    blocks: blocks
  }) {
    let wireTag = EXPR.expr(tag),
      wirePositional = CONTENT.ElementParameters(params),
      wireNamed = EXPR.NamedArguments(args),
      wireNamedBlocks = CONTENT.NamedBlocks(blocks);
    return [opcodes.Component, wireTag, wirePositional.toPresentArray(), wireNamed, wireNamedBlocks];
  }
  ElementParameters({
    body: body
  }) {
    return body.map(p => CONTENT.ElementParameter(p));
  }
  ElementParameter(param) {
    switch (param.type) {
      case "SplatAttr":
        return [opcodes.AttrSplat, param.symbol];
      case "DynamicAttr":
        return [(kind = param.kind, kind.component ? kind.trusting ? opcodes.TrustingComponentAttr : opcodes.ComponentAttr : kind.trusting ? opcodes.TrustingDynamicAttr : opcodes.DynamicAttr), ...dynamicAttr(param)];
      case "StaticAttr":
        return [staticAttrOp(param.kind), ...staticAttr(param)];
      case "Modifier":
        return [opcodes.Modifier, EXPR.expr(param.callee), ...EXPR.Args(param.args)];
    }
    var kind;
  }
  NamedBlocks({
    blocks: blocks
  }) {
    let names = [],
      serializedBlocks = [];
    for (let block of blocks.toArray()) {
      let [name, serializedBlock] = CONTENT.NamedBlock(block);
      names.push(name), serializedBlocks.push(serializedBlock);
    }
    return names.length > 0 ? [names, serializedBlocks] : null;
  }
  NamedBlock({
    name: name,
    body: body,
    scope: scope
  }) {
    let nameChars = name.chars;
    return "inverse" === nameChars && (nameChars = "else"), [nameChars, [CONTENT.list(body), scope.slots]];
  }
  If({
    condition: condition,
    block: block,
    inverse: inverse
  }) {
    return [opcodes.If, EXPR.expr(condition), CONTENT.NamedBlock(block)[1], inverse ? CONTENT.NamedBlock(inverse)[1] : null];
  }
  Each({
    value: value,
    key: key,
    block: block,
    inverse: inverse
  }) {
    return [opcodes.Each, EXPR.expr(value), key ? EXPR.expr(key) : null, CONTENT.NamedBlock(block)[1], inverse ? CONTENT.NamedBlock(inverse)[1] : null];
  }
  Let({
    positional: positional,
    block: block
  }) {
    return [opcodes.Let, EXPR.Positional(positional), CONTENT.NamedBlock(block)[1]];
  }
  WithDynamicVars({
    named: named,
    block: block
  }) {
    return [opcodes.WithDynamicVars, EXPR.NamedArguments(named), CONTENT.NamedBlock(block)[1]];
  }
  InvokeComponent({
    definition: definition,
    args: args,
    blocks: blocks
  }) {
    return [opcodes.InvokeComponent, EXPR.expr(definition), EXPR.Positional(args.positional), EXPR.NamedArguments(args.named), blocks ? CONTENT.NamedBlocks(blocks) : null];
  }
}();
function staticAttr({
  name: name,
  value: value,
  namespace: namespace
}) {
  let out = [deflateAttrName(name.chars), value.chars];
  return namespace && out.push(namespace), out;
}
function dynamicAttr({
  name: name,
  value: value,
  namespace: namespace
}) {
  let out = [deflateAttrName(name.chars), EXPR.expr(value)];
  return namespace && out.push(namespace), out;
}
function staticAttrOp(kind) {
  return kind.component ? opcodes.StaticComponentAttr : opcodes.StaticAttr;
}
const defaultId = (() => {
    const req = "object" == typeof module && "function" == typeof module.require ? module.require : globalThis.require;
    if (req) try {
      const crypto = req("crypto"),
        idFn = src => {
          const hash = crypto.createHash("sha1");
          // trim to 6 bytes of data (2^48 - 1)
          return hash.update(src, "utf8"), hash.digest("base64").substring(0, 8);
        };
      return idFn("test"), idFn;
    } catch {
      // do nothing
    }
    return function () {
      return null;
    };
  })(),
  defaultOptions = {
    id: defaultId
  };

/*
 * Compile a string into a template javascript string.
 *
 * Example usage:
 *     import { precompile } from '@glimmer/compiler';
 *     import { templateFactory } from 'glimmer-runtime';
 *     let templateJs = precompile("Howdy {{name}}");
 *     let factory = templateFactory(new Function("return " + templateJs)());
 *     let template = factory.create(env);
 *
 * @method precompile
 * @param {string} string a Glimmer template string
 * @return {string} a template javascript string
 */
function precompileJSON(string, options = defaultOptions) {
  const source = new api$2.Source(string ?? "", options.meta?.moduleName),
    [ast, locals] = normalize(source, {
      lexicalScope: () => !1,
      ...options
    }),
    block = function (source, root, isStrict) {
      // create a new context for the normalization pass
      let state = new NormalizationState(root.table, isStrict),
        template = VISIT_STMTS.visitList(root.body, state).mapOk(body => new Template({
          loc: root.loc,
          scope: root.table,
          body: body.toArray()
        }));
      return isStrict && (template = template.andThen(template => StrictModeValidationPass.validate(template))), template;
    }(0, ast, options.strictMode ?? !1).mapOk(pass2In => function (template) {
      let statements = CONTENT.list(template.body),
        scope = template.scope;
      return [statements, scope.symbols, scope.hasEval, scope.upvars];
    }(pass2In));
  if (block.isOk) return [block.value, locals];
  throw block.reason;
}

// UUID used as a unique placeholder for placing a snippet of JS code into
// the otherwise JSON stringified value below.
/*
 * Compile a string into a template javascript string.
 *
 * Example usage:
 *     import { precompile } from '@glimmer/compiler';
 *     import { templateFactory } from 'glimmer-runtime';
 *     let templateJs = precompile("Howdy {{name}}");
 *     let factory = templateFactory(new Function("return " + templateJs)());
 *     let template = factory.create(env);
 *
 * @method precompile
 * @param {string} string a Glimmer template string
 * @return {string} a template javascript string
 */
function precompile(source, options = defaultOptions) {
  const [block, usedLocals] = precompileJSON(source, options),
    moduleName = options.meta?.moduleName,
    idFn = options.id || defaultId,
    blockJSON = JSON.stringify(block),
    templateJSONObject = {
      id: idFn(JSON.stringify(options.meta) + blockJSON),
      block: blockJSON,
      moduleName: moduleName ?? "(unknown template module)",
      // lying to the type checker here because we're going to
      // replace it just below, after stringification
      scope: "796d24e6-2450-4fb0-8cdf-b65638b5ef70",
      isStrictMode: options.strictMode ?? !1
    };
  0 === usedLocals.length && delete templateJSONObject.scope;
  // JSON is javascript
  let stringified = JSON.stringify(templateJSONObject);
  if (usedLocals.length > 0) {
    const scopeFn = `()=>[${usedLocals.join(",")}]`;
    stringified = stringified.replace('"796d24e6-2450-4fb0-8cdf-b65638b5ef70"', scopeFn);
  }
  return stringified;
}

/**
 * All possible options passed to `template()` may specify a `moduleName`.
 */

/**
 * When using `template` in a class, you call it in a `static` block and pass
 * the class as the `component` option.
 *
 * ```ts
 * class MyComponent extends Component {
 *   static {
 *     template('{{this.greeting}}, {{@place}}!',
 *       { component: this },
 *       // explicit or implicit option goes here
 *     );
 *   }
 * }
 * ```
 *
 * For the full explicit form, see {@linkcode ExplicitClassOptions}. For the
 * full implicit form, see {@linkcode ImplicitClassOptions}.
 */

/**
 * When using `template` outside of a class (i.e. a "template-only component"), you can pass
 * a `scope` option that explicitly provides the lexical scope for the template.
 *
 * This is called the "explicit form".
 *
 * ```ts
 * const greeting = 'Hello';
 * const HelloWorld = template('{{greeting}} World!', { scope: () => ({ greeting }) });
 * ```
 */

/**
 * When using `template` *inside* a class (see
 * {@linkcode BaseClassTemplateOptions}), you can pass a `scope` option that
 * explicitly provides the lexical scope for the template, just like a template-only
 * component (see {@linkcode ExplicitTemplateOnlyOptions}).
 *
 * ```ts
 * class MyComponent extends Component {
 *   static {
 *     template('{{this.greeting}}, {{@place}}!',
 *       { component: this },
 *       // explicit or implicit option goes here
 *     );
 *   }
 * }
 * ```
 *
 * ## The Scope Function's `instance` Parameter
 *
 * However, the explicit `scope` function in a *class* also takes an `instance` option
 * that provides access to the component's instance.
 *
 * Once it's supported in Handlebars, this will make it possible to represent private
 * fields when using the explicit form.
 *
 * ```ts
 * class MyComponent extends Component {
 *   static {
 *     template('{{this.#greeting}}, {{@place}}!',
 *       { component: this },
 *       scope: (instance) => ({ '#greeting': instance.#greeting }),
 *     );
 *   }
 * }
 * ```
 */

/**
 * The *implicit* form of the `template` function takes an `eval` option that
 * allows the runtime compiler to evaluate local template variables without
 * needing to maintain an explicit list of the local variables used in the
 * template scope.
 *
 * The eval options *must* be passed in the following form:
 *
 * ```ts
 * {
 *   eval() { return eval(arguments[0]) }
 * }
 * ```
 *
 * ## Requirements of the `eval` Option
 *
 * **The syntactic form presented above is the only form you should use when
 * passing an `eval` option.**
 *
 * This is _required_ if you want your code to be compatible with the
 * compile-time implementation of `@ember/template-compiler`. While the runtime
 * compiler offers a tiny bit of additional wiggle room, you still need to follow
 * very strict rules.
 *
 * We don't recommend trying to memorize the rules. Instead, we recommend using
 * the snippet presented above and supported by the compile-time implementation.
 *
 * ### The Technical Requirements of the `eval` Option
 *
 * The `eval` function is passed a single parameter that is a JavaScript
 * identifier. This will be extended in the future to support private fields.
 *
 * Since keywords in JavaScript are contextual (e.g. `await` and `yield`), the
 * parameter might be a keyword. The `@ember/template-compiler/runtime` expects
 * the function to throw a `SyntaxError` if the identifier name is not valid in
 * the current scope. (The direct `eval` function takes care of this out of the
 * box.)
 *
 * Requirements:
 *
 * 1. The `eval` method must receive its parameter as `arguments[0]`, which
 *    ensures that the variable name passed to `eval()` is not shadowed by the
 *    function's parameter name.
 * 2. The `eval` option must be a function or concise method, and not an arrow.
 *    This is because arrows do not have their own `arguments`, which breaks
 *    (1).
 * 3. The `eval` method must call "*direct* `eval`", and not an alias of `eval`.
 *    Direct `eval` evaluates the code in the scope it was called from, while
 *    aliased versions of `eval` (including `new Function`) evaluate the code in
 *    the global scope.
 * 4. The `eval` method must return the result of calling "direct `eval`".
 *
 * The easiest way to achieve these requirements is to use the exact syntax
 * presented above. This is *also* the only way to be compatible
 *
 * ## Rationale
 *
 * This is useful for two reasons:
 *
 * 1. This form is a useful _intermediate_ form for the compile-time toolchain.
 *    It allows the content-tag preprocessor to convert the `<template>` syntax
 *    into valid JavaScript without needing to involve full-fledged lexical
 *    analysis.
 * 2. This form is a convenient form for manual prototyping when using the
 *    runtime compiler directly. While it requires some extra typing relative to
 *    `<template>`, it's a mechanical 1:1 transformation of the syntax.
 *
 * In practice, implementations that use a runtime compiler (for example, a
 * playground running completely in the browser) should probably use the
 * `content-tag` preprocessor to convert the template into the implicit form,
 * and then rely on `@ember/template-compiler/runtime` to evaluate the template.
 */

/**
 * When using `template` outside of a class (i.e. a "template-only component"), you can pass
 * an `eval` option that _implicitly_ provides the lexical scope for the template.
 *
 * This is called the "implicit form".
 *
 * ```ts
 * const greeting = 'Hello';
 * const HelloWorld = template('{{greeting}} World!', {
 *   eval() { return arguments[0] }
 * });
 * ```
 *
 * For more details on the requirements of the `eval` option, see {@linkcode ImplicitEvalOption}.
 */

/**
 * When using `template` inside of a class, you can pass an `eval` option that
 * _implicitly_ provides the lexical scope for the template, just as you can
 * with a {@linkcode ImplicitTemplateOnlyOptions | template-only component}.
 *
 * This is called the "implicit form".
 *
 * ```ts
 * class MyComponent extends Component {
 *   static {
 *     template('{{this.greeting}}, {{@place}}!',
 *       { component: this },
 *       eval() { return arguments[0] }
 *     );
 *   }
 * }
 * ```
 *
 * ## Note  on Private Fields
 *
 * The current implementation of `@ember/template-compiler` does not support
 * private fields, but once the Handlebars parser adds support for private field
 * syntax and it's implemented in the Glimmer compiler, the implicit form should
 * be able to support them.
 */

function template(templateString, providedOptions) {
  const options = {
    strictMode: true,
    ...providedOptions
  };
  const evaluate = buildEvaluator(options);
  const normalizedOptions = compileOptions(options);
  const component = normalizedOptions.component ?? templateOnly();
  queueMicrotask(() => {
    const source = precompile(templateString, normalizedOptions);
    const template = templateFactory(evaluate(`(${source})`));
    setComponentTemplate(template, component);
  });
  return component;
}
const evaluator = source => {
  return new Function(`return  ${source}`)();
};
function buildEvaluator(options) {
  if (options === undefined) {
    return evaluator;
  }
  if (options.eval) {
    return options.eval;
  } else {
    const scope = options.scope?.();
    if (!scope) {
      return evaluator;
    }
    return source => {
      const argNames = Object.keys(scope);
      const argValues = Object.values(scope);
      return new Function(...argNames, `return (${source})`)(...argValues);
    };
  }
}

export { template };