diff --git a/dragonlib/api.py b/dragonlib/api.py index bb1748f5..8cbcfcb0 100644 --- a/dragonlib/api.py +++ b/dragonlib/api.py @@ -49,10 +49,14 @@ def ascii_listing2program_dump(self, basic_program_ascii, program_start=None): """ if program_start is None: program_start = self.DEFAULT_PROGRAM_START -# return self.listing.ascii_listing2program_dump(basic_program_ascii, program_start) parser = BASICParser() parsed_lines = parser.parse(basic_program_ascii) + if not parsed_lines: + log.critical("No parsed lines %s from %s ?!?" % ( + repr(parsed_lines), repr(basic_program_ascii) + )) + log.info("Parsed BASIC: %s", repr(parsed_lines)) basic_lines = [] for line_no, code_objects in sorted(parsed_lines.items()): diff --git a/dragonlib/core/basic.py b/dragonlib/core/basic.py index b2f4bd6c..7ee972cd 100644 --- a/dragonlib/core/basic.py +++ b/dragonlib/core/basic.py @@ -14,7 +14,9 @@ import re from dragonlib.core import basic_parser -from dragonlib.utils.logging_utils import log, log_program_dump +from dragonlib.utils.iter_utils import list_replace +from dragonlib.utils.logging_utils import log, log_program_dump,\ + pformat_byte_hex_list from dragonpy.utils.byte_word_values import word2bytes @@ -74,7 +76,20 @@ def code_objects2token(self, code_objects): for code_object in code_objects: if code_object.PART_TYPE == basic_parser.CODE_TYPE_CODE: # Code part - tokens += self.ascii2token(code_object.content) + content = code_object.content + """ + NOTE: The BASIC interpreter changed REM shortcut and ELSE + internaly: + "'" <-> ":'" + "ELSE" <-> ":ELSE" + + See also: + http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4310&p=11632#p11630 + """ + log.info("replace ' and ELSE with :' and :ELSE") + content=content.replace("'", ":'") + content=content.replace("ELSE", ":ELSE") + tokens += self.ascii2token(content) else: # Strings, Comments or DATA tokens += self.chars2tokens(code_object.content) @@ -112,12 +127,41 @@ def __init__(self, token_util): self.token_util = token_util self.line_number = None self.line_code = None + + try: + colon_token = self.token_util.ascii2token_dict[":"] + except KeyError: # XXX: Always not defined as token? + colon_token = ord(":") + rem_token = self.token_util.ascii2token_dict["'"] + else_token = self.token_util.ascii2token_dict["ELSE"] + self.tokens_replace_rules = ( + ((colon_token, rem_token), rem_token), + ((colon_token, else_token), else_token), + ) def token_load(self, line_number, tokens): self.line_number = line_number assert tokens[-1] == 0x00, "line code %s doesn't ends with \\x00: %s" % ( repr(tokens), repr(tokens[-1]) ) + + """ + NOTE: The BASIC interpreter changed REM shortcut and ELSE + internaly: + "'" <-> ":'" + "ELSE" <-> ":ELSE" + + See also: + http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4310&p=11632#p11630 + """ + for src, dst in self.tokens_replace_rules: + log.info("Relace tokens %s with $%02x", + pformat_byte_hex_list(src), dst + ) + log.debug("Before..: %s", pformat_byte_hex_list(tokens) ) + tokens = list_replace(tokens, src, dst) + log.debug("After...: %s", pformat_byte_hex_list(tokens) ) + self.line_code = tokens[:-1] # rstrip \x00 def ascii_load(self, line_ascii): @@ -177,6 +221,11 @@ def dump2basic_lines(self, dump, program_start, basic_lines=None): # program end log.critical("return: %s", repr(basic_lines)) return basic_lines + + assert next_address>program_start, "Next address $%04x not bigger than program start $%04x ?!?" % ( + next_address,program_start + ) + line_number = (dump[2] << 8) + dump[3] log.critical("line_number: %i", line_number) length = next_address - program_start @@ -253,9 +302,8 @@ def pformat_program_dump(self, program_dump, program_start, formated_dump=None): tokens = program_dump[4:length] formated_dump.append("tokens:") formated_dump += self.token_util.pformat_tokens(tokens) - rest = program_dump[length:] - print rest, next_address, formated_dump - return self.pformat_program_dump(rest, next_address, formated_dump) + + return self.pformat_program_dump(program_dump[length:], next_address, formated_dump) def debug_listing(self, basic_lines): for line in basic_lines: diff --git a/dragonlib/tests/run_all_dragonlib_tests.py b/dragonlib/tests/run_all_dragonlib_tests.py new file mode 100644 index 00000000..9bcad2ea --- /dev/null +++ b/dragonlib/tests/run_all_dragonlib_tests.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +""" + DragonPy - Dragon 32 emulator in Python + ======================================= + + :copyleft: 2014 by the DragonPy team, see AUTHORS for more details. + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +import unittest + +from dragonpy.tests.test_base import TextTestRunner2 + + +if __name__ == "__main__": + loader = unittest.TestLoader() + tests = loader.discover('dragonlib') + + test_runner = TextTestRunner2(verbosity=2, +# failfast=True, + ) + + test_runner.run(tests) + print " --- END --- " \ No newline at end of file diff --git a/dragonlib/tests/test_api.py b/dragonlib/tests/test_api.py index b09faf9e..4ebfda28 100644 --- a/dragonlib/tests/test_api.py +++ b/dragonlib/tests/test_api.py @@ -17,7 +17,9 @@ from dragonlib.api import Dragon32API from dragonlib.core.basic import BasicLine from dragonlib.tests.test_base import BaseTestCase -from dragonlib.utils.logging_utils import log, setup_logging, log_program_dump +from dragonlib.utils.logging_utils import log, setup_logging, log_program_dump,\ + pformat_program_dump +from dragonpy.tests.test_base import TextTestRunner2 class BaseDragon32ApiTestCase(BaseTestCase): @@ -29,6 +31,43 @@ def assertEqualProgramDump(self, first, second, msg=None): second = self.dragon32api.pformat_program_dump(second) self.assertEqual(first, second, msg) + def assertListing2Dump(self, ascii_listing,program_dump,debug=False): + if debug: + print "\n"+"_"*79 + print " *** Debug Listing:\n%s" % ascii_listing + print " -"*39 + print " *** Debug Dump:\n%s\n" % ( + pformat_program_dump(program_dump) + ) + print " -"*39 + + created_program_dump = self.dragon32api.ascii_listing2program_dump( + ascii_listing + ) + if debug: + print " *** Created dump from listing:\n%s\n" % ( + pformat_program_dump(created_program_dump) + ) + print " -"*39 + + if not created_program_dump: + log.critical("Created dump empty? repr: %s", repr(created_program_dump)) + + if debug: + print " *** Full Dump:\n%s" % "\n".join( + self.dragon32api.pformat_program_dump(created_program_dump) + ) + self.assertEqualProgramDump(created_program_dump, program_dump) + + def assertDump2Listing(self, ascii_listing,program_dump): +# log_program_dump(created_program_dump) +# print "\n".join( +# self.dragon32api.pformat_program_dump(created_program_dump) +# ) + created_listing = self.dragon32api.program_dump2ascii_lines(program_dump) + ascii_listing=ascii_listing.splitlines() + self.assertEqual(created_listing, ascii_listing) + def _prepare_text(self, txt): """ prepare the multiline, indentation text. @@ -183,22 +222,13 @@ def test_ascii2RAM02(self): def test_listing2program_strings_dont_in_comment(self): """ - TODO: Don't replace tokens in comments - - NOTE: The REM shortcut >'< would be replace by >:'< internally from - the BASIC Interpreter. - - See also: - http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4310&p=11632#p11630 + Don't replace tokens in comments """ - program_dump = self.dragon32api.ascii_listing2program_dump( - "10 :'IF THEN ELSE" - ) - print "\n".join( - self.dragon32api.pformat_program_dump(program_dump) - ) - self.assertEqualProgramDump(program_dump, ( - 0x1e, 0x14, # next address + ascii_listing = self._prepare_text(""" + 10 'IF THEN ELSE" + """) + program_dump= ( + 0x1e, 0x15, # next address 0x00, 0x0a, # 10 0x3a, # : 0x83, # ' @@ -207,33 +237,120 @@ def test_listing2program_strings_dont_in_comment(self): 0x54, 0x48, 0x45, 0x4e, # T, H, E, N 0x20, # " " 0x45, 0x4c, 0x53, 0x45, # E, L, S, E + 0x22, # " 0x00, # end of line 0x00, 0x00, # program end - )) + ) + self.assertListing2Dump(ascii_listing,program_dump, +# debug=True + ) + self.assertDump2Listing(ascii_listing,program_dump) def test_listing2program_strings_dont_in_strings(self): """ Don't replace tokens in strings """ - program_dump = self.dragon32api.ascii_listing2program_dump( - '10 PRINT"FOR NEXT' + ascii_listing = self._prepare_text(""" + 10 PRINT"FOR NEXT + """) + program_dump= ( + 0x1e, 0x10, # next address + 0x00, 0x0a, # 10 + 0x87, # PRINT + 0x22, # " + 0x46, 0x4f, 0x52, # F, O, R + 0x20, # " " + 0x4e, 0x45, 0x58, 0x54, # N, E, X, T + 0x00, # end of line + 0x00, 0x00, # program end ) - log_program_dump(program_dump) - print "\n".join( - self.dragon32api.pformat_program_dump(program_dump) + self.assertListing2Dump(ascii_listing,program_dump, +# debug=True ) - self.assertEqualProgramDump(program_dump, ( - 0x1e, 0x10, # start address - 0x00, # line start - 0x0a, # 10 - 0x87, # PRINT - 0x22, # " - 0x46, 0x4f, 0x52, # F, O, R - 0x20, # " " - 0x4e, 0x45, 0x58, 0x54, # N, E, X, T + self.assertDump2Listing(ascii_listing,program_dump) + + + def test_ascii_listing2program_dump_with_bigger_line_number(self): + """ + Don't replace tokens in strings + """ + ascii_listing = self._prepare_text(""" + 65000 CLS + """) + program_dump = ( + 0x1e, 0x07, # start address + 0xfd, 0xe8, # 65000 + 0xa0, # CLS 0x00, # end of line 0x00, 0x00, # program end - )) + ) + self.assertListing2Dump(ascii_listing,program_dump) + self.assertDump2Listing(ascii_listing,program_dump) + + def test_auto_add_colon_before_comment(self): + """ + NOTE: The REM shortcut >'< would be replace by >:'< internally from + the BASIC Interpreter. + + See also: + http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4310&p=11632#p11630 + """ + ascii_listing = self._prepare_text(""" + 100 'FOO + """) + program_dump = ( + 0x1e, 0x0b, # next address (length: 10) + 0x00, 0x64, # 100 (line number) + 0x3a, # : ===> Insert/remove it automaticly + 0x83, # ' + 0x46, # F + 0x4f, # O + 0x4f, # O + 0x00, # EOL + 0x00, 0x00, # end address + ) + self.assertListing2Dump(ascii_listing,program_dump, + debug=True + ) + self.assertDump2Listing(ascii_listing,program_dump) + + def test_auto_add_colon_before_else(self): + """ + NOTE: The REM shortcut >'< would be replace by >:'< internally from + the BASIC Interpreter. + + See also: + http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4310&p=11632#p11630 + """ + ascii_listing = self._prepare_text(""" + 100 IF A=1 THEN 10 ELSE 20 + """) + program_dump = ( + 0x1e, 0x16, # -> next address (length: 21) + 0x00, 0x64, # -> 100 (line number) + 0x85, # -> 'IF' + 0x20, # -> ' ' + 0x41, # -> 'A' + 0xcb, # -> '=' + 0x31, # -> '1' + 0x20, # -> ' ' + 0xbf, # -> 'THEN' + 0x20, # -> ' ' + 0x31, # -> '1' + 0x30, # -> '0' + 0x20, # -> ' ' + 0x3a, # : ===> Insert/remove it automaticly + 0x84, # -> 'ELSE' + 0x20, # -> ' ' + 0x32, # -> '2' + 0x30, # -> '0' + 0x00, # -> EOL + 0x00, 0x00, # -> end address + ) + self.assertListing2Dump(ascii_listing,program_dump, + debug=True + ) + self.assertDump2Listing(ascii_listing,program_dump) class RenumTests(BaseDragon32ApiTestCase): @@ -339,17 +456,18 @@ def test_on_gosub_dont_exists(self): if __name__ == '__main__': setup_logging(log, # level=1 # hardcore debug ;) -# level=10 # DEBUG -# level=20 # INFO +# level=10 # DEBUG +# level=20 # INFO # level=30 # WARNING # level=40 # ERROR level=50 # CRITICAL/FATAL ) unittest.main( + testRunner= TextTestRunner2, argv=( sys.argv[0], - # "Dragon32BASIC_HighLevel_ApiTest", +# "Dragon32BASIC_HighLevel_ApiTest.test_listing2program_strings_dont_in_comment", ), # verbosity=1, verbosity=2, diff --git a/dragonlib/utils/iter_utils.py b/dragonlib/utils/iter_utils.py new file mode 100644 index 00000000..687c1ae2 --- /dev/null +++ b/dragonlib/utils/iter_utils.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# encoding:utf-8 + +""" + iter utilities + ~~~~~~~~~~~~~~ + + :copyleft: 2014 by the DragonPy team, see AUTHORS for more details. + :license: GNU GPL v3 or above, see LICENSE for more details. +""" + +def list_replace(iterable, src, dst): + """ + Thanks to "EyDu": + http://www.python-forum.de/viewtopic.php?f=1&t=34539 (de) + + >>> list_replace([1,2,3], (1,2), "X") + ['X', 3] + + >>> list_replace([1,2,3,4], (2,3), 9) + [1, 9, 4] + + >>> list_replace([1,2,3], (2,), "X") + [1, 'X', 3] + + >>> list_replace([1,2,3,4,5], (2,3,4), "X") + [1, 'X', 5] + + >>> list_replace([1,2,3,4,5], (4,5), "X") + [1, 2, 3, 'X'] + + >>> list_replace([1,2,3,4,5], (1,2), "X") + ['X', 3, 4, 5] + + >>> list_replace([1,2,3,3,3,4,5], (3,3), "X") + [1, 2, 'X', 3, 4, 5] + + >>> list_replace((58, 131, 73, 70), (58, 131), 131) + [131, 73, 70] + """ + result=[] + iterable=list(iterable) + + try: + dst=list(dst) + except TypeError: # e.g.: int + dst=[dst] + + src=list(src) + src_len=len(src) + index = 0 + while index < len(iterable): + element = iterable[index:index+src_len] +# print element, src + if element == src: + result += dst + index += src_len + else: + result.append(iterable[index]) + index += 1 + return result + + +if __name__ == "__main__": + import doctest + print doctest.testmod() \ No newline at end of file diff --git a/dragonlib/utils/logging_utils.py b/dragonlib/utils/logging_utils.py index 590be332..a7362ade 100644 --- a/dragonlib/utils/logging_utils.py +++ b/dragonlib/utils/logging_utils.py @@ -76,9 +76,18 @@ def log_memory_dump(memory, start, end, mem_info, level=99): log.log(level, "\t%s", msg) +def pformat_hex_list(hex_list): + return u" ".join([u"$%x" % v for v in hex_list]) + +def pformat_byte_hex_list(hex_list): + return u" ".join([u"$%02x" % v for v in hex_list]) + +def pformat_word_hex_list(hex_list): + return u" ".join([u"$%02x" % v for v in hex_list]) + def log_hexlist(byte_list, group=8, start=0x0000, level=99): def _log(level, addr, line): - msg = " ".join(["$%02x" % v for v in line]) + msg = pformat_byte_hex_list(line) msg = "%04x - %s" % (addr, msg) log.log(level, msg) @@ -97,7 +106,7 @@ def _log(level, addr, line): def pformat_program_dump(ram_content): - msg = u" ".join(["$%02x" % v for v in ram_content]) + msg = pformat_byte_hex_list(ram_content) msg = msg.replace(u"$00 ", u"\n$00\n") return msg