create.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. 'use strict';
  2. var Tokenizer = require('../tokenizer');
  3. var List = require('../utils/list');
  4. var sequence = require('./sequence');
  5. var noop = function() {};
  6. function createParseContext(name) {
  7. return function() {
  8. return this[name]();
  9. };
  10. }
  11. function processConfig(config) {
  12. var parserConfig = {
  13. context: {},
  14. scope: {},
  15. atrule: {},
  16. pseudo: {}
  17. };
  18. if (config.parseContext) {
  19. for (var name in config.parseContext) {
  20. switch (typeof config.parseContext[name]) {
  21. case 'function':
  22. parserConfig.context[name] = config.parseContext[name];
  23. break;
  24. case 'string':
  25. parserConfig.context[name] = createParseContext(config.parseContext[name]);
  26. break;
  27. }
  28. }
  29. }
  30. if (config.scope) {
  31. for (var name in config.scope) {
  32. parserConfig.scope[name] = config.scope[name];
  33. }
  34. }
  35. if (config.atrule) {
  36. for (var name in config.atrule) {
  37. var atrule = config.atrule[name];
  38. if (atrule.parse) {
  39. parserConfig.atrule[name] = atrule.parse;
  40. }
  41. }
  42. }
  43. if (config.pseudo) {
  44. for (var name in config.pseudo) {
  45. var pseudo = config.pseudo[name];
  46. if (pseudo.parse) {
  47. parserConfig.pseudo[name] = pseudo.parse;
  48. }
  49. }
  50. }
  51. if (config.node) {
  52. for (var name in config.node) {
  53. parserConfig[name] = config.node[name].parse;
  54. }
  55. }
  56. return parserConfig;
  57. }
  58. module.exports = function createParser(config) {
  59. var parser = {
  60. scanner: new Tokenizer(),
  61. filename: '<unknown>',
  62. needPositions: false,
  63. onParseError: noop,
  64. onParseErrorThrow: false,
  65. parseAtrulePrelude: true,
  66. parseRulePrelude: true,
  67. parseValue: true,
  68. parseCustomProperty: false,
  69. readSequence: sequence,
  70. createList: function() {
  71. return new List();
  72. },
  73. createSingleNodeList: function(node) {
  74. return new List().appendData(node);
  75. },
  76. getFirstListNode: function(list) {
  77. return list && list.first();
  78. },
  79. getLastListNode: function(list) {
  80. return list.last();
  81. },
  82. parseWithFallback: function(consumer, fallback) {
  83. var startToken = this.scanner.currentToken;
  84. try {
  85. return consumer.call(this);
  86. } catch (e) {
  87. if (this.onParseErrorThrow) {
  88. throw e;
  89. }
  90. var fallbackNode = fallback.call(this, startToken);
  91. this.onParseErrorThrow = true;
  92. this.onParseError(e, fallbackNode);
  93. this.onParseErrorThrow = false;
  94. return fallbackNode;
  95. }
  96. },
  97. getLocation: function(start, end) {
  98. if (this.needPositions) {
  99. return this.scanner.getLocationRange(
  100. start,
  101. end,
  102. this.filename
  103. );
  104. }
  105. return null;
  106. },
  107. getLocationFromList: function(list) {
  108. if (this.needPositions) {
  109. var head = this.getFirstListNode(list);
  110. var tail = this.getLastListNode(list);
  111. return this.scanner.getLocationRange(
  112. head !== null ? head.loc.start.offset - this.scanner.startOffset : this.scanner.tokenStart,
  113. tail !== null ? tail.loc.end.offset - this.scanner.startOffset : this.scanner.tokenStart,
  114. this.filename
  115. );
  116. }
  117. return null;
  118. }
  119. };
  120. config = processConfig(config || {});
  121. for (var key in config) {
  122. parser[key] = config[key];
  123. }
  124. return function(source, options) {
  125. options = options || {};
  126. var context = options.context || 'default';
  127. var ast;
  128. parser.scanner.setSource(source, options.offset, options.line, options.column);
  129. parser.filename = options.filename || '<unknown>';
  130. parser.needPositions = Boolean(options.positions);
  131. parser.onParseError = typeof options.onParseError === 'function' ? options.onParseError : noop;
  132. parser.onParseErrorThrow = false;
  133. parser.parseAtrulePrelude = 'parseAtrulePrelude' in options ? Boolean(options.parseAtrulePrelude) : true;
  134. parser.parseRulePrelude = 'parseRulePrelude' in options ? Boolean(options.parseRulePrelude) : true;
  135. parser.parseValue = 'parseValue' in options ? Boolean(options.parseValue) : true;
  136. parser.parseCustomProperty = 'parseCustomProperty' in options ? Boolean(options.parseCustomProperty) : false;
  137. if (!parser.context.hasOwnProperty(context)) {
  138. throw new Error('Unknown context `' + context + '`');
  139. }
  140. ast = parser.context[context].call(parser, options);
  141. if (!parser.scanner.eof) {
  142. parser.scanner.error();
  143. }
  144. return ast;
  145. };
  146. };