/*
* Copyright 2014 Basis Technology Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.basistech.tclre;

/**
 * from regc_lex.c
 * Note that this continues the pattern of all the state living
 * in the 'Compiler' object.
 */
class Lex {
    /* lexical contexts */
    private static final int L_ERE = 1; /* mainline ERE/ARE */
    private static final int L_BRE = 2; /* mainline BRE */
    private static final int L_Q = 3;   /* Flags.REG_QUOTE */
    private static final int L_EBND = 4; /* ERE/ARE bound */
    private static final int L_BBND = 5;    /* BRE bound */
    private static final int L_BRACK = 6;   /* brackets */
    private static final int L_CEL = 7; /* collating element */
    private static final int L_ECL = 8; /* equivalence class */
    private static final int L_CCL = 9; /* character class */

    /*
     * string constants to interpolate as expansions of things like \d
     */
    //CHECKSTYLE:OFF
    private static final char backd[] = {       /* \d */
            '[', '[', ':',
            'd', 'i', 'g', 'i', 't',
            ':', ']', ']'
    };

    private static final char backD[] = {       /* \D */
            '[', '^', '[', ':',
            'd', 'i', 'g', 'i', 't',
            ':', ']', ']'
    };
    private static final char brbackd[] = { /* \d within brackets */
            '[', ':',
            'd', 'i', 'g', 'i', 't',
            ':', ']'
    };
    private static final char backs[] = {       /* \s */
            '[', '[', ':',
            's', 'p', 'a', 'c', 'e',
            ':', ']', ']'
    };
    private static final char backS[] = {       /* \S */
            '[', '^', '[', ':',
            's', 'p', 'a', 'c', 'e',
            ':', ']', ']'
    };
    private static final char brbacks[] = { /* \s within brackets */
            '[', ':',
            's', 'p', 'a', 'c', 'e',
            ':', ']'
    };
    private static final char backw[] = {       /* \w */
            '[', '[', ':',
            'a', 'l', 'n', 'u', 'm',
            ':', ']', '_', ']'
    };
    private static final char backW[] = {       /* \W */
            '[', '^', '[', ':',
            'a', 'l', 'n', 'u', 'm',
            ':', ']', '_', ']'
    };
    private static final char brbackw[] = { /* \w within brackets */
            '[', ':',
            'a', 'l', 'n', 'u', 'm',
            ':', ']', '_'
    };
    //CHECKSTYLE:ON

    private Compiler v;

    Lex(Compiler v) {
        this.v = v;
    }

    boolean see(int t) {
        return v.nexttype == t;
    }

    private char charAt(int index) {
        return v.pattern[index];
    }

    private char charAtNow() {
        return charAt(v.now);
    }

    private char charAtNowAdvance() {
        return charAt(v.now++);
    }

    private char charAtNowPlus(int offset) {
        return charAt(v.now + offset);
    }

    /* scanning macros (know about v) */
    boolean ateos() {
        return v.now >= v.stop;
    }

    boolean have(int n) {
        return v.stop - v.now >= n;
    }

    boolean next1(char c) {
        return !ateos() && charAtNow() == c;
    }

    boolean next2(char a, char b) {
        return have(2) && charAtNow() == a && charAtNowPlus(1) == b;
    }

    boolean next3(char a, char b, char c) {
        return have(3)
                && charAtNow() == a
                && charAtNowPlus(1) == b
                && charAtNowPlus(2) == c;
    }

    void set(int c) {
        v.nexttype = c;
    }

    void set(int c, int n) {
        v.nexttype = c;
        v.nextvalue = n;
    }

    boolean ret(int c) {
        set(c);
        return true;
    }

    boolean retv(int c, int n) {
        set(c, n);
        return true;
    }

    boolean lasttype(int t) {
        return v.lasttype == t;
    }

    void intocon(int c) {
        v.lexcon = c;
    }

    boolean incon(int c) {
        return v.lexcon == c;
    }

    /**
     * lexstart - set up lexical stuff, scan leading options
     */
    void lexstart() throws RegexException {
        prefixes();         /* may turn on new type bits etc. */

        if (0 != (v.cflags & Flags.REG_QUOTE)) {
            assert 0 == (v.cflags & (Flags.REG_ADVANCED | Flags.REG_EXPANDED | Flags.REG_NEWLINE));
            intocon(L_Q);
        } else if (0 != (v.cflags & Flags.REG_EXTENDED)) {
            assert 0 == (v.cflags & Flags.REG_QUOTE);
            intocon(L_ERE);
        } else {
            assert 0 == (v.cflags & (Flags.REG_QUOTE | Flags.REG_ADVF));
            intocon(L_BRE);
        }

        v.nexttype = Compiler.EMPTY;        /* remember we were at the start */
        next();         /* set up the first token */
    }

    boolean iscalpha(char c) {
        return c < 0x80 && Character.isLetter(c);
    }

    boolean iscdigit(char x) {
        return x < 0x80 && Character.isDigit(x);
    }

    boolean iscalnum(char x) {
        return x < 0x80 && Character.isLetterOrDigit(x);
    }

    boolean iscspace(char x) {
        return x < 0x80 && Character.isSpaceChar(x);
    }

    /**
     * prefixes - implement various special prefixes
     */
    void prefixes() throws RegexException {
    /* literal string doesn't get any of this stuff */
        if (0 != (v.cflags & Flags.REG_QUOTE)) {
            return;
        }

    /* initial "***" gets special things */
        if (have(4) && next3('*', '*', '*')) {
            switch (charAtNowPlus(3)) {
            case '?':       /* "***?" error, msg shows version */
                throw new RegexException("REG_BADPAT");
            case '=':       /* "***=" shifts to literal string */
                v.note(Flags.REG_UNONPOSIX);
                v.cflags |= Flags.REG_QUOTE;
                v.cflags &= ~(Flags.REG_ADVANCED | Flags.REG_EXPANDED | Flags.REG_NEWLINE);
                v.now += 4;
                return;     /* and there can be no more prefixes */
            case ':':       /* "***:" shifts to AREs */
                v.note(Flags.REG_UNONPOSIX);
                v.cflags |= Flags.REG_ADVANCED;
                v.now += 4;
                break;
            default:        /* otherwise *** is just an error */
                throw new RegexException("REG_BADRPT");
            }
        }

    /* BREs and EREs don't get embedded options */
        if ((v.cflags & Flags.REG_ADVANCED) != Flags.REG_ADVANCED) {
            return;
        }

    /* embedded options (AREs only) */
        if (have(3) && next2('(', '?') && iscalpha(charAtNowPlus(2))) {
            v.note(Flags.REG_UNONPOSIX);
            v.now += 2;
            for (; !ateos() && iscalpha(charAtNow()); v.now++) {
                switch (charAtNow()) {
                case 'b':       /* BREs (but why???) */
                    v.cflags &= ~(Flags.REG_ADVANCED | Flags.REG_QUOTE);
                    break;
                case 'c':       /* case sensitive */
                    v.cflags &= ~Flags.REG_ICASE;
                    break;
                case 'e':       /* plain EREs */
                    v.cflags |= Flags.REG_EXTENDED;
                    v.cflags &= ~(Flags.REG_ADVF | Flags.REG_QUOTE);
                    break;
                case 'i':       /* case insensitive */
                    v.cflags |= Flags.REG_ICASE;
                    break;
                case 'm':       /* Perloid synonym for n */
                case 'n':       /* \n affects ^ $ . [^ */
                    v.cflags |= Flags.REG_NEWLINE;
                    break;
                case 'p':       /* ~Perl, \n affects . [^ */
                    v.cflags |= Flags.REG_NLSTOP;
                    v.cflags &= ~Flags.REG_NLANCH;
                    break;
                case 'q':       /* literal string */
                    v.cflags |= Flags.REG_QUOTE;
                    v.cflags &= ~Flags.REG_ADVANCED;
                    break;
                case 's':       /* single line, \n ordinary */
                    v.cflags &= ~Flags.REG_NEWLINE;
                    break;
                case 't':       /* tight syntax */
                    v.cflags &= ~Flags.REG_EXPANDED;
                    break;
                case 'w':       /* weird, \n affects ^ $ only */
                    v.cflags &= ~Flags.REG_NLSTOP;
                    v.cflags |= Flags.REG_NLANCH;
                    break;
                case 'x':       /* expanded syntax */
                    v.cflags |= Flags.REG_EXPANDED;
                    break;
                default:
                    throw new RegexException("REG_BADOPT");
                }
            }

            if (!next1(')')) {
                throw new RegexException("REG_BADOPT");
            }
            v.now++;
            if (0 != (v.cflags & Flags.REG_QUOTE)) {
                v.cflags &= ~(Flags.REG_EXPANDED | Flags.REG_NEWLINE);
            }
        }
    }

    /**
     * lexnest - "call a subroutine", interpolating string at the lexical level
     * Note, this is not a very general facility.  There are a number of
     * implicit assumptions about what sorts of strings can be subroutines.
     */
    void lexnest(char[] interpolated) {
        assert v.savepattern == null;   /* only one level of nesting */
        v.savepattern = v.pattern;
        v.savenow = v.now;
        v.savestop = v.stop;
        v.savenow = v.now;
        v.pattern = interpolated;
        v.now = 0;
        v.stop = v.pattern.length;
    }

    /**
     * lexword - interpolate a bracket expression for word characters
     * Possibly ought to inquire whether there is a "word" character class.
     */
    void lexword() {
        lexnest(backw);
    }

    int digitval(char c) {
        return c - '0';
    }

    void note(long n) {
        v.note(n);
    }

    //CHECKSTYLE:OFF
    /**
     * next - get next token
     */
    boolean next() throws RegexException {
        char c;

    /* remember flavor of last token */
        v.lasttype = v.nexttype;

    /* REG_BOSONLY */
        if (v.nexttype == Compiler.EMPTY && (0 != (v.cflags & Flags.REG_BOSONLY))) {
        /* at start of a REG_BOSONLY RE */
            return retv(Compiler.SBEGIN, (char)0);      /* same as \A */
        }

    /* if we're nested and we've hit end, return to outer level */
        if (v.savepattern != null && ateos()) {
            v.now = v.savenow;
            v.stop = v.savestop;
            v.savenow = -1;
            v.savestop = -1;
            v.pattern = v.savepattern;
            v.savepattern = null; // mark that it's not saved.
        }

    /* skip white space etc. if appropriate (not in literal or []) */
        if (0 != (v.cflags & Flags.REG_EXPANDED)) {
            switch (v.lexcon) {
            case L_ERE:
            case L_BRE:
            case L_EBND:
            case L_BBND:
                skip();
                break;
            }
        }

    /* handle EOS, depending on context */
        if (ateos()) {
            switch (v.lexcon) {
            case L_ERE:
            case L_BRE:
            case L_Q:
                return ret(Compiler.EOS);
            case L_EBND:
            case L_BBND:
                throw new RegexException("Unbalanced braces.");
            case L_BRACK:
            case L_CEL:
            case L_ECL:
            case L_CCL:
                throw new RegexException("Unbalanced brackets.");
            }
            assert false;
        }

    /* okay, time to actually get a character */
        c = charAtNowAdvance();

    /* deal with the easy contexts, punt EREs to code below */
        switch (v.lexcon) {
        case L_BRE:         /* punt BREs to separate function */
            return brenext(c);
        case L_ERE:         /* see below */
            break;
        case L_Q:           /* literal strings are easy */
            return retv(Compiler.PLAIN, c);
        case L_BBND:            /* bounds are fairly simple */
        case L_EBND:
            switch (c) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                return retv(Compiler.DIGIT, digitval(c));
            case ',':
                return ret(',');
            case '}':       /* ERE bound ends with } */
                if (incon(L_EBND)) {
                    intocon(L_ERE);
                    if (0 != (v.cflags & Flags.REG_ADVF) && next1('?')) {
                        v.now++;
                        note(Flags.REG_UNONPOSIX);
                        return retv('}', 0);
                    }
                    return retv('}', 1);
                } else {
                    throw new RegexException("Errors.REG_BADBR");
                }
            case '\\':      /* BRE bound ends with \} */
                if (incon(L_BBND) && next1('}')) {
                    v.now++;
                    intocon(L_BRE);
                    return ret('}');
                } else {
                    throw new RegexException("Errors.REG_BADBR");
                }
            default:
                throw new RegexException("Errors.REG_BADBR");
            }

        case L_BRACK:           /* brackets are not too hard */
            switch (c) {
            case ']':
                if (lasttype('[')) {
                    return retv(Compiler.PLAIN, c);
                } else {
                    intocon(0 != (v.cflags & Flags.REG_EXTENDED) ? L_ERE : L_BRE);
                    return ret(']');
                }
            case '\\':
                note(Flags.REG_UBBS);
                if (0 == (v.cflags & Flags.REG_ADVF)) {
                    return retv(Compiler.PLAIN, c);
                }
                note(Flags.REG_UNONPOSIX);
                if (ateos()) {
                    throw new RegexException("REG_EESCAPE");
                }
                lexescape();

                switch (v.nexttype) {   /* not all escapes okay here */
                case Compiler.PLAIN:
                    return true;

                case Compiler.CCLASS:
                    switch (v.nextvalue) {
                    case 'd':
                        lexnest(brbackd);
                        break;
                    case 's':
                        lexnest(brbacks);
                        break;
                    case 'w':
                        lexnest(brbackw);
                        break;
                    default:
                        throw new RegexException("Errors.REG_EESCAPE");
                    }
                /* lexnest done, back up and try again */
                    v.nexttype = v.lasttype;
                    return next();

                }
            /* not one of the acceptable escapes */
                throw new RegexException("Errors.REG_EESCAPE");

            case '-':
                if (lasttype('[') || next1(']')) {
                    return retv(Compiler.PLAIN, c);
                } else {
                    return retv(Compiler.RANGE, c);
                }

            case '[':
                if (ateos()) {
                    throw new RegexException("Errors.REG_EBRACK");
                }

                switch (charAtNowAdvance()) {
                case '.':
                    intocon(L_CEL);
                /* might or might not be locale-specific */
                    return ret(Compiler.COLLEL);

                case '=':
                    intocon(L_ECL);
                    note(Flags.REG_ULOCALE);
                    return ret(Compiler.ECLASS);

                case ':':
                    intocon(L_CCL);
                    note(Flags.REG_ULOCALE);
                    return ret(Compiler.CCLASS);

                default:            /* oops */
                    v.now--;
                    return retv(Compiler.PLAIN, c);

                }

            default:
                return retv(Compiler.PLAIN, c);

            }

        case L_CEL:         /* collating elements are easy */
            if (c == '.' && next1(']')) {
                v.now++;
                intocon(L_BRACK);
                return retv(Compiler.END, '.');
            } else {
                return retv(Compiler.PLAIN, c);
            }

        case L_ECL:         /* ditto equivalence classes */
            if (c == '=' && next1(']')) {
                v.now++;
                intocon(L_BRACK);
                return retv(Compiler.END, '=');
            } else {
                return retv(Compiler.PLAIN, c);
            }

        case L_CCL:         /* ditto character classes */
            if (c == ':' && next1(']')) {
                v.now++;
                intocon(L_BRACK);
                return retv(Compiler.END, ':');
            } else {
                return retv(Compiler.PLAIN, c);
            }

        default:
            assert false;
            break;
        }

    /* that got rid of everything except EREs and AREs */
        assert incon(L_ERE);

    /* deal with EREs and AREs, except for backslashes */
        switch (c) {
        case '|':
            return ret('|');

        case '*':
            if (0 != (v.cflags & Flags.REG_ADVF) && next1('?')) {
                v.now++;
                note(Flags.REG_UNONPOSIX);
                return retv('*', 0);
            }
            return retv('*', 1);

        case '+':
            if (0 != (v.cflags & Flags.REG_ADVF) && next1('?')) {
                v.now++;
                note(Flags.REG_UNONPOSIX);
                return retv('+', 0);
            }
            return retv('+', 1);

        case '?':
            if (0 != (v.cflags & Flags.REG_ADVF) && next1('?')) {
                v.now++;
                note(Flags.REG_UNONPOSIX);
                return retv('?', 0);
            }
            return retv('?', 1);

        case '{':       /* bounds start or plain character */
            if (0 != (v.cflags & Flags.REG_EXPANDED)) {
                skip();
            }
            if (ateos() || !iscdigit(charAtNow())) {
                note(Flags.REG_UBRACES);
                note(Flags.REG_UUNSPEC);
                return retv(Compiler.PLAIN, c);
            } else {
                note(Flags.REG_UBOUNDS);
                intocon(L_EBND);
                return ret('{');
            }

        case '(':       /* parenthesis, or advanced extension */
            if (0 != (v.cflags & Flags.REG_ADVF) && next1('?')) {
                note(Flags.REG_UNONPOSIX);
                v.now++;
                char flagChar = charAtNowAdvance();
                switch (flagChar) {
                case ':':       /* non-capturing paren */
                    return retv('(', 0);

                case '#':       /* comment */
                    while (!ateos() && charAtNow() != ')') {
                        v.now++;
                    }
                    if (!ateos()) {
                        v.now++;
                    }
                    assert v.nexttype == v.lasttype;
                    return next();

                case '=':       /* positive lookahead */
                    note(Flags.REG_ULOOKAHEAD);
                    return retv(Compiler.LACON, 1);

                case '!':       /* negative lookahead */
                    note(Flags.REG_ULOOKAHEAD);
                    return retv(Compiler.LACON, 0);

                default:
                    throw new RegexException(String.format("Invalid flag after '(?': %c", flagChar));
                }
            }
            if (0 != (v.cflags & Flags.REG_NOSUB) || 0 != (v.cflags & Flags.REG_NOCAPT)) {
                return retv('(', 0);        /* all parens non-capturing */
            } else {
                return retv('(', 1);
            }

        case ')':
            if (lasttype('(')) {
                note(Flags.REG_UUNSPEC);
            }
            return retv(')', c);

        case '[':       /* easy except for [[:<:]] and [[:>:]] */
            if (have(6) && charAtNow() == '['
                    && charAtNowPlus(1) == ':'
                    && (charAtNowPlus(2) == '<' || charAtNowPlus(2) == '>')
                    && charAtNowPlus(3) == ':'
                    && charAtNowPlus(4) == ']'
                    && charAtNowPlus(5) == ']') {
                c = charAtNowPlus(2);
                v.now += 6;
                note(Flags.REG_UNONPOSIX);
                return ret((c == '<') ? '<' : '>');
            }
            intocon(L_BRACK);
            if (next1('^')) {
                v.now++;
                return retv('[', 0);
            }
            return retv('[', 1);

        case '.':
            return ret('.');

        case '^':
            return ret('^');

        case '$':
            return ret('$');

        case '\\':      /* mostly punt backslashes to code below */
            if (ateos()) {
                throw new RegexException("REG_EESCAPE");
            }
            break;
        default:        /* ordinary character */
            return retv(Compiler.PLAIN, c);

        }

    /* ERE/ARE backslash handling; backslash already eaten */
        assert !ateos();
        if (0 == (v.cflags & Flags.REG_ADVF)) { /* only AREs have non-trivial escapes */
            if (iscalnum(charAtNow())) {
                note(Flags.REG_UBSALNUM);
                note(Flags.REG_UUNSPEC);
            }
            return retv(Compiler.PLAIN, charAtNowAdvance());
        }

        lexescape();

        if (v.nexttype == Compiler.CCLASS) {    /* fudge at lexical level */
            switch (v.nextvalue) {
            case 'd':
                lexnest(backd);
                break;
            case 'D':
                lexnest(backD);
                break;
            case 's':
                lexnest(backs);
                break;
            case 'S':
                lexnest(backS);
                break;
            case 'w':
                lexnest(backw);
                break;
            case 'W':
                lexnest(backW);
                break;

            default:
                throw new RuntimeException("Invalid escape " + Character.toString((char)v.nextvalue));
            }
        /* lexnest done, back up and try again */
            v.nexttype = v.lasttype;
            return next();
        }
    /* otherwise, lexescape has already done the work */
        return true;
    }
    //CHECKSTYLE:ON

    /**
     * brenext - get next BRE token
     * This is much like EREs except for all the stupid backslashes and the
     */
    boolean brenext(char pc) throws RegexException {
        char c = pc;

        switch (c) {
        case '*':
            if (lasttype(Compiler.EMPTY) || lasttype('(') || lasttype('^')) {
                return retv(Compiler.PLAIN, c);
            }
            return ret('*');
        case '[':
            //CHECKSTYLE:OFF
            if (have(6) && charAtNow() == '['
                && charAtNowPlus(1) == ':'
                && (charAtNowPlus(2) == '<' || charAtNowPlus(2) == '>')
                &&  charAtNowPlus(3) == ':'
                &&  charAtNowPlus(4) == ']'
                &&  charAtNowPlus(5) == ']') {
                c = charAtNowPlus(2);
                v.now += 6;
                note(Flags.REG_UNONPOSIX);
                return ret((c == '<') ? '<' : '>');
                //CHECKSTYLE:ON
            }
            intocon(L_BRACK);
            if (next1('^')) {
                v.now++;
                return retv('[', 0);
            }
            return retv('[', 1);
        case '.':
            return ret('.');

        case '^':
            if (lasttype(Compiler.EMPTY)) {
                return ret('^');
            }
            if (lasttype('(')) {
                note(Flags.REG_UUNSPEC);
                return ret('^');
            }
            return retv(Compiler.PLAIN, c);

        case '$':
            if (0 != (v.cflags & Flags.REG_EXPANDED)) {
                skip();
            }
            if (ateos()) {
                return ret('$');
            }
            if (next2('\\', ')')) {
                note(Flags.REG_UUNSPEC);
                return ret('$');
            }
            return retv(Compiler.PLAIN, c);

        case '\\':
            break;      /* see below */
        default:
            return retv(Compiler.PLAIN, c);

        }

        assert c == '\\';

        if (ateos()) {
            throw new RegexException("REG_EESCAPE");
        }

        c = charAtNowAdvance();
        switch (c) {
        case '{':
            intocon(L_BBND);
            note(Flags.REG_UBOUNDS);
            return ret('{');

        case '(':
            return retv('(', 1);

        case ')':
            return retv(')', c);

        case '<':
            note(Flags.REG_UNONPOSIX);
            return ret('<');

        case '>':
            note(Flags.REG_UNONPOSIX);
            return ret('>');

        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            note(Flags.REG_UBACKREF);
            return retv(Compiler.BACKREF, digitval(c));

        default:
            if (iscalnum(c)) {
                note(Flags.REG_UBSALNUM);
                note(Flags.REG_UUNSPEC);
            }
            return retv(Compiler.PLAIN, c);

        }
    }

    void skip() {
        int start = v.now;

        assert 0 != (v.cflags & Flags.REG_EXPANDED);

        for (;;) {
            while (!ateos() && iscspace(charAtNow())) {
                v.now++;
            }
            if (ateos() || charAtNow() != '#') {
                break;              /* NOTE BREAK OUT */
            }
            assert next1('#');
            while (!ateos() && charAtNow() != '\n') {
                v.now++;
            }
        /* leave the newline to be picked up by the iscspace loop */
        }

        if (v.now != start) {
            note(Flags.REG_UNONPOSIX);
        }
    }

    /**
     * lexescape - parse an ARE backslash escape (backslash already eaten)
     * Note slightly nonstandard use of the CCLASS type code.
     */
    //CHECKSTYLE:OFF
    boolean lexescape() throws RegexException {
        int c;
        int save;

        assert 0 != (v.cflags & Flags.REG_ADVF);

        assert !ateos();
        c = charAtNowAdvance();
        if (!iscalnum((char)c)) {
            return retv(Compiler.PLAIN, c);
        }

        note(Flags.REG_UNONPOSIX);
        switch (c) {
        case 'a':
            return retv(Compiler.PLAIN, '\007');

        case 'A':
            return retv(Compiler.SBEGIN, 0);

        case 'b':
            return retv(Compiler.PLAIN, '\b');

        case 'B':
            return retv(Compiler.PLAIN, '\\');

        case 'c':
            if (ateos()) {
                throw new RegexException("Incomplete \\c escape.");
            }
            return retv(Compiler.PLAIN, (char)(charAtNowAdvance() & 037));

        case 'd':
            note(Flags.REG_ULOCALE);
            return retv(Compiler.CCLASS, 'd');

        case 'D':
            note(Flags.REG_ULOCALE);
            return retv(Compiler.CCLASS, 'D');

        case 'e':
            return retv(Compiler.PLAIN, '\033');

        case 'f':
            return retv(Compiler.PLAIN, '\f');

        case 'm':
            return ret('<');

        case 'M':
            return ret('>');

        case 'n':
            return retv(Compiler.PLAIN, '\n');

        case 'r':
            return retv(Compiler.PLAIN, '\r');

        case 's':
            note(Flags.REG_ULOCALE);
            return retv(Compiler.CCLASS, 's');

        case 'S':
            note(Flags.REG_ULOCALE);
            return retv(Compiler.CCLASS, 'S');

        case 't':
            return retv(Compiler.PLAIN, '\t');

        case 'u':
            c = lexdigits(16, 4, 4);
            return retv(Compiler.PLAIN, c);

        case 'U':
            c = lexdigits(16, 8, 8);
            // This escape is for UTF-32 characters. There are, ahem, certain requirements.
            if (c > Character.MAX_CODE_POINT) {
                throw new RegexException("Invalid UTF-32 escape.");
            }
            return retv(Compiler.PLAIN, c);

        case 'v':
            return retv(Compiler.PLAIN, '\u000b');

        case 'w':
            note(Flags.REG_ULOCALE);
            return retv(Compiler.CCLASS, 'w');

        case 'W':
            note(Flags.REG_ULOCALE);
            return retv(Compiler.CCLASS, 'W');

        case 'x':
            c = lexdigits(16, 1, 255);  /* REs >255 long outside spec */
            return retv(Compiler.PLAIN, c);

        case 'y':
            note(Flags.REG_ULOCALE);
            return retv(Compiler.WBDRY, 0);

        case 'Y':
            note(Flags.REG_ULOCALE);
            return retv(Compiler.NWBDRY, 0);

        case 'Z':
            return retv(Compiler.SEND, 0);

        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            save = v.now;
            v.now--;    /* put first digit back */
            c = lexdigits(10, 1, 255);  /* REs >255 long outside spec */
        /* ugly heuristic (first test is "exactly 1 digit?") */
            if (v.now - save == 0 || c <= v.getSubs().size()) {
                note(Flags.REG_UBACKREF);
                return retv(Compiler.BACKREF, (char)c);
            }
        /* oops, doesn't look like it's a backref after all... */
            v.now = save;
        /* and fall through into octal number */
        case '0':
            v.now--;    /* put first digit back */
            c = lexdigits(8, 1, 3);

            return retv(Compiler.PLAIN, c);

        default:
            throw new RegexException("Invalid escape"); // unknown escape.
        }
    }
    //CHECKSTYLE:ON

    /*
 - lexdigits - slurp up digits and return codepoint value
 */
                /* chr value; errors signalled via ERR */
    private int lexdigits(int base, int minlen, int maxlen) throws RegexException {
        int n;          /* unsigned to avoid overflow misbehavior */
        int len;
        int c;
        int d;
        final char ub = (char)base;

        n = 0;
        for (len = 0; len < maxlen && !ateos(); len++) {
            c = charAtNowAdvance();
            switch (c) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                d = digitval((char)c);
                break;
            case 'a':
            case 'A':
                d = 10;
                break;
            case 'b':
            case 'B':
                d = 11;
                break;
            case 'c':
            case 'C':
                d = 12;
                break;
            case 'd':
            case 'D':
                d = 13;
                break;
            case 'e':
            case 'E':
                d = 14;
                break;
            case 'f':
            case 'F':
                d = 15;
                break;
            default:
                v.now--;    /* oops, not a digit at all */
                d = -1;
                break;
            }

            if (d >= base) {    /* not a plausible digit */
                v.now--;
                d = -1;
            }
            if (d < 0) {
                break;      /* NOTE BREAK OUT */
            }
            n = n * ub + d;
        }
        if (len < minlen) {
            throw new RegexException("Not enough digits.");
        }

        return n;
    }
}


/*
* Copyright 2014 Basis Technology Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.basistech.tclre;

import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import com.google.common.collect.RangeMap;
import com.google.common.collect.TreeRangeMap;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;

/**
 * Immutable, sharable, color map.
 * The ColorMap data structure is a fully-populated map from all possible char values to shorts,
 * represented in a complex way.
 * This just uses the obvious array of 2^16 shorts. If we wanted to trade space for time,
 * we could use an Short2ShortOpenHashMap instead.
  */
class RuntimeColorMap implements Serializable {
    static final long serialVersionUID = 2L;
    /* for the BMP, we have this array */
    private final transient short[] bmpMap;
    private final RangeMap<Integer, Short> fullMap;

    /**
     * Make a runtime color map. It might be sensible for the BMP optimization
     * to be saved someplace and not recomputed here.
     * @param fullMap -- the map as built in {@link com.basistech.tclre.ColorMap}
     */
    RuntimeColorMap(RangeMap<Integer, Short> fullMap) {
        this.fullMap = fullMap;
        this.bmpMap = new short[Character.MAX_VALUE + 1];
        computeBmp(fullMap);
    }

    private void computeBmp(RangeMap<Integer, Short> fullMap) {
        for (Map.Entry<Range<Integer>, Short> me : fullMap.asMapOfRanges().entrySet()) {
            Range<Integer> range = me.getKey();
            int min = range.lowerEndpoint();
            if (range.lowerBoundType() == BoundType.OPEN) {
                min++;
            }
            if (min < Character.MAX_VALUE) {
                int rmax = range.upperEndpoint();
                if (range.upperBoundType() == BoundType.OPEN) {
                    rmax--;
                }
                int max = Math.min(Character.MAX_VALUE, rmax);
                for (int x = min; x <= max; x++) {
                    this.bmpMap[x] = me.getValue();
                }
            }
        }
    }

    /**
     * Retrieve the color for a character.
     * @param c a character (BMP)
     * @return the color
     */
    short getcolor(char c) {
        return bmpMap[c];
    }

    /**
     * Retrieve the color for a full codepoint.
     * @param codepoint
     * @return
     */
    short getcolor(int codepoint) {
        try {
            return fullMap.get(codepoint);
        } catch (NullPointerException npe) {
            throw new RuntimeException(String.format(" CP %08x no mapping", codepoint));
        }
    }

    private void writeObject(ObjectOutputStream stream) throws IOException {
        // TreeRangeMap is not Serializable.
        Set<Map.Entry<Range<Integer>, Short>> entries = fullMap.asMapOfRanges().entrySet();
        stream.writeInt(entries.size());
        for (Map.Entry<Range<Integer>, Short> me : entries) {
            stream.writeObject(me.getKey());
            stream.writeShort(me.getValue());
        }
    }

    @SuppressWarnings("unchecked")
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        try {
            Field dataField = RuntimeColorMap.class.getDeclaredField("bmpMap");
            dataField.setAccessible(true);
            dataField.set(this, new short[Character.MAX_VALUE + 1]);
            dataField = RuntimeColorMap.class.getDeclaredField("fullMap");
            dataField.setAccessible(true);
            dataField.set(this, TreeRangeMap.create());
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        int count = in.readInt();
        for (int x = 0; x < count; x++) {
            Range<Integer> k = (Range<Integer>) in.readObject();
            short v = in.readShort();
            fullMap.put(k, v);
        }
        computeBmp(fullMap);
    }
}


/*
* Copyright 2014 Basis Technology Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.basistech.tclre;

import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;

import java.util.BitSet;

/**
 * Runtime DFA.
 */
class Dfa {
    final Object2ObjectMap<BitSet, StateSet> stateSets;
    final int nstates;
    final int ncolors; // length of outarc and inchain vectors (really?)
    final Cnfa cnfa;
    final RuntimeColorMap cm;
    final Runtime runtime;

    Dfa(Runtime runtime, Cnfa cnfa) {
        this.runtime = runtime;
        this.cm = runtime.g.cm;
        this.cnfa = cnfa;
        /*
         * Note that this isn't a cache;
         * Benson believes that the maximum size here is proportional
         * to the complexity of the machine, not to the input.
         */
        stateSets = new Object2ObjectOpenHashMap<BitSet, StateSet>();
        nstates = cnfa.states.length;
        ncolors = cnfa.ncolors;
    }

    /**
     * Called at the start of a match.
     * arguably we could just construct a new DFA each time.
     */
    StateSet initialize(int start) {
        // Discard state sets; reuse would be faster if we kept them,
        // but then we'd need the real cache.
        stateSets.clear();
        StateSet stateSet = new StateSet(nstates, ncolors);
        stateSet.states.set(cnfa.pre);
        stateSet.noprogress = true;
        // Insert into hash table based on that one state.
        stateSets.put(stateSet.states, stateSet);
        stateSet.setLastSeen(start);
        return stateSet;
    }

    /**
     * 'miss' -- the state set was not found in the stateSets.
     *
     * @param co
     * @param cp
     * @return
     */
    StateSet miss(StateSet css, short co, int cp) {
        if (css.outs[co] != null) {
            return css.outs[co];
        }

         /* first, what set of states would we end up in? */
        BitSet work = new BitSet(nstates);
        boolean ispost = false;
        boolean noprogress = true;
        boolean gotstate = false;

        for (int i = 0; i < nstates; i++) {
            if (css.states.get(i)) {
                long ca;
                int ax;
                short caco;
                int catarget;
                for (ax = cnfa.states[i] + 1,
                        ca = cnfa.arcs[ax],
                        caco = Cnfa.carcColor(ca),
                        catarget = Cnfa.carcTarget(ca);
                     caco != Constants.COLORLESS;
                     ax++, ca = cnfa.arcs[ax], caco = Cnfa.carcColor(ca), catarget = Cnfa.carcTarget(ca)) {

                    if (caco == co) {
                        work.set(catarget);
                        gotstate = true;
                        if (catarget == cnfa.post) {
                            ispost = true;
                        }
                        // get target state, index arcs, get color, compare to 0.
                        if (0 == Cnfa.carcColor(cnfa.arcs[cnfa.states[catarget]])) {
                            noprogress = false;
                        }
                    }
                }
            }
        }
        boolean dolacons = gotstate && cnfa.hasLacons;
        boolean sawlacons = false;
        while (dolacons) { /* transitive closure */
            dolacons = false;
            for (int i = 0; i < nstates; i++) {
                if (work.get(i)) {
                    long ca;
                    int ax;
                    short caco;
                    int catarget;
                    for (ax = cnfa.states[i] + 1, ca = cnfa.arcs[ax], caco = Cnfa.carcColor(ca), catarget = Cnfa.carcTarget(ca);
                            caco != Constants.COLORLESS;
                            ax++, ca = cnfa.arcs[ax], caco = Cnfa.carcColor(ca), catarget = Cnfa.carcTarget(ca)) {
                        if (caco <= ncolors) {
                            continue; /* NOTE CONTINUE */
                        }
                        sawlacons = true;
                        if (work.get(catarget)) {
                            continue; /* NOTE CONTINUE */
                        }
                        if (!lacon(cp, caco)) {
                            continue; /* NOTE CONTINUE */
                        }
                        work.set(catarget);
                        dolacons = true;
                        if (catarget == cnfa.post) {
                            ispost = true;
                        }
                        if (0 == Cnfa.carcColor(cnfa.arcs[cnfa.states[catarget]])) {
                            noprogress = false;
                        }
                    }
                }
            }
        }

        if (!gotstate) {
            return null;
        }

        StateSet stateSet = stateSets.get(work);
        if (stateSet == null) {
            stateSet = new StateSet(work, ncolors);
            stateSet.ins = new Arcp(null, Constants.WHITE);
            stateSet.poststate = ispost;
            stateSet.noprogress |= noprogress;
            /* lastseen to be dealt with by caller */
            stateSets.put(work, stateSet);
        }
        if (!sawlacons) {
            css.outs[co] = stateSet;
            css.inchain[co] = stateSet.ins;
            stateSet.ins = new Arcp(css, co);
        }

        return stateSet;
    }

    boolean lacon(int cp, short co) {
        int end;

        int n = co - cnfa.ncolors;
        // compare this to com.basistech.tclre.Nfa.compact(), the LACONS case.
        // that adds a.co to ncolors. So that means that you'd think that the lacons
        // indexing would be related... The 'arc' should have a 'color' which is an index
        //
        RuntimeSubexpression subex = runtime.g.lookaheadConstraintMachine(n);
        Dfa d = new Dfa(runtime, subex.machine);
        end = d.longest(cp, runtime.data.length(), null);
        return (subex.number != 0) ? (end != -1) : (end == -1);
    }

    /**
     * longest - longest-preferred matching engine
     *
     * @return endpoint or -1
     */
    int longest(int start, int stop, boolean[] hitstop) {
        int cp;
        int realstop = (stop == runtime.dataLength) ? stop : stop + 1;
        short co;
        StateSet css;
        int post;

    /* initialize */
        css = initialize(start);
        cp = start;
        if (hitstop != null) {
            hitstop[0] = false;
        }

    /* startup */
        if (cp == 0) {
            co = cnfa.bos[0 != (runtime.eflags & Flags.REG_NOTBOL) ? 0 : 1];
        } else {
            char theChar = runtime.data.charAt(cp - 1);
            if (Character.isLowSurrogate(theChar)) {
                // collect the other end of the surrogate.
                theChar = runtime.data.charAt(cp - 2);
                char high = theChar;
                int codepoint = Character.toCodePoint(high, theChar);
                co = cm.getcolor(codepoint); // and get a color for the pair.
            } else {
                co = cm.getcolor(theChar);
            }
        }
        css = miss(css, co, cp);
        if (css == null) {
            return -1;
        }

        css.setLastSeen(cp);

        StateSet ss;
    /* main loop */
        while (cp < realstop) {
            char theChar = runtime.data.charAt(cp);
            int increment = 1;
            if (Character.isHighSurrogate(theChar)) {
                int codepoint = Character.toCodePoint(theChar, runtime.data.charAt(cp + 1));
                co = cm.getcolor(codepoint);
                increment = 2;
            } else {
                co = cm.getcolor(theChar);
            }
            ss = css.outs[co];
            if (ss == null) {
                ss = miss(css, co, cp + increment);
                if (ss == null) {
                    break;  /* NOTE BREAK OUT */
                }
            }

            cp = cp + increment;
            ss.setLastSeen(cp);
            css = ss;
        }

    /* shutdown */
        if (cp == runtime.dataLength && stop == runtime.dataLength) {
            if (hitstop != null) {
                hitstop[0] = true;
            }
            co = cnfa.eos[0 != (runtime.eflags & Flags.REG_NOTEOL) ? 0 : 1];
            ss = miss(css, co, cp);
        /* special case:  match ended at eol? */
            if (ss != null && ss.poststate) {
                return cp;
            } else if (ss != null) {
                ss.setLastSeen(cp); /* to be tidy */
            }
        }

    /* find last match, if any */
        post = -1;
        for (StateSet thisSS : stateSets.values()) { //.object2ObjectEntrySet()) {
            if (thisSS.poststate && post != thisSS.getLastSeen()
                    && (post == -1 || post < thisSS.getLastSeen())) {
                post = thisSS.getLastSeen();
            }
        }
        if (post != -1) {       /* found one */
            /* Post points after the codepoint after the last one in the match (!) */
            /* So, if that is an SMP codepoint, we need to back up 2 to get to the beginning of it,
             * and thus be just after the last character of the match. */
            char postChar = runtime.data.charAt(post - 1);
            if (Character.isLowSurrogate(postChar)) {
                return post - 2;
            } else {
                return post - 1;
            }
        }
        return -1;
    }

    /**
     * shortest - shortest-preferred matching engine
     *
     * @param start   where the match should start
     * @param min     match must end at or after here
     * @param max     match must end at or before here
     * @param coldp   store coldstart pointer here, if non-null. This is the _beginning_ of the match region.
     * @param hitstop record whether hit end of total input
     * @return endpoint or -1
     */
    int shortest(int start, int min, int max, int[] coldp, boolean[] hitstop) {
        int cp;
        int realmin = min == runtime.dataLength ? min : min + 1;
        int realmax = max == runtime.dataLength ? max : max + 1;
        short co;
        StateSet ss;
        StateSet css;

    /* initialize */
        css = initialize(start);
        cp = start;
        if (hitstop != null) {
            hitstop[0] = false;
        }

    /* startup */
        if (cp == 0) {
            /* If the NOTBOL flag is true, we take color as bos[0], else 1. bos[0] is really BOS, while [1] is supposed to be BOL. So, I guess, if it's NOTBOL, it's BOS. */
            /* The combination of NOTBOL and lookingAt is not defined. */
            co = cnfa.bos[0 != (runtime.eflags & Flags.REG_NOTBOL) ? 0 : 1];
        } else {
            /* Not at bos at all, set color based on prior character. */
            char theChar = runtime.data.charAt(cp - 1);
            if (Character.isLowSurrogate(theChar)) {
                int codepoint = Character.toCodePoint(runtime.data.charAt(cp - 2), theChar);
                co = cm.getcolor(codepoint);
            } else {
                co = cm.getcolor(theChar);
            }
        }

        css = miss(css, co, cp);
        if (css == null) {
            return -1;
        }

        css.setLastSeen(cp);
        ss = css;

    /* main loop */
        while (cp < realmax) {
            int increment = 1;
            char theChar = runtime.data.charAt(cp);
            if (Character.isHighSurrogate(theChar)) {
                int codepoint = Character.toCodePoint(theChar,  runtime.data.charAt(cp + 1));
                co = cm.getcolor(codepoint);
                increment = 2;
            } else {
                co = cm.getcolor(theChar);
            }
            ss = css.outs[co];
            if (ss == null) {
                ss = miss(css, co, cp + increment);
                if (ss == null) {
                    break;  /* NOTE BREAK OUT */
                }
            }

            cp = cp + increment;
            ss.setLastSeen(cp);
            css = ss;
            if (ss.poststate && cp >= realmin) {
                break;      /* NOTE BREAK OUT */
            }
        }

        if (ss == null) {
            return -1;
        }

        int matchStart = lastcold();
        if (coldp != null) {    /* report last no-progress state set, if any */
            coldp[0] = matchStart;
        }

        if (ss.poststate && cp > min) {
            assert cp >= realmin;
            cp--;
            if (Character.isLowSurrogate(runtime.data.charAt(cp))) {
                cp--;
            }

        } else if (cp == runtime.dataLength && max == runtime.dataLength) {
            co = cnfa.eos[0 != (runtime.eflags & Flags.REG_NOTEOL) ? 0 : 1];
            ss = miss(css, co, cp);
        /* match might have ended at eol */
            if ((ss == null || !ss.poststate) && hitstop != null) {
                hitstop[0] = true;
            }
        }

        if (ss == null || !ss.poststate) {
            return -1;
        }

        return cp;
    }

    /**
     * lastcold - determine last point at which no progress had been made
     *
     * @return offset or -1
     */
    int lastcold() {
        int nopr = 0;

        for (StateSet ss : stateSets.values()) {
            if (ss.noprogress && nopr < ss.getLastSeen()) {
                nopr = ss.getLastSeen();
            }
        }
        return nopr;
    }
}


/*
* Copyright 2014 Basis Technology Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.basistech.tclre;

import java.util.concurrent.ExecutionException;

import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.UnicodeSet;

import it.unimi.dsi.fastutil.objects.Object2CharMap;
import it.unimi.dsi.fastutil.objects.Object2CharOpenHashMap;

/**
 * Code from locale.
 */
final class Locale {

/* ASCII character-name table */

    static final Object2CharMap<String> CNAME;
    static final LoadingCache<String, UnicodeSet> KNOWN_SETS_CS = CacheBuilder.newBuilder()
            .build(
                    new CacheLoader<String, UnicodeSet>() {
                        public UnicodeSet load(String cclass) throws RegexException {
                            String className = "[:" + cclass + ":]";
                            try {
                                return new UnicodeSet(className, UnicodeSet.ADD_CASE_MAPPINGS);
                            }  catch (IllegalArgumentException iae) {
                                throw new RegexException("Invalid character class name " + cclass);
                            }
                        }
                    });
    static final LoadingCache<String, UnicodeSet> KNOWN_SETS_CI = CacheBuilder.newBuilder()
            .build(
                    new CacheLoader<String, UnicodeSet>() {
                        public UnicodeSet load(String cclass) throws RegexException {
                            String className = "[:" + cclass + ":]";
                            try {
                                return new UnicodeSet(className, 0);
                            }  catch (IllegalArgumentException iae) {
                                throw new RegexException("Invalid character class name " + cclass);
                            }
                        }
                    });

    //CHECKSTYLE:OFF
    static {
        CNAME = new Object2CharOpenHashMap<String>();

        CNAME.put("NUL", '\0');
        CNAME.put("SOH", '\001');
        CNAME.put("STX", '\002');
        CNAME.put("ETX", '\003');
        CNAME.put("EOT", '\004');
        CNAME.put("ENQ", '\005');
        CNAME.put("ACK", '\006');
        CNAME.put("BEL", '\007');
        CNAME.put("alert", '\007');
        CNAME.put("BS", '\010');
        CNAME.put("backspace", '\b');
        CNAME.put("HT", '\011');
        CNAME.put("tab", '\t');
        CNAME.put("LF", '\012');
        CNAME.put("newline", '\n');
        CNAME.put("VT", '\013');
        CNAME.put("vertical-tab", '\u000b');
        CNAME.put("FF", '\014');
        CNAME.put("form-feed", '\f');
        CNAME.put("CR", '\015');
        CNAME.put("carriage-return", '\r');
        CNAME.put("SO", '\016');
        CNAME.put("SI", '\017');
        CNAME.put("DLE", '\020');
        CNAME.put("DC1", '\021');
        CNAME.put("DC2", '\022');
        CNAME.put("DC3", '\023');
        CNAME.put("DC4", '\024');
        CNAME.put("NAK", '\025');
        CNAME.put("SYN", '\026');
        CNAME.put("ETB", '\027');
        CNAME.put("CAN", '\030');
        CNAME.put("EM", '\031');
        CNAME.put("SUB", '\032');
        CNAME.put("ESC", '\033');
        CNAME.put("IS4", '\034');
        CNAME.put("FS", '\034');
        CNAME.put("IS3", '\035');
        CNAME.put("GS", '\035');
        CNAME.put("IS2", '\036');
        CNAME.put("RS", '\036');
        CNAME.put("IS1", '\037');
        CNAME.put("US", '\037');
        CNAME.put("space", ' ');
        CNAME.put("exclamation-mark", '!');
        CNAME.put("quotation-mark", '"');
        CNAME.put("number-sign", '#');
        CNAME.put("dollar-sign", '$');
        CNAME.put("percent-sign", '%');
        CNAME.put("ampersand", '&');
        CNAME.put("apostrophe", '\'');
        CNAME.put("left-parenthesis", '(');
        CNAME.put("right-parenthesis", ')');
        CNAME.put("asterisk", '*');
        CNAME.put("plus-sign", '+');
        CNAME.put("comma", ',');
        CNAME.put("hyphen", '-');
        CNAME.put("hyphen-minus", '-');
        CNAME.put("period", '.');
        CNAME.put("full-stop", '.');
        CNAME.put("slash", '/');
        CNAME.put("solidus", '/');
        CNAME.put("zero", '0');
        CNAME.put("one", '1');
        CNAME.put("two", '2');
        CNAME.put("three", '3');
        CNAME.put("four", '4');
        CNAME.put("five", '5');
        CNAME.put("six", '6');
        CNAME.put("seven", '7');
        CNAME.put("eight", '8');
        CNAME.put("nine", '9');
        CNAME.put("colon", ':');
        CNAME.put("semicolon", ';');
        CNAME.put("less-than-sign", '<');
        CNAME.put("equals-sign", '=');
        CNAME.put("greater-than-sign", '>');
        CNAME.put("question-mark", '?');
        CNAME.put("commercial-at", '@');
        CNAME.put("left-square-bracket", '[');
        CNAME.put("backslash", '\\');
        CNAME.put("reverse-solidus", '\\');
        CNAME.put("right-square-bracket", ']');
        CNAME.put("circumflex", '^');
        CNAME.put("circumflex-accent", '^');
        CNAME.put("underscore", '_');
        CNAME.put("low-line", '_');
        CNAME.put("grave-accent", '`');
        CNAME.put("left-brace", '{');
        CNAME.put("left-curly-bracket", '{');
        CNAME.put("vertical-line", '|');
        CNAME.put("right-brace", '}');
        CNAME.put("right-curly-bracket", '}');
        CNAME.put("tilde", '~');
        CNAME.put("DEL", '\177');
    }
    //CHECKSTYLE:ON

    private Locale() {
        //
    }

    static int element(String what) throws RegexException {
        // this is a single character or the name of a character.
        // Surrogate pairs? we can't deal yet. This function
        // returns 'int' but no one upstairs is home yet.

        if (what.length() == 1) {
            return what.charAt(0);
        }

        if (CNAME.containsKey(what)) {
            return CNAME.get(what);
        }

        int uc = UCharacter.getCharFromName(what); // what if someone names a non-BMP char?
        if (uc != -1) {
            if (uc > 0xffff) {
                throw new RegexException(String.format(
                        "Limitation: cannot handle equivalence outside of the BMP: %s not possible.",
                        what));
            }
            return uc;
        }

        return -1;
    }

    /**
     * eclass - Because we have no MCCE support, this
     * just processing single characters.
     */
    static UnicodeSet eclass(char c, boolean cases) {

    /* otherwise, none */
        if (cases) {
            return allcases(c);
        } else {
            UnicodeSet set = new UnicodeSet();
            set.add(c);
            return set;
        }
    }

    /**
     * allcases - supply cvec for all case counterparts of a chr (including itself)
     * This is a shortcut, preferably an efficient one, for simple characters;
     * messy cases are done via range().
     */
    static UnicodeSet allcases(int c) {
        UnicodeSet set = new UnicodeSet();
        set.add(c);
        set.closeOver(UnicodeSet.ADD_CASE_MAPPINGS);
        return set;
    }

    /**
     * Return a UnicodeSet for a character class name.
     * It appears that the names that TCL accepts are also acceptable to ICU.
     *
     * @param cclassName class name
     * @param casefold whether to include casefolding
     * @return set
     */
    public static UnicodeSet cclass(String cclassName, boolean casefold) throws RegexException {
        try {
            if (casefold) {
                return KNOWN_SETS_CI.get(cclassName);
            } else {
                return KNOWN_SETS_CS.get(cclassName);
            }
        } catch (ExecutionException e) {
            Throwables.propagateIfInstanceOf(e.getCause(), RegexException.class);
            throw new RegexRuntimeException(e.getCause());
        }
    }
}


/*
* Copyright 2014 Basis Technology Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.basistech.tclre;

/**
 * Mutable builder of immutable CNFA objects.
 */
class CnfaBuilder {
    int ncolors;        /* number of colors */
    boolean hasLacons;
    int pre;        /* setup state number */
    int post;       /* teardown state number */
    short[] bos;     /* colors, if any, assigned to BOS and BOL */
    short[] eos;     /* colors, if any, assigned to EOS and EOL */
    long[] arcs;
    // each state is an index of an arc.
    int[] states;

    CnfaBuilder(int nstates,
         int narcs,
         int preNo,
         int postNo,
         short[] bos,
         short[] eos,
         int maxcolors,
         boolean hasLacons) {

        this.pre = preNo;
        this.post = postNo;
        this.bos = bos;
        this.eos = eos;
        this.ncolors = maxcolors;
        this.hasLacons = hasLacons;

        this.arcs = new long[narcs];
        this.states = new int[nstates];
    }

    Cnfa build() {
        return new Cnfa(ncolors, hasLacons, pre, post, bos, eos, arcs, states);
    }

    void setState(int index, int arcIndex) {
        states[index] = arcIndex;
    }

    void setArc(int index, long arcValue) {
        arcs[index] = arcValue;
    }

    static long packCarc(short color, int targetState) {
        return ((long)color << 32) | targetState;
    }

    /**
     * carcsort - sort compacted-NFA arcs by color
     * Really dumb algorithm, but if the list is long enough for that to matter,
     * you're in real trouble anyway.
     */
    void carcsort(int first, int last) {
        int p;
        int q;
        long tmp;

        if (last - first <= 1) {
            return;
        }

        for (p = first; p <= last; p++) {
            for (q = p; q <= last; q++) {
                short pco = Cnfa.carcColor(arcs[p]);
                short qco = Cnfa.carcColor(arcs[q]);
                int pto = Cnfa.carcTarget(arcs[p]);
                int qto = Cnfa.carcTarget(arcs[q]);
                if (pco > qco || (pco == qco && pto > qto)) {
                    assert p != q;
                    tmp = arcs[p];
                    arcs[p] = arcs[q];
                    arcs[q] = tmp;
                }
            }
        }
    }
}


/*
* Copyright 2014 Basis Technology Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.basistech.tclre;

import com.google.common.base.MoreObjects;

/**
 * subexpression tree
 */
class Subre {
    static final int LONGER = 01;   /* prefers longer match */
    static final int SHORTER = 02;  /* prefers shorter match */
    static final int MIXED = 04;    /* mixed preference below */
    static final int CAP = 010; /* capturing parens below */
    static final int BACKR = 020;   /* back reference below */
    static final int INUSE = 0100;  /* in use in final tree */
    static final int LOCAL = 03;    /* bits which may not propagate up */
    char op;        /* '|', '.' (concat), 'b' (backref), '(', '=' */
    int flags;
    short retry;        /* index into retry memory */
    int subno;      /* subexpression number (for 'b' and '(') */
    short min;      /* min repetitions, for backref only */
    short max;      /* max repetitions, for backref only */
    Subre left; /* left child, if any (also freelist chain) */
    Subre right;    /* right child, if any */
    State begin;    /* outarcs from here... */
    State end;  /* ...ending in inarcs here */
    Cnfa cnfa;

    Subre(char op, int flags, State initState, State finalState) {

        // 0 is valid for lacons.
        assert  "\u0000|.b(=".indexOf(op) != -1 : "invalid op " + Integer.toHexString((int)op);

        this.op = op;
        this.flags = flags;
        min = 1;
        max = 1;
        begin = initState;
        end = finalState;
    }

    /**
     * LONGER -> MIXED
     */
    static int lmix(int f) {
        return f << 2;
    }

    /**
     * SHORTER -> MIXED
     */
    static int smix(int f) {
        return f << 1;
    }

    static int up(int f) {
        return (f & ~LOCAL) | (lmix(f) & smix(f) & MIXED);
    }

    static boolean messy(int f) {
        return 0 != (f & (MIXED | CAP | BACKR));
    }

    static int pref(int f) {
        return f & LOCAL;
    }

    static int pref2(int f1, int f2) {
        return pref(f1) != 0 ? pref(f1) : pref(f2);
    }

    static int combine(int f1, int f2) {
        return up(f1 | f2) | pref2(f1, f2);
    }

    String shortId() {
        String id;
        if (retry != 0) {
            id = Integer.toString(retry);
        } else {
            id = toString();
        }
        return id;
    }

    /**
     * dumpst - dump a subRE tree
     */
    String dumpst(boolean nfapresent) {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("%s. `%c'", shortId(), op));

        if (0 != (flags & LONGER)) {
            sb.append(" longest");
        }
        if (0 != (flags & SHORTER)) {
            sb.append(" shortest");
        }
        if (0 != (flags & MIXED)) {
            sb.append(" hasmixed");
        }
        if (0 != (flags & CAP)) {
            sb.append(" hascapture");
        }
        if (0 != (flags & BACKR)) {
            sb.append(" hasbackref");
        }
        if (0 == (flags & INUSE)) {
            sb.append(" UNUSED");
        }
        if (subno != 0) {
            sb.append(String.format(" (#%d)", subno));
        }
        if (min != 1 || max != 1) {
            sb.append(String.format(" {%d,", min));
            if (max != Compiler.INFINITY) {
                sb.append(String.format("%d", max));
            }
            sb.append("}");
        }
        if (nfapresent) {
            sb.append(String.format(" %d-%d", begin.no, end.no));
        }
        if (left != null) {
            sb.append(String.format(" L:%s", left.toString()));
        }
        if (right != null) {
            sb.append(String.format(" R:%s", right.toString()));
        }

        sb.append("\n");
        if (left != null) {
            left.dumpst(nfapresent);
        }
        if (right != null) {
            right.dumpst(nfapresent);
        }

        return sb.toString();
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("op", op)
                .add("flags", flags)
                .add("retry", retry)
                .add("subno", subno)
                .add("min", min)
                .add("max", max)
                .toString();
    }
}


/*
* Copyright 2014 Basis Technology Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.basistech.tclre;

import com.google.common.base.MoreObjects;

import java.util.Collections;
import java.util.EnumSet;

/*
 * Matcher. This is an incomplete analog of {@link java.util.regex.Matcher}.
 */
final class HsreMatcher implements ReMatcher {

    private CharSequence data;
    private final EnumSet<ExecFlags> flags;
    private final HsrePattern pattern;
    private final Runtime runtime;
    private int regionStart;
    private int regionEnd;
    private int nextFindOffset;
    // correction from Runtime.matches to us.
    private int matchOffset;

    HsreMatcher(HsrePattern pattern, CharSequence data, EnumSet<ExecFlags> flags) throws RegexException {
        this.pattern = pattern;
        this.data = data;
        this.flags = flags;
        regionStart = 0;
        regionEnd = data.length();
        runtime = new Runtime();
    }

    /**
     * @return the pattern.
     */
    @Override
    public RePattern pattern() {
        return pattern;
    }

    /**
     * Look for a match; begin the search at a given offset.
     * @param startOffset the offset.
     * @return true for a match.
     */
    @Override
    public boolean find(int startOffset) throws RegexRuntimeException {
        return findInternal(pattern, startOffset, false);
    }

    private boolean findInternal(HsrePattern pat, int startOffset, boolean lookingAt) {
        if (startOffset < regionStart) {
            throw new IllegalArgumentException("Start offset less than region start");
        }

        // if lookingAt add the LOOKING_AT flag.
        EnumSet<ExecFlags> execFlags = flags;
        if (lookingAt) {
            execFlags = EnumSet.copyOf(flags);
            execFlags.add(ExecFlags.LOOKING_AT);
        }

        try {
            boolean found = runtime.exec(pat, data.subSequence(startOffset, regionEnd), execFlags);
            if (found) {
                // note how much to add to the runtime.match offsets.
                matchOffset = startOffset;
                // and now end does the necessary correction on the offset value from 'runtime'.
                nextFindOffset = end();
            }
            return found;
        } catch (RegexException e) {
            throw new RegexRuntimeException(e);
        }
    }

    /**
     * Look for a match; begin the search at the start.
     * @return true for a match.
     */
    @Override
    public boolean find() throws RegexRuntimeException {
        return find(nextFindOffset);
    }

    /*
     * Called by all the 'resetting' functions.
     * Allows find to work as specified.
    */
    private void resetState() {
        // if there are any matches sitting in the runtime, eliminate.
        if (runtime != null && runtime.match != null) {
            runtime.match.clear();
        }
        nextFindOffset = regionStart;
    }

    @Override
    public ReMatcher region(int start, int end) throws RegexRuntimeException {
        regionStart = start;
        regionEnd = end;
        resetState();
        return this;
    }

    @Override
    public ReMatcher reset() throws RegexRuntimeException {
        regionStart = 0;
        regionEnd = data.length();
        resetState();
        return this;
    }

    @Override
    public ReMatcher reset(CharSequence newSequence) throws RegexRuntimeException {
        data = newSequence;
        regionStart = 0;
        regionEnd = data.length();
        resetState();
        return this;
    }

    @Override
    public ReMatcher flags(ExecFlags... flags) {
        this.flags().clear();
        Collections.addAll(this.flags, flags);
        return null;
    }

    @Override
    public EnumSet<ExecFlags> flags() {
        return flags;
    }

    @Override
    public boolean matches() throws RegexRuntimeException {
        return findInternal(pattern, regionStart, true)
                && start() == regionStart
                && end() == regionEnd;
    }

    @Override
    public int regionStart() {
        return regionStart;
    }

    @Override
    public int regionEnd() {
        return regionEnd;
    }

    @Override
    public boolean lookingAt() {
        return findInternal(pattern, regionStart, true);
    }

    @Override
    public int start() {
        return runtime.match.get(0).start + matchOffset;
    }

    @Override
    public int start(int group) {
        return runtime.match.get(group).start + matchOffset;
    }

    @Override
    public int end() {
        return runtime.match.get(0).end + matchOffset;
    }

    @Override
    public int end(int group) {
        return runtime.match.get(group).end + matchOffset;
    }

    @Override
    public String group() {
        return data.subSequence(start(), end()).toString();
    }

    @Override
    public String group(int group) {
        return data.subSequence(start(group), end(group)).toString();
    }

    @Override
    public int groupCount() {
        return runtime.match.size() - 1; // omit the 'group' for the whole match.
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("pattern", pattern)
                .add("flags", flags)
                .add("regionStart", regionStart)
                .add("regionEnd", regionEnd)
                .add("data", data)
                .toString();
    }
}