<commit_before>if (typeof module === 'object' && typeof define !== 'function') {
	var define = function (factory) {
		module.exports = factory(require, exports, module);
	};
}

define(function(require, exports, module) {
	var _ = require('lodash');
	var utils = require('./common');
	var range = require('../assets/range');
	var stringStream = require('../assets/stringStream');

	var reSpaceTrim = /^(\s*).+?(\s*)$/;
	var reSpace = /\s/g;

	/**
	 * @param {Range} range Full selector range with additional
	 * properties for matching name and content (@see findAllRules())
	 * @param {String} source CSS source
	 */
	function CSSSection(rng, source) {
		/** @type {CSSSection} */
		this.parent = null;
		/** @type {CSSSection} */
		this.nextSibling = null;
		/** @type {CSSSection} */
		this.previousSibling = null;
		this._source = source;

		if (!rng && source) {
			rng = range(0, source);
		}

		this.range = rng;
		this.children = [];
	}

	CSSSection.prototype = {
		addChild: function(section) {
			if (!(section instanceof CSSSection)) {
				section = new CSSSection(section);
			}

			var lastChild = _.last(this.children);
			if (lastChild) {
				lastChild.nextSibling = section;
				section.previousSibling = lastChild;
			}
			section.parent = this;

			this.children.push(section);
			return section;
		},

		/**
		 * Returns root node
		 * @return {CSSSection}
		 */
		root: function() {
			var root = this;
			do {
				if (!root.parent) {
					return root;
				}
			} while(root = root.parent);

			return root;
		},

		/**
		 * Returns currect CSS source
		 * @return {String}
		 */
		source: function() {
			return this._source || this.root()._source;
		},

		/**
		 * Returns section name
		 * @return {String}
		 */
		name: function() {
			var range = this.nameRange();
			if (range) {
				return range.substring(this.source());
			}
		},

		/**
		 * Returns section name range
		 * @return {[type]} [description]
		 */
		nameRange: function() {
			if (this.range && '_selectorEnd' in this.range) {
				return range.create2(this.range.start, this.range._selectorEnd);
			}
		},

		/**
		 * Returns deepest child of current section (or section itself)
		 * which includes given position.
		 * @param  {Number} pos
		 * @return {CSSSection}
		 */
		matchDeep: function(pos) {
			if (!this.range.inside(pos)) {
				return null;
			}

			for (var i = 0, il = this.children.length, m; i < il; i++) {
				m = this.children[i].matchDeep(pos);
				if (m) {
					return m;
				}
			};

			return this.parent ? this : null;
		},

		/**
		 * Returns current and all nested sections ranges
		 * @return {Array}
		 */
		allRanges: function() {
			var out = [];
			if (this.parent) {
				// add current range if it is not root node
				out.push(this.range);
			}

			_.each(this.children, function(child) {
				out = out.concat(child.allRanges());
			});

			return out;
		},

		stringify: function(indent) {
			indent = indent || '';
			var out = '';
			_.each(this.children, function(item) {
				out += indent + item.name().replace(/\n/g, '\\n') + '\n';
				out += item.stringify(indent + '--');
			});

			return out;
		}
	};

	return {
		/**
		 * Finds all CSS rules‘ ranges in given CSS source
		 * @param  {String} content CSS source
		 * @return {Array} Array of ranges
		 */
		findAllRules: function(content) {
			content = this.stripComments(content);
			var stream = stringStream(content);
			var ranges = [], matchedRanges;

			var saveRule = _.bind(function(r) {
				var selRange = this.extractSelector(content, r.start);
				var rule = range.create2(selRange.start, r.end);
				rule._selectorEnd = selRange.end;
				rule._contentStart = r.start;
				ranges.push(rule);
			}, this);

			try {
				while (!stream.eol()) {
					if (stream.peek() == '{') {
						matchedRanges = this.matchBracesRanges(content, stream.pos);
						_.each(matchedRanges, saveRule);

						if (matchedRanges.length) {
							stream.pos = _.last(matchedRanges).end;
							continue;
						}
					}

					stream.next();
				}
			} catch(e) {}

			return ranges.sort(function(a, b) {
				return a.start - b.start;
			});
		},

		/**
		 * Replaces all comments in given CSS source with spaces,
		 * which allows more reliable (and faster) token search
		 * in CSS content
		 * @param  {String} content CSS content
		 * @return {String}
		 */
		stripComments: function(content) {
			var stream = stringStream(content);
			var spaces = '';
			var replaceRanges = [];

			while (!stream.eol()) {
				switch (stream.next()) {
					case '/':
						if (stream.peek() == '*') {
							// multiline CSS comment

							stream.start = stream.pos - 1;

							if (stream.skipTo('*/')) {
								stream.pos += 2;
							} else {
								// unclosed comment
								stream.skipToEnd();
							}

							replaceRanges.push([stream.start, stream.pos]);
						}
						break;
					// TODO: remove preprocessor comments
				}
			}

			if (replaceRanges.length) {
				var offset = 0, fragments = [];
				_.each(replaceRanges, function(r) {
					spaces = utils.repeatString(' ', r[1] - r[0]);
					fragments.push(content.substring(offset, r[0]), spaces);
					offset = r[1];
				});

				fragments.push(content.substring(offset));

				content = fragments.join('');
			}

			return content;
		},

		/**
		 * Matches curly braces content right after given position
		 * @param  {String} content CSS content. Must not contain comments!
		 * @param  {Number} pos     Search start position
		 * @return {Range}
		 */
		matchBracesRanges: function(content, pos, stripComments) {
			if (stripComments) {
				content = this.stripComments(content);
			}

			var stream = stringStream(content);
			stream.start = stream.pos = pos;
			var stack = [], ranges = [];
			while (!stream.eol()) {
				switch (stream.next()) {
					case '{':
						stack.push(stream.pos - 1);
						break;
					case '}':
						if (!stack.length) {
							throw 'Invalid source structure (check for curly braces)';
						}
						ranges.push(range.create2(stack.pop(), stream.pos));
						if (!stack.length) {
							return ranges;
						}
						break;
				}
			}

			return ranges;
		},

		/**
		 * Extracts CSS selector from CSS document from
		 * given position. The selector is located by moving backward
		 * from given position which means that passed position
		 * must point to the end of selector
		 * @param  {String}  content CSS source
		 * @param  {Number}  pos     Search position
		 * @param  {Boolean} stripComments Strip comments from CSS source.
		 * Off by default and assumes that CSS must be comment-free already
		 * (for performance)
		 * @return {Range}
		 */
		extractSelector: function(content, pos, stripComments) {
			if (stripComments) {
				content = this.stripComments(content);
			}

			// find CSS selector
			var ch;
			var endPos = pos;
			while (--pos >= 0) {
				ch = content.charAt(pos);
				if (ch == '{' || ch == '}' || ch == ';') {
					pos++;
					break;
				}
			}

			if (pos < 0) {
				pos = 0;
			}

			var selector = content.substring(pos, endPos);

			// trim whitespace from matched selector
			var m = selector.replace(reSpace, ' ').match(reSpaceTrim);
			if (m) {
				pos += m[1].length;
				endPos -= m[2].length;
			}

			return range.create2(pos, endPos);
		},

		/**
		 * Search for nearest CSS rule/section that contains
		 * given position
		 * @param  {String} content CSS content or matched CSS rules (array of ranges)
		 * @param  {Number} pos     Search position
		 * @return {Range}
		 */
		matchEnclosingRule: function(content, pos) {
			if (_.isString(content)) {
				content = this.findAllRules(content);
			}

			var rules = _.filter(content, function(r) {
				return r.inside(pos);
			});

			return _.last(rules);
		},

		/**
		 * Locates CSS rule next or before given position
		 * @param  {String}  content    CSS content
		 * @param  {Number}  pos        Search start position
		 * @param  {Boolean} isBackward Search backward (find previous rule insteaf of next one)
		 * @return {Range}
		 */
		locateRule: function(content, pos, isBackward) {
			var rules = this.findAllRules(content);
			var ctxRule = this.matchEnclosingRule(rules, pos);
			if (ctxRule) {
				// we have a context rule but it may contain nested rules
				rules = _.filter(rules, function(r) {
					return ctxRule.contains(r);
				});

				// no nested rules
				if (!rules.length) {
					return ctxRule;
				}
			}

			for (var i = 0, il = rules.length; i < il; i++) {
				if (rules[i].start > pos) {
					return rules[isBackward ? i - 1 : i];
				}
			}
		},

		/**
		 * Parses and returns all sections in given CSS
		 * as tree-like structure, e.g. provides nesting
		 * info
		 * @param  {String} content CSS content
		 * @return {CSSSection}
		 */
		sectionTree: function(content) {
			var root = new CSSSection(null, content);
			var rules = this.findAllRules(content);

			// rules are sorted in order they appear in CSS source
			// so we can optimize their nesting routine
			var insert = function(range, ctx) {
				while (ctx && ctx.range) {
					if (ctx.range.contains(range)) {
						return ctx.addChild(range);
					}

					ctx = ctx.parent;
				}

				// if we are here then given range is a top-level section
				return root.addChild(range);
			};

			var ctx = root;
			_.each(rules, function(r) {
				ctx = insert(r, ctx);
			});

			return root;
		},

		CSSSection: CSSSection
	};
});<commit_msg>Improved comment stripping<commit_after>if (typeof module === 'object' && typeof define !== 'function') {
	var define = function (factory) {
		module.exports = factory(require, exports, module);
	};
}

define(function(require, exports, module) {
	var _ = require('lodash');
	var utils = require('./common');
	var range = require('../assets/range');
	var stringStream = require('../assets/stringStream');

	var reSpaceTrim = /^(\s*).+?(\s*)$/;
	var reSpace = /\s/g;

	/**
	 * @param {Range} range Full selector range with additional
	 * properties for matching name and content (@see findAllRules())
	 * @param {String} source CSS source
	 */
	function CSSSection(rng, source) {
		/** @type {CSSSection} */
		this.parent = null;
		/** @type {CSSSection} */
		this.nextSibling = null;
		/** @type {CSSSection} */
		this.previousSibling = null;
		this._source = source;

		if (!rng && source) {
			rng = range(0, source);
		}

		this.range = rng;
		this.children = [];
	}

	CSSSection.prototype = {
		addChild: function(section) {
			if (!(section instanceof CSSSection)) {
				section = new CSSSection(section);
			}

			var lastChild = _.last(this.children);
			if (lastChild) {
				lastChild.nextSibling = section;
				section.previousSibling = lastChild;
			}
			section.parent = this;

			this.children.push(section);
			return section;
		},

		/**
		 * Returns root node
		 * @return {CSSSection}
		 */
		root: function() {
			var root = this;
			do {
				if (!root.parent) {
					return root;
				}
			} while(root = root.parent);

			return root;
		},

		/**
		 * Returns currect CSS source
		 * @return {String}
		 */
		source: function() {
			return this._source || this.root()._source;
		},

		/**
		 * Returns section name
		 * @return {String}
		 */
		name: function() {
			var range = this.nameRange();
			if (range) {
				return range.substring(this.source());
			}
		},

		/**
		 * Returns section name range
		 * @return {[type]} [description]
		 */
		nameRange: function() {
			if (this.range && '_selectorEnd' in this.range) {
				return range.create2(this.range.start, this.range._selectorEnd);
			}
		},

		/**
		 * Returns deepest child of current section (or section itself)
		 * which includes given position.
		 * @param  {Number} pos
		 * @return {CSSSection}
		 */
		matchDeep: function(pos) {
			if (!this.range.inside(pos)) {
				return null;
			}

			for (var i = 0, il = this.children.length, m; i < il; i++) {
				m = this.children[i].matchDeep(pos);
				if (m) {
					return m;
				}
			};

			return this.parent ? this : null;
		},

		/**
		 * Returns current and all nested sections ranges
		 * @return {Array}
		 */
		allRanges: function() {
			var out = [];
			if (this.parent) {
				// add current range if it is not root node
				out.push(this.range);
			}

			_.each(this.children, function(child) {
				out = out.concat(child.allRanges());
			});

			return out;
		},

		stringify: function(indent) {
			indent = indent || '';
			var out = '';
			_.each(this.children, function(item) {
				out += indent + item.name().replace(/\n/g, '\\n') + '\n';
				out += item.stringify(indent + '--');
			});

			return out;
		}
	};

	return {
		/**
		 * Finds all CSS rules‘ ranges in given CSS source
		 * @param  {String} content CSS source
		 * @return {Array} Array of ranges
		 */
		findAllRules: function(content) {
			content = this.stripComments(content);
			var stream = stringStream(content);
			var ranges = [], matchedRanges;

			var saveRule = _.bind(function(r) {
				var selRange = this.extractSelector(content, r.start);
				var rule = range.create2(selRange.start, r.end);
				rule._selectorEnd = selRange.end;
				rule._contentStart = r.start;
				ranges.push(rule);
			}, this);

			try {
				while (!stream.eol()) {
					if (stream.peek() == '{') {
						matchedRanges = this.matchBracesRanges(content, stream.pos);
						_.each(matchedRanges, saveRule);

						if (matchedRanges.length) {
							stream.pos = _.last(matchedRanges).end;
							continue;
						}
					}

					stream.next();
				}
			} catch(e) {}

			return ranges.sort(function(a, b) {
				return a.start - b.start;
			});
		},

		/**
		 * Replaces all comments in given CSS source with spaces,
		 * which allows more reliable (and faster) token search
		 * in CSS content
		 * @param  {String} content CSS content
		 * @return {String}
		 */
		stripComments: function(content) {
			var stream = stringStream(content);
			var replaceRanges = [];
			var ch, ch2;

			while ((ch = stream.next())) {
				if (ch === '"' || ch === "'") {
					// skip string
					while ((ch2 = stream.next())) {
						if (ch2 === '\\') {
							stream.next();
						} else if (ch2 === ch) {
							break;
						}
					}
				} else if (ch === '/') {
					ch2 = stream.peek();
					if (ch2 === '*') { // multiline CSS comment
						stream.start = stream.pos - 1;

						if (stream.skipTo('*/')) {
							stream.pos += 2;
						} else {
							// unclosed comment
							stream.skipToEnd();
						}

						replaceRanges.push([stream.start, stream.pos]);
					} else if (ch2 === '/') {
						// preprocessor’s single line comments
						stream.start = stream.pos - 1;
						while ((ch2 = stream.next())) {
							if (ch2 === '\n' || ch2 == '\r') {
								break
							}
						}

						replaceRanges.push([stream.start, stream.pos]);
					}
				}
			}

			if (replaceRanges.length) {
				var offset = 0, fragments = [];
				_.each(replaceRanges, function(r) {
					var spaces = utils.repeatString(' ', r[1] - r[0]);
					fragments.push(content.substring(offset, r[0]), spaces);
					offset = r[1];
				});

				content = fragments.join('') + content.substring(offset);
			}

			return content;
		},

		/**
		 * Matches curly braces content right after given position
		 * @param  {String} content CSS content. Must not contain comments!
		 * @param  {Number} pos     Search start position
		 * @return {Range}
		 */
		matchBracesRanges: function(content, pos, stripComments) {
			if (stripComments) {
				content = this.stripComments(content);
			}

			var stream = stringStream(content);
			stream.start = stream.pos = pos;
			var stack = [], ranges = [];
			while (!stream.eol()) {
				switch (stream.next()) {
					case '{':
						stack.push(stream.pos - 1);
						break;
					case '}':
						if (!stack.length) {
							throw 'Invalid source structure (check for curly braces)';
						}
						ranges.push(range.create2(stack.pop(), stream.pos));
						if (!stack.length) {
							return ranges;
						}
						break;
				}
			}

			return ranges;
		},

		/**
		 * Extracts CSS selector from CSS document from
		 * given position. The selector is located by moving backward
		 * from given position which means that passed position
		 * must point to the end of selector
		 * @param  {String}  content CSS source
		 * @param  {Number}  pos     Search position
		 * @param  {Boolean} stripComments Strip comments from CSS source.
		 * Off by default and assumes that CSS must be comment-free already
		 * (for performance)
		 * @return {Range}
		 */
		extractSelector: function(content, pos, stripComments) {
			if (stripComments) {
				content = this.stripComments(content);
			}

			// find CSS selector
			var ch;
			var endPos = pos;
			while (--pos >= 0) {
				ch = content.charAt(pos);
				if (ch == '{' || ch == '}' || ch == ';') {
					pos++;
					break;
				}
			}

			if (pos < 0) {
				pos = 0;
			}

			var selector = content.substring(pos, endPos);

			// trim whitespace from matched selector
			var m = selector.replace(reSpace, ' ').match(reSpaceTrim);
			if (m) {
				pos += m[1].length;
				endPos -= m[2].length;
			}

			return range.create2(pos, endPos);
		},

		/**
		 * Search for nearest CSS rule/section that contains
		 * given position
		 * @param  {String} content CSS content or matched CSS rules (array of ranges)
		 * @param  {Number} pos     Search position
		 * @return {Range}
		 */
		matchEnclosingRule: function(content, pos) {
			if (_.isString(content)) {
				content = this.findAllRules(content);
			}

			var rules = _.filter(content, function(r) {
				return r.inside(pos);
			});

			return _.last(rules);
		},

		/**
		 * Locates CSS rule next or before given position
		 * @param  {String}  content    CSS content
		 * @param  {Number}  pos        Search start position
		 * @param  {Boolean} isBackward Search backward (find previous rule insteaf of next one)
		 * @return {Range}
		 */
		locateRule: function(content, pos, isBackward) {
			var rules = this.findAllRules(content);
			var ctxRule = this.matchEnclosingRule(rules, pos);
			if (ctxRule) {
				// we have a context rule but it may contain nested rules
				rules = _.filter(rules, function(r) {
					return ctxRule.contains(r);
				});

				// no nested rules
				if (!rules.length) {
					return ctxRule;
				}
			}

			for (var i = 0, il = rules.length; i < il; i++) {
				if (rules[i].start > pos) {
					return rules[isBackward ? i - 1 : i];
				}
			}
		},

		/**
		 * Parses and returns all sections in given CSS
		 * as tree-like structure, e.g. provides nesting
		 * info
		 * @param  {String} content CSS content
		 * @return {CSSSection}
		 */
		sectionTree: function(content) {
			var root = new CSSSection(null, content);
			var rules = this.findAllRules(content);

			// rules are sorted in order they appear in CSS source
			// so we can optimize their nesting routine
			var insert = function(range, ctx) {
				while (ctx && ctx.range) {
					if (ctx.range.contains(range)) {
						return ctx.addChild(range);
					}

					ctx = ctx.parent;
				}

				// if we are here then given range is a top-level section
				return root.addChild(range);
			};

			var ctx = root;
			_.each(rules, function(r) {
				ctx = insert(r, ctx);
			});

			return root;
		},

		CSSSection: CSSSection
	};
});<|endoftext|>


<commit_before>
/*!
 * CSS - Evaluator
 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Visitor = require('./')
  , nodes = require('../nodes')
  , Stack = require('../stack')
  , Frame = require('../stack/frame')
  , Scope = require('../stack/scope')
  , utils = require('../utils')
  , bifs = require('../functions')
  , dirname = require('path').dirname
  , fs = require('fs');

/**
 * Initialize a new `Evaluator` with the given `root` Node
 * and the following `options`.
 *
 * Options:
 *
 *   - `compress`  Compress the css output, defaults to false
 *
 * @param {Node} root
 * @api public
 */

var Evaluator = module.exports = function Evaluator(root, options) {
  options = options || {};
  Visitor.call(this, root);
  this.stack = new Stack;
  this.filename = options.filename;
  this.functions = options.functions || {};
  this.paths = options.paths || [];
  this.paths.push(dirname(options.filename || '.'));
  this.stack.push(this.global = new Frame(root));
};

/**
 * Inherit from `Visitor.prototype`.
 */

Evaluator.prototype.__proto__ = Visitor.prototype;

/**
 * Proxy visit to expose node line numbers.
 *
 * @param {Node} node
 * @return {Node}
 * @api public
 */

var visit = Visitor.prototype.visit;
Evaluator.prototype.visit = function(node){
  try {
    this.lineno = node.lineno;
    return visit.call(this, node);
  } catch (err) {
    // TODO: less-lame hack to reference
    // the origin node source input
    err.str = err.str || node.source;
    err.stylusStack = err.stylusStack || this.stack.toString();
    throw err;
  }
};

/**
 * Evaluate the tree.
 *
 * @return {Node}
 * @api public
 */

Evaluator.prototype.evaluate = function(){
  return this.visit(this.root);
};

/**
 * Visit Color.
 */

Evaluator.prototype.visitColor = function(color){
  return color;
};

/**
 * Visit HSLA.
 */

Evaluator.prototype.visitHSLA = function(hsla){
  return hsla;
};

/**
 * Visit Literal.
 */

Evaluator.prototype.visitLiteral = function(lit){
  return lit;
};

/**
 * Visit Boolean.
 */

Evaluator.prototype.visitBoolean = function(bool){
  return bool;
};

/**
 * Visit Unit.
 */

Evaluator.prototype.visitUnit = function(unit){
  return unit;
};

/**
 * Visit String.
 */

Evaluator.prototype.visitString = function(string){
  return string;
};

/**
 * Visit Null.
 */

Evaluator.prototype.visitNull = function(node){
  return node;
};

/**
 * Visit Function.
 */

Evaluator.prototype.visitFunction = function(fn){
  return fn;
};

/**
 * Visit Group.
 */

Evaluator.prototype.visitGroup = function(group){
  group.block = this.visit(group.block);
  return group;
};

/**
 * Visit Charset.
 */

Evaluator.prototype.visitCharset = function(charset){
  return charset;
};

/**
 * Visit Call.
 */

Evaluator.prototype.visitCall = function(call){
  var fn = this.lookup(call.name);

  // Undefined function, render literal css
  if (!fn) return this.literalCall(call);
  this.calling = call.name;

  // First node in expression
  if (fn instanceof nodes.Expression) fn = fn.first;

  // Evaluate arguments
  var _ = this.return;
  this.return = true;
  var args = this.visit(call.args);
  this.return = _;

  // Built-in
  if (fn.fn) {
    return this.invokeBuiltin(fn.fn, args);
  // User-defined
  } else if (fn instanceof nodes.Function) {
    return this.invokeFunction(fn, args);
  }
};

/**
 * Visit Ident.
 */

Evaluator.prototype.visitIdent = function(ident){
  // Lookup
  if (nodes.null == ident.val) {
    var val = this.lookup(ident.name);
    return val ? this.visit(val) : ident;
  // Assign
  } else {
    var _ = this.return;
    this.return = true;
    ident.val = this.visit(ident.val);
    this.return = _;
    this.stack.currentFrame.scope.add(ident);
    return ident.val;
  }
};

/**
 * Visit BinOp.
 */

Evaluator.prototype.visitBinOp = function(binop){
  // Special-case "is defined" pseudo binop
  if ('is defined' == binop.op) return this.isDefined(binop.left);

  var _ = this.return;
  this.return = true;
  // Visit operands
  var op = binop.op
    , left = this.visit(binop.left).first
    , right = this.visit(binop.right).first;
  this.return = _;

  // Coercion
  var ignore = ['||', '&&', 'is a'];
  if (!~ignore.indexOf(op)) {
    right = left.coerce(right);
  }

  // Operate
  return this.visit(left.operate(op, right));
};

/**
 * Visit UnaryOp.
 */

Evaluator.prototype.visitUnaryOp = function(unary){
  var op = unary.op
    , node = this.visit(unary.expr).first;

  if ('!' != op) utils.assertType(node, nodes.Unit);

  switch (op) {
    case '-':
      node.val = -node.val;
      break;
    case '+':
      node.val = +node.val;
      break;
    case '~':
      node.val = ~node.val;
      break;
    case '!':
      return node.toBoolean().negate();
  }

  return node;
};

/**
 * Visit TernaryOp.
 */

Evaluator.prototype.visitTernary = function(ternary){
  var ok = this.visit(ternary.cond).toBoolean();
  return nodes.true == ok
    ? this.visit(ternary.trueExpr)
    : this.visit(ternary.falseExpr);
};

/**
 * Visit Expression.
 */

Evaluator.prototype.visitExpression = function(expr){
  for (var i = 0, len = expr.nodes.length; i < len; ++i) {
    expr.nodes[i] = this.visit(expr.nodes[i]);
  }
  return expr;
};

/**
 * Visit Property.
 */

Evaluator.prototype.visitProperty = function(prop){
  var fn = this.lookup(prop.name)
    , call = fn instanceof nodes.Function
    , literal = prop.name == this.calling;

  // Function of the same name
  if (call && !literal) {
    this.calling = prop.name;
    var ret = this.visit(new nodes.Call(prop.name, prop.expr));
    this.calling = null;
    return ret;
  // Regular property
  } else {
    var _ = this.return;
    this.return = true;
    prop.expr = this.visit(prop.expr);
    this.return = _;
    return prop;
  }
};

/**
 * Visit Root.
 */

Evaluator.prototype.visitRoot = function(block){
  for (var i = 0; i < block.nodes.length; ++i) {
    this.rootIndex = i;
    block.nodes[i] = this.visit(block.nodes[i]);
  }
  return block;
};

/**
 * Visit Block.
 */

Evaluator.prototype.visitBlock = function(block){
  if (block.scope) this.stack.push(new Frame(block));
  for (var i = 0, len = block.nodes.length; i < len; ++i) {
    block.nodes[i] = this.visit(block.nodes[i]);
  }
  if (block.scope) this.stack.pop();
  return block;
};

/**
 * Visit If.
 */

Evaluator.prototype.visitIf = function(node){
  var negate = node.negate
    , ok = this.visit(node.cond).first.toBoolean();

  if (negate) {
    if (nodes.false == ok) {
      return this.visit(node.ifBlock);
    }
  } else {
    if (nodes.true == ok) {
      return this.visit(node.ifBlock);
    } else if (node.elseBlock) {
      return this.visit(node.elseBlock);
    }
  }

  return nodes.null;
};

/**
 * Visit Import.
 */

Evaluator.prototype.visitImport = function(import){
  var found
    , root = this.root
    , i = this.rootIndex
    , stylus = require('../stylus')
    , path = import.path
    , relative = this.importPath;

  // Literal
  if (/\.css$/.test(path)) return import;
  path += '.styl';

  // Lookup
  if (relative) this.paths.push(relative);
  found = utils.lookup(path, this.paths);
  if (relative) this.paths.pop();

  // Throw if import failed
  if (!found) throw new Error('failed to locate @import file ' + path);
  this.importPath = dirname(found);

  // Parse the file
  var str = fs.readFileSync(found, 'utf8')
    , rest = root.nodes.splice(++i, root.nodes.length);

  stylus.parse(str, {
      filename: found
    , root: root
  });

  rest.forEach(function(node){
    root.push(node);
  });

  return nodes.null;
};

/**
 * Invoke `fn` with `args`.
 *
 * @param {Function} fn
 * @param {Array} args
 * @return {Node}
 * @api public
 */

Evaluator.prototype.invokeFunction = function(fn, args){
  // Clone the function body
  // to prevent mutation of subsequent calls
  var body = fn.block.clone();

  // Inject argument scope
  var block = new nodes.Block(body.parent);
  body.parent = block;

  // Inject arguments as locals
  this.stack.push(new Frame(block));
  fn.params.nodes.forEach(function(node, i){
    // Argument default support
    var val = args.nodes[i] || node.val;
    node = node.clone();
    node.val = val;

    // Required argument not satisfied
    if (node.val instanceof nodes.Null) {
      throw new Error('argument ' + node + ' required for ' + fn);
    }
    this.stack.currentFrame.scope.add(node);
  }, this);

  // Evaluate
  body = this.visit(body);
  this.stack.pop();

  // Invoke
  return this.invoke(body);
};

/**
 * Invoke built-in `fn` with `args`.
 *
 * @param {Function} fn
 * @param {Array} args
 * @return {Node}
 * @api public
 */

Evaluator.prototype.invokeBuiltin = function(fn, args){
  // Map arguments to first node
  // providing a nicer js api for
  // BIFs
  args = args.nodes.map(function(node){
    return node.first;
  });

  // Invoke the BIF
  body = fn.apply(this, args);

  // Wrap non-expressions as an Expression,
  // again just providing a nicer api
  // to work with.
  if (!(body instanceof nodes.Expression)) {
    var expr = new nodes.Expression;
    expr.push(body);
    body = expr;
  }

  // Invoke
  return this.invoke(body);
};

/**
 * Invoke the given function `body`.
 *
 * @param {Block} body
 * @return {Node}
 * @api public
 */

Evaluator.prototype.invoke = function(body){
  var self = this;

  // Return
  if (this.return) {
    return body.nodes[body.nodes.length - 1];
  // Mixin
  } else {
    (function mixin(nodes) {
      nodes.forEach(function(node){
        if ('block' == node.nodeName) {
          mixin(node.nodes);
        } else {
          self.stack.currentFrame.block.nodes.push(node);
        }
      });
    })(body.nodes);
    return nodes.null;
  }
};

/**
 * Literal function `call`.
 *
 * @param {Call} call
 * @return {call}
 * @api public
 */

Evaluator.prototype.literalCall = function(call){
  call.args = this.visit(call.args);
  return call;
};

/**
 * Lookup `name`, with support for JavaScript
 * functions, and BIFs.
 *
 * @param {String} name
 * @return {Node}
 * @api public
 */

Evaluator.prototype.lookup = function(name){
  var val = this.stack.lookup(name);
  if (val) return val;
  var fn = this.functions[name] || bifs[name];
  if (fn) return new nodes.Function(name, fn);
};

/**
 * Check if the given `node` is an ident, and if it is defined.
 *
 * @param {Node} node
 * @return {Boolean}
 * @api private
 */

Evaluator.prototype.isDefined = function(node){
  if (node instanceof nodes.Ident) {
    return nodes.Boolean(this.lookup(node.name));
  } else {
    throw new Error('invalid "is defined" check on non-variable ' + node);
  }
};
<commit_msg>Fixed recursion issue. Closes #63<commit_after>
/*!
 * CSS - Evaluator
 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Visitor = require('./')
  , nodes = require('../nodes')
  , Stack = require('../stack')
  , Frame = require('../stack/frame')
  , Scope = require('../stack/scope')
  , utils = require('../utils')
  , bifs = require('../functions')
  , dirname = require('path').dirname
  , fs = require('fs');

/**
 * Initialize a new `Evaluator` with the given `root` Node
 * and the following `options`.
 *
 * Options:
 *
 *   - `compress`  Compress the css output, defaults to false
 *
 * @param {Node} root
 * @api public
 */

var Evaluator = module.exports = function Evaluator(root, options) {
  options = options || {};
  Visitor.call(this, root);
  this.stack = new Stack;
  this.filename = options.filename;
  this.functions = options.functions || {};
  this.paths = options.paths || [];
  this.paths.push(dirname(options.filename || '.'));
  this.stack.push(this.global = new Frame(root));
  this.calling = []; // TODO: remove, use stack
};

/**
 * Inherit from `Visitor.prototype`.
 */

Evaluator.prototype.__proto__ = Visitor.prototype;

/**
 * Proxy visit to expose node line numbers.
 *
 * @param {Node} node
 * @return {Node}
 * @api public
 */

var visit = Visitor.prototype.visit;
Evaluator.prototype.visit = function(node){
  try {
    this.lineno = node.lineno;
    return visit.call(this, node);
  } catch (err) {
    // TODO: less-lame hack to reference
    // the origin node source input
    err.str = err.str || node.source;
    err.stylusStack = err.stylusStack || this.stack.toString();
    throw err;
  }
};

/**
 * Evaluate the tree.
 *
 * @return {Node}
 * @api public
 */

Evaluator.prototype.evaluate = function(){
  return this.visit(this.root);
};

/**
 * Visit Color.
 */

Evaluator.prototype.visitColor = function(color){
  return color;
};

/**
 * Visit HSLA.
 */

Evaluator.prototype.visitHSLA = function(hsla){
  return hsla;
};

/**
 * Visit Literal.
 */

Evaluator.prototype.visitLiteral = function(lit){
  return lit;
};

/**
 * Visit Boolean.
 */

Evaluator.prototype.visitBoolean = function(bool){
  return bool;
};

/**
 * Visit Unit.
 */

Evaluator.prototype.visitUnit = function(unit){
  return unit;
};

/**
 * Visit String.
 */

Evaluator.prototype.visitString = function(string){
  return string;
};

/**
 * Visit Null.
 */

Evaluator.prototype.visitNull = function(node){
  return node;
};

/**
 * Visit Function.
 */

Evaluator.prototype.visitFunction = function(fn){
  return fn;
};

/**
 * Visit Group.
 */

Evaluator.prototype.visitGroup = function(group){
  group.block = this.visit(group.block);
  return group;
};

/**
 * Visit Charset.
 */

Evaluator.prototype.visitCharset = function(charset){
  return charset;
};

/**
 * Visit Call.
 */

Evaluator.prototype.visitCall = function(call){
  var fn = this.lookup(call.name);

  // Undefined function, render literal css
  if (!fn) return this.literalCall(call);
  this.calling.push(name);

  // First node in expression
  if (fn instanceof nodes.Expression) fn = fn.first;

  // Evaluate arguments
  var _ = this.return;
  this.return = true;
  var args = this.visit(call.args);
  this.return = _;

  // Built-in
  if (fn.fn) {
    ret = this.invokeBuiltin(fn.fn, args);
  // User-defined
  } else if (fn instanceof nodes.Function) {
    ret = this.invokeFunction(fn, args);
  }

  this.calling.pop();
  return ret;
};

/**
 * Visit Ident.
 */

Evaluator.prototype.visitIdent = function(ident){
  // Lookup
  if (nodes.null == ident.val) {
    var val = this.lookup(ident.name);
    return val ? this.visit(val) : ident;
  // Assign
  } else {
    var _ = this.return;
    this.return = true;
    ident.val = this.visit(ident.val);
    this.return = _;
    this.stack.currentFrame.scope.add(ident);
    return ident.val;
  }
};

/**
 * Visit BinOp.
 */

Evaluator.prototype.visitBinOp = function(binop){
  // Special-case "is defined" pseudo binop
  if ('is defined' == binop.op) return this.isDefined(binop.left);

  var _ = this.return;
  this.return = true;
  // Visit operands
  var op = binop.op
    , left = this.visit(binop.left).first
    , right = this.visit(binop.right).first;
  this.return = _;

  // Coercion
  var ignore = ['||', '&&', 'is a'];
  if (!~ignore.indexOf(op)) {
    right = left.coerce(right);
  }

  // Operate
  return this.visit(left.operate(op, right));
};

/**
 * Visit UnaryOp.
 */

Evaluator.prototype.visitUnaryOp = function(unary){
  var op = unary.op
    , node = this.visit(unary.expr).first;

  if ('!' != op) utils.assertType(node, nodes.Unit);

  switch (op) {
    case '-':
      node.val = -node.val;
      break;
    case '+':
      node.val = +node.val;
      break;
    case '~':
      node.val = ~node.val;
      break;
    case '!':
      return node.toBoolean().negate();
  }

  return node;
};

/**
 * Visit TernaryOp.
 */

Evaluator.prototype.visitTernary = function(ternary){
  var ok = this.visit(ternary.cond).toBoolean();
  return nodes.true == ok
    ? this.visit(ternary.trueExpr)
    : this.visit(ternary.falseExpr);
};

/**
 * Visit Expression.
 */

Evaluator.prototype.visitExpression = function(expr){
  for (var i = 0, len = expr.nodes.length; i < len; ++i) {
    expr.nodes[i] = this.visit(expr.nodes[i]);
  }
  return expr;
};

/**
 * Visit Property.
 */

Evaluator.prototype.visitProperty = function(prop){
  var fn = this.lookup(prop.name)
    , call = fn instanceof nodes.Function
    , literal = ~this.calling.indexOf(name);

  // Function of the same name
  if (call && !literal) {
    this.calling.push(name);
    var ret = this.visit(new nodes.Call(name, prop.expr));
    this.calling.pop();
    return ret;
  // Regular property
  } else {
    var _ = this.return;
    this.return = true;
    prop.expr = this.visit(prop.expr);
    this.return = _;
    return prop;
  }
};

/**
 * Visit Root.
 */

Evaluator.prototype.visitRoot = function(block){
  for (var i = 0; i < block.nodes.length; ++i) {
    this.rootIndex = i;
    block.nodes[i] = this.visit(block.nodes[i]);
  }
  return block;
};

/**
 * Visit Block.
 */

Evaluator.prototype.visitBlock = function(block){
  if (block.scope) this.stack.push(new Frame(block));
  for (var i = 0, len = block.nodes.length; i < len; ++i) {
    block.nodes[i] = this.visit(block.nodes[i]);
  }
  if (block.scope) this.stack.pop();
  return block;
};

/**
 * Visit If.
 */

Evaluator.prototype.visitIf = function(node){
  var negate = node.negate
    , ok = this.visit(node.cond).first.toBoolean();

  if (negate) {
    if (nodes.false == ok) {
      return this.visit(node.ifBlock);
    }
  } else {
    if (nodes.true == ok) {
      return this.visit(node.ifBlock);
    } else if (node.elseBlock) {
      return this.visit(node.elseBlock);
    }
  }

  return nodes.null;
};

/**
 * Visit Import.
 */

Evaluator.prototype.visitImport = function(import){
  var found
    , root = this.root
    , i = this.rootIndex
    , stylus = require('../stylus')
    , path = import.path
    , relative = this.importPath;

  // Literal
  if (/\.css$/.test(path)) return import;
  path += '.styl';

  // Lookup
  if (relative) this.paths.push(relative);
  found = utils.lookup(path, this.paths);
  if (relative) this.paths.pop();

  // Throw if import failed
  if (!found) throw new Error('failed to locate @import file ' + path);
  this.importPath = dirname(found);

  // Parse the file
  var str = fs.readFileSync(found, 'utf8')
    , rest = root.nodes.splice(++i, root.nodes.length);

  stylus.parse(str, {
      filename: found
    , root: root
  });

  rest.forEach(function(node){
    root.push(node);
  });

  return nodes.null;
};

/**
 * Invoke `fn` with `args`.
 *
 * @param {Function} fn
 * @param {Array} args
 * @return {Node}
 * @api public
 */

Evaluator.prototype.invokeFunction = function(fn, args){
  // Clone the function body
  // to prevent mutation of subsequent calls
  var body = fn.block.clone();

  // Inject argument scope
  var block = new nodes.Block(body.parent);
  body.parent = block;

  // Inject arguments as locals
  this.stack.push(new Frame(block));
  fn.params.nodes.forEach(function(node, i){
    // Argument default support
    var val = args.nodes[i] || node.val;
    node = node.clone();
    node.val = val;

    // Required argument not satisfied
    if (node.val instanceof nodes.Null) {
      throw new Error('argument ' + node + ' required for ' + fn);
    }
    this.stack.currentFrame.scope.add(node);
  }, this);

  // Evaluate
  body = this.visit(body);
  this.stack.pop();

  // Invoke
  return this.invoke(body);
};

/**
 * Invoke built-in `fn` with `args`.
 *
 * @param {Function} fn
 * @param {Array} args
 * @return {Node}
 * @api public
 */

Evaluator.prototype.invokeBuiltin = function(fn, args){
  // Map arguments to first node
  // providing a nicer js api for
  // BIFs
  args = args.nodes.map(function(node){
    return node.first;
  });

  // Invoke the BIF
  body = fn.apply(this, args);

  // Wrap non-expressions as an Expression,
  // again just providing a nicer api
  // to work with.
  if (!(body instanceof nodes.Expression)) {
    var expr = new nodes.Expression;
    expr.push(body);
    body = expr;
  }

  // Invoke
  return this.invoke(body);
};

/**
 * Invoke the given function `body`.
 *
 * @param {Block} body
 * @return {Node}
 * @api public
 */

Evaluator.prototype.invoke = function(body){
  var self = this;

  // Return
  if (this.return) {
    return body.nodes[body.nodes.length - 1];
  // Mixin
  } else {
    (function mixin(nodes) {
      nodes.forEach(function(node){
        if ('block' == node.nodeName) {
          mixin(node.nodes);
        } else {
          self.stack.currentFrame.block.nodes.push(node);
        }
      });
    })(body.nodes);
    return nodes.null;
  }
};

/**
 * Literal function `call`.
 *
 * @param {Call} call
 * @return {call}
 * @api public
 */

Evaluator.prototype.literalCall = function(call){
  call.args = this.visit(call.args);
  return call;
};

/**
 * Lookup `name`, with support for JavaScript
 * functions, and BIFs.
 *
 * @param {String} name
 * @return {Node}
 * @api public
 */

Evaluator.prototype.lookup = function(name){
  var val = this.stack.lookup(name);
  if (val) return val;
  var fn = this.functions[name] || bifs[name];
  if (fn) return new nodes.Function(name, fn);
};

/**
 * Check if the given `node` is an ident, and if it is defined.
 *
 * @param {Node} node
 * @return {Boolean}
 * @api private
 */

Evaluator.prototype.isDefined = function(node){
  if (node instanceof nodes.Ident) {
    return nodes.Boolean(this.lookup(node.name));
  } else {
    throw new Error('invalid "is defined" check on non-variable ' + node);
  }
};
<|endoftext|>


<commit_before>
/*!
 * CSS - Evaluator
 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Visitor = require('./')
  , nodes = require('../nodes')
  , Stack = require('../stack')
  , Frame = require('../stack/frame')
  , Scope = require('../stack/scope');

/**
 * Initialize a new `Evaluator` with the given `root` Node
 * and the following `options`.
 *
 * Options:
 *
 *   - `compress`  Compress the css output, defaults to false
 *
 * @param {Node} root
 * @api private
 */

var Evaluator = module.exports = function Evaluator(root, options) {
  options = options || {};
  Visitor.call(this, root);
  this.stack = new Stack;
  this.stack.push(this.global = new Frame(new Scope));
};

/**
 * Inherit from `Visitor.prototype`.
 */

Evaluator.prototype.__proto__ = Visitor.prototype;

/**
 * Evaluate the tree.
 *
 * @return {Node}
 * @api public
 */

Evaluator.prototype.evaluate = function(){
  return this.visit(this.root);
};

/**
 * Visit Root.
 */

Evaluator.prototype.visitRoot = function(block){
  return this.visitBlock(block);
};

/**
 * Visit Block.
 */

Evaluator.prototype.visitBlock = function(block){
  for (var i = 0, len = block.nodes.length; i < len; ++i) {
    block.nodes[i] = this.visit(block.nodes[i]);
  }
  return block;
};

/**
 * Visit Variable.
 */

Evaluator.prototype.visitVariable = function(variable){
  if (this.isProperty) {
    var val = this.stack.lookup(variable.name);
    if (!val) throw new Error('undefined variable ' + variable);
    return this.visit(val);
  } else {
    this.stack.currentFrame.scope.add(variable);
    return new nodes.Null;
  }
};

/**
 * Visit Color.
 */

Evaluator.prototype.visitColor = function(color){
  return color;
};

/**
 * Visit Unit.
 */

Evaluator.prototype.visitUnit = function(unit){
  return unit;
};

/**
 * Visit Selector.
 */

Evaluator.prototype.visitSelector = function(selector){
  selector.block = this.visit(selector.block);
  return selector;
};

/**
 * Visit Keyword.
 */

Evaluator.prototype.visitKeyword = function(keyword){
  return keyword;
};

/**
 * Visit String.
 */

Evaluator.prototype.visitString = function(string){
  return string;
};

/**
 * Visit Null.
 */

Evaluator.prototype.visitNull = function(node){
  return node;
};

/**
 * Visit Expression.
 */

Evaluator.prototype.visitExpression = function(expr){
  for (var i = 0, len = expr.nodes.length; i < len; ++i) {
    expr.nodes[i] = this.visit(expr.nodes[i]);
  }
  return expr;
};

/**
 * Visit Property.
 */

Evaluator.prototype.visitProperty = function(prop){
  this.isProperty = true;
  prop.expr = this.visit(prop.expr);
  this.isProperty = false;
  return prop;
};
<commit_msg>No need for new Scope<commit_after>
/*!
 * CSS - Evaluator
 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Visitor = require('./')
  , nodes = require('../nodes')
  , Stack = require('../stack')
  , Frame = require('../stack/frame')
  , Scope = require('../stack/scope');

/**
 * Initialize a new `Evaluator` with the given `root` Node
 * and the following `options`.
 *
 * Options:
 *
 *   - `compress`  Compress the css output, defaults to false
 *
 * @param {Node} root
 * @api private
 */

var Evaluator = module.exports = function Evaluator(root, options) {
  options = options || {};
  Visitor.call(this, root);
  this.stack = new Stack;
  this.stack.push(this.global = new Frame);
};

/**
 * Inherit from `Visitor.prototype`.
 */

Evaluator.prototype.__proto__ = Visitor.prototype;

/**
 * Evaluate the tree.
 *
 * @return {Node}
 * @api public
 */

Evaluator.prototype.evaluate = function(){
  return this.visit(this.root);
};

/**
 * Visit Root.
 */

Evaluator.prototype.visitRoot = function(block){
  return this.visitBlock(block);
};

/**
 * Visit Block.
 */

Evaluator.prototype.visitBlock = function(block){
  for (var i = 0, len = block.nodes.length; i < len; ++i) {
    block.nodes[i] = this.visit(block.nodes[i]);
  }
  return block;
};

/**
 * Visit Variable.
 */

Evaluator.prototype.visitVariable = function(variable){
  if (this.isProperty) {
    var val = this.stack.lookup(variable.name);
    if (!val) throw new Error('undefined variable ' + variable);
    return this.visit(val);
  } else {
    this.stack.currentFrame.scope.add(variable);
    return new nodes.Null;
  }
};

/**
 * Visit Color.
 */

Evaluator.prototype.visitColor = function(color){
  return color;
};

/**
 * Visit Unit.
 */

Evaluator.prototype.visitUnit = function(unit){
  return unit;
};

/**
 * Visit Selector.
 */

Evaluator.prototype.visitSelector = function(selector){
  selector.block = this.visit(selector.block);
  return selector;
};

/**
 * Visit Keyword.
 */

Evaluator.prototype.visitKeyword = function(keyword){
  return keyword;
};

/**
 * Visit String.
 */

Evaluator.prototype.visitString = function(string){
  return string;
};

/**
 * Visit Null.
 */

Evaluator.prototype.visitNull = function(node){
  return node;
};

/**
 * Visit Expression.
 */

Evaluator.prototype.visitExpression = function(expr){
  for (var i = 0, len = expr.nodes.length; i < len; ++i) {
    expr.nodes[i] = this.visit(expr.nodes[i]);
  }
  return expr;
};

/**
 * Visit Property.
 */

Evaluator.prototype.visitProperty = function(prop){
  this.isProperty = true;
  prop.expr = this.visit(prop.expr);
  this.isProperty = false;
  return prop;
};
<|endoftext|>


<commit_before>
/*!
 * CSS - Evaluator
 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Visitor = require('./')
  , nodes = require('../nodes')
  , Stack = require('../stack')
  , Frame = require('../stack/frame')
  , Scope = require('../stack/scope');

/**
 * Initialize a new `Evaluator` with the given `root` Node
 * and the following `options`.
 *
 * Options:
 *
 *   - `compress`  Compress the css output, defaults to false
 *
 * @param {Node} root
 * @api private
 */

var Evaluator = module.exports = function Evaluator(root, options) {
  options = options || {};
  Visitor.call(this, root);
  this.stack = new Stack;
  this.stack.push(this.global = new Frame);
};

/**
 * Inherit from `Visitor.prototype`.
 */

Evaluator.prototype.__proto__ = Visitor.prototype;

/**
 * Evaluate the tree.
 *
 * @return {Node}
 * @api public
 */

Evaluator.prototype.evaluate = function(){
  return this.visit(this.root);
};

/**
 * Visit Root.
 */

Evaluator.prototype.visitRoot = function(block){
  for (var i = 0, len = block.nodes.length; i < len; ++i) {
    block.nodes[i] = this.visit(block.nodes[i]);
  }
  return block;
};

/**
 * Visit Block.
 */

Evaluator.prototype.visitBlock = function(block){
  this.stack.push(new Frame);
  for (var i = 0, len = block.nodes.length; i < len; ++i) {
    block.nodes[i] = this.visit(block.nodes[i]);
  }
  this.stack.pop();
  return block;
};

/**
 * Visit Variable.
 */

Evaluator.prototype.visitVariable = function(variable){
  if (this.isProperty) {
    var val = this.stack.lookup(variable.name);
    if (!val) throw new Error('undefined variable ' + variable);
    return this.visit(val);
  } else {
    this.stack.currentFrame.scope.add(variable);
    return new nodes.Null;
  }
};

/**
 * Visit Color.
 */

Evaluator.prototype.visitColor = function(color){
  return color;
};

/**
 * Visit Unit.
 */

Evaluator.prototype.visitUnit = function(unit){
  return unit;
};

/**
 * Visit Selector.
 */

Evaluator.prototype.visitSelector = function(selector){
  selector.block = this.visit(selector.block);
  return selector;
};

/**
 * Visit Keyword.
 */

Evaluator.prototype.visitKeyword = function(keyword){
  return keyword;
};

/**
 * Visit String.
 */

Evaluator.prototype.visitString = function(string){
  return string;
};

/**
 * Visit Null.
 */

Evaluator.prototype.visitNull = function(node){
  return node;
};

/**
 * Visit Expression.
 */

Evaluator.prototype.visitExpression = function(expr){
  for (var i = 0, len = expr.nodes.length; i < len; ++i) {
    expr.nodes[i] = this.visit(expr.nodes[i]);
  }
  return expr;
};

/**
 * Visit Property.
 */

Evaluator.prototype.visitProperty = function(prop){
  this.isProperty = true;
  prop.expr = this.visit(prop.expr);
  this.isProperty = false;
  return prop;
};
<commit_msg>Setting up operator method calls<commit_after>
/*!
 * CSS - Evaluator
 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Visitor = require('./')
  , nodes = require('../nodes')
  , Stack = require('../stack')
  , Frame = require('../stack/frame')
  , Scope = require('../stack/scope');

/**
 * Initialize a new `Evaluator` with the given `root` Node
 * and the following `options`.
 *
 * Options:
 *
 *   - `compress`  Compress the css output, defaults to false
 *
 * @param {Node} root
 * @api private
 */

var Evaluator = module.exports = function Evaluator(root, options) {
  options = options || {};
  Visitor.call(this, root);
  this.stack = new Stack;
  this.stack.push(this.global = new Frame);
};

/**
 * Inherit from `Visitor.prototype`.
 */

Evaluator.prototype.__proto__ = Visitor.prototype;

/**
 * Evaluate the tree.
 *
 * @return {Node}
 * @api public
 */

Evaluator.prototype.evaluate = function(){
  return this.visit(this.root);
};

/**
 * Visit Root.
 */

Evaluator.prototype.visitRoot = function(block){
  for (var i = 0, len = block.nodes.length; i < len; ++i) {
    block.nodes[i] = this.visit(block.nodes[i]);
  }
  return block;
};

/**
 * Visit Block.
 */

Evaluator.prototype.visitBlock = function(block){
  this.stack.push(new Frame);
  for (var i = 0, len = block.nodes.length; i < len; ++i) {
    block.nodes[i] = this.visit(block.nodes[i]);
  }
  this.stack.pop();
  return block;
};

/**
 * Visit Variable.
 */

Evaluator.prototype.visitVariable = function(variable){
  if (this.isProperty) {
    var val = this.stack.lookup(variable.name);
    if (!val) throw new Error('undefined variable ' + variable);
    return this.visit(val);
  } else {
    this.stack.currentFrame.scope.add(variable);
    return new nodes.Null;
  }
};

/**
 * Visit Color.
 */

Evaluator.prototype.visitColor = function(color){
  return color;
};

/**
 * Visit Unit.
 */

Evaluator.prototype.visitUnit = function(unit){
  return unit;
};

/**
 * Visit Selector.
 */

Evaluator.prototype.visitSelector = function(selector){
  selector.block = this.visit(selector.block);
  return selector;
};

/**
 * Visit Keyword.
 */

Evaluator.prototype.visitKeyword = function(keyword){
  return keyword;
};

/**
 * Visit String.
 */

Evaluator.prototype.visitString = function(string){
  return string;
};

/**
 * Visit Null.
 */

Evaluator.prototype.visitNull = function(node){
  return node;
};

/**
 * Visit BinOp.
 */

Evaluator.prototype.visitBinOp = function(binop){
  // TODO: validation
  // TODO: conversion
  var op = binop.op
    , left = this.visit(binop.left)
    , right = this.visit(binop.right);
  return this.visit(left[op](right));
};

/**
 * Visit Expression.
 */

Evaluator.prototype.visitExpression = function(expr){
  for (var i = 0, len = expr.nodes.length; i < len; ++i) {
    expr.nodes[i] = this.visit(expr.nodes[i]);
  }
  return expr;
};

/**
 * Visit Property.
 */

Evaluator.prototype.visitProperty = function(prop){
  this.isProperty = true;
  prop.expr = this.visit(prop.expr);
  this.isProperty = false;
  return prop;
};
<|endoftext|>


<commit_before>cg.Renderer = function () {

    var DEFAULT_THEME = {
        "action": {
            "title": {
                "font_family": "Varela Round",
                "font_size": 14,
                "color": "#ccc"
            },
            "text": {
                "font_family": "Varela Round",
                "font_size": 12,
                "color": "#aaa"
            },
            "box": {
                "color": "#161e2b",
                "stroke_color": "#ccc",
                "stroke_size": 2,
                "border_radius": 5
            }
        },
        "type_colors": {
            "boolean": "#1abc9c", // turquoise
            "number": "#2ecc71", // green
            "vec3": "#3498db", // blue
            "vec2": "#2980b9", // blue
            "color": "#9b59b6", // purple
            "list": "#f1c40f", // yellow
            "dictionary": "#e67e22" // orange
        }
    };

    var ACTION_WIDTH = 200;

    /**
     * CodeGraph renderer using snapsvg.io library.
     * @param theme {Object} JSON config for graphic appearance of the graph
     * @constructor
     */
    function Renderer(theme) {
        this._theme = theme || DEFAULT_THEME;
        this._updates = [];
    }

    /**
     *
     * @param paper {Snap.Element}
     * @param node
     */
    Renderer.prototype.render = function (paper, node) {
        return this["_render" + node.constructor.name](paper, node);
    };

    /**
     * Render the graph.
     * @param paper {Snap.Element}
     * @param graph {cg.Graph}
     * @private
     */
    Renderer.prototype._renderGraph = function (paper, graph) {
        var group = paper.g();
        // this.render(paper, graph.groups); TODO: Groups support
        this.render(group, graph.actions);
        this.render(group, graph.connections);
    };

    /**
     * Render the group.
     * @param paper {Snap.Element}
     * @param container {cg.Container}
     * @private
     */
    Renderer.prototype._renderContainer = function (paper, container) {
        container.forEach(function (node) {
            var group = paper.g();
            this.render(group, node);
        }.bind(this));
    };

    /**
     * Render the action.
     * @param paper {Snap.Element}
     * @param action {cg.Action}
     * @private
     */
    Renderer.prototype._renderAction = function (paper, action) {
        var group = paper.g();
        var box = group.rect(action.position.x, action.position.y, ACTION_WIDTH, 40 + action.height * 20, this._theme.action.box.border_radius);
        box.attr({
            "fill": this._theme.action.box.color,
            "stroke": this._theme.action.box.stroke_color,
            "strokeSize": this._theme.action.box.stroke_size,
            "cursor": "move"
        });
        var text = group.text(action.position.x + box.getBBox().width / 2, action.position.y + 20, action.name);
        text.attr({
            "fontFamily": this._theme.action.title.font_family,
            "fontSize": this._theme.action.title.font_size,
            "textAnchor": "middle",
            "fill": this._theme.action.title.color
        });
        for (var inputName in action.inputs) {
            if (action.inputs.hasOwnProperty(inputName)) {
                this.render(group, action.inputs[inputName]);
            }
        }
        for (var outputName in action.outputs) {
            if (action.outputs.hasOwnProperty(outputName)) {
                this.render(group, action.outputs[outputName]);
            }
        }
        var ox = 0,
            oy = 0,
            lx = 0,
            ly = 0;
        group.drag(function (dx, dy) {
            lx = ox + dx;
            ly = oy + dy;
            action.position.x += dx;
            action.position.y += dy;
            group.transform(Snap.format('t{x},{y}', {x: lx, y: ly}));
            for (var i = 0; i < this._updates.length; ++i) {
                this._updates[i]();
            }
        }.bind(this), function () {
            // TODO don't drag if point clicked.
        }.bind(this), function () {
            ox = lx;
            oy = ly;
        }.bind(this));
        return group;
    };

    /**
     * Render the connection.
     * @param paper {Snap.Element}
     * @param connection {cg.Connection}
     * @private
     */
    Renderer.prototype._renderConnection = function (paper, connection) {
        var path = paper.path(this._generateConnectionPath(connection));
        path.attr({
            "stroke": this._theme.type_colors[connection.firstPoint.type],
            "strokeWidth": 2,
            "fill": "none"
        });
        path.connection = connection;
        this._updates.push(function () {
            path.attr("path", this._generateConnectionPath(path.connection)); // FIXME
        }.bind(this));
        return path;
    };

    /**
     * Render the point.
     * @param paper {Snap.Element}
     * @param point {cg.Point}
     * @private
     */
    Renderer.prototype._renderPoint = function (paper, point) {
        var group = paper.g();
        var position = this._getPointPosition(point);
        var circle = paper.circle(position.x, position.y, 3);
        circle.attr({
            "fill": "transparent",
            "stroke": this._theme.type_colors[point.type]
        });
        var text = paper.text(position.x + (point.isInput ? 10 : -10), position.y + 3, point.name);
        text.attr({
            "textAnchor": point.isInput ? "start" : "end",
            "fill": this._theme.action.text.color,
            "fontFamily": this._theme.action.text.font_family,
            "fontSize": this._theme.action.text.font_size
        });
        group.add(circle);
        group.add(text);
        return group;
    };

    Renderer.prototype._getPointPosition = function (point, relative) {
        return new cg.Vec2(
            point.action.position.x + (point.isInput ? 10 : ACTION_WIDTH - 10),
            point.action.position.y + 40 + point.index * 20
        );
    };

    Renderer.prototype._generateConnectionPath = function (connection) {
        var step = 50,
            p1 = this._getPointPosition(connection.firstPoint, true),
            p2 = this._getPointPosition(connection.secondPoint, true);
        return Snap.format("M{x},{y}C{x1},{y1} {x2},{y2} {x3},{y3}", {
            x: p1.x, y: p1.y,
            x1: p1.x + step, y1: p1.y,
            x2: p2.x - step, y2: p2.y,
            x3: p2.x, y3: p2.y
        });
    };

    /**
     * Enable zoom or re-enable zoom.
     * @param paper {Snap.Element}
     * @private
     */
    Renderer.prototype._enableZoom = function (paper) {
        paper.zpd('destroy');
        paper.zpd();
    };

    return Renderer;

}();<commit_msg>Remove useless group.<commit_after>cg.Renderer = function () {

    var DEFAULT_THEME = {
        "action": {
            "title": {
                "font_family": "Varela Round",
                "font_size": 14,
                "color": "#ccc"
            },
            "text": {
                "font_family": "Varela Round",
                "font_size": 12,
                "color": "#aaa"
            },
            "box": {
                "color": "#161e2b",
                "stroke_color": "#ccc",
                "stroke_size": 2,
                "border_radius": 5
            }
        },
        "type_colors": {
            "boolean": "#1abc9c", // turquoise
            "number": "#2ecc71", // green
            "vec3": "#3498db", // blue
            "vec2": "#2980b9", // blue
            "color": "#9b59b6", // purple
            "list": "#f1c40f", // yellow
            "dictionary": "#e67e22" // orange
        }
    };

    var ACTION_WIDTH = 200;

    /**
     * CodeGraph renderer using snapsvg.io library.
     * @param theme {Object} JSON config for graphic appearance of the graph
     * @constructor
     */
    function Renderer(theme) {
        this._theme = theme || DEFAULT_THEME;
        this._updates = [];
    }

    /**
     *
     * @param paper {Snap.Element}
     * @param node
     */
    Renderer.prototype.render = function (paper, node) {
        return this["_render" + node.constructor.name](paper, node);
    };

    /**
     * Render the graph.
     * @param paper {Snap.Element}
     * @param graph {cg.Graph}
     * @private
     */
    Renderer.prototype._renderGraph = function (paper, graph) {
        var group = paper.g();
        // this.render(paper, graph.groups); TODO: Groups support
        this.render(group, graph.actions);
        this.render(group, graph.connections);
    };

    /**
     * Render the group.
     * @param paper {Snap.Element}
     * @param container {cg.Container}
     * @private
     */
    Renderer.prototype._renderContainer = function (paper, container) {
        container.forEach(function (node) {
            this.render(paper, node);
        }.bind(this));
    };

    /**
     * Render the action.
     * @param paper {Snap.Element}
     * @param action {cg.Action}
     * @private
     */
    Renderer.prototype._renderAction = function (paper, action) {
        var group = paper.g();
        var box = group.rect(action.position.x, action.position.y, ACTION_WIDTH, 40 + action.height * 20, this._theme.action.box.border_radius);
        box.attr({
            "fill": this._theme.action.box.color,
            "stroke": this._theme.action.box.stroke_color,
            "strokeSize": this._theme.action.box.stroke_size,
            "cursor": "move"
        });
        var text = group.text(action.position.x + box.getBBox().width / 2, action.position.y + 20, action.name);
        text.attr({
            "fontFamily": this._theme.action.title.font_family,
            "fontSize": this._theme.action.title.font_size,
            "textAnchor": "middle",
            "fill": this._theme.action.title.color
        });
        for (var inputName in action.inputs) {
            if (action.inputs.hasOwnProperty(inputName)) {
                this.render(group, action.inputs[inputName]);
            }
        }
        for (var outputName in action.outputs) {
            if (action.outputs.hasOwnProperty(outputName)) {
                this.render(group, action.outputs[outputName]);
            }
        }
        var ox = 0,
            oy = 0,
            lx = 0,
            ly = 0;
        group.drag(function (dx, dy) {
            lx = ox + dx;
            ly = oy + dy;
            action.position.x += dx;
            action.position.y += dy;
            group.transform(Snap.format('t{x},{y}', {x: lx, y: ly}));
            for (var i = 0; i < this._updates.length; ++i) {
                this._updates[i]();
            }
        }.bind(this), function () {
            // TODO don't drag if point clicked.
        }.bind(this), function () {
            ox = lx;
            oy = ly;
        }.bind(this));
        return group;
    };

    /**
     * Render the connection.
     * @param paper {Snap.Element}
     * @param connection {cg.Connection}
     * @private
     */
    Renderer.prototype._renderConnection = function (paper, connection) {
        var path = paper.path(this._generateConnectionPath(connection));
        path.attr({
            "stroke": this._theme.type_colors[connection.firstPoint.type],
            "strokeWidth": 2,
            "fill": "none"
        });
        path.connection = connection;
        this._updates.push(function () {
            path.attr("path", this._generateConnectionPath(path.connection)); // FIXME
        }.bind(this));
        return path;
    };

    /**
     * Render the point.
     * @param paper {Snap.Element}
     * @param point {cg.Point}
     * @private
     */
    Renderer.prototype._renderPoint = function (paper, point) {
        var group = paper.g();
        var position = this._getPointPosition(point);
        var circle = paper.circle(position.x, position.y, 3);
        circle.attr({
            "fill": "transparent",
            "stroke": this._theme.type_colors[point.type]
        });
        var text = paper.text(position.x + (point.isInput ? 10 : -10), position.y + 3, point.name);
        text.attr({
            "textAnchor": point.isInput ? "start" : "end",
            "fill": this._theme.action.text.color,
            "fontFamily": this._theme.action.text.font_family,
            "fontSize": this._theme.action.text.font_size
        });
        group.add(circle);
        group.add(text);
        return group;
    };

    Renderer.prototype._getPointPosition = function (point, relative) {
        return new cg.Vec2(
            point.action.position.x + (point.isInput ? 10 : ACTION_WIDTH - 10),
            point.action.position.y + 40 + point.index * 20
        );
    };

    Renderer.prototype._generateConnectionPath = function (connection) {
        var step = 50,
            p1 = this._getPointPosition(connection.firstPoint, true),
            p2 = this._getPointPosition(connection.secondPoint, true);
        return Snap.format("M{x},{y}C{x1},{y1} {x2},{y2} {x3},{y3}", {
            x: p1.x, y: p1.y,
            x1: p1.x + step, y1: p1.y,
            x2: p2.x - step, y2: p2.y,
            x3: p2.x, y3: p2.y
        });
    };

    /**
     * Enable zoom or re-enable zoom.
     * @param paper {Snap.Element}
     * @private
     */
    Renderer.prototype._enableZoom = function (paper) {
        paper.zpd('destroy');
        paper.zpd();
    };

    return Renderer;

}();<|endoftext|>


<commit_before>/*
 *  Copyright 2011 Rackspace
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

var _ = require('underscore');
var async = require('async');

var settings = require('../../settings');
var log = require('../../log');

var Poller = require('./base').Poller;

var polling_apis = {
  // independent devops middleware:
  pager_duty: require('./pager_duty'),
  version_one: require('./version_one'),
  github: require('./github'),
  new_relic: require('./new_relic'),
  dreadnot: require('./dreadnot'),
  highscores: require('./highscores'),
  burndown: require('./burndown'),
  service_health: require('./service_health')
};

var poller_to_relatedapi = {
  highscores: 'version_one',
  burndown: 'version_one',
  service_health: null
};

// defaults to its own name
_.each(polling_apis, function(val, name){
  if (poller_to_relatedapi[name] === undefined){
    poller_to_relatedapi[name] = name;
  }
});

var _install_api = function(poller){
  var api_config = poller.get_config();
  // need to prepopulate the api caches
};

var interval_id;

exports.install = function(devops, cb){
  var projects = _.keys(settings.devopsjson_uris);
  var polling_data;

  if (devops.polling_data === undefined) {
    devops.polling_data = {};
  }

  polling_data = devops.polling_data;

  if (interval_id !== undefined) {
    clearInterval(interval_id);
    interval_id = undefined;
  }

  // TODO: throw everything in devops. ditch polling_data and payload.config
  _.each(projects, function(project){
    var polling_api;
    var poller;
    var name;
    var project_poller;
    var poll_intervals = [];
    var pollers = [];
    var related_api_name;
    var api_config;

    if (!polling_data[project]){
      polling_data[project] = {};
    }
    project_poller = polling_data[project];

    for (name in polling_apis){
      api_config = {};
      polling_api = polling_apis[name];
      project_poller[name] = null;
      related_api_name = poller_to_relatedapi[name];
      if (related_api_name !== null) {
        if (!devops[project] || !devops[project].related_apis || !devops[project].related_apis[related_api_name]) {
          continue;
        }
        api_config = devops[project].related_apis[related_api_name];
      }
      api_config.project_name = project;
      poller = new Poller(name, polling_api, api_config);//{error: null, data: null, config: null};

      project_poller[name] = poller;
      poller.poll();

      poll_intervals.push(poller.poll_interval);
      pollers.push(function (cb){
        try {
          poller.poll();
        }
        catch (e) {
          log.error(e);
        }
        cb();
      });
    }

    interval_id = setInterval(function() {
      async.parallel(pollers, function(err, results){
        if (err){
          log.error(err);
        }
      });
    }, _.min(poll_intervals));
  });

  cb(null, devops);
};
<commit_msg>Put poller data for each project in devops[project_name].pollers<commit_after>/*
 *  Copyright 2011 Rackspace
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

var _ = require('underscore');
var async = require('async');

var settings = require('../../settings');
var log = require('../../log');

var Poller = require('./base').Poller;

var polling_apis = {
  // independent devops middleware:
  pager_duty: require('./pager_duty'),
  version_one: require('./version_one'),
  github: require('./github'),
  new_relic: require('./new_relic'),
  dreadnot: require('./dreadnot'),
  highscores: require('./highscores'),
  burndown: require('./burndown'),
  service_health: require('./service_health')
};

var poller_to_relatedapi = {
  highscores: 'version_one',
  burndown: 'version_one',
  service_health: null
};

// defaults to its own name
_.each(polling_apis, function(val, name){
  if (poller_to_relatedapi[name] === undefined){
    poller_to_relatedapi[name] = name;
  }
});

var _install_api = function(poller){
  var api_config = poller.get_config();
  // need to prepopulate the api caches
};

var interval_id;

exports.install = function(devops, cb){
  var projects = _.keys(settings.devopsjson_uris);

  if (interval_id !== undefined) {
    clearInterval(interval_id);
    interval_id = undefined;
  }

  _.each(projects, function(project){
    var polling_api;
    var poller;
    var name;
    var project_poller;
    var poll_intervals = [];
    var pollers = [];
    var related_api_name;
    var api_config;
    var project_pollers;

    if (!devops[project]){
      devops[project] = {};
    }
    if (!devops[project].pollers){
      devops[project].pollers = {};
    }
    project_pollers = devops[project].pollers;

    for (name in polling_apis){
      api_config = {};
      polling_api = polling_apis[name];
      project_pollers[name] = null;
      related_api_name = poller_to_relatedapi[name];
      if (related_api_name !== null) {
        if (!devops[project] || !devops[project].related_apis || !devops[project].related_apis[related_api_name]) {
          continue;
        }
        api_config = devops[project].related_apis[related_api_name];
      }
      api_config.project_name = project;
      poller = new Poller(name, polling_api, api_config);//{error: null, data: null, config: null};

      project_pollers[name] = poller;
      poller.poll();

      poll_intervals.push(poller.poll_interval);
      pollers.push(function (cb){
        try {
          poller.poll();
        }
        catch (e) {
          log.error(e);
        }
        cb();
      });
    }

    interval_id = setInterval(function() {
      async.parallel(pollers, function(err, results){
        if (err){
          log.error(err);
        }
      });
    }, _.min(poll_intervals));
  });

  cb(null, devops);
};
<|endoftext|>


<commit_before>var app = angular.module('rdb', ['ngTable'])

app.controller('MainCtrl', function($scope, $http) {
  var self = this;

  $scope.process = null;
  $scope.running = true;
  $scope.activeThread = null;
  $scope.activeFrame = null;
  $scope.locals = {};
  self.liveThread = null;

  $scope.events = new EventSource('/events');
  $scope.events.addEventListener('message', function(e) {
    var message = JSON.parse(e.data);
    if ((message.event == 'break')) {
      $scope.$apply(function() {
        updateThreads();
        updateRunning();
      });
    }
  }, false);

  $scope.showSettings = function() {
  };

  $scope.pause = function() {
    $http.put('/pause')
      .success(function(res) {
        $scope.running = res.running;
        updateThreads();
      });
  };

  $scope.resume = function() {
    $http.put('/resume')
      .success(function(res) {
        $scope.running = res.running;
      });
  };

  $scope.stepIn = function() {
    $http.put('/step-in')
      .success(function(res) {
        updateThreads();
        updateLocals();
      });
  };

  $scope.stepOver = function() {
    $http.put('/step-over')
      .success(function(res) {
        updateThreads();
        updateLocals();
      });
  };

  $scope.stepOut = function() {
    $http.put('/step-out')
      .success(function(res) {
        updateThreads();
        updateLocals();
      });
  };

  $scope.openFile = function(path, line) {
    $http.post('/open', { path: path, line: line })
      .success(function() {
      });
  }

  $scope.setActiveThread = function(thread) {
    $scope.activeThread = thread;
    $scope.setActiveFrame(thread.backtrace[0]);
  };

  $scope.setActiveFrame = function(frame) {
    $scope.activeFrame = frame;
  };

  function updateThreads() {
    $http.get('/threads')
      .success(function(res) {
        $scope.threads = res;

        for (var i = 0; i < $scope.threads.length; ++i) {
          var thread = $scope.threads[i];
          if (thread.main) {
            self.liveThread = thread;
            $scope.setActiveThread(self.liveThread);
            break;
          }
        }
      });
  }

  function updateLocals() {
    $http.get('/locals')
      .success(function(res) {
        $scope.locals = res;
      });
  }

  function updateRunning() {
    $http.get('/running')
      .success(function(res) {
        $scope.running = res.running;
      });
  }

  function connect() {
    $http.get('/process')
      .success(function(res) {
        $scope.process = res.process;
      });

    updateRunning();
    updateThreads();
    updateLocals();
  }

  connect();
});

app.controller('SourceEditCtrl', function($scope, $http) {
  var self = this;

  // Maps line number to breakpoint ID.
  self.breakpoints = {};
  self.activeLineMarker = null;
  self.activeGutterRow = null;
  self.editor = null;

  function setupEditor() {
    var editor = ace.edit('source-editor');
    editor.setTheme('ace/theme/clouds');
    editor.session.setMode("ace/mode/ruby");
    editor.setReadOnly(true);
    editor.setShowPrintMargin(false);

    editor.on('guttermousedown', function(e) {
      if (e.domEvent.button != 0 /* left click */) {
        return;
      }

      var target = e.domEvent.target;
      if (target.className.indexOf('ace_gutter-cell') < 0) {
        return;
      }

      var row = e.getDocumentPosition().row;
      var line = row + 1;
      var breakpoints = e.editor.session.getBreakpoints();
      var exists = breakpoints[row] != null;

      if (exists) {
        removeBreakpoint(line);
      } else {
        addBreakpoint(line);
      }

      e.stop();
    });

    self.editor = editor;
  }

  $scope.$watch('activeFrame', function(newFrame, oldFrame) {
    if (newFrame == null) {
      return;
    }

    if ((oldFrame == null) || (newFrame.path != oldFrame.path)) {
      // TODO: Avoid requesting source constantly if we already have it locally.
      // TODO: Cache source locally (in-mem, localStorage).
      $http.get('/source', { params: { path: newFrame.path } })
        .success(function(res) {
          $scope.currentSource = res;
          self.editor.setValue($scope.currentSource, newFrame.line);
          setActiveLine(newFrame.line);
        });
    } else {
      setActiveLine(newFrame.line);
    }
  });

  function setActiveLine(line) {
    if (!self.editor)
      return;

    setTimeout(function() {
      self.editor.scrollToLine(line, true, true, function() {});

      var lineText = self.editor.session.getLine(line - 1);
      var endCol = lineText.length;
      var startCol = 1;
      var match = /\S/.exec(lineText);
      if (match) {
        startCol = match.index;
      }

      if (self.activeLineMarker != null) {
        self.editor.session.removeMarker(self.activeLineMarker);
      }

      var row = line - 1;
      var aceRange = ace.require('ace/range').Range;
      var range = new aceRange(row, startCol, row, endCol);
      self.activeLineMarker = self.editor.session.addMarker(range, 'live-command', 'text');

      if (self.activeGutterRow != null) {
        self.editor.session.removeGutterDecoration(self.activeGutterRow, 'live-command');
      }

      self.activeGutterRow = row;
      self.editor.session.addGutterDecoration(self.activeGutterRow, 'live-command');
    }, 0);
  };

  function addBreakpoint(line) {
    var params = { file: $scope.activeFrame.path, line: line };
    $http.post('/breakpoints', params)
      .success(function(res) {
        var row = line - 1;
        self.editor.session.setBreakpoint(row);

        var breakpointId = res.id;
        self.breakpoints[line] = breakpointId;
      });
  }

  function removeBreakpoint(line) {
    var breakpointId = self.breakpoints[line];
    if (breakpointId == null) {
      return;
    }

    $http.delete('/breakpoints/' + breakpointId)
      .success(function(res) {
        var row = line - 1;
        self.editor.session.clearBreakpoint(row);
        delete self.breakpoints[line];
      });
  }

  function updateBreakpoints() {
    $http.get('/breakpoints')
      .success(function(res) {
        for (var line in self.breakpoints) {
          var row = line - 1;
          self.editor.session.clearBreakpoint(row);
        }

        self.breakpoints = {};

        for (var i = 0; i < res.length; ++i) {
          var breakpoint = res[i];
          var row = breakpoint.line - 1;
          self.editor.session.setBreakpoint(row);
          self.breakpoints[breakpoint.line] = breakpoint.id;
        }
      });
  }

  $scope.events.addEventListener('message', function(e) {
    var message = JSON.parse(e.data);
    if ((message.event == 'breakpoint-created') || (message.event == 'breakpoint-deleted')) {
      $scope.$apply(function() {
        updateBreakpoints();
      });
    }
  }, false);

  updateBreakpoints();
  setTimeout(setupEditor, 0);
});

app.controller('ExpressionEditCtrl', function($scope, $http) {
  var self = this;
  self.editor = null;

  function setupEditor() {
    // TODO: Should be a directive?
    var editor = ace.edit('expr-editor');
    editor.renderer.setShowGutter(false);
    editor.renderer.setShowPrintMargin(false);
    editor.setValue('> ');
    editor.navigateLineEnd();
    editor.navigateRight(1);
    editor.session.setUseWrapMode(true);

    editor.commands.addCommand({
      name: 'Clear',
      bindKey: { win: 'Ctrl-L',  mac: 'Command-L' },
      exec: function(editor) {
        editor.setValue('> ');
        editor.navigateLineEnd();
        editor.navigateRight(1);
        return true;
      }
    });

    editor.commands.addCommand({
      name: 'Evaluate',
      bindKey: { win: 'Enter',  mac: 'Enter' },
      exec: function(editor) {
        var lineNumber = editor.selection.getCursor().row;
        var line = editor.session.getLine(lineNumber);
        var match = /\s*>\s*(.*)/.exec(line);
        if (match) {
          var expr = match[1];
          var params = { expr: match[1], frame: getFrameIndex($scope.activeFrame) };
          $http.put('/eval', params)
            .success(function(res) {
              editor.insert('\n');
              editor.insert(JSON.stringify(res));
              editor.insert('\n> ');
            });
        }
        return true;
      }
    });

    self.editor = editor;
  }

  function getFrameIndex(frame) {
    var frames = $scope.activeThread.backtrace;
    for (var i = 0; i < frames.length; ++i) {
      if (frames[i] == frame) {
        return i;
      }
    }

    return null;
  }

  setTimeout(setupEditor, 0);
});

app.controller('EnvironmentCtrl', function($scope, ngTableParams) {
  $scope.tableParams = new ngTableParams({
      count: 0
    }, {
      counts: []
    });

  $scope.$watch('process', function(newProcess, _) {
    if (newProcess == null) {
      return;
    }

    $scope.tableParams = new ngTableParams({
      count: Object.keys(newProcess.env).length
    }, {
      counts: [],
      total: Object.keys(newProcess.env).length,
      getData: function($defer, params) {
        $defer.resolve(newProcess.env)
      }
    });

    // See https://github.com/esvit/ng-table/issues/297
    $scope.tableParams.settings().$scope = $scope;
    $scope.tableParams.reload();
  });
});
<commit_msg>Better handling of server-sent events on the client side.<commit_after>var app = angular.module('rdb', ['ngTable'])

function Events(url) {
  this.source = new EventSource(url);
  this.handlers = {};
  var self = this;

  this.source.addEventListener('message', function(e) {
    var message = JSON.parse(e.data);
    var handlers = self.handlers[message.event];
    if (handlers) {
      for (var i = 0; i < handlers.length; ++i) {
        handlers[i](message);
      }
    }
  }, false);

  this.on = function(eventName, handler) {
    if (typeof eventName == 'string') {
      this.handlers[eventName] = this.handlers[eventName] || [];
      this.handlers[eventName].push(handler);
    } else if (eventName instanceof Array) {
      var names = eventName;
      for (var i = 0; i < names.length; ++i) {
        this.on(names[i], handler);
      }
    }
  };
}

app.controller('MainCtrl', function($scope, $http) {
  var self = this;

  $scope.process = null;
  $scope.running = true;
  $scope.activeThread = null;
  $scope.activeFrame = null;
  $scope.locals = {};
  self.liveThread = null;

  $scope.events = new Events('/events');
  $scope.events.on('break', function() {
    $scope.$apply(function() {
      updateThreads();
      updateRunning();
    });
  });

  $scope.showSettings = function() {
  };

  $scope.pause = function() {
    $http.put('/pause')
      .success(function(res) {
        $scope.running = res.running;
        updateThreads();
      });
  };

  $scope.resume = function() {
    $http.put('/resume')
      .success(function(res) {
        $scope.running = res.running;
      });
  };

  $scope.stepIn = function() {
    $http.put('/step-in')
      .success(function(res) {
        updateThreads();
        updateLocals();
      });
  };

  $scope.stepOver = function() {
    $http.put('/step-over')
      .success(function(res) {
        updateThreads();
        updateLocals();
      });
  };

  $scope.stepOut = function() {
    $http.put('/step-out')
      .success(function(res) {
        updateThreads();
        updateLocals();
      });
  };

  $scope.openFile = function(path, line) {
    $http.post('/open', { path: path, line: line })
      .success(function() {
      });
  }

  $scope.setActiveThread = function(thread) {
    $scope.activeThread = thread;
    $scope.setActiveFrame(thread.backtrace[0]);
  };

  $scope.setActiveFrame = function(frame) {
    $scope.activeFrame = frame;
  };

  function updateThreads() {
    $http.get('/threads')
      .success(function(res) {
        $scope.threads = res;

        for (var i = 0; i < $scope.threads.length; ++i) {
          var thread = $scope.threads[i];
          if (thread.main) {
            self.liveThread = thread;
            $scope.setActiveThread(self.liveThread);
            break;
          }
        }
      });
  }

  function updateLocals() {
    $http.get('/locals')
      .success(function(res) {
        $scope.locals = res;
      });
  }

  function updateRunning() {
    $http.get('/running')
      .success(function(res) {
        $scope.running = res.running;
      });
  }

  function connect() {
    $http.get('/process')
      .success(function(res) {
        $scope.process = res.process;
      });

    updateRunning();
    updateThreads();
    updateLocals();
  }

  connect();
});

app.controller('SourceEditCtrl', function($scope, $http) {
  var self = this;

  // Maps line number to breakpoint ID.
  self.breakpoints = {};
  self.activeLineMarker = null;
  self.activeGutterRow = null;
  self.editor = null;

  function setupEditor() {
    var editor = ace.edit('source-editor');
    editor.setTheme('ace/theme/clouds');
    editor.session.setMode("ace/mode/ruby");
    editor.setReadOnly(true);
    editor.setShowPrintMargin(false);

    editor.on('guttermousedown', function(e) {
      if (e.domEvent.button != 0 /* left click */) {
        return;
      }

      var target = e.domEvent.target;
      if (target.className.indexOf('ace_gutter-cell') < 0) {
        return;
      }

      var row = e.getDocumentPosition().row;
      var line = row + 1;
      var breakpoints = e.editor.session.getBreakpoints();
      var exists = breakpoints[row] != null;

      if (exists) {
        removeBreakpoint(line);
      } else {
        addBreakpoint(line);
      }

      e.stop();
    });

    self.editor = editor;
  }

  $scope.$watch('activeFrame', function(newFrame, oldFrame) {
    if (newFrame == null) {
      return;
    }

    if ((oldFrame == null) || (newFrame.path != oldFrame.path)) {
      // TODO: Avoid requesting source constantly if we already have it locally.
      // TODO: Cache source locally (in-mem, localStorage).
      $http.get('/source', { params: { path: newFrame.path } })
        .success(function(res) {
          $scope.currentSource = res;
          self.editor.setValue($scope.currentSource, newFrame.line);
          setActiveLine(newFrame.line);
        });
    } else {
      setActiveLine(newFrame.line);
    }
  });

  function setActiveLine(line) {
    if (!self.editor)
      return;

    setTimeout(function() {
      self.editor.scrollToLine(line, true, true, function() {});

      var lineText = self.editor.session.getLine(line - 1);
      var endCol = lineText.length;
      var startCol = 1;
      var match = /\S/.exec(lineText);
      if (match) {
        startCol = match.index;
      }

      if (self.activeLineMarker != null) {
        self.editor.session.removeMarker(self.activeLineMarker);
      }

      var row = line - 1;
      var aceRange = ace.require('ace/range').Range;
      var range = new aceRange(row, startCol, row, endCol);
      self.activeLineMarker = self.editor.session.addMarker(range, 'live-command', 'text');

      if (self.activeGutterRow != null) {
        self.editor.session.removeGutterDecoration(self.activeGutterRow, 'live-command');
      }

      self.activeGutterRow = row;
      self.editor.session.addGutterDecoration(self.activeGutterRow, 'live-command');
    }, 0);
  };

  function addBreakpoint(line) {
    var params = { file: $scope.activeFrame.path, line: line };
    $http.post('/breakpoints', params)
      .success(function(res) {
        var row = line - 1;
        self.editor.session.setBreakpoint(row);

        var breakpointId = res.id;
        self.breakpoints[line] = breakpointId;
      });
  }

  function removeBreakpoint(line) {
    var breakpointId = self.breakpoints[line];
    if (breakpointId == null) {
      return;
    }

    $http.delete('/breakpoints/' + breakpointId)
      .success(function(res) {
        var row = line - 1;
        self.editor.session.clearBreakpoint(row);
        delete self.breakpoints[line];
      });
  }

  function updateBreakpoints() {
    $http.get('/breakpoints')
      .success(function(res) {
        for (var line in self.breakpoints) {
          var row = line - 1;
          self.editor.session.clearBreakpoint(row);
        }

        self.breakpoints = {};

        for (var i = 0; i < res.length; ++i) {
          var breakpoint = res[i];
          var row = breakpoint.line - 1;
          self.editor.session.setBreakpoint(row);
          self.breakpoints[breakpoint.line] = breakpoint.id;
        }
      });
  }

  $scope.events.on(['breakpoint-created', 'breakpoint-deleted'], function(e) {
    $scope.$apply(function() {
      updateBreakpoints();
    });
  });

  updateBreakpoints();
  setTimeout(setupEditor, 0);
});

app.controller('ExpressionEditCtrl', function($scope, $http) {
  var self = this;
  self.editor = null;

  function setupEditor() {
    // TODO: Should be a directive?
    var editor = ace.edit('expr-editor');
    editor.renderer.setShowGutter(false);
    editor.renderer.setShowPrintMargin(false);
    editor.setValue('> ');
    editor.navigateLineEnd();
    editor.navigateRight(1);
    editor.session.setUseWrapMode(true);

    editor.commands.addCommand({
      name: 'Clear',
      bindKey: { win: 'Ctrl-L',  mac: 'Command-L' },
      exec: function(editor) {
        editor.setValue('> ');
        editor.navigateLineEnd();
        editor.navigateRight(1);
        return true;
      }
    });

    editor.commands.addCommand({
      name: 'Evaluate',
      bindKey: { win: 'Enter',  mac: 'Enter' },
      exec: function(editor) {
        var lineNumber = editor.selection.getCursor().row;
        var line = editor.session.getLine(lineNumber);
        var match = /\s*>\s*(.*)/.exec(line);
        if (match) {
          var expr = match[1];
          var params = { expr: match[1], frame: getFrameIndex($scope.activeFrame) };
          $http.put('/eval', params)
            .success(function(res) {
              editor.insert('\n');
              editor.insert(JSON.stringify(res));
              editor.insert('\n> ');
            });
        }
        return true;
      }
    });

    self.editor = editor;
  }

  function getFrameIndex(frame) {
    var frames = $scope.activeThread.backtrace;
    for (var i = 0; i < frames.length; ++i) {
      if (frames[i] == frame) {
        return i;
      }
    }

    return null;
  }

  setTimeout(setupEditor, 0);
});

app.controller('EnvironmentCtrl', function($scope, ngTableParams) {
  $scope.tableParams = new ngTableParams({
      count: 0
    }, {
      counts: []
    });

  $scope.$watch('process', function(newProcess, _) {
    if (newProcess == null) {
      return;
    }

    $scope.tableParams = new ngTableParams({
      count: Object.keys(newProcess.env).length
    }, {
      counts: [],
      total: Object.keys(newProcess.env).length,
      getData: function($defer, params) {
        $defer.resolve(newProcess.env)
      }
    });

    // See https://github.com/esvit/ng-table/issues/297
    $scope.tableParams.settings().$scope = $scope;
    $scope.tableParams.reload();
  });
});
<|endoftext|>