From 4176c2c4dc52a0ab8d0a565fb48adbff3142e1bc Mon Sep 17 00:00:00 2001 From: Martin Ruskov Date: Sun, 9 Jan 2022 23:03:09 +0100 Subject: [PATCH 1/8] added optional constructor parameters and repr method --- tests/test_basic.py | 38 ++++++++++++++-- yarnrunner_python/runner.py | 87 ++++++++++++++++++++++++++++++------- 2 files changed, 106 insertions(+), 19 deletions(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index dee5fd8..7deb21c 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,10 +1,10 @@ import os from .context import YarnRunner -compiled_yarn_f1 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn1/basic.yarnc'), 'rb') -names_csv_f1 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn1/basic.csv'), 'r') +compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/basic.yarnc") +compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") +names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/basic.csv") +names_csv_f1 = open(names_csv_fname1, "r") compiled_yarn_f2 = open(os.path.join(os.path.dirname( __file__), '../examples/yarn2/basic.yarnc'), 'rb') names_csv_f2 = open(os.path.join(os.path.dirname( @@ -82,3 +82,33 @@ def test_start_node_choose2(): # ensure the command has run assert side_effect2 == "event:/event/event_name" + + +def test_repr(): + """Testing optional parameters of constructor and repr()""" + + "YarnRunner(open(\"examples/yarn1/basic.yarnc\", \"rb\"), open(\"examples/yarn1/basic.csv\"), autostart=True, visits={\\'choice_1\\': 0, \\'Start\\': 1}, current_node=\\'Start\\')" + + result = repr(runner1) + + assert result.startswith( + f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=True, visits={{'""" + ) + + assert ("""'Start': 1""" in result) and ("""'choice_1': 1""" in result) + assert """current_node='choice_1'""" in result + assert """command_handlers={'runACommand':""" in result + + """YarnRunner(open("examples/yarn1/basic.yarnc", "rb"), open("examples/yarn1/basic.csv"), autostart=True, visits={'Start': 1, 'choice_1': 0}, current_node='Start')""" + + result = repr( + YarnRunner( + compiled_yarn_f1, + names_csv_f1, + autostart=False, + ) + ) + + # assert ("'Start': 0" in result) and ("'choice_1': 0" in result) + + assert result == f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False)""" diff --git a/yarnrunner_python/runner.py b/yarnrunner_python/runner.py index 08937c4..64e6db9 100644 --- a/yarnrunner_python/runner.py +++ b/yarnrunner_python/runner.py @@ -1,34 +1,91 @@ +from typing import Any, Dict, List, Optional, Union + import csv import re from warnings import warn -from .yarn_spinner_pb2 import Program as YarnProgram, Instruction +from .yarn_spinner_pb2 import Program as YarnProgram, Instruction # type: ignore from .vm_std_lib import functions as std_lib_functions class YarnRunner(object): - def __init__(self, compiled_yarn_f, names_csv_f, autostart=True, enable_tracing=False) -> None: + def __init__( + self, + compiled_yarn_f, + names_csv_f, + autostart=True, + enable_tracing=False, + visits: Optional[Dict[str, int]] = None, + variables: Dict[str, Any] = None, + current_node: str = None, + command_handlers: Dict[str, Any] = None, + line_buffer: List[str] = None, + option_buffer: List[Dict[str, Union[int, str]]] = None, + vm_data_stack: Optional[List[Union[str, float, bool, None]]] = None, + vm_instruction_stack: Optional[ + List[Instruction] + ] = None, # TODO: what is the correct type here? + program_counter: int = 0, + ) -> None: self._compiled_yarn = YarnProgram() + self._compiled_yarn_f = compiled_yarn_f # not ideal to store entire input file, but would need to refactor method signature otherwise self._compiled_yarn.ParseFromString(compiled_yarn_f.read()) self._names_csv = csv.DictReader(names_csv_f) self.__construct_string_lookup_table() self._enable_tracing = enable_tracing - self.visits = {key: 0 for key in self._compiled_yarn.nodes.keys()} - self.variables = {} - self.current_node = None - self._command_handlers = {} - self._line_buffer = [] - self._option_buffer = [] - self._vm_data_stack = ["Start"] - self._vm_instruction_stack = [Instruction( - opcode=Instruction.OpCode.RUN_NODE)] - self._program_counter = 0 + self.visits = ( + visits if visits else {key: 0 for key in self._compiled_yarn.nodes.keys()} + ) + self.variables = variables if variables else {} + self.current_node = current_node + self._command_handlers = command_handlers if command_handlers else {} + self._line_buffer = line_buffer if line_buffer else [] + self._option_buffer = option_buffer if option_buffer else [] + self._vm_data_stack = vm_data_stack if vm_data_stack else ["Start"] + self._vm_instruction_stack = ( + vm_instruction_stack + if vm_instruction_stack + else [Instruction(opcode=Instruction.OpCode.RUN_NODE)] + ) + self._program_counter = program_counter self.paused = True - self.finished = False + self.finished = ( + len(self._vm_instruction_stack) != 0 + and self._vm_instruction_stack[-1].opcode == Instruction.OpCode.STOP + ) + self._autostart = autostart if autostart: self.resume() + def __repr__(self): + params = [ + "enable_tracing", + "visits", + "variables", + "current_node", + "command_handlers", + "line_buffer", + "option_buffer", + ] + pairs = { + k: repr(self.__getattribute__(k)) + for k in params + if k in self.__dict__ and self.__getattribute__(k) + } + pairs.update( + { + k: repr(self.__getattribute__(f"_{k}")) + for k in params + if f"_{k}" in self.__dict__ and self.__getattribute__(f"_{k}") + } + ) + args = ", ".join(f"{k}={v}" for k, v in pairs.items()) + return ( + f"""YarnRunner(open("{self._compiled_yarn_f.name}", "rb"), open("{self._compiled_yarn_f.name.replace(".yarnc", ".csv")}"), autostart={self._autostart}""" + + f"""{", " + args if args else ""})""" + ) + def __construct_string_lookup_table(self): self.string_lookup_table = dict() @@ -44,7 +101,7 @@ def resume(self): self.paused = False self.__process_instruction() - def __lookup_string(self, string_key): + def __lookup_string(self, string_key) -> str: if string_key not in self.string_lookup_table: raise Exception( f"{string_key} is not a key in the string lookup table.") @@ -193,7 +250,7 @@ def __run_command(self, instruction): # TODO: maybe do some argument type parsing later self._command_handlers[command](*args) - def __add_option(self, instruction): + def __add_option(self, instruction) -> None: title_string_key = instruction.operands[0].string_value choice_path = instruction.operands[1].string_value From 3635f9bc8b2738907d9c9e28b44cced754bbd488 Mon Sep 17 00:00:00 2001 From: Martin Ruskov Date: Mon, 10 Jan 2022 16:31:05 +0100 Subject: [PATCH 2/8] Added test coverage for constructor and repr() --- tests/test_basic.py | 25 +++++++++-------- tests/test_conditionals.py | 29 ++++++++++++++------ tests/test_expressions.py | 21 ++++++++------ tests/test_jump.py | 56 ++++++++++++++++++++++++++++++++------ tests/test_shortcuts.py | 29 ++++++++++++++------ tests/test_variables.py | 55 +++++++++++++++++++++++++++++++------ 6 files changed, 164 insertions(+), 51 deletions(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index 7deb21c..da4a9dd 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -84,31 +84,34 @@ def test_start_node_choose2(): assert side_effect2 == "event:/event/event_name" -def test_repr(): - """Testing optional parameters of constructor and repr()""" - - "YarnRunner(open(\"examples/yarn1/basic.yarnc\", \"rb\"), open(\"examples/yarn1/basic.csv\"), autostart=True, visits={\\'choice_1\\': 0, \\'Start\\': 1}, current_node=\\'Start\\')" - +def test_init_repr(): result = repr(runner1) assert result.startswith( f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=True, visits={{'""" ) - assert ("""'Start': 1""" in result) and ("""'choice_1': 1""" in result) - assert """current_node='choice_1'""" in result - assert """command_handlers={'runACommand':""" in result + for v in ["'Start': 1", "'choice_1': 1"]: + assert v in result - """YarnRunner(open("examples/yarn1/basic.yarnc", "rb"), open("examples/yarn1/basic.csv"), autostart=True, visits={'Start': 1, 'choice_1': 0}, current_node='Start')""" + assert "current_node='choice_1'" in result + assert "command_handlers={'runACommand':" in result result = repr( YarnRunner( compiled_yarn_f1, names_csv_f1, autostart=False, + current_node="choice_1", + visits={"Start": 1, "choice_1": 3}, ) ) - # assert ("'Start': 0" in result) and ("'choice_1': 0" in result) + assert result.startswith( + f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False, visits={{'""" + ) + + for v in ["'Start': 1", "'choice_1': 3"]: + assert v in result - assert result == f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False)""" + assert "current_node='choice_1'" in result diff --git a/tests/test_conditionals.py b/tests/test_conditionals.py index b153ea6..a2fda74 100644 --- a/tests/test_conditionals.py +++ b/tests/test_conditionals.py @@ -1,14 +1,12 @@ import os from .context import YarnRunner -compiled_yarn_f1 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn1/conditionals.yarnc'), 'rb') -names_csv_f1 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn1/conditionals.csv'), 'r') -compiled_yarn_f2 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn2/conditionals.yarnc'), 'rb') -names_csv_f2 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn2/conditionals.csv'), 'r') +compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/conditionals.yarnc") +compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") +names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/conditionals.csv") +names_csv_f1 = open(names_csv_fname1, "r") +compiled_yarn_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/conditionals.yarnc"), "rb",) +names_csv_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/conditionals.csv"), "r") runner1 = YarnRunner(compiled_yarn_f1, names_csv_f1, autostart=False) runner2 = YarnRunner(compiled_yarn_f2, names_csv_f2, autostart=False) @@ -50,3 +48,18 @@ def test_conditional_traversal2(): assert not runner2.has_line() assert runner2.finished assert runner2.current_node == 'var_is_2' + + +def test_repr(): + """Testing optional parameters of constructor and repr()""" + + result = repr(runner1) + + assert result.startswith( + f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False, visits={{'""" + ) + + for v in ["'var_is_1': 0", "'var_is_2': 1", "'Start': 1"]: + assert v in result + + assert result.endswith("""}, variables={'$var': 2.0}, current_node='var_is_2')""") diff --git a/tests/test_expressions.py b/tests/test_expressions.py index 7f95faa..743142c 100644 --- a/tests/test_expressions.py +++ b/tests/test_expressions.py @@ -1,14 +1,12 @@ import os from .context import YarnRunner -compiled_yarn_f1 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn1/expressions.yarnc'), 'rb') -names_csv_f1 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn1/expressions.csv'), 'r') -compiled_yarn_f2 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn2/expressions.yarnc'), 'rb') -names_csv_f2 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn2/expressions.csv'), 'r') +compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/expressions.yarnc") +compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") +names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/expressions.csv") +names_csv_f1 = open(names_csv_fname1, "r") +compiled_yarn_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/expressions.yarnc"), "rb") +names_csv_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/expressions.csv"), "r") runner1 = YarnRunner(compiled_yarn_f1, names_csv_f1, autostart=False) runner2 = YarnRunner(compiled_yarn_f2, names_csv_f2, autostart=False) @@ -38,3 +36,10 @@ def test_expressions2(): except Exception as e: assert str( e) == "Yarn stories with interpolated inline expressions are not yet supported." + + +def test_repr(): + assert ( + repr(runner1) + == f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False, visits={{'Start': 1}}, variables={{'$name': 'Sam'}}, current_node='Start')""" + ) diff --git a/tests/test_jump.py b/tests/test_jump.py index 7ddebfe..c0da6d8 100644 --- a/tests/test_jump.py +++ b/tests/test_jump.py @@ -1,14 +1,12 @@ import os from .context import YarnRunner -compiled_yarn_f1 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn1/jump.yarnc'), 'rb') -names_csv_f1 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn1/jump.csv'), 'r') -compiled_yarn_f2 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn2/jump.yarnc'), 'rb') -names_csv_f2 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn2/jump.csv'), 'r') +compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/jump.yarnc") +compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") +names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/jump.csv") +names_csv_f1 = open(names_csv_fname1, "r") +compiled_yarn_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/jump.yarnc"), "rb") +names_csv_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/jump.csv"), "r") runner1 = YarnRunner(compiled_yarn_f1, names_csv_f1) runner2 = YarnRunner(compiled_yarn_f2, names_csv_f2) @@ -61,3 +59,45 @@ def test_jumps2(): assert not runner2.has_line() assert runner2.finished assert runner2.current_node == 'jump_complete' + + +def test_init_repr(): + result = repr(runner1) + + assert result.startswith( + f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=True, visits={{'""" + ) + + for v in [ + "'Start': 1", + "'begin_jump': 1", + "'jump_stage_2': 1", + "'jump_complete': 1", + ]: + assert v in result + + assert result.endswith("""}, current_node='jump_complete')""") + + result = repr( + YarnRunner( + compiled_yarn_f1, + names_csv_f1, + autostart=False, + current_node="jump_stage_2", + visits={"Start": 1, "begin_jump": 1, "jump_stage_2": 1, "jump_complete": 0}, + ) + ) + + assert result.startswith( + f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False, visits={{'""" + ) + + for v in [ + "'Start': 1", + "'begin_jump': 1", + "'jump_stage_2': 1", + "'jump_complete': 0", + ]: + assert v in result + + assert "current_node='jump_stage_2'" in result diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py index fee2caa..309fa2c 100644 --- a/tests/test_shortcuts.py +++ b/tests/test_shortcuts.py @@ -1,14 +1,12 @@ import os from .context import YarnRunner -compiled_yarn_f1 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn1/shortcuts.yarnc'), 'rb') -names_csv_f1 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn1/shortcuts.csv'), 'r') -compiled_yarn_f2 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn2/shortcuts.yarnc'), 'rb') -names_csv_f2 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn2/shortcuts.csv'), 'r') +compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/shortcuts.yarnc") +compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") +names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/shortcuts.csv") +names_csv_f1 = open(names_csv_fname1, "r") +compiled_yarn_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/shortcuts.yarnc"), "rb") +names_csv_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/shortcuts.csv"), "r") runner1 = YarnRunner(compiled_yarn_f1, names_csv_f1) runner2 = YarnRunner(compiled_yarn_f2, names_csv_f2) @@ -66,3 +64,18 @@ def test_shortcuts(): assert not runner2.has_line() assert runner2.finished assert runner2.current_node == 'Start' + + +def test_init_repr(): + assert ( + repr(runner1) + == """YarnRunner(open("/home/mapto/work2/YarnRunner-Python/tests/../examples/yarn1/shortcuts.yarnc", "rb"), open("/home/mapto/work2/YarnRunner-Python/tests/../examples/yarn1/shortcuts.csv"), autostart=True, visits={'Start': 1}, current_node='Start')""" + ) + + result = repr( + YarnRunner(compiled_yarn_f1, names_csv_f1, autostart=False, visits={"Start": 5}) + ) + assert ( + result + == """YarnRunner(open("/home/mapto/work2/YarnRunner-Python/tests/../examples/yarn1/shortcuts.yarnc", "rb"), open("/home/mapto/work2/YarnRunner-Python/tests/../examples/yarn1/shortcuts.csv"), autostart=False, visits={'Start': 5})""" + ) diff --git a/tests/test_variables.py b/tests/test_variables.py index 89b9472..f9de003 100644 --- a/tests/test_variables.py +++ b/tests/test_variables.py @@ -1,14 +1,12 @@ import os from .context import YarnRunner -compiled_yarn_f1 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn1/variables.yarnc'), 'rb') -names_csv_f1 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn1/variables.csv'), 'r') -compiled_yarn_f2 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn2/variables.yarnc'), 'rb') -names_csv_f2 = open(os.path.join(os.path.dirname( - __file__), '../examples/yarn2/variables.csv'), 'r') +compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/variables.yarnc") +compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") +names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/variables.csv") +names_csv_f1 = open(names_csv_fname1, "r") +compiled_yarn_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/variables.yarnc"), "rb") +names_csv_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/variables.csv"), "r") runner1 = YarnRunner(compiled_yarn_f1, names_csv_f1) runner2 = YarnRunner(compiled_yarn_f2, names_csv_f2) @@ -29,3 +27,44 @@ def test_variables2(): assert runner2.variables["$value_string"] == "string" assert runner2.variables["$value_float"] == 1.25 assert runner2.variables["$value_bool"] == True + + +def test_init_repr(): + result = repr(runner1) + + assert result.startswith( + f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=True, visits={{'Start': 1}}, variables={{'""" + ) + + for v in [ + "'$value_string': 'string'", + "'$value_float': 1.25", + "'$value_bool': True", + "'$value_null': None", + ]: + assert v in result + + assert result.endswith("""}, current_node='Start')""") + + runner3 = YarnRunner( + compiled_yarn_f1, + names_csv_f1, + autostart=False, + variables={ + "$value_string": "gnirts", + "$value_float": 2.75, + "$value_bool": False, + }, + ) + result = repr(runner3) + + assert result.startswith( + f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False, variables={{'""" + ) + + for v in [ + "'$value_string': 'gnirts'", + "'$value_float': 2.75", + "'$value_bool': False", + ]: + assert v in result From 6232731b0090084afab96d7f0ccd2a9441c70034 Mon Sep 17 00:00:00 2001 From: Martin Ruskov Date: Tue, 11 Jan 2022 07:15:08 +0100 Subject: [PATCH 3/8] Improved repr for command_handlers; improved tests --- tests/test_basic.py | 6 ++++-- tests/test_conditionals.py | 3 ++- tests/test_jump.py | 6 ++++-- tests/test_shortcuts.py | 4 ++-- tests/test_variables.py | 6 ++++-- yarnrunner_python/runner.py | 9 ++++++++- 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index da4a9dd..39f79c2 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -91,8 +91,9 @@ def test_init_repr(): f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=True, visits={{'""" ) + pos = result.index("visits={") for v in ["'Start': 1", "'choice_1': 1"]: - assert v in result + assert v in result[pos:] assert "current_node='choice_1'" in result assert "command_handlers={'runACommand':" in result @@ -111,7 +112,8 @@ def test_init_repr(): f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False, visits={{'""" ) + pos = result.index("visits={") for v in ["'Start': 1", "'choice_1': 3"]: - assert v in result + assert v in result[pos:] assert "current_node='choice_1'" in result diff --git a/tests/test_conditionals.py b/tests/test_conditionals.py index a2fda74..2d0a78c 100644 --- a/tests/test_conditionals.py +++ b/tests/test_conditionals.py @@ -59,7 +59,8 @@ def test_repr(): f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False, visits={{'""" ) + pos = result.index("visits={") for v in ["'var_is_1': 0", "'var_is_2': 1", "'Start': 1"]: - assert v in result + assert v in result[pos:] assert result.endswith("""}, variables={'$var': 2.0}, current_node='var_is_2')""") diff --git a/tests/test_jump.py b/tests/test_jump.py index c0da6d8..028c0b2 100644 --- a/tests/test_jump.py +++ b/tests/test_jump.py @@ -68,13 +68,14 @@ def test_init_repr(): f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=True, visits={{'""" ) + pos = result.index("visits={") for v in [ "'Start': 1", "'begin_jump': 1", "'jump_stage_2': 1", "'jump_complete': 1", ]: - assert v in result + assert v in result[pos:] assert result.endswith("""}, current_node='jump_complete')""") @@ -92,12 +93,13 @@ def test_init_repr(): f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False, visits={{'""" ) + pos = result.index("visits={") for v in [ "'Start': 1", "'begin_jump': 1", "'jump_stage_2': 1", "'jump_complete': 0", ]: - assert v in result + assert v in result[pos:] assert "current_node='jump_stage_2'" in result diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py index 309fa2c..1980477 100644 --- a/tests/test_shortcuts.py +++ b/tests/test_shortcuts.py @@ -69,7 +69,7 @@ def test_shortcuts(): def test_init_repr(): assert ( repr(runner1) - == """YarnRunner(open("/home/mapto/work2/YarnRunner-Python/tests/../examples/yarn1/shortcuts.yarnc", "rb"), open("/home/mapto/work2/YarnRunner-Python/tests/../examples/yarn1/shortcuts.csv"), autostart=True, visits={'Start': 1}, current_node='Start')""" + == f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=True, visits={{'Start': 1}}, current_node='Start')""" ) result = repr( @@ -77,5 +77,5 @@ def test_init_repr(): ) assert ( result - == """YarnRunner(open("/home/mapto/work2/YarnRunner-Python/tests/../examples/yarn1/shortcuts.yarnc", "rb"), open("/home/mapto/work2/YarnRunner-Python/tests/../examples/yarn1/shortcuts.csv"), autostart=False, visits={'Start': 5})""" + == f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False, visits={{'Start': 5}})""" ) diff --git a/tests/test_variables.py b/tests/test_variables.py index f9de003..9e75829 100644 --- a/tests/test_variables.py +++ b/tests/test_variables.py @@ -36,13 +36,14 @@ def test_init_repr(): f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=True, visits={{'Start': 1}}, variables={{'""" ) + pos = result.index("variables={") for v in [ "'$value_string': 'string'", "'$value_float': 1.25", "'$value_bool': True", "'$value_null': None", ]: - assert v in result + assert v in result[pos:] assert result.endswith("""}, current_node='Start')""") @@ -62,9 +63,10 @@ def test_init_repr(): f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False, variables={{'""" ) + pos = result.index("variables={") for v in [ "'$value_string': 'gnirts'", "'$value_float': 2.75", "'$value_bool': False", ]: - assert v in result + assert v in result[pos:] diff --git a/yarnrunner_python/runner.py b/yarnrunner_python/runner.py index 64e6db9..05bce51 100644 --- a/yarnrunner_python/runner.py +++ b/yarnrunner_python/runner.py @@ -64,10 +64,10 @@ def __repr__(self): "visits", "variables", "current_node", - "command_handlers", "line_buffer", "option_buffer", ] + pairs = { k: repr(self.__getattribute__(k)) for k in params @@ -80,7 +80,14 @@ def __repr__(self): if f"_{k}" in self.__dict__ and self.__getattribute__(f"_{k}") } ) + + # self._command_handlers done separately because values are symbols (function names that might be absent from local context) + comms = ", ".join(f"'{k}': {v.__name__}" for k, v in self._command_handlers.items()) + if comms: + pairs.update({"_command_handlers": f"{{{comms}}}"}) + args = ", ".join(f"{k}={v}" for k, v in pairs.items()) + return ( f"""YarnRunner(open("{self._compiled_yarn_f.name}", "rb"), open("{self._compiled_yarn_f.name.replace(".yarnc", ".csv")}"), autostart={self._autostart}""" + f"""{", " + args if args else ""})""" From 63114bbfea5d82f8485a9ab0bc0d2eeb3f43cb82 Mon Sep 17 00:00:00 2001 From: Martin Ruskov Date: Wed, 19 Jan 2022 13:50:41 +0100 Subject: [PATCH 4/8] files alternative to jsons --- tests/test_shortcuts.py | 68 +++++++++++++++++++++++++++++-------- tests/test_variables.py | 40 ---------------------- yarnrunner_python/runner.py | 45 ++++++++++++++++++------ 3 files changed, 89 insertions(+), 64 deletions(-) diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py index 309fa2c..eb58361 100644 --- a/tests/test_shortcuts.py +++ b/tests/test_shortcuts.py @@ -39,6 +39,60 @@ def test_shortcuts1(): assert runner1.current_node == 'Start' +def test_shortcuts2(): + compiled_yarn_f3 = open(compiled_yarn_fname1, "rb") + names_csv_f3 = open(names_csv_fname1, "r") + runner3 = YarnRunner(compiled_yarn_f3, names_csv_f3) + assert "This is a test of shortcut functionality." == runner3.get_line() + assert not runner3.has_line() + assert not runner3.finished + runner3.choose(1) + + assert "Option 2 selected." == runner3.get_line() + assert runner3.has_line() + assert "This is the last line." == runner3.get_line() + assert not runner3.has_line() + assert runner3.finished + assert runner3.current_node == 'Start' + + +def test_shortcuts3(): + compiled_yarn_f3 = open(compiled_yarn_fname1, "rb") + names_csv_f3 = open(names_csv_fname1, "r") + runner3 = YarnRunner(compiled_yarn_f3, names_csv_f3) + assert "This is a test of shortcut functionality." == runner3.get_line() + assert not runner3.has_line() + assert not runner3.finished + + choices = runner3.get_choices() + + assert len(choices) == 4 + assert choices[0]["text"] == "Option 1" + assert choices[1]["text"] == "Option 2" + assert choices[2]["text"] == "Option 3" + assert choices[3]["text"] == "Option 4" + + frozen = repr(runner3) + runner4 = eval(frozen) + + choices = runner4.get_choices() + + assert len(choices) == 4 + assert choices[0]["text"] == "Option 1" + assert choices[1]["text"] == "Option 2" + assert choices[2]["text"] == "Option 3" + assert choices[3]["text"] == "Option 4" + + runner4.choose(0) + + assert "Option 1 selected." == runner4.get_line() + assert runner4.has_line() + assert "This is the last line." == runner4.get_line() + assert not runner4.has_line() + assert runner4.finished + assert runner4.current_node == 'Start' + + def test_start_node_text2(): assert "This is a test of shortcut functionality." == runner2.get_line() assert not runner2.has_line() @@ -65,17 +119,3 @@ def test_shortcuts(): assert runner2.finished assert runner2.current_node == 'Start' - -def test_init_repr(): - assert ( - repr(runner1) - == """YarnRunner(open("/home/mapto/work2/YarnRunner-Python/tests/../examples/yarn1/shortcuts.yarnc", "rb"), open("/home/mapto/work2/YarnRunner-Python/tests/../examples/yarn1/shortcuts.csv"), autostart=True, visits={'Start': 1}, current_node='Start')""" - ) - - result = repr( - YarnRunner(compiled_yarn_f1, names_csv_f1, autostart=False, visits={"Start": 5}) - ) - assert ( - result - == """YarnRunner(open("/home/mapto/work2/YarnRunner-Python/tests/../examples/yarn1/shortcuts.yarnc", "rb"), open("/home/mapto/work2/YarnRunner-Python/tests/../examples/yarn1/shortcuts.csv"), autostart=False, visits={'Start': 5})""" - ) diff --git a/tests/test_variables.py b/tests/test_variables.py index f9de003..69e4093 100644 --- a/tests/test_variables.py +++ b/tests/test_variables.py @@ -28,43 +28,3 @@ def test_variables2(): assert runner2.variables["$value_float"] == 1.25 assert runner2.variables["$value_bool"] == True - -def test_init_repr(): - result = repr(runner1) - - assert result.startswith( - f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=True, visits={{'Start': 1}}, variables={{'""" - ) - - for v in [ - "'$value_string': 'string'", - "'$value_float': 1.25", - "'$value_bool': True", - "'$value_null': None", - ]: - assert v in result - - assert result.endswith("""}, current_node='Start')""") - - runner3 = YarnRunner( - compiled_yarn_f1, - names_csv_f1, - autostart=False, - variables={ - "$value_string": "gnirts", - "$value_float": 2.75, - "$value_bool": False, - }, - ) - result = repr(runner3) - - assert result.startswith( - f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False, variables={{'""" - ) - - for v in [ - "'$value_string': 'gnirts'", - "'$value_float': 2.75", - "'$value_bool': False", - ]: - assert v in result diff --git a/yarnrunner_python/runner.py b/yarnrunner_python/runner.py index 64e6db9..c0a1704 100644 --- a/yarnrunner_python/runner.py +++ b/yarnrunner_python/runner.py @@ -3,6 +3,7 @@ import csv import re from warnings import warn +from google.protobuf import json_format from .yarn_spinner_pb2 import Program as YarnProgram, Instruction # type: ignore from .vm_std_lib import functions as std_lib_functions @@ -10,8 +11,8 @@ class YarnRunner(object): def __init__( self, - compiled_yarn_f, - names_csv_f, + compiled_yarn_f = None, + names_csv_f = None, autostart=True, enable_tracing=False, visits: Optional[Dict[str, int]] = None, @@ -25,12 +26,25 @@ def __init__( List[Instruction] ] = None, # TODO: what is the correct type here? program_counter: int = 0, + compiled_yarn = None, + string_lookup_table = None ) -> None: + assert bool(compiled_yarn) != bool(compiled_yarn_f) self._compiled_yarn = YarnProgram() - self._compiled_yarn_f = compiled_yarn_f # not ideal to store entire input file, but would need to refactor method signature otherwise - self._compiled_yarn.ParseFromString(compiled_yarn_f.read()) - self._names_csv = csv.DictReader(names_csv_f) - self.__construct_string_lookup_table() + if compiled_yarn_f: + self._compiled_yarn_f = compiled_yarn_f # not ideal to store entire input file, but would need to refactor method signature otherwise + self._compiled_yarn.ParseFromString(compiled_yarn_f.read()) + else: + json_format.ParseDict(compiled_yarn, self._compiled_yarn) + + # assert bool(string_lookup_table) != bool(names_csv_f) + if names_csv_f: + self._names_csv = csv.DictReader(names_csv_f) + self.__construct_string_lookup_table() + elif string_lookup_table: + self.string_lookup_table = string_lookup_table + else: + self.string_lookup_table = {} self._enable_tracing = enable_tracing self.visits = ( @@ -43,7 +57,7 @@ def __init__( self._option_buffer = option_buffer if option_buffer else [] self._vm_data_stack = vm_data_stack if vm_data_stack else ["Start"] self._vm_instruction_stack = ( - vm_instruction_stack + [json_format.Parse(i, Instruction()) for i in vm_instruction_stack] if vm_instruction_stack else [Instruction(opcode=Instruction.OpCode.RUN_NODE)] ) @@ -67,6 +81,7 @@ def __repr__(self): "command_handlers", "line_buffer", "option_buffer", + "vm_data_stack", ] pairs = { k: repr(self.__getattribute__(k)) @@ -81,14 +96,24 @@ def __repr__(self): } ) args = ", ".join(f"{k}={v}" for k, v in pairs.items()) + + ins = "" + if self._vm_instruction_stack: + ins = f"""vm_instruction_stack={[json_format.MessageToJson(i) for i in self._vm_instruction_stack]}""" + + yarn = f"compiled_yarn={json_format.MessageToJson(self._compiled_yarn)}" + + lookup_table = f"string_lookup_table={self.string_lookup_table}" + return ( - f"""YarnRunner(open("{self._compiled_yarn_f.name}", "rb"), open("{self._compiled_yarn_f.name.replace(".yarnc", ".csv")}"), autostart={self._autostart}""" - + f"""{", " + args if args else ""})""" + f"""YarnRunner({yarn}, {lookup_table}, autostart={self._autostart}""" + + f"""{", " + args if args else ""}""" + + f"""{", " + ins if ins else ""}""" + + ")" ) def __construct_string_lookup_table(self): self.string_lookup_table = dict() - for entry in self._names_csv: self.string_lookup_table[entry["id"]] = entry From a5e2c4cc4aee45189cd30519f2daa1d2874eeeba Mon Sep 17 00:00:00 2001 From: Martin Ruskov Date: Wed, 19 Jan 2022 14:09:30 +0100 Subject: [PATCH 5/8] removed .orig files included by mistake --- tests/test_shortcuts.py.orig | 138 ----------------------------------- tests/test_variables.py.orig | 75 ------------------- 2 files changed, 213 deletions(-) delete mode 100644 tests/test_shortcuts.py.orig delete mode 100644 tests/test_variables.py.orig diff --git a/tests/test_shortcuts.py.orig b/tests/test_shortcuts.py.orig deleted file mode 100644 index 1dd757d..0000000 --- a/tests/test_shortcuts.py.orig +++ /dev/null @@ -1,138 +0,0 @@ -import os -from .context import YarnRunner - -compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/shortcuts.yarnc") -compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") -names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/shortcuts.csv") -names_csv_f1 = open(names_csv_fname1, "r") -compiled_yarn_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/shortcuts.yarnc"), "rb") -names_csv_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/shortcuts.csv"), "r") - -runner1 = YarnRunner(compiled_yarn_f1, names_csv_f1) -runner2 = YarnRunner(compiled_yarn_f2, names_csv_f2) - - -def test_start_node_text1(): - assert "This is a test of shortcut functionality." == runner1.get_line() - assert not runner1.has_line() - assert not runner1.finished - - -def test_start_node_choices1(): - choices = runner1.get_choices() - - assert len(choices) == 4 - assert choices[0]["text"] == "Option 1" - assert choices[1]["text"] == "Option 2" - assert choices[2]["text"] == "Option 3" - assert choices[3]["text"] == "Option 4" - - -def test_shortcuts1(): - runner1.choose(0) - - assert "Option 1 selected." == runner1.get_line() - assert runner1.has_line() - assert "This is the last line." == runner1.get_line() - assert not runner1.has_line() - assert runner1.finished - assert runner1.current_node == 'Start' - - -def test_shortcuts2(): - compiled_yarn_f3 = open(compiled_yarn_fname1, "rb") - names_csv_f3 = open(names_csv_fname1, "r") - runner3 = YarnRunner(compiled_yarn_f3, names_csv_f3) - assert "This is a test of shortcut functionality." == runner3.get_line() - assert not runner3.has_line() - assert not runner3.finished - runner3.choose(1) - - assert "Option 2 selected." == runner3.get_line() - assert runner3.has_line() - assert "This is the last line." == runner3.get_line() - assert not runner3.has_line() - assert runner3.finished - assert runner3.current_node == 'Start' - - -def test_shortcuts3(): - compiled_yarn_f3 = open(compiled_yarn_fname1, "rb") - names_csv_f3 = open(names_csv_fname1, "r") - runner3 = YarnRunner(compiled_yarn_f3, names_csv_f3) - assert "This is a test of shortcut functionality." == runner3.get_line() - assert not runner3.has_line() - assert not runner3.finished - - choices = runner3.get_choices() - - assert len(choices) == 4 - assert choices[0]["text"] == "Option 1" - assert choices[1]["text"] == "Option 2" - assert choices[2]["text"] == "Option 3" - assert choices[3]["text"] == "Option 4" - - frozen = repr(runner3) - runner4 = eval(frozen) - - choices = runner4.get_choices() - - assert len(choices) == 4 - assert choices[0]["text"] == "Option 1" - assert choices[1]["text"] == "Option 2" - assert choices[2]["text"] == "Option 3" - assert choices[3]["text"] == "Option 4" - - runner4.choose(0) - - assert "Option 1 selected." == runner4.get_line() - assert runner4.has_line() - assert "This is the last line." == runner4.get_line() - assert not runner4.has_line() - assert runner4.finished - assert runner4.current_node == 'Start' - - -def test_start_node_text2(): - assert "This is a test of shortcut functionality." == runner2.get_line() - assert not runner2.has_line() - assert not runner2.finished - - -def test_start_node_choices2(): - choices = runner2.get_choices() - - assert len(choices) == 4 - assert choices[0]["text"] == "Option 1" - assert choices[1]["text"] == "Option 2" - assert choices[2]["text"] == "Option 3" - assert choices[3]["text"] == "Option 4" - - -def test_shortcuts(): - runner2.choose(0) - - assert "Option 1 selected." == runner2.get_line() - assert runner2.has_line() - assert "This is the last line." == runner2.get_line() - assert not runner2.has_line() - assert runner2.finished - assert runner2.current_node == 'Start' - -<<<<<<< HEAD -======= - -def test_init_repr(): - assert ( - repr(runner1) - == f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=True, visits={{'Start': 1}}, current_node='Start')""" - ) - - result = repr( - YarnRunner(compiled_yarn_f1, names_csv_f1, autostart=False, visits={"Start": 5}) - ) - assert ( - result - == f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False, visits={{'Start': 5}})""" - ) ->>>>>>> 6232731b0090084afab96d7f0ccd2a9441c70034 diff --git a/tests/test_variables.py.orig b/tests/test_variables.py.orig deleted file mode 100644 index 0c77af6..0000000 --- a/tests/test_variables.py.orig +++ /dev/null @@ -1,75 +0,0 @@ -import os -from .context import YarnRunner - -compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/variables.yarnc") -compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") -names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/variables.csv") -names_csv_f1 = open(names_csv_fname1, "r") -compiled_yarn_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/variables.yarnc"), "rb") -names_csv_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/variables.csv"), "r") - -runner1 = YarnRunner(compiled_yarn_f1, names_csv_f1) -runner2 = YarnRunner(compiled_yarn_f2, names_csv_f2) - - -def test_variables1(): - assert not runner1.has_line() - assert runner1.finished - assert runner1.variables["$value_string"] == "string" - assert runner1.variables["$value_float"] == 1.25 - assert runner1.variables["$value_bool"] == True - assert runner1.variables["$value_null"] is None - - -def test_variables2(): - assert not runner2.has_line() - assert runner2.finished - assert runner2.variables["$value_string"] == "string" - assert runner2.variables["$value_float"] == 1.25 - assert runner2.variables["$value_bool"] == True - -<<<<<<< HEAD -======= - -def test_init_repr(): - result = repr(runner1) - - assert result.startswith( - f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=True, visits={{'Start': 1}}, variables={{'""" - ) - - pos = result.index("variables={") - for v in [ - "'$value_string': 'string'", - "'$value_float': 1.25", - "'$value_bool': True", - "'$value_null': None", - ]: - assert v in result[pos:] - - assert result.endswith("""}, current_node='Start')""") - - runner3 = YarnRunner( - compiled_yarn_f1, - names_csv_f1, - autostart=False, - variables={ - "$value_string": "gnirts", - "$value_float": 2.75, - "$value_bool": False, - }, - ) - result = repr(runner3) - - assert result.startswith( - f"""YarnRunner(open("{compiled_yarn_fname1}", "rb"), open("{names_csv_fname1}"), autostart=False, variables={{'""" - ) - - pos = result.index("variables={") - for v in [ - "'$value_string': 'gnirts'", - "'$value_float': 2.75", - "'$value_bool': False", - ]: - assert v in result[pos:] ->>>>>>> 6232731b0090084afab96d7f0ccd2a9441c70034 From c500613fceee589332fd790ec4ee7e5abd5832e6 Mon Sep 17 00:00:00 2001 From: Martin Ruskov Date: Wed, 19 Jan 2022 14:44:12 +0100 Subject: [PATCH 6/8] Resolved conflicts with original --- README.md | 1 + examples/yarn2/basic.csv | 8 +-- examples/yarn2/basic.yarn | 2 +- examples/yarn2/basic.yarnc | Bin 690 -> 719 bytes .../yarn2/experimental-newlines-metadata.csv | 1 + examples/yarn2/experimental-newlines.csv | 3 + examples/yarn2/experimental-newlines.yarn | 6 ++ examples/yarn2/experimental-newlines.yarnc | Bin 0 -> 242 bytes tests/test_basic.py | 15 +++-- tests/test_conditionals.py | 14 +++-- tests/test_experimental_newlines.py | 34 ++++++++++ tests/test_expressions.py | 14 +++-- tests/test_jump.py | 15 ++--- tests/test_shortcuts.py | 16 +++-- tests/test_variables.py | 15 ++--- yarnrunner_python/runner.py | 58 +++++++++++++++++- 16 files changed, 157 insertions(+), 45 deletions(-) create mode 100644 examples/yarn2/experimental-newlines-metadata.csv create mode 100644 examples/yarn2/experimental-newlines.csv create mode 100644 examples/yarn2/experimental-newlines.yarn create mode 100644 examples/yarn2/experimental-newlines.yarnc create mode 100644 tests/test_experimental_newlines.py diff --git a/README.md b/README.md index 71d9492..3a07c54 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ As of version v0.0.2, all Yarn Spinner opcodes are currently implemented, as wel - An appropriate replacement for the distinction Yarn makes between Functions and Coroutines in Unity (to allow users to register blocking command handlers via this Python runner independent of Unity) - Complete implementation of YS2's type system, specifically when performing operations on mismatching types - This may be challenging, due to Python being a dynamically typed language +- The [`<>` built-in command](https://docs.yarnspinner.dev/getting-started/writing-in-yarn/commands#wait) ## Development diff --git a/examples/yarn2/basic.csv b/examples/yarn2/basic.csv index fe0758e..1f3d6c3 100644 --- a/examples/yarn2/basic.csv +++ b/examples/yarn2/basic.csv @@ -1,5 +1,5 @@ id,text,file,node,lineNumber -line:/Users/sweaver/Git/YarnRunner-Python/examples/basic.yarn-Start-0,This is the first node.,/Users/sweaver/Git/YarnRunner-Python/examples/basic.yarn,Start,3 -line:/Users/sweaver/Git/YarnRunner-Python/examples/basic.yarn-Start-1,Choice 1,/Users/sweaver/Git/YarnRunner-Python/examples/basic.yarn,Start,5 -line:/Users/sweaver/Git/YarnRunner-Python/examples/basic.yarn-Start-2,Choice 2,/Users/sweaver/Git/YarnRunner-Python/examples/basic.yarn,Start,7 -line:/Users/sweaver/Git/YarnRunner-Python/examples/basic.yarn-choice_1-3,"Here is the node visited as a **result** of the __first__ ""choice"", with a comma.",/Users/sweaver/Git/YarnRunner-Python/examples/basic.yarn,choice_1,13 +line:/Users/sweaver/Git/YarnRunner-Python/examples/yarn2/basic.yarn-Start-0,This is the first node.,/Users/sweaver/Git/YarnRunner-Python/examples/yarn2/basic.yarn,Start,3 +line:/Users/sweaver/Git/YarnRunner-Python/examples/yarn2/basic.yarn-Start-1,Choice 1,/Users/sweaver/Git/YarnRunner-Python/examples/yarn2/basic.yarn,Start,5 +line:/Users/sweaver/Git/YarnRunner-Python/examples/yarn2/basic.yarn-Start-2,Choice 2,/Users/sweaver/Git/YarnRunner-Python/examples/yarn2/basic.yarn,Start,7 +line:/Users/sweaver/Git/YarnRunner-Python/examples/yarn2/basic.yarn-choice_1-3,"Here is the node visited as a **result** of the __first__ ""choice"", with a comma.",/Users/sweaver/Git/YarnRunner-Python/examples/yarn2/basic.yarn,choice_1,13 diff --git a/examples/yarn2/basic.yarn b/examples/yarn2/basic.yarn index 560ab4a..c9d9b5d 100644 --- a/examples/yarn2/basic.yarn +++ b/examples/yarn2/basic.yarn @@ -12,6 +12,6 @@ title: choice_1 Here is the node visited as a **result** of the __first__ "choice", with a comma. -<> +<> === \ No newline at end of file diff --git a/examples/yarn2/basic.yarnc b/examples/yarn2/basic.yarnc index bf8451e7efffc2789b6f51f30363acb1a5447ad2..14ea476fd362459f3042e0ac0b283e85a8bcdd6d 100644 GIT binary patch delta 206 zcmdnQdY)BAsE37%HMk_Ps6?m<&Whk*67uEpo>*YaTA5gsXEf1In!SR91yyX3HHO$^ zF~%2)dl|Vnk~8u%lT+gjg*G5_qJhQ+aQVTFogBy{=V8miEM&xGP*j@d=$xOMo0ylP zkXV#%s9>a^R9ujloLZ~^64Ea&NKMX6%u!HF&PXgOR#FgRm1O_|AtnxfDHbj!9}@sJ CoHu6x delta 204 zcmX@lx`|ar=od2=Yj8Mg;$@pAx6C)Q#az=h;a%#Mx&{AYhDA0H>E)S6Lle3s)oh>++ zg><>Ji%Rnxo%3^Z6Z29O5{uFe6^s;8%Tn`7tn|SYgpAKi%uQ8*If03TUy6l`$;Si$ DsNyu9 diff --git a/examples/yarn2/experimental-newlines-metadata.csv b/examples/yarn2/experimental-newlines-metadata.csv new file mode 100644 index 0000000..85e1f84 --- /dev/null +++ b/examples/yarn2/experimental-newlines-metadata.csv @@ -0,0 +1 @@ +id,node,lineNumber,tags diff --git a/examples/yarn2/experimental-newlines.csv b/examples/yarn2/experimental-newlines.csv new file mode 100644 index 0000000..d792e7d --- /dev/null +++ b/examples/yarn2/experimental-newlines.csv @@ -0,0 +1,3 @@ +id,text,file,node,lineNumber +line:/Users/sweaver/Git/YarnRunner-Python/examples/yarn2/experimental-newlines.yarn-Start-0,Here's the first line.,/Users/sweaver/Git/YarnRunner-Python/examples/yarn2/experimental-newlines.yarn,Start,3 +line:/Users/sweaver/Git/YarnRunner-Python/examples/yarn2/experimental-newlines.yarn-Start-1,And the second line.,/Users/sweaver/Git/YarnRunner-Python/examples/yarn2/experimental-newlines.yarn,Start,5 diff --git a/examples/yarn2/experimental-newlines.yarn b/examples/yarn2/experimental-newlines.yarn new file mode 100644 index 0000000..a032288 --- /dev/null +++ b/examples/yarn2/experimental-newlines.yarn @@ -0,0 +1,6 @@ +title: Start +--- +Here's the first line. + +And the second line. +=== \ No newline at end of file diff --git a/examples/yarn2/experimental-newlines.yarnc b/examples/yarn2/experimental-newlines.yarnc new file mode 100644 index 0000000000000000000000000000000000000000..0a1bc46ce5f25abbb8e14a04e8dec38670270e54 GIT binary patch literal 242 zcmWf7&&b6ZT#{H+BJ>o_%HUuUisg#V$;?Z&(hn_8Eh^S8E>BG?OD)oO&n(f8Of1R^ zD$UDFEz%9BEXl~v(@(8P%q_@CE!M9DN*MvU1*t`uxv6<2i8;D?spTMpiuFKpx?rnx b4TM-_8Gt~D<~A9^Y+~Zzmtx^!@-YAa&k0XA literal 0 HcmV?d00001 diff --git a/tests/test_basic.py b/tests/test_basic.py index 6957bc1..5266f6f 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,10 +1,10 @@ import os from .context import YarnRunner -compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/basic.yarnc") -compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") -names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/basic.csv") -names_csv_f1 = open(names_csv_fname1, "r") +compiled_yarn_f1 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn1/basic.yarnc'), 'rb') +names_csv_f1 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn1/basic.csv'), 'r') compiled_yarn_f2 = open(os.path.join(os.path.dirname( __file__), '../examples/yarn2/basic.yarnc'), 'rb') names_csv_f2 = open(os.path.join(os.path.dirname( @@ -55,7 +55,8 @@ def run_a_command1(arg1, arg2, arg3): def run_a_command2(arg1, arg2, arg3): global side_effect2 - side_effect2 = arg3 + side_effect2 = (arg1, arg2, arg3) + return arg3 runner1.add_command_handler("runACommand", run_a_command1) @@ -77,8 +78,10 @@ def test_start_node_choose2(): runner2.choose(0) assert "Here is the node visited as a **result** of the __first__ \"choice\", with a comma." == runner2.get_line() + assert runner2.has_line() + assert "spaces and /special &chars" == runner2.get_line() assert not runner2.has_line() assert runner2.finished # ensure the command has run - assert side_effect2 == "event:/event/event_name" + assert side_effect2 == ("arg1", "2", "spaces and /special &chars") diff --git a/tests/test_conditionals.py b/tests/test_conditionals.py index f040454..b153ea6 100644 --- a/tests/test_conditionals.py +++ b/tests/test_conditionals.py @@ -1,12 +1,14 @@ import os from .context import YarnRunner -compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/conditionals.yarnc") -compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") -names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/conditionals.csv") -names_csv_f1 = open(names_csv_fname1, "r") -compiled_yarn_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/conditionals.yarnc"), "rb",) -names_csv_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/conditionals.csv"), "r") +compiled_yarn_f1 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn1/conditionals.yarnc'), 'rb') +names_csv_f1 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn1/conditionals.csv'), 'r') +compiled_yarn_f2 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn2/conditionals.yarnc'), 'rb') +names_csv_f2 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn2/conditionals.csv'), 'r') runner1 = YarnRunner(compiled_yarn_f1, names_csv_f1, autostart=False) runner2 = YarnRunner(compiled_yarn_f2, names_csv_f2, autostart=False) diff --git a/tests/test_experimental_newlines.py b/tests/test_experimental_newlines.py new file mode 100644 index 0000000..901c021 --- /dev/null +++ b/tests/test_experimental_newlines.py @@ -0,0 +1,34 @@ +import os +from .context import YarnRunner + +compiled_yarn_f = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn2/experimental-newlines.yarnc'), 'rb') +names_csv_f = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn2/experimental-newlines.csv'), 'r') + +runner_normal = YarnRunner(compiled_yarn_f, names_csv_f) + +# reset file position +compiled_yarn_f.seek(0, 0) +names_csv_f.seek(0, 0) + +runner_experimental = YarnRunner( + compiled_yarn_f, names_csv_f, experimental_newlines=True) + + +def test_normal_newlines(): + assert "Here's the first line." == runner_normal.get_line() + assert runner_normal.has_line() + assert "And the second line." == runner_normal.get_line() + assert not runner_normal.has_line() + assert runner_normal.finished + + +def test_experimental_newlines(): + assert "Here's the first line." == runner_experimental.get_line() + assert runner_experimental.has_line() + assert "" == runner_experimental.get_line() + assert runner_experimental.has_line() + assert "And the second line." == runner_experimental.get_line() + assert not runner_experimental.has_line() + assert runner_experimental.finished diff --git a/tests/test_expressions.py b/tests/test_expressions.py index 4e83d39..7f95faa 100644 --- a/tests/test_expressions.py +++ b/tests/test_expressions.py @@ -1,12 +1,14 @@ import os from .context import YarnRunner -compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/expressions.yarnc") -compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") -names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/expressions.csv") -names_csv_f1 = open(names_csv_fname1, "r") -compiled_yarn_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/expressions.yarnc"), "rb") -names_csv_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/expressions.csv"), "r") +compiled_yarn_f1 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn1/expressions.yarnc'), 'rb') +names_csv_f1 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn1/expressions.csv'), 'r') +compiled_yarn_f2 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn2/expressions.yarnc'), 'rb') +names_csv_f2 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn2/expressions.csv'), 'r') runner1 = YarnRunner(compiled_yarn_f1, names_csv_f1, autostart=False) runner2 = YarnRunner(compiled_yarn_f2, names_csv_f2, autostart=False) diff --git a/tests/test_jump.py b/tests/test_jump.py index c34adb7..7ddebfe 100644 --- a/tests/test_jump.py +++ b/tests/test_jump.py @@ -1,12 +1,14 @@ import os from .context import YarnRunner -compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/jump.yarnc") -compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") -names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/jump.csv") -names_csv_f1 = open(names_csv_fname1, "r") -compiled_yarn_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/jump.yarnc"), "rb") -names_csv_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/jump.csv"), "r") +compiled_yarn_f1 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn1/jump.yarnc'), 'rb') +names_csv_f1 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn1/jump.csv'), 'r') +compiled_yarn_f2 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn2/jump.yarnc'), 'rb') +names_csv_f2 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn2/jump.csv'), 'r') runner1 = YarnRunner(compiled_yarn_f1, names_csv_f1) runner2 = YarnRunner(compiled_yarn_f2, names_csv_f2) @@ -59,4 +61,3 @@ def test_jumps2(): assert not runner2.has_line() assert runner2.finished assert runner2.current_node == 'jump_complete' - diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py index 764eff8..f2607b1 100644 --- a/tests/test_shortcuts.py +++ b/tests/test_shortcuts.py @@ -1,12 +1,16 @@ import os from .context import YarnRunner -compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/shortcuts.yarnc") -compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") -names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/shortcuts.csv") -names_csv_f1 = open(names_csv_fname1, "r") -compiled_yarn_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/shortcuts.yarnc"), "rb") -names_csv_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/shortcuts.csv"), "r") +compiled_yarn_fname1 = os.path.join(os.path.dirname( + __file__), '../examples/yarn1/shortcuts.yarnc') +compiled_yarn_f1 = open(compiled_yarn_fname1, 'rb') +names_csv_fname1 = os.path.join(os.path.dirname( + __file__), '../examples/yarn1/shortcuts.csv') +names_csv_f1 = open(names_csv_fname1, 'r') +compiled_yarn_f2 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn2/shortcuts.yarnc'), 'rb') +names_csv_f2 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn2/shortcuts.csv'), 'r') runner1 = YarnRunner(compiled_yarn_f1, names_csv_f1) runner2 = YarnRunner(compiled_yarn_f2, names_csv_f2) diff --git a/tests/test_variables.py b/tests/test_variables.py index 69e4093..89b9472 100644 --- a/tests/test_variables.py +++ b/tests/test_variables.py @@ -1,12 +1,14 @@ import os from .context import YarnRunner -compiled_yarn_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/variables.yarnc") -compiled_yarn_f1 = open(compiled_yarn_fname1, "rb") -names_csv_fname1 = os.path.join(os.path.dirname(__file__), "../examples/yarn1/variables.csv") -names_csv_f1 = open(names_csv_fname1, "r") -compiled_yarn_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/variables.yarnc"), "rb") -names_csv_f2 = open(os.path.join(os.path.dirname(__file__), "../examples/yarn2/variables.csv"), "r") +compiled_yarn_f1 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn1/variables.yarnc'), 'rb') +names_csv_f1 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn1/variables.csv'), 'r') +compiled_yarn_f2 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn2/variables.yarnc'), 'rb') +names_csv_f2 = open(os.path.join(os.path.dirname( + __file__), '../examples/yarn2/variables.csv'), 'r') runner1 = YarnRunner(compiled_yarn_f1, names_csv_f1) runner2 = YarnRunner(compiled_yarn_f2, names_csv_f2) @@ -27,4 +29,3 @@ def test_variables2(): assert runner2.variables["$value_string"] == "string" assert runner2.variables["$value_float"] == 1.25 assert runner2.variables["$value_bool"] == True - diff --git a/yarnrunner_python/runner.py b/yarnrunner_python/runner.py index fdc1a2e..b396e20 100644 --- a/yarnrunner_python/runner.py +++ b/yarnrunner_python/runner.py @@ -15,6 +15,7 @@ def __init__( names_csv_f = None, autostart=True, enable_tracing=False, + experimental_newlines=False, visits: Optional[Dict[str, int]] = None, variables: Dict[str, Any] = None, current_node: str = None, @@ -26,6 +27,7 @@ def __init__( List[Instruction] ] = None, # TODO: what is the correct type here? program_counter: int = 0, + previous_instruction = None, compiled_yarn = None, string_lookup_table = None ) -> None: @@ -46,6 +48,7 @@ def __init__( else: self.string_lookup_table = {} self._enable_tracing = enable_tracing + self._experimental_newlines = experimental_newlines self.visits = ( visits if visits else {key: 0 for key in self._compiled_yarn.nodes.keys()} @@ -62,6 +65,8 @@ def __init__( else [Instruction(opcode=Instruction.OpCode.RUN_NODE)] ) self._program_counter = program_counter + self._previous_instruction = previous_instruction if previous_instruction else Instruction( + opcode=Instruction.OpCode.RUN_NODE) self.paused = True self.finished = ( len(self._vm_instruction_stack) != 0 @@ -82,6 +87,7 @@ def __repr__(self): "line_buffer", "option_buffer", "vm_data_stack", + "experimental_newlines" ] pairs = { @@ -102,6 +108,7 @@ def __repr__(self): if self._vm_instruction_stack: ins = f"""vm_instruction_stack={[json_format.MessageToJson(i) for i in self._vm_instruction_stack]}""" + prev = f"""previous_instruction={json_format.MessageToJson(self._previous_instruction)}""" yarn = f"compiled_yarn={json_format.MessageToJson(self._compiled_yarn)}" lookup_table = f"string_lookup_table={self.string_lookup_table}" @@ -110,6 +117,7 @@ def __repr__(self): f"""YarnRunner({yarn}, {lookup_table}, autostart={self._autostart}""" + f"""{", " + args if args else ""}""" + f"""{", " + ins if ins else ""}""" + + f"""{", " + prev if prev else ""}""" + ")" ) @@ -135,6 +143,13 @@ def __lookup_string(self, string_key) -> str: else: return self.string_lookup_table[string_key]["text"] + def __lookup_line_no(self, string_key): + if string_key not in self.string_lookup_table: + raise Exception( + f"{string_key} is not a key in the string lookup table.") + else: + return int(self.string_lookup_table[string_key]["lineNumber"]) + def __find_label(self, label_key): labels = self._compiled_yarn.nodes[self.current_node].labels if label_key in labels: @@ -181,6 +196,11 @@ def debug_program_proto(self): print("The protobuf representation of the current program is:") print(self._compiled_yarn) + def debug_to_json_file(self, f): + print("The JSON representation of the compiled Yarn program has been written to the file provided.") + f.write(json_format.MessageToJson(self._compiled_yarn)) + f.close() + ##### Public functions to surface via API below here ##### def get_line(self): @@ -245,6 +265,10 @@ def __go_to_node(self, node_key): self._vm_instruction_stack = ( self._compiled_yarn.nodes[node_key].instructions) self._program_counter = 0 + + # not technically true, but close enough + self._previous_instruction = Instruction( + opcode=Instruction.OpCode.RUN_NODE) self.__process_instruction() def __run_line(self, instruction): @@ -257,11 +281,37 @@ def __run_line(self, instruction): instruction.operands[1]) # TODO: implement substitutions + if self._experimental_newlines: + # attempt to add a newlines if the last thing we did was run a line + # but only if there are empty lines in the source file + if self._previous_instruction.opcode == Instruction.OpCode.RUN_LINE: + prev_line_no = self.__lookup_line_no( + self._previous_instruction.operands[0].string_value) + curr_line_no = self.__lookup_line_no(string_key) + diff = curr_line_no - prev_line_no + if diff > 1: + for _i in range(diff - 1): + self._line_buffer.append('') + self._line_buffer.append(self.__lookup_string(string_key)) def __run_command(self, instruction): + # split the command specifier by spaces, ignoring spaces + # inside single or double quotes (https://stackoverflow.com/a/2787979/) command, * \ - args = instruction.operands[0].string_value.strip().split(" ") + args = re.split(''' (?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', + instruction.operands[0].string_value.strip()) + # don't miss that single space at the start of the regex! + + # the above regex leaves quotes in the arguments, so we'll want to remove those + def sanitize_quotes(arg): + matches = re.match(r'^[\'"](.*)[\'"]$', arg) + if matches: + return matches.group(1) + else: + return arg + + args = [sanitize_quotes(arg) for arg in args] if command not in self._command_handlers.keys(): warn( @@ -275,7 +325,10 @@ def __run_command(self, instruction): # TODO: implement substitutions # TODO: maybe do some argument type parsing later - self._command_handlers[command](*args) + ret = self._command_handlers[command](*args) + + if type(ret) is str: + self._line_buffer.append(ret) def __add_option(self, instruction) -> None: title_string_key = instruction.operands[0].string_value @@ -425,4 +478,5 @@ def __process_instruction(self): opcode_functions[instruction.opcode](instruction) if not self.paused and not self.finished: + self._previous_instruction = instruction self.__process_instruction() From 66b44d1a098a18548d5ca5310ae88da963fb3661 Mon Sep 17 00:00:00 2001 From: Martin Ruskov Date: Mon, 24 Jan 2022 22:58:23 +0100 Subject: [PATCH 7/8] Switched to json serialization --- tests/test_shortcuts.py | 10 ++- yarnrunner_python/runner.py | 145 +++++++++++------------------------- 2 files changed, 50 insertions(+), 105 deletions(-) diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py index f2607b1..05c2bcb 100644 --- a/tests/test_shortcuts.py +++ b/tests/test_shortcuts.py @@ -60,7 +60,7 @@ def test_shortcuts2(): assert runner3.current_node == 'Start' -def test_shortcuts3(): +def test_shortcuts_json(): compiled_yarn_f3 = open(compiled_yarn_fname1, "rb") names_csv_f3 = open(names_csv_fname1, "r") runner3 = YarnRunner(compiled_yarn_f3, names_csv_f3) @@ -76,8 +76,11 @@ def test_shortcuts3(): assert choices[2]["text"] == "Option 3" assert choices[3]["text"] == "Option 4" - frozen = repr(runner3) - runner4 = eval(frozen) + dump = runner3.save() + compiled_yarn_f4 = open(compiled_yarn_fname1, "rb") + names_csv_f4 = open(names_csv_fname1, "r") + runner4 = YarnRunner(compiled_yarn_f4, names_csv_f4) + runner4.load(dump) choices = runner4.get_choices() @@ -96,7 +99,6 @@ def test_shortcuts3(): assert runner4.finished assert runner4.current_node == 'Start' - def test_start_node_text2(): assert "This is a test of shortcut functionality." == runner2.get_line() assert not runner2.has_line() diff --git a/yarnrunner_python/runner.py b/yarnrunner_python/runner.py index fdc6262..b5eb7f0 100644 --- a/yarnrunner_python/runner.py +++ b/yarnrunner_python/runner.py @@ -2,6 +2,7 @@ import csv import re +import json from warnings import warn from google.protobuf import json_format from .yarn_spinner_pb2 import Program as YarnProgram, Instruction # type: ignore @@ -9,118 +10,60 @@ class YarnRunner(object): - def __init__( - self, - compiled_yarn_f = None, - names_csv_f = None, - autostart=True, - enable_tracing=False, - experimental_newlines=False, - visits: Optional[Dict[str, int]] = None, - variables: Dict[str, Any] = None, - current_node: str = None, - command_handlers: Dict[str, Any] = None, - line_buffer: List[str] = None, - option_buffer: List[Dict[str, Union[int, str]]] = None, - vm_data_stack: Optional[List[Union[str, float, bool, None]]] = None, - vm_instruction_stack: Optional[ - List[Instruction] - ] = None, # TODO: what is the correct type here? - program_counter: int = 0, - previous_instruction = None, - compiled_yarn = None, - string_lookup_table = None - ) -> None: - - assert bool(compiled_yarn) != bool(compiled_yarn_f) + def __init__(self, compiled_yarn_f, names_csv_f, autostart=True, enable_tracing=False, experimental_newlines=False) -> None: self._compiled_yarn = YarnProgram() - if compiled_yarn_f: - self._compiled_yarn_f = compiled_yarn_f # not ideal to store entire input file, but would need to refactor method signature otherwise - self._compiled_yarn.ParseFromString(compiled_yarn_f.read()) - else: - json_format.ParseDict(compiled_yarn, self._compiled_yarn) - - # assert bool(string_lookup_table) != bool(names_csv_f) - if names_csv_f: - self._names_csv = csv.DictReader(names_csv_f) - self.__construct_string_lookup_table() - elif string_lookup_table: - self.string_lookup_table = string_lookup_table - else: - self.string_lookup_table = {} + self._compiled_yarn.ParseFromString(compiled_yarn_f.read()) + self._names_csv = csv.DictReader(names_csv_f) + self.__construct_string_lookup_table() self._enable_tracing = enable_tracing self._experimental_newlines = experimental_newlines - self.visits = ( - visits if visits else {key: 0 for key in self._compiled_yarn.nodes.keys()} - ) - self.variables = variables if variables else {} - self.current_node = current_node - self._command_handlers = command_handlers if command_handlers else {} - self._line_buffer = line_buffer if line_buffer else [] - self._option_buffer = option_buffer if option_buffer else [] - self._vm_data_stack = vm_data_stack if vm_data_stack else ["Start"] - self._vm_instruction_stack = ( - [json_format.Parse(i, Instruction()) for i in vm_instruction_stack] - if vm_instruction_stack - else [Instruction(opcode=Instruction.OpCode.RUN_NODE)] - ) - self._program_counter = program_counter - self._previous_instruction = previous_instruction if previous_instruction else Instruction( + self.visits = {key: 0 for key in self._compiled_yarn.nodes.keys()} + self.variables = {} + self.current_node = None + self._command_handlers = {} + self._line_buffer = [] + self._option_buffer = [] + self._vm_data_stack = ["Start"] + self._vm_instruction_stack = [Instruction( + opcode=Instruction.OpCode.RUN_NODE)] + self._program_counter = 0 + self._previous_instruction = Instruction( opcode=Instruction.OpCode.RUN_NODE) self.paused = True - self.finished = ( - len(self._vm_instruction_stack) != 0 - and self._vm_instruction_stack[-1].opcode == Instruction.OpCode.STOP - ) + self.finished = False - self._autostart = autostart if autostart: self.resume() - def __repr__(self): - params = [ - "enable_tracing", - "visits", - "variables", - "current_node", - "command_handlers", - "line_buffer", - "option_buffer", - "vm_data_stack", - "experimental_newlines" - ] - - pairs = { - k: repr(self.__getattribute__(k)) - for k in params - if k in self.__dict__ and self.__getattribute__(k) + def save(self) -> str: + dump = { + # "program_version": hash(self._compiled_yarn) + hash(self.string_lookup_table), + "visits": self.visits, + "variables": self.variables, + "line_buffer": self._line_buffer, + "option_buffer": self._option_buffer, + "vm_data_stack": self._vm_data_stack, + "vm_instruction_stack": [json.loads(json_format.MessageToJson(i)) for i in self._vm_instruction_stack], + "program_counter": self._program_counter, + "previous_instruction": json.loads(json_format.MessageToJson(self._previous_instruction)), + "finished": self.finished } - pairs.update( - { - k: repr(self.__getattribute__(f"_{k}")) - for k in params - if f"_{k}" in self.__dict__ and self.__getattribute__(f"_{k}") - } - ) - args = ", ".join(f"{k}={v}" for k, v in pairs.items()) - - ins = "" - if self._vm_instruction_stack: - ins = f"""vm_instruction_stack={[json_format.MessageToJson(i) for i in self._vm_instruction_stack]}""" - - prev = f"""previous_instruction={json_format.MessageToJson(self._previous_instruction)}""" - yarn = f"compiled_yarn={json_format.MessageToJson(self._compiled_yarn)}" - - lookup_table = f"string_lookup_table={self.string_lookup_table}" - - return ( - f"""YarnRunner({yarn}, {lookup_table}, autostart={self._autostart}""" - + f"""{", " + args if args else ""}""" - + f"""{", " + ins if ins else ""}""" - + f"""{", " + prev if prev else ""}""" - + ")" - ) + return json.dumps(dump) + + def load(self, data: str) -> None: + # if not data or "program_version" not in data or data["program_version"] != hash(self._compiled_yarn) + hash(self.string_lookup_table): + # raise ValueError("Mismatched yarn version") + dump = json.loads(data) + self.visits = dump["visits"] + self.variables = dump["variables"] + self._line_buffer = dump["line_buffer"] + self._option_buffer = dump["option_buffer"] + self._vm_data_stack = dump["vm_data_stack"] + self._vm_instruction_stack = [json_format.Parse(json.dumps(i), Instruction()) for i in dump["vm_instruction_stack"]] + self._program_counter = dump["program_counter"] + self._previous_instruction = json_format.Parse(json.dumps(dump["previous_instruction"]), Instruction()) + self.finished = dump["finished"] def __construct_string_lookup_table(self): self.string_lookup_table = dict() From b09d63bab68af324232682b263f2f6f18f3b4baf Mon Sep 17 00:00:00 2001 From: Martin Ruskov Date: Mon, 24 Jan 2022 23:10:59 +0100 Subject: [PATCH 8/8] Added current_node in export; had forgotten it --- yarnrunner_python/runner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/yarnrunner_python/runner.py b/yarnrunner_python/runner.py index b5eb7f0..ea6ad2c 100644 --- a/yarnrunner_python/runner.py +++ b/yarnrunner_python/runner.py @@ -41,6 +41,7 @@ def save(self) -> str: # "program_version": hash(self._compiled_yarn) + hash(self.string_lookup_table), "visits": self.visits, "variables": self.variables, + "current_node": self.current_node, "line_buffer": self._line_buffer, "option_buffer": self._option_buffer, "vm_data_stack": self._vm_data_stack, @@ -57,6 +58,7 @@ def load(self, data: str) -> None: dump = json.loads(data) self.visits = dump["visits"] self.variables = dump["variables"] + self.current_node = dump["current_node"] self._line_buffer = dump["line_buffer"] self._option_buffer = dump["option_buffer"] self._vm_data_stack = dump["vm_data_stack"]