lexer.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897
  1. /*!
  2. * Stylus - Lexer
  3. * Copyright (c) Automattic <developer.wordpress.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var Token = require('./token')
  10. , nodes = require('./nodes')
  11. , errors = require('./errors');
  12. /**
  13. * Expose `Lexer`.
  14. */
  15. exports = module.exports = Lexer;
  16. /**
  17. * Operator aliases.
  18. */
  19. var alias = {
  20. 'and': '&&'
  21. , 'or': '||'
  22. , 'is': '=='
  23. , 'isnt': '!='
  24. , 'is not': '!='
  25. , ':=': '?='
  26. };
  27. /**
  28. * Initialize a new `Lexer` with the given `str` and `options`.
  29. *
  30. * @param {String} str
  31. * @param {Object} options
  32. * @api private
  33. */
  34. function Lexer(str, options) {
  35. options = options || {};
  36. this.stash = [];
  37. this.indentStack = [];
  38. this.indentRe = null;
  39. this.lineno = 1;
  40. this.column = 1;
  41. // HACK!
  42. function comment(str, val, offset, s) {
  43. var inComment = s.lastIndexOf('/*', offset) > s.lastIndexOf('*/', offset)
  44. , commentIdx = s.lastIndexOf('//', offset)
  45. , i = s.lastIndexOf('\n', offset)
  46. , double = 0
  47. , single = 0;
  48. if (~commentIdx && commentIdx > i) {
  49. while (i != offset) {
  50. if ("'" == s[i]) single ? single-- : single++;
  51. if ('"' == s[i]) double ? double-- : double++;
  52. if ('/' == s[i] && '/' == s[i + 1]) {
  53. inComment = !single && !double;
  54. break;
  55. }
  56. ++i;
  57. }
  58. }
  59. return inComment
  60. ? str
  61. : ((val === ',' && /^[,\t\n]+$/.test(str)) ? str.replace(/\n/, '\r') : val + '\r');
  62. };
  63. // Remove UTF-8 BOM.
  64. if ('\uFEFF' == str.charAt(0)) str = str.slice(1);
  65. this.str = str
  66. .replace(/\s+$/, '\n')
  67. .replace(/\r\n?/g, '\n')
  68. .replace(/\\ *\n/g, '\r')
  69. .replace(/([,(:](?!\/\/[^ ])) *(?:\/\/[^\n]*|\/\*.*?\*\/)?\n\s*/g, comment)
  70. .replace(/\s*\n[ \t]*([,)])/g, comment);
  71. };
  72. /**
  73. * Lexer prototype.
  74. */
  75. Lexer.prototype = {
  76. /**
  77. * Custom inspect.
  78. */
  79. inspect: function(){
  80. var tok
  81. , tmp = this.str
  82. , buf = [];
  83. while ('eos' != (tok = this.next()).type) {
  84. buf.push(tok.inspect());
  85. }
  86. this.str = tmp;
  87. return buf.concat(tok.inspect()).join('\n');
  88. },
  89. /**
  90. * Lookahead `n` tokens.
  91. *
  92. * @param {Number} n
  93. * @return {Object}
  94. * @api private
  95. */
  96. lookahead: function(n){
  97. var fetch = n - this.stash.length;
  98. while (fetch-- > 0) this.stash.push(this.advance());
  99. return this.stash[--n];
  100. },
  101. /**
  102. * Consume the given `len`.
  103. *
  104. * @param {Number|Array} len
  105. * @api private
  106. */
  107. skip: function(len){
  108. var chunk = len[0];
  109. len = chunk ? chunk.length : len;
  110. this.str = this.str.substr(len);
  111. if (chunk) {
  112. this.move(chunk);
  113. } else {
  114. this.column += len;
  115. }
  116. },
  117. /**
  118. * Move current line and column position.
  119. *
  120. * @param {String} str
  121. * @api private
  122. */
  123. move: function(str){
  124. var lines = str.match(/\n/g)
  125. , idx = str.lastIndexOf('\n');
  126. if (lines) this.lineno += lines.length;
  127. this.column = ~idx
  128. ? str.length - idx
  129. : this.column + str.length;
  130. },
  131. /**
  132. * Fetch next token including those stashed by peek.
  133. *
  134. * @return {Token}
  135. * @api private
  136. */
  137. next: function() {
  138. var tok = this.stashed() || this.advance();
  139. this.prev = tok;
  140. return tok;
  141. },
  142. /**
  143. * Check if the current token is a part of selector.
  144. *
  145. * @return {Boolean}
  146. * @api private
  147. */
  148. isPartOfSelector: function() {
  149. var tok = this.stash[this.stash.length - 1] || this.prev;
  150. switch (tok && tok.type) {
  151. // #for
  152. case 'color':
  153. return 2 == tok.val.raw.length;
  154. // .or
  155. case '.':
  156. // [is]
  157. case '[':
  158. return true;
  159. }
  160. return false;
  161. },
  162. /**
  163. * Fetch next token.
  164. *
  165. * @return {Token}
  166. * @api private
  167. */
  168. advance: function() {
  169. var column = this.column
  170. , line = this.lineno
  171. , tok = this.eos()
  172. || this.null()
  173. || this.sep()
  174. || this.keyword()
  175. || this.urlchars()
  176. || this.comment()
  177. || this.newline()
  178. || this.escaped()
  179. || this.important()
  180. || this.literal()
  181. || this.anonFunc()
  182. || this.atrule()
  183. || this.function()
  184. || this.brace()
  185. || this.paren()
  186. || this.color()
  187. || this.string()
  188. || this.unit()
  189. || this.namedop()
  190. || this.boolean()
  191. || this.unicode()
  192. || this.ident()
  193. || this.op()
  194. || (function () {
  195. var token = this.eol();
  196. if (token) {
  197. column = token.column;
  198. line = token.lineno;
  199. }
  200. return token;
  201. }).call(this)
  202. || this.space()
  203. || this.selector();
  204. tok.lineno = line;
  205. tok.column = column;
  206. return tok;
  207. },
  208. /**
  209. * Lookahead a single token.
  210. *
  211. * @return {Token}
  212. * @api private
  213. */
  214. peek: function() {
  215. return this.lookahead(1);
  216. },
  217. /**
  218. * Return the next possibly stashed token.
  219. *
  220. * @return {Token}
  221. * @api private
  222. */
  223. stashed: function() {
  224. return this.stash.shift();
  225. },
  226. /**
  227. * EOS | trailing outdents.
  228. */
  229. eos: function() {
  230. if (this.str.length) return;
  231. if (this.indentStack.length) {
  232. this.indentStack.shift();
  233. return new Token('outdent');
  234. } else {
  235. return new Token('eos');
  236. }
  237. },
  238. /**
  239. * url char
  240. */
  241. urlchars: function() {
  242. var captures;
  243. if (!this.isURL) return;
  244. if (captures = /^[\/:@.;?&=*!,<>#%0-9]+/.exec(this.str)) {
  245. this.skip(captures);
  246. return new Token('literal', new nodes.Literal(captures[0]));
  247. }
  248. },
  249. /**
  250. * ';' [ \t]*
  251. */
  252. sep: function() {
  253. var captures;
  254. if (captures = /^;[ \t]*/.exec(this.str)) {
  255. this.skip(captures);
  256. return new Token(';');
  257. }
  258. },
  259. /**
  260. * '\r'
  261. */
  262. eol: function() {
  263. if ('\r' == this.str[0]) {
  264. ++this.lineno;
  265. this.skip(1);
  266. this.column = 1;
  267. while(this.space());
  268. return this.advance();
  269. }
  270. },
  271. /**
  272. * ' '+
  273. */
  274. space: function() {
  275. var captures;
  276. if (captures = /^([ \t]+)/.exec(this.str)) {
  277. this.skip(captures);
  278. return new Token('space');
  279. }
  280. },
  281. /**
  282. * '\\' . ' '*
  283. */
  284. escaped: function() {
  285. var captures;
  286. if (captures = /^\\(.)[ \t]*/.exec(this.str)) {
  287. var c = captures[1];
  288. this.skip(captures);
  289. return new Token('ident', new nodes.Literal(c));
  290. }
  291. },
  292. /**
  293. * '@css' ' '* '{' .* '}' ' '*
  294. */
  295. literal: function() {
  296. // HACK attack !!!
  297. var captures;
  298. if (captures = /^@css[ \t]*\{/.exec(this.str)) {
  299. this.skip(captures);
  300. var c
  301. , braces = 1
  302. , css = ''
  303. , node;
  304. while (c = this.str[0]) {
  305. this.str = this.str.substr(1);
  306. switch (c) {
  307. case '{': ++braces; break;
  308. case '}': --braces; break;
  309. case '\n':
  310. case '\r':
  311. ++this.lineno;
  312. break;
  313. }
  314. css += c;
  315. if (!braces) break;
  316. }
  317. css = css.replace(/\s*}$/, '');
  318. node = new nodes.Literal(css);
  319. node.css = true;
  320. return new Token('literal', node);
  321. }
  322. },
  323. /**
  324. * '!important' ' '*
  325. */
  326. important: function() {
  327. var captures;
  328. if (captures = /^!important[ \t]*/.exec(this.str)) {
  329. this.skip(captures);
  330. return new Token('ident', new nodes.Literal('!important'));
  331. }
  332. },
  333. /**
  334. * '{' | '}'
  335. */
  336. brace: function() {
  337. var captures;
  338. if (captures = /^([{}])/.exec(this.str)) {
  339. this.skip(1);
  340. var brace = captures[1];
  341. return new Token(brace, brace);
  342. }
  343. },
  344. /**
  345. * '(' | ')' ' '*
  346. */
  347. paren: function() {
  348. var captures;
  349. if (captures = /^([()])([ \t]*)/.exec(this.str)) {
  350. var paren = captures[1];
  351. this.skip(captures);
  352. if (')' == paren) this.isURL = false;
  353. var tok = new Token(paren, paren);
  354. tok.space = captures[2];
  355. return tok;
  356. }
  357. },
  358. /**
  359. * 'null'
  360. */
  361. null: function() {
  362. var captures
  363. , tok;
  364. if (captures = /^(null)\b[ \t]*/.exec(this.str)) {
  365. this.skip(captures);
  366. if (this.isPartOfSelector()) {
  367. tok = new Token('ident', new nodes.Ident(captures[0]));
  368. } else {
  369. tok = new Token('null', nodes.null);
  370. }
  371. return tok;
  372. }
  373. },
  374. /**
  375. * 'if'
  376. * | 'else'
  377. * | 'unless'
  378. * | 'return'
  379. * | 'for'
  380. * | 'in'
  381. */
  382. keyword: function() {
  383. var captures
  384. , tok;
  385. if (captures = /^(return|if|else|unless|for|in)\b(?!-)[ \t]*/.exec(this.str)) {
  386. var keyword = captures[1];
  387. this.skip(captures);
  388. if (this.isPartOfSelector()) {
  389. tok = new Token('ident', new nodes.Ident(captures[0]));
  390. } else {
  391. tok = new Token(keyword, keyword);
  392. }
  393. return tok;
  394. }
  395. },
  396. /**
  397. * 'not'
  398. * | 'and'
  399. * | 'or'
  400. * | 'is'
  401. * | 'is not'
  402. * | 'isnt'
  403. * | 'is a'
  404. * | 'is defined'
  405. */
  406. namedop: function() {
  407. var captures
  408. , tok;
  409. if (captures = /^(not|and|or|is a|is defined|isnt|is not|is)(?!-)\b([ \t]*)/.exec(this.str)) {
  410. var op = captures[1];
  411. this.skip(captures);
  412. if (this.isPartOfSelector()) {
  413. tok = new Token('ident', new nodes.Ident(captures[0]));
  414. } else {
  415. op = alias[op] || op;
  416. tok = new Token(op, op);
  417. }
  418. tok.space = captures[2];
  419. return tok;
  420. }
  421. },
  422. /**
  423. * ','
  424. * | '+'
  425. * | '+='
  426. * | '-'
  427. * | '-='
  428. * | '*'
  429. * | '*='
  430. * | '/'
  431. * | '/='
  432. * | '%'
  433. * | '%='
  434. * | '**'
  435. * | '!'
  436. * | '&'
  437. * | '&&'
  438. * | '||'
  439. * | '>'
  440. * | '>='
  441. * | '<'
  442. * | '<='
  443. * | '='
  444. * | '=='
  445. * | '!='
  446. * | '!'
  447. * | '~'
  448. * | '?='
  449. * | ':='
  450. * | '?'
  451. * | ':'
  452. * | '['
  453. * | ']'
  454. * | '.'
  455. * | '..'
  456. * | '...'
  457. */
  458. op: function() {
  459. var captures;
  460. if (captures = /^([.]{1,3}|&&|\|\||[!<>=?:]=|\*\*|[-+*\/%]=?|[,=?:!~<>&\[\]])([ \t]*)/.exec(this.str)) {
  461. var op = captures[1];
  462. this.skip(captures);
  463. op = alias[op] || op;
  464. var tok = new Token(op, op);
  465. tok.space = captures[2];
  466. this.isURL = false;
  467. return tok;
  468. }
  469. },
  470. /**
  471. * '@('
  472. */
  473. anonFunc: function() {
  474. var tok;
  475. if ('@' == this.str[0] && '(' == this.str[1]) {
  476. this.skip(2);
  477. tok = new Token('function', new nodes.Ident('anonymous'));
  478. tok.anonymous = true;
  479. return tok;
  480. }
  481. },
  482. /**
  483. * '@' (-(\w+)-)?[a-zA-Z0-9-_]+
  484. */
  485. atrule: function() {
  486. var captures;
  487. if (captures = /^@(?!apply)(?:-(\w+)-)?([a-zA-Z0-9-_]+)[ \t]*/.exec(this.str)) {
  488. this.skip(captures);
  489. var vendor = captures[1]
  490. , type = captures[2]
  491. , tok;
  492. switch (type) {
  493. case 'require':
  494. case 'import':
  495. case 'charset':
  496. case 'namespace':
  497. case 'media':
  498. case 'scope':
  499. case 'supports':
  500. return new Token(type);
  501. case 'document':
  502. return new Token('-moz-document');
  503. case 'block':
  504. return new Token('atblock');
  505. case 'extend':
  506. case 'extends':
  507. return new Token('extend');
  508. case 'keyframes':
  509. return new Token(type, vendor);
  510. default:
  511. return new Token('atrule', (vendor ? '-' + vendor + '-' + type : type));
  512. }
  513. }
  514. },
  515. /**
  516. * '//' *
  517. */
  518. comment: function() {
  519. // Single line
  520. if ('/' == this.str[0] && '/' == this.str[1]) {
  521. var end = this.str.indexOf('\n');
  522. if (-1 == end) end = this.str.length;
  523. this.skip(end);
  524. return this.advance();
  525. }
  526. // Multi-line
  527. if ('/' == this.str[0] && '*' == this.str[1]) {
  528. var end = this.str.indexOf('*/');
  529. if (-1 == end) end = this.str.length;
  530. var str = this.str.substr(0, end + 2)
  531. , lines = str.split(/\n|\r/).length - 1
  532. , suppress = true
  533. , inline = false;
  534. this.lineno += lines;
  535. this.skip(end + 2);
  536. // output
  537. if ('!' == str[2]) {
  538. str = str.replace('*!', '*');
  539. suppress = false;
  540. }
  541. if (this.prev && ';' == this.prev.type) inline = true;
  542. return new Token('comment', new nodes.Comment(str, suppress, inline));
  543. }
  544. },
  545. /**
  546. * 'true' | 'false'
  547. */
  548. boolean: function() {
  549. var captures;
  550. if (captures = /^(true|false)\b([ \t]*)/.exec(this.str)) {
  551. var val = nodes.Boolean('true' == captures[1]);
  552. this.skip(captures);
  553. var tok = new Token('boolean', val);
  554. tok.space = captures[2];
  555. return tok;
  556. }
  557. },
  558. /**
  559. * 'U+' [0-9A-Fa-f?]{1,6}(?:-[0-9A-Fa-f]{1,6})?
  560. */
  561. unicode: function() {
  562. var captures;
  563. if (captures = /^u\+[0-9a-f?]{1,6}(?:-[0-9a-f]{1,6})?/i.exec(this.str)) {
  564. this.skip(captures);
  565. return new Token('literal', new nodes.Literal(captures[0]));
  566. }
  567. },
  568. /**
  569. * -*[_a-zA-Z$] [-\w\d$]* '('
  570. */
  571. function: function() {
  572. var captures;
  573. if (captures = /^(-*[_a-zA-Z$][-\w\d$]*)\(([ \t]*)/.exec(this.str)) {
  574. var name = captures[1];
  575. this.skip(captures);
  576. this.isURL = 'url' == name;
  577. var tok = new Token('function', new nodes.Ident(name));
  578. tok.space = captures[2];
  579. return tok;
  580. }
  581. },
  582. /**
  583. * -*[_a-zA-Z$] [-\w\d$]*
  584. */
  585. ident: function() {
  586. var captures;
  587. if (captures = /^-*([_a-zA-Z$]|@apply)[-\w\d$]*/.exec(this.str)) {
  588. this.skip(captures);
  589. return new Token('ident', new nodes.Ident(captures[0]));
  590. }
  591. },
  592. /**
  593. * '\n' ' '+
  594. */
  595. newline: function() {
  596. var captures, re;
  597. // we have established the indentation regexp
  598. if (this.indentRe){
  599. captures = this.indentRe.exec(this.str);
  600. // figure out if we are using tabs or spaces
  601. } else {
  602. // try tabs
  603. re = /^\n([\t]*)[ \t]*/;
  604. captures = re.exec(this.str);
  605. // nope, try spaces
  606. if (captures && !captures[1].length) {
  607. re = /^\n([ \t]*)/;
  608. captures = re.exec(this.str);
  609. }
  610. // established
  611. if (captures && captures[1].length) this.indentRe = re;
  612. }
  613. if (captures) {
  614. var tok
  615. , indents = captures[1].length;
  616. this.skip(captures);
  617. if (this.str[0] === ' ' || this.str[0] === '\t') {
  618. throw new errors.SyntaxError('Invalid indentation. You can use tabs or spaces to indent, but not both.');
  619. }
  620. // Blank line
  621. if ('\n' == this.str[0]) return this.advance();
  622. // Outdent
  623. if (this.indentStack.length && indents < this.indentStack[0]) {
  624. while (this.indentStack.length && this.indentStack[0] > indents) {
  625. this.stash.push(new Token('outdent'));
  626. this.indentStack.shift();
  627. }
  628. tok = this.stash.pop();
  629. // Indent
  630. } else if (indents && indents != this.indentStack[0]) {
  631. this.indentStack.unshift(indents);
  632. tok = new Token('indent');
  633. // Newline
  634. } else {
  635. tok = new Token('newline');
  636. }
  637. return tok;
  638. }
  639. },
  640. /**
  641. * '-'? (digit+ | digit* '.' digit+) unit
  642. */
  643. unit: function() {
  644. var captures;
  645. if (captures = /^(-)?(\d+\.\d+|\d+|\.\d+)(%|[a-zA-Z]+)?[ \t]*/.exec(this.str)) {
  646. this.skip(captures);
  647. var n = parseFloat(captures[2]);
  648. if ('-' == captures[1]) n = -n;
  649. var node = new nodes.Unit(n, captures[3]);
  650. node.raw = captures[0];
  651. return new Token('unit', node);
  652. }
  653. },
  654. /**
  655. * '"' [^"]+ '"' | "'"" [^']+ "'"
  656. */
  657. string: function() {
  658. var captures;
  659. if (captures = /^("[^"]*"|'[^']*')[ \t]*/.exec(this.str)) {
  660. var str = captures[1]
  661. , quote = captures[0][0];
  662. this.skip(captures);
  663. str = str.slice(1,-1).replace(/\\n/g, '\n');
  664. return new Token('string', new nodes.String(str, quote));
  665. }
  666. },
  667. /**
  668. * #rrggbbaa | #rrggbb | #rgba | #rgb | #nn | #n
  669. */
  670. color: function() {
  671. return this.rrggbbaa()
  672. || this.rrggbb()
  673. || this.rgba()
  674. || this.rgb()
  675. || this.nn()
  676. || this.n()
  677. },
  678. /**
  679. * #n
  680. */
  681. n: function() {
  682. var captures;
  683. if (captures = /^#([a-fA-F0-9]{1})[ \t]*/.exec(this.str)) {
  684. this.skip(captures);
  685. var n = parseInt(captures[1] + captures[1], 16)
  686. , color = new nodes.RGBA(n, n, n, 1);
  687. color.raw = captures[0];
  688. return new Token('color', color);
  689. }
  690. },
  691. /**
  692. * #nn
  693. */
  694. nn: function() {
  695. var captures;
  696. if (captures = /^#([a-fA-F0-9]{2})[ \t]*/.exec(this.str)) {
  697. this.skip(captures);
  698. var n = parseInt(captures[1], 16)
  699. , color = new nodes.RGBA(n, n, n, 1);
  700. color.raw = captures[0];
  701. return new Token('color', color);
  702. }
  703. },
  704. /**
  705. * #rgb
  706. */
  707. rgb: function() {
  708. var captures;
  709. if (captures = /^#([a-fA-F0-9]{3})[ \t]*/.exec(this.str)) {
  710. this.skip(captures);
  711. var rgb = captures[1]
  712. , r = parseInt(rgb[0] + rgb[0], 16)
  713. , g = parseInt(rgb[1] + rgb[1], 16)
  714. , b = parseInt(rgb[2] + rgb[2], 16)
  715. , color = new nodes.RGBA(r, g, b, 1);
  716. color.raw = captures[0];
  717. return new Token('color', color);
  718. }
  719. },
  720. /**
  721. * #rgba
  722. */
  723. rgba: function() {
  724. var captures;
  725. if (captures = /^#([a-fA-F0-9]{4})[ \t]*/.exec(this.str)) {
  726. this.skip(captures);
  727. var rgb = captures[1]
  728. , r = parseInt(rgb[0] + rgb[0], 16)
  729. , g = parseInt(rgb[1] + rgb[1], 16)
  730. , b = parseInt(rgb[2] + rgb[2], 16)
  731. , a = parseInt(rgb[3] + rgb[3], 16)
  732. , color = new nodes.RGBA(r, g, b, a/255);
  733. color.raw = captures[0];
  734. return new Token('color', color);
  735. }
  736. },
  737. /**
  738. * #rrggbb
  739. */
  740. rrggbb: function() {
  741. var captures;
  742. if (captures = /^#([a-fA-F0-9]{6})[ \t]*/.exec(this.str)) {
  743. this.skip(captures);
  744. var rgb = captures[1]
  745. , r = parseInt(rgb.substr(0, 2), 16)
  746. , g = parseInt(rgb.substr(2, 2), 16)
  747. , b = parseInt(rgb.substr(4, 2), 16)
  748. , color = new nodes.RGBA(r, g, b, 1);
  749. color.raw = captures[0];
  750. return new Token('color', color);
  751. }
  752. },
  753. /**
  754. * #rrggbbaa
  755. */
  756. rrggbbaa: function() {
  757. var captures;
  758. if (captures = /^#([a-fA-F0-9]{8})[ \t]*/.exec(this.str)) {
  759. this.skip(captures);
  760. var rgb = captures[1]
  761. , r = parseInt(rgb.substr(0, 2), 16)
  762. , g = parseInt(rgb.substr(2, 2), 16)
  763. , b = parseInt(rgb.substr(4, 2), 16)
  764. , a = parseInt(rgb.substr(6, 2), 16)
  765. , color = new nodes.RGBA(r, g, b, a/255);
  766. color.raw = captures[0];
  767. return new Token('color', color);
  768. }
  769. },
  770. /**
  771. * ^|[^\n,;]+
  772. */
  773. selector: function() {
  774. var captures;
  775. if (captures = /^\^|.*?(?=\/\/(?![^\[]*\])|[,\n{])/.exec(this.str)) {
  776. var selector = captures[0];
  777. this.skip(captures);
  778. return new Token('selector', selector);
  779. }
  780. }
  781. };