/**
* Authors: k.inaba
* License: NYSL 0.9982 http://www.kmonos.net/nysl/
*
* Evaluator for Polemy programming language.
*/
module polemy.eval;
import polemy._common;
import polemy.failure;
import polemy.ast;
import polemy.parse;
import polemy.value;
import polemy.layer;
class Evaluator
{
public:
this() { theContext = new Table; }
Value evalAST(AST e)
{
return eval(e, ValueLayer, theContext, OverwriteCtx);
}
Value evalString(S,T...)(S str, T fn_ln_cn)
{
return evalAST(parseString(str,fn_ln_cn));
}
Value evalFile(S,T...)(S filename, T ln_cn)
{
return evalAST(parseFile(filename,ln_cn));
}
Table globalContext()
{
return theContext;
}
private:
Table theContext;
private:
enum : bool { CascadeCtx=false, OverwriteCtx=true };
Value eval( AST e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
{
// dynamic-overload-resolution-pattern: modify here
enum funName = "eval";
alias TypeTuple!(e,lay,ctx,overwriteCtx) params;
// dynamic-overload-resolution-pattern: dispatch
alias typeof(__traits(getOverloads, this, funName)) ovTypes;
alias staticMap!(firstParam, ovTypes) fstTypes;
alias DerivedToFront!(fstTypes) fstTypes_sorted;
foreach(i, T; fstTypes_sorted)
static if( is(T == typeof(params[0])) ) {} else if( auto _x = cast(T)params[0] )
return __traits(getOverloads, this, funName)[i](_x, params[1..$]);
// dynamic-overload-resolution-pattern: default behavior
assert(false, text("eval() for ",typeid(e)," [",e.pos,"] is not defined"));
}
private:
Value eval( Str e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
{
Value v = new StrValue(e.data);
if( lay==RawMacroLayer || lay==MacroLayer )
{
auto ast = new Table;
ast.set("pos", ValueLayer, fromPos(e.pos));
ast.set("is", ValueLayer, new StrValue("str"));
ast.set("data", ValueLayer, v);
return ast;
}
if( lay==ValueLayer )
return v;
return lift(v, lay, ctx, e.pos);
}
Value eval( Int e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
{
Value v = new IntValue(e.data);
if( lay==RawMacroLayer || lay==MacroLayer )
{
auto ast = new Table;
ast.set("pos", ValueLayer, fromPos(e.pos));
ast.set("is", ValueLayer, new StrValue("int"));
ast.set("data", ValueLayer, v);
return ast;
}
if( lay==ValueLayer )
return v;
return lift(v, lay, ctx, e.pos);
}
Value eval( Var e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
{
if( lay==RawMacroLayer || lay==MacroLayer )
{
if( ctx.has(e.name,MacroLayer) )
return ctx.get(e.name, MacroLayer, e.pos);
auto ast = new Table;
ast.set("pos", ValueLayer, fromPos(e.pos));
ast.set("is", ValueLayer, new StrValue("var"));
ast.set("name", ValueLayer, new StrValue(e.name));
return ast;
}
if( lay==ValueLayer || ctx.has(e.name, lay) )
return ctx.get(e.name, lay, e.pos);
return lift(ctx.get(e.name, ValueLayer, e.pos), lay, ctx, e.pos);
}
Value eval( App e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
{
Value f = eval( e.fun, lay, ctx );
if( lay==RawMacroLayer || lay==MacroLayer )
{
if( auto ff = cast(FunValue)f )
return invokeFunction(ff, e.args, MacroLayer, ctx, e.pos);
Table ast = new Table;
ast.set("pos", ValueLayer, fromPos(e.pos));
ast.set("is", ValueLayer, new StrValue("app"));
ast.set("fun", ValueLayer, f);
Table args = new Table;
foreach_reverse(a; e.args)
args = makeCons(eval(a, lay, ctx), args);
ast.set("args", ValueLayer, args);
return ast;
}
else
{
return invokeFunction(f, e.args, lay, ctx, e.pos);
}
}
Value eval( Fun e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
{
if( lay==RawMacroLayer || lay==MacroLayer )
{
Table t = new Table;
t.set("pos", ValueLayer, fromPos(e.pos));
t.set("is", ValueLayer, new StrValue("fun"));
t.set("funbody", ValueLayer, eval(e.funbody,lay,ctx));
Table params = new Table;
foreach_reverse(p; e.params)
{
Table lays = new Table;
foreach_reverse(l; p.layers)
lays = makeCons(new StrValue(l), lays);
Table kv = new Table;
kv.set("name", ValueLayer, new StrValue(p.name));
kv.set("layers", ValueLayer, lays);
Table cons = new Table;
params = makeCons(kv, params);
}
t.set("params", ValueLayer, params);
return t;
}
else
{
return createNewFunction(e, ctx);
}
}
Value eval( Lay e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
{
if( lay == RawMacroLayer )
{
Value r = eval(e.expr, lay, ctx);
auto ast = new Table; // todo: pos
ast.set("pos", ValueLayer, fromPos(e.pos));
ast.set("is", ValueLayer, new StrValue("lay"));
ast.set("layer", ValueLayer, new StrValue(e.layer));
ast.set("expr", ValueLayer, r);
return ast;
}
else
return eval(e.expr, e.layer, ctx);
}
Value eval( Let e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
{
// todo @macro let
if( lay==RawMacroLayer || lay==MacroLayer )
{
auto ast = new Table; // todo: pos
ast.set("pos", ValueLayer, fromPos(e.pos));
ast.set("is", ValueLayer, new StrValue("let"));
ast.set("name", ValueLayer, new StrValue(e.name));
ast.set("layer", ValueLayer, new StrValue(e.layer));
ast.set("init", ValueLayer, eval(e.init, lay, ctx));
ast.set("expr", ValueLayer, eval(e.expr, lay, ctx));
return ast;
}
else
{
if( !overwriteCtx )
ctx = new Table(ctx, Table.Kind.NotPropagateSet);
Value ri = eval(e.init, lay, ctx);
string theLayer = e.layer.empty ? (lay==RawMacroLayer ? MacroLayer : lay) : e.layer;
ctx.set(e.name, theLayer, ri);
return eval(e.expr, lay, ctx, OverwriteCtx);
}
}
private:
Value invokeFunction(Value _f, AST[] args, Layer lay, Table ctx, LexPosition pos=null)
{
if(auto f = cast(FunValue)_f)
{
Table newCtx = new Table(f.definitionContext(), Table.Kind.NotPropagateSet);
foreach(i,p; f.params())
if( p.layers.empty )
newCtx.set(p.name, (lay==RawMacroLayer ? MacroLayer : lay), eval(args[i], lay, ctx));
else
foreach(argLay; p.layers)
newCtx.set(p.name, argLay, eval(args[i], argLay, ctx));
return f.invoke(pos, lay, newCtx);
}
throw genex!RuntimeException(pos, text("tried to call non-function: ",_f));
}
Value lift(Value v, Layer lay, Table ctx, LexPosition pos=null)
{
// functions are automatically lifterd
if( cast(FunValue) v )
return v;
// similar to invoke Function, but with only one argument bound to ValueLayer
if(auto f = cast(FunValue)ctx.get(lay, SystemLayer, pos))
{
Table newCtx = new Table(f.definitionContext(), Table.Kind.NotPropagateSet);
auto ps = f.params();
if( ps.length != 1 )
throw genex!RuntimeException(pos, "lift function must take exactly one argument at "~ValueLayer~" layer");
if( ps[0].layers.length==0 || ps[0].layers.length==1 && ps[0].layers[0]==ValueLayer )
{
newCtx.set(ps[0].name, ValueLayer, v);
return f.invoke(pos, ValueLayer, newCtx);
}
else
throw genex!RuntimeException(pos, "lift function must take exactly one argument at "~ValueLayer~" layer");
}
throw genex!RuntimeException(pos, "tried to call non-function");
}
Value createNewFunction(Fun e, Table ctx)
{
class UserDefinedFunValue : FunValue
{
Fun ast;
Table defCtx;
override const(Parameter[]) params() { return ast.params; }
override Table definitionContext() { return defCtx; }
this(Fun ast, Table defCtx) { this.ast=ast; this.defCtx=defCtx; }
override string toString() const { return sprintf!"(function:%x:%x)"(cast(void*)ast, cast(void*)defCtx); }
override bool opEquals(Object rhs_) const /// member-by-member equality
{
if( auto rhs = cast(typeof(this))rhs_ )
return this.ast==rhs.ast && this.defCtx==rhs.defCtx;
assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_)));
}
override hash_t toHash() const /// member-by-member hash
{
return typeid(this.ast).getHash(&this.ast) + typeid(this.defCtx).getHash(&this.defCtx);
}
override int opCmp(Object rhs_) /// member-by-member compare
{
if( auto rhs = cast(typeof(this))rhs_ )
{
if(auto i = this.ast.opCmp(rhs.ast))
return i;
return this.defCtx.opCmp(rhs.defCtx);
}
assert(false, sprintf!"Cannot compare %s with %s"(typeid(this), typeid(rhs_)));
}
override Value invoke(LexPosition pos, Layer lay, Table ctx)
{
if( lay == MacroLayer )
return eval(ast.funbody, lay, ctx);
auto macroed = tableToAST(ValueLayer, eval(e.funbody, RawMacroLayer, ctx));
return eval(macroed, lay, ctx);
}
}
return new UserDefinedFunValue(e,ctx);
}
public:
/// TODO: move up
/// TDOO: to other layers?
void addPrimitive(R,T...)(string name, Layer lay, R delegate (T) dg)
{
class NativeFunValue : FunValue
{
Parameter[] params_data;
override string toString() { return sprintf!"(native:%x)"(dg.funcptr); }
override const(Parameter[]) params() { return params_data; }
override Table definitionContext() { return new Table; } // todo: cache
this(){
foreach(i, Ti; T)
params_data ~= new Parameter(text(i), []);
}
override Value invoke(LexPosition pos, Layer lay, Table ctx)
{
if( lay != ValueLayer )
throw genex!RuntimeException(pos, "only "~ValueLayer~" layer can call native function");
T typed_args;
foreach(i, Ti; T) {
typed_args[i] = cast(Ti) ctx.get(text(i), ValueLayer);
if( typed_args[i] is null )
throw genex!RuntimeException(pos, sprintf!"type mismatch on the argument %d"(i+1));
}
try {
return dg(typed_args);
} catch( RuntimeException e ) {
throw e.pos is null ? new RuntimeException(pos, e.msg, e.file, e.line) : e;
}
}
}
theContext.set(name, lay, new NativeFunValue);
}
}
version(unittest) import polemy.runtime;
unittest
{
auto e = new Evaluator;
enrollRuntimeLibrary(e);
auto r = assert_nothrow( e.evalString(`var x = 21; x + x*x;`) );
assert_eq( r, new IntValue(BigInt(21+21*21)) );
assert_eq( e.globalContext.get("x",ValueLayer), new IntValue(BigInt(21)) );
assert_nothrow( e.globalContext.get("x",ValueLayer) );
assert_throw!RuntimeException( e.globalContext.get("y",ValueLayer) );
}
unittest
{
auto e = new Evaluator;
enrollRuntimeLibrary(e);
auto r = assert_nothrow( e.evalString(`var x = 21; var x = x + x*x;`) );
assert_eq( r, new IntValue(BigInt(21+21*21)) );
assert_eq( e.globalContext.get("x",ValueLayer), new IntValue(BigInt(21+21*21)) );
assert_nothrow( e.globalContext.get("x",ValueLayer) );
assert_throw!RuntimeException( e.globalContext.get("y",ValueLayer) );
}
unittest
{
auto e = new Evaluator;
enrollRuntimeLibrary(e);
assert_eq( e.evalString(`let x=1; let y=(let x=2); x`), new IntValue(BigInt(1)) );
assert_eq( e.evalString(`let x=1; let y=(let x=2;fun(){x}); y()`), new IntValue(BigInt(2)) );
}
unittest
{
auto e = new Evaluator;
enrollRuntimeLibrary(e);
assert_eq( e.evalString(`@a x=1; @b x=2; @a(x)`), new IntValue(BigInt(1)) );
assert_eq( e.evalString(`@a x=1; @b x=2; @b(x)`), new IntValue(BigInt(2)) );
assert_eq( e.evalString(`let x=1; let _ = (@a x=2;2); x`), new IntValue(BigInt(1)) );
e = new Evaluator;
assert_throw!Throwable( e.evalString(`let x=1; let _ = (@a x=2;2); @a(x)`) );
}
unittest
{
auto e = new Evaluator;
enrollRuntimeLibrary(e);
assert_eq( e.evalString(`
@@s(x){x};
@s "+" = fun(x, y) {@value(
@s(x) - @s(y)
)};
@s(1 + 2)
`), new IntValue(BigInt(-1)) );
}
unittest
{
auto e = new Evaluator;
enrollRuntimeLibrary(e);
assert_eq( e.evalString(`
@@3(x){x};
def incr(x) { x+1 };
@ 3 incr(x) {@value( if(@ 3(x)+1< 3){@ 3(x)+1}else{0} )};
def fb(n @value @3) { @3(n) };
fb(incr(incr(incr(0))))
`), new IntValue(BigInt(0)) );
}
unittest
{
auto e = new Evaluator;
enrollRuntimeLibrary(e);
assert_nothrow( e.evalString(`
@macro twice(x) { x; x };
def main() { twice(1) };
main()
`) );
}
/*
unittest
{
assert_eq( evalString(`var fac = fun(x){
if(x)
{ x*fac(x-1); }
else
{ 1; };
};
fac(10);`).val, new IntValue(BigInt(10*9*8*5040)));
assert_eq( evalString(`var fib = fun(x){
if(x<2)
{ 1; }
else
{ fib(x-1) + fib(x-2); };
};
fib(5);`).val, new IntValue(BigInt(8)));
}
unittest
{
assert_eq( evalString(`@@t = fun(x){x+1}; @t(123)`).val, new IntValue(BigInt(124)) );
// there was a bug that declaration in the first line of function definition
// cannot be recursive
assert_nothrow( evalString(`def foo() {
def bar(y) { if(y<1) {0} else {bar(0)} };
bar(1)
}; foo()`) );
}
*/