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