diff --git a/README.md b/README.md index 3e059ec..f4147f3 100644 --- a/README.md +++ b/README.md @@ -11,28 +11,25 @@ the memory pointer. Have a look at the instruction set: Instruction set: -``` -Lx : Move memory pointer left x chars (defaults to 1). -Rx : Move memory pointer right x chars (defaults to 1). - -Nx : Goto the next char that is x with the instruction pointer. -Px : Goto the prev char that is x with the instruction pointer. -O : Output current value under memory pointer. -I : Write from stdin to the current memory pointer. -K : Decrement the value under the current memory pointer. -H : Increment the value under the current memory pointer. -Jx : Jump with the instruction pointer to the previous 'x' char if the value under the current memory pointer != 0. (Jumps to the beginning if no 'x' char is found) -``` +- Lx: `left`: Move memory pointer on the left for `x` chars (defaults to `1`). +- Rx: `right`: Move memory pointer on the right for `x` chars (defaults to `1`). +- Nx: `next`: Forward Jump: Move execution pointer to the first found `x` on the left of its current position. +- Px: `prev`: Backward Jump: Move execution pointer to the first found `x` on the right of its current position. +- O: `output`: Copy the current value under memory pointer to stdout. +- I: `input`: Copy from stdin to the current memory pointer. +- K: Decrement the value under the current memory pointer. +- H: Increment the value under the current memory pointer. +- Jx: `jump` If the value under the current memory pointer is not `0` (0x30): Move the instruction pointer to the previous `x` instruction (like `Px`). (Jumps to the beginning if no `x` char is found). The Hello World: - R9R9R9R4OROROROROROROROROROROROHello World. + python horrlang.py 'R9R9R9R4OROROROROROROROROROROROHello World.' Or: - Hello World0KORJK + python horrlang.py 'Hello World0KORJK' -Writing a serie of P should like: +Writing a infinite serie of P: R5OP5P or R3OP3 diff --git a/horrlang.py b/horrlang.py index b8050c2..f6d86c9 100755 --- a/horrlang.py +++ b/horrlang.py @@ -1,32 +1,17 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- +"""Horrlang Interpreter.""" import sys - -class InfiniteArray: - def __init__(self, values=None): - self.chunks = {} - if values is not None: - for key, value in enumerate(values): - self[key] = value - - def get_chunk(self, index): - if index not in self.chunks: - self.chunks[index] = [0 for i in xrange(4096)] - return self.chunks[index] - - def __getitem__(self, index): - return self.get_chunk(index / 4096)[index % 4096] - - def __setitem__(self, index, value): - self.get_chunk(index / 4096)[index % 4096] = value +import io +from collections import defaultdict class Machine: - def __init__(self, code, read, write, max_cycles=None): + def __init__(self, code, read=sys.stdin.read, write=sys.stdout.write, max_cycles=None): self.read = read self.write = write - self.mem = InfiniteArray([ord(char) for char in code]) + self.mem = defaultdict(int) + self.mem.update({i: ord(char) for i, char in enumerate(code)}) self.exc_ptr = 0 self.mem_ptr = 0 self.max_cycles = max_cycles @@ -41,34 +26,37 @@ class Machine: def get_integer(self): parameter = 0 - while self.mem[self.exc_ptr + 1] >= 0x30 \ - and self.mem[self.exc_ptr + 1] <= 0x39: + while self.mem[self.exc_ptr + 1] >= 0x30 and self.mem[self.exc_ptr + 1] <= 0x39: self.exc_ptr += 1 if not self.cycle(): return parameter - parameter = parameter * 10 + (self.mem[self.exc_ptr] \ - - ord('0')) + parameter = parameter * 10 + (self.mem[self.exc_ptr] - ord("0")) if parameter == 0: parameter = 1 return parameter def L(self): + """Move memory pointer to the left.""" self.mem_ptr -= self.get_integer() if self.mem_ptr < 0: self.mem_ptr = 0 def R(self): + """Move memory pointer to the right.""" self.mem_ptr += self.get_integer() def O(self): + """Output.""" char = self.mem[self.mem_ptr] - if char > 0 and char < 256: + if 0 < char < 256: self.write(chr(char)) def I(self): + """Input.""" self.mem[self.mem_ptr] = ord(self.read(1)) def N(self): + """Forward jump.""" self.exc_ptr += 1 search = self.mem[self.exc_ptr] while self.mem[self.exc_ptr] != search: @@ -77,6 +65,7 @@ class Machine: return def P(self): + """Backward jump.""" search = self.mem[self.exc_ptr + 1] self.exc_ptr -= 1 while self.mem[self.exc_ptr] != search: @@ -85,7 +74,8 @@ class Machine: return def J(self): - if self.mem[self.mem_ptr] != ord('0'): + """Conditional jump.""" + if self.mem[self.mem_ptr] != ord("0"): search = self.mem[self.exc_ptr + 1] self.exc_ptr -= 1 while self.mem[self.exc_ptr] != search: @@ -94,26 +84,70 @@ class Machine: return def H(self): + """Increment.""" self.mem[self.mem_ptr] = self.mem[self.mem_ptr] + 1 def K(self): + """Decrement.""" self.mem[self.mem_ptr] = self.mem[self.mem_ptr] - 1 + def _debug(self): + print(f"# Cycle {self.cycles}\n") + for i, value in sorted(self.mem.items()): + marker = "" + if i == self.exc_ptr: + marker += "← exc ptr" + control = chr(self.mem[self.exc_ptr]) + if hasattr(self, control): + marker += " (" + getattr(self, control).__doc__ + ")" + if i == self.mem_ptr: + marker += "← mem ptr" + printable = "" + if chr(value).isprintable(): + printable = f"{chr(value)}" + print(f"{i:3d} | {value:5d} | {printable:5s}", marker) + print() + def run(self, debug=False): while True: if self.mem[self.exc_ptr] == 0: return char = self.mem[self.exc_ptr] - if char > 0 and char < 256: + if 0 < char < 256: control = chr(char) if debug: - print control + self._debug() if hasattr(self, control): getattr(self, control)() self.exc_ptr += 1 if not self.cycle(): return -if __name__ == "__main__": - Machine(sys.argv[1], sys.stdin.read, sys.stdout.write, - int(sys.argv[2]) if len(sys.argv) > 2 else None).run() + +def eval(instructions, input="", max_cycles=None): + """Convenience, non-interactive wrapper around HorrlangMachine.""" + stdin = io.StringIO(input) + stdout = io.StringIO() + machine = Machine(instructions, stdin.read, stdout.write, max_cycles=max_cycles) + machine.run() + return stdout.getvalue() + + +import argparse + + +def parse_args(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("code", type=str) + parser.add_argument("--debug", action="store_true") + parser.add_argument("--max-cycles", type=int, default=None) + return parser.parse_args() + + +def main(): + args = parse_args() + Machine(args.code, max_cycles=args.max_cycles,).run(debug=args.debug) + + +if __name__ == '__main__': + main() diff --git a/test_horrlang.py b/test_horrlang.py new file mode 100644 index 0000000..b4aaf31 --- /dev/null +++ b/test_horrlang.py @@ -0,0 +1,21 @@ +from horrlang import eval + + +def test_hello_world(): + assert eval("R9R9R9R4OROROROROROROROROROROROHello World.") == "Hello World." + + +def test_short_hello_world(): + assert eval("Hello World0KORJK") == "Hello World" + + +def test_from_readme(): + assert eval("R5OP5P", max_cycles=100) == "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP" + + +def test_from_readme_short(): + assert eval("R3OP3", max_cycles=100) == "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP" + + +def test_123456789(): + assert eval("R9R3:ROHLKJ:91") == "123456789"