// while文 private SExp evalWhile(SExp param) { SExp ret = SExpNull; while (true) { var condition = Elms2SExp2(param); if (breakLabel!=null || SExp2string(condition) != "#true") return ret; ret = Elms2SExp2(param.next); // これこの時点で評価されるので1回実行されたことになる } }
// car // (car x)は (array x 0)と等価 private SExp evalCar(SExp exp) { SExp array = Elms2SExp2(exp); return evalArrayOf(array, 0); }
// ファイル名を指定しての削除 private SExp evalDeleteFile(SExp param) { string name = eval_getString(param); try { File.Delete(name); } catch { } return string2SExp(name); }
private SExp evalAddto(SExp exp) { var name = eval_getVariname(exp); SExp e = getVariable(name); // 追加開始する初期文字列として変数の元の値を入れておく if (e == SExpUndef) e = null; foreach_SExp( exp.next, o => e = concatSExp(e, SExp2cloneReplaceVariable(o)) ); setVar(name,e); return e; }
// 配列の要素にindex指定でアクセスする。 // (array x 3) のようにしてアクセスする private SExp evalArray(SExp exp) { SExp array = Elms2SExp2(exp); var index = eval_getNum(exp.next); return evalArrayOf(array, index); }
// SExpに対して、その後続要素がnullであるかどうか。 private static bool isNextNull(SExp exp) { return exp == null || exp.next == null || exp.next.elms == null; }
// expのElmsをSExpに変換その2 // 変数名 → 中身のSExp // 文字列 → WrapしたSExp // SExp → 評価してSExpとして返す // (eq param1 param2) のような形でパラメータをとる関数に使うと良い。 private SExp Elms2SExp2(SExp exp) { if (exp == null) return SExpNull; var elms = exp.elms; if (exp == null) return SExpNull; if (elms is VarName) { return getVariable((elms as VarName).name); } if (elms is string) { return string2SExp(elms as string); } if (elms is SExp) return eval(elms as SExp); return SExpNull; }
// tolowerなど (tolower x 'AAA') のような形でパラメータをとるタイプの // 命令を簡単に実装できるようにするためのヘルパクラス private SExp eval_tolower_helper(SExp exp, Func<string, string> dg) { // 代入する変数 if (!(exp.elms is VarName)) return null; var name = (exp.elms as VarName).name; // そこ以降を文字列として連結する。 var s = SExp2string(evalGet(exp.next)); return setVar(name , string2SExp(dg(s))); }
private void foreach_num(SExp exp, Action<double> dg) { while (exp != null && exp.elms != null && breakLabel == null) { dg(eval_getNumDouble(exp)); exp = exp.next; } }
// exp.elmsがS式ならそれを評価して文字列化して返す。文字列ならそのまま返す。変数名なら評価して返す。 private string eval_getString(SExp exp) { return SExp2string(Elms2SExp2(exp)); }
// exp.elmsに保持している変数名を取得する。ダブルコーテイションで囲まれた文字列はエラーになる。 // SExpの場合、それを評価して文字列化する。 private string eval_getVariname(SExp exp) { if (exp.elms is SExp) { return SExp2string( eval(exp.elms as SExp) ); } // 変数名を意味する文字列のはずなので、これをそのまま返す // 文字列のはずだが、これがダブルコーテイションで囲われていたり、数値であったりしてはいけない。 var ss = exp.elms as VarName; if (ss == null) throw new LispException("変数名が来ていない。"); if (!LispUtil.isVariableName(ss.name)) throw new LispException("変数名として"+ss+"はおかしい。"); // 何故変数名が " "で囲われているのかは知らないが // 本来ありえないはず。 return ss.name; }
// eval_getNumのdoubleで返す版 private double eval_getNumDouble(SExp exp) { if (exp == null || exp.elms == null) return 0; double u; try { u = double.Parse(eval_getString(exp)); } catch { u = 0; } return u; }
// exp.elmsがS式ならそれを評価して文字列化、文字列ならそのまま。 // そして、それを数値にparseして返す private long eval_getNum(SExp exp) { if (exp == null || exp.elms == null) return 0; long u; try { u = long.Parse(eval_getString(exp)); } catch { u = 0; } return u; }
private SExp evalWrite(SExp exp) { var sb = new StringBuilder(); foreach_SExp( exp, o => sb.Append(evalOut_(o)) ); string ret = sb.ToString(); // 出力ファイル名 var filename = SExp2string(getVar("outfile")); if (filename != null && filename != "#undef") { // BOMで書き出すように変更(2009/11/6) これならBOMが追加される。 using (var sw = new StreamWriter(filename, true , Encoding.UTF8)) { sw.WriteLine(ret); } } return string2SExp(ret); }
// ElmsをSExpに // x = ( exp1 exp2 exp3) のようなコレクションに対してforeachで回すときに使う。 // exp = exp1 を指しているとき、これを参照透明と仮定して、SExpに包みたい // ときに使う private static SExp Elms2SExp(SExp exp) { var elms = exp.elms; SExp ret = null; if (elms is string) ret = string2SExp(elms as string); else if (elms is SExp) ret = elms as SExp; else if (elms is VarName) // ここに来て変数名かよ.. ret = new SExp {elms = exp}; return ret; }
// S式の内容を評価する。 private void foreach_SExp(SExp exp, Action<SExp> dg) { while (exp != null && exp.elms != null && breakLabel == null) { dg(Elms2SExp2(exp)); exp = exp.next; } }
// arrayのN番目の要素を返す、下請け関数 private static SExp evalArrayOf(SExp exp, long index) { if (exp == null) return SExpNull; for (long i = 0; i < index; ++i) { exp = exp.next; if (exp == null) return SExpNull; } return Elms2SExp(exp); // 参照透明なのでcloneする必要がない。 }
// 変数名を実体に置換しながら、Cloneしていく。 private SExp SExp2cloneReplaceVariable(SExp exp) { incStack(); var elms = exp.elms; if (elms is SExp) { var el = elms as SExp; var newexp = new SExp {elms = SExp2cloneReplaceVariable(el)}; if (!isNextNull( exp.next ) ) newexp.next = SExp2cloneReplaceVariable(exp.next); return newexp; } if (elms is VarName) { // これを評価して返す必要がある。 var el = getVariable((elms as VarName).name); var newexp = new SExp { elms = SExp2cloneReplaceVariable(el) }; newexp = concatSExp(newexp,SExp2cloneReplaceVariable(exp.next)); return newexp; } if (elms is string) { // stringは参照透明なので、cloneする必要はない。 var newexp = new SExp { elms = elms }; if (!isNextNull( exp ) ) newexp.next = SExp2cloneReplaceVariable(exp.next); return newexp; } // それ以外ってnullか? return null; }
private SExp doCommand(string command, SExp s) { var param = s.next; try { switch (command) { // 文字列追加 case "addto": return evalAddto(param); // 加算 case "add": return evalAdd(param); // 減算 case "sub": return evalSub(param); // 掛け算 case "mul": return evalMul(param); // 割り算 case "div": return evalDiv(param); // 配列のgetter case "array": return evalArray(param); // 配列のsetter case "setarray": return evalSetArray(param); case "length": return evalLength(param); // 出力 console case "out": return evalOut(param); // ファイルにwrite 'outfile'という変数がファイル名を示す。 case "write": return evalWrite(param); // 式を評価して、その値を返す case "get": return evalGet(param); // ファイルの削除 case "del": return evalDeleteFile(param); // 後ろの値を評価せずに代入 case "let": return evalLet(param); // 後ろの値を評価してから代入 case "set": return evalSet(param); // 関数の定義用。 case "func": return evalFunc(param); case "break": return evalBreak(param); case "eval": return evalEval(param); // case "evalweak": // return evalEvalWeak(param); case "evalinclude": return evalEvalInclude(param); case "evalimport": return evalEvalImport(param); case "evalstr": return evalEvalStr(param); // 文字置換 case "replace": return evalReplace(param); // 正規表現置換 case "regex": return evalRegex(param); // foreach case "foreach": return evalForeach(param); // print case "print": return evalPrint(param); // 回数指定繰り返し case "loop": return evalLoop(param); // 小文字化 case "tolower": return evalTolower(param); // 大文字化 case "toupper": return evalToupper(param); // 条件分岐 case "if": return evalIf(param); // == case "eq": return evalEq(param); // != case "neq": return evalNeq(param); // and case "and": return evalAnd(param); // or case "or": return evalOr(param); // 制御構造 // while case "while": return evalWhile(param); // for case "for": return evalFor(param); // downfor ループカウンタが1ずつ減るfor case "downfor": return evalDownfor(param); // 永久ループ case "forever": return evalForever(param); case "switch": return evalSwitch(param); // 比較演算子 // less or equal case "le": return evalLe(param); // less than case "lt": return evalLt(param); // greater equal case "ge": return evalGe(param); // greater than case "gt": return evalGt(param); // 本家LISPの機能 case "car": return evalCar(param); case "cdr": return evalCdr(param); // ファイルを読み込み、それをS式とする。 // ただし、S式は //%で始まる行 case "import": return evalImport(param); // ファイルを読み込み、それをS式とする。 case "include": return evalInclude(param); // 以下、ユーティリティ case "rand": return evalRand(param); // 以下、unroller(YaneC) case "unroller": return evalUnroller(param); } // ユーザー定義関数であれば、それをevalして返す if (functions.ContainsKey(command)) { // local変数が使えるようにする。 // 後続要素を評価して、それらを次のコンテキストのlocal変数に突っ込む var local = new Dictionary<string, SExp>(); int i = 0; while (param!=null && param.elms != null) { // @0,@1,@2,…というローカル変数に突っ込む local["@" + i] = Elms2SExp2(param); ++i; param = param.next; } // ローカル変数コンテキストを切り替える。 localVariables.Push(local); var ret = eval(functions[command]); // 評価 // ローカル変数コンテキストを戻す localVariables.Pop(); return ret; } } catch (Exception ex) { string error = command + "で例外発生。 (" + ex.Message + ") 場所 " + s.line + " 行 (" + s.sourcePos + ")"; Console.WriteLine(error); return string2SExp(error); } // error("Eval不可能な式 :" + command ,s.elms); { string error = command + "は、evalできない。場所" + s.line + " 行 (" + s.sourcePos + ")"; Console.WriteLine(error); return string2SExp(error); // エラーメッセージを文字列として返す。 } }
// Lisp.evalの返し値(SExp)を読みやすい形式に変換して返す。 // 返されたものがstring(文字列)なら、わかるように ' 'で囲って返す。 public static string eval(SExp e) { var lisp = new Lisp(); var ret = lisp.eval(e); return lisp.SExp2string(ret); }
// 加算 private SExp evalAdd(SExp exp) { double sum = 0; foreach_num( exp, n => { sum += n; } ); return string2SExp(sum.ToString()); }
public SExp eval(SExp s) { evalCount++; // evalの深さを測定しておく。 try { if (s == null || s.elms == null) return null; // 評価不可能 // 外側のラベルめがけてbreak中なのか? if (breakLabel != null) return null; // 現在実行しているステートメントを例外発生時の表示のために保持しておく。 evalNow = s; var exp = s.elms as SExp; if (exp != null) { // ( (add 1 2) (sub 4 3) )のようにコマンドが並んでいるケース。 // この場合、後続するリストもすべて実行する必要がある.. if (s.next != null) { var o1 = eval(exp); var o2 = eval(s.next); // 最後の要素が評価されてこのevalの値になるはずだが return o2 ?? o1; } return eval(exp); } var command = s.elms as VarName; if (command != null) { stackCount = 0; // stack overflowの防止用 var name = command.name; // コマンド名 // コマンド名に見えるものは実はラベルなのか? var label = isLabel(name); if (label != null) { labels.Push(label); SExp ret = (s.next != null) ? eval(s.next) : null; labels.Pop(); // このスコープから抜けるのだから、ラベルはもう不要になる…はず // このlabelからbreakしている最中なら、そのbreak labelをリセット if (label == breakLabel) breakLabel = null; return ret; } else { // 命令なので実行する。 return doCommand(name, s); } } // stringなら無視しとけばいいか。 // return string2SExp( command + "は、evalできない。場所 : " + s.line + " 行 (" + s.sourcePos + ")"); if (!isNextNull(s)) return eval(s.next); return null; // 駄目ぽ }finally { evalCount--; if (evalCount == 0 && breakLabel!=null) throw new LispException("breakラベル"+ breakLabel+"が見つからない。" ); } }
// 論理and(&&) private SExp evalAnd(SExp param) { var elms1 = Elms2SExp2(param); var elms2 = Elms2SExp2(param.next); // return (elms1 == SExpTrue && elms2 == SExpTrue) // ↑これだと、user側が#true定数を作れなくて困る?(´ω`) return (SExp2string(elms1) == "#true" && SExp2string(elms2) == "#true") ? SExpTrue : SExpFalse; }
// 変数名に値を設定する。 public SExp setVar(string name, SExp exp) { // 変数名が@で始まるなら、それはlocal変数 if (string.IsNullOrEmpty(name)) return null; if (name[0] == '@') { if (localVariables.Count == 0) throw new LispException("トップレベルでローカル変数は使えない。"); return localVariables.Peek()[name] = exp; } return variables[name] = exp; }
// ラベルめがけてのbreak(JavaScript風) private SExp evalBreak(SExp exp) { var name = eval_getVariname(exp); // これがbreakラベル breakLabel = name; return null; }
// SExpの最初のエレメントの持っている文字列を取得する。 public string SExp2string(SExp exp) { if (exp == null || exp.elms == null) return ""; var elms = exp.elms; string s; if (elms is VarName) s = "#VarName:" + (elms as VarName).name; else if (elms is SExp) s = SExp2string(elms as SExp); // 再帰的に文字列化 else if (elms is string) s= elms as string; else s = "#noname"; return s + (!isNextNull(exp) ? SExp2string(exp.next) : ""); }
// cdr private SExp evalCdr(SExp exp) { SExp array = Elms2SExp2(exp); if (isNextNull(array)) return SExpNull; return array.next; // 後続要素 }
// SExp同士を連結する private static SExp concatSExp(SExp e1, SExp e2) { var exp = e1; if (e1 == null) return e2; while (true) { if (isNextNull(exp) ) { exp.next = e2; return e1; } exp = exp.next; } // expをe2に置換する。すなわち、一つ前のに置換してしまう。 }
// 割り算 private SExp evalDiv(SExp exp) { double sum = 1; bool isFirst = true; foreach_num( exp, n => { if (isFirst) { sum = n; isFirst = false; } else sum /= n; } ); return string2SExp(sum.ToString()); }
private SExp evalUnroller(SExp param) { var f = eval_getString(param); var u = new YaneUnroller.Unroller(); var tree = u.ParseProgram(f); return string2SExp(tree.ToString()); }