Monday, February 8, 2010

I had the idea of making a compiler for the 01_ programming language that compiled to the Java Virtual Machine (JVM). For starters, I wrote a few classes for the runtime library.
  • rt01_.val: For values in the only datatype in 01_, a list of bits. I included the trampoline() method, which must be called before evaluation, to avoid stack overflows, and to allow discarded values to be garbage collected.
  • rt01_.constant: For literal constants. I decided on representing the constant as a string of 0 and 1, since strings are the only way to represent arbitrary length constants in the Java class file format without needing initialization code to construct it. Additionally, the same string in the constant pool could be reused as the name of the field holding that constant. Compiling to Java means the field names have to be valid Java identifiers, but they don't have to be when compiling directly to Java class files.
  • rt01_.concat: For the only operation in 01_, list concatenation.
  • rt01_.input: For reading input as a list of bits.
  • rt01_.function: To be extended by classes representing 01_ functions. They should receive their arguments in the constructor, and build their results in eval(), and have a main() for being invoked from the command line.

Then, to test it out, I started with using it to write an interpreter. At first, it was getting OutOfMemoryErrors. I had written Function.eval() as

public rt01_.function eval(final rt01_.val[] args) {
return new rt01_.function() {
protected rt01_.val eval() {
for (Def def : defs) {
rt01_.val val = def.eval(args);
if (val != null)
return val;
}
throw new RuntimeException(...);
}
};
}

The capture of args in the anonymous class prevented the arguments from being garbage collected. Once I fixed that, the interpreter ran much faster than my previous interpreter in Java, faster than my interpreter in C, and even slightly faster than my interpreter in Haskell. Once I implemented the interpreter, it was a simple matter to extend it to compile to Java. The compiled code was maybe 5-10% faster than the interpreter.

I want to compile directly to class files rather than to Java, which will take a lot more work. This interpreter and compiler to Java was pretty much a weekend hack. I imagine the generated code would be pretty much the same as compiling to Java. The main difference is that the function names wouldn't have to be as heavily mangled, since they would no longer have to be valid Java identifiers, and would not have to avoid Java reserved words. Also, the SourceFile and LineNumberTable attributes could point to the 01_ source.

Here is the runtime:

package rt01_;

public abstract class val {
protected val trampoline() {
return null;
}

public boolean nil() {
return false;
}

public abstract boolean head();
public abstract val tail();

public static final val NIL = new val() {
public boolean nil() {
return true;
}

public boolean head() {
throw new NullPointerException();
}

public val tail() {
throw new NullPointerException();
}
};

public static val trampoline(val val) {
for (val v = val.trampoline(); v != null; v = val.trampoline())
val = v;
return val;
}
}

package rt01_;

public class constant extends val {
private String bits;
private int index;
private val tail = null;

public constant(String bits) {
this(bits, 0);
}

private constant(String bits, int index) {
this.bits = bits;
this.index = index;
}

protected val trampoline() {
if (index >= bits.length())
return NIL;
return null;
}

public boolean head() {
return '1' == bits.charAt(index);
}

public val tail() {
if (tail == null) {
if (index + 1 < bits.length())
tail = new constant(bits, index + 1);
else
tail = NIL;
}
return tail;
}
}

package rt01_;

public class concat extends val {
private val first;
private val second;
private val tail = null;

public concat(val first, val second) {
this.first = first;
this.second = second;
}

protected val trampoline() {
first = trampoline(first);
if (first.nil())
return second;
else
return null;
}

public boolean head() {
return first.head();
}

public val tail() {
if (tail == null)
tail = new concat(first.tail(), second);
return tail;
}
}

package rt01_;

import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;

public class input extends val {
private InputStream in;
private int byt;
private int bit;
private val tail = null;

public input(String file) throws IOException {
this(new FileInputStream(file));
}

public input(InputStream in) {
this(in, -1, 0);
}

private input(InputStream in, int byt, int bit) {
this.in = in;
this.byt = byt;
this.bit = bit;
}

protected val trampoline() {
if (bit == 0) {
try {
byt = in.read();
} catch (IOException e) {
throw new RuntimeException(e);
}
if (byt < 0)
return NIL;
bit = 128;
}
return null;
}

public boolean head() {
return (byt & bit) != 0;
}

public val tail() {
if (tail == null)
tail = new input(in, byt, bit >> 1);
return tail;
}
}

package rt01_;

import java.io.OutputStream;
import java.io.PrintStream;

public abstract class function extends val {
private val val = null;

protected val trampoline() {
if (val == null)
val = eval();
return val;
}

protected abstract val eval();

public boolean nil() {
throw new RuntimeException();
}

public boolean head() {
throw new RuntimeException();
}

public val tail() {
throw new RuntimeException();
}

public static void main(String[] args, int arity, String name) throws Exception {
Class<?>[] types = new Class<?>[arity];
for (int i = 0; i < arity; i++)
types[i] = val.class;
int index = 0;
boolean bits = false;
if (args.length > 0 && "-bits".equals(args[0])) {
index = 1;
bits = true;
}
val val = (function) Class.forName(name).getDeclaredConstructor(types).newInstance((Object[]) args(args, arity, index));
if (bits)
writeBits(val, System.out);
else
write(val, System.out);
System.out.flush();
}

public static val[] args(String[] args, int arity, int index) throws Exception {
val[] vals = new val[arity];
val stdin = null;
for (int i = 0; i < arity; i++) {
if (index + i < args.length) {
if (!"-".equals(args[index + i])) {
vals[i] = new input(args[index + i]);
} else {
if (stdin == null)
stdin = new input(System.in);
vals[i] = stdin;
}
} else if (stdin == null) {
stdin = new input(System.in);
vals[i] = stdin;
} else {
vals[i] = NIL;
}
}
return vals;
}

public static void writeBits(val val, PrintStream out) throws Exception {
for (;;) {
val = trampoline(val);
if (val.nil())
break;
out.print(val.head() ? "1" : "0");
val = val.tail();
}
}

public static void write(val val, OutputStream out) throws Exception {
int byt = 0;
int bit = 128;
for (;;) {
val = trampoline(val);
if (val.nil())
break;
byt |= val.head() ? bit : 0;
bit >>= 1;
if (bit == 0) {
out.write(byt);
byt = 0;
bit = 128;
}
val = val.tail();
}
}
}

Here is the interpreter and compiler to Java:

public class BoundExpr extends Expr {
private int index;

public BoundExpr(Token token, int index) {
super(token);
this.index = index;
}

public rt01_.val eval(rt01_.val[] bindings) {
return bindings[index];
}

public int getIndex() {
return index;
}
}

import java.io.FileWriter;
import java.io.PrintWriter;

public class Compiler {
public static void main(String[] args) throws Exception {
Parser parser = new Parser();
for (String arg : args)
parser.add(arg);
for (Function function : parser.getFunctions().values()) {
PrintWriter out = new PrintWriter(new FileWriter(function.getMangledName() + ".java"));
function.compile(out);
out.flush();
out.close();
}
}
}

public class ConcatExpr extends Expr {
private Expr first;
private Expr second;

public ConcatExpr(Expr first, Expr second) {
super(first.getToken());
this.first = first;
this.second = second;
}

public rt01_.val eval(rt01_.val[] bindings) {
return new rt01_.concat(first.eval(bindings), second.eval(bindings));
}

public Expr getFirst() {
return first;
}

public Expr getSecond() {
return second;
}
}

import java.util.List;

public class ConstantExpr extends Expr {
private String bits;
private rt01_.constant val;

public ConstantExpr(Token token, boolean[] bits) {
super(token);
StringBuilder sb = new StringBuilder();
for (boolean bit : bits)
sb.append(bit ? "1" : "0");
this.bits = sb.toString();
val = new rt01_.constant(this.bits);
}

public rt01_.val eval(rt01_.val[] bindings) {
return val;
}

public String getBits() {
return bits;
}
}

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Map;

public class Def {
private Token name;
private Pattern[] patterns;
private Token[] body;

private int bindingCount;
private Expr expr;

public Def(Token name, Pattern[] patterns, Token[] body) {
this.name = name;
this.patterns = patterns;
this.body = body;

bindingCount = 0;
for (Pattern pattern : patterns)
if (pattern.isBinding())
bindingCount++;
}

public int getArity() {
return patterns.length;
}

public Token getName() {
return name;
}

private class ParseState {
int index;
Expr expr;
}

public void parse(Map<String,Function> functions) {
if (body.length == 0) {
expr = new ConstantExpr(name, new boolean[0]);
} else {
ParseState state = new ParseState();
state.index = 0;
state.expr = null;
parse(functions, state);
expr = state.expr;
}
body = null;
}

private void parse(Map<String,Function> functions, ParseState state) {
state.expr = null;
parse1(functions, state);
if (state.index < body.length) {
Expr first = state.expr;
parse(functions, state);
state.expr = new ConcatExpr(first, state.expr);
}
}

private void parse1(Map<String,Function> functions, ParseState state) {
Token token = body[state.index];
switch (token.getType()) {
case EQUALS: case DOT:
assert false;
throw new RuntimeException();
case ZERO: case ONE: case NIL:
parseConstant(token, state);
return;
case SYMBOL:
state.index++;
state.expr = binding(token);
if (state.expr != null)
return;
if (!functions.containsKey(token.getSymbol()))
throw new RuntimeException(token.getLocation() + ": unknown function: " + token.getSymbol());
Function function = functions.get(token.getSymbol());
Expr[] args = new Expr[function.getArity()];
for (int i = 0; i < args.length; i++) {
parse1(functions, state);
args[i] = state.expr;
}
state.expr = new FuncallExpr(token, function, args);
}
}

private void parseConstant(Token token, ParseState state) {
ArrayList<Boolean> bits = new ArrayList<Boolean>();
loop: while (state.index < body.length) {
switch (body[state.index].getType()) {
case ZERO:
state.index++;
bits.add(false);
break;
case ONE:
state.index++;
bits.add(true);
break;
case NIL:
state.index++;
default:
break loop;
}
}
state.expr = new ConstantExpr(token, DefReader.toBitArray(bits));
}

private BoundExpr binding(Token token) {
int bindingIndex = 0;
for (Pattern pattern : patterns) {
if (!pattern.isBinding())
continue;
if (pattern.getToken().getSymbol().equals(token.getSymbol()))
return new BoundExpr(token, bindingIndex);
bindingIndex++;
}
return null;
}

public Expr getExpr() {
return expr;
}

public rt01_.val eval(rt01_.val[] args) {
assert args.length == patterns.length;
rt01_.val[] bindings = new rt01_.val[bindingCount];
int bindingIndex = 0;
for (int i = 0; i < args.length; i++) {
rt01_.val val = patterns[i].match(args[i]);
if (val == null)
return null;
if (patterns[i].isBinding())
bindings[bindingIndex++] = val;
}
return expr.eval(bindings);
}

public void compile(int n, PrintWriter out) throws Exception {
out.print(" private rt01_.val m");
out.print(n);
out.println("() {");
if (patterns.length > 0)
out.println(" rt01_.val val;");
int bindingIndex = 0;
for (int i = 0; i < patterns.length; i++) {
Pattern pattern = patterns[i];
boolean[] bits = pattern.getBits();
if (bits.length > 0) {
out.print(" a");
out.print(i);
out.print(" = trampoline(a");
out.print(i);
out.println(");");
}
out.print(" val = a");
out.print(i);
out.println(";");
boolean start = true;
for (boolean bit : bits) {
if (start)
start = false;
else
out.println(" val = trampoline(val);");
out.print(" if (val.nil() || ");
if (bit) out.print("!");
out.println("val.head()) return null;");
out.println(" val = val.tail();");
}
if (pattern.isLiteral()) {
out.println(" val = trampoline(val);");
out.println(" if (!val.nil()) return null;");
} else if (pattern.isBinding()) {
out.print(" rt01_.val b");
out.print(bindingIndex);
out.println(" = val;");
bindingIndex++;
}
}
out.print(" return ");
compileExpr(expr, out);
out.println(";");
out.println(" }");
}

private void compileExpr(Expr e, PrintWriter out) throws Exception {
if (e instanceof BoundExpr) {
out.print("b");
out.print(((BoundExpr) e).getIndex());
} else if (e instanceof ConstantExpr) {
out.print("_");
out.print(((ConstantExpr) e).getBits());
} else if (e instanceof ConcatExpr) {
out.print("new rt01_.concat(");
compileExpr(((ConcatExpr) e).getFirst(), out);
out.print(",");
compileExpr(((ConcatExpr) e).getSecond(), out);
out.print(")");
} else if (e instanceof FuncallExpr) {
out.print("new ");
out.print(((FuncallExpr) e).getFunction().getMangledName());
out.print("(");
boolean start = true;
for (Expr arg : ((FuncallExpr) e).getArgs()) {
if (start)
start = false;
else
out.print(",");
compileExpr(arg, out);
}
out.print(")");
}
}
}

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class DefReader implements Iterator<Def> {
private Iterator<Token> tokenizer;
private Def def = null;

public DefReader(Iterator<Token> tokenizer) {
this.tokenizer = tokenizer;
}

public boolean hasNext() {
if (def == null)
def = readNext();
return def != null;
}

public Def next() {
if (def == null)
return readNext();
Def result = def;
def = null;
return result;
}

public void remove() {
}

private Def readNext() {
if (!tokenizer.hasNext())
return null;
Token name = tokenizer.next();
if (name.getType() != Token.Type.SYMBOL)
throw new RuntimeException(name.getLocation() + ": symbol expected");
ArrayList<Pattern> patterns = readPatterns(name);
ArrayList<Token> body = new ArrayList<Token>();
for (;;) {
if (!tokenizer.hasNext())
throw new RuntimeException(name.getLocation() + ": incomplete definition");
Token token = tokenizer.next();
if (token.getType() == Token.Type.DOT)
break;
body.add(token);
}
return new Def(name, patterns.toArray(new Pattern[patterns.size()]), body.toArray(new Token[body.size()]));
}

private ArrayList<Pattern> readPatterns(Token name) {
ArrayList<Pattern> patterns = new ArrayList<Pattern>();
ArrayList<Boolean> bits = new ArrayList<Boolean>();
Token startToken = null;
for (;;) {
if (!tokenizer.hasNext())
throw new RuntimeException(name.getLocation() + ": incomplete definition");
Token token = tokenizer.next();
if (startToken == null)
startToken = token;
switch (token.getType()) {
case EQUALS:
if (bits.size() > 0)
patterns.add(new Pattern(startToken, toBitArray(bits), null));
return patterns;
case ZERO:
bits.add(false);
break;
case ONE:
bits.add(true);
break;
case DOT:
case NIL:
case SYMBOL:
patterns.add(new Pattern(startToken, toBitArray(bits), token));
bits.clear();
startToken = null;
break;
}
}
}

public static boolean[] toBitArray(List<Boolean> list) {
boolean[] bits = new boolean[list.size()];
for (int i = 0; i < list.size(); i++)
bits[i] = list.get(i);
return bits;
}
}
public abstract class Expr {
private Token token;

protected Expr(Token token) {
this.token = token;
}

public abstract rt01_.val eval(rt01_.val[] bindings);

public Token getToken() {
return token;
}
}

public class FuncallExpr extends Expr {
private Function function;
private Expr[] args;

public FuncallExpr(Token token, Function function, Expr[] args) {
super(token);
this.function = function;
this.args = args;
}

public rt01_.val eval(rt01_.val[] bindings) {
rt01_.val[] argVals = new rt01_.val[args.length];
for (int i = 0; i < argVals.length; i++)
argVals[i] = args[i].eval(bindings);
return function.eval(argVals);
}

public Function getFunction() {
return function;
}

public Expr[] getArgs() {
return args;
}
}

import java.io.File;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Map;

public class Function {
private Def[] defs;

public Function(Def[] defs) {
this.defs = defs;
}

public int getArity() {
return defs[0].getArity();
}

public void parse(Map<String,Function> functions) {
for (Def def : defs)
def.parse(functions);
}

private class Result extends rt01_.function {
private rt01_.val[] args;
Result(rt01_.val[] args) {
this.args = args;
}

protected rt01_.val eval() {
for (Def def : defs) {
rt01_.val val = def.eval(args);
if (val != null) {
args = null; // let arguments get garbage collected
return val;
}
}
Token name = defs[defs.length-1].getName();
throw new RuntimeException(name.getLocation() + ": no matching pattern in definition of " + name.getSymbol());
}
}

public rt01_.function eval(rt01_.val[] args) {
return new Result(args);
}

private static final HashSet<String> reserved = new HashSet<String>();
static {
reserved.add("abstract");
reserved.add("assert");
reserved.add("boolean");
reserved.add("break");
reserved.add("byte");
reserved.add("case");
reserved.add("catch");
reserved.add("char");
reserved.add("class");
reserved.add("const");
reserved.add("continue");
reserved.add("default");
reserved.add("do");
reserved.add("double");
reserved.add("else");
reserved.add("enum");
reserved.add("extends");
reserved.add("false");
reserved.add("final");
reserved.add("finally");
reserved.add("float");
reserved.add("for");
reserved.add("goto");
reserved.add("if");
reserved.add("implements");
reserved.add("import");
reserved.add("instanceof");
reserved.add("int");
reserved.add("interface");
reserved.add("long");
reserved.add("native");
reserved.add("new");
reserved.add("null");
reserved.add("package");
reserved.add("private");
reserved.add("protected");
reserved.add("public");
reserved.add("return");
reserved.add("short");
reserved.add("static");
reserved.add("switch");
reserved.add("synchronized");
reserved.add("strictfp");
reserved.add("super");
reserved.add("this");
reserved.add("throw");
reserved.add("throws");
reserved.add("transient");
reserved.add("true");
reserved.add("try");
reserved.add("void");
reserved.add("volatile");
reserved.add("while");
}

public static String mangle(String name) {
StringBuilder sb = new StringBuilder();
if (reserved.contains(name))
sb.append("__");
for (int i = 0; i < name.length(); i++)
switch (name.charAt(i)) {
case '0': case '1': case '2': case '3': case '4': case '5':
case '6': case '7': case '8': case '9':
if (i == 0)
sb.append("__");
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
case 's': case 't': case 'u': case 'v': case 'w': case 'x':
case 'y': case 'z':
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
case 'Y': case 'Z':
sb.append(name.charAt(i));
break;
default:
sb.append('_').append(Integer.toHexString(name.charAt(i))).append('_');
}
return sb.toString();
}

public String getMangledName() {
return mangle(defs[0].getName().getSymbol());
}

public void compile(PrintWriter out) throws Exception {
int arity = getArity();
String name = getMangledName();
out.print("public class ");
out.print(name);
out.println(" extends rt01_.function {");
for (int i = 0; i < arity; i++) {
out.print(" private rt01_.val a");
out.print(i);
out.println(";");
}
out.print(" public ");
out.print(name);
out.print("(");
for (int i = 0; i < arity; i++) {
if (i > 0)
out.print(",");
out.print("rt01_.val p");
out.print(i);
}
out.println(") {");
for (int i = 0; i < arity; i++) {
out.print(" a");
out.print(i);
out.print("=p");
out.print(i);
out.println(";");
}
out.println(" }");
out.println(" protected rt01_.val eval() {");
out.println(" rt01_.val val;");
out.print(" if (");
for (int i = 0; i < defs.length; i++) {
out.print("(val = m");
out.print(i);
out.print("()) == null && ");
}
out.print("true) throw new RuntimeException(\"");
out.print(getLocation(defs[defs.length-1].getName()));
out.println(": pattern match failed\");");
for (int i = 0; i < arity; i++) {
out.print(" a");
out.print(i);
out.println(" = null;");
}
out.println(" return val;");
out.println(" }");
HashSet<String> constants = new HashSet<String>();
for (Def def : defs)
collectConstants(constants, def.getExpr());
for (String constant : constants) {
out.print(" private static final rt01_.val _");
out.print(constant);
if (constant.length() == 0) {
out.println(" = NIL;");
} else {
out.print(" = new rt01_.constant(\"");
out.print(constant);
out.println("\");");
}
}
for (int i = 0; i < defs.length; i++)
defs[i].compile(i, out);
out.println(" public static void main(String[] args) throws Exception {");
out.print(" main(args,");
out.print(arity);
out.print(",\"");
out.print(name);
out.println("\");");
out.println(" }");
out.println("}");
}

private void collectConstants(HashSet<String> constants, Expr expr) {
if (expr instanceof ConstantExpr) {
constants.add(((ConstantExpr) expr).getBits());
} else if (expr instanceof ConcatExpr) {
collectConstants(constants, ((ConcatExpr) expr).getFirst());
collectConstants(constants, ((ConcatExpr) expr).getSecond());
} else if (expr instanceof FuncallExpr) {
for (Expr arg : ((FuncallExpr) expr).getArgs())
collectConstants(constants, arg);
}
}

public static String getLocation(Token token) {
return new File(token.getFileName()).getName() + ":" + token.getLineNumber() + ":" + token.getColumn();
}
}

import java.io.File;
import java.util.Map;

public class Interpreter {
public static void main(String[] args) throws Exception {
Parser parser = new Parser();
int i;
String fname = null;
for (i = 0; i < args.length && !args[i].equals("-"); i++) {
parser.add(args[i]);
fname = getFunction(args[i]);
}
if (i + 1 < args.length) {
fname = args[i+1];
i = i + 2;
}
boolean writeBits = false;
if (i < args.length && "-bits".equals(args[i])) {
i++;
writeBits = true;
}
Function f = parser.getFunctions().get(fname);
if (writeBits)
rt01_.function.writeBits(f.eval(rt01_.function.args(args, f.getArity(), i)), System.out);
else
rt01_.function.write(f.eval(rt01_.function.args(args, f.getArity(), i)), System.out);
System.out.flush();
}

private static String getFunction(String fileName) {
String fn = new File(fileName).getName();
if (fn.indexOf('.') > 0)
return fn.substring(0, fn.indexOf('.'));
else
return fn;
}
}

import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class Parser {
private HashMap<String,Integer> arities = new HashMap<String,Integer>();
private HashMap<String,List<Def>> functions = new HashMap<String,List<Def>>();

public void add(String fileName) throws Exception {
add(new Tokenizer(fileName));
}

public void add(Reader in, String fileName, int lineNumber, int column) {
add(new Tokenizer(in, fileName, lineNumber, column));
}

public void add(Iterator<Token> tokenizer) {
addDefs(new DefReader(tokenizer));
}

public void addDefs(Iterator<Def> defs) {
while (defs.hasNext()) {
Def def = defs.next();
Token name = def.getName();
if (arities.containsKey(name.getSymbol())) {
if (def.getArity() != arities.get(name.getSymbol()))
throw new RuntimeException(name.getLocation() + ": arity mismatch in definition of " + name.getSymbol());
} else {
arities.put(name.getSymbol(), def.getArity());
functions.put(name.getSymbol(), new ArrayList<Def>());
}
functions.get(name.getSymbol()).add(def);
}
}

public Map<String,Function> getFunctions() {
HashMap<String,Function> fns = new HashMap<String,Function>();
for (Map.Entry<String,List<Def>> entry : functions.entrySet()) {
List<Def> defs = entry.getValue();
fns.put(entry.getKey(), new Function(defs.toArray(new Def[defs.size()])));
}
for (Function function : fns.values())
function.parse(fns);
return fns;
}
}

public class Pattern {
private Token startToken;
private boolean[] bits;
private Token token;
private String fileName;
private int lineNumber;

public Pattern(Token startToken, boolean[] bits, Token token) {
this.startToken = startToken;
this.bits = bits;
this.token = token;
}

public Token getStartToken() {
return startToken;
}

public boolean[] getBits() {
return bits;
}

public Token getToken() {
return token;
}

public boolean isLiteral() {
return token == null || token.getType() == Token.Type.NIL;
}

public boolean isWild() {
return token != null && token.getType() == Token.Type.DOT;
}

public boolean isBinding() {
return token != null && token.getType() == Token.Type.SYMBOL;
}

public rt01_.val match(rt01_.val val) {
for (int i = 0; i < bits.length; i++) {
val = rt01_.val.trampoline(val);
if (val.nil() || val.head() != bits[i])
return null;
val = val.tail();
}
if (isLiteral()) {
val = rt01_.val.trampoline(val);
if (!val.nil())
return null;
}
return val;
}
}

public class Token {
public enum Type {
ZERO, ONE, NIL, DOT, EQUALS, SYMBOL
}

private Type type;
private String symbol;
private String fileName;
private int lineNumber;
private int column;

public Token(Type type, String symbol, String fileName, int lineNumber, int column) {
this.type = type;
this.symbol = symbol;
this.fileName = fileName;
this.lineNumber = lineNumber;
this.column = column;
}

public Type getType() {
return type;
}

public String getSymbol() {
return symbol;
}

public String getFileName() {
return fileName;
}

public int getLineNumber() {
return lineNumber;
}

public int getColumn() {
return column;
}

public String getLocation() {
return fileName + ":" + lineNumber + ":" + column;
}
}

import java.io.FileReader;
import java.io.Reader;
import java.util.Iterator;

public class Tokenizer implements Iterator<Token> {
private Reader in;
private String fileName;
private int lineNumber;
private int column = 0;

private int pushback = -1;
private Token next;

public Tokenizer(String fileName) throws Exception {
this(new FileReader(fileName), fileName, 1, 0);
}

public Tokenizer(Reader in, String fileName, int lineNumber, int column) {
this.in = in;
this.fileName = fileName;
this.lineNumber = lineNumber;
}

public boolean hasNext() {
if (next == null)
readNext();
return next != null;
}

public Token next() {
if (next == null)
readNext();
Token result = next;
next = null;
return result;
}

public void remove() {
}

private void pushback(int lastChar) {
assert pushback < 0;
pushback = lastChar;
column--;
if (lastChar == '\n')
lineNumber--;
}

private int nextChar() {
int nextChar = -1;
if (pushback >= 0) {
nextChar = pushback;
pushback = -1;
} else {
try {
nextChar = in.read();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
if (nextChar >= 0) {
column++;
if (nextChar == '\n') {
lineNumber++;
column = 0;
}
}
return nextChar;
}

private void readNext() {
for (;;) {
int nextChar = nextChar();
if (nextChar < 0)
return;
int saveLineNumber = lineNumber;
int saveColumn = column;
switch (nextChar) {
case '0':
next = new Token(Token.Type.ZERO, null, fileName, lineNumber, column);
return;
case '1':
next = new Token(Token.Type.ONE, null, fileName, lineNumber, column);
return;
case '_':
next = new Token(Token.Type.NIL, null, fileName, lineNumber, column);
return;
case '.':
next = new Token(Token.Type.DOT, null, fileName, lineNumber, column);
return;
case '=':
nextChar = nextChar();
if (nextChar != '=') {
pushback(nextChar);
next = new Token(Token.Type.EQUALS, null, fileName, saveLineNumber, saveColumn);
return;
}
while (nextChar >= 0 && nextChar != '\n')
nextChar = nextChar();
continue;
case ' ': case '\t': case '\r': case '\n':
continue;
default:
StringBuilder sb = new StringBuilder();
sb.append((char) nextChar);
for (;;) {
nextChar = nextChar();
if (nextChar < 0) {
next = new Token(Token.Type.SYMBOL, sb.toString(), fileName, saveLineNumber, saveColumn);
return;
}
switch (nextChar) {
case '0': case '1': case '_': case '.': case '=':
case ' ': case '\t': case '\r': case '\n':
pushback(nextChar);
next = new Token(Token.Type.SYMBOL, sb.toString(), fileName, saveLineNumber, saveColumn);
return;
default:
sb.append((char) nextChar);
}
}
}
}
}
}


Here is 99 bottles of beer compiled to Java:

public class __2 extends rt01_.function {
private rt01_.val a0;
public __2(rt01_.val p0) {
a0=p0;
}
protected rt01_.val eval() {
rt01_.val val;
if ((val = m0()) == null && true) throw new RuntimeException("99.01_:5:1: pattern match failed");
a0 = null;
return val;
}
private static final rt01_.val _010 = new rt01_.constant("010");
private rt01_.val m0() {
rt01_.val val;
a0 = trampoline(a0);
val = a0;
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || !val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || !val.head()) return null;
val = val.tail();
rt01_.val b0 = val;
return new rt01_.concat(_010,b0);
}
public static void main(String[] args) throws Exception {
main(args,1,"__2");
}
}
public class __33r extends rt01_.function {
public __33r() {
}
protected rt01_.val eval() {
rt01_.val val;
if ((val = m0()) == null && true) throw new RuntimeException("99.01_:8:1: pattern match failed");
return val;
}
private static final rt01_.val _001000000110111101101110001000000111010001101000011001010010000001110111011000010110110001101100 = new rt01_.constant("001000000110111101101110001000000111010001101000011001010010000001110111011000010110110001101100");
private rt01_.val m0() {
return _001000000110111101101110001000000111010001101000011001010010000001110111011000010110110001101100;
}
public static void main(String[] args) throws Exception {
main(args,0,"__33r");
}
}
public class __4 extends rt01_.function {
private rt01_.val a0;
public __4(rt01_.val p0) {
a0=p0;
}
protected rt01_.val eval() {
rt01_.val val;
if ((val = m0()) == null && (val = m1()) == null && (val = m2()) == null && (val = m3()) == null && true) throw new RuntimeException("99.01_:14:1: pattern match failed");
a0 = null;
return val;
}
private static final rt01_.val _ = NIL;
private static final rt01_.val _1 = new rt01_.constant("1");
private static final rt01_.val _1001 = new rt01_.constant("1001");
private static final rt01_.val _0 = new rt01_.constant("0");
private rt01_.val m0() {
rt01_.val val;
a0 = trampoline(a0);
val = a0;
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (!val.nil()) return null;
return _1001;
}
private rt01_.val m1() {
rt01_.val val;
val = a0;
val = trampoline(val);
if (!val.nil()) return null;
return _;
}
private rt01_.val m2() {
rt01_.val val;
a0 = trampoline(a0);
val = a0;
if (val.nil() || val.head()) return null;
val = val.tail();
rt01_.val b0 = val;
return new rt01_.concat(_1,new __4(b0));
}
private rt01_.val m3() {
rt01_.val val;
a0 = trampoline(a0);
val = a0;
if (val.nil() || !val.head()) return null;
val = val.tail();
rt01_.val b0 = val;
return new rt01_.concat(_0,b0);
}
public static void main(String[] args) throws Exception {
main(args,1,"__4");
}
}
public class __5 extends rt01_.function {
private rt01_.val a0;
private rt01_.val a1;
public __5(rt01_.val p0,rt01_.val p1) {
a0=p0;
a1=p1;
}
protected rt01_.val eval() {
rt01_.val val;
if ((val = m0()) == null && (val = m1()) == null && (val = m2()) == null && (val = m3()) == null && true) throw new RuntimeException("99.01_:20:1: pattern match failed");
a0 = null;
a1 = null;
return val;
}
private static final rt01_.val _00110001 = new rt01_.constant("00110001");
private static final rt01_.val _01101110011011110010000001101101011011110111001001100101 = new rt01_.constant("01101110011011110010000001101101011011110111001001100101");
private static final rt01_.val _0011 = new rt01_.constant("0011");
private static final rt01_.val _01110011 = new rt01_.constant("01110011");
private rt01_.val m0() {
rt01_.val val;
a0 = trampoline(a0);
val = a0;
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
a1 = trampoline(a1);
val = a1;
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (!val.nil()) return null;
return new rt01_.concat(_01101110011011110010000001101101011011110111001001100101,new rt01_.concat(new __7(),_01110011));
}
private rt01_.val m1() {
rt01_.val val;
a0 = trampoline(a0);
val = a0;
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
a1 = trampoline(a1);
val = a1;
if (val.nil() || !val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (!val.nil()) return null;
return new rt01_.concat(_00110001,new __7());
}
private rt01_.val m2() {
rt01_.val val;
a0 = trampoline(a0);
val = a0;
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = a1;
rt01_.val b0 = val;
return new rt01_.concat(_0011,new rt01_.concat(new __6(b0),new rt01_.concat(new __7(),_01110011)));
}
private rt01_.val m3() {
rt01_.val val;
val = a0;
rt01_.val b0 = val;
val = a1;
rt01_.val b1 = val;
return new rt01_.concat(_0011,new rt01_.concat(new __6(b0),new rt01_.concat(_0011,new rt01_.concat(new __6(b1),new rt01_.concat(new __7(),_01110011)))));
}
public static void main(String[] args) throws Exception {
main(args,2,"__5");
}
}
public class __6 extends rt01_.function {
private rt01_.val a0;
public __6(rt01_.val p0) {
a0=p0;
}
protected rt01_.val eval() {
rt01_.val val;
if ((val = m0()) == null && (val = m1()) == null && (val = m2()) == null && true) throw new RuntimeException("99.01_:25:1: pattern match failed");
a0 = null;
return val;
}
private static final rt01_.val _ = NIL;
private static final rt01_.val _1 = new rt01_.constant("1");
private static final rt01_.val _0 = new rt01_.constant("0");
private rt01_.val m0() {
rt01_.val val;
val = a0;
val = trampoline(val);
if (!val.nil()) return null;
return _;
}
private rt01_.val m1() {
rt01_.val val;
a0 = trampoline(a0);
val = a0;
if (val.nil() || val.head()) return null;
val = val.tail();
rt01_.val b0 = val;
return new rt01_.concat(new __6(b0),_0);
}
private rt01_.val m2() {
rt01_.val val;
a0 = trampoline(a0);
val = a0;
if (val.nil() || !val.head()) return null;
val = val.tail();
rt01_.val b0 = val;
return new rt01_.concat(new __6(b0),_1);
}
public static void main(String[] args) throws Exception {
main(args,1,"__6");
}
}
public class __7 extends rt01_.function {
public __7() {
}
protected rt01_.val eval() {
rt01_.val val;
if ((val = m0()) == null && true) throw new RuntimeException("99.01_:28:1: pattern match failed");
return val;
}
private static final rt01_.val _00100000011000100110111101110100011101000110110001100101 = new rt01_.constant("00100000011000100110111101110100011101000110110001100101");
private rt01_.val m0() {
return _00100000011000100110111101110100011101000110110001100101;
}
public static void main(String[] args) throws Exception {
main(args,0,"__7");
}
}
public class __8 extends rt01_.function {
private rt01_.val a0;
private rt01_.val a1;
public __8(rt01_.val p0,rt01_.val p1) {
a0=p0;
a1=p1;
}
protected rt01_.val eval() {
rt01_.val val;
if ((val = m0()) == null && (val = m1()) == null && true) throw new RuntimeException("99.01_:32:1: pattern match failed");
a0 = null;
a1 = null;
return val;
}
private rt01_.val m0() {
rt01_.val val;
val = a0;
rt01_.val b0 = val;
a1 = trampoline(a1);
val = a1;
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (!val.nil()) return null;
return new __4(b0);
}
private rt01_.val m1() {
rt01_.val val;
val = a0;
rt01_.val b0 = val;
val = a1;
return b0;
}
public static void main(String[] args) throws Exception {
main(args,2,"__8");
}
}
public class __9 extends rt01_.function {
private rt01_.val a0;
private rt01_.val a1;
public __9(rt01_.val p0,rt01_.val p1) {
a0=p0;
a1=p1;
}
protected rt01_.val eval() {
rt01_.val val;
if ((val = m0()) == null && (val = m1()) == null && true) throw new RuntimeException("99.01_:36:1: pattern match failed");
a0 = null;
a1 = null;
return val;
}
private static final rt01_.val _1001 = new rt01_.constant("1001");
private static final rt01_.val _0010110000100000 = new rt01_.constant("0010110000100000");
private static final rt01_.val _001011100000101001010100011000010110101101100101001000000110111101101110011001010010000001100100011011110111011101101110001000000110000101101110011001000010000001110000011000010111001101110011001000000110100101110100001000000110000101110010011011110111010101101110011001000010110000100000 = new rt01_.constant("001011100000101001010100011000010110101101100101001000000110111101101110011001010010000001100100011011110111011101101110001000000110000101101110011001000010000001110000011000010111001101110011001000000110100101110100001000000110000101110010011011110111010101101110011001000010110000100000");
private static final rt01_.val _0010111000001010 = new rt01_.constant("0010111000001010");
private static final rt01_.val _0000 = new rt01_.constant("0000");
private static final rt01_.val _00101110000010100100011101101111001000000111010001101111001000000111010001101000011001010010000001110011011101000110111101110010011001010010000001100001011011100110010000100000011000100111010101111001001000000111001101101111011011010110010100100000011011010110111101110010011001010010110000100000 = new rt01_.constant("00101110000010100100011101101111001000000111010001101111001000000111010001101000011001010010000001110011011101000110111101110010011001010010000001100001011011100110010000100000011000100111010101111001001000000111001101101111011011010110010100100000011011010110111101110010011001010010110000100000");
private static final rt01_.val _001011100000101000001010 = new rt01_.constant("001011100000101000001010");
private rt01_.val m0() {
rt01_.val val;
a0 = trampoline(a0);
val = a0;
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
a1 = trampoline(a1);
val = a1;
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (val.nil() || val.head()) return null;
val = val.tail();
val = trampoline(val);
if (!val.nil()) return null;
return new rt01_.concat(new __2(new __5(_0000,_0000)),new rt01_.concat(new b(),new rt01_.concat(new __33r(),new rt01_.concat(_0010110000100000,new rt01_.concat(new __5(_0000,_0000),new rt01_.concat(new b(),new rt01_.concat(_00101110000010100100011101101111001000000111010001101111001000000111010001101000011001010010000001110011011101000110111101110010011001010010000001100001011011100110010000100000011000100111010101111001001000000111001101101111011011010110010100100000011011010110111101110010011001010010110000100000,new rt01_.concat(new __5(_1001,_1001),new rt01_.concat(new b(),new rt01_.concat(new __33r(),_0010111000001010))))))))));
}
private rt01_.val m1() {
rt01_.val val;
val = a0;
rt01_.val b0 = val;
val = a1;
rt01_.val b1 = val;
return new rt01_.concat(new __5(b0,b1),new rt01_.concat(new b(),new rt01_.concat(new __33r(),new rt01_.concat(_0010110000100000,new rt01_.concat(new __5(b0,b1),new rt01_.concat(new b(),new rt01_.concat(_001011100000101001010100011000010110101101100101001000000110111101101110011001010010000001100100011011110111011101101110001000000110000101101110011001000010000001110000011000010111001101110011001000000110100101110100001000000110000101110010011011110111010101101110011001000010110000100000,new rt01_.concat(new __5(new __8(b0,b1),new __4(b1)),new rt01_.concat(new b(),new rt01_.concat(new __33r(),new rt01_.concat(_001011100000101000001010,new __9(new __8(b0,b1),new __4(b1)))))))))))));
}
public static void main(String[] args) throws Exception {
main(args,2,"__9");
}
}
public class __99 extends rt01_.function {
public __99() {
}
protected rt01_.val eval() {
rt01_.val val;
if ((val = m0()) == null && true) throw new RuntimeException("99.01_:38:1: pattern match failed");
return val;
}
private static final rt01_.val _1001 = new rt01_.constant("1001");
private rt01_.val m0() {
return new __9(_1001,_1001);
}
public static void main(String[] args) throws Exception {
main(args,0,"__99");
}
}
public class b extends rt01_.function {
public b() {
}
protected rt01_.val eval() {
rt01_.val val;
if ((val = m0()) == null && true) throw new RuntimeException("99.01_:2:1: pattern match failed");
return val;
}
private static final rt01_.val _0010000001101111011001100010000001100010011001010110010101110010 = new rt01_.constant("0010000001101111011001100010000001100010011001010110010101110010");
private rt01_.val m0() {
return _0010000001101111011001100010000001100010011001010110010101110010;
}
public static void main(String[] args) throws Exception {
main(args,0,"b");
}
}

Monday, February 1, 2010

Here is a Parenthesis Hell interpreter in Java. It uses a mutable trie for the letrec bindings, where the interpreter in Haskell uses an immutable trie. Also, phi.Value.iterator() is a bit more complicated than the Haskell equivalent, which would be

data Flattened = HEAD | TAIL

flattened :: Value -> [Flattened]
flattened Nil = []
flattened (Cons head tail) = HEAD : flattened head ++ TAIL : flattened tail

I originally wrote the interpreter as multiple classes, but contained all of them as nested classes in a single class for this post. Some of the code would have been more straightforward had it been written recursively, but then larger data would cause stack overflows.

import java.io.EOFException;
import java.io.FileReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.LinkedList;

public class phi {
public static class EnumTrie<K extends Enum<K>,V> {
private EnumMap<K,EnumTrie<K,V>> map;
private V value = null;

public EnumTrie(Class<K> keyType) {
map = new EnumMap<K,EnumTrie<K,V>>(keyType);
}

private EnumTrie(EnumTrie<K,V> trie) {
map = new EnumMap<K,EnumTrie<K,V>>(trie.map);
map.clear();
}

public V getValue() {
return value;
}

public EnumTrie<K,V> getSubtrie(K k) {
return map.get(k);
}

public EnumTrie<K,V> findPrefix(Iterator<K> iterator) {
EnumTrie<K,V> trie = this;
while (trie != null && iterator.hasNext())
trie = trie.map.get(iterator.next());
return trie;
}

public V get(Iterable<K> key) {
EnumTrie<K,V> trie = findPrefix(key.iterator());
return trie != null ? trie.value : null;
}

private EnumTrie<K,V> addPrefix(Iterator<K> iterator) {
EnumTrie<K,V> trie = this;
while (iterator.hasNext()) {
K k = iterator.next();
EnumTrie<K,V> subtrie = trie.map.get(k);
if (subtrie == null) {
subtrie = new EnumTrie<K,V>(trie);
trie.map.put(k, subtrie);
}
trie = subtrie;
}
return trie;
}

public V put(Iterable<K> key, V value) {
EnumTrie<K,V> trie;
if (value != null) {
trie = addPrefix(key.iterator());
} else {
trie = findPrefix(key.iterator());
if (trie == null)
return null;
}
trie.value = value;
return value;
}
}

public static abstract class Value implements Iterable<Value.Flattened> {
public enum Flattened { HEAD, TAIL }

public static final Value NIL = new NilValue();

public boolean isNil() {
return false;
}

public abstract Value getHead();
public abstract Value getTail();

public static Value read(Reader reader) throws IOException {
LinkedList<Value> stack = new LinkedList<Value>();
LinkedList<Boolean> open = new LinkedList<Boolean>();
for (;;) {
int c = reader.read();
if (c < 0) {
throw new EOFException();
} else if (c == '(') {
open.push(true);
} else if (c == ')') {
if (open.size() == 0)
throw new EOFException();
if (open.pop()) {
if (open.size() == 0)
return NIL;
open.push(false);
stack.push(NIL);
} else {
Value value = new Pair(stack.pop(), NIL);
while (!open.pop())
value = new Pair(stack.pop(), value);
if (open.size() == 0)
return value;
stack.push(value);
open.push(false);
}
}
}
}

public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('(');
toString(sb);
sb.append(')');
return sb.toString();
}

private void toString(StringBuilder sb) {
if (!isNil()) {
sb.append('(');
getHead().toString(sb);
sb.append(')');
getTail().toString(sb);
}
}

public Iterator<Flattened> iterator() {
final LinkedList<Value> stack = new LinkedList<Value>();
return new Iterator<Flattened>() {
private Value value = Value.this;

public boolean hasNext() {
return !value.isNil() || stack.size() > 0;
}

public Flattened next() {
if (value.isNil()) {
value = stack.pop().getTail();
return Flattened.TAIL;
} else {
stack.push(value);
value = value.getHead();
return Flattened.HEAD;
}
}

public void remove() {
throw new UnsupportedOperationException();
}
};
}

private static class NilValue extends Value {
public boolean isNil() {
return true;
}

public Value getHead() {
return NilValue.this;
}

public Value getTail() {
return NilValue.this;
}
}

public static class Pair extends Value {
private Value head;
private Value tail;

public Pair(Value head, Value tail) {
this.head = head;
this.tail = tail;
}

public Value getHead() {
return head;
}

public Value getTail() {
return tail;
}
}

public static void write(Value value, OutputStream out) throws IOException {
int bit = 128;
int b = 0;
while (!value.isNil()) {
Value head = value.getHead();
if (head.isNil()) {
value = value.getTail();
} else {
b |= bit;
value = head;
}
if (bit == 1) {
out.write(b);
bit = 128;
b = 0;
} else {
bit >>= 1;
}
}
}

public static class InputValue extends Value {
private InputStream in;
private int bit;
private int b;
private Value head = null;
private Value tail = null;

public InputValue(InputStream in) {
this(in, 128, 0);
}

private InputValue(InputStream in, int bit, int b) {
this.in = in;
this.bit = bit;
this.b = b;
}

private void init() {
if (head == null) {
head = Value.NIL;
tail = Value.NIL;
if (bit == 0) {
try {
b = in.read();
} catch (IOException e) {
b = -1;
}
if (b < 0)
return;
bit = 128;
}
if ((b & bit) != 0)
head = new InputValue(in, bit>>1, b);
else
tail = new InputValue(in, bit>>1, b);
}
}

public Value getHead() {
init();
return head;
}

public Value getTail() {
init();
return tail;
}
}
}

public static class Scope {
private Scope outer;
private EnumTrie<Value.Flattened,Interp.Op> bindings;
private Value arg;

public Scope(Scope outer, EnumTrie<Value.Flattened,Interp.Op> bindings, Value arg) {
this.outer = outer;
this.bindings = bindings;
this.arg = arg;
}

public Value getArg() {
Scope scope = this;
while (scope.arg == null)
scope = scope.outer;
return scope.arg;
}

public Interp.Op getBinding(Value name) {
Scope scope = this;
while (scope != null) {
if (scope.bindings != null) {
Interp.Op op = scope.bindings.get(name);
if (op != null)
return op;
}
scope = scope.outer;
}
return null;
}
}

public static class Interp {
private Scope rootScope;

public Interp(Value input) {
rootScope = new Scope(null, rootBindings(), input);
}

public Value eval(Value expr) {
return eval(expr, rootScope);
}

protected Value eval(final Value expr, final Scope scope) {
if (expr.isNil())
return scope.getArg();
return new LazyValue() {
protected Value getValue() {
return scope.getBinding(expr.getHead()).op(expr.getTail(), scope);
}
};
}

public interface Op {
public Value op(Value expr, Scope scope);
}

private EnumTrie<Value.Flattened,Interp.Op> rootBindings() {
Value E3 = Value.NIL;
Value EE33 = new Value.Pair(E3,E3);
Value EE3E33 = new Value.Pair(E3,EE33);
Value EE3E3E33 = new Value.Pair(E3,EE3E33);
Value EEE333 = new Value.Pair(EE33,E3);
Value EEE33E33 = new Value.Pair(EE33,EE33);
Value EEEE3333 = new Value.Pair(EEE333,E3);
Value EE3EE333 = new Value.Pair(E3,EEE333);
EnumTrie<Value.Flattened,Interp.Op> rootBindings = new EnumTrie<Value.Flattened,Interp.Op>(Value.Flattened.class);
rootBindings.put(E3, new QuoteOp());
rootBindings.put(EE33, new LetOp());
rootBindings.put(EE3E33, new CdrOp());
rootBindings.put(EE3E3E33, new IfOp());
rootBindings.put(EEE333, new CarOp());
rootBindings.put(EEE33E33, new ConsOp());
rootBindings.put(EEEE3333, new EvalOp());
rootBindings.put(EE3EE333, new ConcatOp());
return rootBindings;
}

private class QuoteOp implements Op {
public Value op(Value arg, Scope scope) {
return arg;
}
}

private class LetOp implements Op {
public Value op(Value arg, Scope scope) {
EnumTrie<Value.Flattened,Interp.Op> bindings = new EnumTrie<Value.Flattened,Interp.Op>(Value.Flattened.class);
Scope letScope = new Scope(scope, bindings, null);
Value bindingList = arg.getHead();
while (!bindingList.isNil()) {
Value binding = bindingList.getHead();
bindingList = bindingList.getTail();
bindings.put(binding.getHead(), new DefinedOp(binding.getTail(), letScope));
}
return eval(arg.getTail(), letScope);
}
}

private class DefinedOp implements Op {
private Value body;
private Scope letScope;

public DefinedOp(Value body, Scope letScope) {
this.body = body;
this.letScope = letScope;
}

public Value op(final Value arg, final Scope scope) {
return eval(body, new Scope(letScope, null, eval(arg, scope)));
}
}

private class CdrOp implements Op {
public Value op(Value arg, Scope scope) {
return eval(arg, scope).getTail();
}
}

private class IfOp implements Op {
public Value op(Value arg, Scope scope) {
if (arg.isNil())
return Value.NIL;
Value body = arg.getTail();
if (body.isNil())
return Value.NIL;
if (eval(arg.getHead(), scope).isNil())
return eval(body.getTail(), scope);
else
return eval(body.getHead(), scope);
}
}

private class CarOp implements Op {
public Value op(Value arg, Scope scope) {
return eval(arg, scope).getHead();
}
}

private class ConsOp implements Op {
public Value op(Value arg, Scope scope) {
if (arg.isNil())
return Value.NIL;
return new Value.Pair(eval(arg.getHead(), scope), eval(arg.getTail(), scope));
}
}

private class EvalOp implements Op {
public Value op(Value arg, Scope scope) {
return eval(eval(arg, scope), scope);
}
}

private class ConcatOp implements Op {
public Value op(Value arg, Scope scope) {
if (arg.isNil())
return Value.NIL;
Value value = eval(arg.getHead(), scope);
Value rest = eval(arg.getTail(), scope);
LinkedList<Boolean> stack = new LinkedList<Boolean>();
LinkedList<Value> tails = new LinkedList<Value>();
for (;;) {
if (value.isNil())
break;
Value head = value.getHead();
Value tail = value.getTail();
if (head.isNil()) {
if (tail.isNil())
break;
stack.push(false);
value = tail;
} else {
stack.push(true);
tails.push(tail);
value = head;
}
}
while (!stack.isEmpty()) {
if (stack.pop())
rest = new Value.Pair(rest, tails.pop());
else
rest = new Value.Pair(Value.NIL, rest);
}
return rest;
}
}

private abstract class LazyValue extends Value {
private Value value = null;
protected abstract Value getValue();
public boolean isNil() {
if (value == null)
value = getValue();
return value.isNil();
}

public Value getHead() {
if (value == null)
value = getValue();
return value.getHead();
}

public Value getTail() {
if (value == null)
value = getValue();
return value.getTail();
}
}
}

public static void main(String[] args) throws Exception {
Value expr;
Value input;
if (args.length == 0) {
expr = Value.read(new InputStreamReader(System.in));
input = Value.NIL;
} else {
expr = Value.read(new FileReader(args[0]));
input = new Value.InputValue(System.in);
}
Value.write(new Interp(input).eval(expr), System.out);
System.out.flush();
}
}

Monday, January 25, 2010

I got pulled into optimizing some ancient C code. This was pre-ANSI C. This first thing I found was that there was floating-point function being called repeatedly in some loops with identical arguments, so I added some code near the top of the file that was something like this:

static float floating_point_function_table[20];
static int floating_point_function_table_initialized = 0;

static void init_floating_point_function_table()
{
int i;
if (floating_point_function_table_initialized)
return;
for (i = 0; i < 20; i++)
floating_point_function_table[i] = floating_point_function((float) i);
}

When I tried it out, it was a lot faster. After replacing the function_point_function() calls with the table lookups, the loops, which did some other stuff as well, ran in about one sixth the time. But the results were garbage. And I was baffled for at least an hour. The function looked something like this:

float floating_point_function(arg)
float arg;
{
...
}

Since this was ancient code, it was implicitly declared to return int when it was being called in init_floating_point_function_table(), so I moved init_floating_point_function_table() below the definition of floating_point_function(), and it worked.

I also discovered a bunch of loops like this:

for (i = 0; i < SOME_CONSTANT/SOME_CONSTANT_ARRAY[some_parameter]; i++) ...

in code that stood out when profiling, and declared a new int variable at the top of the function:

int SOME_CONSTANT_OVER_SOME_CONSTANT_ARRAY_some_parameter;
SOME_CONSTANT_OVER_SOME_CONSTANT_ARRAY_some_parameter = SOME_CONSTANT/SOME_CONSTANT_ARRAY[some_parameter];

and changed the loops to:

for (i = 0; i < SOME_CONSTANT_OVER_SOME_CONSTANT_ARRAY_some_parameter; i++) ...

and, surprisingly, even when compiled with full optimization, this change caused this section of code to run in less than half the time.

Monday, January 18, 2010

Here is 99 bottles of beer in Parenthesis Hell.

((())((((())(()))(()()())(((())))((()()())(((()))((())))(((())()
)(((())(()))((())())(((()))((())))(()()))(()())((())))(()()())((
()())((())))(((())())(())((())(()))((())())((()())((())))(()()))
(()()))(()()))((()((()()(()()()((()(()()(()((()()(((()((()(()()(
()(((()(()()())))))))))))))))))))))(()()())()((()()())(((())))((
()()())(((()))((())))((()()())(((()))((()))((())))((()()())(((()
))((()))((()))((())))((()()())(((()))((()))((()))((()))((())))((
()()())(((()))((()))((()))((()))((()))((())))((()()())(((()))(((
)))((()))((()))((()))((()))((())))((()()())(((()))((()))((()))((
()))((()))((()))((()))((())))(()()()(((()()(())))))()()()(((()()
()()))))()()()((()(((()))))))()()()((()((()())))))()()()((()(()(
())))))()()()((()(()()()))))()()()((()()((())))))()()()((()()(()
()))))()()()((()()()(()))))()()()((()()()()())))((()((()()()((()
((()((((()(((()(()(()((()(((()()(((()(()()()))))))))))))))))))))
)))))(()()())(((())))(((())(()))((())())((()((()()(()()()((()(()
()(()((()()(((()((()(()()(()(((()(()()())))))))))))))))))))))(((
))))(()((()()(()()()((()(()()(()((()()(((()((()(()()(()(((()(()(
)())))))))))))))))))))))(()()))(()()())((()()))((()((()()(()()()
((()(()()(()((()()(((()((()(()()(()(((()(()()())))))))))))))))))
))))(()()))()()((()(((()()((()((((()()(()()()()()()((()((()(()((
()((((()(((()()(()()((()()(()(()))))))))))))))))))))))))))))))))
((()(()()()()((()((()((((()(((()(()(()((()(((()()(((()(()()())))
)))))))))))))))))))))(()()())(((())))(((())(()))((())())((()((()
()(()()()((()(()()(()((()()(((()((()(()()(()(((()(()()()))))))))
)))))))))))))((())))(()((()()(()()()((()(()()(()((()()(((()((()(
()()(()(((()(()()())))))))))))))))))))))(()()))(()()())((()()))(
(()((()()(()()()((()(()()(()((()()(((()((()(()()(()(((()(()()())
))))))))))))))))))))(()()))()()(()()(((()()((()((((()()(()()()()
()()((()((()(()((()((((()(((()()(()()((()()(()(())))))))))))))))
))))))))))))))))((()(((()()((()))))))(()()())(((())))(()()(((()(
)((()))))))(()()())((()()))((()()())(((()))(()()))(()()(((()()((
()))))))()())()()(((()()((()))))))((()((()()()(()()((()((((()(((
()(()()()(((()(()()()((()((()()()((()()(()(()(((()()((()))))))))
)))))))))))))))))))))))((())(()))((())())((()((()()()((()((()(((
(()(((()(()(()((()(((()()(((()(()()()))))))))))))))))))))))))))(
(())(()))((())())(()()()(()()()()()()((()()()(()()((()((((()((((
)(()()()(((()(()()()((()((()()()((()()(()(()))))))))))))))))))))
)))))))((())(()))((())())((()(((()()((())))))))()()()(()()()()()
()((()((((()((()()((()()()(()()()()()()((()()()(()()((()()(()(()
((()()(()(()(((()()(()()))))))))))))))))))))))))))))((()(()()()(
)(()()((()((((()(((()(()()()(((()(()()()((()((()()()((()()(()(()
(((()()((()))))))))))))))))))))))))))))))((())(()))((())())((()(
()()()()((()((()((((()(((()(()(()((()(((()()(((()(()()()))))))))
)))))))))))))))))((())(()))((())())(()()()(()()()()()()((()()()(
()()((()((((()(((()(()()()(((()(()()()((()((()()()((()()(()(()))
)))))))))))))))))))))))))((())(()))((())())((()(((()()((()))))))
)()()()(()()()()()()((()((((()((()()((()()()(()()()()()()((()()(
)(()()((()()(()(()((()()(()(()(((()()(()()))))))))))))))))))))))
))))))((()((()()(()()()((()()(()(()((()()()((()))))))))))))(()()
())((()()))(((())())(((())))((()))(()()))((())())(((()))((())))(
)((((((((())))))))))((()(((()((()()()((()()()(())))))))))((())((
)))((())())((()(()()()()(()()((()((((()(((()(()()()(((()(()()()(
(()((()()()((()()(()(()(((()()((()))))))))))))))))))))))))))))))
)((())(()))((())())(()()()(()()()()()()((()((((()((()(((()()()((
)()()()()()(((()(()()()((()(()()()()((()()(()(()()(()()()()()()(
((()(((()((()()()()(()((()((()()()((()((()()()()(()((()()()()(()
()()()()())))))))))))))))))))))))))))))))))))))))))))))))((())((
)))((())())((()((()()()(()()((()((((()(((()(()()()(((()(()()()((
()((()()()((()()(()(()(((()()((())))))))))))))))))))))))))))))))
)(()()())(((())))((()(((()((()()()((()()(()())))))))))(()((()()(
()()()((()()(()(()((()()()((())))))))))))))(()()())((()()))((()(
((()((()()()((()()(()())))))))))(()((()()(()()()((()()(()(()((()
()()((())))))))))))))()()()(()(((()()()()()(()(()()(()()()(((()(
(()((((()()(()()()()()()(((()(()()()((()((((()()(()()()()()()(((
()(()()()((()(()()()()((()()(()(()()(()()()()()()(((()()((()((((
)(()()()((()((((()(((()()(()()((()()(()(()()(()()()()()()((()()(
)()(()((()(((()()((()()(()()()()(()()()()()()((()()()(()()(((()(
()(()((((()()(()()(()()()()()()(((()()((()((()((((()((()((()(()(
(()()(()(()()(()()()()()()((()((()(()((()((((()(((()()(()()((()(
)(()(()()(()((()()()()(()()()()()()()(((()()(()()(((()()(()()(()
()()()()()((()()()(()()((()((((()(((()(()()()(((()(()()()((()(((
)()()((()()(()(()(((()()((()()(()()()()()()((()((((()((()()((()(
)()(()()()()()()((()()()(()()((()()(()(()((()()(()(()(((()()(()(
)()(()()()()()()((()((((()((()(((()()()(()()()()()()(((()(()()()
((()(()()()()((()()(()(()()(()()()()()()(((()(((()((()()()()(()(
(()((()()()((()((()()()()(()(((()()()()()(()(()())))))))))))))))
))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
))))))))))))))))))))))))))))))))))))))))))((()(((()((()()()((()(
)(()())))))))))((())(()))((())())(()()()(()(((()()()()()(()(()()
(()(()(()()()((()()()()(()((()(()((()((()()(()(()()(()()()()()()
((()((((()((()(((()()((()()(()(()()(()()()()()()((()()(()()()(((
)((((()(((()(((()((()(((()()()(()()()()()()((()()()()(()((()((((
)()((()()(()()()()(()()()()()()(((()()()()()((()()()()(()(((()()
((()(((()()((()()(()()()()()()((()(()()(()(((()(()()()()(()()()(
)()()((()()()()(()(((()()(()()((()((((()(((()(()(()((()(((()()((
()()(()()()()(()((()()()()(()()()()()())))))))))))))))))))))))))
))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
)))))))))))))))))))))))))))))))))))))))((())(()))((())())((()(((
)()()(()()((()((((()(((()(()()()(((()(()()()((()((()()()((()()((
)(()(((()()((()))))))))))))))))))))))))))))))))()()()(()()()()()
()((()((((()((()(((()()()(()()()()()()(((()(()()()((()(()()()()(
(()()(()(()()(()()()()()()(((()(((()((()()()()(()((()((()()()(((
)((()()()()(()(((()()()()()(()(()()()()()(()(()())))))))))))))))
))))))))))))))))))))))))))))))))))))((()(((()((()()))))))((())((
)))((())())((()(((()((()()()((()()()(()))))))))))(()()())(((()))
)((()(((()((()()))))))(()((()()(()()()((()()(()(()((()()()((()))
)))))))))))(()()())((()()))((()(((()((()()))))))(()((()()(()()()
((()()(()(()((()()()((())))))))))))))()))(()(((()((()()))))))()(
((((((((())))))))))((((((((())))))))))

I did not actually write the preceding code. I actually wrote what follows, which looks much more Lisp-like, and ran it through a preprocessor that did symbol and string substitutions.

(((arg . ())
(quote . ())
(let . (()))
(cdr . (()()))
(if . (()()()))
(car . ((())))
(cons . ((())()))
(eval . (((()))))
(concat . ((())(()))))
.
(let ((concat
. (if (car . arg)
(if (car car . arg)
(cons (concat cons (car car . arg) . (cdr . arg))
. (cdr car . arg))
. (if (cdr car . arg)
(cons (quote . ())
. (concat cons (cdr car . arg) . (cdr . arg)))
. (cdr . arg)))
. (cdr . arg)))
("digit" .
(if arg
(if (car . arg)
(if (car car . arg)
(if (car car car . arg)
(if (car car car car . arg)
(if (car car car car car . arg)
(if (car car car car car car . arg)
(if (car car car car car car car . arg)
(if (car car car car car car car car . arg)
(quote . "9")
. (quote . "8"))
. (quote . "7"))
. (quote . "6"))
. (quote . "5"))
. (quote . "4"))
. (quote . "3"))
. (quote . "2"))
. (quote . "1"))
. (quote . "0")))
("count" .
(if (car . arg)
(concat cons ("digit" car . arg) . ("digit" cdr . arg))
. (if (cdr . arg)
("digit" cdr . arg)
. (quote . "no more"))))
("Count" .
(if (car . arg)
(concat cons ("digit" car . arg) . ("digit" cdr . arg))
. (if (cdr . arg)
("digit" cdr . arg)
. (quote . "No more"))))
("s" .
(if (car . arg)
(quote . "s")
. (if (cdr . arg)
(if (car cdr . arg)
(quote . "s")
. (quote . ""))
. (quote . "s"))))
("bottles" .
(concat cons ("count" . arg) .
(concat cons (quote . " bottle") .
(concat cons ("s" . arg) . (quote . " of beer")))))
("Bottles" .
(concat cons ("Count" . arg) .
(concat cons (quote . " bottle") .
(concat cons ("s" . arg) . (quote . " of beer")))))
("dec" .
(if (cdr . arg)
(cons (car . arg) . (car cdr . arg))
. (cons (car car . arg) . (quote . (((((((((())))))))))))))
("v1" .
(concat cons ("Bottles" . arg)
concat cons (quote . " on the wall, ")
concat cons ("bottles" . arg) .
(if (car . arg)
("v2" "dec" . arg)
. (if (cdr . arg)
("v2" "dec" . arg)
. (quote . ".
Go to the store and buy some more, 99 bottles of beer on the wall.
")))))
("v2" .
(concat cons (quote . ".
Take one down and pass it around, ")
concat cons ("bottles" . arg)
. (quote . " on the wall.

")))
("v" .
(concat cons ("v1" . arg)
. (if (car . arg)
("v" "dec" . arg)
. (if (cdr . arg)
("v" "dec" . arg)
. (quote . ()))))))
. ("v" quote . ((((((((((()))))))))) . (((((((((())))))))))))))

Monday, January 11, 2010

After load testing, the caching optimization still had performance issues, so a synchronized HashMap.get got turned into an unsynchronized HashMap.get.

The code becomes something like

for (;;) {
Object entry = null;
// cache is a jdk1.4 HashMap, so if there is a concurrent modification,
// the worst can happen is either an erroneous null is returned or an
// ArrayIndexOutOfBoundsException is thrown. This HashMap.get is not
// synchronized for performance reasons, as revealed in load tests.
//
// In general, a concurrent modification could result a HashMap.get
// to return a (very recently) stale value, but that is not a concern
// in this case, since the only stale value could be an expired lock
// object, which will result in retrying the HashMap.get.
//
// The ArrayIndexOutOfBoundsException is not likely to ever be thrown,
// but the HashMap.remove in the finally block could theoretically
// cause it. The catch block causes the ArrayIndexOutOfBoundsException
// to be the same as a potentially erroneous null.
//
// The erroneous null is not a problem, because if null is returned,
// because the subsequent HashMap.get is synchronized.
try {
entry = cache.get(key);
} catch (ArrayIndexOutOfBoundsException e) {
}
if (entry == null) {
Object lock = new Object();
synchronized (lock) {
try {
synchronized (cache) {
if (cache.get(key) != null) {
lock = null;
continue;
}
cache.put(key, lock);
}
CacheData data = computeData(key);
synchronized (cache) {
cache.put(key, data);
}
lock = null;
return data;
} finally {
if (lock != null) {
synchronized (cache) {
cache.remove(key);
}
}
}
}
} else if (entry instanceof CacheData) {
return (CacheData) entry;
} else {
synchronized (entry) {}
}
}

I hope that some big fat comment like the one I wrote above about why the HashMap.get is unsynchronized and why it should still work correctly gets put in the actual code.

I manually decompiled the jdk1.4 HashMap.get to investigate the consequences of the unsynchronized HashMap.get. In this case, the modifications to the HashMap are adding a key and a lock object, replacing a lock object with a value, and (only if there errors) removing a key that maps to a lock object. The keys are Strings, so stupid code (in computeData(), for example) that mutates the keys is not a concern. The CacheData is also immutable, and is actually a String, but that's irrelevant.

static Object maskNull(Object a0) {
return a0 == null ? NULL_KEY : a0;
}

static int hash(Object a0) {
int i1 = a0.hash();
i1 += (i1 << 9) ^ -1;
i1 ^= i1 >>> 14;
i1 += i1 << 4;
i1 ^= i1 >>> 10;
return i1;
}

static int indexFor(int i0, int i1) {
return i0 & (i1 - 1);
}

static boolean eq(Object a0, Object a1) {
return a0 == a1 ? true : a0.equals(a1) ? true : false;
}

public Object get(Object a1) {
Object a2 = maskNull(a1);
int i3 = hash(a2);
int i4 = indexFor(i3, this.table.length);
HashMap.Entry a5 = this.table[i4];
for (;;) {
if (a5 == null)
return a5;
if (a5.hash == i3 && eq(a2, a5.key))
return a5.value;
a5 = a5.next;
}
}

Monday, January 4, 2010

The Finite Groups programming language

Finite Groups is a programming language with a syntax that is intended to look somewhat mathematical. As a language, it is not very powerful, being essentially just a calculator -- every Finite Groups program halts. The data type is a list of group elements. The groups are cyclic groups, dihedral groups, symmetric groups, alternating groups, dicyclic groups, and cartesian products of groups.
Syntax

A short comment begins with < and ends with > and cannot contain < or >, and cannot be </code>. A short comment cannot be <sub> after a group name, and cannot be </sub> within a type name.

A long comment begins with </code> and ends with <code> or may continue to the end of the source. If there is any <code> in the source, then everything up to the first <code> is a comment.

A program consists of a single expression. The symbol i, with type C[i], is predefined as the program's input.

Expressions are

  • symbol - A lowercase letter, a value defined in an enclosing let expression, or "i".

  • constant - One or more strings enclosed with double quotes. Adjacent strings are concatenated. Backslash escapes can be used to include double quotes, backslashes, newlines, returns, tabs, vertical tabs, and form-feeds.

  • inverse - "-" followed by an expression.

  • product - An expression followed by another expression.

  • cartesian product - Two expressions separated by "*".

  • reduction - "!" followed by an expression.

  • left projection - "P" followed by an expression.

  • right projection - An expression followed by "P".

  • type name - A group name followed by "[", followed by one or more expressions, separated by ",", followed by "]" or two type names separated by "*".

  • permutation - "S", followed by "[", followed by two expressions separated by "->", followed by "]".

  • let - "let" followed by one or more bindings, followed by "in", followed by an expression.


Group names

  • "C" - cyclic group, "Z" is a synonym.

  • "D" - dihedral group, "Dih" is a synonym.

  • "S" - symmetric group.

  • "A" - alternating group.

  • "Dic" - dicyclic group.


A let binding is a symbol (a lower-case letter), followed by an optional type specification, followed by "=", followed by an expression. All preceding bindings are visible in the expression. The binding itself and any following bindings are not visible. The binding itself is visible in the type specification, but with the unspecified type of the expression.

A type specification is a ":", followed by a type name.

For even more mathematical appearances, the following synonyms are recognized:

  • !: &Pi;

  • [: <sub>

  • ]: </sub>

  • ->: &rarr;

  • *: &times;


Associativity

Associativity can be explicitly specified with parentheses.

Associativity is otherwise deliberately left undefined. For group products, since (ab)c = a(bc), associativity is irrelevant. For other operations, a sophisticated parser could use spacing to infer associativity. For example, !a b*c = (!a)(b*c), while !ab * c = (!(ab))*c or !ab * c = ((!a)b)*c. An even more sophisticated parser could use type analysis to rule out interpretations that would result in illegal code.
Types

A type is a group name and a list of expressions. The unique elements in the list of expressions, in order, are used to generate a group. Values of the type are lists of elements in that group.

  • C[expr] is a cyclic group. The first expr element is the identity.

  • D[expr] is a dihedral group. The first epxr element is the identity, and the remaining expr elements are the rotations.

  • S[expr] is a symmetric group. The group elements are the permutations of the expr elements.

  • A[expr] is an alternating group. The group elements are the even permutations of the expr elements.

  • Dic[expr] is a dicyclic group. The expr elements form a cyclic subgroup of the group, where the first expr element is the identity.


Values


  • constant - "string" has type C["string"]

  • inverse - Inverse of each element in the list

  • product - If the two values are of the same type, then if a = {a1,a2,a3...} and b = {b1,b2,b3...}, then ab = {a1b1,a1b2,a1b3...,a2b1...} with the group operation. If the left type is a permutation, and every value in the permutation is a value in the right type, then the right values are mapped by the permutation with the same distributivity as with the group operation, and the result has the type of the right type.

  • cartesian product - With the same distributivity as with the product, the type of the result is the product of the left and right types.

  • reduction - Results in a single element list by folding the elements in the list with the group operation.

  • left/right projection - The left/right projections of a cartesian product.

  • type name - A list with all the elements in the group in the group's natural order.

  • permutation - S[expr1->expr2] is a single element list of type S[expr1] that maps the elements of expr1 to the elements of expr2.


Examples

Hello world

"Hello world!\n"

cat

i

rot13

let a = "abcdefghijklmABCDEFGHIJKLMnopqrstuvwxyzNOPQRSTUVWXYZ"
r : C[a] = "n"
i : C[a,i] = i
in S[a->ra]i

touppercase

let t = !"01"
f = tt
a = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
u : C[a] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
l : C[a] = "abcdefghijklmnopqrstuvwxyz"
m = S["01"*a->C[f*u,t*u,f*l,t*l]]
i : C[a,i] = i
in (m(f*i))P

quaternions

let q = Dic"1-"
c : Cq = "-"
i : Dic"1-" = cc
j : Dic"1-" = cccc
k = ij
in k

Interpreter

This interpreter handles associativity by just doing whatever is easiest to parse. The parser also incorrectly disallows <sub> and </sub> comments in places where they should be allowed.

module FiniteGroup where

class FiniteGroup g where
identity :: g -> Integer
order :: g -> Integer
prod :: g -> Integer -> Integer -> Integer
inverse :: g -> Integer -> Integer
reduce :: g -> [Integer] -> Integer
elements :: g -> [Integer]

identity g = 0
reduce g is = foldl (prod g) (identity g) is
elements g = [0..order g - 1]

module Element where

import Data.List(elemIndex,(!!))

data Element =
Element Char
| DihReflectElement Element
| Permutation [Element]
| DicIElement Element
| DicJElement Element
| DicKElement Element
| DirProdElement Element Element
deriving (Eq,Ord)

instance Show Element where
show (Element c) = [c]
show (DihReflectElement e) = "<DRefl " ++ show e ++ ">"
show (Permutation e) = "<Perm " ++ concatMap show e ++ ">"
show (DicIElement e) = "<DicI " ++ show e ++ ">"
show (DicJElement e) = "<DicJ " ++ show e ++ ">"
show (DicKElement e) = "<DicK " ++ show e ++ ">"
show (DirProdElement e1 e2) = '<' : show e1 ++ '*' : show e2 ++ ">"

class Generated g where
generate :: [Element] -> g
generator :: g -> [Element]
generated :: g -> [Element]
elementIndex :: g -> Element -> Maybe Integer
elementIndex g elt = fmap fromIntegral (elemIndex elt (generated g))
element :: g -> Integer -> Element
element g i = generated g !! fromIntegral i

uniq :: Eq a => [a] -> [a]
uniq [] = []
uniq (a:as) = a : uniq (filter (/= a) as)

module Permutation(decodePermutation,encodePermutation,applyPermutation,inversePermutation,toPermutation) where

import Data.List(elemIndex)

encodePermutation :: [Integer] -> Integer
encodePermutation is = encode is 0 (fromIntegral (length is) - 1)

encode :: [Integer] -> Integer -> Integer -> Integer
encode [] e _ = e
encode (i:is) e n = encode (map (\ j -> if j > i then j - 1 else j) is)
(e + i*product [2..n]) (n - 1)

decodePermutation :: Integer -> Integer -> [Integer]
decodePermutation n e = decode n e id

decode :: Integer -> Integer -> (Integer -> Integer) -> [Integer]
decode 0 _ _ = []
decode 1 e expand = [expand e]
decode n e expand =
let (i,e') = e `divMod` product [2..n - 1]
expand' j = expand (if j >= i then j + 1 else j)
in expand i : decode (n - 1) e' expand'

applyPermutation :: [Integer] -> [a] -> [a]
applyPermutation is elements = map ((elements !!) . fromIntegral) is

inversePermutation :: [Integer] -> [Integer]
inversePermutation is = toPermutation is [0..fromIntegral (length is) - 1]

toPermutation :: Eq a => [a] -> [a] -> [Integer]
toPermutation src dest =
map (maybe (error "Permutation.toPermutation: invalid permutation")
fromIntegral . (`elemIndex` src))
dest

module CyclicGroup(CyclicGroup) where

import Element
import FiniteGroup

data CyclicGroup = CyclicGroup [Element]
deriving (Eq,Show)

instance FiniteGroup CyclicGroup where
identity _ = 0
order (CyclicGroup elements) = fromIntegral (length elements)
prod c i1 i2 = (i1 + i2) `mod` order c
inverse c i = (order c - i) `mod` order c

instance Generated CyclicGroup where
generate = CyclicGroup . uniq
generator (CyclicGroup elements) = elements
generated (CyclicGroup elements) = elements

module DihedralGroup(DihedralGroup) where

import Element
import FiniteGroup

data DihedralGroup = DihedralGroup [Element]
deriving (Eq,Show)

instance FiniteGroup DihedralGroup where
identity _ = 0
order d = 2 * size d
prod d i1 i2
| i1 < size d && i2 < size d = (i1 + i2) `mod` size d
| i1 < size d = size d + ((i1 + i2) `mod` size d)
| i2 < size d = size d + ((i1 - i2 + size d) `mod` size d)
| otherwise = (i1 - i2 + size d) `mod` size d
inverse d i | i >= size d = i
| otherwise = (size d - i) `mod` size d

instance Generated DihedralGroup where
generate = DihedralGroup . uniq
generator (DihedralGroup rotations) = rotations
generated (DihedralGroup rotations) =
rotations ++ map DihReflectElement rotations

size :: DihedralGroup -> Integer
size (DihedralGroup rotations) = fromIntegral (length rotations)

module DicyclicGroup(DicyclicGroup) where

import Element
import FiniteGroup

data DicyclicGroup = DicyclicGroup [Element]
deriving (Eq,Show)

instance FiniteGroup DicyclicGroup where
identity _ = 0
order d = 4*size d
prod d i1 i2 =
let n = size d
(k1,j1) = decode n i1
(k2,j2) = decode n i2
in encode n ((k1 + k2 - j1*(2*k2) + (2 + j2)*n) `mod` (2*n))
((j1 + j2) `mod` 2)
inverse d i =
let n = size d
(k,j) = decode n i
in encode n ((2*n - k + j*n) `mod` (2*n)) j

instance Generated DicyclicGroup where
generate = DicyclicGroup . uniq
generator (DicyclicGroup elements) = elements
generated (DicyclicGroup elements) =
elements ++ map DicIElement elements
++ map DicJElement elements ++ map DicKElement elements

size :: DicyclicGroup -> Integer
size (DicyclicGroup elements) = fromIntegral (length elements)

encode :: Integer -> Integer -> Integer -> Integer
encode n k j = 2*(k `mod` n) + ((k `div` n) `mod` 2) + 2*n*j

decode :: Integer -> Integer -> (Integer,Integer)
decode n i = ((i `div` 2) + n*(i `mod` 2), i `div` (2*n))

module SymmetricGroup(SymmetricGroup) where

import Data.List(sort)

import Element
import FiniteGroup
import Permutation

data SymmetricGroup = SymmetricGroup [Element]
deriving (Eq,Show)

instance FiniteGroup SymmetricGroup where
identity _ = 0
order s = product [2..size s]
prod s i1 i2 =
encodePermutation (applyPermutation (decodePermutation (size s) i1)
(decodePermutation (size s) i2))
inverse s i =
encodePermutation (inversePermutation (decodePermutation (size s) i))

instance Generated SymmetricGroup where
generate = SymmetricGroup . sort . uniq
generator (SymmetricGroup elements) = elements
generated s = map (element s) (elements s)
element g i = Permutation (applyPermutation (decodePermutation (size g) i)
(generator g))
elementIndex g (Permutation e) =
if fromIntegral (length e) == size g && all (`elem` e) (generator g)
then Just (encodePermutation (toPermutation (generator g) e))
else Nothing

size :: SymmetricGroup -> Integer
size (SymmetricGroup elements) = fromIntegral (length elements)

module AlternatingGroup(AlternatingGroup) where

import Data.List(sort)

import Element
import FiniteGroup
import Permutation

data AlternatingGroup = AlternatingGroup [Element]
deriving (Eq,Show)

instance FiniteGroup AlternatingGroup where
identity _ = 0
order a = product [3..size a]
prod a i1 i2 =
encodePermutation (applyPermutation (decodePermutation (size a) (2*i1))
(decodePermutation (size a)
(2*i2)))
`div` 2
inverse a i =
encodePermutation (inversePermutation (decodePermutation (size a)
(2*i)))
`div` 2

instance Generated AlternatingGroup where
generate = AlternatingGroup . sort . uniq
generator (AlternatingGroup elements) = elements
generated g = map (element g) (elements g)
element g i = Permutation (applyPermutation (decodePermutation (size g)
(2*i))
(generator g))
elementIndex g (Permutation e) =
if fromIntegral (length e) == size g && all (`elem` e) (generator g)
then let i = encodePermutation (toPermutation (generator g) e)
in if i `mod` 2 == 0
then Just (i `div` 2)
else Nothing
else Nothing

size :: AlternatingGroup -> Integer
size (AlternatingGroup elements) = fromIntegral (length elements)

module Expr where

data Expr =
Symbol String Char
| Inverse String Expr
| Reduce String Expr
| LeftProjection String Expr
| RightProjection String Expr
| Constant String String
| Prod String Expr Expr
| DirProd String Expr Expr
| Let String [Binding] Expr
| Constructor String TypeName
| Permutation String Expr Expr
deriving Show

data TypeName =
Cyclic [Expr]
| Dihedral [Expr]
| Symmetric [Expr]
| Alternating [Expr]
| Dicyclic [Expr]
| DirectProduct TypeName TypeName
deriving Show

data Binding = Binding String Char (Maybe TypeName) Expr
deriving Show

module Parser(parse) where

import Text.ParserCombinators.Parsec(CharParser,anyChar,eof,getPosition,getState,many,many1,manyTill,notFollowedBy,oneOf,noneOf,optionMaybe,runParser,sepBy1,setState,sourceColumn,sourceLine,sourceName,space,try,(<|>),(<?>))
import qualified Text.ParserCombinators.Parsec as Parsec

import Expr

type Parser a = CharParser [Bool] a

parse :: String -> String -> Expr
parse file src = either (error . show) id (runParser program [] file src)

srcLocation :: Parser String
srcLocation =
let format p = sourceName p ++ ':' : show (sourceLine p)
++ ':' : show (sourceColumn p)
in fmap format getPosition

enterLet :: Parser ()
enterLet = getState >>= setState . (True:)

exitLet :: Parser ()
exitLet = getState >>= setState . tail

enterBracketed :: Parser ()
enterBracketed = getState >>= setState . (False:)

exitBracketed :: Parser ()
exitBracketed = getState >>= setState . tail

inLet :: Parser Bool
inLet = fmap (and . (flip map [not . null, head]) . flip ($)) getState

ignore :: Parser a -> Parser ()
ignore = (>> return ())

skipSpace :: Parser ()
skipSpace = ignore (many (ignore space <|> comment))

comment :: Parser ()
comment = (try (Parsec.string "</code>") >> endComment (return ()))
<|> (try (Parsec.char '<'
>> notFollowedBy (try (Parsec.string "sub>") >> return '<')
>> notFollowedBy (try (Parsec.string "/sub>") >> return '<')
>> manyTill (noneOf "<>") (Parsec.char '>')
>> return ()))

endComment :: Parser () -> Parser ()
endComment onEOF = (try (Parsec.string "<code>") >> return ())
<|> (eof >> onEOF)
<|> (anyChar >> endComment onEOF)

program :: Parser Expr
program = do
try (endComment (fail "No comments")) <|> return ()
skipSpace
expr

-- applicative
f <$> a = fmap f a
f <*> a = f >>= (<$> a)
a <$ b = b >> return a
a *> b = a >> b
a <* b = const <$> a <*> b
infixl 4 <$>
infixl 4 <*>
infixl 4 <$
infixl 4 *>
infixl 4 <*

expr :: Parser Expr
expr = do
watchingForIn <- inLet
if watchingForIn
then optionMaybe (string "in")
>>= maybe (return ()) (const (fail "in"))
else return ()
expr1

expr1 :: Parser Expr
expr1 = simpleExpr >>= applyRight >>= applyProduct

simpleExpr :: Parser Expr
simpleExpr =
char '(' *> enterBracketed *> expr1 <* char ')' <* exitBracketed
-- let must be tried before symbol
<|> try (Let <$> srcLocation <*> (string "let" *> bindings) <*> expr1)
<|> Symbol <$> srcLocation
<*> try (oneOf ['a'..'z'] <* skipSpace
<* notFollowedBy (oneOf ":="))
-- try in case it's ->
<|> try (apply Inverse (char '-'))
<|> apply Reduce (char '!' <|> string "&Pi;")
<|> apply LeftProjection (char 'P')
<|> Constant <$> srcLocation <*> (concat <$> many1 stringConst)
-- try in case it's an S constructor
<|> try (Permutation <$> srcLocation
<*> (char 'S' *> openBracket *> expr1)
<*> (arrow *> expr1 <* closeBracket))
<|> Constructor <$> srcLocation <*> typeName

char :: Char -> Parser ()
char c = Parsec.char c *> skipSpace

string :: String -> Parser ()
string str = try (Parsec.string str) *> skipSpace

apply :: (String -> Expr -> Expr) -> Parser op -> Parser Expr
apply op parseOp = op <$> srcLocation <*> (parseOp *> expr1)

applyRight :: Expr -> Parser Expr
applyRight exp = do
loc <- srcLocation
char 'P' *> applyRight (RightProjection loc exp) <|> return exp

applyProduct :: Expr -> Parser Expr
applyProduct exp =
flip DirProd exp <$> srcLocation <*> (times *> expr1)
<|> flip Prod exp <$> srcLocation <*> try expr
<|> return exp

stringConst :: Parser String
stringConst =
Parsec.char '"' *> many stringChar <* Parsec.char '"' <* skipSpace

stringChar :: Parser Char
stringChar = noneOf "\r\n\\\""
<|> try (Parsec.string "\\t") *> return '\t'
<|> try (Parsec.string "\\v") *> return '\v'
<|> try (Parsec.string "\\f") *> return '\f'
<|> try (Parsec.string "\\r") *> return '\r'
<|> try (Parsec.string "\\n") *> return '\n'
<|> try (Parsec.string "\\\\") *> return '\\'
<|> try (Parsec.string "\\\"") *> return '"'

openBracket :: Parser ()
openBracket = (char '[' <|> string "<sub>") *> enterBracketed

closeBracket :: Parser ()
closeBracket = (char ']' <|> string "</sub>") *> exitBracketed

arrow :: Parser ()
arrow = string "->" <|> string "&rarr;"

times :: Parser ()
times = char '*' <|> string "&times;"

bindings :: Parser [Binding]
bindings = enterLet *> manyTill binding (string "in") <* exitLet

binding :: Parser Binding
binding =
Binding <$> srcLocation
<*> oneOf ['a'..'z'] <* skipSpace
<*> optionMaybe (char ':' *> typeName)
<*> (char '=' *> expr1)

typeName :: Parser TypeName
typeName = do
t <- simpleTypeName
try (times *> (DirectProduct t <$> typeName)) <|> return t

simpleTypeName :: Parser TypeName
simpleTypeName = Cyclic <$> (char 'C' *> typeArgs)
<|> Cyclic <$> (char 'Z' *> typeArgs)
<|> Symmetric <$> (char 'S' *> typeArgs)
<|> Alternating <$> (char 'A' *> typeArgs)
<|> Dicyclic <$> (string "Dic" *> typeArgs)
<|> Dihedral <$> (string "Dih" *> typeArgs)
<|> Dihedral <$> (char 'D' *> typeArgs)

typeArgs :: Parser [Expr]
typeArgs = openBracket *> sepBy1 expr1 (char ',') <* closeBracket

module Value where

import Data.List(sort)

import Element
import CyclicGroup
import DihedralGroup
import SymmetricGroup
import AlternatingGroup
import DicyclicGroup
import qualified FiniteGroup as FG
import Permutation

data Type =
CyclicType CyclicGroup
| DihedralType DihedralGroup
| SymmetricType SymmetricGroup
| AlternatingType AlternatingGroup
| DicyclicType DicyclicGroup
| DirectProductType Type Type
deriving (Eq,Show)

data Value = Value [Integer] Type
deriving (Eq,Show)

order :: Type -> Integer
order (CyclicType g) = FG.order g
order (DihedralType g) = FG.order g
order (SymmetricType g) = FG.order g
order (AlternatingType g) = FG.order g
order (DicyclicType g) = FG.order g
order (DirectProductType t1 t2) = order t1 * order t2

projections :: Value -> (Value,Value)
projections (Value elts (DirectProductType t1 t2)) =
(Value (map (`div` order t2) elts) t1,Value (map (`mod` order t2) elts) t2)

directZip :: Value -> Value -> Value
directZip (Value e1 t1) (Value e2 t2) =
Value (zipWith (+) (map (order t2 *) e1) e2) (DirectProductType t1 t2)

inverse :: Value -> Value
inverse (Value elts t@(CyclicType g)) = Value (map (FG.inverse g) elts) t
inverse (Value elts t@(DihedralType g)) = Value (map (FG.inverse g) elts) t
inverse (Value elts t@(SymmetricType g)) = Value (map (FG.inverse g) elts) t
inverse (Value elts t@(AlternatingType g)) = Value (map (FG.inverse g) elts) t
inverse (Value elts t@(DicyclicType g)) = Value (map (FG.inverse g) elts) t
inverse v@(Value _ t@(DirectProductType _ _)) =
let (v1,v2) = projections v in directZip (inverse v1) (inverse v2)

reduce :: Value -> Value
reduce (Value elts t@(CyclicType g)) = Value [FG.reduce g elts] t
reduce (Value elts t@(DihedralType g)) = Value [FG.reduce g elts] t
reduce (Value elts t@(SymmetricType g)) = Value [FG.reduce g elts] t
reduce (Value elts t@(AlternatingType g)) = Value [FG.reduce g elts] t
reduce (Value elts t@(DicyclicType g)) = Value [FG.reduce g elts] t
reduce v@(Value _ t@(DirectProductType _ _)) =
let (v1,v2) = projections v in directZip (reduce v1) (reduce v2)

leftProjection :: (String -> Value) -> Value -> Value
leftProjection _ v@(Value _ t@(DirectProductType _ _)) = fst (projections v)
leftProjection err _ = err "Invalid left projection"

rightProjection :: (String -> Value) -> Value -> Value
rightProjection _ v@(Value _ t@(DirectProductType _ _)) = snd (projections v)
rightProjection err _ = err "Invalid right projection"

prod :: (String -> Value) -> Value -> Value -> Value
prod err v1@(Value e1 t1) v2@(Value e2 t2)
| t1 == t2 = Value [ elemProd t1 i1 i2 | i1 <- e1, i2 <- e2 ] t1
| t1 `appliesTo` t2 =
fromElements err t2
[ mapElement t1 i1 elt2 | i1 <- e1, elt2 <- toElements v2 ]
| otherwise = err "Type mismatch"

dirProd :: Value -> Value -> Value
dirProd (Value e1 t1) (Value e2 t2) =
Value [ i1*(order t2) + i2 | i1 <- e1, i2 <- e2 ] (DirectProductType t1 t2)

elemProd (CyclicType g) i1 i2 = FG.prod g i1 i2
elemProd (DihedralType g) i1 i2 = FG.prod g i1 i2
elemProd (SymmetricType g) i1 i2 = FG.prod g i1 i2
elemProd (AlternatingType g) i1 i2 = FG.prod g i1 i2
elemProd (DicyclicType g) i1 i2 = FG.prod g i1 i2
elemProd (DirectProductType t1 t2) i j =
let (i1,i2) = i `divMod` order t2
(j1,j2) = j `divMod` order t2
in order t2 * elemProd t1 i1 j1 + elemProd t2 i2 j2

appliesTo :: Type -> Type -> Bool
appliesTo (SymmetricType g) t =
all (`elem` toElements (constructorValue t)) (generator g)
appliesTo (AlternatingType g) t =
all (`elem` toElements (constructorValue t)) (generator g)
appliesTo _ _ = False

mapElement :: Type -> Integer -> Element -> Element
mapElement (SymmetricType g) i elt =
let (Permutation dest) = element g i
in maybe elt id (lookup elt (zip (generator g) dest))
mapElement (AlternatingType g) i elt =
let (Permutation dest) = element g i
in maybe elt id (lookup elt (zip (generator g) dest))

mapToType :: (String -> Value) -> Value -> Type -> Value
mapToType err v t = fromElements err t (toElements v)

constructorValue :: Type -> Value
constructorValue t = Value [0..fromIntegral (order t) - 1] t

toElements :: Value -> [Element]
toElements (Value es t) = map (typeElement t) es

typeElement :: Type -> Integer -> Element
typeElement (CyclicType g) i = element g i
typeElement (DihedralType g) i = element g i
typeElement (SymmetricType g) i = element g i
typeElement (AlternatingType g) i = element g i
typeElement (DicyclicType g) i = element g i
typeElement (DirectProductType t1 t2) i =
let (i1,i2) = i `divMod` order t2
in DirProdElement (typeElement t1 i1) (typeElement t2 i2)

fromElements :: (String -> Value) -> Type -> [Element] -> Value
fromElements err t elements =
maybe (err "Value does not match type") (flip Value t)
(sequence (map (typeElementIndex t) elements))

typeElementIndex :: Type -> Element -> Maybe Integer
typeElementIndex (CyclicType g) e = elementIndex g e
typeElementIndex (DihedralType g) e = elementIndex g e
typeElementIndex t@(SymmetricType g) e = widenMap t e >>= elementIndex g
typeElementIndex t@(AlternatingType g) e = widenMap t e >>= elementIndex g
typeElementIndex (DicyclicType g) e = elementIndex g e
typeElementIndex (DirectProductType t1 t2) (DirProdElement e1 e2) = do
i1 <- typeElementIndex t1 e1
i2 <- typeElementIndex t2 e2
return (i1 * order t2 + i2)

permutation :: (String -> Value) -> Value -> Value -> Value
permutation err v1 v2 =
let e1 = uniq (toElements v1)
e2 = uniq (toElements v2)
g = generate e1
e = generator g
perm = applyPermutation (toPermutation e1 e) (toPermutation e e2)
in if e == sort e2
then Value [encodePermutation perm] (SymmetricType g)
else err "Invalid mapping"

widenMap :: Type -> Element -> Maybe Element
widenMap (SymmetricType g) (Permutation elts) =
if all (`elem` generator g) elts
then Just (Permutation (widenPermutation (generator g) elts))
else Nothing
widenMap (AlternatingType g) (Permutation elts) =
if all (`elem` generator g) elts
then Just (Permutation (widenPermutation (generator g) elts))
else Nothing
widenMap _ _ = Nothing

widenPermutation :: [Element] -> [Element] -> [Element]
widenPermutation widenedElts elts =
map (\ e -> maybe e id (lookup e (zip (sort elts) elts))) widenedElts

module Interp(eval,interp) where

import Data.List(elemIndex,sort,(!!))

import Element
import Expr
import Value

interp :: Expr -> String -> String
interp expr input = toString (eval [('i',fromString input)] expr)

eval :: [(Char,Value)] -> Expr -> Value
eval bindings (Symbol loc c) =
maybe (err loc ("Undefined symbol: " ++ [c])) id (lookup c bindings)
eval bindings (Inverse _ expr) = inverse (eval bindings expr)
eval bindings (Reduce _ expr) = reduce (eval bindings expr)
eval bindings (LeftProjection loc expr) =
leftProjection (err loc) (eval bindings expr)
eval bindings (RightProjection loc expr) =
rightProjection (err loc) (eval bindings expr)
eval bindings (Constant _ str) = fromString str
eval bindings (Prod loc l r) =
prod (err loc) (eval bindings l) (eval bindings r)
eval bindings (DirProd _ l r) = dirProd (eval bindings l) (eval bindings r)
eval bindings (Let _ bindList expr) = eval (bind bindings bindList) expr
eval bindings (Constructor _ typeName) = constructor bindings typeName
eval bindings (Expr.Permutation loc l r) =
permutation (err loc) (eval bindings l) (eval bindings r)

err :: String -> String -> a
err loc msg = error (loc ++ ": " ++ msg)

cast :: (String -> Value) -> [(Char,Value)] -> Char
-> TypeName -> Value -> Value
cast err bindings c typeName value =
mapToType err value (makeType ((c,value):bindings) typeName)

constructor :: [(Char,Value)] -> TypeName -> Value
constructor bindings typeName = constructorValue (makeType bindings typeName)

makeType :: [(Char,Value)] -> TypeName -> Type
makeType bindings (Cyclic exprs) =
CyclicType (generate (makeTypeElements bindings exprs))
makeType bindings (Dihedral exprs) =
DihedralType (generate (makeTypeElements bindings exprs))
makeType bindings (Symmetric exprs) =
SymmetricType (generate (makeTypeElements bindings exprs))
makeType bindings (Alternating exprs) =
AlternatingType (generate (makeTypeElements bindings exprs))
makeType bindings (Dicyclic exprs) =
DicyclicType (generate (makeTypeElements bindings exprs))
makeType bindings (DirectProduct t1 t2) =
DirectProductType (makeType bindings t1) (makeType bindings t2)

makeTypeElements :: [(Char,Value)] -> [Expr] -> [Element]
makeTypeElements bindings exprs =
concatMap toElements (map (eval bindings) exprs)

bind :: [(Char,Value)] -> [Binding] -> [(Char,Value)]
bind bindings [] = bindings
bind bindings ((Binding loc char typeName expr):bindList) =
bind ((char,maybe id (cast (err loc) bindings char) typeName
(eval bindings expr))
: bindings)
bindList

fromString :: String -> Value
fromString str =
let elements = map Element str
in fromElements error (CyclicType (generate elements)) elements

toString :: Value -> String
toString v = concatMap show (toElements v)

module Main(main) where

import Interp(interp)
import Parser(parse)
import System.Environment(getArgs)

main :: IO ()
main = do
(filename:_) <- getArgs
fmap (interp . parse filename) (readFile filename) >>= interact