"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const parser = require("../chevrotain/lib-src-parse-parser-parser.cjs");
require("../chevrotain/lib-src-scan-lexer_public.cjs");
const tokens_public = require("../chevrotain/lib-src-scan-tokens_public.cjs");
require("../chevrotain/lib-src-parse-grammar-lookahead.cjs");
require("../chevrotain/lib-src-parse-errors_public.cjs");
require("../chevrotain/lib-src-parse-exceptions_public.cjs");
const ast = require("./lib-languages-generated-ast.cjs");
const errors = require("./lib-utils-errors.cjs");
const stream = require("./lib-utils-stream.cjs");
const grammarUtils = require("./lib-utils-grammar-utils.cjs");
function createParser(grammar, parser2, tokens) {
  const parserContext = {
    parser: parser2,
    tokens,
    ruleNames: /* @__PURE__ */ new Map()
  };
  buildRules(parserContext, grammar);
  return parser2;
}
function buildRules(parserContext, grammar) {
  const reachable = grammarUtils.getAllReachableRules(grammar, false);
  const parserRules = stream.stream(grammar.rules).filter(ast.isParserRule).filter((rule) => reachable.has(rule));
  for (const rule of parserRules) {
    const ctx = Object.assign(Object.assign({}, parserContext), { consume: 1, optional: 1, subrule: 1, many: 1, or: 1 });
    parserContext.parser.rule(rule, buildElement(ctx, rule.definition));
  }
}
function buildElement(ctx, element, ignoreGuard = false) {
  let method;
  if (ast.isKeyword(element)) {
    method = buildKeyword(ctx, element);
  } else if (ast.isAction(element)) {
    method = buildAction(ctx, element);
  } else if (ast.isAssignment(element)) {
    method = buildElement(ctx, element.terminal);
  } else if (ast.isCrossReference(element)) {
    method = buildCrossReference(ctx, element);
  } else if (ast.isRuleCall(element)) {
    method = buildRuleCall(ctx, element);
  } else if (ast.isAlternatives(element)) {
    method = buildAlternatives(ctx, element);
  } else if (ast.isUnorderedGroup(element)) {
    method = buildUnorderedGroup(ctx, element);
  } else if (ast.isGroup(element)) {
    method = buildGroup(ctx, element);
  } else if (ast.isEndOfFile(element)) {
    const idx = ctx.consume++;
    method = () => ctx.parser.consume(idx, tokens_public.EOF, element);
  } else {
    throw new errors.ErrorWithLocation(element.$cstNode, `Unexpected element type: ${element.$type}`);
  }
  return wrap(ctx, ignoreGuard ? void 0 : getGuardCondition(element), method, element.cardinality);
}
function buildAction(ctx, action) {
  const actionType = grammarUtils.getTypeName(action);
  return () => ctx.parser.action(actionType, action);
}
function buildRuleCall(ctx, ruleCall) {
  const rule = ruleCall.rule.ref;
  if (ast.isParserRule(rule)) {
    const idx = ctx.subrule++;
    const fragment = rule.fragment;
    const predicate = ruleCall.arguments.length > 0 ? buildRuleCallPredicate(rule, ruleCall.arguments) : () => ({});
    return (args) => ctx.parser.subrule(idx, getRule(ctx, rule), fragment, ruleCall, predicate(args));
  } else if (ast.isTerminalRule(rule)) {
    const idx = ctx.consume++;
    const method = getToken(ctx, rule.name);
    return () => ctx.parser.consume(idx, method, ruleCall);
  } else if (!rule) {
    throw new errors.ErrorWithLocation(ruleCall.$cstNode, `Undefined rule: ${ruleCall.rule.$refText}`);
  } else {
    errors.assertUnreachable();
  }
}
function buildRuleCallPredicate(rule, namedArgs) {
  const predicates = namedArgs.map((e) => buildPredicate(e.value));
  return (args) => {
    const ruleArgs = {};
    for (let i = 0; i < predicates.length; i++) {
      const ruleTarget = rule.parameters[i];
      const predicate = predicates[i];
      ruleArgs[ruleTarget.name] = predicate(args);
    }
    return ruleArgs;
  };
}
function buildPredicate(condition) {
  if (ast.isDisjunction(condition)) {
    const left = buildPredicate(condition.left);
    const right = buildPredicate(condition.right);
    return (args) => left(args) || right(args);
  } else if (ast.isConjunction(condition)) {
    const left = buildPredicate(condition.left);
    const right = buildPredicate(condition.right);
    return (args) => left(args) && right(args);
  } else if (ast.isNegation(condition)) {
    const value = buildPredicate(condition.value);
    return (args) => !value(args);
  } else if (ast.isParameterReference(condition)) {
    const name = condition.parameter.ref.name;
    return (args) => args !== void 0 && args[name] === true;
  } else if (ast.isBooleanLiteral(condition)) {
    const value = Boolean(condition.true);
    return () => value;
  }
  errors.assertUnreachable();
}
function buildAlternatives(ctx, alternatives) {
  if (alternatives.elements.length === 1) {
    return buildElement(ctx, alternatives.elements[0]);
  } else {
    const methods = [];
    for (const element of alternatives.elements) {
      const predicatedMethod = {
        // Since we handle the guard condition in the alternative already
        // We can ignore the group guard condition inside
        ALT: buildElement(ctx, element, true)
      };
      const guard = getGuardCondition(element);
      if (guard) {
        predicatedMethod.GATE = buildPredicate(guard);
      }
      methods.push(predicatedMethod);
    }
    const idx = ctx.or++;
    return (args) => ctx.parser.alternatives(idx, methods.map((method) => {
      const alt = {
        ALT: () => method.ALT(args)
      };
      const gate = method.GATE;
      if (gate) {
        alt.GATE = () => gate(args);
      }
      return alt;
    }));
  }
}
function buildUnorderedGroup(ctx, group) {
  if (group.elements.length === 1) {
    return buildElement(ctx, group.elements[0]);
  }
  const methods = [];
  for (const element of group.elements) {
    const predicatedMethod = {
      // Since we handle the guard condition in the alternative already
      // We can ignore the group guard condition inside
      ALT: buildElement(ctx, element, true)
    };
    const guard = getGuardCondition(element);
    if (guard) {
      predicatedMethod.GATE = buildPredicate(guard);
    }
    methods.push(predicatedMethod);
  }
  const orIdx = ctx.or++;
  const idFunc = (groupIdx, lParser) => {
    const stackId = lParser.getRuleStack().join("-");
    return `uGroup_${groupIdx}_${stackId}`;
  };
  const alternatives = (args) => ctx.parser.alternatives(orIdx, methods.map((method, idx) => {
    const alt = { ALT: () => true };
    const parser2 = ctx.parser;
    alt.ALT = () => {
      method.ALT(args);
      if (!parser2.isRecording()) {
        const key = idFunc(orIdx, parser2);
        if (!parser2.unorderedGroups.get(key)) {
          parser2.unorderedGroups.set(key, []);
        }
        const groupState = parser2.unorderedGroups.get(key);
        if (typeof (groupState === null || groupState === void 0 ? void 0 : groupState[idx]) === "undefined") {
          groupState[idx] = true;
        }
      }
    };
    const gate = method.GATE;
    if (gate) {
      alt.GATE = () => gate(args);
    } else {
      alt.GATE = () => {
        const trackedAlternatives = parser2.unorderedGroups.get(idFunc(orIdx, parser2));
        const allow = !(trackedAlternatives === null || trackedAlternatives === void 0 ? void 0 : trackedAlternatives[idx]);
        return allow;
      };
    }
    return alt;
  }));
  const wrapped = wrap(ctx, getGuardCondition(group), alternatives, "*");
  return (args) => {
    wrapped(args);
    if (!ctx.parser.isRecording()) {
      ctx.parser.unorderedGroups.delete(idFunc(orIdx, ctx.parser));
    }
  };
}
function buildGroup(ctx, group) {
  const methods = group.elements.map((e) => buildElement(ctx, e));
  return (args) => methods.forEach((method) => method(args));
}
function getGuardCondition(element) {
  if (ast.isGroup(element)) {
    return element.guardCondition;
  }
  return void 0;
}
function buildCrossReference(ctx, crossRef, terminal = crossRef.terminal) {
  if (!terminal) {
    if (!crossRef.type.ref) {
      throw new Error("Could not resolve reference to type: " + crossRef.type.$refText);
    }
    const assignment = grammarUtils.findNameAssignment(crossRef.type.ref);
    const assignTerminal = assignment === null || assignment === void 0 ? void 0 : assignment.terminal;
    if (!assignTerminal) {
      throw new Error("Could not find name assignment for type: " + grammarUtils.getTypeName(crossRef.type.ref));
    }
    return buildCrossReference(ctx, crossRef, assignTerminal);
  } else if (ast.isRuleCall(terminal) && ast.isParserRule(terminal.rule.ref)) {
    const rule = terminal.rule.ref;
    const idx = ctx.subrule++;
    return (args) => ctx.parser.subrule(idx, getRule(ctx, rule), false, crossRef, args);
  } else if (ast.isRuleCall(terminal) && ast.isTerminalRule(terminal.rule.ref)) {
    const idx = ctx.consume++;
    const terminalRule = getToken(ctx, terminal.rule.ref.name);
    return () => ctx.parser.consume(idx, terminalRule, crossRef);
  } else if (ast.isKeyword(terminal)) {
    const idx = ctx.consume++;
    const keyword = getToken(ctx, terminal.value);
    return () => ctx.parser.consume(idx, keyword, crossRef);
  } else {
    throw new Error("Could not build cross reference parser");
  }
}
function buildKeyword(ctx, keyword) {
  const idx = ctx.consume++;
  const token = ctx.tokens[keyword.value];
  if (!token) {
    throw new Error("Could not find token for keyword: " + keyword.value);
  }
  return () => ctx.parser.consume(idx, token, keyword);
}
function wrap(ctx, guard, method, cardinality) {
  const gate = guard && buildPredicate(guard);
  if (!cardinality) {
    if (gate) {
      const idx = ctx.or++;
      return (args) => ctx.parser.alternatives(idx, [
        {
          ALT: () => method(args),
          GATE: () => gate(args)
        },
        {
          ALT: parser.EMPTY_ALT(),
          GATE: () => !gate(args)
        }
      ]);
    } else {
      return method;
    }
  }
  if (cardinality === "*") {
    const idx = ctx.many++;
    return (args) => ctx.parser.many(idx, {
      DEF: () => method(args),
      GATE: gate ? () => gate(args) : void 0
    });
  } else if (cardinality === "+") {
    const idx = ctx.many++;
    if (gate) {
      const orIdx = ctx.or++;
      return (args) => ctx.parser.alternatives(orIdx, [
        {
          ALT: () => ctx.parser.atLeastOne(idx, {
            DEF: () => method(args)
          }),
          GATE: () => gate(args)
        },
        {
          ALT: parser.EMPTY_ALT(),
          GATE: () => !gate(args)
        }
      ]);
    } else {
      return (args) => ctx.parser.atLeastOne(idx, {
        DEF: () => method(args)
      });
    }
  } else if (cardinality === "?") {
    const idx = ctx.optional++;
    return (args) => ctx.parser.optional(idx, {
      DEF: () => method(args),
      GATE: gate ? () => gate(args) : void 0
    });
  } else {
    errors.assertUnreachable();
  }
}
function getRule(ctx, element) {
  const name = getRuleName(ctx, element);
  const rule = ctx.parser.getRule(name);
  if (!rule)
    throw new Error(`Rule "${name}" not found."`);
  return rule;
}
function getRuleName(ctx, element) {
  if (ast.isParserRule(element)) {
    return element.name;
  } else if (ctx.ruleNames.has(element)) {
    return ctx.ruleNames.get(element);
  } else {
    let item = element;
    let parent = item.$container;
    let ruleName = element.$type;
    while (!ast.isParserRule(parent)) {
      if (ast.isGroup(parent) || ast.isAlternatives(parent) || ast.isUnorderedGroup(parent)) {
        const index = parent.elements.indexOf(item);
        ruleName = index.toString() + ":" + ruleName;
      }
      item = parent;
      parent = parent.$container;
    }
    const rule = parent;
    ruleName = rule.name + ":" + ruleName;
    ctx.ruleNames.set(element, ruleName);
    return ruleName;
  }
}
function getToken(ctx, name) {
  const token = ctx.tokens[name];
  if (!token)
    throw new Error(`Token "${name}" not found."`);
  return token;
}
exports.createParser = createParser;
//# sourceMappingURL=lib-parser-parser-builder-base.cjs.map
