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 #!/usr/bin/env python
# -*- coding: utf-8 -*- """Horrlang Interpreter."""
import sys import sys
import io
class InfiniteArray: from collections import defaultdict
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
class Machine: 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.read = read
self.write = write 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.exc_ptr = 0
self.mem_ptr = 0 self.mem_ptr = 0
self.max_cycles = max_cycles self.max_cycles = max_cycles
@ -41,34 +26,37 @@ class Machine:
def get_integer(self): def get_integer(self):
parameter = 0 parameter = 0
while chr(self.mem[self.exc_ptr + 1]) >= '0' \ while self.mem[self.exc_ptr + 1] >= 0x30 and self.mem[self.exc_ptr + 1] <= 0x39:
and chr(self.mem[self.exc_ptr + 1]) <= '9':
self.exc_ptr += 1 self.exc_ptr += 1
if not self.cycle(): if not self.cycle():
return parameter return parameter
parameter = parameter * 10 + (self.mem[self.exc_ptr] \ parameter = parameter * 10 + (self.mem[self.exc_ptr] - ord("0"))
- ord('0'))
if parameter == 0: if parameter == 0:
parameter = 1 parameter = 1
return parameter return parameter
def L(self): def L(self):
"""Move memory pointer to the left."""
self.mem_ptr -= self.get_integer() self.mem_ptr -= self.get_integer()
if self.mem_ptr < 0: if self.mem_ptr < 0:
self.mem_ptr = 0 self.mem_ptr = 0
def R(self): def R(self):
"""Move memory pointer to the right."""
self.mem_ptr += self.get_integer() self.mem_ptr += self.get_integer()
def O(self): def O(self):
"""Output."""
char = self.mem[self.mem_ptr] char = self.mem[self.mem_ptr]
if char > 0 and char < 256: if 0 < char < 256:
self.write(chr(char)) self.write(chr(char))
def I(self): def I(self):
"""Input."""
self.mem[self.mem_ptr] = ord(self.read(1)) self.mem[self.mem_ptr] = ord(self.read(1))
def N(self): def N(self):
"""Forward jump."""
self.exc_ptr += 1 self.exc_ptr += 1
search = self.mem[self.exc_ptr] search = self.mem[self.exc_ptr]
while self.mem[self.exc_ptr] != search: while self.mem[self.exc_ptr] != search:
@ -77,6 +65,7 @@ class Machine:
return return
def P(self): def P(self):
"""Backward jump."""
search = self.mem[self.exc_ptr + 1] search = self.mem[self.exc_ptr + 1]
self.exc_ptr -= 1 self.exc_ptr -= 1
while self.mem[self.exc_ptr] != search: while self.mem[self.exc_ptr] != search:
@ -85,7 +74,8 @@ class Machine:
return return
def J(self): 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] search = self.mem[self.exc_ptr + 1]
self.exc_ptr -= 1 self.exc_ptr -= 1
while self.mem[self.exc_ptr] != search: while self.mem[self.exc_ptr] != search:
@ -94,26 +84,70 @@ class Machine:
return return
def H(self): def H(self):
"""Increment."""
self.mem[self.mem_ptr] = self.mem[self.mem_ptr] + 1 self.mem[self.mem_ptr] = self.mem[self.mem_ptr] + 1
def K(self): def K(self):
"""Decrement."""
self.mem[self.mem_ptr] = self.mem[self.mem_ptr] - 1 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): def run(self, debug=False):
while True: while True:
if self.mem[self.exc_ptr] == 0: if self.mem[self.exc_ptr] == 0:
return return
char = self.mem[self.exc_ptr] char = self.mem[self.exc_ptr]
if char > 0 and char < 256: if 0 < char < 256:
control = chr(char) control = chr(char)
if debug: if debug:
print control self._debug()
if hasattr(self, control): if hasattr(self, control):
getattr(self, control)() getattr(self, control)()
self.exc_ptr += 1 self.exc_ptr += 1
if not self.cycle(): if not self.cycle():
return return
if __name__ == "__main__":
Machine(sys.argv[1], sys.stdin.read, sys.stdout.write, def eval(instructions, input="", max_cycles=None):
int(sys.argv[2]) if len(sys.argv) > 2 else None).run() """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"