"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const parser = require("./lib-src-parse-parser-parser.cjs");
const model = require("../@chevrotain/gast/lib-src-model.cjs");
const visitor = require("../@chevrotain/gast/lib-src-visitor.cjs");
const helpers = require("../@chevrotain/gast/lib-src-helpers.cjs");
const lookahead = require("./lib-src-parse-grammar-lookahead.cjs");
const interpreter = require("./lib-src-parse-grammar-interpreter.cjs");
const tokens = require("./lib-src-scan-tokens.cjs");
const forEach = require("../lodash-es/forEach2.cjs");
const map = require("../lodash-es/map2.cjs");
const flatMap = require("../lodash-es/flatMap2.cjs");
const groupBy = require("../lodash-es/groupBy.cjs");
const pickBy = require("../lodash-es/pickBy.cjs");
const head = require("../lodash-es/head.cjs");
const values = require("../lodash-es/values2.cjs");
const reduce = require("../lodash-es/reduce2.cjs");
const isEmpty = require("../lodash-es/isEmpty2.cjs");
const flatten = require("../lodash-es/flatten2.cjs");
const includes = require("../lodash-es/includes.cjs");
const difference = require("../lodash-es/difference.cjs");
const clone = require("../lodash-es/clone2.cjs");
const dropRight = require("../lodash-es/dropRight.cjs");
const reject = require("../lodash-es/reject.cjs");
const drop = require("../lodash-es/drop.cjs");
const compact = require("../lodash-es/compact.cjs");
const filter = require("../lodash-es/filter2.cjs");
function validateLookahead(options) {
  const lookaheadValidationErrorMessages = options.lookaheadStrategy.validate({
    rules: options.rules,
    tokenTypes: options.tokenTypes,
    grammarName: options.grammarName
  });
  return map.default(lookaheadValidationErrorMessages, (errorMessage) => Object.assign({ type: parser.ParserDefinitionErrorType.CUSTOM_LOOKAHEAD_VALIDATION }, errorMessage));
}
function validateGrammar(topLevels, tokenTypes, errMsgProvider, grammarName) {
  const duplicateErrors = flatMap.default(topLevels, (currTopLevel) => validateDuplicateProductions(currTopLevel, errMsgProvider));
  const termsNamespaceConflictErrors = checkTerminalAndNoneTerminalsNameSpace(topLevels, tokenTypes, errMsgProvider);
  const tooManyAltsErrors = flatMap.default(topLevels, (curRule) => validateTooManyAlts(curRule, errMsgProvider));
  const duplicateRulesError = flatMap.default(topLevels, (curRule) => validateRuleDoesNotAlreadyExist(curRule, topLevels, grammarName, errMsgProvider));
  return duplicateErrors.concat(termsNamespaceConflictErrors, tooManyAltsErrors, duplicateRulesError);
}
function validateDuplicateProductions(topLevelRule, errMsgProvider) {
  const collectorVisitor = new OccurrenceValidationCollector();
  topLevelRule.accept(collectorVisitor);
  const allRuleProductions = collectorVisitor.allProductions;
  const productionGroups = groupBy.default(allRuleProductions, identifyProductionForDuplicates);
  const duplicates = pickBy.default(productionGroups, (currGroup) => {
    return currGroup.length > 1;
  });
  const errors = map.default(values.default(duplicates), (currDuplicates) => {
    const firstProd = head.default(currDuplicates);
    const msg = errMsgProvider.buildDuplicateFoundError(topLevelRule, currDuplicates);
    const dslName = helpers.getProductionDslName(firstProd);
    const defError = {
      message: msg,
      type: parser.ParserDefinitionErrorType.DUPLICATE_PRODUCTIONS,
      ruleName: topLevelRule.name,
      dslName,
      occurrence: firstProd.idx
    };
    const param = getExtraProductionArgument(firstProd);
    if (param) {
      defError.parameter = param;
    }
    return defError;
  });
  return errors;
}
function identifyProductionForDuplicates(prod) {
  return `${helpers.getProductionDslName(prod)}_#_${prod.idx}_#_${getExtraProductionArgument(prod)}`;
}
function getExtraProductionArgument(prod) {
  if (prod instanceof model.Terminal) {
    return prod.terminalType.name;
  } else if (prod instanceof model.NonTerminal) {
    return prod.nonTerminalName;
  } else {
    return "";
  }
}
class OccurrenceValidationCollector extends visitor.GAstVisitor {
  constructor() {
    super(...arguments);
    this.allProductions = [];
  }
  visitNonTerminal(subrule) {
    this.allProductions.push(subrule);
  }
  visitOption(option) {
    this.allProductions.push(option);
  }
  visitRepetitionWithSeparator(manySep) {
    this.allProductions.push(manySep);
  }
  visitRepetitionMandatory(atLeastOne) {
    this.allProductions.push(atLeastOne);
  }
  visitRepetitionMandatoryWithSeparator(atLeastOneSep) {
    this.allProductions.push(atLeastOneSep);
  }
  visitRepetition(many) {
    this.allProductions.push(many);
  }
  visitAlternation(or) {
    this.allProductions.push(or);
  }
  visitTerminal(terminal) {
    this.allProductions.push(terminal);
  }
}
function validateRuleDoesNotAlreadyExist(rule, allRules, className, errMsgProvider) {
  const errors = [];
  const occurrences = reduce.default(allRules, (result, curRule) => {
    if (curRule.name === rule.name) {
      return result + 1;
    }
    return result;
  }, 0);
  if (occurrences > 1) {
    const errMsg = errMsgProvider.buildDuplicateRuleNameError({
      topLevelRule: rule,
      grammarName: className
    });
    errors.push({
      message: errMsg,
      type: parser.ParserDefinitionErrorType.DUPLICATE_RULE_NAME,
      ruleName: rule.name
    });
  }
  return errors;
}
function validateRuleIsOverridden(ruleName, definedRulesNames, className) {
  const errors = [];
  let errMsg;
  if (!includes.default(definedRulesNames, ruleName)) {
    errMsg = `Invalid rule override, rule: ->${ruleName}<- cannot be overridden in the grammar: ->${className}<-as it is not defined in any of the super grammars `;
    errors.push({
      message: errMsg,
      type: parser.ParserDefinitionErrorType.INVALID_RULE_OVERRIDE,
      ruleName
    });
  }
  return errors;
}
function validateNoLeftRecursion(topRule, currRule, errMsgProvider, path = []) {
  const errors = [];
  const nextNonTerminals = getFirstNoneTerminal(currRule.definition);
  if (isEmpty.default(nextNonTerminals)) {
    return [];
  } else {
    const ruleName = topRule.name;
    const foundLeftRecursion = includes.default(nextNonTerminals, topRule);
    if (foundLeftRecursion) {
      errors.push({
        message: errMsgProvider.buildLeftRecursionError({
          topLevelRule: topRule,
          leftRecursionPath: path
        }),
        type: parser.ParserDefinitionErrorType.LEFT_RECURSION,
        ruleName
      });
    }
    const validNextSteps = difference.default(nextNonTerminals, path.concat([topRule]));
    const errorsFromNextSteps = flatMap.default(validNextSteps, (currRefRule) => {
      const newPath = clone.default(path);
      newPath.push(currRefRule);
      return validateNoLeftRecursion(topRule, currRefRule, errMsgProvider, newPath);
    });
    return errors.concat(errorsFromNextSteps);
  }
}
function getFirstNoneTerminal(definition) {
  let result = [];
  if (isEmpty.default(definition)) {
    return result;
  }
  const firstProd = head.default(definition);
  if (firstProd instanceof model.NonTerminal) {
    result.push(firstProd.referencedRule);
  } else if (firstProd instanceof model.Alternative || firstProd instanceof model.Option || firstProd instanceof model.RepetitionMandatory || firstProd instanceof model.RepetitionMandatoryWithSeparator || firstProd instanceof model.RepetitionWithSeparator || firstProd instanceof model.Repetition) {
    result = result.concat(getFirstNoneTerminal(firstProd.definition));
  } else if (firstProd instanceof model.Alternation) {
    result = flatten.default(map.default(firstProd.definition, (currSubDef) => getFirstNoneTerminal(currSubDef.definition)));
  } else if (firstProd instanceof model.Terminal) ;
  else {
    throw Error("non exhaustive match");
  }
  const isFirstOptional = helpers.isOptionalProd(firstProd);
  const hasMore = definition.length > 1;
  if (isFirstOptional && hasMore) {
    const rest = drop.default(definition);
    return result.concat(getFirstNoneTerminal(rest));
  } else {
    return result;
  }
}
class OrCollector extends visitor.GAstVisitor {
  constructor() {
    super(...arguments);
    this.alternations = [];
  }
  visitAlternation(node) {
    this.alternations.push(node);
  }
}
function validateEmptyOrAlternative(topLevelRule, errMsgProvider) {
  const orCollector = new OrCollector();
  topLevelRule.accept(orCollector);
  const ors = orCollector.alternations;
  const errors = flatMap.default(ors, (currOr) => {
    const exceptLast = dropRight.default(currOr.definition);
    return flatMap.default(exceptLast, (currAlternative, currAltIdx) => {
      const possibleFirstInAlt = interpreter.nextPossibleTokensAfter([currAlternative], [], tokens.tokenStructuredMatcher, 1);
      if (isEmpty.default(possibleFirstInAlt)) {
        return [
          {
            message: errMsgProvider.buildEmptyAlternationError({
              topLevelRule,
              alternation: currOr,
              emptyChoiceIdx: currAltIdx
            }),
            type: parser.ParserDefinitionErrorType.NONE_LAST_EMPTY_ALT,
            ruleName: topLevelRule.name,
            occurrence: currOr.idx,
            alternative: currAltIdx + 1
          }
        ];
      } else {
        return [];
      }
    });
  });
  return errors;
}
function validateAmbiguousAlternationAlternatives(topLevelRule, globalMaxLookahead, errMsgProvider) {
  const orCollector = new OrCollector();
  topLevelRule.accept(orCollector);
  let ors = orCollector.alternations;
  ors = reject.default(ors, (currOr) => currOr.ignoreAmbiguities === true);
  const errors = flatMap.default(ors, (currOr) => {
    const currOccurrence = currOr.idx;
    const actualMaxLookahead = currOr.maxLookahead || globalMaxLookahead;
    const alternatives = lookahead.getLookaheadPathsForOr(currOccurrence, topLevelRule, actualMaxLookahead, currOr);
    const altsAmbiguityErrors = checkAlternativesAmbiguities(alternatives, currOr, topLevelRule, errMsgProvider);
    const altsPrefixAmbiguityErrors = checkPrefixAlternativesAmbiguities(alternatives, currOr, topLevelRule, errMsgProvider);
    return altsAmbiguityErrors.concat(altsPrefixAmbiguityErrors);
  });
  return errors;
}
class RepetitionCollector extends visitor.GAstVisitor {
  constructor() {
    super(...arguments);
    this.allProductions = [];
  }
  visitRepetitionWithSeparator(manySep) {
    this.allProductions.push(manySep);
  }
  visitRepetitionMandatory(atLeastOne) {
    this.allProductions.push(atLeastOne);
  }
  visitRepetitionMandatoryWithSeparator(atLeastOneSep) {
    this.allProductions.push(atLeastOneSep);
  }
  visitRepetition(many) {
    this.allProductions.push(many);
  }
}
function validateTooManyAlts(topLevelRule, errMsgProvider) {
  const orCollector = new OrCollector();
  topLevelRule.accept(orCollector);
  const ors = orCollector.alternations;
  const errors = flatMap.default(ors, (currOr) => {
    if (currOr.definition.length > 255) {
      return [
        {
          message: errMsgProvider.buildTooManyAlternativesError({
            topLevelRule,
            alternation: currOr
          }),
          type: parser.ParserDefinitionErrorType.TOO_MANY_ALTS,
          ruleName: topLevelRule.name,
          occurrence: currOr.idx
        }
      ];
    } else {
      return [];
    }
  });
  return errors;
}
function validateSomeNonEmptyLookaheadPath(topLevelRules, maxLookahead, errMsgProvider) {
  const errors = [];
  forEach.default(topLevelRules, (currTopRule) => {
    const collectorVisitor = new RepetitionCollector();
    currTopRule.accept(collectorVisitor);
    const allRuleProductions = collectorVisitor.allProductions;
    forEach.default(allRuleProductions, (currProd) => {
      const prodType = lookahead.getProdType(currProd);
      const actualMaxLookahead = currProd.maxLookahead || maxLookahead;
      const currOccurrence = currProd.idx;
      const paths = lookahead.getLookaheadPathsForOptionalProd(currOccurrence, currTopRule, prodType, actualMaxLookahead);
      const pathsInsideProduction = paths[0];
      if (isEmpty.default(flatten.default(pathsInsideProduction))) {
        const errMsg = errMsgProvider.buildEmptyRepetitionError({
          topLevelRule: currTopRule,
          repetition: currProd
        });
        errors.push({
          message: errMsg,
          type: parser.ParserDefinitionErrorType.NO_NON_EMPTY_LOOKAHEAD,
          ruleName: currTopRule.name
        });
      }
    });
  });
  return errors;
}
function checkAlternativesAmbiguities(alternatives, alternation, rule, errMsgProvider) {
  const foundAmbiguousPaths = [];
  const identicalAmbiguities = reduce.default(alternatives, (result, currAlt, currAltIdx) => {
    if (alternation.definition[currAltIdx].ignoreAmbiguities === true) {
      return result;
    }
    forEach.default(currAlt, (currPath) => {
      const altsCurrPathAppearsIn = [currAltIdx];
      forEach.default(alternatives, (currOtherAlt, currOtherAltIdx) => {
        if (currAltIdx !== currOtherAltIdx && lookahead.containsPath(currOtherAlt, currPath) && // ignore (skip) ambiguities with this "other" alternative
        alternation.definition[currOtherAltIdx].ignoreAmbiguities !== true) {
          altsCurrPathAppearsIn.push(currOtherAltIdx);
        }
      });
      if (altsCurrPathAppearsIn.length > 1 && !lookahead.containsPath(foundAmbiguousPaths, currPath)) {
        foundAmbiguousPaths.push(currPath);
        result.push({
          alts: altsCurrPathAppearsIn,
          path: currPath
        });
      }
    });
    return result;
  }, []);
  const currErrors = map.default(identicalAmbiguities, (currAmbDescriptor) => {
    const ambgIndices = map.default(currAmbDescriptor.alts, (currAltIdx) => currAltIdx + 1);
    const currMessage = errMsgProvider.buildAlternationAmbiguityError({
      topLevelRule: rule,
      alternation,
      ambiguityIndices: ambgIndices,
      prefixPath: currAmbDescriptor.path
    });
    return {
      message: currMessage,
      type: parser.ParserDefinitionErrorType.AMBIGUOUS_ALTS,
      ruleName: rule.name,
      occurrence: alternation.idx,
      alternatives: currAmbDescriptor.alts
    };
  });
  return currErrors;
}
function checkPrefixAlternativesAmbiguities(alternatives, alternation, rule, errMsgProvider) {
  const pathsAndIndices = reduce.default(alternatives, (result, currAlt, idx) => {
    const currPathsAndIdx = map.default(currAlt, (currPath) => {
      return { idx, path: currPath };
    });
    return result.concat(currPathsAndIdx);
  }, []);
  const errors = compact.default(flatMap.default(pathsAndIndices, (currPathAndIdx) => {
    const alternativeGast = alternation.definition[currPathAndIdx.idx];
    if (alternativeGast.ignoreAmbiguities === true) {
      return [];
    }
    const targetIdx = currPathAndIdx.idx;
    const targetPath = currPathAndIdx.path;
    const prefixAmbiguitiesPathsAndIndices = filter.default(pathsAndIndices, (searchPathAndIdx) => {
      return (
        // ignore (skip) ambiguities with this "other" alternative
        alternation.definition[searchPathAndIdx.idx].ignoreAmbiguities !== true && searchPathAndIdx.idx < targetIdx && // checking for strict prefix because identical lookaheads
        // will be be detected using a different validation.
        lookahead.isStrictPrefixOfPath(searchPathAndIdx.path, targetPath)
      );
    });
    const currPathPrefixErrors = map.default(prefixAmbiguitiesPathsAndIndices, (currAmbPathAndIdx) => {
      const ambgIndices = [currAmbPathAndIdx.idx + 1, targetIdx + 1];
      const occurrence = alternation.idx === 0 ? "" : alternation.idx;
      const message = errMsgProvider.buildAlternationPrefixAmbiguityError({
        topLevelRule: rule,
        alternation,
        ambiguityIndices: ambgIndices,
        prefixPath: currAmbPathAndIdx.path
      });
      return {
        message,
        type: parser.ParserDefinitionErrorType.AMBIGUOUS_PREFIX_ALTS,
        ruleName: rule.name,
        occurrence,
        alternatives: ambgIndices
      };
    });
    return currPathPrefixErrors;
  }));
  return errors;
}
function checkTerminalAndNoneTerminalsNameSpace(topLevels, tokenTypes, errMsgProvider) {
  const errors = [];
  const tokenNames = map.default(tokenTypes, (currToken) => currToken.name);
  forEach.default(topLevels, (currRule) => {
    const currRuleName = currRule.name;
    if (includes.default(tokenNames, currRuleName)) {
      const errMsg = errMsgProvider.buildNamespaceConflictError(currRule);
      errors.push({
        message: errMsg,
        type: parser.ParserDefinitionErrorType.CONFLICT_TOKENS_RULES_NAMESPACE,
        ruleName: currRuleName
      });
    }
  });
  return errors;
}
exports.OccurrenceValidationCollector = OccurrenceValidationCollector;
exports.RepetitionCollector = RepetitionCollector;
exports.checkPrefixAlternativesAmbiguities = checkPrefixAlternativesAmbiguities;
exports.getFirstNoneTerminal = getFirstNoneTerminal;
exports.identifyProductionForDuplicates = identifyProductionForDuplicates;
exports.validateAmbiguousAlternationAlternatives = validateAmbiguousAlternationAlternatives;
exports.validateEmptyOrAlternative = validateEmptyOrAlternative;
exports.validateGrammar = validateGrammar;
exports.validateLookahead = validateLookahead;
exports.validateNoLeftRecursion = validateNoLeftRecursion;
exports.validateRuleDoesNotAlreadyExist = validateRuleDoesNotAlreadyExist;
exports.validateRuleIsOverridden = validateRuleIsOverridden;
exports.validateSomeNonEmptyLookaheadPath = validateSomeNonEmptyLookaheadPath;
exports.validateTooManyAlts = validateTooManyAlts;
//# sourceMappingURL=lib-src-parse-grammar-checks.cjs.map
