/*
    Implementation of backtracking std.regex engine.
    Contains both compile-time and run-time versions.
*/
module std.regex.internal.backtracking;

package(std.regex):

import core.stdc.stdlib, std.range.primitives, std.traits, std.typecons;
import std.regex.internal.ir;

import core.memory : pureMalloc, pureFree;

/+
    BacktrackingMatcher implements backtracking scheme of matching
    regular expressions.
+/
@trusted class BacktrackingMatcher(Char, Stream = Input!Char) : Matcher!Char
if (is(Char : dchar))
{
    alias DataIndex = Stream.DataIndex;
    struct State
    {//top bit in pc is set if saved along with matches
        DataIndex index;
        uint pc, counter, infiniteNesting;
    }
    static assert(State.sizeof % size_t.sizeof == 0);
    enum stateSize = State.sizeof / size_t.sizeof;
    enum initialStack = 1 << 11; // items in a block of segmented stack
    alias String = const(Char)[];
    alias RegEx = Regex!Char;
    alias MatchFn = bool function(BacktrackingMatcher) pure;
    const RegEx re;         // regex program
    MatchFn nativeFn; // native code for that program
    // Stream state
    Stream s;
    DataIndex index;
    dchar front;
    bool exhausted;
    // Backtracking machine state
    uint pc, counter;
    DataIndex lastState = 0;    // Top of state stack
    uint infiniteNesting;
    size_t[] memory;
    Trace[]  merge;
    static struct Trace
    {
        ulong mask;
        size_t offset;

        bool mark(size_t idx)
        {
            immutable d = idx - offset;
            if (d < 64) // including overflow
            {
                immutable p = mask & (1UL << d);
                mask |= 1UL << d;
                return p != 0;
            }
            else
            {
                offset = idx;
                mask = 1;
                return false;
            }
        }
    }
    //local slice of matches, global for backref
    Group!DataIndex[] matches, backrefed;
    size_t _refCount;
final:

    override @property ref size_t refCount() { return _refCount; }
    override @property ref const(RegEx) pattern(){ return re; }

    enum kicked = __traits(hasMember, Stream, "search");

    static size_t initialMemory(const ref RegEx re)
    {
        return stackSize(re)*size_t.sizeof + re.hotspotTableSize*Trace.sizeof;
    }

    static size_t stackSize(const ref RegEx re)
    {
        size_t itemSize = stateSize
            + re.ngroup * (Group!DataIndex).sizeof / size_t.sizeof;
        return initialStack * itemSize + 2;
    }

    @property bool atStart(){ return index == 0; }

    @property bool atEnd(){ return index == s.lastIndex && s.atEnd; }

    void next()
    {
        if (!s.nextChar(front, index))
            index = s.lastIndex;
    }

    void search()
    {
        static if (kicked)
        {
            if (!s.search(re.kickstart, front, index))
            {
                index = s.lastIndex;
            }
        }
        else
            next();
    }

    //
    void newStack()
    {
        auto chunk = mallocArray!(size_t)(stackSize(re));
        chunk[0] = cast(size_t)(memory.ptr);
        chunk[1] = lastState;
        memory = chunk[2..$];
        lastState = 0;
    }

    bool prevStack()
    {
        // pointer to previous block
        size_t* prev = cast(size_t*) memory.ptr[-2];
        if (!prev)
        {
            // The last segment is freed in RegexMatch
            return false;
        }
        else
        {
            import core.memory : pureFree;
            // memory used in previous block
            size_t size = memory.ptr[-1];
            pureFree(memory.ptr-2);
            memory = prev[0 .. size];
            lastState = size;
            return true;
        }
    }

    void initExternalMemory(void[] memBlock)
    {
        merge = arrayInChunk!(Trace)(re.hotspotTableSize, memBlock);
        merge[] = Trace.init;
        memory = cast(size_t[]) memBlock;
        memory[0] = 0; // hidden pointer
        memory[1] = 0; // used size
        memory = memory[2..$];
    }

    void initialize(ref const RegEx program, Stream stream, void[] memBlock)
    {
        s = stream;
        exhausted = false;
        initExternalMemory(memBlock);
        backrefed = null;
    }

    override void dupTo(Matcher!Char m, void[] memBlock)
    {
        auto backtracking = cast(BacktrackingMatcher) m;
        backtracking.s = s;
        backtracking.front = front;
        backtracking.index = index;
        backtracking.exhausted = exhausted;
        backtracking.initExternalMemory(memBlock);
    }

    override Matcher!Char rearm(in Char[] data)
    {
        merge[] = Trace.init;
        exhausted = false;
        s = Stream(data);
        next();
        return this;
    }

    this(ref const RegEx program, Stream stream, void[] memBlock, dchar ch, DataIndex idx)
    {
        _refCount = 1;
        re = program;
        nativeFn = null;
        initialize(program, stream, memBlock);
        front = ch;
        index = idx;
    }

    this(ref const RegEx program, MatchFn func, Stream stream, void[] memBlock)
    {
        _refCount = 1;
        re = program;
        initialize(program, stream, memBlock);
        nativeFn = func;
        next();
    }

    this(ref const RegEx program, Stream stream, void[] memBlock)
    {
        _refCount = 1;
        re = program;
        nativeFn = null;
        initialize(program, stream, memBlock);
        next();
    }

    auto fwdMatcher(ref const RegEx re, void[] memBlock)
    {
        alias BackMatcher = BacktrackingMatcher!(Char, Stream);
        auto fwdMatcher = new BackMatcher(re, s, memBlock, front, index);
        return fwdMatcher;
    }

    auto bwdMatcher(ref const RegEx re, void[] memBlock)
    {
        alias BackMatcher = BacktrackingMatcher!(Char, typeof(s.loopBack(index)));
        auto fwdMatcher =
            new BackMatcher(re, s.loopBack(index), memBlock);
        return fwdMatcher;
    }

    //
    int matchFinalize()
    {
        immutable start = index;
        immutable val = matchImpl();
        if (val)
        {//stream is updated here
            matches[0].begin = start;
            matches[0].end = index;
            if (!(re.flags & RegexOption.global) || atEnd)
                exhausted = true;
            if (start == index)//empty match advances input
                next();
            return val;
        }
        else
            return 0;
    }

    //lookup next match, fill matches with indices into input
    override int match(Group!DataIndex[] matches)
    {
        debug(std_regex_matcher)
        {
            writeln("------------------------------------------");
        }
        if (exhausted) //all matches collected
            return false;
        this.matches = matches;
        if (re.flags & RegexInfo.oneShot)
        {
            exhausted = true;
            const DataIndex start = index;
            immutable m = matchImpl();
            if (m)
            {
                matches[0].begin = start;
                matches[0].end = index;
            }
            return m;
        }
        static if (kicked)
        {
            if (!re.kickstart.empty)
            {
                for (;;)
                {
                    immutable val = matchFinalize();
                    if (val)
                        return val;
                    else
                    {
                        if (atEnd)
                            break;
                        search();
                        if (atEnd)
                        {
                            exhausted = true;
                            return matchFinalize();
                        }
                    }
                }
                exhausted = true;
                return 0; //early return
            }
        }
        //no search available - skip a char at a time
        for (;;)
        {
            immutable val = matchFinalize();
            if (val)
                return val;
            else
            {
                if (atEnd)
                    break;
                next();
                if (atEnd)
                {
                    exhausted = true;
                    return matchFinalize();
                }
            }
        }
        exhausted = true;
        return 0;
    }

    /+
        match subexpression against input,
        results are stored in matches
    +/
    int matchImpl() pure
    {
        if (nativeFn)
        {
            debug(std_regex_ctr) writeln("using C-T matcher");
            return nativeFn(this);
        }
        else
        {
            pc = 0;
            counter = 0;
            lastState = 0;
            infiniteNesting = 0;
            matches[] = Group!DataIndex.init;
            auto start = s._index;
            debug(std_regex_matcher)
                writeln("Try match starting at ", s[index .. s.lastIndex]);
            for (;;)
            {
                debug(std_regex_matcher)
                    writefln("PC: %s\tCNT: %s\t%s \tfront: %s src: %s",
                        pc, counter, disassemble(re.ir, pc, re.dict),
                        front, s._index);
                switch (re.ir[pc].code)
                {
                case IR.OrChar://assumes IRL!(OrChar) == 1
                    if (atEnd)
                        goto L_backtrack;
                    uint len = re.ir[pc].sequence;
                    uint end = pc + len;
                    if (re.ir[pc].data != front && re.ir[pc+1].data != front)
                    {
                        for (pc = pc+2; pc < end; pc++)
                            if (re.ir[pc].data == front)
                                break;
                        if (pc == end)
                            goto L_backtrack;
                    }
                    pc = end;
                    next();
                    break;
                case IR.Char:
                    if (atEnd || front != re.ir[pc].data)
                        goto L_backtrack;
                    pc += IRL!(IR.Char);
                    next();
                break;
                case IR.Any:
                    if (atEnd)
                        goto L_backtrack;
                    pc += IRL!(IR.Any);
                    next();
                    break;
                case IR.CodepointSet:
                    if (atEnd || !re.charsets[re.ir[pc].data].scanFor(front))
                        goto L_backtrack;
                    next();
                    pc += IRL!(IR.CodepointSet);
                    break;
                case IR.Trie:
                    if (atEnd || !re.matchers[re.ir[pc].data][front])
                        goto L_backtrack;
                    next();
                    pc += IRL!(IR.Trie);
                    break;
                case IR.Wordboundary:
                    dchar back;
                    DataIndex bi;
                    //at start & end of input
                    if (atStart && wordMatcher[front])
                    {
                        pc += IRL!(IR.Wordboundary);
                        break;
                    }
                    else if (atEnd && s.loopBack(index).nextChar(back, bi)
                            && wordMatcher[back])
                    {
                        pc += IRL!(IR.Wordboundary);
                        break;
                    }
                    else if (s.loopBack(index).nextChar(back, bi))
                    {
                        immutable af = wordMatcher[front];
                        immutable ab = wordMatcher[back];
                        if (af ^ ab)
                        {
                            pc += IRL!(IR.Wordboundary);
                            break;
                        }
                    }
                    goto L_backtrack;
                case IR.Notwordboundary:
                    dchar back;
                    DataIndex bi;
                    //at start & end of input
                    if (atStart && wordMatcher[front])
                        goto L_backtrack;
                    else if (atEnd && s.loopBack(index).nextChar(back, bi)
                            && wordMatcher[back])
                        goto L_backtrack;
                    else if (s.loopBack(index).nextChar(back, bi))
                    {
                        immutable af = wordMatcher[front];
                        immutable ab = wordMatcher[back];
                        if (af ^ ab)
                            goto L_backtrack;
                    }
                    pc += IRL!(IR.Wordboundary);
                    break;
                case IR.Bof:
                    if (atStart)
                        pc += IRL!(IR.Bol);
                    else
                        goto L_backtrack;
                    break;
                case IR.Bol:
                    dchar back;
                    DataIndex bi;
                    if (atStart)
                        pc += IRL!(IR.Bol);
                    else if (s.loopBack(index).nextChar(back,bi)
                        && endOfLine(back, front == '\n'))
                    {
                        pc += IRL!(IR.Bol);
                    }
                    else
                        goto L_backtrack;
                    break;
                case IR.Eof:
                    if (atEnd)
                        pc += IRL!(IR.Eol);
                    else
                        goto L_backtrack;
                    break;
                case IR.Eol:
                    dchar back;
                    DataIndex bi;
                    debug(std_regex_matcher) writefln("EOL (front 0x%x) %s", front, s[index .. s.lastIndex]);
                    //no matching inside \r\n
                    if (atEnd || (endOfLine(front, s.loopBack(index).nextChar(back,bi)
                            && back == '\r')))
                    {
                        pc += IRL!(IR.Eol);
                    }
                    else
                        goto L_backtrack;
                    break;
                case IR.InfiniteStart, IR.InfiniteQStart:
                    pc += re.ir[pc].data + IRL!(IR.InfiniteStart);
                    //now pc is at end IR.Infinite(Q)End
                    uint len = re.ir[pc].data;
                    if (re.ir[pc].code == IR.InfiniteEnd)
                    {
                        pushState(pc+IRL!(IR.InfiniteEnd), counter);
                        pc -= len;
                    }
                    else
                    {
                        pushState(pc - len, counter);
                        pc += IRL!(IR.InfiniteEnd);
                    }
                    break;
                case IR.InfiniteBloomStart:
                    pc += re.ir[pc].data + IRL!(IR.InfiniteBloomStart);
                    //now pc is at end IR.InfiniteBloomEnd
                    immutable len = re.ir[pc].data;
                    immutable filterIdx = re.ir[pc+2].raw;
                    if (re.filters[filterIdx][front])
                        pushState(pc+IRL!(IR.InfiniteBloomEnd), counter);
                    pc -= len;
                    break;
                case IR.RepeatStart, IR.RepeatQStart:
                    pc += re.ir[pc].data + IRL!(IR.RepeatStart);
                    break;
                case IR.RepeatEnd:
                case IR.RepeatQEnd:
                    if (merge[re.ir[pc + 1].raw+counter].mark(index))
                    {
                        // merged!
                        goto L_backtrack;
                    }
                    //len, step, min, max
                    immutable len = re.ir[pc].data;
                    immutable step =  re.ir[pc+2].raw;
                    immutable min = re.ir[pc+3].raw;
                    immutable max = re.ir[pc+4].raw;
                    if (counter < min)
                    {
                        counter += step;
                        pc -= len;
                    }
                    else if (counter < max)
                    {
                        if (re.ir[pc].code == IR.RepeatEnd)
                        {
                            pushState(pc + IRL!(IR.RepeatEnd), counter%step);
                            counter += step;
                            pc -= len;
                        }
                        else
                        {
                            pushState(pc - len, counter + step);
                            counter = counter%step;
                            pc += IRL!(IR.RepeatEnd);
                        }
                    }
                    else
                    {
                        counter = counter%step;
                        pc += IRL!(IR.RepeatEnd);
                    }
                    break;
                case IR.InfiniteEnd:
                case IR.InfiniteQEnd:
                    debug(std_regex_matcher) writeln("Infinited nesting:", infiniteNesting);
                    if (merge[re.ir[pc + 1].raw+counter].mark(index))
                    {
                        // merged!
                        goto L_backtrack;
                    }
                    immutable len = re.ir[pc].data;
                    if (re.ir[pc].code == IR.InfiniteEnd)
                    {
                        pushState(pc + IRL!(IR.InfiniteEnd), counter);
                        pc -= len;
                    }
                    else
                    {
                        pushState(pc-len, counter);
                        pc += IRL!(IR.InfiniteEnd);
                    }
                    break;
                case IR.InfiniteBloomEnd:
                    debug(std_regex_matcher) writeln("Infinited nesting:", infiniteNesting);
                    if (merge[re.ir[pc + 1].raw+counter].mark(index))
                    {
                        // merged!
                        goto L_backtrack;
                    }
                    immutable len = re.ir[pc].data;
                    immutable filterIdx = re.ir[pc+2].raw;
                    if (re.filters[filterIdx][front])
                    {
                        infiniteNesting--;
                        pushState(pc + IRL!(IR.InfiniteBloomEnd), counter);
                        infiniteNesting++;
                    }
                    pc -= len;
                    break;
                case IR.OrEnd:
                    if (merge[re.ir[pc + 1].raw+counter].mark(index))
                    {
                        // merged!
                        goto L_backtrack;
                    }
                    pc += IRL!(IR.OrEnd);
                    break;
                case IR.OrStart:
                    pc += IRL!(IR.OrStart);
                    goto case;
                case IR.Option:
                    immutable len = re.ir[pc].data;
                    if (re.ir[pc+len].code == IR.GotoEndOr)//not a last one
                    {
                        pushState(pc + len + IRL!(IR.Option), counter); //remember 2nd branch
                    }
                    pc += IRL!(IR.Option);
                    break;
                case IR.GotoEndOr:
                    pc = pc + re.ir[pc].data + IRL!(IR.GotoEndOr);
                    break;
                case IR.GroupStart:
                    immutable n = re.ir[pc].data;
                    matches[n].begin = index;
                    debug(std_regex_matcher)  writefln("IR group #%u starts at %u", n, index);
                    pc += IRL!(IR.GroupStart);
                    break;
                case IR.GroupEnd:
                    immutable n = re.ir[pc].data;
                    matches[n].end = index;
                    debug(std_regex_matcher) writefln("IR group #%u ends at %u", n, index);
                    pc += IRL!(IR.GroupEnd);
                    break;
                case IR.LookaheadStart:
                case IR.NeglookaheadStart:
                    immutable len = re.ir[pc].data;
                    auto save = index;
                    immutable ms = re.ir[pc+1].raw, me = re.ir[pc+2].raw;
                    auto mem = pureMalloc(initialMemory(re))[0 .. initialMemory(re)];
                    scope(exit) pureFree(mem.ptr);
                    auto slicedRe = re.withCode(re.ir[
                        pc+IRL!(IR.LookaheadStart) .. pc+IRL!(IR.LookaheadStart)+len+IRL!(IR.LookaheadEnd)
                    ]);
                    static if (Stream.isLoopback)
                    {
                        auto matcher = bwdMatcher(slicedRe, mem);
                    }
                    else
                    {
                        auto matcher = fwdMatcher(slicedRe, mem);
                    }
                    matcher.matches = matches[ms .. me];
                    matcher.backrefed = backrefed.empty ? matches : backrefed;
                    immutable match = (matcher.matchImpl() != 0) ^ (re.ir[pc].code == IR.NeglookaheadStart);
                    s.reset(save);
                    next();
                    if (!match)
                        goto L_backtrack;
                    else
                    {
                        pc += IRL!(IR.LookaheadStart)+len+IRL!(IR.LookaheadEnd);
                    }
                    break;
                case IR.LookbehindStart:
                case IR.NeglookbehindStart:
                    immutable len = re.ir[pc].data;
                    immutable ms = re.ir[pc+1].raw, me = re.ir[pc+2].raw;
                    auto mem = pureMalloc(initialMemory(re))[0 .. initialMemory(re)];
                    scope(exit) pureFree(mem.ptr);
                    auto slicedRe = re.withCode(re.ir[
                        pc + IRL!(IR.LookbehindStart) .. pc + IRL!(IR.LookbehindStart) + len + IRL!(IR.LookbehindEnd)
                    ]);
                    static if (Stream.isLoopback)
                    {
                        alias Matcher = BacktrackingMatcher!(Char, Stream);
                        auto matcher = new Matcher(slicedRe, s, mem, front, index);
                    }
                    else
                    {
                        alias Matcher = BacktrackingMatcher!(Char, typeof(s.loopBack(index)));
                        auto matcher = new Matcher(slicedRe, s.loopBack(index), mem);
                    }
                    matcher.matches = matches[ms .. me];
                    matcher.backrefed  = backrefed.empty ? matches : backrefed;
                    immutable match = (matcher.matchImpl() != 0) ^ (re.ir[pc].code == IR.NeglookbehindStart);
                    if (!match)
                        goto L_backtrack;
                    else
                    {
                        pc += IRL!(IR.LookbehindStart)+len+IRL!(IR.LookbehindEnd);
                    }
                    break;
                case IR.Backref:
                    immutable n = re.ir[pc].data;
                    auto referenced = re.ir[pc].localRef
                            ? s[matches[n].begin .. matches[n].end]
                            : s[backrefed[n].begin .. backrefed[n].end];
                    while (!atEnd && !referenced.empty && front == referenced.front)
                    {
                        next();
                        referenced.popFront();
                    }
                    if (referenced.empty)
                        pc++;
                    else
                        goto L_backtrack;
                    break;
                    case IR.Nop:
                    pc += IRL!(IR.Nop);
                    break;
                case IR.LookaheadEnd:
                case IR.NeglookaheadEnd:
                case IR.LookbehindEnd:
                case IR.NeglookbehindEnd:
                case IR.End:
                    // cleanup stale stack blocks if any
                    while (prevStack()) {}
                    return re.ir[pc].data;
                default:
                    debug printBytecode(re.ir[0..$]);
                    assert(0);
                L_backtrack:
                    if (!popState())
                    {
                        s.reset(start);
                        return 0;
                    }
                }
            }
        }
        assert(0);
    }

    @property size_t stackAvail()
    {
        return memory.length - lastState;
    }

    void stackPush(T)(T val)
        if (!isDynamicArray!T)
    {
        *cast(T*)&memory[lastState] = val;
        enum delta = (T.sizeof+size_t.sizeof/2)/size_t.sizeof;
        lastState += delta;
        debug(std_regex_matcher) writeln("push element SP= ", lastState);
    }

    void stackPush(T)(T[] val)
    {
        static assert(T.sizeof % size_t.sizeof == 0);
        (cast(T*)&memory[lastState])[0 .. val.length]
            = val[0..$];
        lastState += val.length*(T.sizeof/size_t.sizeof);
        debug(std_regex_matcher) writeln("push array SP= ", lastState);
    }

    void stackPop(T)(ref T val)
        if (!isDynamicArray!T)
    {
        enum delta = (T.sizeof+size_t.sizeof/2)/size_t.sizeof;
        lastState -= delta;
        val = *cast(T*)&memory[lastState];
        debug(std_regex_matcher) writeln("pop element SP= ", lastState);
    }

    void stackPop(T)(T[] val)
    {
        stackPop(val);  // call ref version
    }
    void stackPop(T)(ref T[] val)
    {
        lastState -= val.length*(T.sizeof/size_t.sizeof);
        val[0..$] = (cast(T*)&memory[lastState])[0 .. val.length];
        debug(std_regex_matcher) writeln("pop array SP= ", lastState);
    }
    //helper function, saves engine state
    void pushState(uint pc, uint counter)
    {
        if (stateSize + 2 * matches.length > stackAvail)
        {
            newStack();
        }
        *cast(State*)&memory[lastState] =
            State(index, pc, counter, infiniteNesting);
        lastState += stateSize;
        memory[lastState .. lastState + 2 * matches.length] = (cast(size_t[]) matches)[];
        lastState += 2*matches.length;
        debug(std_regex_matcher)
            writefln("Saved(pc=%s) front: %s src: %s",
                pc, front, s[index .. s.lastIndex]);
    }

    //helper function, restores engine state
    bool popState()
    {
        if (!lastState && !prevStack())
            return false;
        lastState -= 2*matches.length;
        auto pm = cast(size_t[]) matches;
        pm[] = memory[lastState .. lastState + 2 * matches.length];
        lastState -= stateSize;
        State* state = cast(State*)&memory[lastState];
        index = state.index;
        pc = state.pc;
        counter = state.counter;
        infiniteNesting = state.infiniteNesting;
        debug(std_regex_matcher)
        {
            writefln("Restored matches", front, s[index .. s.lastIndex]);
            foreach (i, m; matches)
                writefln("Sub(%d) : %s..%s", i, m.begin, m.end);
        }
        s.reset(index);
        next();
        debug(std_regex_matcher)
            writefln("Backtracked (pc=%s) front: %s src: %s",
                pc, front, s[index .. s.lastIndex]);
        return true;
    }
}

//very shitty string formatter, $$ replaced with next argument converted to string
@trusted string ctSub( U...)(string format, U args)
{
    import std.conv : to;
    bool seenDollar;
    foreach (i, ch; format)
    {
        if (ch == '$')
        {
            if (seenDollar)
            {
                static if (args.length > 0)
                {
                    return  format[0 .. i - 1] ~ to!string(args[0])
                        ~ ctSub(format[i + 1 .. $], args[1 .. $]);
                }
                else
                    assert(0);
            }
            else
                seenDollar = true;
        }
        else
            seenDollar = false;

    }
    return format;
}

struct CtContext
{
    import std.conv : to, text;
    //dirty flags
    bool counter;
    //to mark the portion of matches to save
    int match, total_matches;
    int reserved;
    const(CodepointInterval)[][] charsets;


    //state of codegenerator
    static struct CtState
    {
        string code;
        int addr;
    }

    this(Char)(ref const Regex!Char re)
    {
        match = 1;
        reserved = 1; //first match is skipped
        total_matches = re.ngroup;
        foreach (ref set; re.charsets)
        {
            charsets ~= set.intervals;
        }
    }

    CtContext lookaround(uint s, uint e)
    {
        CtContext ct;
        ct.total_matches = e - s;
        ct.match = 1;
        return ct;
    }

    //restore state having current context
    string restoreCode()
    {
        string text;
        //stack is checked in L_backtrack
        text ~= counter
            ? "
                    stackPop(counter);"
            : "
                    counter = 0;";
        if (match < total_matches)
        {
            text ~= ctSub("
                    stackPop(matches[$$..$$]);", reserved, match);
            text ~= ctSub("
                    matches[$$..$] = typeof(matches[0]).init;", match);
        }
        else
            text ~= ctSub("
                    stackPop(matches[$$..$]);", reserved);
        return text;
    }

    //save state having current context
    string saveCode(uint pc, string count_expr="counter")
    {
        string text = ctSub("
                    if (stackAvail < $$*(Group!(DataIndex)).sizeof/size_t.sizeof + $$)
                    {
                        newStack();
                    }", match - reserved, cast(int) counter + 2);
        if (match < total_matches)
            text ~= ctSub("
                    stackPush(matches[$$..$$]);", reserved, match);
        else
            text ~= ctSub("
                    stackPush(matches[$$..$]);", reserved);
        text ~= counter ? ctSub("
                    stackPush($$);", count_expr) : "";
        text ~= ctSub("
                    stackPush(index); stackPush($$); \n", pc);
        return text;
    }

    //
    CtState ctGenBlock(const(Bytecode)[] ir, int addr)
    {
        CtState result;
        result.addr = addr;
        while (!ir.empty)
        {
            auto n = ctGenGroup(ir, result.addr);
            result.code ~= n.code;
            result.addr = n.addr;
        }
        return result;
    }

    //
    CtState ctGenGroup(ref const(Bytecode)[] ir, int addr)
    {
        import std.algorithm.comparison : max;
        auto bailOut = "goto L_backtrack;";
        auto nextInstr = ctSub("goto case $$;", addr+1);
        CtState r;
        assert(!ir.empty);
        switch (ir[0].code)
        {
        case IR.InfiniteStart,  IR.InfiniteBloomStart,IR.InfiniteQStart, IR.RepeatStart, IR.RepeatQStart:
            immutable infLoop =
                ir[0].code == IR.InfiniteStart || ir[0].code == IR.InfiniteQStart ||
                ir[0].code == IR.InfiniteBloomStart;

            counter = counter ||
                ir[0].code == IR.RepeatStart || ir[0].code == IR.RepeatQStart;
            immutable len = ir[0].data;
            auto nir = ir[ir[0].length .. ir[0].length+len];
            r = ctGenBlock(nir, addr+1);
            //start/end codegen
            //r.addr is at last test+ jump of loop, addr+1 is body of loop
            nir = ir[ir[0].length + len .. $];
            r.code = ctGenFixupCode(ir[0 .. ir[0].length], addr, r.addr) ~ r.code;
            r.code ~= ctGenFixupCode(nir, r.addr, addr+1);
            r.addr += 2;   //account end instruction + restore state
            ir = nir;
            break;
        case IR.OrStart:
            immutable len = ir[0].data;
            auto nir = ir[ir[0].length .. ir[0].length+len];
            r = ctGenAlternation(nir, addr);
            ir = ir[ir[0].length + len .. $];
            assert(ir[0].code == IR.OrEnd);
            ir = ir[ir[0].length..$];
            break;
        case IR.LookaheadStart:
        case IR.NeglookaheadStart:
        case IR.LookbehindStart:
        case IR.NeglookbehindStart:
            immutable len = ir[0].data;
            immutable behind = ir[0].code == IR.LookbehindStart || ir[0].code == IR.NeglookbehindStart;
            immutable negative = ir[0].code == IR.NeglookaheadStart || ir[0].code == IR.NeglookbehindStart;
            string fwdType = "typeof(fwdMatcher(re, []))";
            string bwdType = "typeof(bwdMatcher(re, []))";
            string fwdCreate = "fwdMatcher(re, mem)";
            string bwdCreate = "bwdMatcher(re, mem)";
            immutable start = IRL!(IR.LookbehindStart);
            immutable end = IRL!(IR.LookbehindStart)+len+IRL!(IR.LookaheadEnd);
            CtContext context = lookaround(ir[1].raw, ir[2].raw); //split off new context
            auto slice = ir[start .. end];
            r.code ~= ctSub(`
            case $$: //fake lookaround "atom"
                    static if (typeof(matcher.s).isLoopback)
                        alias Lookaround = $$;
                    else
                        alias Lookaround = $$;
                    static bool matcher_$$(Lookaround matcher) @trusted
                    {
                        //(neg)lookaround piece start
                        $$
                        //(neg)lookaround piece ends
                    }
                    auto save = index;
                    auto mem = pureMalloc(initialMemory(re))[0 .. initialMemory(re)];
                    scope(exit) pureFree(mem.ptr);
                    static if (typeof(matcher.s).isLoopback)
                        auto lookaround = $$;
                    else
                        auto lookaround = $$;
                    lookaround.matches = matches[$$..$$];
                    lookaround.backrefed = backrefed.empty ? matches : backrefed;
                    lookaround.nativeFn = &matcher_$$; //hookup closure's binary code
                    int match = $$;
                    s.reset(save);
                    next();
                    if (match)
                        $$
                    else
                        $$`, addr,
                        behind ? fwdType : bwdType, behind ? bwdType : fwdType,
                        addr, context.ctGenRegEx(slice),
                        behind ? fwdCreate : bwdCreate, behind ? bwdCreate : fwdCreate,
                        ir[1].raw, ir[2].raw, //start - end of matches slice
                        addr,
                        negative ? "!lookaround.matchImpl()" : "lookaround.matchImpl()",
                        nextInstr, bailOut);
            ir = ir[end .. $];
            r.addr = addr + 1;
            break;
        case IR.LookaheadEnd: case IR.NeglookaheadEnd:
        case IR.LookbehindEnd: case IR.NeglookbehindEnd:
            ir = ir[IRL!(IR.LookaheadEnd) .. $];
            r.addr = addr;
            break;
        default:
            assert(ir[0].isAtom,  text(ir[0].mnemonic));
            r = ctGenAtom(ir, addr);
        }
        return r;
    }

    //generate source for bytecode contained  in OrStart ... OrEnd
    CtState ctGenAlternation(const(Bytecode)[] ir, int addr)
    {
        CtState[] pieces;
        CtState r;
        enum optL = IRL!(IR.Option);
        for (;;)
        {
            assert(ir[0].code == IR.Option);
            auto len = ir[0].data;
            if (optL+len < ir.length  && ir[optL+len].code == IR.Option)//not a last option
            {
                auto nir = ir[optL .. optL+len-IRL!(IR.GotoEndOr)];
                r = ctGenBlock(nir, addr+2);//space for Option + restore state
                //r.addr+1 to account GotoEndOr  at end of branch
                r.code = ctGenFixupCode(ir[0 .. ir[0].length], addr, r.addr+1) ~ r.code;
                addr = r.addr+1;//leave space for GotoEndOr
                pieces ~= r;
                ir = ir[optL + len .. $];
            }
            else
            {
                pieces ~= ctGenBlock(ir[optL..$], addr);
                addr = pieces[$-1].addr;
                break;
            }
        }
        r = pieces[0];
        for (uint i = 1; i < pieces.length; i++)
        {
            r.code ~= ctSub(`
                case $$:
                    goto case $$; `, pieces[i-1].addr, addr);
            r.code ~= pieces[i].code;
        }
        r.addr = addr;
        return r;
    }

    // generate fixup code for instruction in ir,
    // fixup means it has an alternative way for control flow
    string ctGenFixupCode(const(Bytecode)[] ir, int addr, int fixup)
    {
        return ctGenFixupCode(ir, addr, fixup); // call ref Bytecode[] version
    }
    string ctGenFixupCode(ref const(Bytecode)[] ir, int addr, int fixup)
    {
        string r;
        string testCode;
        r = ctSub(`
                case $$: debug(std_regex_matcher) writeln("#$$");`,
                    addr, addr);
        switch (ir[0].code)
        {
        case IR.InfiniteStart, IR.InfiniteQStart, IR.InfiniteBloomStart:
            r ~= ctSub( `
                    goto case $$;`, fixup);
            ir = ir[ir[0].length..$];
            break;
        case IR.InfiniteEnd:
            testCode = ctQuickTest(ir[IRL!(IR.InfiniteEnd) .. $],addr + 1);
            r ~= ctSub( `
                    if (merge[$$+counter].mark(index))
                    {
                        // merged!
                        goto L_backtrack;
                    }

                    $$
                    {
                        $$
                    }
                    goto case $$;
                case $$: //restore state and go out of loop
                    $$
                    goto case;`, ir[1].raw, testCode, saveCode(addr+1), fixup,
                    addr+1, restoreCode());
            ir = ir[ir[0].length..$];
            break;
        case IR.InfiniteBloomEnd:
            //TODO: check bloom filter and skip on failure
            testCode = ctQuickTest(ir[IRL!(IR.InfiniteBloomEnd) .. $],addr + 1);
            r ~= ctSub( `
                    if (merge[$$+counter].mark(index))
                    {
                        // merged!
                        goto L_backtrack;
                    }

                    $$
                    {
                        $$
                    }
                    goto case $$;
                case $$: //restore state and go out of loop
                    $$
                    goto case;`, ir[1].raw, testCode, saveCode(addr+1), fixup,
                    addr+1, restoreCode());
            ir = ir[ir[0].length..$];
            break;
        case IR.InfiniteQEnd:
            testCode = ctQuickTest(ir[IRL!(IR.InfiniteEnd) .. $],addr + 1);
            auto altCode = testCode.length ? ctSub("else goto case $$;", fixup) : "";
            r ~= ctSub( `
                    if (merge[$$+counter].mark(index))
                    {
                        // merged!
                        goto L_backtrack;
                    }

                    $$
                    {
                        $$
                        goto case $$;
                    }
                    $$
                case $$://restore state and go inside loop
                    $$
                    goto case $$;`, ir[1].raw,
                    testCode, saveCode(addr+1), addr+2, altCode,
                    addr+1, restoreCode(), fixup);
            ir = ir[ir[0].length..$];
            break;
        case IR.RepeatStart, IR.RepeatQStart:
            r ~= ctSub( `
                    goto case $$;`, fixup);
            ir = ir[ir[0].length..$];
            break;
         case IR.RepeatEnd, IR.RepeatQEnd:
            //len, step, min, max
            immutable len = ir[0].data;
            immutable step = ir[2].raw;
            immutable min = ir[3].raw;
            immutable max = ir[4].raw;
            r ~= ctSub(`
                    if (merge[$$+counter].mark(index))
                    {
                        // merged!
                        goto L_backtrack;
                    }
                    if (counter < $$)
                    {
                        debug(std_regex_matcher) writeln("RepeatEnd min case pc=", $$);
                        counter += $$;
                        goto case $$;
                    }`,  ir[1].raw, min, addr, step, fixup);
            if (ir[0].code == IR.RepeatEnd)
            {
                string counter_expr = ctSub("counter % $$", step);
                r ~= ctSub(`
                    else if (counter < $$)
                    {
                            $$
                            counter += $$;
                            goto case $$;
                    }`, max, saveCode(addr+1, counter_expr), step, fixup);
            }
            else
            {
                string counter_expr = ctSub("counter % $$", step);
                r ~= ctSub(`
                    else if (counter < $$)
                    {
                        $$
                        counter = counter % $$;
                        goto case $$;
                    }`, max, saveCode(addr+1,counter_expr), step, addr+2);
            }
            r ~= ctSub(`
                    else
                    {
                        counter = counter % $$;
                        goto case $$;
                    }
                case $$: //restore state
                    $$
                    goto case $$;`, step, addr+2, addr+1, restoreCode(),
                    ir[0].code == IR.RepeatEnd ? addr+2 : fixup );
            ir = ir[ir[0].length..$];
            break;
        case IR.Option:
            r ~= ctSub( `
                {
                    $$
                }
                goto case $$;
            case $$://restore thunk to go to the next group
                $$
                goto case $$;`, saveCode(addr+1), addr+2,
                    addr+1, restoreCode(), fixup);
                ir = ir[ir[0].length..$];
            break;
        default:
            assert(0, text(ir[0].mnemonic));
        }
        return r;
    }


    string ctQuickTest(const(Bytecode)[] ir, int id)
    {
        uint pc = 0;
        while (pc < ir.length && ir[pc].isAtom)
        {
            if (ir[pc].code == IR.GroupStart || ir[pc].code == IR.GroupEnd)
            {
                pc++;
            }
            else if (ir[pc].code == IR.Backref)
                break;
            else
            {
                auto code = ctAtomCode(ir[pc..$], -1);
                return ctSub(`
                    int test_$$()
                    {
                        $$ //$$
                    }
                    if (test_$$() >= 0)`, id, code.ptr ? code : "return 0;",
                        ir[pc].mnemonic, id);
            }
        }
        return "";
    }

    //process & generate source for simple bytecodes at front of ir using address addr
    CtState ctGenAtom(ref const(Bytecode)[] ir, int addr)
    {
        CtState result;
        result.code = ctAtomCode(ir, addr);
        ir.popFrontN(ir[0].code == IR.OrChar ? ir[0].sequence : ir[0].length);
        result.addr = addr + 1;
        return result;
    }

    //D code for atom at ir using address addr, addr < 0 means quickTest
    string ctAtomCode(const(Bytecode)[] ir, int addr)
    {
        string code;
        string bailOut, nextInstr;
        if (addr < 0)
        {
            bailOut = "return -1;";
            nextInstr = "return 0;";
        }
        else
        {
            bailOut = "goto L_backtrack;";
            nextInstr = ctSub("goto case $$;", addr+1);
            code ~=  ctSub( `
                 case $$: debug(std_regex_matcher) writeln("#$$");
                    `, addr, addr);
        }
        switch (ir[0].code)
        {
        case IR.OrChar://assumes IRL!(OrChar) == 1
            code ~=  ctSub(`
                    if (atEnd)
                        $$`, bailOut);
            immutable len = ir[0].sequence;
            for (uint i = 0; i < len; i++)
            {
                code ~= ctSub( `
                    if (front == $$)
                    {
                        $$
                        $$
                    }`,   ir[i].data, addr >= 0 ? "next();" :"", nextInstr);
            }
            code ~= ctSub( `
                $$`, bailOut);
            break;
        case IR.Char:
            code ~= ctSub( `
                    if (atEnd || front != $$)
                        $$
                    $$
                    $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr);
            break;
        case IR.Any:
            code ~= ctSub( `
                    if (atEnd || (!(re.flags & RegexOption.singleline)
                                && (front == '\r' || front == '\n')))
                        $$
                    $$
                    $$`, bailOut, addr >= 0 ? "next();" :"",nextInstr);
            break;
        case IR.CodepointSet:
            if (charsets.length)
            {
                string name = `func_`~to!string(addr+1);
                string funcCode = CodepointSet.toSourceCode(charsets[ir[0].data], name);
                code ~= ctSub( `
                    static $$
                    if (atEnd || !$$(front))
                        $$
                    $$
                $$`, funcCode, name, bailOut, addr >= 0 ? "next();" :"", nextInstr);
            }
            else
                code ~= ctSub( `
                    if (atEnd || !re.charsets[$$].scanFor(front))
                        $$
                    $$
                $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr);
            break;
        case IR.Trie:
            if (charsets.length && charsets[ir[0].data].length  <= 8)
                goto case IR.CodepointSet;
            code ~= ctSub( `
                    if (atEnd || !re.matchers[$$][front])
                        $$
                    $$
                $$`, ir[0].data, bailOut, addr >= 0 ? "next();" :"", nextInstr);
            break;
        case IR.Wordboundary:
            code ~= ctSub( `
                    dchar back;
                    DataIndex bi;
                    if (atStart && wordMatcher[front])
                    {
                        $$
                    }
                    else if (atEnd && s.loopBack(index).nextChar(back, bi)
                            && wordMatcher[back])
                    {
                        $$
                    }
                    else if (s.loopBack(index).nextChar(back, bi))
                    {
                        bool af = wordMatcher[front];
                        bool ab = wordMatcher[back];
                        if (af ^ ab)
                        {
                            $$
                        }
                    }
                    $$`, nextInstr, nextInstr, nextInstr, bailOut);
            break;
        case IR.Notwordboundary:
            code ~= ctSub( `
                    dchar back;
                    DataIndex bi;
                    //at start & end of input
                    if (atStart && wordMatcher[front])
                        $$
                    else if (atEnd && s.loopBack(index).nextChar(back, bi)
                            && wordMatcher[back])
                        $$
                    else if (s.loopBack(index).nextChar(back, bi))
                    {
                        bool af = wordMatcher[front];
                        bool ab = wordMatcher[back];
                        if (af ^ ab)
                            $$
                    }
                    $$`, bailOut, bailOut, bailOut, nextInstr);

            break;
        case IR.Bol:
            code ~= ctSub(`
                    dchar back;
                    DataIndex bi;
                    if (atStart || (s.loopBack(index).nextChar(back,bi)
                        && endOfLine(back, front == '\n')))
                    {
                        debug(std_regex_matcher) writeln("BOL matched");
                        $$
                    }
                    else
                        $$`, nextInstr, bailOut);

            break;
        case IR.Bof:
            code ~= ctSub(`
                    if (atStart)
                    {
                        debug(std_regex_matcher) writeln("BOF matched");
                        $$
                    }
                    else
                        $$`, nextInstr, bailOut);
            break;
        case IR.Eol:
            code ~= ctSub(`
                    dchar back;
                    DataIndex bi;
                    debug(std_regex_matcher) writefln("EOL (front 0x%x) %s", front, s[index .. s.lastIndex]);
                    //no matching inside \r\n
                    if (atEnd || (endOfLine(front, s.loopBack(index).nextChar(back,bi)
                             && back == '\r')))
                    {
                        debug(std_regex_matcher) writeln("EOL matched");
                        $$
                    }
                    else
                        $$`, nextInstr, bailOut);
            break;
        case IR.Eof:
            code ~= ctSub(`
                    if (atEnd)
                    {
                        debug(std_regex_matcher) writeln("BOF matched");
                        $$
                    }
                    else
                        $$`, nextInstr, bailOut);
            break;
        case IR.GroupStart:
            code ~= ctSub(`
                    matches[$$].begin = index;
                    $$`, ir[0].data, nextInstr);
            match = ir[0].data+1;
            break;
        case IR.GroupEnd:
            code ~= ctSub(`
                    matches[$$].end = index;
                    $$`, ir[0].data, nextInstr);
            break;
        case IR.Backref:
            string mStr = "auto referenced = ";
            mStr ~= ir[0].localRef
                ? ctSub("s[matches[$$].begin .. matches[$$].end];",
                    ir[0].data, ir[0].data)
                : ctSub("s[backrefed[$$].begin .. backrefed[$$].end];",
                    ir[0].data, ir[0].data);
            code ~= ctSub( `
                    $$
                    while (!atEnd && !referenced.empty && front == referenced.front)
                    {
                        next();
                        referenced.popFront();
                    }
                    if (referenced.empty)
                        $$
                    else
                        $$`, mStr, nextInstr, bailOut);
            break;
        case IR.Nop:
        case IR.End:
            break;
        default:
            assert(0, text(ir[0].mnemonic, " is not supported yet"));
        }
        return code;
    }

    //generate D code for the whole regex
    public string ctGenRegEx(const(Bytecode)[] ir)
    {
        auto bdy = ctGenBlock(ir, 0);
        auto r = `
            import core.memory : pureMalloc, pureFree;
            with(matcher)
            {
            pc = 0;
            counter = 0;
            lastState = 0;
            matches[] = Group!DataIndex.init;
            auto start = s._index;`;
        r ~= `
            goto StartLoop;
            debug(std_regex_matcher) writeln("Try CT matching  starting at ",s[index .. s.lastIndex]);
        L_backtrack:
            if (lastState || prevStack())
            {
                stackPop(pc);
                stackPop(index);
                s.reset(index);
                next();
            }
            else
            {
                s.reset(start);
                return false;
            }
        StartLoop:
            switch (pc)
            {
        `;
        r ~= bdy.code;
        r ~= ctSub(`
                case $$: break;`,bdy.addr);
        r ~= `
            default:
                assert(0);
            }
            // cleanup stale stack blocks
            while (prevStack()) {}
            return true;
            }
        `;
        return r;
    }

}

string ctGenRegExCode(Char)(const Regex!Char re)
{
    auto context = CtContext(re);
    return context.ctGenRegEx(re.ir);
}
