Skip to content

Commit 6f34d83

Browse files
committed
Implement jumping to labels, implement internal functions from Yarn standard library
1 parent 4796881 commit 6f34d83

7 files changed

Lines changed: 132 additions & 8 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Only a subset of Yarn Spinner opcodes are currently implemented. This will certa
5454

5555
| OpCode | Status |
5656
| ---------------- | ------------------------------------------------------ |
57-
| `JUMP_TO` | 🚫  Not Implemented |
57+
| `JUMP_TO` |   Implemented in `runner.__jump_to` |
5858
| `JUMP` | 🚫  Not Implemented |
5959
| `RUN_LINE` |  Implemented in `runner.__run_line` |
6060
| `RUN_COMMAND` |  Implemented in `runner.__run_command` |
@@ -64,9 +64,9 @@ Only a subset of Yarn Spinner opcodes are currently implemented. This will certa
6464
| `PUSH_FLOAT` |  Implemented in `runner.__push_float` |
6565
| `PUSH_BOOL` | 🚫  Not Implemented |
6666
| `PUSH_NULL` | 🚫  Not Implemented |
67-
| `JUMP_IF_FALSE` | 🚫  Not Implemented |
67+
| `JUMP_IF_FALSE` |   Implemented in `runner.__jump_if_false` |
6868
| `POP` |  Implemented in `runner.__pop` |
69-
| `CALL_FUNC` | 🚫  Not Implemented |
69+
| `CALL_FUNC` |   Implemented in `runner.__call_func` |
7070
| `PUSH_VARIABLE` |  Implemented in `runner.__push_variable` |
7171
| `STORE_VARIABLE` |  Implemented in `runner.__store_variable` |
7272
| `STOP` |  Implemented in `runner.__stop` |

examples/conditionals.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
id,text,file,node,lineNumber
2+
conditionals-Start-0,This is a test of conditionals and variables.,conditionals,Start,3
3+
conditionals-Start-1,"This is a fallback, and should not run.",conditionals,Start,12
4+
conditionals-var_is_1-2,This node should never be reached.,conditionals,var_is_1,18
5+
conditionals-var_is_2-3,This node should be reached.,conditionals,var_is_2,22

examples/conditionals.yarn

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
title: Start
2+
---
3+
This is a test of conditionals and variables.
4+
5+
<<set $var to 2>>
6+
7+
<<if $var eq 1>>
8+
[[var_is_1]]
9+
<<elseif $var eq 2>>
10+
[[var_is_2]]
11+
<<else>>
12+
This is a fallback, and should not run.
13+
<<endif>>
14+
15+
===
16+
title: var_is_1
17+
---
18+
This node should never be reached.
19+
===
20+
title: var_is_2
21+
---
22+
This node should be reached.
23+
===

examples/conditionals.yarnc

537 Bytes
Binary file not shown.

tests/test_conditionals.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import os
2+
from .context import YarnRunner
3+
4+
compiled_yarn_f = open(os.path.join(os.path.dirname(
5+
__file__), '../examples/conditionals.yarnc'), 'rb')
6+
names_csv_f = open(os.path.join(os.path.dirname(
7+
__file__), '../examples/conditionals.csv'), 'r')
8+
9+
runner = YarnRunner(compiled_yarn_f, names_csv_f, autostart=False)
10+
11+
12+
def test_start_node_text():
13+
runner.debug_program_proto()
14+
runner.resume()
15+
16+
assert "This is a test of conditionals and variables." == runner.get_line()
17+
assert runner.has_line()
18+
19+
20+
def test_conditional_traversal():
21+
choices = runner.get_choices()
22+
23+
assert len(choices) == 0
24+
25+
assert "This node should be reached." == runner.get_line()
26+
assert not runner.has_line()
27+
assert runner.finished
28+
assert runner.current_node == 'var_is_2'

yarnrunner_python/runner.py

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import csv
22
from warnings import warn
33
from .yarn_spinner_pb2 import Program as YarnProgram, Instruction
4+
from .vm_std_lib import functions as std_lib_functions
45

56

67
class YarnRunner(object):
@@ -48,6 +49,14 @@ def __lookup_string(self, string_key):
4849
else:
4950
return self.string_lookup_table[string_key]["text"]
5051

52+
def __find_label(self, label_key):
53+
labels = self._compiled_yarn.nodes[self.current_node].labels
54+
if label_key in labels:
55+
return labels[label_key]
56+
else:
57+
raise Exception(
58+
f"The current node `{self.current_node}` does not have a label named `{label_key}")
59+
5160
def debug_vm(self):
5261
print(f"VM paused: {self.paused}")
5362
print(f"VM finished: {self.finished}")
@@ -61,10 +70,16 @@ def debug_variables(self):
6170
print(self.variables)
6271

6372
def debug_vm_instruction_stack(self):
64-
print(f"The current program counter is: {self._program_counter}")
73+
print(f"The current node is: {self.current_node}")
6574
print("The current VM instruction stack is:")
6675
for (idx, instruction) in enumerate(self._vm_instruction_stack):
67-
print(f" {idx}: {Instruction.OpCode.Name(instruction.opcode)}")
76+
arrow = "-->" if idx == self._program_counter else " "
77+
print(
78+
f"{arrow} {idx}: {Instruction.OpCode.Name(instruction.opcode)}(", end='')
79+
print(*list(map(lambda o: o.string_value or o.float_value,
80+
instruction.operands)), sep=", ", end=")\n")
81+
print("The current labels are:")
82+
print(self._compiled_yarn.nodes[self.current_node].labels)
6883

6984
def debug_program_proto(self):
7085
print("The protobuf representation of the current program is:")
@@ -105,7 +120,14 @@ def add_command_handler(self, command, fn):
105120

106121
##### OpCode Implementations below here #####
107122

123+
def __jump_to(self, instruction):
124+
# print(f"Jump from {self._program_counter} ", end='')
125+
self._program_counter = self.__find_label(
126+
instruction.operands[0].string_value)
127+
print(f"to {self._program_counter}")
128+
108129
def __go_to_node(self, node_key):
130+
# print(f"Go from {self.current_node} to node {node_key}")
109131
if node_key not in self._compiled_yarn.nodes.keys():
110132
raise Exception(
111133
f"{node_key} is not a valid node in this Yarn story.")
@@ -147,9 +169,37 @@ def __show_options(self, _instruction):
147169
def __push_float(self, instruction):
148170
self._vm_data_stack.insert(0, instruction.operands[0].float_value)
149171

172+
def __jump_if_false(self, instruction):
173+
if self._vm_data_stack[0] == False:
174+
self.__jump_to(instruction)
175+
150176
def __pop(self, _instruction):
151177
self._vm_data_stack.pop(0)
152178

179+
def __call_func(self, instruction):
180+
function_name = instruction.operands[0].string_value
181+
if function_name not in std_lib_functions:
182+
raise Exception(
183+
f"The internal function `{function_name}` is not implemented in this Yarn runtime.")
184+
185+
expected_params, fn = std_lib_functions[function_name]
186+
actual_params = int(self._vm_data_stack.pop(0))
187+
188+
if expected_params != actual_params:
189+
raise Exception(
190+
f"The internal function `{function_name} expects {expected_params} parameters but received {actual_params} parameters")
191+
192+
params = []
193+
while expected_params > 0:
194+
params.insert(0, self._vm_data_stack.pop(0))
195+
expected_params -= 1
196+
197+
# invoke the function
198+
ret = fn(params)
199+
200+
# Store the return value on the stack
201+
self._vm_data_stack.insert(0, ret)
202+
153203
def __push_variable(self, instruction):
154204
self._vm_data_stack.insert(
155205
0, self.variables[instruction.operands[0].string_value])
@@ -193,7 +243,7 @@ def noop(instruction):
193243
f"OpCode {Instruction.OpCode.Name(instruction.opcode)} is not yet implemented")
194244

195245
opcode_functions = {
196-
Instruction.OpCode.JUMP_TO: noop,
246+
Instruction.OpCode.JUMP_TO: self.__jump_to,
197247
Instruction.OpCode.JUMP: noop,
198248
Instruction.OpCode.RUN_LINE: self.__run_line,
199249
Instruction.OpCode.RUN_COMMAND: self.__run_command,
@@ -203,9 +253,9 @@ def noop(instruction):
203253
Instruction.OpCode.PUSH_FLOAT: self.__push_float,
204254
Instruction.OpCode.PUSH_BOOL: noop,
205255
Instruction.OpCode.PUSH_NULL: noop,
206-
Instruction.OpCode.JUMP_IF_FALSE: noop,
256+
Instruction.OpCode.JUMP_IF_FALSE: self.__jump_if_false,
207257
Instruction.OpCode.POP: self.__pop,
208-
Instruction.OpCode.CALL_FUNC: noop,
258+
Instruction.OpCode.CALL_FUNC: self.__call_func,
209259
Instruction.OpCode.PUSH_VARIABLE: self.__push_variable,
210260
Instruction.OpCode.STORE_VARIABLE: self.__store_variable,
211261
Instruction.OpCode.STOP: self.__stop,

yarnrunner_python/vm_std_lib.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
functions = {
2+
'Add': (2, lambda p: p[0] + p[1]),
3+
'Minus': (2, lambda p: p[0] - p[1]),
4+
'UnaryMinus': (1, lambda p: -p[0]),
5+
'Divide': (2, lambda p: p[0] / p[1]),
6+
'Multiply': (2, lambda p: p[0] * p[1]),
7+
'Modulo': (2, lambda p: p[0] % p[1]),
8+
'EqualTo': (2, lambda p: p[0] == p[1]),
9+
'NotEqualTo': (2, lambda p: p[0] != p[1]),
10+
'GreaterThan': (2, lambda p: p[0] > p[1]),
11+
'GreaterThanOrEqualTo': (2, lambda p: p[0] >= p[1]),
12+
'LessThan': (2, lambda p: p[0] < p[1]),
13+
'LessThanOrEqualTo': (2, lambda p: p[0] <= p[1]),
14+
'And': (2, lambda p: p[0] and p[1]),
15+
'Or': (2, lambda p: p[0] or p[1]),
16+
'Xor': (2, lambda p: p[0] ^ p[1]),
17+
'Not': (1, lambda p: not p[0])
18+
}

0 commit comments

Comments
 (0)