File: /var/www/tana/frontend/node_modules/csso/lib/parser/index.js
'use strict';
var TokenType = require('./const').TokenType;
var Scanner = require('./scanner');
var List = require('../utils/list');
var needPositions;
var filename;
var scanner;
var SCOPE_ATRULE_EXPRESSION = 1;
var SCOPE_SELECTOR = 2;
var SCOPE_VALUE = 3;
var specialFunctions = {};
specialFunctions[SCOPE_ATRULE_EXPRESSION] = {
    url: getUri
};
specialFunctions[SCOPE_SELECTOR] = {
    url: getUri,
    not: getNotFunction
};
specialFunctions[SCOPE_VALUE] = {
    url: getUri,
    expression: getOldIEExpression,
    var: getVarFunction
};
var initialContext = {
    stylesheet: getStylesheet,
    atrule: getAtrule,
    atruleExpression: getAtruleExpression,
    ruleset: getRuleset,
    selector: getSelector,
    simpleSelector: getSimpleSelector,
    block: getBlock,
    declaration: getDeclaration,
    value: getValue
};
var blockMode = {
    'declaration': true,
    'property': true
};
function parseError(message) {
    var error = new Error(message);
    var offset = 0;
    var line = 1;
    var column = 1;
    var lines;
    if (scanner.token !== null) {
        offset = scanner.token.offset;
        line = scanner.token.line;
        column = scanner.token.column;
    } else if (scanner.prevToken !== null) {
        lines = scanner.prevToken.value.trimRight();
        offset = scanner.prevToken.offset + lines.length;
        lines = lines.split(/\n|\r\n?|\f/);
        line = scanner.prevToken.line + lines.length - 1;
        column = lines.length > 1
            ? lines[lines.length - 1].length + 1
            : scanner.prevToken.column + lines[lines.length - 1].length;
    }
    error.name = 'CssSyntaxError';
    error.parseError = {
        offset: offset,
        line: line,
        column: column
    };
    throw error;
}
function eat(tokenType) {
    if (scanner.token !== null && scanner.token.type === tokenType) {
        scanner.next();
        return true;
    }
    parseError(tokenType + ' is expected');
}
function expectIdentifier(name, eat) {
    if (scanner.token !== null) {
        if (scanner.token.type === TokenType.Identifier &&
            scanner.token.value.toLowerCase() === name) {
            if (eat) {
                scanner.next();
            }
            return true;
        }
    }
    parseError('Identifier `' + name + '` is expected');
}
function expectAny(what) {
    if (scanner.token !== null) {
        for (var i = 1, type = scanner.token.type; i < arguments.length; i++) {
            if (type === arguments[i]) {
                return true;
            }
        }
    }
    parseError(what + ' is expected');
}
function getInfo() {
    if (needPositions && scanner.token) {
        return {
            source: filename,
            offset: scanner.token.offset,
            line: scanner.token.line,
            column: scanner.token.column
        };
    }
    return null;
}
function removeTrailingSpaces(list) {
    while (list.tail) {
        if (list.tail.data.type === 'Space') {
            list.remove(list.tail);
        } else {
            break;
        }
    }
}
function getStylesheet(nested) {
    var child = null;
    var node = {
        type: 'StyleSheet',
        info: getInfo(),
        rules: new List()
    };
    scan:
    while (scanner.token !== null) {
        switch (scanner.token.type) {
            case TokenType.Space:
                scanner.next();
                child = null;
                break;
            case TokenType.Comment:
                // ignore comments except exclamation comments on top level
                if (nested || scanner.token.value.charAt(2) !== '!') {
                    scanner.next();
                    child = null;
                } else {
                    child = getComment();
                }
                break;
            case TokenType.Unknown:
                child = getUnknown();
                break;
            case TokenType.CommercialAt:
                child = getAtrule();
                break;
            case TokenType.RightCurlyBracket:
                if (!nested) {
                    parseError('Unexpected right curly brace');
                }
                break scan;
            default:
                child = getRuleset();
        }
        if (child !== null) {
            node.rules.insert(List.createItem(child));
        }
    }
    return node;
}
// '//' ...
// TODO: remove it as wrong thing
function getUnknown() {
    var info = getInfo();
    var value = scanner.token.value;
    eat(TokenType.Unknown);
    return {
        type: 'Unknown',
        info: info,
        value: value
    };
}
function isBlockAtrule() {
    for (var offset = 1, cursor; cursor = scanner.lookup(offset); offset++) {
        var type = cursor.type;
        if (type === TokenType.RightCurlyBracket) {
            return true;
        }
        if (type === TokenType.LeftCurlyBracket ||
            type === TokenType.CommercialAt) {
            return false;
        }
    }
    return true;
}
function getAtruleExpression() {
    var child = null;
    var node = {
        type: 'AtruleExpression',
        info: getInfo(),
        sequence: new List()
    };
    scan:
    while (scanner.token !== null) {
        switch (scanner.token.type) {
            case TokenType.Semicolon:
                break scan;
            case TokenType.LeftCurlyBracket:
                break scan;
            case TokenType.Space:
                if (node.sequence.isEmpty()) {
                    scanner.next(); // ignore spaces in beginning
                    child = null;
                } else {
                    child = getS();
                }
                break;
            case TokenType.Comment: // ignore comments
                scanner.next();
                child = null;
                break;
            case TokenType.Comma:
                child = getOperator();
                break;
            case TokenType.Colon:
                child = getPseudo();
                break;
            case TokenType.LeftParenthesis:
                child = getBraces(SCOPE_ATRULE_EXPRESSION);
                break;
            default:
                child = getAny(SCOPE_ATRULE_EXPRESSION);
        }
        if (child !== null) {
            node.sequence.insert(List.createItem(child));
        }
    }
    removeTrailingSpaces(node.sequence);
    return node;
}
function getAtrule() {
    eat(TokenType.CommercialAt);
    var node = {
        type: 'Atrule',
        info: getInfo(),
        name: readIdent(false),
        expression: getAtruleExpression(),
        block: null
    };
    if (scanner.token !== null) {
        switch (scanner.token.type) {
            case TokenType.Semicolon:
                scanner.next();  // {
                break;
            case TokenType.LeftCurlyBracket:
                scanner.next();  // {
                if (isBlockAtrule()) {
                    node.block = getBlock();
                } else {
                    node.block = getStylesheet(true);
                }
                eat(TokenType.RightCurlyBracket);
                break;
            default:
                parseError('Unexpected input');
        }
    }
    return node;
}
function getRuleset() {
    return {
        type: 'Ruleset',
        info: getInfo(),
        selector: getSelector(),
        block: getBlockWithBrackets()
    };
}
function getSelector() {
    var isBadSelector = false;
    var lastComma = true;
    var node = {
        type: 'Selector',
        info: getInfo(),
        selectors: new List()
    };
    scan:
    while (scanner.token !== null) {
        switch (scanner.token.type) {
            case TokenType.LeftCurlyBracket:
                break scan;
            case TokenType.Comma:
                if (lastComma) {
                    isBadSelector = true;
                }
                lastComma = true;
                scanner.next();
                break;
            default:
                if (!lastComma) {
                    isBadSelector = true;
                }
                lastComma = false;
                node.selectors.insert(List.createItem(getSimpleSelector()));
                if (node.selectors.tail.data.sequence.isEmpty()) {
                    isBadSelector = true;
                }
        }
    }
    if (lastComma) {
        isBadSelector = true;
        // parseError('Unexpected trailing comma');
    }
    if (isBadSelector) {
        node.selectors = new List();
    }
    return node;
}
function getSimpleSelector(nested) {
    var child = null;
    var combinator = null;
    var node = {
        type: 'SimpleSelector',
        info: getInfo(),
        sequence: new List()
    };
    scan:
    while (scanner.token !== null) {
        switch (scanner.token.type) {
            case TokenType.Comma:
                break scan;
            case TokenType.LeftCurlyBracket:
                if (nested) {
                    parseError('Unexpected input');
                }
                break scan;
            case TokenType.RightParenthesis:
                if (!nested) {
                    parseError('Unexpected input');
                }
                break scan;
            case TokenType.Comment:
                scanner.next();
                child = null;
                break;
            case TokenType.Space:
                child = null;
                if (!combinator && node.sequence.head) {
                    combinator = getCombinator();
                } else {
                    scanner.next();
                }
                break;
            case TokenType.PlusSign:
            case TokenType.GreaterThanSign:
            case TokenType.Tilde:
            case TokenType.Solidus:
                if (combinator && combinator.name !== ' ') {
                    parseError('Unexpected combinator');
                }
                child = null;
                combinator = getCombinator();
                break;
            case TokenType.FullStop:
                child = getClass();
                break;
            case TokenType.LeftSquareBracket:
                child = getAttribute();
                break;
            case TokenType.NumberSign:
                child = getShash();
                break;
            case TokenType.Colon:
                child = getPseudo();
                break;
            case TokenType.LowLine:
            case TokenType.Identifier:
            case TokenType.Asterisk:
                child = getNamespacedIdentifier(false);
                break;
            case TokenType.HyphenMinus:
            case TokenType.DecimalNumber:
                child = tryGetPercentage() || getNamespacedIdentifier(false);
                break;
            default:
                parseError('Unexpected input');
        }
        if (child !== null) {
            if (combinator !== null) {
                node.sequence.insert(List.createItem(combinator));
                combinator = null;
            }
            node.sequence.insert(List.createItem(child));
        }
    }
    if (combinator && combinator.name !== ' ') {
        parseError('Unexpected combinator');
    }
    return node;
}
function getDeclarations() {
    var child = null;
    var declarations = new List();
    scan:
    while (scanner.token !== null) {
        switch (scanner.token.type) {
            case TokenType.RightCurlyBracket:
                break scan;
            case TokenType.Space:
            case TokenType.Comment:
                scanner.next();
                child = null;
                break;
            case TokenType.Semicolon: // ;
                scanner.next();
                child = null;
                break;
            default:
                child = getDeclaration();
        }
        if (child !== null) {
            declarations.insert(List.createItem(child));
        }
    }
    return declarations;
}
function getBlockWithBrackets() {
    var info = getInfo();
    var node;
    eat(TokenType.LeftCurlyBracket);
    node = {
        type: 'Block',
        info: info,
        declarations: getDeclarations()
    };
    eat(TokenType.RightCurlyBracket);
    return node;
}
function getBlock() {
    return {
        type: 'Block',
        info: getInfo(),
        declarations: getDeclarations()
    };
}
function getDeclaration(nested) {
    var info = getInfo();
    var property = getProperty();
    var value;
    eat(TokenType.Colon);
    // check it's a filter
    if (/filter$/.test(property.name.toLowerCase()) && checkProgid()) {
        value = getFilterValue();
    } else {
        value = getValue(nested);
    }
    return {
        type: 'Declaration',
        info: info,
        property: property,
        value: value
    };
}
function getProperty() {
    var name = '';
    var node = {
        type: 'Property',
        info: getInfo(),
        name: null
    };
    for (; scanner.token !== null; scanner.next()) {
        var type = scanner.token.type;
        if (type !== TokenType.Solidus &&
            type !== TokenType.Asterisk &&
            type !== TokenType.DollarSign) {
            break;
        }
        name += scanner.token.value;
    }
    node.name = name + readIdent(true);
    readSC();
    return node;
}
function getValue(nested) {
    var child = null;
    var node = {
        type: 'Value',
        info: getInfo(),
        important: false,
        sequence: new List()
    };
    readSC();
    scan:
    while (scanner.token !== null) {
        switch (scanner.token.type) {
            case TokenType.RightCurlyBracket:
            case TokenType.Semicolon:
                break scan;
            case TokenType.RightParenthesis:
                if (!nested) {
                    parseError('Unexpected input');
                }
                break scan;
            case TokenType.Space:
                child = getS();
                break;
            case TokenType.Comment: // ignore comments
                scanner.next();
                child = null;
                break;
            case TokenType.NumberSign:
                child = getVhash();
                break;
            case TokenType.Solidus:
            case TokenType.Comma:
                child = getOperator();
                break;
            case TokenType.LeftParenthesis:
            case TokenType.LeftSquareBracket:
                child = getBraces(SCOPE_VALUE);
                break;
            case TokenType.ExclamationMark:
                node.important = getImportant();
                child = null;
                break;
            default:
                // check for unicode range: U+0F00, U+0F00-0FFF, u+0F00??
                if (scanner.token.type === TokenType.Identifier) {
                    var prefix = scanner.token.value;
                    if (prefix === 'U' || prefix === 'u') {
                        if (scanner.lookupType(1, TokenType.PlusSign)) {
                            scanner.next(); // U or u
                            scanner.next(); // +
                            child = {
                                type: 'Identifier',
                                info: getInfo(), // FIXME: wrong position
                                name: prefix + '+' + readUnicodeRange(true)
                            };
                            break;
                        }
                    }
                }
                child = getAny(SCOPE_VALUE);
        }
        if (child !== null) {
            node.sequence.insert(List.createItem(child));
        }
    }
    removeTrailingSpaces(node.sequence);
    return node;
}
// any = string | percentage | dimension | number | uri | functionExpression | funktion | unary | operator | ident
function getAny(scope) {
    switch (scanner.token.type) {
        case TokenType.String:
            return getString();
        case TokenType.LowLine:
        case TokenType.Identifier:
            break;
        case TokenType.FullStop:
        case TokenType.DecimalNumber:
        case TokenType.HyphenMinus:
        case TokenType.PlusSign:
            var number = tryGetNumber();
            if (number !== null) {
                if (scanner.token !== null) {
                    if (scanner.token.type === TokenType.PercentSign) {
                        return getPercentage(number);
                    } else if (scanner.token.type === TokenType.Identifier) {
                        return getDimension(number.value);
                    }
                }
                return number;
            }
            if (scanner.token.type === TokenType.HyphenMinus) {
                var next = scanner.lookup(1);
                if (next && (next.type === TokenType.Identifier || next.type === TokenType.HyphenMinus)) {
                    break;
                }
            }
            if (scanner.token.type === TokenType.HyphenMinus ||
                scanner.token.type === TokenType.PlusSign) {
                return getOperator();
            }
            parseError('Unexpected input');
        default:
            parseError('Unexpected input');
    }
    var ident = getIdentifier(false);
    if (scanner.token !== null && scanner.token.type === TokenType.LeftParenthesis) {
        return getFunction(scope, ident);
    }
    return ident;
}
function readAttrselector() {
    expectAny('Attribute selector (=, ~=, ^=, $=, *=, |=)',
        TokenType.EqualsSign,        // =
        TokenType.Tilde,             // ~=
        TokenType.CircumflexAccent,  // ^=
        TokenType.DollarSign,        // $=
        TokenType.Asterisk,          // *=
        TokenType.VerticalLine       // |=
    );
    var name;
    if (scanner.token.type === TokenType.EqualsSign) {
        name = '=';
        scanner.next();
    } else {
        name = scanner.token.value + '=';
        scanner.next();
        eat(TokenType.EqualsSign);
    }
    return name;
}
// '[' S* attrib_name ']'
// '[' S* attrib_name S* attrib_match S* [ IDENT | STRING ] S* attrib_flags? S* ']'
function getAttribute() {
    var node = {
        type: 'Attribute',
        info: getInfo(),
        name: null,
        operator: null,
        value: null,
        flags: null
    };
    eat(TokenType.LeftSquareBracket);
    readSC();
    node.name = getNamespacedIdentifier(true);
    readSC();
    if (scanner.token !== null && scanner.token.type !== TokenType.RightSquareBracket) {
        // avoid case `[name i]`
        if (scanner.token.type !== TokenType.Identifier) {
            node.operator = readAttrselector();
            readSC();
            if (scanner.token !== null && scanner.token.type === TokenType.String) {
                node.value = getString();
            } else {
                node.value = getIdentifier(false);
            }
            readSC();
        }
        // attribute flags
        if (scanner.token !== null && scanner.token.type === TokenType.Identifier) {
            node.flags = scanner.token.value;
            scanner.next();
            readSC();
        }
    }
    eat(TokenType.RightSquareBracket);
    return node;
}
function getBraces(scope) {
    var close;
    var child = null;
    var node = {
        type: 'Braces',
        info: getInfo(),
        open: scanner.token.value,
        close: null,
        sequence: new List()
    };
    if (scanner.token.type === TokenType.LeftParenthesis) {
        close = TokenType.RightParenthesis;
    } else {
        close = TokenType.RightSquareBracket;
    }
    // left brace
    scanner.next();
    readSC();
    scan:
    while (scanner.token !== null) {
        switch (scanner.token.type) {
            case close:
                node.close = scanner.token.value;
                break scan;
            case TokenType.Space:
                child = getS();
                break;
            case TokenType.Comment:
                scanner.next();
                child = null;
                break;
            case TokenType.NumberSign: // ??
                child = getVhash();
                break;
            case TokenType.LeftParenthesis:
            case TokenType.LeftSquareBracket:
                child = getBraces(scope);
                break;
            case TokenType.Solidus:
            case TokenType.Asterisk:
            case TokenType.Comma:
            case TokenType.Colon:
                child = getOperator();
                break;
            default:
                child = getAny(scope);
        }
        if (child !== null) {
            node.sequence.insert(List.createItem(child));
        }
    }
    removeTrailingSpaces(node.sequence);
    // right brace
    eat(close);
    return node;
}
// '.' ident
function getClass() {
    var info = getInfo();
    eat(TokenType.FullStop);
    return {
        type: 'Class',
        info: info,
        name: readIdent(false)
    };
}
// '#' ident
function getShash() {
    var info = getInfo();
    eat(TokenType.NumberSign);
    return {
        type: 'Id',
        info: info,
        name: readIdent(false)
    };
}
// + | > | ~ | /deep/
function getCombinator() {
    var info = getInfo();
    var combinator;
    switch (scanner.token.type) {
        case TokenType.Space:
            combinator = ' ';
            scanner.next();
            break;
        case TokenType.PlusSign:
        case TokenType.GreaterThanSign:
        case TokenType.Tilde:
            combinator = scanner.token.value;
            scanner.next();
            break;
        case TokenType.Solidus:
            combinator = '/deep/';
            scanner.next();
            expectIdentifier('deep', true);
            eat(TokenType.Solidus);
            break;
        default:
            parseError('Combinator (+, >, ~, /deep/) is expected');
    }
    return {
        type: 'Combinator',
        info: info,
        name: combinator
    };
}
// '/*' .* '*/'
function getComment() {
    var info = getInfo();
    var value = scanner.token.value;
    var len = value.length;
    if (len > 4 && value.charAt(len - 2) === '*' && value.charAt(len - 1) === '/') {
        len -= 2;
    }
    scanner.next();
    return {
        type: 'Comment',
        info: info,
        value: value.substring(2, len)
    };
}
// special reader for units to avoid adjoined IE hacks (i.e. '1px\9')
function readUnit() {
    if (scanner.token !== null && scanner.token.type === TokenType.Identifier) {
        var unit = scanner.token.value;
        var backSlashPos = unit.indexOf('\\');
        // no backslash in unit name
        if (backSlashPos === -1) {
            scanner.next();
            return unit;
        }
        // patch token
        scanner.token.value = unit.substr(backSlashPos);
        scanner.token.offset += backSlashPos;
        scanner.token.column += backSlashPos;
        // return unit w/o backslash part
        return unit.substr(0, backSlashPos);
    }
    parseError('Identifier is expected');
}
// number ident
function getDimension(number) {
    return {
        type: 'Dimension',
        info: getInfo(),
        value: number || readNumber(),
        unit: readUnit()
    };
}
// number "%"
function tryGetPercentage() {
    var number = tryGetNumber();
    if (number && scanner.token !== null && scanner.token.type === TokenType.PercentSign) {
        return getPercentage(number);
    }
    return null;
}
function getPercentage(number) {
    var info;
    if (!number) {
        info = getInfo();
        number = readNumber();
    } else {
        info = number.info;
        number = number.value;
    }
    eat(TokenType.PercentSign);
    return {
        type: 'Percentage',
        info: info,
        value: number
    };
}
// ident '(' functionBody ')' |
// not '(' <simpleSelector>* ')'
function getFunction(scope, ident) {
    var defaultArguments = getFunctionArguments;
    if (!ident) {
        ident = getIdentifier(false);
    }
    // parse special functions
    var name = ident.name.toLowerCase();
    if (specialFunctions.hasOwnProperty(scope)) {
        if (specialFunctions[scope].hasOwnProperty(name)) {
            return specialFunctions[scope][name](scope, ident);
        }
    }
    return getFunctionInternal(defaultArguments, scope, ident);
}
function getFunctionInternal(functionArgumentsReader, scope, ident) {
    var args;
    eat(TokenType.LeftParenthesis);
    args = functionArgumentsReader(scope);
    eat(TokenType.RightParenthesis);
    return {
        type: scope === SCOPE_SELECTOR ? 'FunctionalPseudo' : 'Function',
        info: ident.info,
        name: ident.name,
        arguments: args
    };
}
function getFunctionArguments(scope) {
    var args = new List();
    var argument = null;
    var child = null;
    readSC();
    scan:
    while (scanner.token !== null) {
        switch (scanner.token.type) {
            case TokenType.RightParenthesis:
                break scan;
            case TokenType.Space:
                child = getS();
                break;
            case TokenType.Comment: // ignore comments
                scanner.next();
                child = null;
                break;
            case TokenType.NumberSign: // TODO: not sure it should be here
                child = getVhash();
                break;
            case TokenType.LeftParenthesis:
            case TokenType.LeftSquareBracket:
                child = getBraces(scope);
                break;
            case TokenType.Comma:
                if (argument) {
                    removeTrailingSpaces(argument.sequence);
                } else {
                    args.insert(List.createItem({
                        type: 'Argument',
                        sequence: new List()
                    }));
                }
                scanner.next();
                readSC();
                argument = null;
                child = null;
                break;
            case TokenType.Solidus:
            case TokenType.Asterisk:
            case TokenType.Colon:
            case TokenType.EqualsSign:
                child = getOperator();
                break;
            default:
                child = getAny(scope);
        }
        if (argument === null) {
            argument = {
                type: 'Argument',
                sequence: new List()
            };
            args.insert(List.createItem(argument));
        }
        if (child !== null) {
            argument.sequence.insert(List.createItem(child));
        }
    }
    if (argument !== null) {
        removeTrailingSpaces(argument.sequence);
    }
    return args;
}
function getVarFunction(scope, ident) {
    return getFunctionInternal(getVarFunctionArguments, scope, ident);
}
function getNotFunctionArguments() {
    var args = new List();
    var wasSelector = false;
    scan:
    while (scanner.token !== null) {
        switch (scanner.token.type) {
            case TokenType.RightParenthesis:
                if (!wasSelector) {
                    parseError('Simple selector is expected');
                }
                break scan;
            case TokenType.Comma:
                if (!wasSelector) {
                    parseError('Simple selector is expected');
                }
                wasSelector = false;
                scanner.next();
                break;
            default:
                wasSelector = true;
                args.insert(List.createItem(getSimpleSelector(true)));
        }
    }
    return args;
}
function getNotFunction(scope, ident) {
    var args;
    eat(TokenType.LeftParenthesis);
    args = getNotFunctionArguments(scope);
    eat(TokenType.RightParenthesis);
    return {
        type: 'Negation',
        info: ident.info,
        // name: ident.name,  // TODO: add name?
        sequence: args        // FIXME: -> arguments?
    };
}
// var '(' ident (',' <declaration-value>)? ')'
function getVarFunctionArguments() { // TODO: special type Variable?
    var args = new List();
    readSC();
    args.insert(List.createItem({
        type: 'Argument',
        sequence: new List([getIdentifier(true)])
    }));
    readSC();
    if (scanner.token !== null && scanner.token.type === TokenType.Comma) {
        eat(TokenType.Comma);
        readSC();
        args.insert(List.createItem({
            type: 'Argument',
            sequence: new List([getValue(true)])
        }));
        readSC();
    }
    return args;
}
// url '(' ws* (string | raw) ws* ')'
function getUri(scope, ident) {
    var node = {
        type: 'Url',
        info: ident.info,
        // name: ident.name,
        value: null
    };
    eat(TokenType.LeftParenthesis); // (
    readSC();
    if (scanner.token.type === TokenType.String) {
        node.value = getString();
        readSC();
    } else {
        var rawInfo = getInfo();
        var raw = '';
        for (; scanner.token !== null; scanner.next()) {
            var type = scanner.token.type;
            if (type === TokenType.Space ||
                type === TokenType.LeftParenthesis ||
                type === TokenType.RightParenthesis) {
                break;
            }
            raw += scanner.token.value;
        }
        node.value = {
            type: 'Raw',
            info: rawInfo,
            value: raw
        };
        readSC();
    }
    eat(TokenType.RightParenthesis); // )
    return node;
}
// expression '(' raw ')'
function getOldIEExpression(scope, ident) {
    var balance = 0;
    var raw = '';
    eat(TokenType.LeftParenthesis);
    for (; scanner.token !== null; scanner.next()) {
        if (scanner.token.type === TokenType.RightParenthesis) {
            if (balance === 0) {
                break;
            }
            balance--;
        } else if (scanner.token.type === TokenType.LeftParenthesis) {
            balance++;
        }
        raw += scanner.token.value;
    }
    eat(TokenType.RightParenthesis);
    return {
        type: 'Function',
        info: ident.info,
        name: ident.name,
        arguments: new List([{
            type: 'Argument',
            sequence: new List([{
                type: 'Raw',
                value: raw
            }])
        }])
    };
}
function readUnicodeRange(tryNext) {
    var hex = '';
    for (; scanner.token !== null; scanner.next()) {
        if (scanner.token.type !== TokenType.DecimalNumber &&
            scanner.token.type !== TokenType.Identifier) {
            break;
        }
        hex += scanner.token.value;
    }
    if (!/^[0-9a-f]{1,6}$/i.test(hex)) {
        parseError('Unexpected input');
    }
    // U+abc???
    if (tryNext) {
        for (; hex.length < 6 && scanner.token !== null; scanner.next()) {
            if (scanner.token.type !== TokenType.QuestionMark) {
                break;
            }
            hex += scanner.token.value;
            tryNext = false;
        }
    }
    // U+aaa-bbb
    if (tryNext) {
        if (scanner.token !== null && scanner.token.type === TokenType.HyphenMinus) {
            scanner.next();
            var next = readUnicodeRange(false);
            if (!next) {
                parseError('Unexpected input');
            }
            hex += '-' + next;
        }
    }
    return hex;
}
function readIdent(varAllowed) {
    var name = '';
    // optional first -
    if (scanner.token !== null && scanner.token.type === TokenType.HyphenMinus) {
        name = '-';
        scanner.next();
        if (varAllowed && scanner.token !== null && scanner.token.type === TokenType.HyphenMinus) {
            name = '--';
            scanner.next();
        }
    }
    expectAny('Identifier',
        TokenType.LowLine,
        TokenType.Identifier
    );
    if (scanner.token !== null) {
        name += scanner.token.value;
        scanner.next();
        for (; scanner.token !== null; scanner.next()) {
            var type = scanner.token.type;
            if (type !== TokenType.LowLine &&
                type !== TokenType.Identifier &&
                type !== TokenType.DecimalNumber &&
                type !== TokenType.HyphenMinus) {
                break;
            }
            name += scanner.token.value;
        }
    }
    return name;
}
function getNamespacedIdentifier(checkColon) {
    if (scanner.token === null) {
        parseError('Unexpected end of input');
    }
    var info = getInfo();
    var name;
    if (scanner.token.type === TokenType.Asterisk) {
        checkColon = false;
        name = '*';
        scanner.next();
    } else {
        name = readIdent(false);
    }
    if (scanner.token !== null) {
        if (scanner.token.type === TokenType.VerticalLine &&
            scanner.lookupType(1, TokenType.EqualsSign) === false) {
            name += '|';
            if (scanner.next() !== null) {
                if (scanner.token.type === TokenType.HyphenMinus ||
                    scanner.token.type === TokenType.Identifier ||
                    scanner.token.type === TokenType.LowLine) {
                    name += readIdent(false);
                } else if (scanner.token.type === TokenType.Asterisk) {
                    checkColon = false;
                    name += '*';
                    scanner.next();
                }
            }
        }
    }
    if (checkColon && scanner.token !== null && scanner.token.type === TokenType.Colon) {
        scanner.next();
        name += ':' + readIdent(false);
    }
    return {
        type: 'Identifier',
        info: info,
        name: name
    };
}
function getIdentifier(varAllowed) {
    return {
        type: 'Identifier',
        info: getInfo(),
        name: readIdent(varAllowed)
    };
}
// ! ws* important
function getImportant() { // TODO?
    // var info = getInfo();
    eat(TokenType.ExclamationMark);
    readSC();
    // return {
    //     type: 'Identifier',
    //     info: info,
    //     name: readIdent(false)
    // };
    expectIdentifier('important');
    readIdent(false);
    // should return identifier in future for original source restoring as is
    // returns true for now since it's fit to optimizer purposes
    return true;
}
// odd | even | number? n
function getNth() {
    expectAny('Number, odd or even',
        TokenType.Identifier,
        TokenType.DecimalNumber
    );
    var info = getInfo();
    var value = scanner.token.value;
    var cmpValue;
    if (scanner.token.type === TokenType.DecimalNumber) {
        var next = scanner.lookup(1);
        if (next !== null &&
            next.type === TokenType.Identifier &&
            next.value.toLowerCase() === 'n') {
            value += next.value;
            scanner.next();
        }
    } else {
        var cmpValue = value.toLowerCase();
        if (cmpValue !== 'odd' && cmpValue !== 'even' && cmpValue !== 'n') {
            parseError('Unexpected identifier');
        }
    }
    scanner.next();
    return {
        type: 'Nth',
        info: info,
        value: value
    };
}
function getNthSelector() {
    var info = getInfo();
    var sequence = new List();
    var node;
    var child = null;
    eat(TokenType.Colon);
    expectIdentifier('nth', false);
    node = {
        type: 'FunctionalPseudo',
        info: info,
        name: readIdent(false),
        arguments: new List([{
            type: 'Argument',
            sequence: sequence
        }])
    };
    eat(TokenType.LeftParenthesis);
    scan:
    while (scanner.token !== null) {
        switch (scanner.token.type) {
            case TokenType.RightParenthesis:
                break scan;
            case TokenType.Space:
            case TokenType.Comment:
                scanner.next();
                child = null;
                break;
            case TokenType.HyphenMinus:
            case TokenType.PlusSign:
                child = getOperator();
                break;
            default:
                child = getNth();
        }
        if (child !== null) {
            sequence.insert(List.createItem(child));
        }
    }
    eat(TokenType.RightParenthesis);
    return node;
}
function readNumber() {
    var wasDigits = false;
    var number = '';
    var offset = 0;
    if (scanner.lookupType(offset, TokenType.HyphenMinus)) {
        number = '-';
        offset++;
    }
    if (scanner.lookupType(offset, TokenType.DecimalNumber)) {
        wasDigits = true;
        number += scanner.lookup(offset).value;
        offset++;
    }
    if (scanner.lookupType(offset, TokenType.FullStop)) {
        number += '.';
        offset++;
    }
    if (scanner.lookupType(offset, TokenType.DecimalNumber)) {
        wasDigits = true;
        number += scanner.lookup(offset).value;
        offset++;
    }
    if (wasDigits) {
        while (offset--) {
            scanner.next();
        }
        return number;
    }
    return null;
}
function tryGetNumber() {
    var info = getInfo();
    var number = readNumber();
    if (number !== null) {
        return {
            type: 'Number',
            info: info,
            value: number
        };
    }
    return null;
}
// '/' | '*' | ',' | ':' | '=' | '+' | '-'
// TODO: remove '=' since it's wrong operator, but theat as operator
// to make old things like `filter: alpha(opacity=0)` works
function getOperator() {
    var node = {
        type: 'Operator',
        info: getInfo(),
        value: scanner.token.value
    };
    scanner.next();
    return node;
}
function getFilterValue() { // TODO
    var progid;
    var node = {
        type: 'Value',
        info: getInfo(),
        important: false,
        sequence: new List()
    };
    while (progid = checkProgid()) {
        node.sequence.insert(List.createItem(getProgid(progid)));
    }
    readSC(node);
    if (scanner.token !== null && scanner.token.type === TokenType.ExclamationMark) {
        node.important = getImportant();
    }
    return node;
}
// 'progid:' ws* 'DXImageTransform.Microsoft.' ident ws* '(' .* ')'
function checkProgid() {
    function checkSC(offset) {
        for (var cursor; cursor = scanner.lookup(offset); offset++) {
            if (cursor.type !== TokenType.Space &&
                cursor.type !== TokenType.Comment) {
                break;
            }
        }
        return offset;
    }
    var offset = checkSC(0);
    if (scanner.lookup(offset + 1) === null ||
        scanner.lookup(offset + 0).value.toLowerCase() !== 'progid' ||
        scanner.lookup(offset + 1).type !== TokenType.Colon) {
        return false; // fail
    }
    offset += 2;
    offset = checkSC(offset);
    if (scanner.lookup(offset + 5) === null ||
        scanner.lookup(offset + 0).value.toLowerCase() !== 'dximagetransform' ||
        scanner.lookup(offset + 1).type !== TokenType.FullStop ||
        scanner.lookup(offset + 2).value.toLowerCase() !== 'microsoft' ||
        scanner.lookup(offset + 3).type !== TokenType.FullStop ||
        scanner.lookup(offset + 4).type !== TokenType.Identifier) {
        return false; // fail
    }
    offset += 5;
    offset = checkSC(offset);
    if (scanner.lookupType(offset, TokenType.LeftParenthesis) === false) {
        return false; // fail
    }
    for (var cursor; cursor = scanner.lookup(offset); offset++) {
        if (cursor.type === TokenType.RightParenthesis) {
            return cursor;
        }
    }
    return false;
}
function getProgid(progidEnd) {
    var value = '';
    var node = {
        type: 'Progid',
        info: getInfo(),
        value: null
    };
    if (!progidEnd) {
        progidEnd = checkProgid();
    }
    if (!progidEnd) {
        parseError('progid is expected');
    }
    readSC(node);
    var rawInfo = getInfo();
    for (; scanner.token && scanner.token !== progidEnd; scanner.next()) {
        value += scanner.token.value;
    }
    eat(TokenType.RightParenthesis);
    value += ')';
    node.value = {
        type: 'Raw',
        info: rawInfo,
        value: value
    };
    readSC(node);
    return node;
}
// <pseudo-element> | <nth-selector> | <pseudo-class>
function getPseudo() {
    var next = scanner.lookup(1);
    if (next === null) {
        scanner.next();
        parseError('Colon or identifier is expected');
    }
    if (next.type === TokenType.Colon) {
        return getPseudoElement();
    }
    if (next.type === TokenType.Identifier &&
        next.value.toLowerCase() === 'nth') {
        return getNthSelector();
    }
    return getPseudoClass();
}
// :: ident
function getPseudoElement() {
    var info = getInfo();
    eat(TokenType.Colon);
    eat(TokenType.Colon);
    return {
        type: 'PseudoElement',
        info: info,
        name: readIdent(false)
    };
}
// : ( ident | function )
function getPseudoClass() {
    var info = getInfo();
    var ident = eat(TokenType.Colon) && getIdentifier(false);
    if (scanner.token !== null && scanner.token.type === TokenType.LeftParenthesis) {
        return getFunction(SCOPE_SELECTOR, ident);
    }
    return {
        type: 'PseudoClass',
        info: info,
        name: ident.name
    };
}
// ws
function getS() {
    var node = {
        type: 'Space'
        // value: scanner.token.value
    };
    scanner.next();
    return node;
}
function readSC() {
    // var nodes = [];
    scan:
    while (scanner.token !== null) {
        switch (scanner.token.type) {
            case TokenType.Space:
                scanner.next();
                // nodes.push(getS());
                break;
            case TokenType.Comment:
                scanner.next();
                // nodes.push(getComment());
                break;
            default:
                break scan;
        }
    }
    return null;
    // return nodes.length ? new List(nodes) : null;
}
// node: String
function getString() {
    var node = {
        type: 'String',
        info: getInfo(),
        value: scanner.token.value
    };
    scanner.next();
    return node;
}
// # ident
function getVhash() {
    var info = getInfo();
    var value;
    eat(TokenType.NumberSign);
    expectAny('Number or identifier',
        TokenType.DecimalNumber,
        TokenType.Identifier
    );
    value = scanner.token.value;
    if (scanner.token.type === TokenType.DecimalNumber &&
        scanner.lookupType(1, TokenType.Identifier)) {
        scanner.next();
        value += scanner.token.value;
    }
    scanner.next();
    return {
        type: 'Hash',
        info: info,
        value: value
    };
}
module.exports = function parse(source, options) {
    var ast;
    if (!options || typeof options !== 'object') {
        options = {};
    }
    var context = options.context || 'stylesheet';
    needPositions = Boolean(options.positions);
    filename = options.filename || '<unknown>';
    if (!initialContext.hasOwnProperty(context)) {
        throw new Error('Unknown context `' + context + '`');
    }
    scanner = new Scanner(source, blockMode.hasOwnProperty(context), options.line, options.column);
    scanner.next();
    ast = initialContext[context]();
    scanner = null;
    // console.log(JSON.stringify(ast, null, 4));
    return ast;
};