Skip to content

Instantly share code, notes, and snippets.

@Cxarli
Created April 27, 2015 18:30
Show Gist options
  • Save Cxarli/55cd6fe60fe92bd837a1 to your computer and use it in GitHub Desktop.
Save Cxarli/55cd6fe60fe92bd837a1 to your computer and use it in GitHub Desktop.
Java ><> compiler
package me.comexpert.fish;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;
/**
* This is the main class from the Fish.java interpreter,
* created by Charlie Bouthoorn. For fun.
*
* It handles the main function, the arguments,
* the parsing and the control of the stack.
*
* Have fun with it !
*
* This class and all function in it use the Creative Commons BY-SA 4.0 license below.
*
* You are free to:
* Share — copy and redistribute the material in any medium or format
* Adapt — remix, transform, and build upon the material for any purpose, even commercially.
*
* Gotten from http://creativecommons.org/licenses/by-sa/4.0/
*
* @author Charlie Bouthoorn
*/
public class Fish {
// All variables
int sid = 0; // The current StackID
int wait = 0000; // Time to wait between commands in milliseconds
int stacksize = 64*1024; // The size of the stack
char quote = 0; // The current quote, ' or "
int[] pos = {-1, 0}; // The current position
double register = 0.0; // The value in the register
boolean reg = false; // Boolean whether the register is set or not
boolean run = true; // Run while this is true
boolean skip = false; // When a command has to be skipped
boolean tick = false; // When I have to tick even on NOP
boolean debug = false; // Debugging mode
boolean string = false; // When string is turned on
boolean credit = false; // Show some credit after running the program
Stack[] stacks = new Stack[1024]; // The array of stacks
char[][] grid = null; // The grid from the file
Directions dir = Directions.RIGHT; // The current direction
StringBuilder sb = new StringBuilder(); // The StringBuilder for the string
/**
* Main constructor
*
* @param args The arguments from main
*/
public Fish(String[] args) {
// Variables
String filename = args.length>=1 ? args[0] : null;
String[] data = null;
stacks[sid] = new Stack(stacksize, this);
boolean isFile = true;
for(int i=0; i<args.length; i++) {
switch(args[i]) {
case "-h":
showHelp();
return;
case "-c":
isFile=false;
data=args[++i].split("\n");
break;
case "-s":
case "--string":
String s=args[++i];
for(char c:s.toCharArray()) {
stacks[sid].push(c);
}
break;
case "-v":
case "--value":
i++;
while(i<args.length && isNumber(args[i])) {
stacks[sid].push(Double.parseDouble(args[i]));
i++;
}
i--;
break;
case "-t":
case "--time":
if(isNumber(args[++i])) {
// Wait has to be integer
wait=(int)Math.round( Double.parseDouble(args[i]) * 1000 );
}
break;
case "-a":
case "--always-tick":
tick=true;
break;
case "--credit":
credit=true;
break;
case "-X":
debug=true;
break;
default:
System.out.println("Ignore argument "+args[i]+" !");
break;
}
}
if(isFile) {
// If the filename isn't given, you need help
if(filename == null) {
showHelp();
System.exit(-1);
return;
}
FileIO fio = new FileIO(filename);
try {
data=fio.readData();
}
catch (NullPointerException | FileNotFoundException e) {
System.out.println("File \""+filename+"\" not found in currrent directory!");
e.printStackTrace(System.out);
return;
} catch (IOException ioe) {
System.out.println("An unexpected error occured:");
ioe.printStackTrace(System.out);
return;
}
}
if(data==null) {
System.out.println("No code provided!");
System.exit(-1);
return;
}
if(debug) {
for(String s:data) {
System.out.println(s);
}
}
// Creating grid
grid=new char[data.length][];
// Convert String[] to char[][]
int i=0;
for(String line:data) {
grid[i++]=line.toCharArray();
}
// Get maximum x
int top=grid[0].length;
for(char[] line:grid) {
if(line.length > top) {
top=line.length;
}
}
// Make sure every line is 'top' long
for(int a=0; a<grid.length; a++) {
char[] line=grid[a];
char[] newline=new char[top];
int b=0;
for(char c:line) {
newline[b++]=c;
}
grid[a]=newline;
}
// Grid is prepared from here on
try {
while(run) {
// Move the position according to the direction
pos=dir.move(pos);
// Check for out-of-bounds
check();
if(debug)
stacks[sid].print();
if(skip) {
skip=false;
continue;
}
// Parse current character
parse();
// Check for NOP, and whether to wait or not
char c=grid[pos[1]][pos[0]];
boolean isNOP=(c==' ' || c==0);
if(tick || !isNOP) {
sleep(wait);
}
}
// Done running
} catch(IOException ioe) {
System.out.println("An unexpected error happened: ");
ioe.printStackTrace(System.out);
return;
}
// After all code is done, optionally do some more things, then exit
if(credit)
System.out.print("\nThis code was run with Charlie's Fish.java compiler :)\n");
System.exit(0);
return;
}
/**
* Show the help mimiced from the official fish.py interpreter
*/
public void showHelp() {
System.out.println("usage: java Fish [-h] (<script file> | -c <code>) [<options>]");
System.out.println("");
System.out.println(" Execute a ><> script.");
System.out.println("");
System.out.println(" Executing a script is as easy as:");
System.out.println(" java Fish <script file>");
System.out.println("");
System.out.println(" You can also execute code directly using the -c/--code flag:");
System.out.println(" java Fish -c '1n23nn;'");
System.out.println(" > 132");
System.out.println("");
System.out.println(" The -v and -s flags can be used to prepopulate the stack:");
System.out.println(" java Fish echo.fish -s \"hello, world\" -v 32 49 50 51 -s \"456\"");
System.out.println(" > hello, world 123456");
System.out.println("");
System.out.println("optional arguments:");
System.out.println(" -h, --help show this help message and exit");
System.out.println("");
System.out.println("code:");
System.out.println(" script .fish file to execute");
System.out.println(" -c <code>, --code <code>");
System.out.println(" string of instructions to execute");
System.out.println("");
System.out.println("options:");
System.out.println(" -s <string>, --string <string>");
System.out.println(" -v <number> [<number> ...], --value <number> [<number> ...]");
System.out.println(" push numbers or strings onto the stack before");
System.out.println(" execution starts");
System.out.println(" -t <seconds>, --tick <seconds>");
System.out.println(" define a tick time, or a delay between the execution");
System.out.println(" of each instruction");
System.out.println(" -a, --always-tick make every instruction cause a tick (delay), even");
System.out.println(" whitespace and skipped instructions");
}
/**
* Sleep <code>time</code> milliseconds
* @param time
*/
public void sleep(int time) {
try {
Thread.sleep(time);
} catch(Exception e) { /**/ }
}
/**
* Check for out-of-bounds positioning, then loop back
*/
public void check() {
int y=pos[1], maxy=grid.length-1;
if(y<0) {
y=maxy;
}
if(y>maxy) {
y=0;
}
int x=pos[0], maxx=grid[y].length-1;
if(x<0) {
x=maxx;
}
if(x>maxx) {
x=0;
}
pos=new int[]{x, y};
}
/**
* Parses the current character
* Note: I'm <code>return</code>'ing for fun! I know it's useless because I'm using <code>else if</code>
*
* @throws IOException When System.in failed to read()
*/
public void parse() throws IOException {
char c=grid[pos[1]][pos[0]];
if(debug)
System.out.println(c);
// ^C
if(c==3) {
System.out.println("^C");
System.exit(0);
return;
}
// String
else if(c=='"' || c=='\'') {
if(string) {
if(quote==c) {
// Get the built string
String s=sb.toString();
// Reset the builder
sb=new StringBuilder();
if(debug)
System.out.println("STRING "+quote+s+quote+"");
// Push every character from the string to the current stack
for(char x:s.toCharArray()) {
stacks[sid].push(x);
}
// Reset values
string=false;
quote=0;
} else {
// Append quote to the builder
sb.append(c);
}
} else {
// Enable string'ing
string=true;
quote=c;
}
return;
}
// Append character to the builder when string'ing is turned on
else if(string) {
sb.append(c);
return;
}
// NOP
else if(c==0 || c==' ') {
return;
}
// Movement
else if(c=='>' || c=='<' || c=='^' || c=='v' || c=='/' || c=='\\' || c=='|' || c=='_' || c=='x' || c=='#') {
// Let Directions handle this
dir=dir.get(c);
return;
}
// Trampoline
else if(c=='!') {
if(debug)
System.out.println("SKIP");
skip=true;
}
// Conditional trampoline
else if(c=='?') {
if(debug)
System.out.println("COND SKIP");
double x=stacks[sid].pop();
skip=(x==0);
}
// Jump
else if(c=='.') {
if(debug)
System.out.println("JUMP");
double y=stacks[sid].pop();
double x=stacks[sid].pop();
if(!isInteger(y) || !isInteger(x)) { // Integer check
throw new NumberFormatException("X or Y is no integer!");
}
// Safe to cast
pos=new int[]{(int)x, (int)y};
}
// Numbers
else if((c>='0' && c<='9') || (c>='a' && c<='f')) {
if(debug)
System.out.println("NUMBER");
// Parse in hexadecimal (base 16)
int i=Integer.parseInt(c+"", 16);
stacks[sid].push(i);
return;
}
// Expressions
else if(c=='+' || c=='-' || c=='*' || c==',' || c=='%' || c=='=' || c=='(' || c==')') {
double y=stacks[sid].pop();
double x=stacks[sid].pop();
double z=0;
if(debug)
System.out.println("EXPR "+c);
// Calculate
switch(c) {
case '+':
z=x+y;
break;
case '-':
z=x-y;
break;
case '*':
z=x*y;
break;
case ',':
z=x/y;
break;
case '%':
z=x%y;
break;
case '=':
z=(x==y)?1:0;
break;
case '(':
z=(y>x)?1:0;
break;
case ')':
z=(y<x)?1:0;
break;
default:
break;
}
stacks[sid].push(z);
}
// Stack manipulation
else if(c==':' || c=='~' || c=='$' || c=='@' || c=='}' || c=='{' || c=='r' || c=='l') {
// Let the stack handle this
stacks[sid].action(c);
}
// New stacks
else if(c=='[' || c==']') {
if(c=='[') {
if(debug) System.out.println("NEW STACK");
double x=stacks[sid].pop();
if(Math.floor(x)!=Math.ceil(x)) {
throw new NumberFormatException("X is no Integer!");
}
sid++;
stacks[sid]=new Stack(stacksize, this);
for(int a=0; a<x; a++) {
double item=stacks[sid-1].pop();
stacks[sid].push(item);
}
stacks[sid].reverse();
}
else if(c==']') {
sid--;
stacks[sid]=merge(stacks[sid], stacks[sid+1]);
}
else {
// This is impossible
}
}
// Numberical output
else if(c=='n') {
double i=stacks[sid].pop();
// I hate 10.0 as output, while it should be 10
if(isInteger(i)) {
System.out.print((int)i);
}
// But 5.5 should stay 5.5
else if(i>=Double.MIN_VALUE && i<=Double.MAX_VALUE) {
System.out.print(i);
}
// And Infinity is ugly
else {
// I know no better Exception than NPE here :/
throw new NullPointerException("Number too large");
}
return;
}
// Characterictic output
else if(c=='o') {
double i=stacks[sid].pop();
System.out.print((char)i);
return;
}
// Input
else if(c=='i') {
int i=System.in.read();
stacks[sid].push(i); // No need to cast to (char) here
return;
}
// Register
else if(c=='&') {
if(debug)
System.out.println("REGISTER");
if(reg) stacks[sid].push(register);
else register=stacks[sid].pop();
reg=!reg;
return;
}
// Get character
else if(c=='g') {
if(debug)
System.out.println("GET");
double y=stacks[sid].pop();
double x=stacks[sid].pop();
if(!isInteger(y) || !isInteger(x)) {
throw new NumberFormatException("X or Y is no integer!");
}
char v=grid[ (int)y ][ (int)x ];
stacks[sid].push(v);
return;
}
// Change character
else if(c=='p') {
if(debug)
System.out.println("PLACE");
double y=stacks[sid].pop();
double x=stacks[sid].pop();
double v=stacks[sid].pop();
if(!isInteger(y) || !isInteger(x) || !isInteger(v)) {
throw new NumberFormatException("X, Y or V is no integer!");
}
grid[ (int)x ][ (int)y ]=(char) v;
return;
}
// Terminater
else if (c==';') {
run=false;
return;
}
// Unknown
else {
System.out.println("What the f*** should '"+c+"' do ?!");
return;
}
}
/**
* Check whether the given String is a number
* by trying to parse it by Integer.parseInt() and Double.parseDouble()
* If one of them fails, return false, else true
*
* @param s The string to check
* @return Whether the string is a valid Integer or Double
*/
public boolean isNumber(String s) {
try {
Integer.parseInt(s);
return true;
} catch(Exception e) { /**/ }
try {
Double.parseDouble(s);
return true;
} catch(Exception e) { /**/ }
return false;
}
/**
* Check if the given Double is a valid Integer
* by comparing <code>Math.floor()</code> and <code>Math.ceil()</code>
* and by checking if it's higher than or equal to <code>Integer.MIN_VALUE</code>
* and higher than or equal to <code>Integer.MAX_VALUE</code>
*
* @param x The double to check
* @return True if the given double passes the tests, false otherwise
*/
public boolean isInteger(double x) {
return Math.floor(x)==Math.ceil(x) && x>Integer.MIN_VALUE && x<Integer.MAX_VALUE;
}
/**
* Merge two stacks to create a new stack.
* The second stack (<code>y</code>) is placed AFTER the first stack (<code>x</code>)
* Note: Both the input stacks are being cleared !
*
* @param x The first stack
* @param y The second stack
* @return The merged stack
*/
public Stack merge(Stack x, Stack y) {
if(debug) {
System.out.println("MERGE");
System.out.println("~ X ~");
x.forcePrint();
System.out.println("~ Y ~");
y.forcePrint();
}
Stack z=new Stack(stacksize, this);
while(y.length()>0) {
z.push(y.pop());
}
while(x.length()>0) {
z.push(x.pop());
}
z.reverse();
if(debug) {
System.out.println("~ Z ~");
z.forcePrint();
}
return z;
}
/**
* The required-by-Java method main()
* <code>public static Fish fish</code> is for FUN!
*/
public static Fish fish;
public static void main(String[] args) {
fish=new Fish(args);
}
}
/**
* The enum for all possible directions in the 2D world; RIGHT, LEFT, DOWN and UP
*
* This class and all function in it use the Creative Commons BY-SA 4.0 license below.
*
* You are free to:
* Share — copy and redistribute the material in any medium or format
* Adapt — remix, transform, and build upon the material for any purpose, even commercially.
*
* Gotten from http://creativecommons.org/licenses/by-sa/4.0/
*
* But seriously, who uses this class ? :P
*
* @author Charlie Bouthoorn
*
*/
enum Directions {
RIGHT, LEFT, DOWN, UP;
/**
* Move the given position according to the current direction
*
* @param pos The old position
* @return The new position
*/
public int[] move(int[] pos) {
switch(this) {
case RIGHT:
pos[0]+=1;
break;
case LEFT:
pos[0]-=1;
break;
case DOWN:
pos[1]+=1;
break;
case UP:
pos[1]-=1;
break;
default:
break;
}
return pos;
}
/**
* Get the new direction corresponding to the given character and the current direction
*
* @param c
* @return
*/
public Directions get(char c) {
switch(c) {
case '>':
return RIGHT;
case '<':
return LEFT;
case '^':
return UP;
case 'v':
return DOWN;
case '/':
switch(this) {
case RIGHT:
return UP;
case LEFT:
return DOWN;
case DOWN:
return LEFT;
case UP:
return RIGHT;
default:
return null;
}
case '\\':
switch(this) {
case RIGHT:
return DOWN;
case LEFT:
return UP;
case DOWN:
return RIGHT;
case UP:
return LEFT;
default:
return null;
}
case '|':
if(this==UP || this==DOWN) return this;
else if(this==RIGHT) return LEFT;
else if(this==LEFT) return RIGHT;
else return null;
case '_':
if(this==RIGHT || this==LEFT) return this;
else if(this==UP) return DOWN;
else if(this==DOWN) return UP;
else return null;
case 'x':
int min=1, max=4;
int ran=new Random().nextInt(max-min)+min;
switch(ran) {
case 1:
return RIGHT;
case 2:
return LEFT;
case 3:
return UP;
case 4:
return DOWN;
default:
// Impossible
System.err.println("Something impossible happened in the Random function :S");
return null;
}
case '#':
if(this==RIGHT) return LEFT;
else if(this==LEFT) return RIGHT;
else if(this==UP) return DOWN;
else if(this==DOWN) return UP;
else return null;
default:
return null;
}
}
}
/**
* A stack used by Fish
*
* This class and all function in it use the Creative Commons BY-SA 4.0 license below,
* except the <code>reverse()</code> function, that license is given in his JavaDoc
*
* You are free to:
* Share — copy and redistribute the material in any medium or format
* Adapt — remix, transform, and build upon the material for any purpose, even commercially.
*
* Gotten from http://creativecommons.org/licenses/by-sa/4.0/
*
* @author Charlie Bouthoorn
*/
class Stack {
Fish fish;
double[] stack; // The current stack
double[] temp=null; // A temporary stack
int i=-1; // The current index
/**
* Main constructor
*
* @param size The stacksize
* @param fish The fish it belongs to (Fish are mighty kings <code>:D</code>!)
*/
public Stack(int size, Fish fish) {
this.stack=new double[size];
this.fish=fish;
}
/**
* Push a new value to the stack
*
* @param x The value to push
*/
public void push(double x) {
if(fish.debug)
System.out.println("PUSH "+x+" TO "+(i+1));
if(i>=stack.length-1) {
throw new ArrayIndexOutOfBoundsException("Can't push to full stack!");
}
stack[++i]=x;
}
/**
* Pop a value from the stack
*
* @return The top value
*/
public double pop() {
if(fish.debug)
System.out.println("POP "+stack[i]+" FROM "+i);
if(i<=-1) {
throw new ArrayIndexOutOfBoundsException("Can't pop from empty stack!");
}
double x=stack[i];
stack[i]=0;
i--;
return x;
}
/**
* Reverse the stack
*
* This piece of code uses the Creative Commons BY-SA 2.5 license,
* which can be found here: http://creativecommons.org/licenses/by-sa/2.5/
*
* Gotten from "Bill the Lizard" (<code>http://stackoverflow.com/users/1288/bill-the-lizard</code>)
* at StackOverflow at the following link: <code>http://stackoverflow.com/a/3523066/4141219</code>
*/
public void reverse() {
for (int left = 0, right = i; left < right; left++, right--) {
double x = stack[left];
stack[left] = stack[right];
stack[right] = x;
}
}
/**
* Print the values of the stack, if it didn't change since the previous print
*
* @see #forcePrint()
*/
public void print() {
if(!eq(temp,stack)) {
//System.out.println("LENGTH: "+length()+"; i: "+i);
if(length()==-1) {
System.out.println("-- EMPTY --");
return;
}
System.out.println("-----");
int x=0;
for(double d:stack) {
if (x>=length())
break;
System.out.println(x+" "+d);
x++;
}
System.out.println("-----");
temp=stack.clone();
}
}
/**
* Print the values of the stack, no matter what
*/
public void forcePrint() {
temp=null;
print();
}
/**
* Check if the given arrays are having the same values
*
* <code>x.equals(y)</code> doesn't work for me, don't know why ;-;
*
* @param x The first array
* @param y The second array
* @return Whether the arrays have the same values
*/
public boolean eq(double[] x, double[] y) {
if(x==null || y==null ) return x==null && y==null;
if(x.length != y.length) return false;
for(int a=0; a<x.length; a++) {
if(x[a] != y[a])
return false;
}
return true;
}
/**
* @return The length of the stack (The current index + 1)
*/
public int length() {
return i+1;
}
/**
* Do the action given by c
*
* @param c The action, one of <code>:!$@}{rl</code> (No, I'm not swearing :|)
*/
public void action(char c) {
double x,y,z;
switch(c) {
case ':':
if(fish.debug)
System.out.println("COPY");
x=pop();
push(x);
push(x);
break;
case '~':
if(fish.debug)
System.out.println("REMOVE");
x=pop();
break;
case '$':
if(fish.debug)
System.out.println("SWAP 2");
x=pop();
y=pop();
push(x);
push(y);
break;
case '@':
if(fish.debug)
System.out.println("SWAP 3");
x=pop();
y=pop();
z=pop();
push(x);
push(z);
push(y);
break;
case '}':
if(fish.debug)
System.out.println("MOVE RIGHT");
x=stack[i];
for(int a=i; a>0; a--) {
stack[a]=stack[a-1];
}
stack[0]=x;
break;
case '{':
if(fish.debug)
System.out.println("MOVE LEFT");
x=stack[0];
for(int a=0; a<i; a++) {
stack[a]=stack[a+1];
}
stack[i]=x;
break;
case 'r':
if(fish.debug)
System.out.println("REVERSE");
reverse();
break;
case 'l':
if(fish.debug)
System.out.println("LENGTH");
push(length());
break;
default:
break;
}
}
}
/**
* This class is able to read and write to files.
*
* This class and all function in it use the Creative Commons BY-SA 4.0 license below.
*
* You are free to:
* Share — copy and redistribute the material in any medium or format
* Adapt — remix, transform, and build upon the material for any purpose, even commercially.
*
* Gotten from http://creativecommons.org/licenses/by-sa/4.0/
*
* @author Charlie Bouthoorn
*/
class FileIO {
File file;
int bufSize=64*1024;
char newLine='\n';
/**
* Construct a FileIO for the file with filename <code>filename</code>
*
* @param filename The filename for the file to use
*/
public FileIO(String filename) {
this(new File(filename));
}
/**
* Construct a FileIO for the file <code>file</code>
*
* @param file The file to use
*/
public FileIO(File file) {
this.file=file;
}
/**
* Get all data from <code>file</code> in an array, splitted at a new-line character (<code>\n</code>)
*
* @see #writeData(String[])
* @return An array of all the lines in <code>file</code>
* @throws NullPointerException When <code>file</code> does not exist
* @throws FileNotFoundException When <code>file</code> is not found
* @throws IOException When I failed to read <code>file</code>
*
*/
public String[] readData() throws NullPointerException, FileNotFoundException, IOException {
synchronized(this.file) {
if(!this.file.exists()) {
throw new NullPointerException("File \""+this.file+"\" does not exist!");
}
if(!this.file.canRead()) {
throw new IOException("File \""+this.file+"\" can't be read!");
}
try(FileInputStream fis=new FileInputStream(this.file)) {
byte[] buf=new byte[this.bufSize];
int bytes=fis.read(buf);
String data=bytes<0 ? "":new String(buf, 0, bytes);
return data.split(this.newLine+"");
}
}
}
/**
* Write the data from <code>data</code> to file <code>file</code>
*
* @see #getData()
* @param data The data to save
* @throws FileNotFoundException When <code>file</code> is not found
* @throws IOException When I failed to write to <code>file</code>
*/
public void writeData(String[] data) throws FileNotFoundException, IOException {
synchronized(this.file) {
StringBuilder sb=new StringBuilder();
for(String line:data) sb.append(line+'\n');
try(FileOutputStream fos=new FileOutputStream(this.file)) {
byte[] buf=sb.toString().getBytes();
fos.write(buf);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment