Created
May 12, 2011 13:54
-
-
Save cthom06/968535 to your computer and use it in GitHub Desktop.
Modified fish interpreter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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