From c761c0a4824c0a165414ab1c81da8513de610664 Mon Sep 17 00:00:00 2001 From: Troy Varney Date: Wed, 28 Nov 2012 20:59:35 -0600 Subject: [PATCH 1/6] Fixed parsing error with numbers in symbol names. --- crono/src/crono/Parser.jj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crono/src/crono/Parser.jj b/crono/src/crono/Parser.jj index 31a22c2..5bda05c 100644 --- a/crono/src/crono/Parser.jj +++ b/crono/src/crono/Parser.jj @@ -61,7 +61,7 @@ TOKEN: | < CHAR : "'" ([" "-"~"] | ("\\" ~["\n", "\r", "\t"])) "'" > | < STRING : "\"" ("\\" ~["\n"] | ~["\"", "\n"])* "\"" > | < TYPEID : ":" (["a"-"z","A"-"Z"] (["a"-"z","A"-"Z","-"])?)+ > - | < SYMBOL : ["!"-"&", "*"-"/", ";"-"Z", "\\", "^"-"~"] (["!"-"'", "*"-"/", ";"-"Z", "\\", "^"-"~"])* > + | < SYMBOL : ["!"-"&", "*"-"/", ";"-"Z", "\\", "^"-"~"] (["!"-"'", "*"-"9", ";"-"Z", "\\", "^"-"~"])* > | < QUOTE : "'" > } From be0c7b6941e0acddb24599676d0cd03002803411 Mon Sep 17 00:00:00 2001 From: Troy Varney Date: Wed, 28 Nov 2012 21:02:15 -0600 Subject: [PATCH 2/6] Added subst function to perform symbol substitution. --- crono/src/crono/CronoFunction.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crono/src/crono/CronoFunction.java b/crono/src/crono/CronoFunction.java index 38522ee..b65b4b5 100644 --- a/crono/src/crono/CronoFunction.java +++ b/crono/src/crono/CronoFunction.java @@ -57,6 +57,16 @@ public CronoType run(Visitor v, CronoType[] args) { public String toString() { return "cdr"; } + }), + SUBST(new Function(new TypeId[]{CronoType.TYPEID}, CronoType.TYPEID, 1, + EvalType.PARTIAL) + { + public CronoType run(Visitor v, CronoType[] args) { + return args[0]; + } + public String toString() { + return "subst"; + } }), LIST(new Function(new TypeId[]{CronoType.TYPEID}, Cons.TYPEID, 1, true) { From bd7ad3f5a175c8ae003a3a3793ab3fef17cea773 Mon Sep 17 00:00:00 2001 From: Troy Varney Date: Wed, 28 Nov 2012 21:40:15 -0600 Subject: [PATCH 3/6] Added try and raise functions. --- crono/src/crono/CronoException.java | 11 +++++++++ crono/src/crono/CronoFunction.java | 34 +++++++++++++++++++++++++++ crono/src/crono/type/CronoString.java | 12 ++++++++++ 3 files changed, 57 insertions(+) create mode 100644 crono/src/crono/CronoException.java diff --git a/crono/src/crono/CronoException.java b/crono/src/crono/CronoException.java new file mode 100644 index 0000000..6698ff0 --- /dev/null +++ b/crono/src/crono/CronoException.java @@ -0,0 +1,11 @@ +package crono; + +import crono.type.CronoType; + +public class CronoException extends InterpreterException { + public final CronoType thrown; + public CronoException(CronoType thrown) { + super("CronoException: %s", thrown); + this.thrown = thrown; + } +} \ No newline at end of file diff --git a/crono/src/crono/CronoFunction.java b/crono/src/crono/CronoFunction.java index b65b4b5..77678dd 100644 --- a/crono/src/crono/CronoFunction.java +++ b/crono/src/crono/CronoFunction.java @@ -917,6 +917,40 @@ public String toString() { return "eval"; } }), + TRY(new Function(new TypeId[]{Symbol.TYPEID, CronoType.TYPEID, + CronoType.TYPEID}, CronoType.TYPEID, 3, + false, EvalType.NONE) + { + public CronoType run(Visitor v, CronoType[] args) { + Visitor.VisitorState state = v.getState(); + CronoType result = Nil.NIL; + try{ + result = args[1].accept(v); + }catch(CronoException ce) { + v.setState(state); + v.getEnv().put((Symbol)args[0], ce.thrown); + result = args[2].accept(v); + }catch(Exception ex) { + v.setState(state); + CronoString thrown = CronoString.fromString(ex.getMessage()); + v.getEnv().put((Symbol)args[0], thrown); + result = args[2].accept(v); + } + return result; + } + public String toString() { + return "try"; + } + }), + RAISE(new Function(new TypeId[]{CronoType.TYPEID}, Nil.TYPEID, 1) + { + public CronoType run(Visitor v, CronoType[] args) { + throw new CronoException(args[0]); + } + public String toString() { + return "raise"; + } + }), ; public final Function function; diff --git a/crono/src/crono/type/CronoString.java b/crono/src/crono/type/CronoString.java index 90ea92b..7ed13e5 100644 --- a/crono/src/crono/type/CronoString.java +++ b/crono/src/crono/type/CronoString.java @@ -17,6 +17,18 @@ public class CronoString extends CronoArray { protected StringBuilder data; protected int size; + public static CronoString fromString(String str) { + CronoString s = new CronoString(); + s.data.append(str); + s.size = str.length(); + return s; + } + + public CronoString() { + super(CronoCharacter.TYPEID); + data = new StringBuilder(); + size = 0; + } public CronoString(String str) { super(CronoCharacter.TYPEID); From 7c64820db602ab3e9c4c3a1372baeddf6e8bb90a Mon Sep 17 00:00:00 2001 From: Troy Varney Date: Wed, 28 Nov 2012 21:42:32 -0600 Subject: [PATCH 4/6] Fixed issue with de-indenting after using the raise function. --- crono/src/crono/Interpreter.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crono/src/crono/Interpreter.java b/crono/src/crono/Interpreter.java index 032faa1..750b9ac 100644 --- a/crono/src/crono/Interpreter.java +++ b/crono/src/crono/Interpreter.java @@ -138,8 +138,12 @@ protected void indent() { */ protected void deindent() { int size = indent.length(); - indent.deleteCharAt(size - 1); - indent.deleteCharAt(size - 2); + if(size < 2) { + indent = new StringBuilder(); + }else { + indent.deleteCharAt(size - 1); + indent.deleteCharAt(size - 2); + } } /** From 97347991d5bd0b69f284d454a45c85c892235133 Mon Sep 17 00:00:00 2001 From: Troy Varney Date: Wed, 28 Nov 2012 21:46:12 -0600 Subject: [PATCH 5/6] Fixed possible bug with the interpreter not resetting options when a Function throws an exception. --- crono/src/crono/Interpreter.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/crono/src/crono/Interpreter.java b/crono/src/crono/Interpreter.java index 750b9ac..a7a130e 100644 --- a/crono/src/crono/Interpreter.java +++ b/crono/src/crono/Interpreter.java @@ -395,7 +395,12 @@ public CronoType visit(Cons c) { CronoType[] argarray = new CronoType[args.size()]; optionsOff(); - CronoType lresult = lfun.run(this, args.toArray(argarray)); + CronoType lresult = null; + try { + lresult = lfun.run(this, args.toArray(argarray)); + }catch(RuntimeException e) { + except(e); + } resetOptions(); deindent(); @@ -421,8 +426,13 @@ public CronoType visit(Cons c) { } optionsOff(); - CronoType fresult; - fresult = ((Function)value).run(this, args.toArray(argarray)); + CronoType fresult = null; + Function fun2 = (Function)value; + try { + fresult = fun2.run(this, args.toArray(argarray)); + }catch(RuntimeException re) { + except(re); + } resetOptions(); deindent(); From 2e85fe585ca0b87eafb572f0d5eb7bb9e9fecaa5 Mon Sep 17 00:00:00 2001 From: Troy Varney Date: Thu, 29 Nov 2012 03:50:16 -0600 Subject: [PATCH 6/6] Added proper nested-lambda symbol substitution, a debug mode to build.xml (line numbers, yay!), and some new Exception classes. --- crono/build.xml | 10 +- crono/src/crono/Interpreter.java | 172 ++++++++++++---------- crono/src/crono/SymbolScopeException.java | 9 ++ crono/src/crono/TooManyArgsException.java | 27 ++++ crono/src/crono/TypeScopeException.java | 9 ++ crono/src/crono/type/LambdaFunction.java | 10 +- 6 files changed, 150 insertions(+), 87 deletions(-) create mode 100644 crono/src/crono/SymbolScopeException.java create mode 100644 crono/src/crono/TooManyArgsException.java create mode 100644 crono/src/crono/TypeScopeException.java diff --git a/crono/build.xml b/crono/build.xml index 64d099f..09f458f 100644 --- a/crono/build.xml +++ b/crono/build.xml @@ -16,13 +16,21 @@ + + + + + + + + - + diff --git a/crono/src/crono/Interpreter.java b/crono/src/crono/Interpreter.java index 324575a..4710cfc 100644 --- a/crono/src/crono/Interpreter.java +++ b/crono/src/crono/Interpreter.java @@ -49,9 +49,6 @@ public InterpreterState() { } } - private static final String _scope_err = "No object %s in scope"; - private static final String _too_many_args = - "Too many arguments to %s: %d/%d recieved"; private static final String _type_scope_err = "No type %s in scope"; private static final String _type_mismatch = "Function '%s' expected arguments %s; got %s"; @@ -67,6 +64,10 @@ public InterpreterState() { protected StringBuilder indent; protected Stack envStack; + /* Used for keeping track of how deep we are down the nested-lambda rabbit + * hole. */ + private int lambdaDepth; + /** * Creates a new Interpreter with default option values. */ @@ -77,6 +78,8 @@ public Interpreter() { trace(false); printAST(false); + lambdaDepth = 0; + indent = new StringBuilder(); eval = Function.EvalType.FULL; @@ -275,7 +278,6 @@ public CronoType visit(Cons c) { CronoType value = iter.next().accept(this); if(value instanceof Function) { Function fun = ((Function)value); - EvalType reserve = eval; eval = fun.eval; @@ -292,8 +294,11 @@ public CronoType visit(Cons c) { int nargs = fun.arity; if(arglen < nargs) { if(arglen == 0) { - /* Special case -- we don't have to do anything to the - * function to return it properly. */ + if(fun instanceof LambdaFunction) { + /* We technically have to substitute these */ + fun = substitute((LambdaFunction)fun,new CronoType[0]); + } + deindent(); traceResult(fun); printEnvironment(); @@ -302,49 +307,10 @@ public CronoType visit(Cons c) { /* Curry it */ if(fun instanceof LambdaFunction) { + /* Lambdas are easy - just use the substitute method */ LambdaFunction lfun = ((LambdaFunction)fun); - Environment env = getEnv(); - if(!dynamic) { - /* Use the lambda's stored environment */ - env = lfun.environment; - } - /* We want to preserve the current environment */ - env = new Environment(false); - - /* Put known values into the new environment */ - for(int i = 0; i < arglen; ++i) { - env.put(lfun.arglist[i], args.get(i)); - } - /* Create new argument list and remove remaining args from - * the new environment */ - List largs = new ArrayList(); - for(int i = arglen; i < lfun.arglist.length; ++i) { - largs.add(lfun.arglist[i]); - env.remove(lfun.arglist[i]); - } - - /* Evaluate the body as much as possible */ - reserve = eval; - - CronoType[] lbody = new CronoType[lfun.body.length]; - optionsOff(); /*< Extra indent for clarity */ - { - eval = EvalType.PARTIAL; - pushEnv(env); - for(int i = 0; i < lfun.body.length; ++i) { - lbody[i] = lfun.body[i].accept(this); - } - eval = reserve; - popEnv(); - } - resetOptions(); /*< Set options back to what they were */ - - /* Return the new, partially evaluated lambda */ - Symbol[] arglist = new Symbol[largs.size()]; - - LambdaFunction clfun; - clfun = new LambdaFunction(largs.toArray(arglist), lbody, - lfun.environment); + CronoType[] larr = new CronoType[arglen]; + LambdaFunction clfun = substitute(lfun,args.toArray(larr)); deindent(); traceResult(clfun); printEnvironment(); @@ -379,46 +345,31 @@ public CronoType visit(Cons c) { } if(arglen > nargs && !fun.variadic) { eval = reserve; - except(new InterpreterException(_too_many_args, fun, arglen, - nargs)); + except(new TooManyArgsException(fun, arglen, nargs)); } /* Full evaluation */ if(eval == EvalType.FULL) { - if(fun instanceof LambdaFunction && dynamic) { - /* We have to trick the lambda function if we want dynamic - * scoping. I hate making so many objects left and right, - * but this is the easiest way to do what I want here. */ - LambdaFunction lfun = ((LambdaFunction)fun); - lfun = new LambdaFunction(lfun.arglist, lfun.body, - getEnv()); - CronoType[] argarray = new CronoType[args.size()]; - - optionsOff(); - CronoType lresult = null; - try { - lresult = lfun.run(this, args.toArray(argarray)); - }catch(RuntimeException e) { - except(e); + CronoType[] argarray = + args.toArray(new CronoType[args.size()]); + + if(fun instanceof LambdaFunction) { + LambdaFunction lfun = (LambdaFunction)fun; + if(dynamic) { + lfun = new LambdaFunction(lfun.arglist, lfun.body, + getEnv()); } - resetOptions(); - - deindent(); - traceResult(lresult); - printEnvironment(); - - return lresult; + fun = substitute(lfun, argarray); } - CronoType[] argarray = new CronoType[args.size()]; - argarray = args.toArray(argarray); TypeId[] types = new TypeId[args.size()]; for(int i = 0; i < types.length; ++i) { types[i] = argarray[i].typeId(); } - int check = Math.min(argarray.length,fun.args.length); - for(int i = 0; i < check; ++i) { - if(!(fun.args[i].isType(argarray[i]))) { + int check = 0; + for(int i = 0; i < fun.args.length; ++i) { + check = Math.min(i, argarray.length); + if(!(fun.args[i].isType(argarray[check]))) { String argstr = Arrays.toString(types); String expected = Arrays.toString(fun.args); except(new InterpreterException(_type_mismatch, fun, @@ -428,9 +379,8 @@ public CronoType visit(Cons c) { optionsOff(); CronoType fresult = null; - Function fun2 = (Function)value; try { - fresult = fun2.run(this, args.toArray(argarray)); + fresult = fun.run(this, args.toArray(argarray)); }catch(RuntimeException re) { except(re); } @@ -493,7 +443,7 @@ public CronoType visit(Atom a) { t = getEnv().get((Symbol)a); if(t == null) { if(eval == EvalType.FULL) { - except(new InterpreterException(_scope_err, a.repr())); + except(new SymbolScopeException((Symbol)a)); } t = a; } @@ -505,7 +455,7 @@ public CronoType visit(Atom a) { t = getEnv().getType((CronoTypeId)t); if(t == null) { if(eval == EvalType.FULL) { - except(new InterpreterException(_type_scope_err, a)); + except(new TypeScopeException(((CronoTypeId)res).type)); } t = res; /*< Revert to symbol resolution */ } @@ -558,6 +508,66 @@ public void setState(Visitor.VisitorState state) { } } + /* Cut down on code duplication. */ + private LambdaFunction substitute(LambdaFunction lfun, CronoType[] args) { + EvalType reserve = eval; + LambdaFunction ret = null; + + /* Everything we do here should be undone after substutition. */ + lambdaDepth++; + if(lambdaDepth <= 1) { + optionsOff(); + eval = EvalType.PARTIAL; + pushEnv(new Environment(false)); + ret = doSubst(lfun, args); + popEnv(); + eval = reserve; + resetOptions(); + }else { + /* If this is not the first time, we need to create a copy of the + * environment. This could be optimized by using some form of + * transactional database instead of a map for the Environment. + */ + pushEnv(new Environment(getEnv())); + ret = doSubst(lfun, args); + popEnv(); + } + lambdaDepth--; + return ret; + } + private LambdaFunction doSubst(LambdaFunction lfun, CronoType[] args) { + /* Initialization for substitution */ + Environment env = getEnv(); + CronoType[] body = new CronoType[lfun.body.length]; + int remain = lfun.arglist.length - args.length; + if(remain < 0) { + throw new TooManyArgsException(lfun, lfun.arglist.length, + args.length); + } + Symbol[] largs = new Symbol[remain]; + + /* Put variables we have in the substitution env */ + for(int i = 0; i < args.length; ++i) { + env.put(lfun.arglist[i], args[i]); + } + + /* Remove symbols that this lambda defines but doesn't have. */ + Symbol missing = null; + for(int i = 0; i < largs.length; ++i) { + missing = lfun.arglist[args.length + i]; + largs[i] = missing; + env.remove(missing); + } + + /* Loop over the body and have the interpreter visit them */ + for(int i = 0; i < body.length; ++i) { + /* This may call substitute */ + body[i] = lfun.body[i].accept(this); + } + + return new LambdaFunction(largs, body, lfun.environment); + } + public Environment getEnv() { return envStack.peek(); } diff --git a/crono/src/crono/SymbolScopeException.java b/crono/src/crono/SymbolScopeException.java new file mode 100644 index 0000000..ebc5612 --- /dev/null +++ b/crono/src/crono/SymbolScopeException.java @@ -0,0 +1,9 @@ +package crono; + +import crono.type.Symbol; + +public class SymbolScopeException extends InterpreterException { + public SymbolScopeException(Symbol sym) { + super("No object %s in scope", sym); + } +} diff --git a/crono/src/crono/TooManyArgsException.java b/crono/src/crono/TooManyArgsException.java new file mode 100644 index 0000000..4b83289 --- /dev/null +++ b/crono/src/crono/TooManyArgsException.java @@ -0,0 +1,27 @@ +package crono; + +import crono.type.CronoType; +import crono.type.Function; + +public class TooManyArgsException extends InterpreterException { + protected static String argsTypeString(CronoType[] args) { + StringBuilder builder = new StringBuilder("["); + if(args.length > 0) { + builder.append(args[0].typeId().toString()); + } + for(int i = 1; i < args.length; ++i) { + builder.append(", "); + builder.append(args[i].typeId().toString()); + } + builder.append("]"); + return builder.toString(); + } + + public TooManyArgsException(Function f, int required, int given) { + super("Too many arguments to %s: %d/%d recieved", f, required, given); + } + public TooManyArgsException(Function f, int r, int g, CronoType[] args) { + super("Too many arguments to %s: %d/%d recieved; args = %s", f,r,g, + argsTypeString(args)); + } +} diff --git a/crono/src/crono/TypeScopeException.java b/crono/src/crono/TypeScopeException.java new file mode 100644 index 0000000..8caba63 --- /dev/null +++ b/crono/src/crono/TypeScopeException.java @@ -0,0 +1,9 @@ +package crono; + +import crono.type.TypeId; + +public class TypeScopeException extends InterpreterException { + public TypeScopeException(TypeId id) { + super("No type %s in scope", id.toString()); + } +} diff --git a/crono/src/crono/type/LambdaFunction.java b/crono/src/crono/type/LambdaFunction.java index 5f6a3ed..c8eefb9 100644 --- a/crono/src/crono/type/LambdaFunction.java +++ b/crono/src/crono/type/LambdaFunction.java @@ -40,16 +40,16 @@ public EvalType eval() { public CronoType run(Visitor v, CronoType[] args) { Environment env = new Environment(environment); - for(int i = 0; i < args.length; ++i) { - env.put(arglist[i], args[i]); - } + //for(int i = 0; i < args.length; ++i) { + // env.put(arglist[i], args[i]); + //} - v.pushEnv(env); + //v.pushEnv(env); CronoType ret = null; for(int i = 0; i < body.length; ++i) { ret = body[i].accept(v); } - v.popEnv(); + //v.popEnv(); return ret; }