evaluator.js 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613
  1. /*!
  2. * Stylus - Evaluator
  3. * Copyright (c) Automattic <developer.wordpress.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var Visitor = require('./')
  10. , units = require('../units')
  11. , nodes = require('../nodes')
  12. , Stack = require('../stack')
  13. , Frame = require('../stack/frame')
  14. , utils = require('../utils')
  15. , bifs = require('../functions')
  16. , dirname = require('path').dirname
  17. , colors = require('../colors')
  18. , debug = require('debug')('stylus:evaluator')
  19. , fs = require('fs');
  20. /**
  21. * Import `file` and return Block node.
  22. *
  23. * @api private
  24. */
  25. function importFile(node, file, literal) {
  26. var importStack = this.importStack
  27. , Parser = require('../parser')
  28. , stat;
  29. // Handling the `require`
  30. if (node.once) {
  31. if (this.requireHistory[file]) return nodes.null;
  32. this.requireHistory[file] = true;
  33. if (literal && !this.includeCSS) {
  34. return node;
  35. }
  36. }
  37. // Avoid overflows from importing the same file over again
  38. if (~importStack.indexOf(file))
  39. throw new Error('import loop has been found');
  40. var str = fs.readFileSync(file, 'utf8');
  41. // shortcut for empty files
  42. if (!str.trim()) return nodes.null;
  43. // Expose imports
  44. node.path = file;
  45. node.dirname = dirname(file);
  46. // Store the modified time
  47. stat = fs.statSync(file);
  48. node.mtime = stat.mtime;
  49. this.paths.push(node.dirname);
  50. if (this.options._imports) this.options._imports.push(node.clone());
  51. // Parse the file
  52. importStack.push(file);
  53. nodes.filename = file;
  54. if (literal) {
  55. literal = new nodes.Literal(str.replace(/\r\n?/g, '\n'));
  56. literal.lineno = literal.column = 1;
  57. if (!this.resolveURL) return literal;
  58. }
  59. // parse
  60. var block = new nodes.Block
  61. , parser = new Parser(str, utils.merge({ root: block }, this.options));
  62. try {
  63. block = parser.parse();
  64. } catch (err) {
  65. var line = parser.lexer.lineno
  66. , column = parser.lexer.column;
  67. if (literal && this.includeCSS && this.resolveURL) {
  68. this.warn('ParseError: ' + file + ':' + line + ':' + column + '. This file included as-is');
  69. return literal;
  70. } else {
  71. err.filename = file;
  72. err.lineno = line;
  73. err.column = column;
  74. err.input = str;
  75. throw err;
  76. }
  77. }
  78. // Evaluate imported "root"
  79. block = block.clone(this.currentBlock);
  80. block.parent = this.currentBlock;
  81. block.scope = false;
  82. var ret = this.visit(block);
  83. importStack.pop();
  84. if (!this.resolveURL || this.resolveURL.nocheck) this.paths.pop();
  85. return ret;
  86. }
  87. /**
  88. * Initialize a new `Evaluator` with the given `root` Node
  89. * and the following `options`.
  90. *
  91. * Options:
  92. *
  93. * - `compress` Compress the css output, defaults to false
  94. * - `warn` Warn the user of duplicate function definitions etc
  95. *
  96. * @param {Node} root
  97. * @api private
  98. */
  99. var Evaluator = module.exports = function Evaluator(root, options) {
  100. options = options || {};
  101. Visitor.call(this, root);
  102. var functions = this.functions = options.functions || {};
  103. this.stack = new Stack;
  104. this.imports = options.imports || [];
  105. this.globals = options.globals || {};
  106. this.paths = options.paths || [];
  107. this.prefix = options.prefix || '';
  108. this.filename = options.filename;
  109. this.includeCSS = options['include css'];
  110. this.resolveURL = functions.url
  111. && 'resolver' == functions.url.name
  112. && functions.url.options;
  113. this.paths.push(dirname(options.filename || '.'));
  114. this.stack.push(this.global = new Frame(root));
  115. this.warnings = options.warn;
  116. this.options = options;
  117. this.calling = []; // TODO: remove, use stack
  118. this.importStack = [];
  119. this.requireHistory = {};
  120. this.return = 0;
  121. };
  122. /**
  123. * Inherit from `Visitor.prototype`.
  124. */
  125. Evaluator.prototype.__proto__ = Visitor.prototype;
  126. /**
  127. * Proxy visit to expose node line numbers.
  128. *
  129. * @param {Node} node
  130. * @return {Node}
  131. * @api private
  132. */
  133. var visit = Visitor.prototype.visit;
  134. Evaluator.prototype.visit = function(node){
  135. try {
  136. return visit.call(this, node);
  137. } catch (err) {
  138. if (err.filename) throw err;
  139. err.lineno = node.lineno;
  140. err.column = node.column;
  141. err.filename = node.filename;
  142. err.stylusStack = this.stack.toString();
  143. try {
  144. err.input = fs.readFileSync(err.filename, 'utf8');
  145. } catch (err) {
  146. // ignore
  147. }
  148. throw err;
  149. }
  150. };
  151. /**
  152. * Perform evaluation setup:
  153. *
  154. * - populate global scope
  155. * - iterate imports
  156. *
  157. * @api private
  158. */
  159. Evaluator.prototype.setup = function(){
  160. var root = this.root;
  161. var imports = [];
  162. this.populateGlobalScope();
  163. this.imports.forEach(function(file){
  164. var expr = new nodes.Expression;
  165. expr.push(new nodes.String(file));
  166. imports.push(new nodes.Import(expr));
  167. }, this);
  168. root.nodes = imports.concat(root.nodes);
  169. };
  170. /**
  171. * Populate the global scope with:
  172. *
  173. * - css colors
  174. * - user-defined globals
  175. *
  176. * @api private
  177. */
  178. Evaluator.prototype.populateGlobalScope = function(){
  179. var scope = this.global.scope;
  180. // colors
  181. Object.keys(colors).forEach(function(name){
  182. var color = colors[name]
  183. , rgba = new nodes.RGBA(color[0], color[1], color[2], color[3])
  184. , node = new nodes.Ident(name, rgba);
  185. rgba.name = name;
  186. scope.add(node);
  187. });
  188. // expose url function
  189. scope.add(new nodes.Ident(
  190. 'embedurl',
  191. new nodes.Function('embedurl', require('../functions/url')({
  192. limit: false
  193. }))
  194. ));
  195. // user-defined globals
  196. var globals = this.globals;
  197. Object.keys(globals).forEach(function(name){
  198. var val = globals[name];
  199. if (!val.nodeName) val = new nodes.Literal(val);
  200. scope.add(new nodes.Ident(name, val));
  201. });
  202. };
  203. /**
  204. * Evaluate the tree.
  205. *
  206. * @return {Node}
  207. * @api private
  208. */
  209. Evaluator.prototype.evaluate = function(){
  210. debug('eval %s', this.filename);
  211. this.setup();
  212. return this.visit(this.root);
  213. };
  214. /**
  215. * Visit Group.
  216. */
  217. Evaluator.prototype.visitGroup = function(group){
  218. group.nodes = group.nodes.map(function(selector){
  219. selector.val = this.interpolate(selector);
  220. debug('ruleset %s', selector.val);
  221. return selector;
  222. }, this);
  223. group.block = this.visit(group.block);
  224. return group;
  225. };
  226. /**
  227. * Visit Return.
  228. */
  229. Evaluator.prototype.visitReturn = function(ret){
  230. ret.expr = this.visit(ret.expr);
  231. throw ret;
  232. };
  233. /**
  234. * Visit Media.
  235. */
  236. Evaluator.prototype.visitMedia = function(media){
  237. media.block = this.visit(media.block);
  238. media.val = this.visit(media.val);
  239. return media;
  240. };
  241. /**
  242. * Visit QueryList.
  243. */
  244. Evaluator.prototype.visitQueryList = function(queries){
  245. var val, query;
  246. queries.nodes.forEach(this.visit, this);
  247. if (1 == queries.nodes.length) {
  248. query = queries.nodes[0];
  249. if (val = this.lookup(query.type)) {
  250. val = val.first.string;
  251. if (!val) return queries;
  252. var Parser = require('../parser')
  253. , parser = new Parser(val, this.options);
  254. queries = this.visit(parser.queries());
  255. }
  256. }
  257. return queries;
  258. };
  259. /**
  260. * Visit Query.
  261. */
  262. Evaluator.prototype.visitQuery = function(node){
  263. node.predicate = this.visit(node.predicate);
  264. node.type = this.visit(node.type);
  265. node.nodes.forEach(this.visit, this);
  266. return node;
  267. };
  268. /**
  269. * Visit Feature.
  270. */
  271. Evaluator.prototype.visitFeature = function(node){
  272. node.name = this.interpolate(node);
  273. if (node.expr) {
  274. this.return++;
  275. node.expr = this.visit(node.expr);
  276. this.return--;
  277. }
  278. return node;
  279. };
  280. /**
  281. * Visit Object.
  282. */
  283. Evaluator.prototype.visitObject = function(obj){
  284. for (var key in obj.vals) {
  285. obj.vals[key] = this.visit(obj.vals[key]);
  286. }
  287. return obj;
  288. };
  289. /**
  290. * Visit Member.
  291. */
  292. Evaluator.prototype.visitMember = function(node){
  293. var left = node.left
  294. , right = node.right
  295. , obj = this.visit(left).first;
  296. if ('object' != obj.nodeName) {
  297. throw new Error(left.toString() + ' has no property .' + right);
  298. }
  299. if (node.val) {
  300. this.return++;
  301. obj.set(right.name, this.visit(node.val));
  302. this.return--;
  303. }
  304. return obj.get(right.name);
  305. };
  306. /**
  307. * Visit Keyframes.
  308. */
  309. Evaluator.prototype.visitKeyframes = function(keyframes){
  310. var val;
  311. if (keyframes.fabricated) return keyframes;
  312. keyframes.val = this.interpolate(keyframes).trim();
  313. if (val = this.lookup(keyframes.val)) {
  314. keyframes.val = val.first.string || val.first.name;
  315. }
  316. keyframes.block = this.visit(keyframes.block);
  317. if ('official' != keyframes.prefix) return keyframes;
  318. this.vendors.forEach(function(prefix){
  319. // IE never had prefixes for keyframes
  320. if ('ms' == prefix) return;
  321. var node = keyframes.clone();
  322. node.val = keyframes.val;
  323. node.prefix = prefix;
  324. node.block = keyframes.block;
  325. node.fabricated = true;
  326. this.currentBlock.push(node);
  327. }, this);
  328. return nodes.null;
  329. };
  330. /**
  331. * Visit Function.
  332. */
  333. Evaluator.prototype.visitFunction = function(fn){
  334. // check local
  335. var local = this.stack.currentFrame.scope.lookup(fn.name);
  336. if (local) this.warn('local ' + local.nodeName + ' "' + fn.name + '" previously defined in this scope');
  337. // user-defined
  338. var user = this.functions[fn.name];
  339. if (user) this.warn('user-defined function "' + fn.name + '" is already defined');
  340. // BIF
  341. var bif = bifs[fn.name];
  342. if (bif) this.warn('built-in function "' + fn.name + '" is already defined');
  343. return fn;
  344. };
  345. /**
  346. * Visit Each.
  347. */
  348. Evaluator.prototype.visitEach = function(each){
  349. this.return++;
  350. var expr = utils.unwrap(this.visit(each.expr))
  351. , len = expr.nodes.length
  352. , val = new nodes.Ident(each.val)
  353. , key = new nodes.Ident(each.key || '__index__')
  354. , scope = this.currentScope
  355. , block = this.currentBlock
  356. , vals = []
  357. , self = this
  358. , body
  359. , obj;
  360. this.return--;
  361. each.block.scope = false;
  362. function visitBody(key, val) {
  363. scope.add(val);
  364. scope.add(key);
  365. body = self.visit(each.block.clone());
  366. vals = vals.concat(body.nodes);
  367. }
  368. // for prop in obj
  369. if (1 == len && 'object' == expr.nodes[0].nodeName) {
  370. obj = expr.nodes[0];
  371. for (var prop in obj.vals) {
  372. val.val = new nodes.String(prop);
  373. key.val = obj.get(prop);
  374. visitBody(key, val);
  375. }
  376. } else {
  377. for (var i = 0; i < len; ++i) {
  378. val.val = expr.nodes[i];
  379. key.val = new nodes.Unit(i);
  380. visitBody(key, val);
  381. }
  382. }
  383. this.mixin(vals, block);
  384. return vals[vals.length - 1] || nodes.null;
  385. };
  386. /**
  387. * Visit Call.
  388. */
  389. Evaluator.prototype.visitCall = function(call){
  390. debug('call %s', call);
  391. var fn = this.lookup(call.name)
  392. , literal
  393. , ret;
  394. // url()
  395. this.ignoreColors = 'url' == call.name;
  396. // Variable function
  397. if (fn && 'expression' == fn.nodeName) {
  398. fn = fn.nodes[0];
  399. }
  400. // Not a function? try user-defined or built-ins
  401. if (fn && 'function' != fn.nodeName) {
  402. fn = this.lookupFunction(call.name);
  403. }
  404. // Undefined function? render literal CSS
  405. if (!fn || fn.nodeName != 'function') {
  406. debug('%s is undefined', call);
  407. // Special case for `calc`
  408. if ('calc' == this.unvendorize(call.name)) {
  409. literal = call.args.nodes && call.args.nodes[0];
  410. if (literal) ret = new nodes.Literal(call.name + literal);
  411. } else {
  412. ret = this.literalCall(call);
  413. }
  414. this.ignoreColors = false;
  415. return ret;
  416. }
  417. this.calling.push(call.name);
  418. // Massive stack
  419. if (this.calling.length > 200) {
  420. throw new RangeError('Maximum stylus call stack size exceeded');
  421. }
  422. // First node in expression
  423. if ('expression' == fn.nodeName) fn = fn.first;
  424. // Evaluate arguments
  425. this.return++;
  426. var args = this.visit(call.args);
  427. for (var key in args.map) {
  428. args.map[key] = this.visit(args.map[key].clone());
  429. }
  430. this.return--;
  431. // Built-in
  432. if (fn.fn) {
  433. debug('%s is built-in', call);
  434. ret = this.invokeBuiltin(fn.fn, args);
  435. // User-defined
  436. } else if ('function' == fn.nodeName) {
  437. debug('%s is user-defined', call);
  438. // Evaluate mixin block
  439. if (call.block) call.block = this.visit(call.block);
  440. ret = this.invokeFunction(fn, args, call.block);
  441. }
  442. this.calling.pop();
  443. this.ignoreColors = false;
  444. return ret;
  445. };
  446. /**
  447. * Visit Ident.
  448. */
  449. Evaluator.prototype.visitIdent = function(ident){
  450. var prop;
  451. // Property lookup
  452. if (ident.property) {
  453. if (prop = this.lookupProperty(ident.name)) {
  454. return this.visit(prop.expr.clone());
  455. }
  456. return nodes.null;
  457. // Lookup
  458. } else if (ident.val.isNull) {
  459. var val = this.lookup(ident.name);
  460. // Object or Block mixin
  461. if (val && ident.mixin) this.mixinNode(val);
  462. return val ? this.visit(val) : ident;
  463. // Assign
  464. } else {
  465. this.return++;
  466. ident.val = this.visit(ident.val);
  467. this.return--;
  468. this.currentScope.add(ident);
  469. return ident.val;
  470. }
  471. };
  472. /**
  473. * Visit BinOp.
  474. */
  475. Evaluator.prototype.visitBinOp = function(binop){
  476. // Special-case "is defined" pseudo binop
  477. if ('is defined' == binop.op) return this.isDefined(binop.left);
  478. this.return++;
  479. // Visit operands
  480. var op = binop.op
  481. , left = this.visit(binop.left)
  482. , right = ('||' == op || '&&' == op)
  483. ? binop.right : this.visit(binop.right);
  484. // HACK: ternary
  485. var val = binop.val
  486. ? this.visit(binop.val)
  487. : null;
  488. this.return--;
  489. // Operate
  490. try {
  491. return this.visit(left.operate(op, right, val));
  492. } catch (err) {
  493. // disregard coercion issues in equality
  494. // checks, and simply return false
  495. if ('CoercionError' == err.name) {
  496. switch (op) {
  497. case '==':
  498. return nodes.false;
  499. case '!=':
  500. return nodes.true;
  501. }
  502. }
  503. throw err;
  504. }
  505. };
  506. /**
  507. * Visit UnaryOp.
  508. */
  509. Evaluator.prototype.visitUnaryOp = function(unary){
  510. var op = unary.op
  511. , node = this.visit(unary.expr);
  512. if ('!' != op) {
  513. node = node.first.clone();
  514. utils.assertType(node, 'unit');
  515. }
  516. switch (op) {
  517. case '-':
  518. node.val = -node.val;
  519. break;
  520. case '+':
  521. node.val = +node.val;
  522. break;
  523. case '~':
  524. node.val = ~node.val;
  525. break;
  526. case '!':
  527. return node.toBoolean().negate();
  528. }
  529. return node;
  530. };
  531. /**
  532. * Visit TernaryOp.
  533. */
  534. Evaluator.prototype.visitTernary = function(ternary){
  535. var ok = this.visit(ternary.cond).toBoolean();
  536. return ok.isTrue
  537. ? this.visit(ternary.trueExpr)
  538. : this.visit(ternary.falseExpr);
  539. };
  540. /**
  541. * Visit Expression.
  542. */
  543. Evaluator.prototype.visitExpression = function(expr){
  544. for (var i = 0, len = expr.nodes.length; i < len; ++i) {
  545. expr.nodes[i] = this.visit(expr.nodes[i]);
  546. }
  547. // support (n * 5)px etc
  548. if (this.castable(expr)) expr = this.cast(expr);
  549. return expr;
  550. };
  551. /**
  552. * Visit Arguments.
  553. */
  554. Evaluator.prototype.visitArguments = Evaluator.prototype.visitExpression;
  555. /**
  556. * Visit Property.
  557. */
  558. Evaluator.prototype.visitProperty = function(prop){
  559. var name = this.interpolate(prop)
  560. , fn = this.lookup(name)
  561. , call = fn && 'function' == fn.first.nodeName
  562. , literal = ~this.calling.indexOf(name)
  563. , _prop = this.property;
  564. // Function of the same name
  565. if (call && !literal && !prop.literal) {
  566. var args = nodes.Arguments.fromExpression(utils.unwrap(prop.expr.clone()));
  567. prop.name = name;
  568. this.property = prop;
  569. this.return++;
  570. this.property.expr = this.visit(prop.expr);
  571. this.return--;
  572. var ret = this.visit(new nodes.Call(name, args));
  573. this.property = _prop;
  574. return ret;
  575. // Regular property
  576. } else {
  577. this.return++;
  578. prop.name = name;
  579. prop.literal = true;
  580. this.property = prop;
  581. prop.expr = this.visit(prop.expr);
  582. this.property = _prop;
  583. this.return--;
  584. return prop;
  585. }
  586. };
  587. /**
  588. * Visit Root.
  589. */
  590. Evaluator.prototype.visitRoot = function(block){
  591. // normalize cached imports
  592. if (block != this.root) {
  593. block.constructor = nodes.Block;
  594. return this.visit(block);
  595. }
  596. for (var i = 0; i < block.nodes.length; ++i) {
  597. block.index = i;
  598. block.nodes[i] = this.visit(block.nodes[i]);
  599. }
  600. return block;
  601. };
  602. /**
  603. * Visit Block.
  604. */
  605. Evaluator.prototype.visitBlock = function(block){
  606. this.stack.push(new Frame(block));
  607. for (block.index = 0; block.index < block.nodes.length; ++block.index) {
  608. try {
  609. block.nodes[block.index] = this.visit(block.nodes[block.index]);
  610. } catch (err) {
  611. if ('return' == err.nodeName) {
  612. if (this.return) {
  613. this.stack.pop();
  614. throw err;
  615. } else {
  616. block.nodes[block.index] = err;
  617. break;
  618. }
  619. } else {
  620. throw err;
  621. }
  622. }
  623. }
  624. this.stack.pop();
  625. return block;
  626. };
  627. /**
  628. * Visit Atblock.
  629. */
  630. Evaluator.prototype.visitAtblock = function(atblock){
  631. atblock.block = this.visit(atblock.block);
  632. return atblock;
  633. };
  634. /**
  635. * Visit Atrule.
  636. */
  637. Evaluator.prototype.visitAtrule = function(atrule){
  638. atrule.val = this.interpolate(atrule);
  639. if (atrule.block) atrule.block = this.visit(atrule.block);
  640. return atrule;
  641. };
  642. /**
  643. * Visit Supports.
  644. */
  645. Evaluator.prototype.visitSupports = function(node){
  646. var condition = node.condition
  647. , val;
  648. this.return++;
  649. node.condition = this.visit(condition);
  650. this.return--;
  651. val = condition.first;
  652. if (1 == condition.nodes.length
  653. && 'string' == val.nodeName) {
  654. node.condition = val.string;
  655. }
  656. node.block = this.visit(node.block);
  657. return node;
  658. };
  659. /**
  660. * Visit If.
  661. */
  662. Evaluator.prototype.visitIf = function(node){
  663. var ret
  664. , block = this.currentBlock
  665. , negate = node.negate;
  666. this.return++;
  667. var ok = this.visit(node.cond).first.toBoolean();
  668. this.return--;
  669. node.block.scope = node.block.hasMedia;
  670. // Evaluate body
  671. if (negate) {
  672. // unless
  673. if (ok.isFalse) {
  674. ret = this.visit(node.block);
  675. }
  676. } else {
  677. // if
  678. if (ok.isTrue) {
  679. ret = this.visit(node.block);
  680. // else
  681. } else if (node.elses.length) {
  682. var elses = node.elses
  683. , len = elses.length
  684. , cond;
  685. for (var i = 0; i < len; ++i) {
  686. // else if
  687. if (elses[i].cond) {
  688. elses[i].block.scope = elses[i].block.hasMedia;
  689. this.return++;
  690. cond = this.visit(elses[i].cond).first.toBoolean();
  691. this.return--;
  692. if (cond.isTrue) {
  693. ret = this.visit(elses[i].block);
  694. break;
  695. }
  696. // else
  697. } else {
  698. elses[i].scope = elses[i].hasMedia;
  699. ret = this.visit(elses[i]);
  700. }
  701. }
  702. }
  703. }
  704. // mixin conditional statements within
  705. // a selector group or at-rule
  706. if (ret && !node.postfix && block.node
  707. && ~['group'
  708. , 'atrule'
  709. , 'media'
  710. , 'supports'
  711. , 'keyframes'].indexOf(block.node.nodeName)) {
  712. this.mixin(ret.nodes, block);
  713. return nodes.null;
  714. }
  715. return ret || nodes.null;
  716. };
  717. /**
  718. * Visit Extend.
  719. */
  720. Evaluator.prototype.visitExtend = function(extend){
  721. var block = this.currentBlock;
  722. if ('group' != block.node.nodeName) block = this.closestGroup;
  723. extend.selectors.forEach(function(selector){
  724. block.node.extends.push({
  725. // Cloning the selector for when we are in a loop and don't want it to affect
  726. // the selector nodes and cause the values to be different to expected
  727. selector: this.interpolate(selector.clone()).trim(),
  728. optional: selector.optional,
  729. lineno: selector.lineno,
  730. column: selector.column
  731. });
  732. }, this);
  733. return nodes.null;
  734. };
  735. /**
  736. * Visit Import.
  737. */
  738. Evaluator.prototype.visitImport = function(imported){
  739. this.return++;
  740. var path = this.visit(imported.path).first
  741. , nodeName = imported.once ? 'require' : 'import'
  742. , found
  743. , literal;
  744. this.return--;
  745. debug('import %s', path);
  746. // url() passed
  747. if ('url' == path.name) {
  748. if (imported.once) throw new Error('You cannot @require a url');
  749. return imported;
  750. }
  751. // Ensure string
  752. if (!path.string) throw new Error('@' + nodeName + ' string expected');
  753. var name = path = path.string;
  754. // Absolute URL or hash
  755. if (/(?:url\s*\(\s*)?['"]?(?:#|(?:https?:)?\/\/)/i.test(path)) {
  756. if (imported.once) throw new Error('You cannot @require a url');
  757. return imported;
  758. }
  759. // Literal
  760. if (/\.css(?:"|$)/.test(path)) {
  761. literal = true;
  762. if (!imported.once && !this.includeCSS) {
  763. return imported;
  764. }
  765. }
  766. // support optional .styl
  767. if (!literal && !/\.styl$/i.test(path)) path += '.styl';
  768. // Lookup
  769. found = utils.find(path, this.paths, this.filename);
  770. if (!found) {
  771. found = utils.lookupIndex(name, this.paths, this.filename);
  772. }
  773. // Throw if import failed
  774. if (!found) throw new Error('failed to locate @' + nodeName + ' file ' + path);
  775. var block = new nodes.Block;
  776. for (var i = 0, len = found.length; i < len; ++i) {
  777. block.push(importFile.call(this, imported, found[i], literal));
  778. }
  779. return block;
  780. };
  781. /**
  782. * Invoke `fn` with `args`.
  783. *
  784. * @param {Function} fn
  785. * @param {Array} args
  786. * @return {Node}
  787. * @api private
  788. */
  789. Evaluator.prototype.invokeFunction = function(fn, args, content){
  790. var block = new nodes.Block(fn.block.parent);
  791. // Clone the function body
  792. // to prevent mutation of subsequent calls
  793. var body = fn.block.clone(block);
  794. // mixin block
  795. var mixinBlock = this.stack.currentFrame.block;
  796. // new block scope
  797. this.stack.push(new Frame(block));
  798. var scope = this.currentScope;
  799. // normalize arguments
  800. if ('arguments' != args.nodeName) {
  801. var expr = new nodes.Expression;
  802. expr.push(args);
  803. args = nodes.Arguments.fromExpression(expr);
  804. }
  805. // arguments local
  806. scope.add(new nodes.Ident('arguments', args));
  807. // mixin scope introspection
  808. scope.add(new nodes.Ident('mixin', this.return
  809. ? nodes.false
  810. : new nodes.String(mixinBlock.nodeName)));
  811. // current property
  812. if (this.property) {
  813. var prop = this.propertyExpression(this.property, fn.name);
  814. scope.add(new nodes.Ident('current-property', prop));
  815. } else {
  816. scope.add(new nodes.Ident('current-property', nodes.null));
  817. }
  818. // current call stack
  819. var expr = new nodes.Expression;
  820. for (var i = this.calling.length - 1; i-- ; ) {
  821. expr.push(new nodes.Literal(this.calling[i]));
  822. };
  823. scope.add(new nodes.Ident('called-from', expr));
  824. // inject arguments as locals
  825. var i = 0
  826. , len = args.nodes.length;
  827. fn.params.nodes.forEach(function(node){
  828. // rest param support
  829. if (node.rest) {
  830. node.val = new nodes.Expression;
  831. for (; i < len; ++i) node.val.push(args.nodes[i]);
  832. node.val.preserve = true;
  833. node.val.isList = args.isList;
  834. // argument default support
  835. } else {
  836. var arg = args.map[node.name] || args.nodes[i++];
  837. node = node.clone();
  838. if (arg) {
  839. arg.isEmpty ? args.nodes[i - 1] = this.visit(node) : node.val = arg;
  840. } else {
  841. args.push(node.val);
  842. }
  843. // required argument not satisfied
  844. if (node.val.isNull) {
  845. throw new Error('argument "' + node + '" required for ' + fn);
  846. }
  847. }
  848. scope.add(node);
  849. }, this);
  850. // mixin block
  851. if (content) scope.add(new nodes.Ident('block', content, true));
  852. // invoke
  853. return this.invoke(body, true, fn.filename);
  854. };
  855. /**
  856. * Invoke built-in `fn` with `args`.
  857. *
  858. * @param {Function} fn
  859. * @param {Array} args
  860. * @return {Node}
  861. * @api private
  862. */
  863. Evaluator.prototype.invokeBuiltin = function(fn, args){
  864. // Map arguments to first node
  865. // providing a nicer js api for
  866. // BIFs. Functions may specify that
  867. // they wish to accept full expressions
  868. // via .raw
  869. if (fn.raw) {
  870. args = args.nodes;
  871. } else {
  872. if (!fn.params) {
  873. fn.params = utils.params(fn);
  874. }
  875. args = fn.params.reduce(function(ret, param){
  876. var arg = args.map[param] || args.nodes.shift()
  877. if (arg) {
  878. arg = utils.unwrap(arg);
  879. var len = arg.nodes.length;
  880. if (len > 1) {
  881. for (var i = 0; i < len; ++i) {
  882. ret.push(utils.unwrap(arg.nodes[i].first));
  883. }
  884. } else {
  885. ret.push(arg.first);
  886. }
  887. }
  888. return ret;
  889. }, []);
  890. }
  891. // Invoke the BIF
  892. var body = utils.coerce(fn.apply(this, args));
  893. // Always wrapping allows js functions
  894. // to return several values with a single
  895. // Expression node
  896. var expr = new nodes.Expression;
  897. expr.push(body);
  898. body = expr;
  899. // Invoke
  900. return this.invoke(body);
  901. };
  902. /**
  903. * Invoke the given function `body`.
  904. *
  905. * @param {Block} body
  906. * @return {Node}
  907. * @api private
  908. */
  909. Evaluator.prototype.invoke = function(body, stack, filename){
  910. var self = this
  911. , ret;
  912. if (filename) this.paths.push(dirname(filename));
  913. // Return
  914. if (this.return) {
  915. ret = this.eval(body.nodes);
  916. if (stack) this.stack.pop();
  917. // Mixin
  918. } else {
  919. body = this.visit(body);
  920. if (stack) this.stack.pop();
  921. this.mixin(body.nodes, this.currentBlock);
  922. ret = nodes.null;
  923. }
  924. if (filename) this.paths.pop();
  925. return ret;
  926. };
  927. /**
  928. * Mixin the given `nodes` to the given `block`.
  929. *
  930. * @param {Array} nodes
  931. * @param {Block} block
  932. * @api private
  933. */
  934. Evaluator.prototype.mixin = function(nodes, block){
  935. if (!nodes.length) return;
  936. var len = block.nodes.length
  937. , head = block.nodes.slice(0, block.index)
  938. , tail = block.nodes.slice(block.index + 1, len);
  939. this._mixin(nodes, head, block);
  940. block.index = 0;
  941. block.nodes = head.concat(tail);
  942. };
  943. /**
  944. * Mixin the given `items` to the `dest` array.
  945. *
  946. * @param {Array} items
  947. * @param {Array} dest
  948. * @param {Block} block
  949. * @api private
  950. */
  951. Evaluator.prototype._mixin = function(items, dest, block){
  952. var node
  953. , len = items.length;
  954. for (var i = 0; i < len; ++i) {
  955. switch ((node = items[i]).nodeName) {
  956. case 'return':
  957. return;
  958. case 'block':
  959. this._mixin(node.nodes, dest, block);
  960. break;
  961. case 'media':
  962. // fix link to the parent block
  963. var parentNode = node.block.parent.node;
  964. if (parentNode && 'call' != parentNode.nodeName) {
  965. node.block.parent = block;
  966. }
  967. case 'property':
  968. var val = node.expr;
  969. // prevent `block` mixin recursion
  970. if (node.literal && 'block' == val.first.name) {
  971. val = utils.unwrap(val);
  972. val.nodes[0] = new nodes.Literal('block');
  973. }
  974. default:
  975. dest.push(node);
  976. }
  977. }
  978. };
  979. /**
  980. * Mixin the given `node` to the current block.
  981. *
  982. * @param {Node} node
  983. * @api private
  984. */
  985. Evaluator.prototype.mixinNode = function(node){
  986. node = this.visit(node.first);
  987. switch (node.nodeName) {
  988. case 'object':
  989. this.mixinObject(node);
  990. return nodes.null;
  991. case 'block':
  992. case 'atblock':
  993. this.mixin(node.nodes, this.currentBlock);
  994. return nodes.null;
  995. }
  996. };
  997. /**
  998. * Mixin the given `object` to the current block.
  999. *
  1000. * @param {Object} object
  1001. * @api private
  1002. */
  1003. Evaluator.prototype.mixinObject = function(object){
  1004. var Parser = require('../parser')
  1005. , root = this.root
  1006. , str = '$block ' + object.toBlock()
  1007. , parser = new Parser(str, utils.merge({ root: block }, this.options))
  1008. , block;
  1009. try {
  1010. block = parser.parse();
  1011. } catch (err) {
  1012. err.filename = this.filename;
  1013. err.lineno = parser.lexer.lineno;
  1014. err.column = parser.lexer.column;
  1015. err.input = str;
  1016. throw err;
  1017. }
  1018. block.parent = root;
  1019. block.scope = false;
  1020. var ret = this.visit(block)
  1021. , vals = ret.first.nodes;
  1022. for (var i = 0, len = vals.length; i < len; ++i) {
  1023. if (vals[i].block) {
  1024. this.mixin(vals[i].block.nodes, this.currentBlock);
  1025. break;
  1026. }
  1027. }
  1028. };
  1029. /**
  1030. * Evaluate the given `vals`.
  1031. *
  1032. * @param {Array} vals
  1033. * @return {Node}
  1034. * @api private
  1035. */
  1036. Evaluator.prototype.eval = function(vals){
  1037. if (!vals) return nodes.null;
  1038. var len = vals.length
  1039. , node = nodes.null;
  1040. try {
  1041. for (var i = 0; i < len; ++i) {
  1042. node = vals[i];
  1043. switch (node.nodeName) {
  1044. case 'if':
  1045. if ('block' != node.block.nodeName) {
  1046. node = this.visit(node);
  1047. break;
  1048. }
  1049. case 'each':
  1050. case 'block':
  1051. node = this.visit(node);
  1052. if (node.nodes) node = this.eval(node.nodes);
  1053. break;
  1054. default:
  1055. node = this.visit(node);
  1056. }
  1057. }
  1058. } catch (err) {
  1059. if ('return' == err.nodeName) {
  1060. return err.expr;
  1061. } else {
  1062. throw err;
  1063. }
  1064. }
  1065. return node;
  1066. };
  1067. /**
  1068. * Literal function `call`.
  1069. *
  1070. * @param {Call} call
  1071. * @return {call}
  1072. * @api private
  1073. */
  1074. Evaluator.prototype.literalCall = function(call){
  1075. call.args = this.visit(call.args);
  1076. return call;
  1077. };
  1078. /**
  1079. * Lookup property `name`.
  1080. *
  1081. * @param {String} name
  1082. * @return {Property}
  1083. * @api private
  1084. */
  1085. Evaluator.prototype.lookupProperty = function(name){
  1086. var i = this.stack.length
  1087. , index = this.currentBlock.index
  1088. , top = i
  1089. , nodes
  1090. , block
  1091. , len
  1092. , other;
  1093. while (i--) {
  1094. block = this.stack[i].block;
  1095. if (!block.node) continue;
  1096. switch (block.node.nodeName) {
  1097. case 'group':
  1098. case 'function':
  1099. case 'if':
  1100. case 'each':
  1101. case 'atrule':
  1102. case 'media':
  1103. case 'atblock':
  1104. case 'call':
  1105. nodes = block.nodes;
  1106. // scan siblings from the property index up
  1107. if (i + 1 == top) {
  1108. while (index--) {
  1109. // ignore current property
  1110. if (this.property == nodes[index]) continue;
  1111. other = this.interpolate(nodes[index]);
  1112. if (name == other) return nodes[index].clone();
  1113. }
  1114. // sequential lookup for non-siblings (for now)
  1115. } else {
  1116. len = nodes.length;
  1117. while (len--) {
  1118. if ('property' != nodes[len].nodeName
  1119. || this.property == nodes[len]) continue;
  1120. other = this.interpolate(nodes[len]);
  1121. if (name == other) return nodes[len].clone();
  1122. }
  1123. }
  1124. break;
  1125. }
  1126. }
  1127. return nodes.null;
  1128. };
  1129. /**
  1130. * Return the closest mixin-able `Block`.
  1131. *
  1132. * @return {Block}
  1133. * @api private
  1134. */
  1135. Evaluator.prototype.__defineGetter__('closestBlock', function(){
  1136. var i = this.stack.length
  1137. , block;
  1138. while (i--) {
  1139. block = this.stack[i].block;
  1140. if (block.node) {
  1141. switch (block.node.nodeName) {
  1142. case 'group':
  1143. case 'keyframes':
  1144. case 'atrule':
  1145. case 'atblock':
  1146. case 'media':
  1147. case 'call':
  1148. return block;
  1149. }
  1150. }
  1151. }
  1152. });
  1153. /**
  1154. * Return the closest group block.
  1155. *
  1156. * @return {Block}
  1157. * @api private
  1158. */
  1159. Evaluator.prototype.__defineGetter__('closestGroup', function(){
  1160. var i = this.stack.length
  1161. , block;
  1162. while (i--) {
  1163. block = this.stack[i].block;
  1164. if (block.node && 'group' == block.node.nodeName) {
  1165. return block;
  1166. }
  1167. }
  1168. });
  1169. /**
  1170. * Return the current selectors stack.
  1171. *
  1172. * @return {Array}
  1173. * @api private
  1174. */
  1175. Evaluator.prototype.__defineGetter__('selectorStack', function(){
  1176. var block
  1177. , stack = [];
  1178. for (var i = 0, len = this.stack.length; i < len; ++i) {
  1179. block = this.stack[i].block;
  1180. if (block.node && 'group' == block.node.nodeName) {
  1181. block.node.nodes.forEach(function(selector) {
  1182. if (!selector.val) selector.val = this.interpolate(selector);
  1183. }, this);
  1184. stack.push(block.node.nodes);
  1185. }
  1186. }
  1187. return stack;
  1188. });
  1189. /**
  1190. * Lookup `name`, with support for JavaScript
  1191. * functions, and BIFs.
  1192. *
  1193. * @param {String} name
  1194. * @return {Node}
  1195. * @api private
  1196. */
  1197. Evaluator.prototype.lookup = function(name){
  1198. var val;
  1199. if (this.ignoreColors && name in colors) return;
  1200. if (val = this.stack.lookup(name)) {
  1201. return utils.unwrap(val);
  1202. } else {
  1203. return this.lookupFunction(name);
  1204. }
  1205. };
  1206. /**
  1207. * Map segments in `node` returning a string.
  1208. *
  1209. * @param {Node} node
  1210. * @return {String}
  1211. * @api private
  1212. */
  1213. Evaluator.prototype.interpolate = function(node){
  1214. var self = this
  1215. , isSelector = ('selector' == node.nodeName);
  1216. function toString(node) {
  1217. switch (node.nodeName) {
  1218. case 'function':
  1219. case 'ident':
  1220. return node.name;
  1221. case 'literal':
  1222. case 'string':
  1223. if (self.prefix && !node.prefixed && !node.val.nodeName) {
  1224. node.val = node.val.replace(/\.(?=[\w-])|^\.$/g, '.' + self.prefix);
  1225. node.prefixed = true;
  1226. }
  1227. return node.val;
  1228. case 'unit':
  1229. // Interpolation inside keyframes
  1230. return '%' == node.type ? node.val + '%' : node.val;
  1231. case 'member':
  1232. return toString(self.visit(node));
  1233. case 'expression':
  1234. // Prevent cyclic `selector()` calls.
  1235. if (self.calling && ~self.calling.indexOf('selector') && self._selector) return self._selector;
  1236. self.return++;
  1237. var ret = toString(self.visit(node).first);
  1238. self.return--;
  1239. if (isSelector) self._selector = ret;
  1240. return ret;
  1241. }
  1242. }
  1243. if (node.segments) {
  1244. return node.segments.map(toString).join('');
  1245. } else {
  1246. return toString(node);
  1247. }
  1248. };
  1249. /**
  1250. * Lookup JavaScript user-defined or built-in function.
  1251. *
  1252. * @param {String} name
  1253. * @return {Function}
  1254. * @api private
  1255. */
  1256. Evaluator.prototype.lookupFunction = function(name){
  1257. var fn = this.functions[name] || bifs[name];
  1258. if (fn) return new nodes.Function(name, fn);
  1259. };
  1260. /**
  1261. * Check if the given `node` is an ident, and if it is defined.
  1262. *
  1263. * @param {Node} node
  1264. * @return {Boolean}
  1265. * @api private
  1266. */
  1267. Evaluator.prototype.isDefined = function(node){
  1268. if ('ident' == node.nodeName) {
  1269. return nodes.Boolean(this.lookup(node.name));
  1270. } else {
  1271. throw new Error('invalid "is defined" check on non-variable ' + node);
  1272. }
  1273. };
  1274. /**
  1275. * Return `Expression` based on the given `prop`,
  1276. * replacing cyclic calls to the given function `name`
  1277. * with "__CALL__".
  1278. *
  1279. * @param {Property} prop
  1280. * @param {String} name
  1281. * @return {Expression}
  1282. * @api private
  1283. */
  1284. Evaluator.prototype.propertyExpression = function(prop, name){
  1285. var expr = new nodes.Expression
  1286. , val = prop.expr.clone();
  1287. // name
  1288. expr.push(new nodes.String(prop.name));
  1289. // replace cyclic call with __CALL__
  1290. function replace(node) {
  1291. if ('call' == node.nodeName && name == node.name) {
  1292. return new nodes.Literal('__CALL__');
  1293. }
  1294. if (node.nodes) node.nodes = node.nodes.map(replace);
  1295. return node;
  1296. }
  1297. replace(val);
  1298. expr.push(val);
  1299. return expr;
  1300. };
  1301. /**
  1302. * Cast `expr` to the trailing ident.
  1303. *
  1304. * @param {Expression} expr
  1305. * @return {Unit}
  1306. * @api private
  1307. */
  1308. Evaluator.prototype.cast = function(expr){
  1309. return new nodes.Unit(expr.first.val, expr.nodes[1].name);
  1310. };
  1311. /**
  1312. * Check if `expr` is castable.
  1313. *
  1314. * @param {Expression} expr
  1315. * @return {Boolean}
  1316. * @api private
  1317. */
  1318. Evaluator.prototype.castable = function(expr){
  1319. return 2 == expr.nodes.length
  1320. && 'unit' == expr.first.nodeName
  1321. && ~units.indexOf(expr.nodes[1].name);
  1322. };
  1323. /**
  1324. * Warn with the given `msg`.
  1325. *
  1326. * @param {String} msg
  1327. * @api private
  1328. */
  1329. Evaluator.prototype.warn = function(msg){
  1330. if (!this.warnings) return;
  1331. console.warn('\u001b[33mWarning:\u001b[0m ' + msg);
  1332. };
  1333. /**
  1334. * Return the current `Block`.
  1335. *
  1336. * @return {Block}
  1337. * @api private
  1338. */
  1339. Evaluator.prototype.__defineGetter__('currentBlock', function(){
  1340. return this.stack.currentFrame.block;
  1341. });
  1342. /**
  1343. * Return an array of vendor names.
  1344. *
  1345. * @return {Array}
  1346. * @api private
  1347. */
  1348. Evaluator.prototype.__defineGetter__('vendors', function(){
  1349. return this.lookup('vendors').nodes.map(function(node){
  1350. return node.string;
  1351. });
  1352. });
  1353. /**
  1354. * Return the property name without vendor prefix.
  1355. *
  1356. * @param {String} prop
  1357. * @return {String}
  1358. * @api public
  1359. */
  1360. Evaluator.prototype.unvendorize = function(prop){
  1361. for (var i = 0, len = this.vendors.length; i < len; i++) {
  1362. if ('official' != this.vendors[i]) {
  1363. var vendor = '-' + this.vendors[i] + '-';
  1364. if (~prop.indexOf(vendor)) return prop.replace(vendor, '');
  1365. }
  1366. }
  1367. return prop;
  1368. };
  1369. /**
  1370. * Return the current frame `Scope`.
  1371. *
  1372. * @return {Scope}
  1373. * @api private
  1374. */
  1375. Evaluator.prototype.__defineGetter__('currentScope', function(){
  1376. return this.stack.currentFrame.scope;
  1377. });
  1378. /**
  1379. * Return the current `Frame`.
  1380. *
  1381. * @return {Frame}
  1382. * @api private
  1383. */
  1384. Evaluator.prototype.__defineGetter__('currentFrame', function(){
  1385. return this.stack.currentFrame;
  1386. });