AnPlusB.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. var cmpChar = require('../../tokenizer').cmpChar;
  2. var isDigit = require('../../tokenizer').isDigit;
  3. var TYPE = require('../../tokenizer').TYPE;
  4. var WHITESPACE = TYPE.WhiteSpace;
  5. var COMMENT = TYPE.Comment;
  6. var IDENT = TYPE.Ident;
  7. var NUMBER = TYPE.Number;
  8. var DIMENSION = TYPE.Dimension;
  9. var PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)
  10. var HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-)
  11. var N = 0x006E; // U+006E LATIN SMALL LETTER N (n)
  12. var DISALLOW_SIGN = true;
  13. var ALLOW_SIGN = false;
  14. function checkInteger(offset, disallowSign) {
  15. var pos = this.scanner.tokenStart + offset;
  16. var code = this.scanner.source.charCodeAt(pos);
  17. if (code === PLUSSIGN || code === HYPHENMINUS) {
  18. if (disallowSign) {
  19. this.error('Number sign is not allowed');
  20. }
  21. pos++;
  22. }
  23. for (; pos < this.scanner.tokenEnd; pos++) {
  24. if (!isDigit(this.scanner.source.charCodeAt(pos))) {
  25. this.error('Integer is expected', pos);
  26. }
  27. }
  28. }
  29. function checkTokenIsInteger(disallowSign) {
  30. return checkInteger.call(this, 0, disallowSign);
  31. }
  32. function expectCharCode(offset, code) {
  33. if (!cmpChar(this.scanner.source, this.scanner.tokenStart + offset, code)) {
  34. var msg = '';
  35. switch (code) {
  36. case N:
  37. msg = 'N is expected';
  38. break;
  39. case HYPHENMINUS:
  40. msg = 'HyphenMinus is expected';
  41. break;
  42. }
  43. this.error(msg, this.scanner.tokenStart + offset);
  44. }
  45. }
  46. // ... <signed-integer>
  47. // ... ['+' | '-'] <signless-integer>
  48. function consumeB() {
  49. var offset = 0;
  50. var sign = 0;
  51. var type = this.scanner.tokenType;
  52. while (type === WHITESPACE || type === COMMENT) {
  53. type = this.scanner.lookupType(++offset);
  54. }
  55. if (type !== NUMBER) {
  56. if (this.scanner.isDelim(PLUSSIGN, offset) ||
  57. this.scanner.isDelim(HYPHENMINUS, offset)) {
  58. sign = this.scanner.isDelim(PLUSSIGN, offset) ? PLUSSIGN : HYPHENMINUS;
  59. do {
  60. type = this.scanner.lookupType(++offset);
  61. } while (type === WHITESPACE || type === COMMENT);
  62. if (type !== NUMBER) {
  63. this.scanner.skip(offset);
  64. checkTokenIsInteger.call(this, DISALLOW_SIGN);
  65. }
  66. } else {
  67. return null;
  68. }
  69. }
  70. if (offset > 0) {
  71. this.scanner.skip(offset);
  72. }
  73. if (sign === 0) {
  74. type = this.scanner.source.charCodeAt(this.scanner.tokenStart);
  75. if (type !== PLUSSIGN && type !== HYPHENMINUS) {
  76. this.error('Number sign is expected');
  77. }
  78. }
  79. checkTokenIsInteger.call(this, sign !== 0);
  80. return sign === HYPHENMINUS ? '-' + this.consume(NUMBER) : this.consume(NUMBER);
  81. }
  82. // An+B microsyntax https://www.w3.org/TR/css-syntax-3/#anb
  83. module.exports = {
  84. name: 'AnPlusB',
  85. structure: {
  86. a: [String, null],
  87. b: [String, null]
  88. },
  89. parse: function() {
  90. /* eslint-disable brace-style*/
  91. var start = this.scanner.tokenStart;
  92. var a = null;
  93. var b = null;
  94. // <integer>
  95. if (this.scanner.tokenType === NUMBER) {
  96. checkTokenIsInteger.call(this, ALLOW_SIGN);
  97. b = this.consume(NUMBER);
  98. }
  99. // -n
  100. // -n <signed-integer>
  101. // -n ['+' | '-'] <signless-integer>
  102. // -n- <signless-integer>
  103. // <dashndashdigit-ident>
  104. else if (this.scanner.tokenType === IDENT && cmpChar(this.scanner.source, this.scanner.tokenStart, HYPHENMINUS)) {
  105. a = '-1';
  106. expectCharCode.call(this, 1, N);
  107. switch (this.scanner.getTokenLength()) {
  108. // -n
  109. // -n <signed-integer>
  110. // -n ['+' | '-'] <signless-integer>
  111. case 2:
  112. this.scanner.next();
  113. b = consumeB.call(this);
  114. break;
  115. // -n- <signless-integer>
  116. case 3:
  117. expectCharCode.call(this, 2, HYPHENMINUS);
  118. this.scanner.next();
  119. this.scanner.skipSC();
  120. checkTokenIsInteger.call(this, DISALLOW_SIGN);
  121. b = '-' + this.consume(NUMBER);
  122. break;
  123. // <dashndashdigit-ident>
  124. default:
  125. expectCharCode.call(this, 2, HYPHENMINUS);
  126. checkInteger.call(this, 3, DISALLOW_SIGN);
  127. this.scanner.next();
  128. b = this.scanner.substrToCursor(start + 2);
  129. }
  130. }
  131. // '+'? n
  132. // '+'? n <signed-integer>
  133. // '+'? n ['+' | '-'] <signless-integer>
  134. // '+'? n- <signless-integer>
  135. // '+'? <ndashdigit-ident>
  136. else if (this.scanner.tokenType === IDENT || (this.scanner.isDelim(PLUSSIGN) && this.scanner.lookupType(1) === IDENT)) {
  137. var sign = 0;
  138. a = '1';
  139. // just ignore a plus
  140. if (this.scanner.isDelim(PLUSSIGN)) {
  141. sign = 1;
  142. this.scanner.next();
  143. }
  144. expectCharCode.call(this, 0, N);
  145. switch (this.scanner.getTokenLength()) {
  146. // '+'? n
  147. // '+'? n <signed-integer>
  148. // '+'? n ['+' | '-'] <signless-integer>
  149. case 1:
  150. this.scanner.next();
  151. b = consumeB.call(this);
  152. break;
  153. // '+'? n- <signless-integer>
  154. case 2:
  155. expectCharCode.call(this, 1, HYPHENMINUS);
  156. this.scanner.next();
  157. this.scanner.skipSC();
  158. checkTokenIsInteger.call(this, DISALLOW_SIGN);
  159. b = '-' + this.consume(NUMBER);
  160. break;
  161. // '+'? <ndashdigit-ident>
  162. default:
  163. expectCharCode.call(this, 1, HYPHENMINUS);
  164. checkInteger.call(this, 2, DISALLOW_SIGN);
  165. this.scanner.next();
  166. b = this.scanner.substrToCursor(start + sign + 1);
  167. }
  168. }
  169. // <ndashdigit-dimension>
  170. // <ndash-dimension> <signless-integer>
  171. // <n-dimension>
  172. // <n-dimension> <signed-integer>
  173. // <n-dimension> ['+' | '-'] <signless-integer>
  174. else if (this.scanner.tokenType === DIMENSION) {
  175. var code = this.scanner.source.charCodeAt(this.scanner.tokenStart);
  176. var sign = code === PLUSSIGN || code === HYPHENMINUS;
  177. for (var i = this.scanner.tokenStart + sign; i < this.scanner.tokenEnd; i++) {
  178. if (!isDigit(this.scanner.source.charCodeAt(i))) {
  179. break;
  180. }
  181. }
  182. if (i === this.scanner.tokenStart + sign) {
  183. this.error('Integer is expected', this.scanner.tokenStart + sign);
  184. }
  185. expectCharCode.call(this, i - this.scanner.tokenStart, N);
  186. a = this.scanner.source.substring(start, i);
  187. // <n-dimension>
  188. // <n-dimension> <signed-integer>
  189. // <n-dimension> ['+' | '-'] <signless-integer>
  190. if (i + 1 === this.scanner.tokenEnd) {
  191. this.scanner.next();
  192. b = consumeB.call(this);
  193. } else {
  194. expectCharCode.call(this, i - this.scanner.tokenStart + 1, HYPHENMINUS);
  195. // <ndash-dimension> <signless-integer>
  196. if (i + 2 === this.scanner.tokenEnd) {
  197. this.scanner.next();
  198. this.scanner.skipSC();
  199. checkTokenIsInteger.call(this, DISALLOW_SIGN);
  200. b = '-' + this.consume(NUMBER);
  201. }
  202. // <ndashdigit-dimension>
  203. else {
  204. checkInteger.call(this, i - this.scanner.tokenStart + 2, DISALLOW_SIGN);
  205. this.scanner.next();
  206. b = this.scanner.substrToCursor(i + 1);
  207. }
  208. }
  209. } else {
  210. this.error();
  211. }
  212. if (a !== null && a.charCodeAt(0) === PLUSSIGN) {
  213. a = a.substr(1);
  214. }
  215. if (b !== null && b.charCodeAt(0) === PLUSSIGN) {
  216. b = b.substr(1);
  217. }
  218. return {
  219. type: 'AnPlusB',
  220. loc: this.getLocation(start, this.scanner.tokenStart),
  221. a: a,
  222. b: b
  223. };
  224. },
  225. generate: function(node) {
  226. var a = node.a !== null && node.a !== undefined;
  227. var b = node.b !== null && node.b !== undefined;
  228. if (a) {
  229. this.chunk(
  230. node.a === '+1' ? '+n' : // eslint-disable-line operator-linebreak, indent
  231. node.a === '1' ? 'n' : // eslint-disable-line operator-linebreak, indent
  232. node.a === '-1' ? '-n' : // eslint-disable-line operator-linebreak, indent
  233. node.a + 'n' // eslint-disable-line operator-linebreak, indent
  234. );
  235. if (b) {
  236. b = String(node.b);
  237. if (b.charAt(0) === '-' || b.charAt(0) === '+') {
  238. this.chunk(b.charAt(0));
  239. this.chunk(b.substr(1));
  240. } else {
  241. this.chunk('+');
  242. this.chunk(b);
  243. }
  244. }
  245. } else {
  246. this.chunk(String(node.b));
  247. }
  248. }
  249. };