Author: Stephan <step...@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') _______________________________________________ pypy-commit mailing list pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit