Commits

Mathias Panzenböck committed cedba92

handle operator precedence

  • Participants
  • Parent commits d9721d2

Comments (0)

Files changed (3)

+syntax: glob
+*~
+.*

File lib/minvisitor.js

 	sequenceExpression: function (expressions) {
 		return expressions.join(",");
 	},
-	unaryExpression: function (op, arg) {
+	visitUnaryExpression: function (node) {
+		var op = node.operator;
+		var arg = this.visit(node.argument);
+		if (node.hasPrecedence(node)) {
+			arg = '('+arg+')';
+		}
 		switch (op) {
 			case '+':
 			case '-':
 				return op+arg;
 		}
 	},
-	binaryExpression: function (op, left, right) {
+	visitBinaryExpression: function (node) {
+		var op = node.operator;
+		var left = this.visit(node.left);
+		var right = this.visit(node.right);
 		var code;
+		if (node.hasLeftPrecedence(node.left)) {
+			left = '('+left+')';
+		}
+		if (node.hasRightPrecedence(node.right)) {
+			right = '('+right+')';
+		}
 		switch (op) {
 			case '+':
 			case '-':
 		}
 		return code;
 	},
-	assignmentExpression: function (op, left, right) {
+	visitLogicalExpression: function (node) {
+		var op = node.operator;
+		var left = this.visit(node.left);
+		var right = this.visit(node.right);
+		if (node.hasLeftPrecedence(node.left)) {
+			left = '('+left+')';
+		}
+		if (node.hasRightPrecedence(node.right)) {
+			right = '('+right+')';
+		}
+		return left+op+right;
+	},
+	visitAssignmentExpression: function (node) {
+		var op = node.operator;
+		var left = this.visit(node.left);
+		var right = this.visit(node.right);
+		if (node.hasLeftPrecedence(node.left)) {
+			left = '('+left+')';
+		}
+		if (node.hasRightPrecedence(node.right)) {
+			right = '('+right+')';
+		}
 		var c = op.charAt(0);
 		switch (c) {
 			case '+':
 				return left+op+right;
 		}
 	},
-	updateExpression: function (op, arg, prefix) {
+	visitUpdateExpression: function (node) {
+		var op = node.operator;
+		var arg = this.visit(node.argument);
+		if (node.hasPrecedence(node.argument)) {
+			arg = '('+arg+')';
+		}
 		var c = op.charAt(0);
-		if (prefix) {
+		if (node.prefix) {
 			switch (c) {
 				case '+':
 				case '-':
 			}
 		}
 	},
-	logicalExpression: function (op, left, right) {
-		return left+op+right;
-	},
-	conditionalExpression: function (test, cons, alt) {
+	visitConditionalExpression: function (node) {
+		var test = this.visit(node.test);
+		var cons = this.visit(node.consequent);
+		var alt  = this.visit(node.alternate);
+		if (node.hasLeftPrecedence(node.test)) {
+			test = '('+test+')';
+		}
+		if (node.hasRightPrecedence(node.alternate)) {
+			alt = '('+alt+')';
+		}
 		return test+"?"+cons+":"+alt;
 	},
-	newExpression: function (constr, args) {
-		return "new"+lwrap(constr)+(args ? "("+args.join(",")+")" : "()");
+	visitNewExpression: function (constr, args) {
+		var constr = this.visit(node.constr);
+		var args = node.arguments;
+		if (node.hasPrecedence(node.constr)) {
+			constr = '('+constr+')';
+		}
+		else {
+			constr = lwrap(constr);
+		}
+		return "new"+constr+(args ?
+			"("+args.map(this.visit.bind(this)).join(",")+")" :
+			"()");
 	},
-	callExpression: function (callee, args) {
-		return callee+"("+args.join(",")+")";
+	visitCallExpression: function (node) {
+		var callee = this.visit(node.callee);
+		if (node.hasPrecedence(node.callee)) {
+			callee = '('+callee+')';
+		}
+		return callee+"("+node.arguments.map(this.visit.bind(this)).join(",")+")";
 	},
-	memberExpression: function (obj, prop, computed) {
-		return computed ? obj+"["+prop+"]" : obj+"."+prop;
+	visitMemberExpression: function (node) {
+		var obj = this.visit(node.object);
+		var prop = this.visit(node.property);
+		if (node.hasPrecedence(node.object)) {
+			obj = '('+obj+')';
+		}
+		return node.computed ? obj+"["+prop+"]" : obj+"."+prop;
 	},
-	switchCase: function (test, cons) {
-		if (!test) return "default:"+cons.join("");
+	visitSwitchCase: function (node) {
+		var cons = node.consequent.map(this.visit.bind(this)).join("");
+		if (!node.test) return "default:"+cons.join("");
+		var test = this.visit(node.test);
 		return "case"+lwrap(test)+":"+cons.join("");
 	},
 	catchClause: function (arg, body) {
 		return name;
 	},
 	literal: function (value) {
-		return typeof(value) === "string" ? JSON.stringify(value) : String(value);
+		if (typeof(value) === "string") {
+			return JSON.stringify(value);
+		}
+		else {
+			return String(value);
+		}
 	},
 	property: function (kind, key, value) {
 		switch (kind) {

File lib/nodes.js

 "use strict";
 
+var extend = require("extend");
+
+var PrecedenceMap = {
+	'.':   { order: 1, rtl: false },
+	'[]':  { order: 1, rtl: false },
+	'new': { order: 1, rtl: true },
+	'()':  { order: 2, rtl: false },
+	'++u': { order: 3, rtl: false },
+	'u++': { order: 3, rtl: true },
+	'--u': { order: 3, rtl: false },
+	'u--': { order: 3, rtl: true },
+	'!':   { order: 4, rtl: true },
+	'~':   { order: 4, rtl: true },
+	'+u':  { order: 4, rtl: true },
+	'-u':  { order: 4, rtl: true },
+	'typeof': { order: 4, rtl: true },
+	'void':   { order: 4, rtl: true },
+	'delete': { order: 4, rtl: true },
+	'*':   { order: 5, rtl: false },
+	'/':   { order: 5, rtl: false },
+	'%':   { order: 5, rtl: false },
+	'+':   { order: 6, rtl: false },
+	'-':   { order: 6, rtl: false },
+	'<<':  { order: 7, rtl: false },
+	'>>':  { order: 7, rtl: false },
+	'>>>': { order: 7, rtl: false },
+	'<':   { order: 8, rtl: false },
+	'<=':  { order: 8, rtl: false },
+	'>':   { order: 8, rtl: false },
+	'>=':  { order: 8, rtl: false },
+	'in':  { order: 8, rtl: false },
+	'instanceof': { order: 8, rtl: false },
+	'==':   { order: 9, rtl: false },
+	'!=':   { order: 9, rtl: false },
+	'===':  { order: 9, rtl: false },
+	'!==':  { order: 9, rtl: false },
+	'&':    { order: 10, rtl: false },
+	'^':    { order: 11, rtl: false },
+	'|':    { order: 12, rtl: false },
+	'&&':   { order: 13, rtl: false },
+	'||':   { order: 14, rtl: false },
+	'?:':   { order: 15, rtl: true },
+	'=':    { order: 16, rtl: true },
+	'+=':   { order: 16, rtl: true },
+	'-=':   { order: 16, rtl: true },
+	'*=':   { order: 16, rtl: true },
+	'/=':   { order: 16, rtl: true },
+	'%=':   { order: 16, rtl: true },
+	'<<=':  { order: 16, rtl: true },
+	'>>=':  { order: 16, rtl: true },
+	'>>>=': { order: 16, rtl: true },
+	'&=':   { order: 16, rtl: true },
+	'^=':   { order: 16, rtl: true },
+	'|=':   { order: 16, rtl: true },
+	',':    { order: 17, rtl: false }
+};
+
 function Node () {}
 function Statement () {}
 function Declaration () {}
 	this.type = "SequenceExpression";
 	this.expressions = expressions;
 	this.loc = loc;
+	this.precedence = PrecedenceMap[','];
 }
 
 function UnaryExpression (operator, argument, loc) {
 	this.operator = operator;
 	this.argument = argument;
 	this.loc = loc;
+	switch (operator) {
+		case '+':
+		case '-':
+			this.precedence = PrecedenceMap[operator+'u'];
+			break;
+		default:
+			this.precedence = PrecedenceMap[operator];
+	}
 }
 
 function BinaryExpression (operator, left, right, loc) {
 	this.left = left;
 	this.right = right;
 	this.loc = loc;
+	this.precedence = PrecedenceMap[operator];
 }
 
 function AssignmentExpression (operator, left, right, loc) {
 	this.left = left;
 	this.right = right;
 	this.loc = loc;
+	this.precedence = PrecedenceMap[operator];
 }
 
 function UpdateExpression (operator, argument, prefix, loc) {
 	this.argument = argument;
 	this.prefix = prefix;
 	this.loc = loc;
+	this.precedence = PrecedenceMap[prefix ? operator+'u' : 'u'+operator];
 }
 
 function LogicalExpression (operator, left, right, loc) {
 	this.left = left;
 	this.right = right;
 	this.loc = loc;
+	this.precedence = PrecedenceMap[operator];
 }
 
 function ConditionalExpression (test, consequent, alternate, loc) {
 	this.consequent = consequent;
 	this.alternate = alternate;
 	this.loc = loc;
+	this.precedence = PrecedenceMap[operator];
 }
 
 function NewExpression (constructor, args, loc) {
 	this.constructor = constructor;
 	this.arguments = args;
 	this.loc = loc;
+	this.precedence = PrecedenceMap["new"];
 }
 
 function CallExpression (callee, args, loc) {
 	this.callee = callee;
 	this.arguments = args;
 	this.loc = loc;
+	this.precedence = PrecedenceMap["()"];
 }
 
 function MemberExpression (object, property, computed, loc) {
 	this.property = property;
 	this.computed = computed;
 	this.loc = loc;
+	this.precedence = PrecedenceMap[computed ? "[]" : "."];
 }
 
 function SwitchCase (test, consequent, loc) {
 };
 
 Statement.prototype = new Node();
-Expression.prototype = new Node();
+Expression.prototype = extend(new Node(), {
+	hasLeftPrecedence: function (node) {
+		if (this.precedence.order < node.precedence.order) {
+			return true;
+		}
+		else if (this.precedence.order > node.precedence.order) {
+			return false;
+		}
+		else {
+			return this.precedence.rtl && node.precedence.rtl;
+		}
+	},
+	hasRightPrecedence: function (node) {
+		if (this.precedence.order < node.precedence.order) {
+			return true;
+		}
+		else if (this.precedence.order > node.precedence.order) {
+			return false;
+		}
+		else {
+			return !this.precedence.rtl;
+		}
+	},
+	hasPrecedence: function (node) {
+		if (this.precedence.order < node.precedence.order) {
+			return true;
+		}
+		else if (this.precedence.order > node.precedence.order) {
+			return false;
+		}
+		else {
+			return this.precedence.rtl;
+		}
+	},
+	precedence: {order: 0, rtl: false}
+});
 Declaration.prototype = new Statement();
 Program.prototype = new Node();
 EmptyStatement.prototype = new Statement();