[pypy-commit] lang-js default: allow context to declare, load and store local variables without resolving

stepahn noreply at buildbot.pypy.org
Wed Jun 8 12:43:20 CEST 2011


Author: Stephan <stephan at stzal.com>
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')


More information about the pypy-commit mailing list