Skip to content

Instantly share code, notes, and snippets.

@cthom06
Created May 12, 2011 13:54
Show Gist options
  • Save cthom06/968535 to your computer and use it in GitHub Desktop.
Save cthom06/968535 to your computer and use it in GitHub Desktop.
Modified fish interpreter
#!/usr/bin/python
"""
A python interpreter for the esoteric language ><> (fish). Not very elegantly coded, but it works.
More information: http://esolangs.org/wiki/Fish
"""
import sys
import time
import random
from collections import defaultdict
# make getch work on both windows and linux/mac
class _Getch:
def __init__(self):
try:
self.impl = _GetchWindows()
except ImportError:
self.impl = _GetchUnix()
def __call__(self): return self.impl()
class _GetchUnix:
def __init__(self):
import tty, sys
def __call__(self):
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class _GetchWindows:
def __init__(self):
import msvcrt
def __call__(self):
import msvcrt
return msvcrt.getch()
def getch():
c = sys.stdin.read(1)
if c == "": return chr(0)
return c[0]
if len(sys.argv) < 2:
print("""Usage: fish.py <script> [tick]
script -- a fish script file (.fish)
tick -- the delay between each instruction in seconds (to slow down execution for debugging)""")
sys.exit(1)
_sleep = 0
if len(sys.argv) >= 3:
try:
_sleep = float(sys.argv[2])
except:
print("Invalid sleep time, defaulting to {}".format(_sleep))
# Compile code into the codebox (a 2D list)
_code = defaultdict(lambda: defaultdict(int))
line_n = 0
try:
with open(sys.argv[1]) as file:
for line in file:
char_n = 0
for char in line:
if ord(char) not in (10, 13):
_code[line_n][char_n] = ord(char)
char_n = char_n + 1
line_n = line_n + 1
except IOError:
print("Invalid file '{}'".format(sys.argv[1]))
sys.exit(1)
class Thread():
register = None
string = None
new_thread = False
def __init__(self, x_pos=0, y_pos=0, x_dir=0, y_dir=0):
self.x_pos, self.y_pos, self.x_dir, self.y_dir = x_pos, y_pos, x_dir, y_dir
self.local_stack = []
self.stack = self.local_stack
def push(self, val):
self.stack.append(val)
def pop(self):
try:
return self.stack.pop();
except IndexError:
error();
def move(self):
self.x_pos = self.x_pos + self.x_dir
self.y_pos = self.y_pos + self.y_dir
# if we reach either the top or bottom of the code field, wrap around
try:
if self.y_pos > max(_code.keys()):
self.y_pos = 0
elif self.y_pos < 0:
self.y_pos = max(_code.keys())
except:
pass
# wrap if we reach the right hand side and we're moving rightwards
# (we could be moving up or down, which is okay in this case)
try:
if self.x_pos > max(_code[self.y_pos].keys()) and self.x_dir == 1:
self.x_pos = 0;
# also wrap if we reach the left hand side
elif self.x_pos < 0:
self.x_pos = max(_code[self.y_pos].keys())
except:
pass
def get_instruction(self):
try:
mc = chr(_code[self.y_pos][self.x_pos])
if _sleep:
print mc, self.local_stack
sys.stdout.flush()
return mc
except:
return False
def output(out):
"""Output text without newlines."""
try:
sys.stdout.write(str(out))
except:
sys.exit(1)
def error():
print("something smells fishy...")
sys.exit(1)
global_stack = []
_threads = [Thread(x_pos=-1, x_dir=1)]
while (len(_threads) > 0):
for thread in _threads:
thread.move();
instr = thread.get_instruction()
if (instr == "\x00"):
instr = " "
elif (instr == False):
error()
if not thread.string is None:
if thread.string != instr:
thread.push(ord(instr))
else:
thread.string = None
else:
new_x_dir, new_y_dir = thread.x_dir, thread.y_dir
if instr == ">": # change direction left
new_x_dir, new_y_dir = 1, 0
elif instr == "<": # change direction right
new_x_dir, new_y_dir = -1, 0
elif instr == "v": # change direction down
new_x_dir, new_y_dir = 0, 1
elif instr == "^": # change direction up
new_x_dir, new_y_dir = 0, -1
elif instr == "/": # mirror
new_x_dir, new_y_dir = -thread.y_dir, -thread.x_dir
elif instr == "\\": # mirror
new_x_dir, new_y_dir = thread.y_dir, thread.x_dir
elif instr == "|": # mirror
new_x_dir = -thread.x_dir
elif instr == "_": # mirror
new_y_dir = -thread.y_dir
elif instr == "#": # mirror (all directions)
new_x_dir, new_y_dir = -thread.x_dir, -thread.y_dir
elif instr == "x": # random direction
r = random.randint(1,4)
if r == 1:
new_x_dir, new_y_dir = 1, 0
elif r == 2:
new_x_dir, new_y_dir = -1, 0
elif r == 3:
new_x_dir, new_y_dir = 0, 1
elif r == 4:
new_x_dir, new_y_dir = 0, -1
elif instr == "[": # new thread
thread.new_thread = True
elif instr in set("0123456789abcdef"): # push corresponding (hex) value to stack
thread.push(int(instr, 16))
# all arithmetic: pop a and b, push b <operator> a
elif instr == "+":
a, b = thread.pop(), thread.pop()
try:
thread.push(b+a)
except:
error()
elif instr == "-":
a, b = thread.pop(), thread.pop()
try:
thread.push(b-a)
except:
error()
elif instr == "*":
a, b = thread.pop(), thread.pop()
try:
thread.push(b*a)
except:
error()
elif instr == ",":
a, b = float(thread.pop()), float(thread.pop())
try:
thread.push(b/a)
except:
error()
elif instr == "%":
a, b = thread.pop(), thread.pop()
try:
thread.push(b%a)
except:
error()
elif instr == "=":
a, b = thread.pop(), thread.pop()
thread.push(1 if b==a else 0)
elif instr == ")":
a, b = thread.pop(), thread.pop()
thread.push(1 if b > a else 0)
elif instr == "(":
a, b = thread.pop(), thread.pop()
thread.push(1 if b < a else 0)
elif instr in ('"', "'"): # turn on string parsing
thread.string = instr
elif instr == "!": # skip one command
thread.move()
elif instr == "?": # skip one command if top of stack is 0 or stack is empty
if len(thread.stack) == 0 or thread.pop() == 0:
thread.move()
elif instr == ":": # duplicate top value
try:
thread.push(thread.stack[-1])
except:
error()
elif instr == "~": # remove top value
thread.pop()
elif instr == "$": # rotate top values
a, b = thread.pop(), thread.pop()
thread.push(a)
thread.push(b)
elif instr == "@": # rotate top three values clockwise
a, b, c = thread.pop(), thread.pop(), thread.pop()
thread.push(a)
thread.push(c)
thread.push(b)
elif instr == "&": # load/unload value from register
if thread.register == None:
thread.register = thread.pop()
else:
thread.push(thread.register)
thread.register = None
elif instr == "r": # reverse stack
thread.stack.reverse()
elif instr == "}": # shift stack right
thread.stack = thread.stack[-1:] + thread.stack[:-1]
elif instr == "{": #shift stack left
thread.stack = thread.stack[1:] + thread.stack[:1]
elif instr == "m": # move current stack to the other one
if thread.stack is thread.local_stack:
global_stack[len(global_stack):] = thread.local_stack
thread.local_stack[:] = []
else:
thread.local_stack[len(thread.local_stack):] = global_stack
global_stack[:] = []
elif instr == ".": # switch stacks
if thread.stack is thread.local_stack:
thread.stack = global_stack
else:
thread.stack = thread.local_stack
elif instr == "g": # pops a, b and pushes the value at b,a (col, line) in the codebox
thread.push(_code[thread.pop()][thread.pop()])
elif instr == "p": # pops a, b, c and puts a at position c,b in the codebox
b, c, a = thread.pop(), thread.pop(), thread.pop()
if not b in _code:
_code[b] = {}
_code[b][c] = a
elif instr == "o": # pop and output as character
try:
output(chr(thread.pop()))
except:
error()
elif instr == "n": # pop and output as integer
output(thread.pop())
elif instr == "i": # input one character
thread.push(ord(getch()))
elif instr == "]": # stop thread
_threads.remove(thread)
elif instr == ";": # stop execution
sys.exit(0)
elif instr == " ":
pass
else:
error()
# if we are creating a new thread and the direction has changed, create the new thread in the new direction
if thread.new_thread and (new_x_dir != thread.x_dir or new_y_dir != thread.y_dir):
_threads.append(Thread(x_pos=thread.x_pos, y_pos=thread.y_pos, x_dir=new_x_dir, y_dir=new_y_dir))
thread.new_thread = False
else:
# if not, simply update the direction in the current thread
thread.x_dir, thread.y_dir = new_x_dir, new_y_dir
time.sleep(_sleep)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment