import std.stdio; import std.string; import std.random; /* http://www.gigamonkeys.com/book/functions.html http://sunsite.ualberta.ca/Documentation/Gnu/gdb-4.18/html_chapter/readline_2.html#SEC34 http://www.muppetlabs.com/~breadbox/intercal-man/ */ /* TODO: keyword arguments (&key) implement the standard useful functions implement overloads for floats and ratios implement continuations make a better repl write a readAll and evaluateAll that can take a string of many forms and do them all fix bugs make the error reporting better refactor the List struct at least. (and general cleanup) write contracts and tests */ /* Function params specification ( ... &optional... &key... &rest) First comes positional and optional params, just like C++ default values Next come the keyword params, which are bound only to keywords given; they never go under the positional or optional positional params Rest params eat everything else; it never takes optional nor keyword ones */ /++ Lispy encapsulates a small lisp-like interpreter that can be trivially embedded in and expanded by another program. You would normally use it by registering the functions you wrote to expand it, then read an expression, and evaluate what the reader returned. Examples: ------------------------------- Lispy.Atom* doSomethingUseful(Lispy.List* args){ // do something useful here that a scripter should be able to access } Lispy l = new Lispy; // register your extension class with the script interpreter // First is the name the script writer would see. Note that you should use all // uppercase for these names, and dashes (-) are the preferred way of separating // words in lisp like languages. // Second is the number of arguments expected. // Third is a delegate that is called when the script wants to. It must be of the signature // seen above - every function must return a value. // Finally, an optional argument is a documentation string for the function that the user can // see from the script. l.registerFunction("DO-SOMETHING-USEFUL", 0, &doSomethingUseful, "Documentation for the function."); auto result = l.read("(DO-SOMETHING-USEFUL)"); // reads the script's text, and returns a form that // can be evaluated. you should store this and pass it on: result = l.evaluate(result); // actually performs the script. The return value is the result of // the evaluation (in this case, the return value of doSomethingUseful) // Repeat all you want. The lispy class maintains functions registered and user defined, and all // global symbols, so you can keep calling it to continue. ------------------------------- Authors: Adam D. Ruppe, destructionator@gmail.com Date: Jun 07, 2007 License: GNU GPL Copyright Adam D. Ruppe, 2007-2008 +/ class Lispy{ public: /* template parseArgs(A, T, R...){ void parseArgs(A args, out T t, out R r){ // if(args is null) // throw new Exception("not enough args"); void* a = &t; if (typeid(T) == typeid(int)){ a = cast(void*)&args.data.intData; t = *cast(int*)a; } if (typeid(T) == typeid(char[])){ a = cast(void*)&args.data.stringData; } static if(r.length) parseArgs(args.next, r); } } */ template extractArgs(A, T, R...){ void extractArgs(A args, out T t, out R r){ args.data.to(t); static if(r.length) extractArgs(args.next, r); } } this(){ Atom* nil = new Atom; nil.type = Atom.symbol; nil.constant = true; nil.symbolName = "NIL"; internSymbol("NIL", nil); Atom* t = new Atom; t.type = Atom.symbol; t.constant = true; t.symbolName = "T"; internSymbol("T", t); // register the default built-in functions // registerFunction("PRINT", 1, &Lprint); registerFunction("TYPE-OF", 1, &Ltypeof); registerFunction("+", -1, &Lplus); registerFunction("-", -1, &Lminus); registerFunction("*", -1, &Lmultiply); registerFunction("/", -1, &Ldivide); registerFunction("=", -1, &Lequals); registerFunction("LIST", -1, &Llist); registerFunction("FDEFINITION", 1, &fdefinition); registerFunction("GETF", 2, &Lgetf); registerFunction("RANDOM", 1, &Lrandom); registerFunction("DOCUMENTATION", 1, &documentation); registerFunction("CAT", -1, &Lcat); // readAndEvaluateAll(import("lispy.lisp")); } /** Returns the nil atom; similar to null in D. This should be what you return if you would ever return null or false from a script function: ----- Lispy.Atom* someFunction(Lispy.List* args){ return lispy.nil; } ----- */ Atom* nil(){ return symbols["NIL"]; } /** Returns the t atom, which means true. */ Atom* t(){ return symbols["T"]; } // Built in function implementations Atom* Lcat(List* args){ Atom* a = new Atom; a.stringData = args.data.stringData; foreach(i; *(args.next)) a._stringData ~= i.data.stringData; return a; } Atom* documentation(List* args){ Function* f = args.data.functionData; Atom* a = new Atom; a.stringData = f.comment; return a; } Atom* Lequals(List* args){ List* l = args; while(l.next !is null){ if(*(l.data) != *(l.next.data)) return nil; l = l.next; } return t; } Atom* Lrandom(List* args){ Atom* a = new Atom; a.intData = rand() % args.data.intData; return a; } Atom* Lplus(List* args){ Atom* a = new Atom; a.intData = args.data.intData; foreach(i; *(args.next)) a._intData += i.data.intData; return a; } Atom* Ldivide(List* args){ Atom* a = new Atom; a.intData = args.data.intData; foreach(i; *(args.next)) a._intData /= i.data.intData; return a; } Atom* Lmultiply(List* args){ Atom* a = new Atom; a.intData = args.data.intData; foreach(i; *(args.next)) a._intData *= i.data.intData; return a; } Atom* Lminus(List* args){ Atom* a = new Atom; a.intData = args.data.intData; foreach(i; *(args.next)) a._intData -= i.data.intData; return a; } Atom* Lgetf(List* args){ foreach(i;*(args.data.listData)) if(i.data.type == Atom.symbol) if(i.data.symbolName == args.next.data.symbolName) return i.next.data; return nil; } Atom* Llist(List* args){ Atom* a = new Atom; a.listData = args; return a; } Atom* fdefinition(List* args){ Function* f = args.data.functionData; Atom* r = new Atom; List* l = new List; r.listData = l; l.data = new Atom; l.data.stringData = f.name; if(f.type == Function.lispy){ l.next = new List; l.next.data = new Atom; l.next.data.listData = f.arguments; /* l.next.next = new List; l.next.next.data = new Atom; l.next.next.data.listData = f.localSymbols.names; l.next.next.next = new List; l.next.next.next.data = new Atom; l.next.next.next.data.listData = f.localSymbols.symbols; l.next.next.next.next = f.code; */ l.next.next = f.code; } else{ l.next = new List; l.next.data = new Atom; l.next.data.intData = f.argumentsLength; l.next.next = new List; l.next.next.data = new Atom; l.next.next.data.stringData = "NATIVE"; } return r; } /* Atom* Lprint(List* args){ if(args.data !is null) writefln("%s", print(args.data)); return symbols["NIL"]; } */ Atom* Ltypeof(List* args){ Atom* a = new Atom; switch(args.data.type){ case Atom.list: a.stringData = "LIST"; break; case Atom.symbol: a.stringData = "SYMBOL"; break; case Atom.string: a.stringData = "STRING"; break; case Atom.func: a.stringData = "FUNCTION"; break; default: a.stringData = std.string.toString(args.data.type); } return a; } //*********************************** // Lispy proper begins here Atom* internSymbol(char[] name, Atom* value){ Atom** v = name in symbols; if(v) if((*v).constant) throw new Exception("Cannot write to a constant"); symbols[name] = value; return value; } struct Atom{ int type; bool constant; enum { list, symbol, func, continuation, string, integer, floatingPoint, ratio // self evaluating } void to(out int a){ a = intData; } void to(out char[] a){ if(type == string) a = stringData; else a = symbolName; } void to(out Atom* a){ return this; } char[] toString(){ switch(type){ case symbol: return _symbolName; case string: return _stringData; case integer: return std.string.toString(_intData); case floatingPoint: return std.string.toString(_floatData); default: throw new Exception("Type mismatch"); } } union { List* _listData; char[] _symbolName; char[] _stringData; Function* _functionData; int _intData; float _floatData; struct _ratioData { int numerator; int denominator; } } bool opEquals(in Atom a){ if(type != a.type) return false; switch(type){ case list: return _listData == a._listData; case symbol: return _symbolName == a._symbolName; case func: return _functionData == a._functionData; case string: return _stringData == a._stringData; case integer: return _intData == a._intData; case floatingPoint: return _floatData == a._floatData; // case ratio: // return _ratioData == a._ratioData; } } // this should probably be refactored into a class so the above can be private. List* listData(){ if(type != list) throw new Exception("Atom is wrong type - not a list"); return _listData; } void listData(List* l){ type = list; _listData = l; } char[] symbolName(){ if(type != symbol) throw new Exception("Atom is wrong type - not a symbol"); return _symbolName; } void symbolName(char[] s){ type = symbol; _symbolName = s; } char[] stringData(){ if(type != string) throw new Exception("Atom is wrong type"); return _stringData; } void stringData(char[] d){ type = string; _stringData = d; } Function* functionData(){ if(type != func) throw new Exception("Atom is wrong type - not a function"); return _functionData; } void functionData(Function* f){ type = func; _functionData = f; } int intData(){ if(type != integer) throw new Exception("Atom is wrong type - not an integer"); return _intData; } void intData(int a){ type = integer; _intData = a; } float floatData(){ if(type != floatingPoint) throw new Exception("Atom is wrong type"); return _floatData; } void floatData(float f){ type = floatingPoint; _floatData = f; } /* _ratioData ratioData(){ if(type != ratio) throw new Exception("Atom is wrong type"); return _ratioData; } void ratioData(int n, int d){ type = ratio; _ratioData.numerator = n; _ratioData.denominator = d; } */ } struct List{ Atom* data; List* next; void append(Atom* what){ if(data is null){ data = what; return; } List* end = this; while(end.next !is null) end = end.next; end.next = new List; end.next.data = what; } int opApply(int delegate(inout List) dg){ int result = 0; List* l; for(l = this; l != null; l = l.next){ result = dg(*l); if(result) break; } return result; } Atom* first(){ return data; } Atom* second(){ return next.data; } Atom* third(){ return next.next.data; } Atom* fourth(){ return next.next.next.data; } } void ignoreWhitespace(char[] expression, inout int location){ while(location < expression.length && ( expression[location] == ' ' || expression[location] == '\t' || expression[location] == '\n' )) location++; } char[] extractWord(char[] expression, inout int location){ const char[] endingSymbols = "(), \t\n;"; ignoreWhitespace(expression, location); int start = location; bool quoting; outer: while(location < expression.length){ char a = expression[location]; if(a == '\"') quoting = !quoting; if(!quoting) foreach(b; endingSymbols) if(a == b) break outer; location++; } return expression[start..location]; } Atom* read(char[] expression, inout int location){ Atom* result; // Ignore opening whitespace here, since it is irrelevant ignoreWhitespace(expression, location); if(location >= expression.length) return null; if(expression[location] >= '0' && expression[location] <= '9'){ result = new Atom; char[] word = extractWord(expression, location); result.intData = cast(int) atoi(word); // explicit cast to suppress warning } else switch(expression[location]){ case ';': //comment while(expression[location++] != '\n'){ if(location >= expression.length) break; } return read(expression,location); break; case '\'': // quote result = new Atom; result.type = Atom.list; result.listData = new List; result.listData.data = new Atom; result.listData.data.type = Atom.symbol; result.listData.data.symbolName = "QUOTE"; location++; result.listData.append(read(expression, location)); break; case '(': // list result = new Atom; result.type = Atom.list; result.listData = new List; location++; if(location >= expression.length) return null; if(expression[location] == ')') // the empty list (NIL) location++; else while(expression[location] != ')'){ result.listData.append(read(expression, location)); if(location >= expression.length) break; } location++; if(location > expression.length){ location = expression.length; return result; } break; case '\"': // string char[] word = extractWord(expression, location); result = new Atom; result.type = Atom.string; result.stringData = word[1..(length-1)]; // leaving off the quotes break; // case '+': // case '-': // if(expression[location + 1] == // break; default: // some other primitive char[] word = extractWord(expression, location); if(word.length == 0) return null; result = new Atom; result.type = Atom.symbol; result.symbolName = toupper(word); //FIXME should handle escaping } return result; } // the breaks here are commented out since they generate warnings; they are unreachable due to the // preceding returns Atom* evaluate(Atom* expression, LocalSymbols* specialSymbols = null){ if(expression is null) return null; switch(expression.type){ case Atom.list: if(expression.listData.data is null) // the empty list evaluates to itself return expression; switch(expression.listData.data.symbolName){ // Handle special symbols case "SET": Atom* what = evaluate(expression.listData.next.data, specialSymbols); Atom* as = evaluate(expression.listData.next.next.data, specialSymbols); if(specialSymbols !is null){ Atom* a = specialSymbols.set(what, as); if(a is null) return internSymbol(what.symbolName, as); return a; } return internSymbol(what.symbolName, as); //break; case "LET": LocalSymbols* l = new LocalSymbols; if(specialSymbols !is null) l.populateFromLocals(specialSymbols); List* stuff = expression.listData.next; List* toBeSet = stuff.data.listData; List* code = stuff.next; while(toBeSet !is null && toBeSet.data !is null){ List* data = toBeSet.data.listData; Atom* what = data.data; Atom* as = evaluate(data.next.data, specialSymbols); l.let(what, as); toBeSet = toBeSet.next; } Atom* result; while(code !is null){ Atom* c = new Atom; c = code.data; result = evaluate(c, l); code = code.next; } return result; //break; case "QUOTE": Atom* a = expression.listData.next.data; return a; //break; case "FUNCTION": Function* f = expression.listData.next.data.symbolName in functions; if(f is null) throw new Exception("No such function"); Atom* a = new Atom; a.functionData = f; return a; //break; case "DEFUN": char[] s = expression.listData.next.data.symbolName; List* args = expression.listData.next.next.data.listData; char[] comment; List* code; if(expression.listData.next.next.next.data.type == Atom.string){ comment = expression.listData.next.next.next.data.stringData; code = expression.listData.next.next.next.next; } else code = expression.listData.next.next.next; registerFunction(s, args, code, specialSymbols, comment); return internSymbol(s, expression.listData.next.data); //break; case "DEFMACRO": char[] s = expression.listData.next.data.symbolName; char[] comment; List* args = expression.listData.next.next.data.listData; List* code; if(expression.listData.next.next.next.data.type == Atom.string){ comment = expression.listData.next.next.next.data.stringData; code = expression.listData.next.next.next.next; } else code = expression.listData.next.next.next; registerMacro(s, args, code); return internSymbol(s, expression.listData.next.data); //break; case "LAMBDA": Function* f = new Function; f.type = Function.lispy; f.name = "LAMBDA"; f.arguments = expression.listData.next.data.listData; f.code = expression.listData.next.next; f.localSymbols = specialSymbols; Atom* a = new Atom; a.functionData = f; return a; //break; case "FUNCALL": return callFunction(evaluate(expression.listData.next.data, specialSymbols), expression.listData.next.next, specialSymbols); //break; case "ALIAS": aliases[expression.listData.next.data.symbolName] = expression.listData.next.next.data.symbolName; return expression.listData.next.next.data; //break; case "IF": Atom* condition = evaluate(expression.listData.next.data, specialSymbols); Atom* ifTrue = expression.listData.next.next.data; Atom* ifFalse = (expression.listData.next.next.next !is null) ? expression.listData.next.next.next.data : null; if(condition == nil) if(ifFalse) return evaluate(ifFalse, specialSymbols); else return nil; else return evaluate(ifTrue, specialSymbols); //break; default: return callFunction(expression.listData.data, expression.listData.next, specialSymbols); } break; case Atom.symbol: if(expression.symbolName[0] == ':' ||expression.symbolName[0] == '&') return expression; if(specialSymbols !is null){ Atom* b = specialSymbols.get(expression); if(b) return b; } char[] name = expression.symbolName; char[]* newname = name in aliases; if(newname !is null) name = *newname; Atom** a = name in symbols; if(a) return *a; throw new Exception(expression.symbolName ~ ": not defined"); //break; default: // case Atom.string: return expression; //break; } return null; } char[] print(Atom* expression){ char[] result; if(expression is null) return "NULL EXPRESSION"; switch(expression.type){ case Atom.symbol: result ~= expression.symbolName; break; case Atom.list: result ~= "("; foreach(b; *expression.listData){ if(b.data) result ~= print(b.data); if(b.next) result ~= " "; } result ~= ")"; break; case Atom.string: result ~= "\""; result ~= expression.stringData;; result ~= "\""; break; case Atom.func: result ~= "= 0 && l != (*f).argumentsLength) throw new Exception("Wrong number of arguments given to function"); result = (*f).nativeCode(evalArgs); } return result; } void readAndEvaluateAll(char[] text){ int location; while(location < text.length){ Atom* r = read(text, location); evaluate(r); // HERE IS ANOTHER } } protected: private: Atom*[char[]] symbols; Function[char[]] functions; char[][char[]] aliases; } version(standalone){ char[] readline(){ char line[80]; if(scanf("%80[^\n]%*c", line.ptr) != 1) throw new Exception("Readline failure"); char[] s = toString(toStringz(line)); return s; } int main(char[][] args){ Lispy.Atom* result; Lispy li = new Lispy; bool quit; int l; Lispy.Atom* Lprint(Lispy.List* args){ if(args.data !is null) writef("%s\n", li.print(args.data)); return li.nil; } li.registerFunction("PRINT", 1, &Lprint); Lispy.Atom* Lbye(Lispy.List* args){ quit = true; return li.nil; } li.registerFunction("BYE", 0, &Lbye); while(!quit){ writef("> "); char[] line = readline(); if(line.length == 0){ continue; } try{ result = li.read(line, l); result = li.evaluate(result); }catch(Exception e){ writef("Error: %s\n", e.toString()); result = null; } if(result) writef("%s\n", li.print(result)); } return 0; } }