import { IfNothingYetEnteredAutocompleter } from "../Autocompleter.js";
import { BNF } from "../core/BNF.js";
import { NonTerminal } from "../core/NonTerminal.js";
import { Terminal } from "../core/Terminal.js";
import { IntRange } from "../util/IntRange.js";
import { Join } from "./Join.js";
import { Optional } from "./Optional.js";
import { Or } from "./Or.js";
import { Plus } from "./Plus.js";
import { Repeat } from "./Repeat.js";
import { Sequence } from "./Sequence.js";
import { Star } from "./Star.js";
import { Evaluator } from "../Evaluator.js";
import { Autocompletion } from "../core/Autocompletion.js";
export class EBNFCore {
    constructor(other) {
        this.symbols = new Map();
        this.rules = [];
        this.bnf = new BNF();
        this.compiled = false;
        if (other !== undefined) {
            for (let [key, value] of other.symbols)
                this.symbols.set(key, value);
            this.rules.push(...other.rules);
            this.compiled = other.compiled;
        }
    }
    getSymbol(type) {
        return this.symbols.get(type);
    }
    compile(topLevelSymbol) {
        this.compiled = false; // otherwise removeRules() and addRule() will complain
        // update the start symbol
        this.removeRules(BNF.ARTIFICIAL_START_SYMBOL);
        const sequence = new Sequence(BNF.ARTIFICIAL_START_SYMBOL, topLevelSymbol, BNF.ARTIFICIAL_STOP_SYMBOL);
        this.addRule(sequence);
        sequence.setEvaluator(Evaluator.FIRST_CHILD_EVALUATOR);
        this.bnf.reset();
        for (let r of this.rules)
            r.createBNF(this.bnf);
        this.compiled = true;
    }
    getBNF() {
        return this.bnf;
    }
    getRules(target) {
        return this.rules.filter(p => p.getTarget().equals(target));
    }
    plus(type, child) {
        const tgt = this.newOrExistingNonTerminal(type);
        const plus = new Plus(tgt, child.getSymbol());
        plus.setParsedChildNames(child.getName());
        this.addRule(plus);
        return plus;
    }
    star(type, child) {
        const tgt = this.newOrExistingNonTerminal(type);
        const star = new Star(tgt, child.getSymbol());
        star.setParsedChildNames(child.getName());
        this.addRule(star);
        return star;
    }
    or(type, ...options) {
        const tgt = this.newOrExistingNonTerminal(type);
        const or = new Or(tgt, ...EBNFCore.getSymbols(...options));
        or.setParsedChildNames(...EBNFCore.getNames(...options));
        this.addRule(or);
        return or;
    }
    optional(type, child) {
        const tgt = this.newOrExistingNonTerminal(type);
        const optional = new Optional(tgt, child.getSymbol());
        optional.setParsedChildNames(child.getName());
        this.addRule(optional);
        return optional;
    }
    repeat(type, child, from, to) {
        const tgt = this.newOrExistingNonTerminal(type);
        const repeat = new Repeat(tgt, child.getSymbol(), from, to);
        repeat.setParsedChildNames(child.getName());
        this.addRule(repeat);
        return repeat;
    }
    join(type, child, open, close, delimiter, onlyKeepEntries, o) {
        const tgt = this.newOrExistingNonTerminal(type);
        let cardinality = undefined;
        let names = undefined;
        if (o instanceof IntRange) {
            names = [child.getName()];
            cardinality = o;
        }
        else {
            names = o;
            cardinality = new IntRange(names.length, names.length);
        }
        const join = new Join(tgt, child.getSymbol(), open, close, delimiter, cardinality);
        join.setOnlyKeepEntries(onlyKeepEntries);
        join.setParsedChildNames(...names);
        this.addRule(join);
        return join;
    }
    list(type, child) {
        const wsStar = this.star(undefined, Terminal.WHITESPACE.withName()).withName("ws*");
        const delimiter = this.sequence(undefined, wsStar, Terminal.literal(",").withName(), wsStar);
        delimiter.setAutocompleter(new IfNothingYetEnteredAutocompleter(", ", ""));
        return this.join(type, child, undefined, undefined, delimiter.getTarget(), true, IntRange.STAR);
    }
    tuple(type, child, ...names) {
        const wsStar = this.star(undefined, Terminal.WHITESPACE.withName()).withName("ws*");
        wsStar.get().setAutocompleter((pn, _justCheck) => Autocompletion.literal(pn, [""]));
        const open = this.sequence(undefined, Terminal.literal("(").withName("open"), wsStar);
        const close = this.sequence(undefined, wsStar, Terminal.literal(")").withName("close"));
        const delimiter = this.sequence(undefined, wsStar, Terminal.literal(",").withName("delimiter"), wsStar);
        const ret = this.join(type, child, open.getTarget(), close.getTarget(), delimiter.getTarget(), true, names);
        ret.setAutocompleter((pn, justCheck) => {
            if (pn.getParsedString().length > 0)
                return undefined;
            if (justCheck)
                return Autocompletion.doesAutocomplete(pn);
            const seq = new Autocompletion.EntireSequence(pn);
            seq.addLiteral(open.getTarget(), "open", "(");
            seq.addParameterized(child.getSymbol(), names[0], names[0]);
            for (let i = 1; i < names.length; i++) {
                seq.addLiteral(delimiter.getTarget(), "delimiter", ", ");
                seq.addParameterized(child.getSymbol(), names[i], names[i]);
            }
            seq.addLiteral(close.getTarget(), "close", ")");
            return seq.asArray();
        });
        return ret;
    }
    sequence(type, ...children) {
        const tgt = this.newOrExistingNonTerminal(type);
        const sequence = new Sequence(tgt, ...EBNFCore.getSymbols(...children));
        sequence.setParsedChildNames(...EBNFCore.getNames(...children));
        this.addRule(sequence);
        return sequence;
    }
    static getSymbols(...named) {
        const ret = new Array(named.length);
        for (let i = 0; i < named.length; i++)
            ret[i] = named[i].getSymbol();
        return ret;
    }
    static getNames(...named) {
        const ret = new Array(named.length);
        for (let i = 0; i < named.length; i++)
            ret[i] = named[i].getName();
        return ret;
    }
    addRule(rule) {
        let s = rule.getTarget();
        if (this.symbols.get(s.getSymbol()) === undefined)
            this.symbols.set(s.getSymbol(), s);
        for (let s of rule.getChildren()) {
            if (!s.isEpsilon() && this.symbols.get(s.getSymbol()) === undefined)
                this.symbols.set(s.getSymbol(), s);
        }
        this.rules.push(rule);
        this.compiled = false;
    }
    removeRules(symbol) {
        for (let i = this.rules.length - 1; i >= 0; i--)
            if (this.rules[i].getTarget().equals(symbol))
                this.rules.splice(i, 1);
        this.compiled = false;
    }
    newOrExistingNonTerminal(type) {
        if (type === undefined)
            return undefined;
        let s = this.symbols.get(type);
        if (s === undefined)
            s = new NonTerminal(type);
        return s;
    }
}
//# sourceMappingURL=EBNFCore.js.map