Commits

Mathias Panzenböck committed 7fe768c

scope++

Comments (0)

Files changed (1)

 
 var Visitor = require("./visitor.js").Visitor;
 
-function Scope (lexical, names) {
+function Scope (lexical, parent) {
 	this.lexical = !!lexical;
-	this._names = {};
-	if (names) this.update(names);
+	this.names   = {};
+	this.parent  = parent || null;
 }
 
 Scope.prototype = {
-	add: function (name) {
-		this._names[name] = true;
-	},
-	remove: function (name) {
-		delete this._names[name];
+	add: function (name,literal) {
+		if (literal) {
+			this.names[name] = true;
+		}
+		else {
+			this.scope().names[name] = true;
+		}
 	},
 	has: function (name) {
-		return this._names[name] === true;
+		return this.names[name] === true || this.parent && this.parent.has(name);
 	},
-	update: function (names) {
-		if (names instanceof Scope) {
-			for (var name in names._names) {
-				this._names[name] = true;
+	scope: function () {
+		var scope = this;
+		while (scope) {
+			if (!scope.lexical) {
+				return scope;
 			}
+			scope = scope.parent;
 		}
-		else if (Array.isArray(names)) {
-			for (var i = 0; i < names.length; ++ i) {
-				this._names[names[i]] = true;
+		return scope;
+	},
+	globalScope: function () {
+		var scope = this;
+		while (scope.parent) {
+			scope = scope.parent;
+		}
+		return scope;
+	},
+	scopeOf: function (name) {
+		var scope = this;
+		while (scope) {
+			if (scope.names[name] === true) {
+				return scope;
 			}
+			scope = scope.parent;
 		}
-		else {
-			for (var name in names) {
-				this._names[name] = true;
-			}
-		}
+		return null;
 	},
-	toArray: function () {
-		var names = [];
-		for (var name in this._names) {
-			names.push(name);
-		}
-		return names;
-	},
-	toString: function () {
-		return "new Scope("+!!this.lexical+","+JSON.stringify(this.toArray())+")";
+	isGlobal: function (name) {
+		var scope = this.scopeOf(name);
+		return !scope || !scope.parent;
 	}
 };
 
-function Context () {
-	this.path = [];
-	this.scopes = [new Scope()];
-}
-
-Context.prototype = {
-};
-
 function EnrichingVisitor () {
-	this.scopes = [];
-	this.literalScope = null;
 	this.scope = null;
 }
 
 EnrichingVisitor.prototype = {
-	push: function (node,literal) {
-		// XXX: enrich with chained link of scopes, not a single one!	
-
-		node.scope = new Scope(literal);
-		this.scopes.push(node.scope);
-		return node.scope;
+	push: function (literal) {
+		return (this.scope = node.scope = new Scope(literal,this.scope));
 	},
 	pop: function () {
-		return this.scopes.pop();
+		return (this.scope = this.scope.parent);
 	},
-	enrich: function () {
+	warn: function (node) {
+		var args = Array.prototype.slice.call(arguments,1);
+		if (node.loc) {
+			args.unshift(
+				node.loc.start.line+':'+
+				node.loc.start.column+': to '+
+				node.loc.end.line+':'+
+				node.loc.end.column+':');
+		}
+		console.warn.apply(console,args);
 	},
 	visitProgram: function (node) {
-		this.push(node);
+		node.scope = this.push();
 		node.body.forEach(this.visit.bind(this));
 		this.pop();
 	},
 	visitEmptyStatement: function (node) {
-
+		node.scope = this.scope;
 	},
 	visitBlockStatement: function (node) {
-		this.push(node,true);
+		node.scope = this.push(true);
 		node.body.forEach(this.visit.bind(this));
 		this.pop();
 	},
 	visitExpressionStatement: function (node) {
+		node.scope = this.scope;
 		this.visit(node.expression);
 	},
 	visitIfStatement: function (node) {
+		node.scope = this.scope;
 		this.visit(node.test);
 		this.visit(node.consequent);
 		if (node.alternate) this.visit(node.alternate);
+	},
+	visitLabeledStatement: function (node) {
+		node.scope = this.scope;
+		this.visit(node.label);
+		this.visit(node.body);
+	},
+	visitBreakStatement: function (node) {
+		node.scope = this.scope;
+		if (node.label) {
+			this.visit(node.label);
+		}
+	},
+	visitContinueStatement: function (node) {
+		node.scope = this.scope;
+		if (node.label) {
+			this.visit(node.label);
+		}
+	},
+	visitWithStatement: function (node) {
+		this.warn(node,"with statement is considered harmful and makes static scope analysis impossible");
+		node.scope = this.scope;
+		this.visit(node.object);
+		this.visit(node.body);
+	},
+	visitSwitchStatement: function (node) {
+		node.scope = this.scope;
+		this.visit(node.discriminant);
+		node.cases.forEach(this.visit.bind(this));
+	},
+	visitReturnStatement: function (node) {
+		node.scope = this.scope;
+		if (node.argument) {
+			this.visit(node.argument);
+		}
+	},
+	visitThrowStatement: function (node) {
+		node.scope = this.scope;
+		this.visit(node.argument);
+	},
+	visitTryStatement: function (node) {
+		node.scope = this.scope;
+		this.visit(node.block);
+		node.handlers.forEach(this.visit.bind(this));
+		if (node.finalizer) {
+			this.visit(node.finalizer);
+		}
+	},
+	visitWhileStatement: function (node) {
+		node.scope = this.scope;
+		this.visit(node.test);
+		this.visit(node.body);
+	},
+	visitDoWhileStatement: function (node) {
+		node.scope = this.scope;
+		this.visit(node.body);
+		this.visit(node.test);
+	},
+	visitForStatement: function (node) {
+		node.scope = this.scope;
+		if (node.init) this.visit(node.init);
+		if (node.test) this.visit(node.test);
+		if (node.update) this.visit(node.update);
+		this.visit(body);
+	},
+	visitForInStatement: function (node) {
+		node.scope = this.scope;
+		this.visit(node.left);
+		this.visit(node.right);
+		this.visit(node.body);
+	},
+	visitDebuggerStatement: function (node) {
+		node.scope = this.scope;
+	},
+	visitFunctionDeclaration: function (node) {
+		node.scope = this.scope;
+		this.visit(node.id);
+		node.scope.add(node.id.name);
+		node.params.forEach(this.visit.bind(this));
+		this.visit(node.body);
+	},
+	visitVariableDeclaration: function (node) {
+		node.scope = this.scope;
+		var literal = node.kind === 'let';
+		for (var i = 0; i < node.declarations.length; ++ i) {
+			var decl = node.declarations[i];
+			this.visit(decl);
+			this.scope.add(decl.id.name,literal);
+		}
+	},
+	visitVariableDeclarator: function (node) {
+		node.scope = this.scope;
+		this.visit(node.id);
+		if (node.init) this.visit(node.init);
+	},
+	visitThisExpression: function (node) {
+		node.scope = this.scope;
+	},
+	visitArrayExpression: function (node) {
+		node.scope = this.scope;
+		node.elements.forEach(this.visit.bind(this));
+	},
+	visitObjectExpression: function (node) {
+		node.scope = this.scope;
+		node.properties.forEach(this.visit.bind(this));
+	},
+	visitFunctionExpression: function (node) {
+		node.scope = this.scope;
+		if (node.id) this.visit(node.id);
+		node.params.forEach(this.visit.bind(this));
+		this.visit(node.body);
+	},
+	visitSequenceExpression: function (node) {
+		node.scope = this.scope;
+		node.expressions.forEach(this.visit.bind(this));
+	},
+	visitUnaryExpression: function (node) {
+		node.scope = this.scope;
+		this.visit(node.argument);
+	},
+	visitBinaryExpression: function (node) {
+		node.scope = this.scope;
+		this.visit(node.left);
+		this.visit(node.right);
+	},
+	visitAssignmentExpression: function (node) {
+		node.scope = this.scope;
+		this.visit(node.left);
+		this.visit(node.right);
+	},
+	visitUpdateExpression: function (node) {
+		node.scope = this.scope;
+		this.visit(node.argument);
+	},
+	visitLogicalExpression: function (node) {
+		node.scope = this.scope;
+		this.visit(node.left);
+		this.visit(node.right);
+	},
+	visitConditionalExpression: function (node) {
+		node.scope = this.scope;
+		this.visit(node.test);
+		this.visit(node.consequent);
+		this.visit(node.alternate);
+	},
+	visitNewExpression: function (node) {
+		node.scope = this.scope;
+		this.visit(node.callee),
+		if (node.arguments) {
+			node.arguments.forEach(this.visit.bind(this));
+		}
+	},
+	visitCallExpression: function (node) {
+		node.scope = this.scope;
+		this.visit(node.callee);
+		node.arguments.forEach(this.visit.bind(this));
+	},
+	visitMemberExpression: function (node) {
+		node.scope = this.scope;
+		this.visit(node.object);
+		this.visit(node.property);
+	},
+	visitSwitchCase: function (node) {
+		node.scope = this.scope;
+		if (node.test) this.visit(node.test);
+		node.consequent.forEach(this.visit.bind(this));
+	},
+	visitCatchClause: function (node) {
+		node.scope = this.scope;
+		this.visit(node.param);
+		this.visit(node.body);
+	},
+	visitIdentifier: function (node) {
+		node.scope = this.scope;
+	},
+	visitLiteral: function (node) {
+		node.scope = this.scope;
+	},
+	visitProperty: function (node) {
+		node.scope = this.scope;
+		this.visit(node.key);
+		this.visit(node.value);
 	}
-
-
 };