| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 | var OffsetToLocation = require('../common/OffsetToLocation');var SyntaxError = require('../common/SyntaxError');var TokenStream = require('../common/TokenStream');var List = require('../common/List');var tokenize = require('../tokenizer');var constants = require('../tokenizer/const');var findWhiteSpaceStart = require('../tokenizer/utils').findWhiteSpaceStart;var sequence = require('./sequence');var noop = function() {};var TYPE = constants.TYPE;var NAME = constants.NAME;var WHITESPACE = TYPE.WhiteSpace;var IDENT = TYPE.Ident;var FUNCTION = TYPE.Function;var URL = TYPE.Url;var HASH = TYPE.Hash;var PERCENTAGE = TYPE.Percentage;var NUMBER = TYPE.Number;var NUMBERSIGN = 0x0023; // U+0023 NUMBER SIGN (#)var NULL = 0;function createParseContext(name) {    return function() {        return this[name]();    };}function processConfig(config) {    var parserConfig = {        context: {},        scope: {},        atrule: {},        pseudo: {}    };    if (config.parseContext) {        for (var name in config.parseContext) {            switch (typeof config.parseContext[name]) {                case 'function':                    parserConfig.context[name] = config.parseContext[name];                    break;                case 'string':                    parserConfig.context[name] = createParseContext(config.parseContext[name]);                    break;            }        }    }    if (config.scope) {        for (var name in config.scope) {            parserConfig.scope[name] = config.scope[name];        }    }    if (config.atrule) {        for (var name in config.atrule) {            var atrule = config.atrule[name];            if (atrule.parse) {                parserConfig.atrule[name] = atrule.parse;            }        }    }    if (config.pseudo) {        for (var name in config.pseudo) {            var pseudo = config.pseudo[name];            if (pseudo.parse) {                parserConfig.pseudo[name] = pseudo.parse;            }        }    }    if (config.node) {        for (var name in config.node) {            parserConfig[name] = config.node[name].parse;        }    }    return parserConfig;}module.exports = function createParser(config) {    var parser = {        scanner: new TokenStream(),        locationMap: new OffsetToLocation(),        filename: '<unknown>',        needPositions: false,        onParseError: noop,        onParseErrorThrow: false,        parseAtrulePrelude: true,        parseRulePrelude: true,        parseValue: true,        parseCustomProperty: false,        readSequence: sequence,        createList: function() {            return new List();        },        createSingleNodeList: function(node) {            return new List().appendData(node);        },        getFirstListNode: function(list) {            return list && list.first();        },        getLastListNode: function(list) {            return list.last();        },        parseWithFallback: function(consumer, fallback) {            var startToken = this.scanner.tokenIndex;            try {                return consumer.call(this);            } catch (e) {                if (this.onParseErrorThrow) {                    throw e;                }                var fallbackNode = fallback.call(this, startToken);                this.onParseErrorThrow = true;                this.onParseError(e, fallbackNode);                this.onParseErrorThrow = false;                return fallbackNode;            }        },        lookupNonWSType: function(offset) {            do {                var type = this.scanner.lookupType(offset++);                if (type !== WHITESPACE) {                    return type;                }            } while (type !== NULL);            return NULL;        },        eat: function(tokenType) {            if (this.scanner.tokenType !== tokenType) {                var offset = this.scanner.tokenStart;                var message = NAME[tokenType] + ' is expected';                // tweak message and offset                switch (tokenType) {                    case IDENT:                        // when identifier is expected but there is a function or url                        if (this.scanner.tokenType === FUNCTION || this.scanner.tokenType === URL) {                            offset = this.scanner.tokenEnd - 1;                            message = 'Identifier is expected but function found';                        } else {                            message = 'Identifier is expected';                        }                        break;                    case HASH:                        if (this.scanner.isDelim(NUMBERSIGN)) {                            this.scanner.next();                            offset++;                            message = 'Name is expected';                        }                        break;                    case PERCENTAGE:                        if (this.scanner.tokenType === NUMBER) {                            offset = this.scanner.tokenEnd;                            message = 'Percent sign is expected';                        }                        break;                    default:                        // when test type is part of another token show error for current position + 1                        // e.g. eat(HYPHENMINUS) will fail on "-foo", but pointing on "-" is odd                        if (this.scanner.source.charCodeAt(this.scanner.tokenStart) === tokenType) {                            offset = offset + 1;                        }                }                this.error(message, offset);            }            this.scanner.next();        },        consume: function(tokenType) {            var value = this.scanner.getTokenValue();            this.eat(tokenType);            return value;        },        consumeFunctionName: function() {            var name = this.scanner.source.substring(this.scanner.tokenStart, this.scanner.tokenEnd - 1);            this.eat(FUNCTION);            return name;        },        getLocation: function(start, end) {            if (this.needPositions) {                return this.locationMap.getLocationRange(                    start,                    end,                    this.filename                );            }            return null;        },        getLocationFromList: function(list) {            if (this.needPositions) {                var head = this.getFirstListNode(list);                var tail = this.getLastListNode(list);                return this.locationMap.getLocationRange(                    head !== null ? head.loc.start.offset - this.locationMap.startOffset : this.scanner.tokenStart,                    tail !== null ? tail.loc.end.offset - this.locationMap.startOffset : this.scanner.tokenStart,                    this.filename                );            }            return null;        },        error: function(message, offset) {            var location = typeof offset !== 'undefined' && offset < this.scanner.source.length                ? this.locationMap.getLocation(offset)                : this.scanner.eof                    ? this.locationMap.getLocation(findWhiteSpaceStart(this.scanner.source, this.scanner.source.length - 1))                    : this.locationMap.getLocation(this.scanner.tokenStart);            throw new SyntaxError(                message || 'Unexpected input',                this.scanner.source,                location.offset,                location.line,                location.column            );        }    };    config = processConfig(config || {});    for (var key in config) {        parser[key] = config[key];    }    return function(source, options) {        options = options || {};        var context = options.context || 'default';        var ast;        tokenize(source, parser.scanner);        parser.locationMap.setSource(            source,            options.offset,            options.line,            options.column        );        parser.filename = options.filename || '<unknown>';        parser.needPositions = Boolean(options.positions);        parser.onParseError = typeof options.onParseError === 'function' ? options.onParseError : noop;        parser.onParseErrorThrow = false;        parser.parseAtrulePrelude = 'parseAtrulePrelude' in options ? Boolean(options.parseAtrulePrelude) : true;        parser.parseRulePrelude = 'parseRulePrelude' in options ? Boolean(options.parseRulePrelude) : true;        parser.parseValue = 'parseValue' in options ? Boolean(options.parseValue) : true;        parser.parseCustomProperty = 'parseCustomProperty' in options ? Boolean(options.parseCustomProperty) : false;        if (!parser.context.hasOwnProperty(context)) {            throw new Error('Unknown context `' + context + '`');        }        ast = parser.context[context].call(parser, options);        if (!parser.scanner.eof) {            parser.error();        }        return ast;    };};
 |