| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181 |
- /*!
- * Stylus - Parser
- * Copyright (c) Automattic <developer.wordpress.com>
- * MIT Licensed
- */
- /**
- * Module dependencies.
- */
- var Lexer = require('./lexer')
- , nodes = require('./nodes')
- , Token = require('./token')
- , units = require('./units')
- , errors = require('./errors')
- , cache = require('./cache');
- // debuggers
- var debug = {
- lexer: require('debug')('stylus:lexer')
- , selector: require('debug')('stylus:parser:selector')
- };
- /**
- * Selector composite tokens.
- */
- var selectorTokens = [
- 'ident'
- , 'string'
- , 'selector'
- , 'function'
- , 'comment'
- , 'boolean'
- , 'space'
- , 'color'
- , 'unit'
- , 'for'
- , 'in'
- , '['
- , ']'
- , '('
- , ')'
- , '+'
- , '-'
- , '*'
- , '*='
- , '<'
- , '>'
- , '='
- , ':'
- , '&'
- , '&&'
- , '~'
- , '{'
- , '}'
- , '.'
- , '..'
- , '/'
- ];
- /**
- * CSS pseudo-classes and pseudo-elements.
- * See http://dev.w3.org/csswg/selectors4/
- */
- var pseudoSelectors = [
- // Logical Combinations
- 'matches'
- , 'not'
- // Linguistic Pseudo-classes
- , 'dir'
- , 'lang'
- // Location Pseudo-classes
- , 'any-link'
- , 'link'
- , 'visited'
- , 'local-link'
- , 'target'
- , 'scope'
- // User Action Pseudo-classes
- , 'hover'
- , 'active'
- , 'focus'
- , 'drop'
- // Time-dimensional Pseudo-classes
- , 'current'
- , 'past'
- , 'future'
- // The Input Pseudo-classes
- , 'enabled'
- , 'disabled'
- , 'read-only'
- , 'read-write'
- , 'placeholder-shown'
- , 'checked'
- , 'indeterminate'
- , 'valid'
- , 'invalid'
- , 'in-range'
- , 'out-of-range'
- , 'required'
- , 'optional'
- , 'user-error'
- // Tree-Structural pseudo-classes
- , 'root'
- , 'empty'
- , 'blank'
- , 'nth-child'
- , 'nth-last-child'
- , 'first-child'
- , 'last-child'
- , 'only-child'
- , 'nth-of-type'
- , 'nth-last-of-type'
- , 'first-of-type'
- , 'last-of-type'
- , 'only-of-type'
- , 'nth-match'
- , 'nth-last-match'
- // Grid-Structural Selectors
- , 'nth-column'
- , 'nth-last-column'
- // Pseudo-elements
- , 'first-line'
- , 'first-letter'
- , 'before'
- , 'after'
- // Non-standard
- , 'selection'
- ];
- /**
- * Initialize a new `Parser` with the given `str` and `options`.
- *
- * @param {String} str
- * @param {Object} options
- * @api private
- */
- var Parser = module.exports = function Parser(str, options) {
- var self = this;
- options = options || {};
- Parser.cache = Parser.cache || Parser.getCache(options);
- this.hash = Parser.cache.key(str, options);
- this.lexer = {};
- if (!Parser.cache.has(this.hash)) {
- this.lexer = new Lexer(str, options);
- }
- this.prefix = options.prefix || '';
- this.root = options.root || new nodes.Root;
- this.state = ['root'];
- this.stash = [];
- this.parens = 0;
- this.css = 0;
- this.state.pop = function(){
- self.prevState = [].pop.call(this);
- };
- };
- /**
- * Get cache instance.
- *
- * @param {Object} options
- * @return {Object}
- * @api private
- */
- Parser.getCache = function(options) {
- return false === options.cache
- ? cache(false)
- : cache(options.cache || 'memory', options);
- };
- /**
- * Parser prototype.
- */
- Parser.prototype = {
- /**
- * Constructor.
- */
- constructor: Parser,
- /**
- * Return current state.
- *
- * @return {String}
- * @api private
- */
- currentState: function() {
- return this.state[this.state.length - 1];
- },
- /**
- * Return previous state.
- *
- * @return {String}
- * @api private
- */
- previousState: function() {
- return this.state[this.state.length - 2];
- },
- /**
- * Parse the input, then return the root node.
- *
- * @return {Node}
- * @api private
- */
- parse: function(){
- var block = this.parent = this.root;
- if (Parser.cache.has(this.hash)) {
- block = Parser.cache.get(this.hash);
- // normalize cached imports
- if ('block' == block.nodeName) block.constructor = nodes.Root;
- } else {
- while ('eos' != this.peek().type) {
- this.skipWhitespace();
- if ('eos' == this.peek().type) break;
- var stmt = this.statement();
- this.accept(';');
- if (!stmt) this.error('unexpected token {peek}, not allowed at the root level');
- block.push(stmt);
- }
- Parser.cache.set(this.hash, block);
- }
- return block;
- },
- /**
- * Throw an `Error` with the given `msg`.
- *
- * @param {String} msg
- * @api private
- */
- error: function(msg){
- var type = this.peek().type
- , val = undefined == this.peek().val
- ? ''
- : ' ' + this.peek().toString();
- if (val.trim() == type.trim()) val = '';
- throw new errors.ParseError(msg.replace('{peek}', '"' + type + val + '"'));
- },
- /**
- * Accept the given token `type`, and return it,
- * otherwise return `undefined`.
- *
- * @param {String} type
- * @return {Token}
- * @api private
- */
- accept: function(type){
- if (type == this.peek().type) {
- return this.next();
- }
- },
- /**
- * Expect token `type` and return it, throw otherwise.
- *
- * @param {String} type
- * @return {Token}
- * @api private
- */
- expect: function(type){
- if (type != this.peek().type) {
- this.error('expected "' + type + '", got {peek}');
- }
- return this.next();
- },
- /**
- * Get the next token.
- *
- * @return {Token}
- * @api private
- */
- next: function() {
- var tok = this.stash.length
- ? this.stash.pop()
- : this.lexer.next()
- , line = tok.lineno
- , column = tok.column || 1;
- if (tok.val && tok.val.nodeName) {
- tok.val.lineno = line;
- tok.val.column = column;
- }
- nodes.lineno = line;
- nodes.column = column;
- debug.lexer('%s %s', tok.type, tok.val || '');
- return tok;
- },
- /**
- * Peek with lookahead(1).
- *
- * @return {Token}
- * @api private
- */
- peek: function() {
- return this.lexer.peek();
- },
- /**
- * Lookahead `n` tokens.
- *
- * @param {Number} n
- * @return {Token}
- * @api private
- */
- lookahead: function(n){
- return this.lexer.lookahead(n);
- },
- /**
- * Check if the token at `n` is a valid selector token.
- *
- * @param {Number} n
- * @return {Boolean}
- * @api private
- */
- isSelectorToken: function(n) {
- var la = this.lookahead(n).type;
- switch (la) {
- case 'for':
- return this.bracketed;
- case '[':
- this.bracketed = true;
- return true;
- case ']':
- this.bracketed = false;
- return true;
- default:
- return ~selectorTokens.indexOf(la);
- }
- },
- /**
- * Check if the token at `n` is a pseudo selector.
- *
- * @param {Number} n
- * @return {Boolean}
- * @api private
- */
- isPseudoSelector: function(n){
- var val = this.lookahead(n).val;
- return val && ~pseudoSelectors.indexOf(val.name);
- },
- /**
- * Check if the current line contains `type`.
- *
- * @param {String} type
- * @return {Boolean}
- * @api private
- */
- lineContains: function(type){
- var i = 1
- , la;
- while (la = this.lookahead(i++)) {
- if (~['indent', 'outdent', 'newline', 'eos'].indexOf(la.type)) return;
- if (type == la.type) return true;
- }
- },
- /**
- * Valid selector tokens.
- */
- selectorToken: function() {
- if (this.isSelectorToken(1)) {
- if ('{' == this.peek().type) {
- // unclosed, must be a block
- if (!this.lineContains('}')) return;
- // check if ':' is within the braces.
- // though not required by Stylus, chances
- // are if someone is using {} they will
- // use CSS-style props, helping us with
- // the ambiguity in this case
- var i = 0
- , la;
- while (la = this.lookahead(++i)) {
- if ('}' == la.type) {
- // Check empty block.
- if (i == 2 || (i == 3 && this.lookahead(i - 1).type == 'space'))
- return;
- break;
- }
- if (':' == la.type) return;
- }
- }
- return this.next();
- }
- },
- /**
- * Skip the given `tokens`.
- *
- * @param {Array} tokens
- * @api private
- */
- skip: function(tokens) {
- while (~tokens.indexOf(this.peek().type))
- this.next();
- },
- /**
- * Consume whitespace.
- */
- skipWhitespace: function() {
- this.skip(['space', 'indent', 'outdent', 'newline']);
- },
- /**
- * Consume newlines.
- */
- skipNewlines: function() {
- while ('newline' == this.peek().type)
- this.next();
- },
- /**
- * Consume spaces.
- */
- skipSpaces: function() {
- while ('space' == this.peek().type)
- this.next();
- },
- /**
- * Consume spaces and comments.
- */
- skipSpacesAndComments: function() {
- while ('space' == this.peek().type
- || 'comment' == this.peek().type)
- this.next();
- },
- /**
- * Check if the following sequence of tokens
- * forms a function definition, ie trailing
- * `{` or indentation.
- */
- looksLikeFunctionDefinition: function(i) {
- return 'indent' == this.lookahead(i).type
- || '{' == this.lookahead(i).type;
- },
- /**
- * Check if the following sequence of tokens
- * forms a selector.
- *
- * @param {Boolean} [fromProperty]
- * @return {Boolean}
- * @api private
- */
- looksLikeSelector: function(fromProperty) {
- var i = 1
- , brace;
- // Real property
- if (fromProperty && ':' == this.lookahead(i + 1).type
- && (this.lookahead(i + 1).space || 'indent' == this.lookahead(i + 2).type))
- return false;
- // Assume selector when an ident is
- // followed by a selector
- while ('ident' == this.lookahead(i).type
- && ('newline' == this.lookahead(i + 1).type
- || ',' == this.lookahead(i + 1).type)) i += 2;
- while (this.isSelectorToken(i)
- || ',' == this.lookahead(i).type) {
- if ('selector' == this.lookahead(i).type)
- return true;
- if ('&' == this.lookahead(i + 1).type)
- return true;
- if ('.' == this.lookahead(i).type && 'ident' == this.lookahead(i + 1).type)
- return true;
- if ('*' == this.lookahead(i).type && 'newline' == this.lookahead(i + 1).type)
- return true;
- // Pseudo-elements
- if (':' == this.lookahead(i).type
- && ':' == this.lookahead(i + 1).type)
- return true;
- // #a after an ident and newline
- if ('color' == this.lookahead(i).type
- && 'newline' == this.lookahead(i - 1).type)
- return true;
- if (this.looksLikeAttributeSelector(i))
- return true;
- if (('=' == this.lookahead(i).type || 'function' == this.lookahead(i).type)
- && '{' == this.lookahead(i + 1).type)
- return false;
- // Hash values inside properties
- if (':' == this.lookahead(i).type
- && !this.isPseudoSelector(i + 1)
- && this.lineContains('.'))
- return false;
- // the ':' token within braces signifies
- // a selector. ex: "foo{bar:'baz'}"
- if ('{' == this.lookahead(i).type) brace = true;
- else if ('}' == this.lookahead(i).type) brace = false;
- if (brace && ':' == this.lookahead(i).type) return true;
- // '{' preceded by a space is considered a selector.
- // for example "foo{bar}{baz}" may be a property,
- // however "foo{bar} {baz}" is a selector
- if ('space' == this.lookahead(i).type
- && '{' == this.lookahead(i + 1).type)
- return true;
- // Assume pseudo selectors are NOT properties
- // as 'td:th-child(1)' may look like a property
- // and function call to the parser otherwise
- if (':' == this.lookahead(i++).type
- && !this.lookahead(i-1).space
- && this.isPseudoSelector(i))
- return true;
- // Trailing space
- if ('space' == this.lookahead(i).type
- && 'newline' == this.lookahead(i + 1).type
- && '{' == this.lookahead(i + 2).type)
- return true;
- if (',' == this.lookahead(i).type
- && 'newline' == this.lookahead(i + 1).type)
- return true;
- }
- // Trailing comma
- if (',' == this.lookahead(i).type
- && 'newline' == this.lookahead(i + 1).type)
- return true;
- // Trailing brace
- if ('{' == this.lookahead(i).type
- && 'newline' == this.lookahead(i + 1).type)
- return true;
- // css-style mode, false on ; }
- if (this.css) {
- if (';' == this.lookahead(i).type ||
- '}' == this.lookahead(i - 1).type)
- return false;
- }
- // Trailing separators
- while (!~[
- 'indent'
- , 'outdent'
- , 'newline'
- , 'for'
- , 'if'
- , ';'
- , '}'
- , 'eos'].indexOf(this.lookahead(i).type))
- ++i;
- if ('indent' == this.lookahead(i).type)
- return true;
- },
- /**
- * Check if the following sequence of tokens
- * forms an attribute selector.
- */
- looksLikeAttributeSelector: function(n) {
- var type = this.lookahead(n).type;
- if ('=' == type && this.bracketed) return true;
- return ('ident' == type || 'string' == type)
- && ']' == this.lookahead(n + 1).type
- && ('newline' == this.lookahead(n + 2).type || this.isSelectorToken(n + 2))
- && !this.lineContains(':')
- && !this.lineContains('=');
- },
- /**
- * Check if the following sequence of tokens
- * forms a keyframe block.
- */
- looksLikeKeyframe: function() {
- var i = 2
- , type;
- switch (this.lookahead(i).type) {
- case '{':
- case 'indent':
- case ',':
- return true;
- case 'newline':
- while ('unit' == this.lookahead(++i).type
- || 'newline' == this.lookahead(i).type) ;
- type = this.lookahead(i).type;
- return 'indent' == type || '{' == type;
- }
- },
- /**
- * Check if the current state supports selectors.
- */
- stateAllowsSelector: function() {
- switch (this.currentState()) {
- case 'root':
- case 'atblock':
- case 'selector':
- case 'conditional':
- case 'function':
- case 'atrule':
- case 'for':
- return true;
- }
- },
- /**
- * Try to assign @block to the node.
- *
- * @param {Expression} expr
- * @private
- */
- assignAtblock: function(expr) {
- try {
- expr.push(this.atblock(expr));
- } catch(err) {
- this.error('invalid right-hand side operand in assignment, got {peek}');
- }
- },
- /**
- * statement
- * | statement 'if' expression
- * | statement 'unless' expression
- */
- statement: function() {
- var stmt = this.stmt()
- , state = this.prevState
- , block
- , op;
- // special-case statements since it
- // is not an expression. We could
- // implement postfix conditionals at
- // the expression level, however they
- // would then fail to enclose properties
- if (this.allowPostfix) {
- this.allowPostfix = false;
- state = 'expression';
- }
- switch (state) {
- case 'assignment':
- case 'expression':
- case 'function arguments':
- while (op =
- this.accept('if')
- || this.accept('unless')
- || this.accept('for')) {
- switch (op.type) {
- case 'if':
- case 'unless':
- stmt = new nodes.If(this.expression(), stmt);
- stmt.postfix = true;
- stmt.negate = 'unless' == op.type;
- this.accept(';');
- break;
- case 'for':
- var key
- , val = this.id().name;
- if (this.accept(',')) key = this.id().name;
- this.expect('in');
- var each = new nodes.Each(val, key, this.expression());
- block = new nodes.Block(this.parent, each);
- block.push(stmt);
- each.block = block;
- stmt = each;
- }
- }
- }
- return stmt;
- },
- /**
- * ident
- * | selector
- * | literal
- * | charset
- * | namespace
- * | import
- * | require
- * | media
- * | atrule
- * | scope
- * | keyframes
- * | mozdocument
- * | for
- * | if
- * | unless
- * | comment
- * | expression
- * | 'return' expression
- */
- stmt: function() {
- var type = this.peek().type;
- switch (type) {
- case 'keyframes':
- return this.keyframes();
- case '-moz-document':
- return this.mozdocument();
- case 'comment':
- case 'selector':
- case 'literal':
- case 'charset':
- case 'namespace':
- case 'import':
- case 'require':
- case 'extend':
- case 'media':
- case 'atrule':
- case 'ident':
- case 'scope':
- case 'supports':
- case 'unless':
- case 'function':
- case 'for':
- case 'if':
- return this[type]();
- case 'return':
- return this.return();
- case '{':
- return this.property();
- default:
- // Contextual selectors
- if (this.stateAllowsSelector()) {
- switch (type) {
- case 'color':
- case '~':
- case '>':
- case '<':
- case ':':
- case '&':
- case '&&':
- case '[':
- case '.':
- case '/':
- return this.selector();
- // relative reference
- case '..':
- if ('/' == this.lookahead(2).type)
- return this.selector();
- case '+':
- return 'function' == this.lookahead(2).type
- ? this.functionCall()
- : this.selector();
- case '*':
- return this.property();
- // keyframe blocks (10%, 20% { ... })
- case 'unit':
- if (this.looksLikeKeyframe()) return this.selector();
- case '-':
- if ('{' == this.lookahead(2).type)
- return this.property();
- }
- }
- // Expression fallback
- var expr = this.expression();
- if (expr.isEmpty) this.error('unexpected {peek}');
- return expr;
- }
- },
- /**
- * indent (!outdent)+ outdent
- */
- block: function(node, scope) {
- var delim
- , stmt
- , next
- , block = this.parent = new nodes.Block(this.parent, node);
- if (false === scope) block.scope = false;
- this.accept('newline');
- // css-style
- if (this.accept('{')) {
- this.css++;
- delim = '}';
- this.skipWhitespace();
- } else {
- delim = 'outdent';
- this.expect('indent');
- }
- while (delim != this.peek().type) {
- // css-style
- if (this.css) {
- if (this.accept('newline') || this.accept('indent')) continue;
- stmt = this.statement();
- this.accept(';');
- this.skipWhitespace();
- } else {
- if (this.accept('newline')) continue;
- // skip useless indents and comments
- next = this.lookahead(2).type;
- if ('indent' == this.peek().type
- && ~['outdent', 'newline', 'comment'].indexOf(next)) {
- this.skip(['indent', 'outdent']);
- continue;
- }
- if ('eos' == this.peek().type) return block;
- stmt = this.statement();
- this.accept(';');
- }
- if (!stmt) this.error('unexpected token {peek} in block');
- block.push(stmt);
- }
- // css-style
- if (this.css) {
- this.skipWhitespace();
- this.expect('}');
- this.skipSpaces();
- this.css--;
- } else {
- this.expect('outdent');
- }
- this.parent = block.parent;
- return block;
- },
- /**
- * comment space*
- */
- comment: function(){
- var node = this.next().val;
- this.skipSpaces();
- return node;
- },
- /**
- * for val (',' key) in expr
- */
- for: function() {
- this.expect('for');
- var key
- , val = this.id().name;
- if (this.accept(',')) key = this.id().name;
- this.expect('in');
- this.state.push('for');
- this.cond = true;
- var each = new nodes.Each(val, key, this.expression());
- this.cond = false;
- each.block = this.block(each, false);
- this.state.pop();
- return each;
- },
- /**
- * return expression
- */
- return: function() {
- this.expect('return');
- var expr = this.expression();
- return expr.isEmpty
- ? new nodes.Return
- : new nodes.Return(expr);
- },
- /**
- * unless expression block
- */
- unless: function() {
- this.expect('unless');
- this.state.push('conditional');
- this.cond = true;
- var node = new nodes.If(this.expression(), true);
- this.cond = false;
- node.block = this.block(node, false);
- this.state.pop();
- return node;
- },
- /**
- * if expression block (else block)?
- */
- if: function() {
- this.expect('if');
- this.state.push('conditional');
- this.cond = true;
- var node = new nodes.If(this.expression())
- , cond
- , block;
- this.cond = false;
- node.block = this.block(node, false);
- this.skip(['newline', 'comment']);
- while (this.accept('else')) {
- if (this.accept('if')) {
- this.cond = true;
- cond = this.expression();
- this.cond = false;
- block = this.block(node, false);
- node.elses.push(new nodes.If(cond, block));
- } else {
- node.elses.push(this.block(node, false));
- break;
- }
- this.skip(['newline', 'comment']);
- }
- this.state.pop();
- return node;
- },
- /**
- * @block
- *
- * @param {Expression} [node]
- */
- atblock: function(node){
- if (!node) this.expect('atblock');
- node = new nodes.Atblock;
- this.state.push('atblock');
- node.block = this.block(node, false);
- this.state.pop();
- return node;
- },
- /**
- * atrule selector? block?
- */
- atrule: function(){
- var type = this.expect('atrule').val
- , node = new nodes.Atrule(type)
- , tok;
- this.skipSpacesAndComments();
- node.segments = this.selectorParts();
- this.skipSpacesAndComments();
- tok = this.peek().type;
- if ('indent' == tok || '{' == tok || ('newline' == tok
- && '{' == this.lookahead(2).type)) {
- this.state.push('atrule');
- node.block = this.block(node);
- this.state.pop();
- }
- return node;
- },
- /**
- * scope
- */
- scope: function(){
- this.expect('scope');
- var selector = this.selectorParts()
- .map(function(selector) { return selector.val; })
- .join('');
- this.selectorScope = selector.trim();
- return nodes.null;
- },
- /**
- * supports
- */
- supports: function(){
- this.expect('supports');
- var node = new nodes.Supports(this.supportsCondition());
- this.state.push('atrule');
- node.block = this.block(node);
- this.state.pop();
- return node;
- },
- /**
- * supports negation
- * | supports op
- * | expression
- */
- supportsCondition: function(){
- var node = this.supportsNegation()
- || this.supportsOp();
- if (!node) {
- this.cond = true;
- node = this.expression();
- this.cond = false;
- }
- return node;
- },
- /**
- * 'not' supports feature
- */
- supportsNegation: function(){
- if (this.accept('not')) {
- var node = new nodes.Expression;
- node.push(new nodes.Literal('not'));
- node.push(this.supportsFeature());
- return node;
- }
- },
- /**
- * supports feature (('and' | 'or') supports feature)+
- */
- supportsOp: function(){
- var feature = this.supportsFeature()
- , op
- , expr;
- if (feature) {
- expr = new nodes.Expression;
- expr.push(feature);
- while (op = this.accept('&&') || this.accept('||')) {
- expr.push(new nodes.Literal('&&' == op.val ? 'and' : 'or'));
- expr.push(this.supportsFeature());
- }
- return expr;
- }
- },
- /**
- * ('(' supports condition ')')
- * | feature
- */
- supportsFeature: function(){
- this.skipSpacesAndComments();
- if ('(' == this.peek().type) {
- var la = this.lookahead(2).type;
- if ('ident' == la || '{' == la) {
- return this.feature();
- } else {
- this.expect('(');
- var node = new nodes.Expression;
- node.push(new nodes.Literal('('));
- node.push(this.supportsCondition());
- this.expect(')')
- node.push(new nodes.Literal(')'));
- this.skipSpacesAndComments();
- return node;
- }
- }
- },
- /**
- * extend
- */
- extend: function(){
- var tok = this.expect('extend')
- , selectors = []
- , sel
- , node
- , arr;
- do {
- arr = this.selectorParts();
- if (!arr.length) continue;
- sel = new nodes.Selector(arr);
- selectors.push(sel);
- if ('!' !== this.peek().type) continue;
- tok = this.lookahead(2);
- if ('ident' !== tok.type || 'optional' !== tok.val.name) continue;
- this.skip(['!', 'ident']);
- sel.optional = true;
- } while(this.accept(','));
- node = new nodes.Extend(selectors);
- node.lineno = tok.lineno;
- node.column = tok.column;
- return node;
- },
- /**
- * media queries
- */
- media: function() {
- this.expect('media');
- this.state.push('atrule');
- var media = new nodes.Media(this.queries());
- media.block = this.block(media);
- this.state.pop();
- return media;
- },
- /**
- * query (',' query)*
- */
- queries: function() {
- var queries = new nodes.QueryList
- , skip = ['comment', 'newline', 'space'];
- do {
- this.skip(skip);
- queries.push(this.query());
- this.skip(skip);
- } while (this.accept(','));
- return queries;
- },
- /**
- * expression
- * | (ident | 'not')? ident ('and' feature)*
- * | feature ('and' feature)*
- */
- query: function() {
- var query = new nodes.Query
- , expr
- , pred
- , id;
- // hash values support
- if ('ident' == this.peek().type
- && ('.' == this.lookahead(2).type
- || '[' == this.lookahead(2).type)) {
- this.cond = true;
- expr = this.expression();
- this.cond = false;
- query.push(new nodes.Feature(expr.nodes));
- return query;
- }
- if (pred = this.accept('ident') || this.accept('not')) {
- pred = new nodes.Literal(pred.val.string || pred.val);
- this.skipSpacesAndComments();
- if (id = this.accept('ident')) {
- query.type = id.val;
- query.predicate = pred;
- } else {
- query.type = pred;
- }
- this.skipSpacesAndComments();
- if (!this.accept('&&')) return query;
- }
- do {
- query.push(this.feature());
- } while (this.accept('&&'));
- return query;
- },
- /**
- * '(' ident ( ':'? expression )? ')'
- */
- feature: function() {
- this.skipSpacesAndComments();
- this.expect('(');
- this.skipSpacesAndComments();
- var node = new nodes.Feature(this.interpolate());
- this.skipSpacesAndComments();
- this.accept(':')
- this.skipSpacesAndComments();
- this.inProperty = true;
- node.expr = this.list();
- this.inProperty = false;
- this.skipSpacesAndComments();
- this.expect(')');
- this.skipSpacesAndComments();
- return node;
- },
- /**
- * @-moz-document call (',' call)* block
- */
- mozdocument: function(){
- this.expect('-moz-document');
- var mozdocument = new nodes.Atrule('-moz-document')
- , calls = [];
- do {
- this.skipSpacesAndComments();
- calls.push(this.functionCall());
- this.skipSpacesAndComments();
- } while (this.accept(','));
- mozdocument.segments = [new nodes.Literal(calls.join(', '))];
- this.state.push('atrule');
- mozdocument.block = this.block(mozdocument, false);
- this.state.pop();
- return mozdocument;
- },
- /**
- * import expression
- */
- import: function() {
- this.expect('import');
- this.allowPostfix = true;
- return new nodes.Import(this.expression(), false);
- },
- /**
- * require expression
- */
- require: function() {
- this.expect('require');
- this.allowPostfix = true;
- return new nodes.Import(this.expression(), true);
- },
- /**
- * charset string
- */
- charset: function() {
- this.expect('charset');
- var str = this.expect('string').val;
- this.allowPostfix = true;
- return new nodes.Charset(str);
- },
- /**
- * namespace ident? (string | url)
- */
- namespace: function() {
- var str
- , prefix;
- this.expect('namespace');
- this.skipSpacesAndComments();
- if (prefix = this.accept('ident')) {
- prefix = prefix.val;
- }
- this.skipSpacesAndComments();
- str = this.accept('string') || this.url();
- this.allowPostfix = true;
- return new nodes.Namespace(str, prefix);
- },
- /**
- * keyframes name block
- */
- keyframes: function() {
- var tok = this.expect('keyframes')
- , keyframes;
- this.skipSpacesAndComments();
- keyframes = new nodes.Keyframes(this.selectorParts(), tok.val);
- this.skipSpacesAndComments();
- // block
- this.state.push('atrule');
- keyframes.block = this.block(keyframes);
- this.state.pop();
- return keyframes;
- },
- /**
- * literal
- */
- literal: function() {
- return this.expect('literal').val;
- },
- /**
- * ident space?
- */
- id: function() {
- var tok = this.expect('ident');
- this.accept('space');
- return tok.val;
- },
- /**
- * ident
- * | assignment
- * | property
- * | selector
- */
- ident: function() {
- var i = 2
- , la = this.lookahead(i).type;
- while ('space' == la) la = this.lookahead(++i).type;
- switch (la) {
- // Assignment
- case '=':
- case '?=':
- case '-=':
- case '+=':
- case '*=':
- case '/=':
- case '%=':
- return this.assignment();
- // Member
- case '.':
- if ('space' == this.lookahead(i - 1).type) return this.selector();
- if (this._ident == this.peek()) return this.id();
- while ('=' != this.lookahead(++i).type
- && !~['[', ',', 'newline', 'indent', 'eos'].indexOf(this.lookahead(i).type)) ;
- if ('=' == this.lookahead(i).type) {
- this._ident = this.peek();
- return this.expression();
- } else if (this.looksLikeSelector() && this.stateAllowsSelector()) {
- return this.selector();
- }
- // Assignment []=
- case '[':
- if (this._ident == this.peek()) return this.id();
- while (']' != this.lookahead(i++).type
- && 'selector' != this.lookahead(i).type
- && 'eos' != this.lookahead(i).type) ;
- if ('=' == this.lookahead(i).type) {
- this._ident = this.peek();
- return this.expression();
- } else if (this.looksLikeSelector() && this.stateAllowsSelector()) {
- return this.selector();
- }
- // Operation
- case '-':
- case '+':
- case '/':
- case '*':
- case '%':
- case '**':
- case '&&':
- case '||':
- case '>':
- case '<':
- case '>=':
- case '<=':
- case '!=':
- case '==':
- case '?':
- case 'in':
- case 'is a':
- case 'is defined':
- // Prevent cyclic .ident, return literal
- if (this._ident == this.peek()) {
- return this.id();
- } else {
- this._ident = this.peek();
- switch (this.currentState()) {
- // unary op or selector in property / for
- case 'for':
- case 'selector':
- return this.property();
- // Part of a selector
- case 'root':
- case 'atblock':
- case 'atrule':
- return '[' == la
- ? this.subscript()
- : this.selector();
- case 'function':
- case 'conditional':
- return this.looksLikeSelector()
- ? this.selector()
- : this.expression();
- // Do not disrupt the ident when an operand
- default:
- return this.operand
- ? this.id()
- : this.expression();
- }
- }
- // Selector or property
- default:
- switch (this.currentState()) {
- case 'root':
- return this.selector();
- case 'for':
- case 'selector':
- case 'function':
- case 'conditional':
- case 'atblock':
- case 'atrule':
- return this.property();
- default:
- var id = this.id();
- if ('interpolation' == this.previousState()) id.mixin = true;
- return id;
- }
- }
- },
- /**
- * '*'? (ident | '{' expression '}')+
- */
- interpolate: function() {
- var node
- , segs = []
- , star;
- star = this.accept('*');
- if (star) segs.push(new nodes.Literal('*'));
- while (true) {
- if (this.accept('{')) {
- this.state.push('interpolation');
- segs.push(this.expression());
- this.expect('}');
- this.state.pop();
- } else if (node = this.accept('-')){
- segs.push(new nodes.Literal('-'));
- } else if (node = this.accept('ident')){
- segs.push(node.val);
- } else {
- break;
- }
- }
- if (!segs.length) this.expect('ident');
- return segs;
- },
- /**
- * property ':'? expression
- * | ident
- */
- property: function() {
- if (this.looksLikeSelector(true)) return this.selector();
- // property
- var ident = this.interpolate()
- , prop = new nodes.Property(ident)
- , ret = prop;
- // optional ':'
- this.accept('space');
- if (this.accept(':')) this.accept('space');
- this.state.push('property');
- this.inProperty = true;
- prop.expr = this.list();
- if (prop.expr.isEmpty) ret = ident[0];
- this.inProperty = false;
- this.allowPostfix = true;
- this.state.pop();
- // optional ';'
- this.accept(';');
- return ret;
- },
- /**
- * selector ',' selector
- * | selector newline selector
- * | selector block
- */
- selector: function() {
- var arr
- , group = new nodes.Group
- , scope = this.selectorScope
- , isRoot = 'root' == this.currentState()
- , selector;
- do {
- // Clobber newline after ,
- this.accept('newline');
- arr = this.selectorParts();
- // Push the selector
- if (isRoot && scope) arr.unshift(new nodes.Literal(scope + ' '));
- if (arr.length) {
- selector = new nodes.Selector(arr);
- selector.lineno = arr[0].lineno;
- selector.column = arr[0].column;
- group.push(selector);
- }
- } while (this.accept(',') || this.accept('newline'));
- if ('selector-parts' == this.currentState()) return group.nodes;
- this.state.push('selector');
- group.block = this.block(group);
- this.state.pop();
- return group;
- },
- selectorParts: function(){
- var tok
- , arr = [];
- // Selector candidates,
- // stitched together to
- // form a selector.
- while (tok = this.selectorToken()) {
- debug.selector('%s', tok);
- // Selector component
- switch (tok.type) {
- case '{':
- this.skipSpaces();
- var expr = this.expression();
- this.skipSpaces();
- this.expect('}');
- arr.push(expr);
- break;
- case this.prefix && '.':
- var literal = new nodes.Literal(tok.val + this.prefix);
- literal.prefixed = true;
- arr.push(literal);
- break;
- case 'comment':
- // ignore comments
- break;
- case 'color':
- case 'unit':
- arr.push(new nodes.Literal(tok.val.raw));
- break;
- case 'space':
- arr.push(new nodes.Literal(' '));
- break;
- case 'function':
- arr.push(new nodes.Literal(tok.val.name + '('));
- break;
- case 'ident':
- arr.push(new nodes.Literal(tok.val.name || tok.val.string));
- break;
- default:
- arr.push(new nodes.Literal(tok.val));
- if (tok.space) arr.push(new nodes.Literal(' '));
- }
- }
- return arr;
- },
- /**
- * ident ('=' | '?=') expression
- */
- assignment: function() {
- var op
- , node
- , name = this.id().name;
- if (op =
- this.accept('=')
- || this.accept('?=')
- || this.accept('+=')
- || this.accept('-=')
- || this.accept('*=')
- || this.accept('/=')
- || this.accept('%=')) {
- this.state.push('assignment');
- var expr = this.list();
- // @block support
- if (expr.isEmpty) this.assignAtblock(expr);
- node = new nodes.Ident(name, expr);
- this.state.pop();
- switch (op.type) {
- case '?=':
- var defined = new nodes.BinOp('is defined', node)
- , lookup = new nodes.Expression;
- lookup.push(new nodes.Ident(name));
- node = new nodes.Ternary(defined, lookup, node);
- break;
- case '+=':
- case '-=':
- case '*=':
- case '/=':
- case '%=':
- node.val = new nodes.BinOp(op.type[0], new nodes.Ident(name), expr);
- break;
- }
- }
- return node;
- },
- /**
- * definition
- * | call
- */
- function: function() {
- var parens = 1
- , i = 2
- , tok;
- // Lookahead and determine if we are dealing
- // with a function call or definition. Here
- // we pair parens to prevent false negatives
- out:
- while (tok = this.lookahead(i++)) {
- switch (tok.type) {
- case 'function':
- case '(':
- ++parens;
- break;
- case ')':
- if (!--parens) break out;
- break;
- case 'eos':
- this.error('failed to find closing paren ")"');
- }
- }
- // Definition or call
- switch (this.currentState()) {
- case 'expression':
- return this.functionCall();
- default:
- return this.looksLikeFunctionDefinition(i)
- ? this.functionDefinition()
- : this.expression();
- }
- },
- /**
- * url '(' (expression | urlchars)+ ')'
- */
- url: function() {
- this.expect('function');
- this.state.push('function arguments');
- var args = this.args();
- this.expect(')');
- this.state.pop();
- return new nodes.Call('url', args);
- },
- /**
- * '+'? ident '(' expression ')' block?
- */
- functionCall: function() {
- var withBlock = this.accept('+');
- if ('url' == this.peek().val.name) return this.url();
- var name = this.expect('function').val.name;
- this.state.push('function arguments');
- this.parens++;
- var args = this.args();
- this.expect(')');
- this.parens--;
- this.state.pop();
- var call = new nodes.Call(name, args);
- if (withBlock) {
- this.state.push('function');
- call.block = this.block(call);
- this.state.pop();
- }
- return call;
- },
- /**
- * ident '(' params ')' block
- */
- functionDefinition: function() {
- var name = this.expect('function').val.name;
- // params
- this.state.push('function params');
- this.skipWhitespace();
- var params = this.params();
- this.skipWhitespace();
- this.expect(')');
- this.state.pop();
- // Body
- this.state.push('function');
- var fn = new nodes.Function(name, params);
- fn.block = this.block(fn);
- this.state.pop();
- return new nodes.Ident(name, fn);
- },
- /**
- * ident
- * | ident '...'
- * | ident '=' expression
- * | ident ',' ident
- */
- params: function() {
- var tok
- , node
- , params = new nodes.Params;
- while (tok = this.accept('ident')) {
- this.accept('space');
- params.push(node = tok.val);
- if (this.accept('...')) {
- node.rest = true;
- } else if (this.accept('=')) {
- node.val = this.expression();
- }
- this.skipWhitespace();
- this.accept(',');
- this.skipWhitespace();
- }
- return params;
- },
- /**
- * (ident ':')? expression (',' (ident ':')? expression)*
- */
- args: function() {
- var args = new nodes.Arguments
- , keyword;
- do {
- // keyword
- if ('ident' == this.peek().type && ':' == this.lookahead(2).type) {
- keyword = this.next().val.string;
- this.expect(':');
- args.map[keyword] = this.expression();
- // arg
- } else {
- args.push(this.expression());
- }
- } while (this.accept(','));
- return args;
- },
- /**
- * expression (',' expression)*
- */
- list: function() {
- var node = this.expression();
- while (this.accept(',')) {
- if (node.isList) {
- list.push(this.expression());
- } else {
- var list = new nodes.Expression(true);
- list.push(node);
- list.push(this.expression());
- node = list;
- }
- }
- return node;
- },
- /**
- * negation+
- */
- expression: function() {
- var node
- , expr = new nodes.Expression;
- this.state.push('expression');
- while (node = this.negation()) {
- if (!node) this.error('unexpected token {peek} in expression');
- expr.push(node);
- }
- this.state.pop();
- if (expr.nodes.length) {
- expr.lineno = expr.nodes[0].lineno;
- expr.column = expr.nodes[0].column;
- }
- return expr;
- },
- /**
- * 'not' ternary
- * | ternary
- */
- negation: function() {
- if (this.accept('not')) {
- return new nodes.UnaryOp('!', this.negation());
- }
- return this.ternary();
- },
- /**
- * logical ('?' expression ':' expression)?
- */
- ternary: function() {
- var node = this.logical();
- if (this.accept('?')) {
- var trueExpr = this.expression();
- this.expect(':');
- var falseExpr = this.expression();
- node = new nodes.Ternary(node, trueExpr, falseExpr);
- }
- return node;
- },
- /**
- * typecheck (('&&' | '||') typecheck)*
- */
- logical: function() {
- var op
- , node = this.typecheck();
- while (op = this.accept('&&') || this.accept('||')) {
- node = new nodes.BinOp(op.type, node, this.typecheck());
- }
- return node;
- },
- /**
- * equality ('is a' equality)*
- */
- typecheck: function() {
- var op
- , node = this.equality();
- while (op = this.accept('is a')) {
- this.operand = true;
- if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
- node = new nodes.BinOp(op.type, node, this.equality());
- this.operand = false;
- }
- return node;
- },
- /**
- * in (('==' | '!=') in)*
- */
- equality: function() {
- var op
- , node = this.in();
- while (op = this.accept('==') || this.accept('!=')) {
- this.operand = true;
- if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
- node = new nodes.BinOp(op.type, node, this.in());
- this.operand = false;
- }
- return node;
- },
- /**
- * relational ('in' relational)*
- */
- in: function() {
- var node = this.relational();
- while (this.accept('in')) {
- this.operand = true;
- if (!node) this.error('illegal unary "in", missing left-hand operand');
- node = new nodes.BinOp('in', node, this.relational());
- this.operand = false;
- }
- return node;
- },
- /**
- * range (('>=' | '<=' | '>' | '<') range)*
- */
- relational: function() {
- var op
- , node = this.range();
- while (op =
- this.accept('>=')
- || this.accept('<=')
- || this.accept('<')
- || this.accept('>')
- ) {
- this.operand = true;
- if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
- node = new nodes.BinOp(op.type, node, this.range());
- this.operand = false;
- }
- return node;
- },
- /**
- * additive (('..' | '...') additive)*
- */
- range: function() {
- var op
- , node = this.additive();
- if (op = this.accept('...') || this.accept('..')) {
- this.operand = true;
- if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
- node = new nodes.BinOp(op.val, node, this.additive());
- this.operand = false;
- }
- return node;
- },
- /**
- * multiplicative (('+' | '-') multiplicative)*
- */
- additive: function() {
- var op
- , node = this.multiplicative();
- while (op = this.accept('+') || this.accept('-')) {
- this.operand = true;
- node = new nodes.BinOp(op.type, node, this.multiplicative());
- this.operand = false;
- }
- return node;
- },
- /**
- * defined (('**' | '*' | '/' | '%') defined)*
- */
- multiplicative: function() {
- var op
- , node = this.defined();
- while (op =
- this.accept('**')
- || this.accept('*')
- || this.accept('/')
- || this.accept('%')) {
- this.operand = true;
- if ('/' == op && this.inProperty && !this.parens) {
- this.stash.push(new Token('literal', new nodes.Literal('/')));
- this.operand = false;
- return node;
- } else {
- if (!node) this.error('illegal unary "' + op + '", missing left-hand operand');
- node = new nodes.BinOp(op.type, node, this.defined());
- this.operand = false;
- }
- }
- return node;
- },
- /**
- * unary 'is defined'
- * | unary
- */
- defined: function() {
- var node = this.unary();
- if (this.accept('is defined')) {
- if (!node) this.error('illegal unary "is defined", missing left-hand operand');
- node = new nodes.BinOp('is defined', node);
- }
- return node;
- },
- /**
- * ('!' | '~' | '+' | '-') unary
- * | subscript
- */
- unary: function() {
- var op
- , node;
- if (op =
- this.accept('!')
- || this.accept('~')
- || this.accept('+')
- || this.accept('-')) {
- this.operand = true;
- node = this.unary();
- if (!node) this.error('illegal unary "' + op + '"');
- node = new nodes.UnaryOp(op.type, node);
- this.operand = false;
- return node;
- }
- return this.subscript();
- },
- /**
- * member ('[' expression ']')+ '='?
- * | member
- */
- subscript: function() {
- var node = this.member()
- , id;
- while (this.accept('[')) {
- node = new nodes.BinOp('[]', node, this.expression());
- this.expect(']');
- }
- // TODO: TernaryOp :)
- if (this.accept('=')) {
- node.op += '=';
- node.val = this.list();
- // @block support
- if (node.val.isEmpty) this.assignAtblock(node.val);
- }
- return node;
- },
- /**
- * primary ('.' id)+ '='?
- * | primary
- */
-
- member: function() {
- var node = this.primary();
- if (node) {
- while (this.accept('.')) {
- var id = new nodes.Ident(this.expect('ident').val.string);
- node = new nodes.Member(node, id);
- }
- this.skipSpaces();
- if (this.accept('=')) {
- node.val = this.list();
- // @block support
- if (node.val.isEmpty) this.assignAtblock(node.val);
- }
- }
- return node;
- },
- /**
- * '{' '}'
- * | '{' pair (ws pair)* '}'
- */
- object: function(){
- var obj = new nodes.Object
- , id, val, comma;
- this.expect('{');
- this.skipWhitespace();
- while (!this.accept('}')) {
- if (this.accept('comment')
- || this.accept('newline')) continue;
- if (!comma) this.accept(',');
- id = this.accept('ident') || this.accept('string');
- if (!id) this.error('expected "ident" or "string", got {peek}');
- id = id.val.hash;
- this.skipSpacesAndComments();
- this.expect(':');
- val = this.expression();
- obj.set(id, val);
- comma = this.accept(',');
- this.skipWhitespace();
- }
- return obj;
- },
- /**
- * unit
- * | null
- * | color
- * | string
- * | ident
- * | boolean
- * | literal
- * | object
- * | atblock
- * | atrule
- * | '(' expression ')' '%'?
- */
- primary: function() {
- var tok;
- this.skipSpaces();
- // Parenthesis
- if (this.accept('(')) {
- ++this.parens;
- var expr = this.expression()
- , paren = this.expect(')');
- --this.parens;
- if (this.accept('%')) expr.push(new nodes.Ident('%'));
- tok = this.peek();
- // (1 + 2)px, (1 + 2)em, etc.
- if (!paren.space
- && 'ident' == tok.type
- && ~units.indexOf(tok.val.string)) {
- expr.push(new nodes.Ident(tok.val.string));
- this.next();
- }
- return expr;
- }
- tok = this.peek();
- // Primitive
- switch (tok.type) {
- case 'null':
- case 'unit':
- case 'color':
- case 'string':
- case 'literal':
- case 'boolean':
- case 'comment':
- return this.next().val;
- case !this.cond && '{':
- return this.object();
- case 'atblock':
- return this.atblock();
- // property lookup
- case 'atrule':
- var id = new nodes.Ident(this.next().val);
- id.property = true;
- return id;
- case 'ident':
- return this.ident();
- case 'function':
- return tok.anonymous
- ? this.functionDefinition()
- : this.functionCall();
- }
- }
- };
|