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, cmpStr } = require('../tokenizer/utils'); 
 | 
var sequence = require('./sequence'); 
 | 
var noop = function() {}; 
 | 
  
 | 
var TYPE = constants.TYPE; 
 | 
var NAME = constants.NAME; 
 | 
var WHITESPACE = TYPE.WhiteSpace; 
 | 
var COMMENT = TYPE.Comment; 
 | 
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 onComment = options.onComment; 
 | 
        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 + '`'); 
 | 
        } 
 | 
  
 | 
        if (typeof onComment === 'function') { 
 | 
            parser.scanner.forEachToken((type, start, end) => { 
 | 
                if (type === COMMENT) { 
 | 
                    const loc = parser.getLocation(start, end); 
 | 
                    const value = cmpStr(source, end - 2, end, '*/') 
 | 
                        ? source.slice(start + 2, end - 2) 
 | 
                        : source.slice(start + 2, end); 
 | 
  
 | 
                    onComment(value, loc); 
 | 
                } 
 | 
            }); 
 | 
        } 
 | 
  
 | 
        ast = parser.context[context].call(parser, options); 
 | 
  
 | 
        if (!parser.scanner.eof) { 
 | 
            parser.error(); 
 | 
        } 
 | 
  
 | 
        return ast; 
 | 
    }; 
 | 
}; 
 |