import { Autocompleter, EntireSequenceCompleter } from "./Autocompleter.js";
import { Lexer } from "./core/Lexer.js";
import { NonTerminal } from "./core/NonTerminal.js";
import { ParsingState } from "./core/ParsingState.js";
import { RDParser } from "./core/RDParser.js";
import { Terminal } from "./core/Terminal.js";
import { EBNF } from "./ebnf/EBNF.js";
import { EBNFParsedNodeFactory } from "./ebnf/EBNFParsedNodeFactory.js";
import { EBNFParser } from "./ebnf/EBNFParser.js";
import { Evaluator } from "./Evaluator.js";
import { IntRange } from "./util/IntRange.js";
import { Join } from "./ebnf/Join.js";
export class Parser {
    constructor() {
        this.grammar = new EBNF();
        this.LINEBREAK = Terminal.literal("\n");
        this.targetGrammar = new EBNF();
        this.symbol2Autocompletion = new Map();
        this.compiled = false;
        this.parseStartListeners = [];
        this.QUANTIFIER = this.quantifier();
        this.IDENTIFIER = this.identifier("identifier");
        this.VARIABLE_NAME = this.variableName();
        this.ENTRY_NAME = this.entryName();
        this.LIST = this.list();
        this.TUPLE = this.tuple();
        this.CHARACTER_CLASS = this.characterClass();
        this.TYPE = this.type();
        this.VARIABLE = this.variable();
        this.NO_VARIABLE = this.noVariable();
        this.EXPRESSION = this.expression();
        this.LINEBREAK_STAR = this.targetGrammar.star("linebreak-star", this.LINEBREAK.withName());
        this.program();
    }
    getGrammar() {
        return this.grammar;
    }
    getTargetGrammar() {
        return this.targetGrammar;
    }
    defineSentence(pattern, evaluator, howToComplete) {
        let autocompleter;
        if (howToComplete === undefined)
            autocompleter = undefined;
        else if (typeof howToComplete === 'boolean')
            autocompleter = howToComplete
                ? new EntireSequenceCompleter(this.targetGrammar, this.symbol2Autocompletion)
                : Autocompleter.DEFAULT_INLINE_AUTOCOMPLETER;
        else
            autocompleter = howToComplete;
        return this.defineType("sentence", pattern, evaluator, autocompleter);
    }
    defineType(type, pattern, evaluator, howToComplete) {
        let autocompleter;
        if (howToComplete === undefined)
            autocompleter = undefined;
        else if (typeof howToComplete === 'boolean')
            autocompleter = howToComplete
                ? new EntireSequenceCompleter(this.targetGrammar, this.symbol2Autocompletion)
                : Autocompleter.DEFAULT_INLINE_AUTOCOMPLETER;
        else
            autocompleter = howToComplete;
        this.grammar.compile(this.EXPRESSION.getTarget());
        const parser = new RDParser(this.grammar.getBNF(), new Lexer(pattern), EBNFParsedNodeFactory.INSTANCE);
        let pn = parser.parse();
        if (!pn.getMatcher().state.equals(ParsingState.SUCCESSFUL))
            throw new Error("Parsing failed");
        const rhs = pn.evaluate();
        const newRule = this.targetGrammar.sequence(type, ...rhs);
        if (evaluator !== undefined)
            newRule.setEvaluator(evaluator);
        if (autocompleter !== undefined)
            newRule.setAutocompleter(autocompleter);
        return newRule.withName(type);
    }
    compile(symbol) {
        if (symbol === undefined)
            symbol = this.targetGrammar.getSymbol("program");
        this.targetGrammar.compile(symbol);
        this.compiled = true;
    }
    undefineType(type) {
        const unitsSymbol = this.targetGrammar.getSymbol(type);
        this.targetGrammar.removeRules(unitsSymbol);
        this.compiled = false;
    }
    parse(text, autocompletions) {
        if (!this.compiled)
            this.compile();
        this.symbol2Autocompletion.clear();
        const grammar = this.targetGrammar.getBNF();
        const rdParser = new EBNFParser(grammar, new Lexer(text));
        rdParser.addParseStartListener(() => this.fireParsingStarted());
        return rdParser.parse(autocompletions);
    }
    quantifier() {
        const g = this.grammar;
        return g.or("quantifier", g.sequence(undefined, Terminal.literal("?").withName()).setEvaluator(_pn => IntRange.OPTIONAL).withName("optional"), g.sequence(undefined, Terminal.literal("+").withName()).setEvaluator(_pn => IntRange.PLUS).withName("plus"), g.sequence(undefined, Terminal.literal("*").withName()).setEvaluator(_pn => IntRange.STAR).withName("star"), g.sequence(undefined, g.INTEGER_RANGE.withName("range")).setEvaluator(pn => pn.evaluate(0)).withName("range"), g.sequence(undefined, g.INTEGER.withName("int")).setEvaluator(pn => new IntRange(pn.evaluate(0))).withName("fixed"));
    }
    /**
     * [A-Za-z_] ([A-Za-z0-9-_]* [A-Za-z0-9_])?
     *
     * Start:  letter or underscore
     * Middle: letter or underscore or dash or digit
     * End:    letter or underscore or digit
     *
     */
    identifier(name) {
        if (name === undefined)
            name = "identifier";
        const g = this.grammar;
        return g.sequence(name, Terminal.characterClass("[A-Za-z_]").withName(), g.optional(undefined, g.sequence(undefined, g.star(undefined, Terminal.characterClass("[A-Za-z0-9_-]").withName()).withName("star"), Terminal.characterClass("[A-Za-z0-9_]").withName()).withName("seq")).withName("opt"));
    }
    /**
     * (was: ExtendedName)
     *
     * [^:{}\n]+
     *
     * Everything but ':', '{', '}'
     */
    variableName() {
        return this.grammar.plus("var-name", Terminal.characterClass("[^:{}]").withName()).setEvaluator(Evaluator.DEFAULT_EVALUATOR);
    }
    entryName() {
        return this.identifier("entry-name");
    }
    // evaluates to the target grammar's list rule (i.e. Join).
    list() {
        const g = this.grammar;
        return g.sequence("list", Terminal.literal("list").withName(), g.WHITESPACE_STAR.withName("ws*"), Terminal.literal("<").withName(), g.WHITESPACE_STAR.withName("ws*"), this.IDENTIFIER.withName("type"), g.WHITESPACE_STAR.withName("ws*"), Terminal.literal(">").withName()).setEvaluator(pn => {
            const identifier = pn.evaluate("type");
            const entry = this.targetGrammar.getSymbol(identifier);
            if (entry === undefined)
                throw new Error("Could not find " + identifier + " in the target grammar.");
            const namedEntry = (entry instanceof Terminal)
                ? entry.withName(identifier)
                : entry.withName(identifier);
            return this.targetGrammar.list(undefined, namedEntry);
        });
    }
    tuple() {
        const g = this.grammar;
        return g.sequence("tuple", Terminal.literal("tuple").withName(), g.WHITESPACE_STAR.withName("ws*"), Terminal.literal("<").withName(), g.WHITESPACE_STAR.withName("ws*"), this.IDENTIFIER.withName("type"), g.plus(undefined, g.sequence(undefined, g.WHITESPACE_STAR.withName("ws*"), Terminal.literal(",").withName(), g.WHITESPACE_STAR.withName("ws*"), this.ENTRY_NAME.withName("entry-name"), g.WHITESPACE_STAR.withName("ws*")).withName("sequence-names")).withName("plus-names"), Terminal.literal(">").withName()).setEvaluator(pn => {
            const type = pn.evaluate("type");
            const plus = pn.getChild("plus-names");
            const nTuple = plus.numChildren();
            const entryNames = new Array(nTuple);
            for (let i = 0; i < nTuple; i++)
                entryNames[i] = plus.getChild(i).evaluate("entry-name");
            const entry = this.targetGrammar.getSymbol(type);
            if (entry === undefined)
                throw new Error("Could not find " + type + " in the target grammar.");
            const namedEntry = (entry instanceof Terminal)
                ? entry.withName()
                : entry.withName();
            return this.targetGrammar.tuple(undefined, namedEntry, ...entryNames).getTarget();
        });
    }
    characterClass() {
        return this.grammar.sequence("character-class", Terminal.literal("[").withName(), this.grammar.plus(undefined, this.grammar.or(undefined, Terminal.characterClass("[^]]").withName(), Terminal.literal("\\]").withName()).withName()).withName("plus"), Terminal.literal("]").withName()).setEvaluator(pn => {
            const pattern = pn.getParsedString();
            return Terminal.characterClass(pattern);
        });
    }
    type() {
        const g = this.grammar;
        return g.or("type", g.sequence(undefined, this.IDENTIFIER.withName("identifier")).setEvaluator(pn => {
            const str = pn.getParsedString();
            const symbol = this.targetGrammar.getSymbol(str);
            if (symbol === undefined)
                throw new Error("Unknown type '" + str + "'");
            return symbol;
        }).withName("type"), this.LIST.withName("list"), this.TUPLE.withName("tuple"), this.CHARACTER_CLASS.withName("character-class"));
    }
    /*
     * {name:[:type][:quantifier]}
     * - either just the name: {From frame}
     * - or name and type: {frame:int}
     */
    variable() {
        const g = this.grammar;
        return g.sequence("variable", Terminal.literal("{").withName(), this.VARIABLE_NAME.withName("variable-name"), g.optional(undefined, g.sequence(undefined, Terminal.literal(":").withName(), this.TYPE.withName("type")).withName("seq-type")).withName("opt-type"), g.optional(undefined, g.sequence(undefined, Terminal.literal(":").withName(), this.QUANTIFIER.withName("quantifier")).withName("seq-quantifier")).withName("opt-quantifier"), Terminal.literal("}").withName()).setEvaluator(pn => {
            const variableName = pn.evaluate("variable-name");
            const typeObject = pn.evaluate("opt-type", "seq-type", "type");
            const quantifierObject = pn.evaluate("opt-quantifier", "seq-quantifier", "quantifier");
            // typeObject is either
            // - a type (symbol) from the target grammar, or
            // - a character-class (i.e. a terminal), or
            // - a tuple (i.e. symbol of the tuple in the target grammar), or
            // - a list (i.e. a Rule, or more specifically a Join).
            if (typeObject instanceof Join) {
                const join = typeObject;
                if (quantifierObject != null)
                    join.setCardinality(quantifierObject);
                return join.getTarget().withName(variableName);
            }
            let symbol = typeObject === undefined
                ? Terminal.literal(variableName)
                : typeObject;
            let namedSymbol = (symbol instanceof Terminal)
                ? symbol.withName(variableName)
                : symbol.withName(variableName);
            if (quantifierObject !== undefined) {
                let autocompleter = undefined;
                // set a new fallback autocompleter. This is important for e.g. {bla:[a-z]:4} or {bla:digit:4}
                if (typeObject instanceof Terminal)
                    autocompleter = Autocompleter.DEFAULT_INLINE_AUTOCOMPLETER;
                const range = quantifierObject;
                if (range.equals(IntRange.STAR))
                    symbol = this.targetGrammar.star(undefined, namedSymbol).setAutocompleter(autocompleter).getTarget();
                else if (range.equals(IntRange.PLUS))
                    symbol = this.targetGrammar.plus(undefined, namedSymbol).setAutocompleter(autocompleter).getTarget();
                else if (range.equals(IntRange.OPTIONAL))
                    symbol = this.targetGrammar.optional(undefined, namedSymbol).setAutocompleter(autocompleter).getTarget();
                else
                    symbol = this.targetGrammar.repeat(undefined, namedSymbol, range.getLower(), range.getUpper()).setAutocompleter(autocompleter).getTarget();
                namedSymbol = symbol.withName(variableName);
            }
            return namedSymbol;
        });
    }
    noVariable() {
        const g = this.grammar;
        return g.sequence("no-variable", Terminal.characterClass("[^ \t\n{]").withName(), g.optional(undefined, g.sequence(undefined, g.star(undefined, Terminal.characterClass("[^{\n]").withName()).withName("middle"), Terminal.characterClass("[^ \t\n{]").withName()).withName("seq")).withName("tail")).setEvaluator(pn => Terminal.literal(pn.getParsedString()).withName());
    }
    expression() {
        const g = this.grammar;
        return g.join("expression", g.or(undefined, this.NO_VARIABLE.withName("no-variable"), this.VARIABLE.withName("variable")).withName("or"), undefined, undefined, g.WHITESPACE_STAR.getTarget(), false, IntRange.PLUS).setEvaluator(parsedNode => {
            const nChildren = parsedNode.numChildren();
            const rhsList = [];
            rhsList.push(parsedNode.evaluate(0));
            for (let i = 1; i < nChildren; i++) {
                const child = parsedNode.getChild(i);
                if (i % 2 == 0) { // or
                    rhsList.push(child.evaluate());
                }
                else { // ws*
                    const hasWS = child.numChildren() > 0;
                    if (hasWS)
                        rhsList.push(this.targetGrammar.WHITESPACE_PLUS.withName("ws+"));
                }
            }
            return rhsList;
        });
    }
    program() {
        return this.targetGrammar.join("program", new NonTerminal("sentence").withName("sentence"), this.LINEBREAK_STAR.getTarget(), this.LINEBREAK_STAR.getTarget(), this.LINEBREAK_STAR.getTarget(), true, IntRange.STAR);
    }
    addParseStartListener(listener) {
        this.parseStartListeners.push(listener);
    }
    removeParseStartListener(listener) {
        const idx = this.parseStartListeners.indexOf(listener);
        if (idx !== -1)
            this.parseStartListeners.splice(idx, 1);
    }
    fireParsingStarted() {
        for (let l of this.parseStartListeners)
            l();
    }
}
//# sourceMappingURL=Parser.js.map