AnPlusB.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. var cmpChar = require('../../tokenizer').cmpChar;
  2. var isNumber = require('../../tokenizer').isNumber;
  3. var TYPE = require('../../tokenizer').TYPE;
  4. var IDENTIFIER = TYPE.Identifier;
  5. var NUMBER = TYPE.Number;
  6. var PLUSSIGN = TYPE.PlusSign;
  7. var HYPHENMINUS = TYPE.HyphenMinus;
  8. var N = 110; // 'n'.charCodeAt(0)
  9. var DISALLOW_SIGN = true;
  10. var ALLOW_SIGN = false;
  11. function checkTokenIsInteger(scanner, disallowSign) {
  12. var pos = scanner.tokenStart;
  13. if (scanner.source.charCodeAt(pos) === PLUSSIGN ||
  14. scanner.source.charCodeAt(pos) === HYPHENMINUS) {
  15. if (disallowSign) {
  16. scanner.error();
  17. }
  18. pos++;
  19. }
  20. for (; pos < scanner.tokenEnd; pos++) {
  21. if (!isNumber(scanner.source.charCodeAt(pos))) {
  22. scanner.error('Unexpected input', pos);
  23. }
  24. }
  25. }
  26. // An+B microsyntax https://www.w3.org/TR/css-syntax-3/#anb
  27. module.exports = {
  28. name: 'AnPlusB',
  29. structure: {
  30. a: [String, null],
  31. b: [String, null]
  32. },
  33. parse: function() {
  34. var start = this.scanner.tokenStart;
  35. var end = start;
  36. var prefix = '';
  37. var a = null;
  38. var b = null;
  39. if (this.scanner.tokenType === NUMBER ||
  40. this.scanner.tokenType === PLUSSIGN) {
  41. checkTokenIsInteger(this.scanner, ALLOW_SIGN);
  42. prefix = this.scanner.getTokenValue();
  43. this.scanner.next();
  44. end = this.scanner.tokenStart;
  45. }
  46. if (this.scanner.tokenType === IDENTIFIER) {
  47. var bStart = this.scanner.tokenStart;
  48. if (cmpChar(this.scanner.source, bStart, HYPHENMINUS)) {
  49. if (prefix === '') {
  50. prefix = '-';
  51. bStart++;
  52. } else {
  53. this.scanner.error('Unexpected hyphen minus');
  54. }
  55. }
  56. if (!cmpChar(this.scanner.source, bStart, N)) {
  57. this.scanner.error();
  58. }
  59. a = prefix === '' ? '1' :
  60. prefix === '+' ? '+1' :
  61. prefix === '-' ? '-1' :
  62. prefix;
  63. var len = this.scanner.tokenEnd - bStart;
  64. if (len > 1) {
  65. // ..n-..
  66. if (this.scanner.source.charCodeAt(bStart + 1) !== HYPHENMINUS) {
  67. this.scanner.error('Unexpected input', bStart + 1);
  68. }
  69. if (len > 2) {
  70. // ..n-{number}..
  71. this.scanner.tokenStart = bStart + 2;
  72. } else {
  73. // ..n- {number}
  74. this.scanner.next();
  75. this.scanner.skipSC();
  76. }
  77. checkTokenIsInteger(this.scanner, DISALLOW_SIGN);
  78. b = '-' + this.scanner.getTokenValue();
  79. this.scanner.next();
  80. end = this.scanner.tokenStart;
  81. } else {
  82. prefix = '';
  83. this.scanner.next();
  84. end = this.scanner.tokenStart;
  85. this.scanner.skipSC();
  86. if (this.scanner.tokenType === HYPHENMINUS ||
  87. this.scanner.tokenType === PLUSSIGN) {
  88. prefix = this.scanner.getTokenValue();
  89. this.scanner.next();
  90. this.scanner.skipSC();
  91. }
  92. if (this.scanner.tokenType === NUMBER) {
  93. checkTokenIsInteger(this.scanner, prefix !== '');
  94. if (!isNumber(this.scanner.source.charCodeAt(this.scanner.tokenStart))) {
  95. prefix = this.scanner.source.charAt(this.scanner.tokenStart);
  96. this.scanner.tokenStart++;
  97. }
  98. if (prefix === '') {
  99. // should be an operator before number
  100. this.scanner.error();
  101. } else if (prefix === '+') {
  102. // plus is using by default
  103. prefix = '';
  104. }
  105. b = prefix + this.scanner.getTokenValue();
  106. this.scanner.next();
  107. end = this.scanner.tokenStart;
  108. } else {
  109. if (prefix) {
  110. this.scanner.eat(NUMBER);
  111. }
  112. }
  113. }
  114. } else {
  115. if (prefix === '' || prefix === '+') { // no number
  116. this.scanner.error(
  117. 'Number or identifier is expected',
  118. this.scanner.tokenStart + (
  119. this.scanner.tokenType === PLUSSIGN ||
  120. this.scanner.tokenType === HYPHENMINUS
  121. )
  122. );
  123. }
  124. b = prefix;
  125. }
  126. return {
  127. type: 'AnPlusB',
  128. loc: this.getLocation(start, end),
  129. a: a,
  130. b: b
  131. };
  132. },
  133. generate: function(node) {
  134. var a = node.a !== null && node.a !== undefined;
  135. var b = node.b !== null && node.b !== undefined;
  136. if (a) {
  137. this.chunk(
  138. node.a === '+1' ? '+n' :
  139. node.a === '1' ? 'n' :
  140. node.a === '-1' ? '-n' :
  141. node.a + 'n'
  142. );
  143. if (b) {
  144. b = String(node.b);
  145. if (b.charAt(0) === '-' || b.charAt(0) === '+') {
  146. this.chunk(b.charAt(0));
  147. this.chunk(b.substr(1));
  148. } else {
  149. this.chunk('+');
  150. this.chunk(b);
  151. }
  152. }
  153. } else {
  154. this.chunk(String(node.b));
  155. }
  156. }
  157. };