lexer.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882
  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 + '\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. || this.eol()
  195. || this.space()
  196. || this.selector();
  197. tok.lineno = line;
  198. tok.column = column;
  199. return tok;
  200. },
  201. /**
  202. * Lookahead a single token.
  203. *
  204. * @return {Token}
  205. * @api private
  206. */
  207. peek: function() {
  208. return this.lookahead(1);
  209. },
  210. /**
  211. * Return the next possibly stashed token.
  212. *
  213. * @return {Token}
  214. * @api private
  215. */
  216. stashed: function() {
  217. return this.stash.shift();
  218. },
  219. /**
  220. * EOS | trailing outdents.
  221. */
  222. eos: function() {
  223. if (this.str.length) return;
  224. if (this.indentStack.length) {
  225. this.indentStack.shift();
  226. return new Token('outdent');
  227. } else {
  228. return new Token('eos');
  229. }
  230. },
  231. /**
  232. * url char
  233. */
  234. urlchars: function() {
  235. var captures;
  236. if (!this.isURL) return;
  237. if (captures = /^[\/:@.;?&=*!,<>#%0-9]+/.exec(this.str)) {
  238. this.skip(captures);
  239. return new Token('literal', new nodes.Literal(captures[0]));
  240. }
  241. },
  242. /**
  243. * ';' [ \t]*
  244. */
  245. sep: function() {
  246. var captures;
  247. if (captures = /^;[ \t]*/.exec(this.str)) {
  248. this.skip(captures);
  249. return new Token(';');
  250. }
  251. },
  252. /**
  253. * '\r'
  254. */
  255. eol: function() {
  256. if ('\r' == this.str[0]) {
  257. ++this.lineno;
  258. this.skip(1);
  259. return this.advance();
  260. }
  261. },
  262. /**
  263. * ' '+
  264. */
  265. space: function() {
  266. var captures;
  267. if (captures = /^([ \t]+)/.exec(this.str)) {
  268. this.skip(captures);
  269. return new Token('space');
  270. }
  271. },
  272. /**
  273. * '\\' . ' '*
  274. */
  275. escaped: function() {
  276. var captures;
  277. if (captures = /^\\(.)[ \t]*/.exec(this.str)) {
  278. var c = captures[1];
  279. this.skip(captures);
  280. return new Token('ident', new nodes.Literal(c));
  281. }
  282. },
  283. /**
  284. * '@css' ' '* '{' .* '}' ' '*
  285. */
  286. literal: function() {
  287. // HACK attack !!!
  288. var captures;
  289. if (captures = /^@css[ \t]*\{/.exec(this.str)) {
  290. this.skip(captures);
  291. var c
  292. , braces = 1
  293. , css = ''
  294. , node;
  295. while (c = this.str[0]) {
  296. this.str = this.str.substr(1);
  297. switch (c) {
  298. case '{': ++braces; break;
  299. case '}': --braces; break;
  300. case '\n':
  301. case '\r':
  302. ++this.lineno;
  303. break;
  304. }
  305. css += c;
  306. if (!braces) break;
  307. }
  308. css = css.replace(/\s*}$/, '');
  309. node = new nodes.Literal(css);
  310. node.css = true;
  311. return new Token('literal', node);
  312. }
  313. },
  314. /**
  315. * '!important' ' '*
  316. */
  317. important: function() {
  318. var captures;
  319. if (captures = /^!important[ \t]*/.exec(this.str)) {
  320. this.skip(captures);
  321. return new Token('ident', new nodes.Literal('!important'));
  322. }
  323. },
  324. /**
  325. * '{' | '}'
  326. */
  327. brace: function() {
  328. var captures;
  329. if (captures = /^([{}])/.exec(this.str)) {
  330. this.skip(1);
  331. var brace = captures[1];
  332. return new Token(brace, brace);
  333. }
  334. },
  335. /**
  336. * '(' | ')' ' '*
  337. */
  338. paren: function() {
  339. var captures;
  340. if (captures = /^([()])([ \t]*)/.exec(this.str)) {
  341. var paren = captures[1];
  342. this.skip(captures);
  343. if (')' == paren) this.isURL = false;
  344. var tok = new Token(paren, paren);
  345. tok.space = captures[2];
  346. return tok;
  347. }
  348. },
  349. /**
  350. * 'null'
  351. */
  352. null: function() {
  353. var captures
  354. , tok;
  355. if (captures = /^(null)\b[ \t]*/.exec(this.str)) {
  356. this.skip(captures);
  357. if (this.isPartOfSelector()) {
  358. tok = new Token('ident', new nodes.Ident(captures[0]));
  359. } else {
  360. tok = new Token('null', nodes.null);
  361. }
  362. return tok;
  363. }
  364. },
  365. /**
  366. * 'if'
  367. * | 'else'
  368. * | 'unless'
  369. * | 'return'
  370. * | 'for'
  371. * | 'in'
  372. */
  373. keyword: function() {
  374. var captures
  375. , tok;
  376. if (captures = /^(return|if|else|unless|for|in)\b[ \t]*/.exec(this.str)) {
  377. var keyword = captures[1];
  378. this.skip(captures);
  379. if (this.isPartOfSelector()) {
  380. tok = new Token('ident', new nodes.Ident(captures[0]));
  381. } else {
  382. tok = new Token(keyword, keyword);
  383. }
  384. return tok;
  385. }
  386. },
  387. /**
  388. * 'not'
  389. * | 'and'
  390. * | 'or'
  391. * | 'is'
  392. * | 'is not'
  393. * | 'isnt'
  394. * | 'is a'
  395. * | 'is defined'
  396. */
  397. namedop: function() {
  398. var captures
  399. , tok;
  400. if (captures = /^(not|and|or|is a|is defined|isnt|is not|is)(?!-)\b([ \t]*)/.exec(this.str)) {
  401. var op = captures[1];
  402. this.skip(captures);
  403. if (this.isPartOfSelector()) {
  404. tok = new Token('ident', new nodes.Ident(captures[0]));
  405. } else {
  406. op = alias[op] || op;
  407. tok = new Token(op, op);
  408. }
  409. tok.space = captures[2];
  410. return tok;
  411. }
  412. },
  413. /**
  414. * ','
  415. * | '+'
  416. * | '+='
  417. * | '-'
  418. * | '-='
  419. * | '*'
  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. op: function() {
  450. var captures;
  451. if (captures = /^([.]{1,3}|&&|\|\||[!<>=?:]=|\*\*|[-+*\/%]=?|[,=?:!~<>&\[\]])([ \t]*)/.exec(this.str)) {
  452. var op = captures[1];
  453. this.skip(captures);
  454. op = alias[op] || op;
  455. var tok = new Token(op, op);
  456. tok.space = captures[2];
  457. this.isURL = false;
  458. return tok;
  459. }
  460. },
  461. /**
  462. * '@('
  463. */
  464. anonFunc: function() {
  465. var tok;
  466. if ('@' == this.str[0] && '(' == this.str[1]) {
  467. this.skip(2);
  468. tok = new Token('function', new nodes.Ident('anonymous'));
  469. tok.anonymous = true;
  470. return tok;
  471. }
  472. },
  473. /**
  474. * '@' (-(\w+)-)?[a-zA-Z0-9-_]+
  475. */
  476. atrule: function() {
  477. var captures;
  478. if (captures = /^@(?:-(\w+)-)?([a-zA-Z0-9-_]+)[ \t]*/.exec(this.str)) {
  479. this.skip(captures);
  480. var vendor = captures[1]
  481. , type = captures[2]
  482. , tok;
  483. switch (type) {
  484. case 'require':
  485. case 'import':
  486. case 'charset':
  487. case 'namespace':
  488. case 'media':
  489. case 'scope':
  490. case 'supports':
  491. return new Token(type);
  492. case 'document':
  493. return new Token('-moz-document');
  494. case 'block':
  495. return new Token('atblock');
  496. case 'extend':
  497. case 'extends':
  498. return new Token('extend');
  499. case 'keyframes':
  500. return new Token(type, vendor);
  501. default:
  502. return new Token('atrule', (vendor ? '-' + vendor + '-' + type : type));
  503. }
  504. }
  505. },
  506. /**
  507. * '//' *
  508. */
  509. comment: function() {
  510. // Single line
  511. if ('/' == this.str[0] && '/' == this.str[1]) {
  512. var end = this.str.indexOf('\n');
  513. if (-1 == end) end = this.str.length;
  514. this.skip(end);
  515. return this.advance();
  516. }
  517. // Multi-line
  518. if ('/' == this.str[0] && '*' == this.str[1]) {
  519. var end = this.str.indexOf('*/');
  520. if (-1 == end) end = this.str.length;
  521. var str = this.str.substr(0, end + 2)
  522. , lines = str.split(/\n|\r/).length - 1
  523. , suppress = true
  524. , inline = false;
  525. this.lineno += lines;
  526. this.skip(end + 2);
  527. // output
  528. if ('!' == str[2]) {
  529. str = str.replace('*!', '*');
  530. suppress = false;
  531. }
  532. if (this.prev && ';' == this.prev.type) inline = true;
  533. return new Token('comment', new nodes.Comment(str, suppress, inline));
  534. }
  535. },
  536. /**
  537. * 'true' | 'false'
  538. */
  539. boolean: function() {
  540. var captures;
  541. if (captures = /^(true|false)\b([ \t]*)/.exec(this.str)) {
  542. var val = nodes.Boolean('true' == captures[1]);
  543. this.skip(captures);
  544. var tok = new Token('boolean', val);
  545. tok.space = captures[2];
  546. return tok;
  547. }
  548. },
  549. /**
  550. * 'U+' [0-9A-Fa-f?]{1,6}(?:-[0-9A-Fa-f]{1,6})?
  551. */
  552. unicode: function() {
  553. var captures;
  554. if (captures = /^u\+[0-9a-f?]{1,6}(?:-[0-9a-f]{1,6})?/i.exec(this.str)) {
  555. this.skip(captures);
  556. return new Token('literal', new nodes.Literal(captures[0]));
  557. }
  558. },
  559. /**
  560. * -*[_a-zA-Z$] [-\w\d$]* '('
  561. */
  562. function: function() {
  563. var captures;
  564. if (captures = /^(-*[_a-zA-Z$][-\w\d$]*)\(([ \t]*)/.exec(this.str)) {
  565. var name = captures[1];
  566. this.skip(captures);
  567. this.isURL = 'url' == name;
  568. var tok = new Token('function', new nodes.Ident(name));
  569. tok.space = captures[2];
  570. return tok;
  571. }
  572. },
  573. /**
  574. * -*[_a-zA-Z$] [-\w\d$]*
  575. */
  576. ident: function() {
  577. var captures;
  578. if (captures = /^-*[_a-zA-Z$][-\w\d$]*/.exec(this.str)) {
  579. this.skip(captures);
  580. return new Token('ident', new nodes.Ident(captures[0]));
  581. }
  582. },
  583. /**
  584. * '\n' ' '+
  585. */
  586. newline: function() {
  587. var captures, re;
  588. // we have established the indentation regexp
  589. if (this.indentRe){
  590. captures = this.indentRe.exec(this.str);
  591. // figure out if we are using tabs or spaces
  592. } else {
  593. // try tabs
  594. re = /^\n([\t]*)[ \t]*/;
  595. captures = re.exec(this.str);
  596. // nope, try spaces
  597. if (captures && !captures[1].length) {
  598. re = /^\n([ \t]*)/;
  599. captures = re.exec(this.str);
  600. }
  601. // established
  602. if (captures && captures[1].length) this.indentRe = re;
  603. }
  604. if (captures) {
  605. var tok
  606. , indents = captures[1].length;
  607. this.skip(captures);
  608. if (this.str[0] === ' ' || this.str[0] === '\t') {
  609. throw new errors.SyntaxError('Invalid indentation. You can use tabs or spaces to indent, but not both.');
  610. }
  611. // Blank line
  612. if ('\n' == this.str[0]) return this.advance();
  613. // Outdent
  614. if (this.indentStack.length && indents < this.indentStack[0]) {
  615. while (this.indentStack.length && this.indentStack[0] > indents) {
  616. this.stash.push(new Token('outdent'));
  617. this.indentStack.shift();
  618. }
  619. tok = this.stash.pop();
  620. // Indent
  621. } else if (indents && indents != this.indentStack[0]) {
  622. this.indentStack.unshift(indents);
  623. tok = new Token('indent');
  624. // Newline
  625. } else {
  626. tok = new Token('newline');
  627. }
  628. return tok;
  629. }
  630. },
  631. /**
  632. * '-'? (digit+ | digit* '.' digit+) unit
  633. */
  634. unit: function() {
  635. var captures;
  636. if (captures = /^(-)?(\d+\.\d+|\d+|\.\d+)(%|[a-zA-Z]+)?[ \t]*/.exec(this.str)) {
  637. this.skip(captures);
  638. var n = parseFloat(captures[2]);
  639. if ('-' == captures[1]) n = -n;
  640. var node = new nodes.Unit(n, captures[3]);
  641. node.raw = captures[0];
  642. return new Token('unit', node);
  643. }
  644. },
  645. /**
  646. * '"' [^"]+ '"' | "'"" [^']+ "'"
  647. */
  648. string: function() {
  649. var captures;
  650. if (captures = /^("[^"]*"|'[^']*')[ \t]*/.exec(this.str)) {
  651. var str = captures[1]
  652. , quote = captures[0][0];
  653. this.skip(captures);
  654. str = str.slice(1,-1).replace(/\\n/g, '\n');
  655. return new Token('string', new nodes.String(str, quote));
  656. }
  657. },
  658. /**
  659. * #rrggbbaa | #rrggbb | #rgba | #rgb | #nn | #n
  660. */
  661. color: function() {
  662. return this.rrggbbaa()
  663. || this.rrggbb()
  664. || this.rgba()
  665. || this.rgb()
  666. || this.nn()
  667. || this.n()
  668. },
  669. /**
  670. * #n
  671. */
  672. n: function() {
  673. var captures;
  674. if (captures = /^#([a-fA-F0-9]{1})[ \t]*/.exec(this.str)) {
  675. this.skip(captures);
  676. var n = parseInt(captures[1] + captures[1], 16)
  677. , color = new nodes.RGBA(n, n, n, 1);
  678. color.raw = captures[0];
  679. return new Token('color', color);
  680. }
  681. },
  682. /**
  683. * #nn
  684. */
  685. nn: function() {
  686. var captures;
  687. if (captures = /^#([a-fA-F0-9]{2})[ \t]*/.exec(this.str)) {
  688. this.skip(captures);
  689. var n = parseInt(captures[1], 16)
  690. , color = new nodes.RGBA(n, n, n, 1);
  691. color.raw = captures[0];
  692. return new Token('color', color);
  693. }
  694. },
  695. /**
  696. * #rgb
  697. */
  698. rgb: function() {
  699. var captures;
  700. if (captures = /^#([a-fA-F0-9]{3})[ \t]*/.exec(this.str)) {
  701. this.skip(captures);
  702. var rgb = captures[1]
  703. , r = parseInt(rgb[0] + rgb[0], 16)
  704. , g = parseInt(rgb[1] + rgb[1], 16)
  705. , b = parseInt(rgb[2] + rgb[2], 16)
  706. , color = new nodes.RGBA(r, g, b, 1);
  707. color.raw = captures[0];
  708. return new Token('color', color);
  709. }
  710. },
  711. /**
  712. * #rgba
  713. */
  714. rgba: function() {
  715. var captures;
  716. if (captures = /^#([a-fA-F0-9]{4})[ \t]*/.exec(this.str)) {
  717. this.skip(captures);
  718. var rgb = captures[1]
  719. , r = parseInt(rgb[0] + rgb[0], 16)
  720. , g = parseInt(rgb[1] + rgb[1], 16)
  721. , b = parseInt(rgb[2] + rgb[2], 16)
  722. , a = parseInt(rgb[3] + rgb[3], 16)
  723. , color = new nodes.RGBA(r, g, b, a/255);
  724. color.raw = captures[0];
  725. return new Token('color', color);
  726. }
  727. },
  728. /**
  729. * #rrggbb
  730. */
  731. rrggbb: function() {
  732. var captures;
  733. if (captures = /^#([a-fA-F0-9]{6})[ \t]*/.exec(this.str)) {
  734. this.skip(captures);
  735. var rgb = captures[1]
  736. , r = parseInt(rgb.substr(0, 2), 16)
  737. , g = parseInt(rgb.substr(2, 2), 16)
  738. , b = parseInt(rgb.substr(4, 2), 16)
  739. , color = new nodes.RGBA(r, g, b, 1);
  740. color.raw = captures[0];
  741. return new Token('color', color);
  742. }
  743. },
  744. /**
  745. * #rrggbbaa
  746. */
  747. rrggbbaa: function() {
  748. var captures;
  749. if (captures = /^#([a-fA-F0-9]{8})[ \t]*/.exec(this.str)) {
  750. this.skip(captures);
  751. var rgb = captures[1]
  752. , r = parseInt(rgb.substr(0, 2), 16)
  753. , g = parseInt(rgb.substr(2, 2), 16)
  754. , b = parseInt(rgb.substr(4, 2), 16)
  755. , a = parseInt(rgb.substr(6, 2), 16)
  756. , color = new nodes.RGBA(r, g, b, a/255);
  757. color.raw = captures[0];
  758. return new Token('color', color);
  759. }
  760. },
  761. /**
  762. * ^|[^\n,;]+
  763. */
  764. selector: function() {
  765. var captures;
  766. if (captures = /^\^|.*?(?=\/\/(?![^\[]*\])|[,\n{])/.exec(this.str)) {
  767. var selector = captures[0];
  768. this.skip(captures);
  769. return new Token('selector', selector);
  770. }
  771. }
  772. };