aboutsummaryrefslogtreecommitdiff
path: root/node_modules/stylus/lib/lexer.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/stylus/lib/lexer.js')
-rw-r--r--node_modules/stylus/lib/lexer.js882
1 files changed, 882 insertions, 0 deletions
diff --git a/node_modules/stylus/lib/lexer.js b/node_modules/stylus/lib/lexer.js
new file mode 100644
index 00000000..a9457e50
--- /dev/null
+++ b/node_modules/stylus/lib/lexer.js
@@ -0,0 +1,882 @@
+
+/*!
+ * 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);
+ }
+ }
+};