import { EmbeddedActionsParser } from "../chevrotain/lib-src-parse-parser-parser.js";
import "../chevrotain/lib-src-scan-lexer_public.js";
import "../chevrotain/lib-src-scan-tokens_public.js";
import "../chevrotain/lib-src-parse-grammar-lookahead.js";
import { LLkLookaheadStrategy } from "../chevrotain/lib-src-parse-grammar-llk_lookahead.js";
import { defaultParserErrorProvider } from "../chevrotain/lib-src-parse-errors_public.js";
import "../chevrotain/lib-src-parse-exceptions_public.js";
import { LLStarLookaheadStrategy } from "../chevrotain-allstar/lib-all-star-lookahead.js";
import { isKeyword, isAssignment, isCrossReference } from "./lib-languages-generated-ast.js";
import { isDataTypeRule, getExplicitRuleType } from "./lib-utils-grammar-utils.js";
import { linkContentToContainer, assignMandatoryProperties, getContainerOfType } from "./lib-utils-ast-utils.js";
import { CstNodeBuilder } from "./lib-parser-cst-node-builder.js";
const DatatypeSymbol = /* @__PURE__ */ Symbol("Datatype");
function isDataTypeNode(node) {
  return node.$type === DatatypeSymbol;
}
const ruleSuffix = "​";
const withRuleSuffix = (name) => name.endsWith(ruleSuffix) ? name : name + ruleSuffix;
class AbstractLangiumParser {
  constructor(services) {
    this._unorderedGroups = /* @__PURE__ */ new Map();
    this.allRules = /* @__PURE__ */ new Map();
    this.lexer = services.parser.Lexer;
    const tokens = this.lexer.definition;
    const production = services.LanguageMetaData.mode === "production";
    this.wrapper = new ChevrotainWrapper(tokens, Object.assign(Object.assign({}, services.parser.ParserConfig), { skipValidations: production, errorMessageProvider: services.parser.ParserErrorMessageProvider }));
  }
  alternatives(idx, choices) {
    this.wrapper.wrapOr(idx, choices);
  }
  optional(idx, callback) {
    this.wrapper.wrapOption(idx, callback);
  }
  many(idx, callback) {
    this.wrapper.wrapMany(idx, callback);
  }
  atLeastOne(idx, callback) {
    this.wrapper.wrapAtLeastOne(idx, callback);
  }
  getRule(name) {
    return this.allRules.get(name);
  }
  isRecording() {
    return this.wrapper.IS_RECORDING;
  }
  get unorderedGroups() {
    return this._unorderedGroups;
  }
  getRuleStack() {
    return this.wrapper.RULE_STACK;
  }
  finalize() {
    this.wrapper.wrapSelfAnalysis();
  }
}
class LangiumParser extends AbstractLangiumParser {
  get current() {
    return this.stack[this.stack.length - 1];
  }
  constructor(services) {
    super(services);
    this.nodeBuilder = new CstNodeBuilder();
    this.stack = [];
    this.assignmentMap = /* @__PURE__ */ new Map();
    this.linker = services.references.Linker;
    this.converter = services.parser.ValueConverter;
    this.astReflection = services.shared.AstReflection;
  }
  rule(rule, impl) {
    const type = this.computeRuleType(rule);
    const ruleMethod = this.wrapper.DEFINE_RULE(withRuleSuffix(rule.name), this.startImplementation(type, impl).bind(this));
    this.allRules.set(rule.name, ruleMethod);
    if (rule.entry) {
      this.mainRule = ruleMethod;
    }
    return ruleMethod;
  }
  computeRuleType(rule) {
    if (rule.fragment) {
      return void 0;
    } else if (isDataTypeRule(rule)) {
      return DatatypeSymbol;
    } else {
      const explicit = getExplicitRuleType(rule);
      return explicit !== null && explicit !== void 0 ? explicit : rule.name;
    }
  }
  parse(input, options = {}) {
    this.nodeBuilder.buildRootNode(input);
    const lexerResult = this.lexerResult = this.lexer.tokenize(input);
    this.wrapper.input = lexerResult.tokens;
    const ruleMethod = options.rule ? this.allRules.get(options.rule) : this.mainRule;
    if (!ruleMethod) {
      throw new Error(options.rule ? `No rule found with name '${options.rule}'` : "No main rule available.");
    }
    const result = ruleMethod.call(this.wrapper, {});
    this.nodeBuilder.addHiddenNodes(lexerResult.hidden);
    this.unorderedGroups.clear();
    this.lexerResult = void 0;
    return {
      value: result,
      lexerErrors: lexerResult.errors,
      lexerReport: lexerResult.report,
      parserErrors: this.wrapper.errors
    };
  }
  startImplementation($type, implementation) {
    return (args) => {
      const createNode = !this.isRecording() && $type !== void 0;
      if (createNode) {
        const node = { $type };
        this.stack.push(node);
        if ($type === DatatypeSymbol) {
          node.value = "";
        }
      }
      let result;
      try {
        result = implementation(args);
      } catch (err) {
        result = void 0;
      }
      if (result === void 0 && createNode) {
        result = this.construct();
      }
      return result;
    };
  }
  extractHiddenTokens(token) {
    const hiddenTokens = this.lexerResult.hidden;
    if (!hiddenTokens.length) {
      return [];
    }
    const offset = token.startOffset;
    for (let i = 0; i < hiddenTokens.length; i++) {
      const token2 = hiddenTokens[i];
      if (token2.startOffset > offset) {
        return hiddenTokens.splice(0, i);
      }
    }
    return hiddenTokens.splice(0, hiddenTokens.length);
  }
  consume(idx, tokenType, feature) {
    const token = this.wrapper.wrapConsume(idx, tokenType);
    if (!this.isRecording() && this.isValidToken(token)) {
      const hiddenTokens = this.extractHiddenTokens(token);
      this.nodeBuilder.addHiddenNodes(hiddenTokens);
      const leafNode = this.nodeBuilder.buildLeafNode(token, feature);
      const { assignment, isCrossRef } = this.getAssignment(feature);
      const current = this.current;
      if (assignment) {
        const convertedValue = isKeyword(feature) ? token.image : this.converter.convert(token.image, leafNode);
        this.assign(assignment.operator, assignment.feature, convertedValue, leafNode, isCrossRef);
      } else if (isDataTypeNode(current)) {
        let text = token.image;
        if (!isKeyword(feature)) {
          text = this.converter.convert(text, leafNode).toString();
        }
        current.value += text;
      }
    }
  }
  /**
   * Most consumed parser tokens are valid. However there are two cases in which they are not valid:
   *
   * 1. They were inserted during error recovery by the parser. These tokens don't really exist and should not be further processed
   * 2. They contain invalid token ranges. This might include the special EOF token, or other tokens produced by invalid token builders.
   */
  isValidToken(token) {
    return !token.isInsertedInRecovery && !isNaN(token.startOffset) && typeof token.endOffset === "number" && !isNaN(token.endOffset);
  }
  subrule(idx, rule, fragment, feature, args) {
    let cstNode;
    if (!this.isRecording() && !fragment) {
      cstNode = this.nodeBuilder.buildCompositeNode(feature);
    }
    const subruleResult = this.wrapper.wrapSubrule(idx, rule, args);
    if (!this.isRecording() && cstNode && cstNode.length > 0) {
      this.performSubruleAssignment(subruleResult, feature, cstNode);
    }
  }
  performSubruleAssignment(result, feature, cstNode) {
    const { assignment, isCrossRef } = this.getAssignment(feature);
    if (assignment) {
      this.assign(assignment.operator, assignment.feature, result, cstNode, isCrossRef);
    } else if (!assignment) {
      const current = this.current;
      if (isDataTypeNode(current)) {
        current.value += result.toString();
      } else if (typeof result === "object" && result) {
        const object = this.assignWithoutOverride(result, current);
        const newItem = object;
        this.stack.pop();
        this.stack.push(newItem);
      }
    }
  }
  action($type, action) {
    if (!this.isRecording()) {
      let last = this.current;
      if (action.feature && action.operator) {
        last = this.construct();
        this.nodeBuilder.removeNode(last.$cstNode);
        const node = this.nodeBuilder.buildCompositeNode(action);
        node.content.push(last.$cstNode);
        const newItem = { $type };
        this.stack.push(newItem);
        this.assign(action.operator, action.feature, last, last.$cstNode, false);
      } else {
        last.$type = $type;
      }
    }
  }
  construct() {
    if (this.isRecording()) {
      return void 0;
    }
    const obj = this.current;
    linkContentToContainer(obj);
    this.nodeBuilder.construct(obj);
    this.stack.pop();
    if (isDataTypeNode(obj)) {
      return this.converter.convert(obj.value, obj.$cstNode);
    } else {
      assignMandatoryProperties(this.astReflection, obj);
    }
    return obj;
  }
  getAssignment(feature) {
    if (!this.assignmentMap.has(feature)) {
      const assignment = getContainerOfType(feature, isAssignment);
      this.assignmentMap.set(feature, {
        assignment,
        isCrossRef: assignment ? isCrossReference(assignment.terminal) : false
      });
    }
    return this.assignmentMap.get(feature);
  }
  assign(operator, feature, value, cstNode, isCrossRef) {
    const obj = this.current;
    let item;
    if (isCrossRef && typeof value === "string") {
      item = this.linker.buildReference(obj, feature, cstNode, value);
    } else {
      item = value;
    }
    switch (operator) {
      case "=": {
        obj[feature] = item;
        break;
      }
      case "?=": {
        obj[feature] = true;
        break;
      }
      case "+=": {
        if (!Array.isArray(obj[feature])) {
          obj[feature] = [];
        }
        obj[feature].push(item);
      }
    }
  }
  assignWithoutOverride(target, source) {
    for (const [name, existingValue] of Object.entries(source)) {
      const newValue = target[name];
      if (newValue === void 0) {
        target[name] = existingValue;
      } else if (Array.isArray(newValue) && Array.isArray(existingValue)) {
        existingValue.push(...newValue);
        target[name] = existingValue;
      }
    }
    const targetCstNode = target.$cstNode;
    if (targetCstNode) {
      targetCstNode.astNode = void 0;
      target.$cstNode = void 0;
    }
    return target;
  }
  get definitionErrors() {
    return this.wrapper.definitionErrors;
  }
}
class AbstractParserErrorMessageProvider {
  buildMismatchTokenMessage(options) {
    return defaultParserErrorProvider.buildMismatchTokenMessage(options);
  }
  buildNotAllInputParsedMessage(options) {
    return defaultParserErrorProvider.buildNotAllInputParsedMessage(options);
  }
  buildNoViableAltMessage(options) {
    return defaultParserErrorProvider.buildNoViableAltMessage(options);
  }
  buildEarlyExitMessage(options) {
    return defaultParserErrorProvider.buildEarlyExitMessage(options);
  }
}
class LangiumParserErrorMessageProvider extends AbstractParserErrorMessageProvider {
  buildMismatchTokenMessage({ expected, actual }) {
    const expectedMsg = expected.LABEL ? "`" + expected.LABEL + "`" : expected.name.endsWith(":KW") ? `keyword '${expected.name.substring(0, expected.name.length - 3)}'` : `token of type '${expected.name}'`;
    return `Expecting ${expectedMsg} but found \`${actual.image}\`.`;
  }
  buildNotAllInputParsedMessage({ firstRedundant }) {
    return `Expecting end of file but found \`${firstRedundant.image}\`.`;
  }
}
class LangiumCompletionParser extends AbstractLangiumParser {
  constructor() {
    super(...arguments);
    this.tokens = [];
    this.elementStack = [];
    this.lastElementStack = [];
    this.nextTokenIndex = 0;
    this.stackSize = 0;
  }
  action() {
  }
  construct() {
    return void 0;
  }
  parse(input) {
    this.resetState();
    const tokens = this.lexer.tokenize(input, { mode: "partial" });
    this.tokens = tokens.tokens;
    this.wrapper.input = [...this.tokens];
    this.mainRule.call(this.wrapper, {});
    this.unorderedGroups.clear();
    return {
      tokens: this.tokens,
      elementStack: [...this.lastElementStack],
      tokenIndex: this.nextTokenIndex
    };
  }
  rule(rule, impl) {
    const ruleMethod = this.wrapper.DEFINE_RULE(withRuleSuffix(rule.name), this.startImplementation(impl).bind(this));
    this.allRules.set(rule.name, ruleMethod);
    if (rule.entry) {
      this.mainRule = ruleMethod;
    }
    return ruleMethod;
  }
  resetState() {
    this.elementStack = [];
    this.lastElementStack = [];
    this.nextTokenIndex = 0;
    this.stackSize = 0;
  }
  startImplementation(implementation) {
    return (args) => {
      const size = this.keepStackSize();
      try {
        implementation(args);
      } finally {
        this.resetStackSize(size);
      }
    };
  }
  removeUnexpectedElements() {
    this.elementStack.splice(this.stackSize);
  }
  keepStackSize() {
    const size = this.elementStack.length;
    this.stackSize = size;
    return size;
  }
  resetStackSize(size) {
    this.removeUnexpectedElements();
    this.stackSize = size;
  }
  consume(idx, tokenType, feature) {
    this.wrapper.wrapConsume(idx, tokenType);
    if (!this.isRecording()) {
      this.lastElementStack = [...this.elementStack, feature];
      this.nextTokenIndex = this.currIdx + 1;
    }
  }
  subrule(idx, rule, fragment, feature, args) {
    this.before(feature);
    this.wrapper.wrapSubrule(idx, rule, args);
    this.after(feature);
  }
  before(element) {
    if (!this.isRecording()) {
      this.elementStack.push(element);
    }
  }
  after(element) {
    if (!this.isRecording()) {
      const index = this.elementStack.lastIndexOf(element);
      if (index >= 0) {
        this.elementStack.splice(index);
      }
    }
  }
  get currIdx() {
    return this.wrapper.currIdx;
  }
}
const defaultConfig = {
  recoveryEnabled: true,
  nodeLocationTracking: "full",
  skipValidations: true,
  errorMessageProvider: new LangiumParserErrorMessageProvider()
};
class ChevrotainWrapper extends EmbeddedActionsParser {
  constructor(tokens, config) {
    const useDefaultLookahead = config && "maxLookahead" in config;
    super(tokens, Object.assign(Object.assign(Object.assign({}, defaultConfig), { lookaheadStrategy: useDefaultLookahead ? new LLkLookaheadStrategy({ maxLookahead: config.maxLookahead }) : new LLStarLookaheadStrategy({
      // If validations are skipped, don't log the lookahead warnings
      logging: config.skipValidations ? () => {
      } : void 0
    }) }), config));
  }
  get IS_RECORDING() {
    return this.RECORDING_PHASE;
  }
  DEFINE_RULE(name, impl) {
    return this.RULE(name, impl);
  }
  wrapSelfAnalysis() {
    this.performSelfAnalysis();
  }
  wrapConsume(idx, tokenType) {
    return this.consume(idx, tokenType);
  }
  wrapSubrule(idx, rule, args) {
    return this.subrule(idx, rule, {
      ARGS: [args]
    });
  }
  wrapOr(idx, choices) {
    this.or(idx, choices);
  }
  wrapOption(idx, callback) {
    this.option(idx, callback);
  }
  wrapMany(idx, callback) {
    this.many(idx, callback);
  }
  wrapAtLeastOne(idx, callback) {
    this.atLeastOne(idx, callback);
  }
}
export {
  AbstractLangiumParser,
  AbstractParserErrorMessageProvider,
  DatatypeSymbol,
  LangiumCompletionParser,
  LangiumParser,
  LangiumParserErrorMessageProvider
};
//# sourceMappingURL=lib-parser-langium-parser.js.map
