"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var chunker_1 = tslib_1.__importDefault(require("./chunker"));
exports.default = (function () {
    var // Less input string
    input;
    var // current chunk
    j;
    var // holds state for backtracking
    saveStack = [];
    var // furthest index the parser has gone to
    furthest;
    var // if this is furthest we got to, this is the probably cause
    furthestPossibleErrorMessage;
    var // chunkified input
    chunks;
    var // current chunk
    current;
    var // index of current chunk, in `input`
    currentPos;
    var parserInput = {};
    var CHARCODE_SPACE = 32;
    var CHARCODE_TAB = 9;
    var CHARCODE_LF = 10;
    var CHARCODE_CR = 13;
    var CHARCODE_PLUS = 43;
    var CHARCODE_COMMA = 44;
    var CHARCODE_FORWARD_SLASH = 47;
    var CHARCODE_9 = 57;
    function skipWhitespace(length) {
        var oldi = parserInput.i;
        var oldj = j;
        var curr = parserInput.i - currentPos;
        var endIndex = parserInput.i + current.length - curr;
        var mem = (parserInput.i += length);
        var inp = input;
        var c;
        var nextChar;
        var comment;
        for (; parserInput.i < endIndex; parserInput.i++) {
            c = inp.charCodeAt(parserInput.i);
            if (parserInput.autoCommentAbsorb && c === CHARCODE_FORWARD_SLASH) {
                nextChar = inp.charAt(parserInput.i + 1);
                if (nextChar === '/') {
                    comment = { index: parserInput.i, isLineComment: true };
                    var nextNewLine = inp.indexOf('\n', parserInput.i + 2);
                    if (nextNewLine < 0) {
                        nextNewLine = endIndex;
                    }
                    parserInput.i = nextNewLine;
                    comment.text = inp.substr(comment.index, parserInput.i - comment.index);
                    parserInput.commentStore.push(comment);
                    continue;
                }
                else if (nextChar === '*') {
                    var nextStarSlash = inp.indexOf('*/', parserInput.i + 2);
                    if (nextStarSlash >= 0) {
                        comment = {
                            index: parserInput.i,
                            text: inp.substr(parserInput.i, nextStarSlash + 2 - parserInput.i),
                            isLineComment: false
                        };
                        parserInput.i += comment.text.length - 1;
                        parserInput.commentStore.push(comment);
                        continue;
                    }
                }
                break;
            }
            if ((c !== CHARCODE_SPACE) && (c !== CHARCODE_LF) && (c !== CHARCODE_TAB) && (c !== CHARCODE_CR)) {
                break;
            }
        }
        current = current.slice(length + parserInput.i - mem + curr);
        currentPos = parserInput.i;
        if (!current.length) {
            if (j < chunks.length - 1) {
                current = chunks[++j];
                skipWhitespace(0); // skip space at the beginning of a chunk
                return true; // things changed
            }
            parserInput.finished = true;
        }
        return oldi !== parserInput.i || oldj !== j;
    }
    parserInput.save = function () {
        currentPos = parserInput.i;
        saveStack.push({ current: current, i: parserInput.i, j: j });
    };
    parserInput.restore = function (possibleErrorMessage) {
        if (parserInput.i > furthest || (parserInput.i === furthest && possibleErrorMessage && !furthestPossibleErrorMessage)) {
            furthest = parserInput.i;
            furthestPossibleErrorMessage = possibleErrorMessage;
        }
        var state = saveStack.pop();
        current = state.current;
        currentPos = parserInput.i = state.i;
        j = state.j;
    };
    parserInput.forget = function () {
        saveStack.pop();
    };
    parserInput.isWhitespace = function (offset) {
        var pos = parserInput.i + (offset || 0);
        var code = input.charCodeAt(pos);
        return (code === CHARCODE_SPACE || code === CHARCODE_CR || code === CHARCODE_TAB || code === CHARCODE_LF);
    };
    // Specialization of $(tok)
    parserInput.$re = function (tok) {
        if (parserInput.i > currentPos) {
            current = current.slice(parserInput.i - currentPos);
            currentPos = parserInput.i;
        }
        var m = tok.exec(current);
        if (!m) {
            return null;
        }
        skipWhitespace(m[0].length);
        if (typeof m === 'string') {
            return m;
        }
        return m.length === 1 ? m[0] : m;
    };
    parserInput.$char = function (tok) {
        if (input.charAt(parserInput.i) !== tok) {
            return null;
        }
        skipWhitespace(1);
        return tok;
    };
    parserInput.$str = function (tok) {
        var tokLength = tok.length;
        // https://jsperf.com/string-startswith/21
        for (var i = 0; i < tokLength; i++) {
            if (input.charAt(parserInput.i + i) !== tok.charAt(i)) {
                return null;
            }
        }
        skipWhitespace(tokLength);
        return tok;
    };
    parserInput.$quoted = function (loc) {
        var pos = loc || parserInput.i;
        var startChar = input.charAt(pos);
        if (startChar !== '\'' && startChar !== '"') {
            return;
        }
        var length = input.length;
        var currentPosition = pos;
        for (var i = 1; i + currentPosition < length; i++) {
            var nextChar = input.charAt(i + currentPosition);
            switch (nextChar) {
                case '\\':
                    i++;
                    continue;
                case '\r':
                case '\n':
                    break;
                case startChar:
                    var str = input.substr(currentPosition, i + 1);
                    if (!loc && loc !== 0) {
                        skipWhitespace(i + 1);
                        return str;
                    }
                    return [startChar, str];
                default:
            }
        }
        return null;
    };
    /**
     * Permissive parsing. Ignores everything except matching {} [] () and quotes
     * until matching token (outside of blocks)
     */
    parserInput.$parseUntil = function (tok) {
        var quote = '';
        var returnVal = null;
        var inComment = false;
        var blockDepth = 0;
        var blockStack = [];
        var parseGroups = [];
        var length = input.length;
        var startPos = parserInput.i;
        var lastPos = parserInput.i;
        var i = parserInput.i;
        var loop = true;
        var testChar;
        if (typeof tok === 'string') {
            testChar = function (char) { return char === tok; };
        }
        else {
            testChar = function (char) { return tok.test(char); };
        }
        do {
            var prevChar = void 0;
            var nextChar = input.charAt(i);
            if (blockDepth === 0 && testChar(nextChar)) {
                returnVal = input.substr(lastPos, i - lastPos);
                if (returnVal) {
                    parseGroups.push(returnVal);
                }
                else {
                    parseGroups.push(' ');
                }
                returnVal = parseGroups;
                skipWhitespace(i - startPos);
                loop = false;
            }
            else {
                if (inComment) {
                    if (nextChar === '*' &&
                        input.charAt(i + 1) === '/') {
                        i++;
                        blockDepth--;
                        inComment = false;
                    }
                    i++;
                    continue;
                }
                switch (nextChar) {
                    case '\\':
                        i++;
                        nextChar = input.charAt(i);
                        parseGroups.push(input.substr(lastPos, i - lastPos + 1));
                        lastPos = i + 1;
                        break;
                    case '/':
                        if (input.charAt(i + 1) === '*') {
                            i++;
                            inComment = true;
                            blockDepth++;
                        }
                        break;
                    case '\'':
                    case '"':
                        quote = parserInput.$quoted(i);
                        if (quote) {
                            parseGroups.push(input.substr(lastPos, i - lastPos), quote);
                            i += quote[1].length - 1;
                            lastPos = i + 1;
                        }
                        else {
                            skipWhitespace(i - startPos);
                            returnVal = nextChar;
                            loop = false;
                        }
                        break;
                    case '{':
                        blockStack.push('}');
                        blockDepth++;
                        break;
                    case '(':
                        blockStack.push(')');
                        blockDepth++;
                        break;
                    case '[':
                        blockStack.push(']');
                        blockDepth++;
                        break;
                    case '}':
                    case ')':
                    case ']':
                        var expected = blockStack.pop();
                        if (nextChar === expected) {
                            blockDepth--;
                        }
                        else {
                            // move the parser to the error and return expected
                            skipWhitespace(i - startPos);
                            returnVal = expected;
                            loop = false;
                        }
                }
                i++;
                if (i > length) {
                    loop = false;
                }
            }
            prevChar = nextChar;
        } while (loop);
        return returnVal ? returnVal : null;
    };
    parserInput.autoCommentAbsorb = true;
    parserInput.commentStore = [];
    parserInput.finished = false;
    // Same as $(), but don't change the state of the parser,
    // just return the match.
    parserInput.peek = function (tok) {
        if (typeof tok === 'string') {
            // https://jsperf.com/string-startswith/21
            for (var i = 0; i < tok.length; i++) {
                if (input.charAt(parserInput.i + i) !== tok.charAt(i)) {
                    return false;
                }
            }
            return true;
        }
        else {
            return tok.test(current);
        }
    };
    // Specialization of peek()
    // TODO remove or change some currentChar calls to peekChar
    parserInput.peekChar = function (tok) { return input.charAt(parserInput.i) === tok; };
    parserInput.currentChar = function () { return input.charAt(parserInput.i); };
    parserInput.prevChar = function () { return input.charAt(parserInput.i - 1); };
    parserInput.getInput = function () { return input; };
    parserInput.peekNotNumeric = function () {
        var c = input.charCodeAt(parserInput.i);
        // Is the first char of the dimension 0-9, '.', '+' or '-'
        return (c > CHARCODE_9 || c < CHARCODE_PLUS) || c === CHARCODE_FORWARD_SLASH || c === CHARCODE_COMMA;
    };
    parserInput.start = function (str, chunkInput, failFunction) {
        input = str;
        parserInput.i = j = currentPos = furthest = 0;
        // chunking apparently makes things quicker (but my tests indicate
        // it might actually make things slower in node at least)
        // and it is a non-perfect parse - it can't recognise
        // unquoted urls, meaning it can't distinguish comments
        // meaning comments with quotes or {}() in them get 'counted'
        // and then lead to parse errors.
        // In addition if the chunking chunks in the wrong place we might
        // not be able to parse a parser statement in one go
        // this is officially deprecated but can be switched on via an option
        // in the case it causes too much performance issues.
        if (chunkInput) {
            chunks = chunker_1.default(str, failFunction);
        }
        else {
            chunks = [str];
        }
        current = chunks[0];
        skipWhitespace(0);
    };
    parserInput.end = function () {
        var message;
        var isFinished = parserInput.i >= input.length;
        if (parserInput.i < furthest) {
            message = furthestPossibleErrorMessage;
            parserInput.i = furthest;
        }
        return {
            isFinished: isFinished,
            furthest: parserInput.i,
            furthestPossibleErrorMessage: message,
            furthestReachedEnd: parserInput.i >= input.length - 1,
            furthestChar: input[parserInput.i]
        };
    };
    return parserInput;
});
//# sourceMappingURL=parser-input.js.map