Author: Stephan <[email protected]>
Branch:
Changeset: r94:6d5e9a501a64
Date: 2011-06-03 14:47 +0200
http://bitbucket.org/pypy/lang-js/changeset/6d5e9a501a64/
Log: allow context to declare, load and store local variables without
resolving
diff --git a/js/astbuilder.py b/js/astbuilder.py
--- a/js/astbuilder.py
+++ b/js/astbuilder.py
@@ -4,6 +4,51 @@
from js import operations
+class Scope(object):
+ def __init__(self):
+ self.local_variables = []
+
+ def __repr__(self):
+ return 'Scope ' + repr(self.local_variables)
+
+ def add_local(self, identifier):
+ if not self.is_local(identifier):
+ self.local_variables.append(identifier)
+ return self.get_local(identifier)
+
+ def is_local(self, identifier):
+ return identifier in self.local_variables
+
+ def get_local(self, identifier):
+ if self.is_local(identifier):
+ return self.local_variables.index(identifier)
+ else:
+ return None
+
+class Scopes(object):
+ def __init__(self):
+ self.scopes = []
+
+ def current_scope(self):
+ if not self.scopes:
+ return None
+ else:
+ return self.scopes[-1]
+
+ def new_scope(self):
+ self.scopes.append(Scope())
+
+ def end_scope(self):
+ self.scopes.pop()
+
+ def add_local(self, identifier):
+ if self.current_scope() is not None:
+ return self.current_scope().add_local(identifier)
+
+ def get_local(self, identifier):
+ if self.current_scope() is not None:
+ return self.current_scope().get_local(identifier)
+
class FakeParseError(Exception):
def __init__(self, pos, msg):
self.pos = pos
@@ -54,6 +99,7 @@
def __init__(self):
self.varlists = []
self.funclists = []
+ self.scopes = Scopes()
self.sourcename = ""
RPythonVisitor.__init__(self)
@@ -166,12 +212,12 @@
return self.UNOP_TO_CLS[op.additional_info](pos, child)
def _dispatch_assignment(self, pos, left, atype, prepost):
- from js.operations import Identifier, Member, MemberDot,
VariableIdentifier
-
is_post = prepost == 'post'
- if isinstance(left, Identifier) or isinstance(left,
VariableIdentifier):
+ if self.is_local_identifier(left):
+ return operations.LocalAssignmentOperation(pos, left, None, atype,
is_post)
+ elif self.is_identifier(left):
return operations.AssignmentOperation(pos, left, None, atype,
is_post)
- elif isinstance(left, Member) or isinstance(left, MemberDot):
+ elif self.is_member(left):
return operations.MemberAssignmentOperation(pos, left, None,
atype, is_post)
else:
raise FakeParseError(pos, "invalid lefthand expression")
@@ -242,7 +288,11 @@
pass
else:
i, vardecl = t
- return operations.VariableIdentifier(pos, i, name)
+ local = self.scopes.get_local(name)
+ if local is not None:
+ return operations.LocalIdentifier(pos, name, local)
+ else:
+ return operations.VariableIdentifier(pos, i, name)
return operations.Identifier(pos, name)
def visit_program(self, node):
@@ -274,6 +324,7 @@
return operations.SourceElements(pos, var_decl, func_decl, nodes,
self.sourcename)
def functioncommon(self, node, declaration=True):
+ self.scopes.new_scope()
pos = self.get_pos(node)
i=0
identifier, i = self.get_next_expr(node, i)
@@ -286,6 +337,7 @@
funcobj = operations.FunctionStatement(pos, identifier, p,
functionbody)
if declaration:
self.funclists[-1][identifier.get_literal()] = funcobj
+ self.scopes.end_scope()
return funcobj
def visit_functiondeclaration(self, node):
@@ -298,12 +350,16 @@
def visit_variabledeclaration(self, node):
pos = self.get_pos(node)
identifier = self.dispatch(node.children[0])
+ local = self.scopes.add_local(identifier.get_literal())
self.varlists[-1][identifier.get_literal()] = None
if len(node.children) > 1:
expr = self.dispatch(node.children[1])
else:
expr = None
- return operations.VariableDeclaration(pos, identifier, expr)
+ if local is not None:
+ return operations.LocalVariableDeclaration(pos, identifier, local,
expr)
+ else:
+ return operations.VariableDeclaration(pos, identifier, expr)
visit_variabledeclarationnoin = visit_variabledeclaration
def visit_expressionstatement(self, node):
@@ -326,17 +382,30 @@
return left
+ def is_identifier(self, obj):
+ from js.operations import Identifier, VariableIdentifier
+ return isinstance(obj, Identifier) or isinstance(obj,
VariableIdentifier)
+
+ def is_member(self, obj):
+ from js.operations import Member, MemberDot
+ return isinstance(obj, Member) or isinstance(obj, MemberDot)
+
+ def is_local_identifier(self, obj):
+ from js.operations import LocalIdentifier
+ return isinstance(obj, LocalIdentifier)
+
def visit_assignmentexpression(self, node):
- from js.operations import Identifier, VariableIdentifier, Member,
MemberDot
pos = self.get_pos(node)
left = self.dispatch(node.children[0])
operation = node.children[1].additional_info
right = self.dispatch(node.children[2])
- if isinstance(left, Identifier) or isinstance(left,
VariableIdentifier):
+ if self.is_local_identifier(left):
+ return operations.LocalAssignmentOperation(pos, left, right,
operation)
+ elif self.is_identifier(left):
return operations.AssignmentOperation(pos, left, right, operation)
- elif isinstance(left, Member) or isinstance(left, MemberDot):
+ elif self.is_member(left):
return operations.MemberAssignmentOperation(pos, left, right,
operation)
else:
raise FakeParseError(pos, "invalid lefthand expression")
diff --git a/js/jsobj.py b/js/jsobj.py
--- a/js/jsobj.py
+++ b/js/jsobj.py
@@ -590,11 +590,44 @@
self.property = Property('',w_Undefined)
else:
self.property = jsproperty
+ self.local_identifiers = []
+ self.local_values = []
def __str__(self):
return "<ExCtx %s, var: %s>"%(self.scope, self.variable)
+ def declare_local(self, name):
+ self.scope[-1].Put(self, name, w_Undefined, flags = DD)
+ self.local_identifiers.append(name)
+ self.local_values.append(w_Undefined)
+
+ def get_local_value(self, idx):
+ return self.local_values[idx]
+
+ def get_local_identifier(self, idx):
+ return self.local_identifiers[idx]
+
+ def get_local_index(self, name):
+ if name in self.local_identifiers:
+ return self.local_identifiers.index(name)
+ else:
+ return None
+
+ def assign_local(self, idx, value):
+ name = self.get_local_identifier(idx)
+ self.store(name, value)
+ self.store_local(idx, value)
+
def assign(self, name, value):
+ idx = self.get_local_index(name)
+ if idx is not None:
+ self.store_local(idx, value)
+ self.store(name, value)
+
+ def store_local(self, idx, value):
+ self.local_values[idx]=value
+
+ def store(self, name, value):
assert name is not None
for i in range(len(self.scope)-1, -1, -1):
obj = self.scope[i]
diff --git a/js/opcodes.py b/js/opcodes.py
--- a/js/opcodes.py
+++ b/js/opcodes.py
@@ -4,7 +4,6 @@
w_True, w_False, W_List, w_Null, W_Iterator, W_Root
import js.jsobj as jsobj
from js.execution import JsTypeError, ReturnException, ThrowException
-from pypy.rlib.unroll import unrolling_iterable
from js.baseop import plus, sub, compare, AbstractEC, StrictEC,\
compare_e, increment, decrement, commonnew, mult, division, uminus, mod
from pypy.rlib.rarithmetic import intmask
@@ -467,7 +466,7 @@
self.name = name
def eval(self, ctx, stack):
- ctx.scope[-1].Put(ctx, self.name, w_Undefined, flags = jsobj.DD)
+ ctx.declare_local(self.name)
def __repr__(self):
return 'DECLARE_VAR "%s"' % (self.name,)
@@ -632,6 +631,27 @@
obj = stack.pop().ToObject(ctx)
stack.append(newbool(obj.Delete(what)))
+class LOAD_LOCAL(Opcode):
+ def __init__(self, local):
+ self.local = local
+
+ def eval(self, ctx, stack):
+ stack.append(ctx.get_local_value(self.local))
+
+ def __repr__(self):
+ return 'LOAD_LOCAL %d' % (self.local,)
+
+class STORE_LOCAL(Opcode):
+ def __init__(self, local):
+ self.local = local
+
+ def eval(self, ctx, stack):
+ value = stack.top()
+ ctx.assign_local(self.local, value)
+
+ def __repr__(self):
+ return 'STORE_LOCAL %d' % (self.local,)
+
# different opcode mappings, to make annotator happy
OpcodeMap = {}
diff --git a/js/operations.py b/js/operations.py
--- a/js/operations.py
+++ b/js/operations.py
@@ -192,6 +192,21 @@
def emit_store(self, bytecode):
bytecode.emit('STORE', self.identifier)
+class LocalAssignmentOperation(AssignmentOperation):
+ def __init__(self, pos, left, right, operand, post = False):
+ self.left = left
+ self.local = left.local
+ self.identifier = left.get_literal()
+ self.right = right
+ if self.right is None:
+ self.right = Empty(pos)
+ self.pos = pos
+ self.operand = operand
+ self.post = post
+
+ def emit_store(self, bytecode):
+ bytecode.emit('STORE_LOCAL', self.local)
+
class MemberAssignmentOperation(BaseAssignment):
def __init__(self, pos, left, right, operand, post = False):
self.pos = pos
@@ -751,6 +766,33 @@
def __repr__(self):
return "VariableDeclaration %s:%s" % (self.identifier, self.expr)
+class LocalVariableDeclaration(Expression):
+ def __init__(self, pos, identifier, local, expr=None):
+ self.pos = pos
+ self.identifier = identifier.get_literal()
+ self.local = local
+ self.expr = expr
+
+ def emit(self, bytecode):
+ if self.expr is not None:
+ self.expr.emit(bytecode)
+ bytecode.emit('STORE_LOCAL', self.local)
+
+ def __repr__(self):
+ return "LocalVariableDeclaration %d(%s):%s" % (self.local,
self.identifier, self.expr)
+
+class LocalIdentifier(Expression):
+ def __init__(self, pos, identifier, local):
+ self.pos = pos
+ self.identifier = identifier
+ self.local = local
+
+ def emit(self, bytecode):
+ bytecode.emit('LOAD_LOCAL', self.local)
+
+ def get_literal(self):
+ return self.identifier
+
class VariableIdentifier(Expression):
def __init__(self, pos, depth, identifier):
self.pos = pos
diff --git a/js/test/test_interp.py b/js/test/test_interp.py
--- a/js/test/test_interp.py
+++ b/js/test/test_interp.py
@@ -876,3 +876,27 @@
def test_date_get_time():
yield assertv, "var i = new Date(); i.valueOf() == i.getTime()", True
+
+def test_declare_local_var():
+ yield assertv, """
+ function f() {
+ var i = 4;
+ function g() {
+ return i + 8;
+ }
+ return g();
+ }
+ f();
+ """, 12
+ py.test.skip("does not work yet")
+ yield assertv, """
+ function f() {
+ var i;
+ function g() {
+ i = 4;
+ return 8;
+ }
+ return g() + i;
+ }
+ f();
+ """, 12
diff --git a/js/test/test_locals.py b/js/test/test_locals.py
new file mode 100644
--- /dev/null
+++ b/js/test/test_locals.py
@@ -0,0 +1,32 @@
+import py
+
+from js.astbuilder import Scopes
+
+def test_scopes_is_local():
+ scopes = Scopes()
+ scopes.new_scope()
+ assert scopes.get_local('a') is None
+ scopes.add_local('a')
+ assert scopes.get_local('a') is not None
+ scopes.add_local('b')
+ assert scopes.get_local('b') is not None
+ scopes.new_scope()
+ assert scopes.get_local('a') is None
+ scopes.add_local('a')
+ assert scopes.get_local('a') is not None
+ assert scopes.get_local('b') is None
+
+def test_scopes_get_local():
+ scopes = Scopes()
+ scopes.new_scope()
+ scopes.add_local('a')
+ scopes.add_local('b')
+ assert scopes.get_local('a') == 0
+ assert scopes.get_local('b') == 1
+ assert scopes.get_local('c') is None
+
+ scopes.new_scope()
+ scopes.add_local('b')
+ assert scopes.get_local('b') == 0
+ assert scopes.get_local('a') is None
+
diff --git a/js/test/test_parser.py b/js/test/test_parser.py
--- a/js/test/test_parser.py
+++ b/js/test/test_parser.py
@@ -377,6 +377,12 @@
'LOAD_VARIABLE "a"',
'LOAD_MEMBER'])
+ def test_store_local(self):
+ self.check("function f() {var x; x = 1}",
+ ['DECLARE_FUNCTION f [] [\n DECLARE_VAR "x"\n LOAD_INTCONSTANT
1\n STORE_LOCAL 0\n]'])
+ self.check('function f() {var x = 1; y = 2;}',
+ ['DECLARE_FUNCTION f [] [\n DECLARE_VAR "x"\n LOAD_INTCONSTANT
1\n STORE_LOCAL 0\n LOAD_INTCONSTANT 2\n STORE "y"\n]'])
+
class TestToAstStatement(BaseTestToAST):
def setup_class(cls):
cls.parse = parse_func('statement')
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit