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]
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()
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", "", ]
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", "", ]
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', )
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', )
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', )
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', )
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', )
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))
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', )
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)
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', )
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", "", ]
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", "", ]