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;
- };
- };
|