883 lines
18 KiB
JavaScript
883 lines
18 KiB
JavaScript
|
|
||
|
/*!
|
||
|
* Stylus - Lexer
|
||
|
* Copyright (c) Automattic <developer.wordpress.com>
|
||
|
* MIT Licensed
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Module dependencies.
|
||
|
*/
|
||
|
|
||
|
var Token = require('./token')
|
||
|
, nodes = require('./nodes')
|
||
|
, errors = require('./errors');
|
||
|
|
||
|
/**
|
||
|
* Expose `Lexer`.
|
||
|
*/
|
||
|
|
||
|
exports = module.exports = Lexer;
|
||
|
|
||
|
/**
|
||
|
* Operator aliases.
|
||
|
*/
|
||
|
|
||
|
var alias = {
|
||
|
'and': '&&'
|
||
|
, 'or': '||'
|
||
|
, 'is': '=='
|
||
|
, 'isnt': '!='
|
||
|
, 'is not': '!='
|
||
|
, ':=': '?='
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Initialize a new `Lexer` with the given `str` and `options`.
|
||
|
*
|
||
|
* @param {String} str
|
||
|
* @param {Object} options
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function Lexer(str, options) {
|
||
|
options = options || {};
|
||
|
this.stash = [];
|
||
|
this.indentStack = [];
|
||
|
this.indentRe = null;
|
||
|
this.lineno = 1;
|
||
|
this.column = 1;
|
||
|
|
||
|
// HACK!
|
||
|
function comment(str, val, offset, s) {
|
||
|
var inComment = s.lastIndexOf('/*', offset) > s.lastIndexOf('*/', offset)
|
||
|
, commentIdx = s.lastIndexOf('//', offset)
|
||
|
, i = s.lastIndexOf('\n', offset)
|
||
|
, double = 0
|
||
|
, single = 0;
|
||
|
|
||
|
if (~commentIdx && commentIdx > i) {
|
||
|
while (i != offset) {
|
||
|
if ("'" == s[i]) single ? single-- : single++;
|
||
|
if ('"' == s[i]) double ? double-- : double++;
|
||
|
|
||
|
if ('/' == s[i] && '/' == s[i + 1]) {
|
||
|
inComment = !single && !double;
|
||
|
break;
|
||
|
}
|
||
|
++i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return inComment
|
||
|
? str
|
||
|
: val + '\r';
|
||
|
};
|
||
|
|
||
|
// Remove UTF-8 BOM.
|
||
|
if ('\uFEFF' == str.charAt(0)) str = str.slice(1);
|
||
|
|
||
|
this.str = str
|
||
|
.replace(/\s+$/, '\n')
|
||
|
.replace(/\r\n?/g, '\n')
|
||
|
.replace(/\\ *\n/g, '\r')
|
||
|
.replace(/([,(:](?!\/\/[^ ])) *(?:\/\/[^\n]*|\/\*.*?\*\/)?\n\s*/g, comment)
|
||
|
.replace(/\s*\n[ \t]*([,)])/g, comment);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Lexer prototype.
|
||
|
*/
|
||
|
|
||
|
Lexer.prototype = {
|
||
|
|
||
|
/**
|
||
|
* Custom inspect.
|
||
|
*/
|
||
|
|
||
|
inspect: function(){
|
||
|
var tok
|
||
|
, tmp = this.str
|
||
|
, buf = [];
|
||
|
while ('eos' != (tok = this.next()).type) {
|
||
|
buf.push(tok.inspect());
|
||
|
}
|
||
|
this.str = tmp;
|
||
|
return buf.concat(tok.inspect()).join('\n');
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Lookahead `n` tokens.
|
||
|
*
|
||
|
* @param {Number} n
|
||
|
* @return {Object}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
lookahead: function(n){
|
||
|
var fetch = n - this.stash.length;
|
||
|
while (fetch-- > 0) this.stash.push(this.advance());
|
||
|
return this.stash[--n];
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Consume the given `len`.
|
||
|
*
|
||
|
* @param {Number|Array} len
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
skip: function(len){
|
||
|
var chunk = len[0];
|
||
|
len = chunk ? chunk.length : len;
|
||
|
this.str = this.str.substr(len);
|
||
|
if (chunk) {
|
||
|
this.move(chunk);
|
||
|
} else {
|
||
|
this.column += len;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Move current line and column position.
|
||
|
*
|
||
|
* @param {String} str
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
move: function(str){
|
||
|
var lines = str.match(/\n/g)
|
||
|
, idx = str.lastIndexOf('\n');
|
||
|
|
||
|
if (lines) this.lineno += lines.length;
|
||
|
this.column = ~idx
|
||
|
? str.length - idx
|
||
|
: this.column + str.length;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Fetch next token including those stashed by peek.
|
||
|
*
|
||
|
* @return {Token}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
next: function() {
|
||
|
var tok = this.stashed() || this.advance();
|
||
|
this.prev = tok;
|
||
|
return tok;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Check if the current token is a part of selector.
|
||
|
*
|
||
|
* @return {Boolean}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
isPartOfSelector: function() {
|
||
|
var tok = this.stash[this.stash.length - 1] || this.prev;
|
||
|
switch (tok && tok.type) {
|
||
|
// #for
|
||
|
case 'color':
|
||
|
return 2 == tok.val.raw.length;
|
||
|
// .or
|
||
|
case '.':
|
||
|
// [is]
|
||
|
case '[':
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Fetch next token.
|
||
|
*
|
||
|
* @return {Token}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
advance: function() {
|
||
|
var column = this.column
|
||
|
, line = this.lineno
|
||
|
, tok = this.eos()
|
||
|
|| this.null()
|
||
|
|| this.sep()
|
||
|
|| this.keyword()
|
||
|
|| this.urlchars()
|
||
|
|| this.comment()
|
||
|
|| this.newline()
|
||
|
|| this.escaped()
|
||
|
|| this.important()
|
||
|
|| this.literal()
|
||
|
|| this.anonFunc()
|
||
|
|| this.atrule()
|
||
|
|| this.function()
|
||
|
|| this.brace()
|
||
|
|| this.paren()
|
||
|
|| this.color()
|
||
|
|| this.string()
|
||
|
|| this.unit()
|
||
|
|| this.namedop()
|
||
|
|| this.boolean()
|
||
|
|| this.unicode()
|
||
|
|| this.ident()
|
||
|
|| this.op()
|
||
|
|| this.eol()
|
||
|
|| this.space()
|
||
|
|| this.selector();
|
||
|
tok.lineno = line;
|
||
|
tok.column = column;
|
||
|
return tok;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Lookahead a single token.
|
||
|
*
|
||
|
* @return {Token}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
peek: function() {
|
||
|
return this.lookahead(1);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Return the next possibly stashed token.
|
||
|
*
|
||
|
* @return {Token}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
stashed: function() {
|
||
|
return this.stash.shift();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* EOS | trailing outdents.
|
||
|
*/
|
||
|
|
||
|
eos: function() {
|
||
|
if (this.str.length) return;
|
||
|
if (this.indentStack.length) {
|
||
|
this.indentStack.shift();
|
||
|
return new Token('outdent');
|
||
|
} else {
|
||
|
return new Token('eos');
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* url char
|
||
|
*/
|
||
|
|
||
|
urlchars: function() {
|
||
|
var captures;
|
||
|
if (!this.isURL) return;
|
||
|
if (captures = /^[\/:@.;?&=*!,<>#%0-9]+/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
return new Token('literal', new nodes.Literal(captures[0]));
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* ';' [ \t]*
|
||
|
*/
|
||
|
|
||
|
sep: function() {
|
||
|
var captures;
|
||
|
if (captures = /^;[ \t]*/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
return new Token(';');
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* '\r'
|
||
|
*/
|
||
|
|
||
|
eol: function() {
|
||
|
if ('\r' == this.str[0]) {
|
||
|
++this.lineno;
|
||
|
this.skip(1);
|
||
|
return this.advance();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* ' '+
|
||
|
*/
|
||
|
|
||
|
space: function() {
|
||
|
var captures;
|
||
|
if (captures = /^([ \t]+)/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
return new Token('space');
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* '\\' . ' '*
|
||
|
*/
|
||
|
|
||
|
escaped: function() {
|
||
|
var captures;
|
||
|
if (captures = /^\\(.)[ \t]*/.exec(this.str)) {
|
||
|
var c = captures[1];
|
||
|
this.skip(captures);
|
||
|
return new Token('ident', new nodes.Literal(c));
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* '@css' ' '* '{' .* '}' ' '*
|
||
|
*/
|
||
|
|
||
|
literal: function() {
|
||
|
// HACK attack !!!
|
||
|
var captures;
|
||
|
if (captures = /^@css[ \t]*\{/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
var c
|
||
|
, braces = 1
|
||
|
, css = ''
|
||
|
, node;
|
||
|
while (c = this.str[0]) {
|
||
|
this.str = this.str.substr(1);
|
||
|
switch (c) {
|
||
|
case '{': ++braces; break;
|
||
|
case '}': --braces; break;
|
||
|
case '\n':
|
||
|
case '\r':
|
||
|
++this.lineno;
|
||
|
break;
|
||
|
}
|
||
|
css += c;
|
||
|
if (!braces) break;
|
||
|
}
|
||
|
css = css.replace(/\s*}$/, '');
|
||
|
node = new nodes.Literal(css);
|
||
|
node.css = true;
|
||
|
return new Token('literal', node);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* '!important' ' '*
|
||
|
*/
|
||
|
|
||
|
important: function() {
|
||
|
var captures;
|
||
|
if (captures = /^!important[ \t]*/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
return new Token('ident', new nodes.Literal('!important'));
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* '{' | '}'
|
||
|
*/
|
||
|
|
||
|
brace: function() {
|
||
|
var captures;
|
||
|
if (captures = /^([{}])/.exec(this.str)) {
|
||
|
this.skip(1);
|
||
|
var brace = captures[1];
|
||
|
return new Token(brace, brace);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* '(' | ')' ' '*
|
||
|
*/
|
||
|
|
||
|
paren: function() {
|
||
|
var captures;
|
||
|
if (captures = /^([()])([ \t]*)/.exec(this.str)) {
|
||
|
var paren = captures[1];
|
||
|
this.skip(captures);
|
||
|
if (')' == paren) this.isURL = false;
|
||
|
var tok = new Token(paren, paren);
|
||
|
tok.space = captures[2];
|
||
|
return tok;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* 'null'
|
||
|
*/
|
||
|
|
||
|
null: function() {
|
||
|
var captures
|
||
|
, tok;
|
||
|
if (captures = /^(null)\b[ \t]*/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
if (this.isPartOfSelector()) {
|
||
|
tok = new Token('ident', new nodes.Ident(captures[0]));
|
||
|
} else {
|
||
|
tok = new Token('null', nodes.null);
|
||
|
}
|
||
|
return tok;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* 'if'
|
||
|
* | 'else'
|
||
|
* | 'unless'
|
||
|
* | 'return'
|
||
|
* | 'for'
|
||
|
* | 'in'
|
||
|
*/
|
||
|
|
||
|
keyword: function() {
|
||
|
var captures
|
||
|
, tok;
|
||
|
if (captures = /^(return|if|else|unless|for|in)\b[ \t]*/.exec(this.str)) {
|
||
|
var keyword = captures[1];
|
||
|
this.skip(captures);
|
||
|
if (this.isPartOfSelector()) {
|
||
|
tok = new Token('ident', new nodes.Ident(captures[0]));
|
||
|
} else {
|
||
|
tok = new Token(keyword, keyword);
|
||
|
}
|
||
|
return tok;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* 'not'
|
||
|
* | 'and'
|
||
|
* | 'or'
|
||
|
* | 'is'
|
||
|
* | 'is not'
|
||
|
* | 'isnt'
|
||
|
* | 'is a'
|
||
|
* | 'is defined'
|
||
|
*/
|
||
|
|
||
|
namedop: function() {
|
||
|
var captures
|
||
|
, tok;
|
||
|
if (captures = /^(not|and|or|is a|is defined|isnt|is not|is)(?!-)\b([ \t]*)/.exec(this.str)) {
|
||
|
var op = captures[1];
|
||
|
this.skip(captures);
|
||
|
if (this.isPartOfSelector()) {
|
||
|
tok = new Token('ident', new nodes.Ident(captures[0]));
|
||
|
} else {
|
||
|
op = alias[op] || op;
|
||
|
tok = new Token(op, op);
|
||
|
}
|
||
|
tok.space = captures[2];
|
||
|
return tok;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* ','
|
||
|
* | '+'
|
||
|
* | '+='
|
||
|
* | '-'
|
||
|
* | '-='
|
||
|
* | '*'
|
||
|
* | '*='
|
||
|
* | '/'
|
||
|
* | '/='
|
||
|
* | '%'
|
||
|
* | '%='
|
||
|
* | '**'
|
||
|
* | '!'
|
||
|
* | '&'
|
||
|
* | '&&'
|
||
|
* | '||'
|
||
|
* | '>'
|
||
|
* | '>='
|
||
|
* | '<'
|
||
|
* | '<='
|
||
|
* | '='
|
||
|
* | '=='
|
||
|
* | '!='
|
||
|
* | '!'
|
||
|
* | '~'
|
||
|
* | '?='
|
||
|
* | ':='
|
||
|
* | '?'
|
||
|
* | ':'
|
||
|
* | '['
|
||
|
* | ']'
|
||
|
* | '.'
|
||
|
* | '..'
|
||
|
* | '...'
|
||
|
*/
|
||
|
|
||
|
op: function() {
|
||
|
var captures;
|
||
|
if (captures = /^([.]{1,3}|&&|\|\||[!<>=?:]=|\*\*|[-+*\/%]=?|[,=?:!~<>&\[\]])([ \t]*)/.exec(this.str)) {
|
||
|
var op = captures[1];
|
||
|
this.skip(captures);
|
||
|
op = alias[op] || op;
|
||
|
var tok = new Token(op, op);
|
||
|
tok.space = captures[2];
|
||
|
this.isURL = false;
|
||
|
return tok;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* '@('
|
||
|
*/
|
||
|
|
||
|
anonFunc: function() {
|
||
|
var tok;
|
||
|
if ('@' == this.str[0] && '(' == this.str[1]) {
|
||
|
this.skip(2);
|
||
|
tok = new Token('function', new nodes.Ident('anonymous'));
|
||
|
tok.anonymous = true;
|
||
|
return tok;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* '@' (-(\w+)-)?[a-zA-Z0-9-_]+
|
||
|
*/
|
||
|
|
||
|
atrule: function() {
|
||
|
var captures;
|
||
|
if (captures = /^@(?:-(\w+)-)?([a-zA-Z0-9-_]+)[ \t]*/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
var vendor = captures[1]
|
||
|
, type = captures[2]
|
||
|
, tok;
|
||
|
switch (type) {
|
||
|
case 'require':
|
||
|
case 'import':
|
||
|
case 'charset':
|
||
|
case 'namespace':
|
||
|
case 'media':
|
||
|
case 'scope':
|
||
|
case 'supports':
|
||
|
return new Token(type);
|
||
|
case 'document':
|
||
|
return new Token('-moz-document');
|
||
|
case 'block':
|
||
|
return new Token('atblock');
|
||
|
case 'extend':
|
||
|
case 'extends':
|
||
|
return new Token('extend');
|
||
|
case 'keyframes':
|
||
|
return new Token(type, vendor);
|
||
|
default:
|
||
|
return new Token('atrule', (vendor ? '-' + vendor + '-' + type : type));
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* '//' *
|
||
|
*/
|
||
|
|
||
|
comment: function() {
|
||
|
// Single line
|
||
|
if ('/' == this.str[0] && '/' == this.str[1]) {
|
||
|
var end = this.str.indexOf('\n');
|
||
|
if (-1 == end) end = this.str.length;
|
||
|
this.skip(end);
|
||
|
return this.advance();
|
||
|
}
|
||
|
|
||
|
// Multi-line
|
||
|
if ('/' == this.str[0] && '*' == this.str[1]) {
|
||
|
var end = this.str.indexOf('*/');
|
||
|
if (-1 == end) end = this.str.length;
|
||
|
var str = this.str.substr(0, end + 2)
|
||
|
, lines = str.split(/\n|\r/).length - 1
|
||
|
, suppress = true
|
||
|
, inline = false;
|
||
|
this.lineno += lines;
|
||
|
this.skip(end + 2);
|
||
|
// output
|
||
|
if ('!' == str[2]) {
|
||
|
str = str.replace('*!', '*');
|
||
|
suppress = false;
|
||
|
}
|
||
|
if (this.prev && ';' == this.prev.type) inline = true;
|
||
|
return new Token('comment', new nodes.Comment(str, suppress, inline));
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* 'true' | 'false'
|
||
|
*/
|
||
|
|
||
|
boolean: function() {
|
||
|
var captures;
|
||
|
if (captures = /^(true|false)\b([ \t]*)/.exec(this.str)) {
|
||
|
var val = nodes.Boolean('true' == captures[1]);
|
||
|
this.skip(captures);
|
||
|
var tok = new Token('boolean', val);
|
||
|
tok.space = captures[2];
|
||
|
return tok;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* 'U+' [0-9A-Fa-f?]{1,6}(?:-[0-9A-Fa-f]{1,6})?
|
||
|
*/
|
||
|
|
||
|
unicode: function() {
|
||
|
var captures;
|
||
|
if (captures = /^u\+[0-9a-f?]{1,6}(?:-[0-9a-f]{1,6})?/i.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
return new Token('literal', new nodes.Literal(captures[0]));
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* -*[_a-zA-Z$] [-\w\d$]* '('
|
||
|
*/
|
||
|
|
||
|
function: function() {
|
||
|
var captures;
|
||
|
if (captures = /^(-*[_a-zA-Z$][-\w\d$]*)\(([ \t]*)/.exec(this.str)) {
|
||
|
var name = captures[1];
|
||
|
this.skip(captures);
|
||
|
this.isURL = 'url' == name;
|
||
|
var tok = new Token('function', new nodes.Ident(name));
|
||
|
tok.space = captures[2];
|
||
|
return tok;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* -*[_a-zA-Z$] [-\w\d$]*
|
||
|
*/
|
||
|
|
||
|
ident: function() {
|
||
|
var captures;
|
||
|
if (captures = /^-*[_a-zA-Z$][-\w\d$]*/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
return new Token('ident', new nodes.Ident(captures[0]));
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* '\n' ' '+
|
||
|
*/
|
||
|
|
||
|
newline: function() {
|
||
|
var captures, re;
|
||
|
|
||
|
// we have established the indentation regexp
|
||
|
if (this.indentRe){
|
||
|
captures = this.indentRe.exec(this.str);
|
||
|
// figure out if we are using tabs or spaces
|
||
|
} else {
|
||
|
// try tabs
|
||
|
re = /^\n([\t]*)[ \t]*/;
|
||
|
captures = re.exec(this.str);
|
||
|
|
||
|
// nope, try spaces
|
||
|
if (captures && !captures[1].length) {
|
||
|
re = /^\n([ \t]*)/;
|
||
|
captures = re.exec(this.str);
|
||
|
}
|
||
|
|
||
|
// established
|
||
|
if (captures && captures[1].length) this.indentRe = re;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (captures) {
|
||
|
var tok
|
||
|
, indents = captures[1].length;
|
||
|
|
||
|
this.skip(captures);
|
||
|
if (this.str[0] === ' ' || this.str[0] === '\t') {
|
||
|
throw new errors.SyntaxError('Invalid indentation. You can use tabs or spaces to indent, but not both.');
|
||
|
}
|
||
|
|
||
|
// Blank line
|
||
|
if ('\n' == this.str[0]) return this.advance();
|
||
|
|
||
|
// Outdent
|
||
|
if (this.indentStack.length && indents < this.indentStack[0]) {
|
||
|
while (this.indentStack.length && this.indentStack[0] > indents) {
|
||
|
this.stash.push(new Token('outdent'));
|
||
|
this.indentStack.shift();
|
||
|
}
|
||
|
tok = this.stash.pop();
|
||
|
// Indent
|
||
|
} else if (indents && indents != this.indentStack[0]) {
|
||
|
this.indentStack.unshift(indents);
|
||
|
tok = new Token('indent');
|
||
|
// Newline
|
||
|
} else {
|
||
|
tok = new Token('newline');
|
||
|
}
|
||
|
|
||
|
return tok;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* '-'? (digit+ | digit* '.' digit+) unit
|
||
|
*/
|
||
|
|
||
|
unit: function() {
|
||
|
var captures;
|
||
|
if (captures = /^(-)?(\d+\.\d+|\d+|\.\d+)(%|[a-zA-Z]+)?[ \t]*/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
var n = parseFloat(captures[2]);
|
||
|
if ('-' == captures[1]) n = -n;
|
||
|
var node = new nodes.Unit(n, captures[3]);
|
||
|
node.raw = captures[0];
|
||
|
return new Token('unit', node);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* '"' [^"]+ '"' | "'"" [^']+ "'"
|
||
|
*/
|
||
|
|
||
|
string: function() {
|
||
|
var captures;
|
||
|
if (captures = /^("[^"]*"|'[^']*')[ \t]*/.exec(this.str)) {
|
||
|
var str = captures[1]
|
||
|
, quote = captures[0][0];
|
||
|
this.skip(captures);
|
||
|
str = str.slice(1,-1).replace(/\\n/g, '\n');
|
||
|
return new Token('string', new nodes.String(str, quote));
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* #rrggbbaa | #rrggbb | #rgba | #rgb | #nn | #n
|
||
|
*/
|
||
|
|
||
|
color: function() {
|
||
|
return this.rrggbbaa()
|
||
|
|| this.rrggbb()
|
||
|
|| this.rgba()
|
||
|
|| this.rgb()
|
||
|
|| this.nn()
|
||
|
|| this.n()
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* #n
|
||
|
*/
|
||
|
|
||
|
n: function() {
|
||
|
var captures;
|
||
|
if (captures = /^#([a-fA-F0-9]{1})[ \t]*/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
var n = parseInt(captures[1] + captures[1], 16)
|
||
|
, color = new nodes.RGBA(n, n, n, 1);
|
||
|
color.raw = captures[0];
|
||
|
return new Token('color', color);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* #nn
|
||
|
*/
|
||
|
|
||
|
nn: function() {
|
||
|
var captures;
|
||
|
if (captures = /^#([a-fA-F0-9]{2})[ \t]*/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
var n = parseInt(captures[1], 16)
|
||
|
, color = new nodes.RGBA(n, n, n, 1);
|
||
|
color.raw = captures[0];
|
||
|
return new Token('color', color);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* #rgb
|
||
|
*/
|
||
|
|
||
|
rgb: function() {
|
||
|
var captures;
|
||
|
if (captures = /^#([a-fA-F0-9]{3})[ \t]*/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
var rgb = captures[1]
|
||
|
, r = parseInt(rgb[0] + rgb[0], 16)
|
||
|
, g = parseInt(rgb[1] + rgb[1], 16)
|
||
|
, b = parseInt(rgb[2] + rgb[2], 16)
|
||
|
, color = new nodes.RGBA(r, g, b, 1);
|
||
|
color.raw = captures[0];
|
||
|
return new Token('color', color);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* #rgba
|
||
|
*/
|
||
|
|
||
|
rgba: function() {
|
||
|
var captures;
|
||
|
if (captures = /^#([a-fA-F0-9]{4})[ \t]*/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
var rgb = captures[1]
|
||
|
, r = parseInt(rgb[0] + rgb[0], 16)
|
||
|
, g = parseInt(rgb[1] + rgb[1], 16)
|
||
|
, b = parseInt(rgb[2] + rgb[2], 16)
|
||
|
, a = parseInt(rgb[3] + rgb[3], 16)
|
||
|
, color = new nodes.RGBA(r, g, b, a/255);
|
||
|
color.raw = captures[0];
|
||
|
return new Token('color', color);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* #rrggbb
|
||
|
*/
|
||
|
|
||
|
rrggbb: function() {
|
||
|
var captures;
|
||
|
if (captures = /^#([a-fA-F0-9]{6})[ \t]*/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
var rgb = captures[1]
|
||
|
, r = parseInt(rgb.substr(0, 2), 16)
|
||
|
, g = parseInt(rgb.substr(2, 2), 16)
|
||
|
, b = parseInt(rgb.substr(4, 2), 16)
|
||
|
, color = new nodes.RGBA(r, g, b, 1);
|
||
|
color.raw = captures[0];
|
||
|
return new Token('color', color);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* #rrggbbaa
|
||
|
*/
|
||
|
|
||
|
rrggbbaa: function() {
|
||
|
var captures;
|
||
|
if (captures = /^#([a-fA-F0-9]{8})[ \t]*/.exec(this.str)) {
|
||
|
this.skip(captures);
|
||
|
var rgb = captures[1]
|
||
|
, r = parseInt(rgb.substr(0, 2), 16)
|
||
|
, g = parseInt(rgb.substr(2, 2), 16)
|
||
|
, b = parseInt(rgb.substr(4, 2), 16)
|
||
|
, a = parseInt(rgb.substr(6, 2), 16)
|
||
|
, color = new nodes.RGBA(r, g, b, a/255);
|
||
|
color.raw = captures[0];
|
||
|
return new Token('color', color);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* ^|[^\n,;]+
|
||
|
*/
|
||
|
|
||
|
selector: function() {
|
||
|
var captures;
|
||
|
if (captures = /^\^|.*?(?=\/\/(?![^\[]*\])|[,\n{])/.exec(this.str)) {
|
||
|
var selector = captures[0];
|
||
|
this.skip(captures);
|
||
|
return new Token('selector', selector);
|
||
|
}
|
||
|
}
|
||
|
};
|