Commits

Alex Gaynor committed 519911a

Initial bytecode compiler.

Comments (0)

Files changed (9)

minijs/astcompiler.py

+from minijs import consts
+from minijs.bytecode import Bytecode
+
+
+class CompilerContext(object):
+    def __init__(self, space):
+        self.space = space
+        self.data = []
+        self.consts = []
+        self.names = {}
+
+    def emit(self, bc, *args):
+        self.data.append(chr(bc))
+        for arg in args:
+            self.data.append(chr(arg))
+
+    def create_name(self, name):
+        if name not in self.names:
+            self.names[name] = len(self.names)
+        return self.names[name]
+
+    def create_float_const(self, floatval):
+        pos = len(self.consts)
+        self.consts.append(self.space.newfloat(floatval))
+        return pos
+
+    def create_bytecode(self):
+        bcs = "".join(self.data)
+        max_stackdepth = self.count_stackdepth(bcs)
+        return Bytecode(bcs, max_stackdepth, self.consts)
+
+    def count_stackdepth(self, bc):
+        i = 0
+        current_stackdepth = 0
+        max_stackdepth = 0
+        while i < len(bc):
+            c = ord(bc[i])
+            i += 1
+            stack_effect = consts.BYTECODE_STACK_EFFECT[c]
+            i += consts.BYTECODE_NUM_ARGS[c]
+            current_stackdepth += stack_effect
+            max_stackdepth = max(current_stackdepth, max_stackdepth)
+        return max_stackdepth

minijs/bytecode.py

+class Bytecode(object):
+    def __init__(self, code, max_stackdepth, consts):
+        self.code = code
+        self.max_stackdepth = max_stackdepth
+        self.consts = consts
+import sys
+
+# name, arguments, stack effect
+BYTECODES = [
+    ("LOAD_CONST", 1, +1),
+    ("STORE_NAME", 1, 0),
+    ("DISCARD_TOP", 0, -1),
+    ("RETURN_NULL", 0, 0),
+]
+
+BYTECODE_NAMES = []
+BYTECODE_NUM_ARGS = []
+BYTECODE_STACK_EFFECT = []
+module = sys.modules[__name__]
+for i, (bc_name, num_args, stack_effect) in enumerate(BYTECODES):
+    setattr(module, bc_name, i)
+    BYTECODE_NAMES.append(bc_name)
+    BYTECODE_NUM_ARGS.append(num_args)
+    BYTECODE_STACK_EFFECT.append(stack_effect)
+del i, bc_name, num_args, stack_effect, module

minijs/objects/__init__.py

Empty file added.

minijs/objects/base.py

+class W_Root(object):
+    _attrs_ = ()

minijs/objects/floatobject.py

+from minijs.objects.base import W_Root
+
+
+class W_FloatObject(W_Root):
+    def __init__(self, floatval):
+        self.floatval = floatval

minijs/objspace.py

+from minijs import consts
+from minijs.astcompiler import CompilerContext
+from minijs.objects.floatobject import W_FloatObject
 from minijs.parser import Transformer, _parse
 
 
 class ObjectSpace(object):
     def parse(self, source):
-        return Transformer().visit_main(_parse(source))
+        return Transformer().visit_main(_parse(source))
+
+    def compile(self, source):
+        astnode = self.parse(source)
+        c = CompilerContext(self)
+        astnode.compile(c)
+        c.emit(consts.RETURN_NULL)
+        return c.create_bytecode()
+
+
+    def newfloat(self, floatval):
+        return W_FloatObject(floatval)
 
 from pypy.rlib.parsing.ebnfparse import parse_ebnf, make_parse_function
 
+from minijs import consts
+
 
 with open(os.path.join(os.path.dirname(__file__), "grammar.txt")) as f:
     grammar = f.read()
     def __eq__(self, other):
         return type(self) is type(other) and self.__dict__ == other.__dict__
 
+    def compile(self, ctx):
+        raise NotImplementedError(type(self).__name__)
+
 class Block(Node):
     def __init__(self, stmts):
         self.stmts = stmts
 
+    def compile(self, ctx):
+        for stmt in self.stmts:
+            stmt.compile(ctx)
+
 class Stmt(Node):
     def __init__(self, expr):
         self.expr = expr
 
+    def compile(self, ctx):
+        self.expr.compile(ctx)
+        ctx.emit(consts.DISCARD_TOP)
+
 class Assignment(Node):
     def __init__(self, var, expr):
         self.var = var
         self.expr = expr
 
+    def compile(self, ctx):
+        self.expr.compile(ctx)
+        ctx.emit(consts.STORE_NAME, ctx.create_name(self.var))
+
 class If(Node):
     def __init__(self, cond, body):
         self.cond = cond
     def __init__(self, floatval):
         self.floatval = floatval
 
+    def compile(self, ctx):
+        ctx.emit(consts.LOAD_CONST, ctx.create_float_const(self.floatval))
+
 
 class Transformer(object):
     def visit_main(self, node):

tests/test_compiler.py

+from minijs import consts
+
+
+class TestCompiler(object):
+    def assert_compiles(self, space, source, expected_bytecode):
+        bc = space.compile(source)
+        expected = [
+            line.strip()
+            for line in expected_bytecode.splitlines()
+            if line.strip()
+        ]
+
+        actual = []
+        i = 0
+        while i < len(bc.code):
+            c = ord(bc.code[i])
+            line = consts.BYTECODE_NAMES[c]
+            i += 1
+            for j in xrange(consts.BYTECODE_NUM_ARGS[c]):
+                line += " %s" % ord(bc.code[i])
+                i += 1
+            actual.append(line)
+        assert actual == expected
+        return bc
+
+    def test_assignment(self, space):
+        bc = self.assert_compiles(space, "a = 3;", """
+        LOAD_CONST 0
+        STORE_NAME 0
+        DISCARD_TOP
+        RETURN_NULL
+        """)
+        [c] = bc.consts
+        assert c.floatval == 3
+        assert bc.max_stackdepth == 1