Beispiel #1
0
def test_program_average_compile():
    # Isolate the compiler by using the included solution for everything else:
    platform = BUNDLED_PLATFORM
    simulator = "codegen"

    with open("examples/project_11/Average/Main.jack") as f:
        src = f.read()

    ast = platform.parser(src)

    asm = AssemblySource()
    project_11.compile_class(ast, asm)

    # If it fails, you probably want to see the opcodes it wrote:
    for l in asm.lines:
        print(l)

    ops = [
        platform.parse_line(l) for l in asm.lines
        if platform.parse_line(l) is not None
    ]

    translator = platform.translator()
    translator.preamble()

    for op in ops:
        translator.handle(op)

    translate_library(translator, platform)

    translator.finish()
    translator.check_references()
Beispiel #2
0
def test_program_seven_opcodes():
    """This program is so simple that there's probably only one reasonable way to compile it,
    so just compare the VM opcodes."""

    with open("examples/project_11/Seven/Main.jack") as f:
        src = f.read()

    ast = project_10.parse_class(src)

    asm = AssemblySource()

    project_11.compile_class(ast, asm)

    expected = """
  function Main.main 1
  push constant 1
  push constant 2
  push constant 3
  call Math.multiply 2
  add
  call Output.printInt 1
  pop temp 0
  push constant 0
  return

"""

    assert list(asm.lines) == expected.split("\n")[1:-1]
Beispiel #3
0
def test_other_instance_field_access():
    ast = project_10.parse_class("""
        class BoxedInt {
            field int value;

            method boolean compare(BoxedInt other) {
                return value < other.value;
            }
        }
        """)

    asm = AssemblySource()
    project_11.compile_class(ast, asm)

    assert asm.lines == [
        "  function BoxedInt.compare 1",
        "  push argument 0",
        "  pop pointer 0",
        "  push this 0",
        "  push argument 1",
        "  pop pointer 1",
        "  push that 0",
        "  lt",
        "  return",
        "",
    ]
Beispiel #4
0
    def __init__(self):
        # Parameters controlling how many specialized opcode variants are emitted.
        # More specialization means a larger library, but smaller object code and
        # fewer cycles, generally.
        # May be manually tweaked. A smart translator would inspect the source and choose them
        # to optimize for size/speed.
        self.SPECIALIZED_MAX_PUSH_CONSTANT = 6  # TODO: 12?
        self.SPECIALIZED_MAX_POP_SEGMENT = 6  # TODO: 10?
        self.SPECIALIZED_MAX_PUSH_SEGMENT = 6
        self.SPECIALIZED_MAX_FUNCTION_NUM_LOCALS = 10  # TODO: ?
        self.SPECIALIZED_MAX_CALL_NUM_ARGS = 4  # TODO: ?

        self.asm = AssemblySource()

        self.class_namespace = "_"
        self.function_namespace = "_"

        start = self.asm.next_label("start")
        self.asm.instr(f"@{start}")
        self.asm.instr("0;JMP")

        # "Microcoded" instructions, which for this translator basically includes _all_ opcodes,
        # plus many special-cases:
        # If there's a single argument, it's passed in A. If more than one, additional args are
        # passed in R13-R15. See each implementation for specifics.
        self._library()

        # Early check that the library of opcodes fits in the first half of the ROM, as required.
        # Practically speaking, probably want it to be _much_ smaller than that.
        assert self.asm.instruction_count <= 2**14

        self.asm.label(start)
Beispiel #5
0
    def __init__(self):
        # AssemblySource deals with keeping track of emitted instructions, and provides a nice
        # log of execution which makes debugging a lot easier. In any case, the tests assume you use
        # it, so you might as well not fight it.
        self.asm = AssemblySource()

        # SOLVERS: remove this when all the method bodies are filled in
        self.solved = solved_07.Translator(self.asm)
Beispiel #6
0
def test_compile_string_lib(string_class=project_12.STRING_CLASS,
                            platform=BUNDLED_PLATFORM):
    """First, just make sure this particular class makes it through the compiler."""

    asm = AssemblySource()
    platform.compiler(string_class, asm)

    assert len(asm.lines) > 0
Beispiel #7
0
def test_program_complex_arrays():
    # Isolate the compiler by using the included solution for everything else:
    platform = BUNDLED_PLATFORM
    simulator = "codegen"

    with open("examples/project_11/ComplexArrays/Main.jack") as f:
        src = f.read()

    ast = platform.parser(src)

    asm = AssemblySource()
    project_11.compile_class(ast, asm)

    # If it fails, you probably want to see the opcodes it wrote:
    for l in asm.lines:
        print(l)

    ops = [
        platform.parse_line(l) for l in asm.lines
        if platform.parse_line(l) is not None
    ]

    translator = platform.translator()
    translator.preamble()

    for op in ops:
        translator.handle(op)

    # Note: using the full OS implementation is simpler then the fancy tricks done in test_12
    # to isolate individual OS classes, but it also means that this test might need millions of
    # cycles to run, including writing all the results to the screen buffer.
    translate_library(translator, platform)

    translator.finish()
    translator.check_references()

    computer = run(platform.chip, simulator=simulator)

    output_stream = StringWriter()
    translator.asm.run(platform.assemble,
                       computer,
                       stop_cycles=5_000_000,
                       debug=True,
                       tty=output_stream)

    output_lines = "".join(output_stream.strs).split("\n")
    assert output_lines == [
        "Test 1: expected result: 5; actual result: 5",
        "Test 2: expected result: 40; actual result: 40",
        "Test 3: expected result: 0; actual result: 0",
        "Test 4: expected result: 77; actual result: 77",
        "Test 5: expected result: 110; actual result: 110",
        "",
    ]
Beispiel #8
0
def test_trivial_expression():
    ast = project_10.ExpressionP.parse(project_10.lex("1 + 2"))

    symbol_table = project_11.SymbolTable("Main")
    asm = AssemblySource()
    project_11.compile_expression(ast, symbol_table, asm)

    assert asm.lines == [
        "  push constant 1",
        "  push constant 2",
        "  add",
    ]
Beispiel #9
0
def test_missing_return():
    """Another very common error."""

    ast = project_10.parse_class("""
        class Foo {
            function void noReturn() {
                // oops, forgot to `return;` here
            }
        }
        """)

    with pytest.raises(Exception) as exc_info:
        asm = AssemblySource()
        project_11.compile_class(ast, asm)
    assert exc_info.value.args == ('Missing "return" in Foo.noReturn', )
Beispiel #10
0
def test_constructor_bad_result_type():
    """This could be confusing, so make it an error."""

    ast = project_10.parse_class("""
        class Foo {
            constructor Bar new() {
                return this;
            }
        }
        """)

    with pytest.raises(Exception) as exc_info:
        asm = AssemblySource()
        project_11.compile_class(ast, asm)
    assert exc_info.value.args == (
        'Result type does not match: constructor Foo.new', )
Beispiel #11
0
def test_no_this():
    """This could be confusing, so make it an error."""

    ast = project_10.parse_class("""
        class Foo {
            function void run() {
                return this;
            }
        }
        """)

    with pytest.raises(Exception) as exc_info:
        asm = AssemblySource()
        project_11.compile_class(ast, asm)
    assert exc_info.value.args == (
        'Undefined "this" in static context: function Foo.run', )
Beispiel #12
0
def test_constructor_void_return():
    """Constructor must return "this", or the caller will be surprised."""

    ast = project_10.parse_class("""
        class Foo {
            constructor Foo new() {
                return;  // meaning "null"
            }
        }
        """)

    with pytest.raises(Exception) as exc_info:
        asm = AssemblySource()
        project_11.compile_class(ast, asm)
    assert exc_info.value.args == (
        'Does not return "this": constructor Foo.new', )
Beispiel #13
0
def test_constructor_wrong_name():
    """This could be confusing, so make it an error."""

    ast = project_10.parse_class("""
        class Foo {
            constructor Foo notNew() {
                return this;
            }
        }
        """)

    with pytest.raises(Exception) as exc_info:
        asm = AssemblySource()
        project_11.compile_class(ast, asm)
    assert exc_info.value.args == (
        'Must be named "new": constructor Foo.notNew', )
Beispiel #14
0
def test_program_convert_to_bin():
    # Isolate the compiler by using the included solution for everything else:
    platform = BUNDLED_PLATFORM
    simulator = "codegen"

    with open("examples/project_11/ConvertToBin/Main.jack") as f:
        src = f.read()

    ast = platform.parser(src)

    asm = AssemblySource()
    project_11.compile_class(ast, asm)

    # If it fails, you probably want to see the opcodes it wrote:
    for l in asm.lines:
        print(l)

    ops = [
        platform.parse_line(l) for l in asm.lines
        if platform.parse_line(l) is not None
    ]

    translator = platform.translator()
    translator.preamble()

    for op in ops:
        translator.handle(op)

    # Note: using the full OS implementation is simpler then the fancy tricks done in test_12
    # to isolate individual OS classes, but it also means that this test might need 100s
    # of thousands of cycles to run (mainly initializing the OS unnecessarily.)
    translate_library(translator, platform)

    translator.finish()
    translator.check_references()

    computer = run(platform.chip, simulator=simulator)

    computer.poke(8000, 0xBEEF)

    translator.asm.run(platform.assemble,
                       computer,
                       stop_cycles=200_000,
                       debug=True)

    for b in range(16):
        assert computer.peek(8001 + b) == bool(0xBEEF & (1 << b))
Beispiel #15
0
def test_call_method_from_function_context():
    """A common error case; referring to a function using the method-call syntax."""

    ast = project_10.parse_class("""
        class Foo {
            function void run() {
                do go();  // Probably meant `Foo.go()`
                return;
            }
        }
        """)

    with pytest.raises(Exception) as exc_info:
        asm = AssemblySource()
        project_11.compile_class(ast, asm)
    assert exc_info.value.args == (
        'Tried to use implicit "this" in static (function) context: Foo.run', )
Beispiel #16
0
def test_return_on_both_branches():
    """Accept this common pattern."""

    ast = project_10.parse_class("""
        class Foo {
            function void toInt(boolean x) {
                if (x) {
                    return 1;
                }
                else {
                    return 0;
                }
            }
        }
        """)

    asm = AssemblySource()
    project_11.compile_class(ast, asm)
Beispiel #17
0
def test_field_in_function_context():
    """A common error case; referring to an instance member within a function."""

    ast = project_10.parse_class("""
        class Foo {
            field int x;

            function int run() {
                return x;
            }
        }
        """)

    with pytest.raises(Exception) as exc_info:
        asm = AssemblySource()
        project_11.compile_class(ast, asm)
    assert exc_info.value.args == (
        'Tried to use field "x" in static context: function Foo.run', )
Beispiel #18
0
def test_call_function_from_function_context():
    ast = project_10.parse_class("""
        class Foo {
            function void run() {
                do Bar.go();
                return;
            }
        }
        """)

    asm = AssemblySource()
    project_11.compile_class(ast, asm)

    assert asm.lines == [
        "  function Foo.run 1",
        "  call Bar.go 0",
        "  pop temp 0",
        "  push constant 0",
        "  return",
        "",
    ]
Beispiel #19
0
    def __init__(self, asm=None):
        self.asm = asm if asm else AssemblySource()
        self.class_namespace = "static"
        self.function_namespace = "_"

        self.defined_functions = []
        self.referenced_functions = []
        self.last_function_start = None

        # HACK: some code that's always required, even when preamble is not used.

        start = self.asm.next_label("start")
        self.asm.instr(f"@{start}")
        self.asm.instr("0;JMP")

        # "Microcoded" instructions:
        self.eq_label = self._compare("EQ")
        self.lt_label = self._compare("LT")
        self.gt_label = self._compare("GT")
        self.return_label = self._return()
        self.call_label = self._call()

        self.asm.label(start)
Beispiel #20
0
def test_call_method_from_method_context():
    ast = project_10.parse_class("""
        class Foo {
            method void run() {
                do go();
                return;
            }
        }
        """)

    asm = AssemblySource()
    project_11.compile_class(ast, asm)

    assert asm.lines == [
        "  function Foo.run 1",
        "  push argument 0",
        "  pop pointer 0",
        "  push pointer 0",  # implicit `this` for self call
        "  call Foo.go 1",
        "  pop temp 0",
        "  push constant 0",
        "  return",
        "",
    ]
Beispiel #21
0
    def __init__(self):
        self.asm = AssemblySource()

        solved_07.Translator.__init__(self, self.asm)

        self.top_in_d = False