Compare commits

...

5 Commits

4 changed files with 125 additions and 64 deletions

32
README
View File

@ -1,32 +0,0 @@
Horrlang is an esoteric language, you should not try it.
In Horrlang, memory and instructions are mixed, so you can use old instructions
as new memory places, or do some metaprogramming rewriting your code as it runs.
Just like in LISP but ... without lists ... good luck ...
In Horrlang you have basically 2 pointers, a memory one and an instruction
one. Both start at 0. The instruction one follows instructions that can move
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)
The Hello World :
R9R9R9R4OROROROROROROROROROROROHello World.
Writing a serie of P should like :
R5OP5P or R3OP3
Write 123456789 using a loop :
R9R3:ROHLKJ:81

38
README.md Normal file
View File

@ -0,0 +1,38 @@
# Horrlang is an esoteric language, you should not try it.
In Horrlang, memory and instructions are mixed, so you can use old instructions
as new memory places, or do some metaprogramming rewriting your code as it runs.
Just like in LISP but ... without lists ... good luck ...
In Horrlang you have basically 2 pointers, a memory one and an instruction
one. Both start at 0. The instruction one follows instructions that can move
the memory pointer. Have a look at the instruction set:
Instruction set:
- 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:
python horrlang.py 'R9R9R9R4OROROROROROROROROROROROHello World.'
Or:
python horrlang.py 'Hello World0KORJK'
Writing a infinite serie of P:
R5OP5P or R3OP3
Write 123456789 using a loop:
R9R3:ROHLKJ:81

View File

@ -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 chr(self.mem[self.exc_ptr + 1]) >= '0' \
and chr(self.mem[self.exc_ptr + 1]) <= '9':
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()

21
test_horrlang.py Normal file
View File

@ -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"