"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CharacterClass = exports.Epsilon = exports.Literal = exports.Terminal = void 0;
const Named_js_1 = require("./Named.js");
const Matcher_js_1 = require("./Matcher.js");
const ParsingState_js_1 = require("./ParsingState.js");
const Symbol_js_1 = require("./Symbol.js");
class Terminal extends Symbol_js_1.Sym {
    static literal(s) {
        return new Literal(s);
    }
    static characterClass(pattern) {
        return new CharacterClass(pattern);
    }
    constructor(symbol) {
        super(symbol);
    }
    isTerminal() {
        return true;
    }
    isNonTerminal() {
        return false;
    }
    isEpsilon() {
        return false;
    }
    withName(name = undefined) {
        return new Named_js_1.Named(this, name);
    }
}
exports.Terminal = Terminal;
class Epsilon extends Terminal {
    constructor() {
        super("epsilon");
    }
    isEpsilon() {
        return true;
    }
    matches(lexer) {
        return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.SUCCESSFUL, lexer.getPosition(), "");
    }
    evaluate(_matcher) {
        return undefined;
    }
}
exports.Epsilon = Epsilon;
class EndOfInput extends Terminal {
    constructor() {
        super("EOI");
    }
    matches(lexer) {
        const pos = lexer.getPosition();
        if (lexer.isAtEnd())
            return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.SUCCESSFUL, pos, " ");
        return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.FAILED, pos, "");
    }
    evaluate(_matcher) {
        return undefined;
    }
}
class Digit extends Terminal {
    constructor() {
        super("digit");
    }
    matches(lexer) {
        const pos = lexer.getPosition();
        if (lexer.isAtEnd())
            return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.END_OF_INPUT, pos, "");
        const c = lexer.peek();
        if (c.match(/[0-9]/))
            return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.SUCCESSFUL, pos, c);
        return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.FAILED, pos, c);
    }
    evaluate(matcher) {
        return matcher.parsed.charAt(0);
    }
}
class Literal extends Terminal {
    constructor(symbol) {
        super(symbol);
    }
    matches(lexer) {
        const pos = lexer.getPosition();
        const symbol = this.getSymbol();
        for (let i = 0; i < symbol.length; i++) {
            if (lexer.isAtEnd(i))
                return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.END_OF_INPUT, pos, lexer.substring(pos, pos + i + 1));
            if (lexer.peek(i) !== symbol.charAt(i))
                return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.FAILED, pos, lexer.substring(pos, pos + i + 1));
        }
        return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.SUCCESSFUL, pos, symbol);
    }
    evaluate(matcher) {
        return matcher.parsed;
    }
    toString() {
        return `'${this.getSymbol()}'`;
    }
}
exports.Literal = Literal;
class Letter extends Terminal {
    constructor() {
        super("letter");
    }
    matches(lexer) {
        const pos = lexer.getPosition();
        if (lexer.isAtEnd())
            return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.END_OF_INPUT, pos, "");
        const c = lexer.peek();
        console.debug("Test if " + c + " is a letter");
        if (c.match(/[a-zA-Z]/)) {
            console.debug("It is");
            return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.SUCCESSFUL, pos, c);
        }
        else {
            console.debug("it is not");
        }
        return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.FAILED, pos, c);
    }
    evaluate(matcher) {
        return matcher.parsed.charAt(0);
    }
}
class Whitespace extends Terminal {
    constructor() {
        super("whitespace");
    }
    matches(lexer) {
        const pos = lexer.getPosition();
        if (lexer.isAtEnd())
            return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.END_OF_INPUT, pos, "");
        const c = lexer.peek();
        if (c === " " || c === "\t")
            return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.SUCCESSFUL, pos, c);
        return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.FAILED, pos, c);
    }
    evaluate(matcher) {
        return matcher.parsed.charAt(0);
    }
}
class CharacterClass extends Terminal {
    constructor(pattern) {
        super(pattern.trim());
        const b = pattern.trim();
        if (b.length === 0)
            throw new Error("empty character class pattern");
        if (b.charAt(0) !== "[" || b.charAt(b.length - 1) !== "]")
            throw new Error("Wrong character class format: " + pattern);
        let start = 1;
        let end = b.length - 2;
        const negated = b.charAt(1) === "^";
        if (negated)
            start++;
        this.ranges = new Ranges(negated);
        if (b.charAt(start) === "-") {
            this.ranges.add(new SingleCharacterRange('-'.charCodeAt(0)));
            start++;
        }
        if (b.charAt(end) === "-") {
            this.ranges.add(new SingleCharacterRange('-'.charCodeAt(0)));
            end--;
        }
        let idx = start;
        while (idx <= end) {
            const nIdx = idx + 1;
            const c = b.charAt(idx);
            if (nIdx <= end && b.charAt(nIdx) === "-") {
                const u = b.charAt(idx + 2);
                if (c === "-" || u === "-")
                    throw new Error("Wrong character class format: " + pattern);
                this.ranges.add(new CharacterRange(c.charCodeAt(0), u.charCodeAt(0)));
                idx = idx + 3;
            }
            else {
                this.ranges.add(new SingleCharacterRange(c.charCodeAt(0)));
                idx++;
            }
        }
    }
    matches(lexer) {
        const pos = lexer.getPosition();
        if (lexer.isAtEnd())
            return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.END_OF_INPUT, pos, "");
        const c = lexer.peek();
        if (this.ranges.checkCharacter(c.charCodeAt(0)))
            return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.SUCCESSFUL, pos, c);
        return new Matcher_js_1.Matcher(ParsingState_js_1.ParsingState.FAILED, pos, c);
    }
    evaluate(matcher) {
        return matcher.parsed.charAt(0);
    }
    toString() {
        let ret = super.toString();
        // TODO ret = ret.replaceAll(/\n/gi, "\\n");
        return ret;
    }
}
exports.CharacterClass = CharacterClass;
class CharacterRange {
    constructor(lower, upper) {
        this.lower = lower;
        this.upper = upper;
    }
    checkCharacter(i) {
        return i >= this.lower && i <= this.upper;
    }
    equals(o) {
        if (!(o instanceof CharacterRange))
            return false;
        const c = o;
        return this.lower === c.lower && this.upper === c.upper;
    }
}
class SingleCharacterRange extends CharacterRange {
    constructor(number) {
        super(number, number);
        this.number = number;
    }
    checkCharacter(i) {
        return i === this.number;
    }
}
class Ranges {
    constructor(negated) {
        this.ranges = [];
        this.negated = false;
        this.negated = negated;
    }
    add(range) {
        this.ranges.push(range);
    }
    checkCharacter(i) {
        for (var range of this.ranges) {
            let check = range.checkCharacter(i);
            if (!this.negated && check)
                return true;
            if (this.negated && check)
                return false;
        }
        return this.negated;
    }
    equals(o) {
        if (!(o instanceof Ranges))
            return false;
        let sorter = (a, b) => {
            if (a.lower < b.lower)
                return -1;
            if (a.lower > b.lower)
                return 1;
            if (a.upper < b.upper)
                return -1;
            if (a.upper > b.upper)
                return 1;
            return 0;
        };
        let arr1 = this.ranges.slice();
        let arr2 = o.ranges.slice();
        arr1.sort(sorter);
        arr2.sort(sorter);
        return arraysEqual(arr1, arr2);
    }
}
function arraysEqual(a, b) {
    if (a === b)
        return true;
    if (a == null || b == null)
        return false;
    if (a.length !== b.length)
        return false;
    // If you don't care about the order of the elements inside
    // the array, you should sort both arrays here.
    // Please note that calling sort on an array will modify that array.
    // you might want to clone your array first.
    for (var i = 0; i < a.length; ++i) {
        if (!a[i].equals(b[i]))
            return false;
    }
    return true;
}
(function (Terminal) {
    Terminal.EPSILON = new Epsilon();
    Terminal.DIGIT = new Digit();
    Terminal.LETTER = new Letter();
    Terminal.WHITESPACE = new Whitespace();
    Terminal.END_OF_INPUT = new EndOfInput();
})(Terminal || (exports.Terminal = Terminal = {}));
//# sourceMappingURL=Terminal.js.map