1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-19 09:38:28 +03:00
mal/impls/cs/step7_quote.cs
Nicolas Boulenguez fbfe6784d2 Change quasiquote algorithm
- Add a `vec` built-in function in step7 so that `quasiquote` does not
  require `apply` from step9.
- Introduce quasiquoteexpand special in order to help debugging step7.
  This may also prepare newcomers to understand step8.
- Add soft tests.
- Do not quote numbers, strings and so on.

Should ideally have been in separate commits:
- elisp: simplify and fix (keyword :k)
- factor: fix copy/paste error in let*/step7, simplify eval-ast.
- guile: improve list/vector types
- haskell: revert evaluation during quasiquote
- logo, make: cosmetic issues
2020-08-11 01:01:56 +02:00

233 lines
8.0 KiB
C#

using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using Mal;
using MalVal = Mal.types.MalVal;
using MalString = Mal.types.MalString;
using MalSymbol = Mal.types.MalSymbol;
using MalInt = Mal.types.MalInt;
using MalList = Mal.types.MalList;
using MalVector = Mal.types.MalVector;
using MalHashMap = Mal.types.MalHashMap;
using MalFunc = Mal.types.MalFunc;
using Env = Mal.env.Env;
namespace Mal {
class step7_quote {
// read
static MalVal READ(string str) {
return reader.read_str(str);
}
// eval
public static bool starts_with(MalVal ast, string sym) {
if (ast is MalList && !(ast is MalVector)) {
MalList list = (MalList)ast;
if (list.size() == 2 && list[0] is MalSymbol) {
MalSymbol a0 = (MalSymbol)list[0];
return a0.getName() == sym;
}
}
return false;
}
public static MalVal qq_loop(MalList ast) {
MalVal acc = new MalList();
for(int i=ast.size()-1; 0<=i; i-=1) {
MalVal elt = ast[i];
if (starts_with(elt, "splice-unquote")) {
acc = new MalList(new MalSymbol("concat"), ((MalList)elt)[1], acc);
} else {
acc = new MalList(new MalSymbol("cons"), quasiquote(elt), acc);
}
}
return acc;
}
public static MalVal quasiquote(MalVal ast) {
// Check Vector subclass before List.
if (ast is MalVector) {
return new MalList(new MalSymbol("vec"), qq_loop(((MalList)ast)));
} else if (starts_with(ast, "unquote")) {
return ((MalList)ast)[1];
} else if (ast is MalList) {
return qq_loop((MalList)ast);
} else if (ast is MalSymbol || ast is MalHashMap) {
return new MalList(new MalSymbol("quote"), ast);
} else {
return ast;
}
}
static MalVal eval_ast(MalVal ast, Env env) {
if (ast is MalSymbol) {
return env.get((MalSymbol)ast);
} else if (ast is MalList) {
MalList old_lst = (MalList)ast;
MalList new_lst = ast.list_Q() ? new MalList()
: (MalList)new MalVector();
foreach (MalVal mv in old_lst.getValue()) {
new_lst.conj_BANG(EVAL(mv, env));
}
return new_lst;
} else if (ast is MalHashMap) {
var new_dict = new Dictionary<string, MalVal>();
foreach (var entry in ((MalHashMap)ast).getValue()) {
new_dict.Add(entry.Key, EVAL((MalVal)entry.Value, env));
}
return new MalHashMap(new_dict);
} else {
return ast;
}
}
static MalVal EVAL(MalVal orig_ast, Env env) {
MalVal a0, a1, a2, res;
MalList el;
while (true) {
//Console.WriteLine("EVAL: " + printer._pr_str(orig_ast, true));
if (!orig_ast.list_Q()) {
return eval_ast(orig_ast, env);
}
// apply list
MalList ast = (MalList)orig_ast;
if (ast.size() == 0) { return ast; }
a0 = ast[0];
String a0sym = a0 is MalSymbol ? ((MalSymbol)a0).getName()
: "__<*fn*>__";
switch (a0sym) {
case "def!":
a1 = ast[1];
a2 = ast[2];
res = EVAL(a2, env);
env.set((MalSymbol)a1, res);
return res;
case "let*":
a1 = ast[1];
a2 = ast[2];
MalSymbol key;
MalVal val;
Env let_env = new Env(env);
for(int i=0; i<((MalList)a1).size(); i+=2) {
key = (MalSymbol)((MalList)a1)[i];
val = ((MalList)a1)[i+1];
let_env.set(key, EVAL(val, let_env));
}
orig_ast = a2;
env = let_env;
break;
case "quote":
return ast[1];
case "quasiquoteexpand":
return quasiquote(ast[1]);
case "quasiquote":
orig_ast = quasiquote(ast[1]);
break;
case "do":
eval_ast(ast.slice(1, ast.size()-1), env);
orig_ast = ast[ast.size()-1];
break;
case "if":
a1 = ast[1];
MalVal cond = EVAL(a1, env);
if (cond == Mal.types.Nil || cond == Mal.types.False) {
// eval false slot form
if (ast.size() > 3) {
orig_ast = ast[3];
} else {
return Mal.types.Nil;
}
} else {
// eval true slot form
orig_ast = ast[2];
}
break;
case "fn*":
MalList a1f = (MalList)ast[1];
MalVal a2f = ast[2];
Env cur_env = env;
return new MalFunc(a2f, env, a1f,
args => EVAL(a2f, new Env(cur_env, a1f, args)) );
default:
el = (MalList)eval_ast(ast, env);
var f = (MalFunc)el[0];
MalVal fnast = f.getAst();
if (fnast != null) {
orig_ast = fnast;
env = f.genEnv(el.rest());
} else {
return f.apply(el.rest());
}
break;
}
}
}
// print
static string PRINT(MalVal exp) {
return printer._pr_str(exp, true);
}
// repl
static void Main(string[] args) {
var repl_env = new Mal.env.Env(null);
Func<string, MalVal> RE = (string str) => EVAL(READ(str), repl_env);
// core.cs: defined using C#
foreach (var entry in core.ns) {
repl_env.set(new MalSymbol(entry.Key), entry.Value);
}
repl_env.set(new MalSymbol("eval"), new MalFunc(
a => EVAL(a[0], repl_env)));
int fileIdx = 0;
if (args.Length > 0 && args[0] == "--raw") {
Mal.readline.mode = Mal.readline.Mode.Raw;
fileIdx = 1;
}
MalList _argv = new MalList();
for (int i=fileIdx+1; i < args.Length; i++) {
_argv.conj_BANG(new MalString(args[i]));
}
repl_env.set(new MalSymbol("*ARGV*"), _argv);
// core.mal: defined using the language itself
RE("(def! not (fn* (a) (if a false true)))");
RE("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))");
if (args.Length > fileIdx) {
RE("(load-file \"" + args[fileIdx] + "\")");
return;
}
// repl loop
while (true) {
string line;
try {
line = Mal.readline.Readline("user> ");
if (line == null) { break; }
if (line == "") { continue; }
} catch (IOException e) {
Console.WriteLine("IOException: " + e.Message);
break;
}
try {
Console.WriteLine(PRINT(RE(line)));
} catch (Mal.types.MalContinue) {
continue;
} catch (Exception e) {
Console.WriteLine("Error: " + e.Message);
Console.WriteLine(e.StackTrace);
continue;
}
}
}
}
}