| 'use strict'; |
| |
| var Tokenizer = require('../tokenizer'); |
| var List = require('../utils/list'); |
| var sequence = require('./sequence'); |
| var noop = function() {}; |
| |
| 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 Tokenizer(), |
| 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.currentToken; |
| |
| 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; |
| } |
| }, |
| |
| getLocation: function(start, end) { |
| if (this.needPositions) { |
| return this.scanner.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.scanner.getLocationRange( |
| head !== null ? head.loc.start.offset - this.scanner.startOffset : this.scanner.tokenStart, |
| tail !== null ? tail.loc.end.offset - this.scanner.startOffset : this.scanner.tokenStart, |
| this.filename |
| ); |
| } |
| |
| return null; |
| } |
| }; |
| |
| 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; |
| |
| parser.scanner.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.scanner.error(); |
| } |
| |
| return ast; |
| }; |
| }; |