Artifact Content
Not logged in

Artifact 5a9949a1eb8839b1af6cdc22e322980bdbadc182


/**
 * 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;

/// Objects for maitaining global environment and evaluation of expression on it
class Evaluator
{
public:
	/// Initialize evaluator with empty context
	this() { theContext = new Table; }

	/// Evaluate the AST
	Value evalAST(AST e)
	{
		return eval(e, ValueLayer, theContext, OverwriteCtx);
	}

	/// Evaluate the string
	Value evalString(S,T...)(S str, T fn_ln_cn)
	{
		return evalAST(parseString(str,fn_ln_cn));
	}

	/// Evaluate the file
	Value evalFile(S,T...)(S filename, T ln_cn)
	{
		return evalAST(parseFile(filename,ln_cn));
	}

	/// Get the global context
	Table globalContext()
	{
		return theContext;
	}

private:
	Table theContext;

	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"));
	}

	Value eval( Str e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
	{
		if( isMacroishLayer(lay) )
			return ast2table(e, (AST e){return eval(e,lay,ctx);});
		if( lay==ValueLayer )
			return new StrValue(e.data);
		return lift(new StrValue(e.data), lay, ctx, e.pos);
	}

	Value eval( Int e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
	{
		if( isMacroishLayer(lay) )
			return ast2table(e, (AST e){return eval(e,lay,ctx);});
		if( lay==ValueLayer )
			return new IntValue(e.data);
		return lift(new IntValue(e.data), lay, ctx, e.pos);
	}

	Value eval( Var e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
	{
		if( isMacroishLayer(lay) )
			if( ctx.has(e.name,MacroLayer) )
				return ctx.get(e.name, MacroLayer, e.pos);
			else
				return ast2table(e, (AST e){return eval(e,lay,ctx);});
		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( isMacroishLayer(lay) )
			if( auto ff = cast(FunValue)f )
				return invokeFunction(ff, e.args, MacroLayer, ctx, e.pos);
			else
				return ast2table(e, (AST e){return eval(e,lay,ctx);});
		return invokeFunction(f, e.args, lay, ctx, e.pos);
	}
	
	Value eval( Fun e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
	{
		if( isMacroishLayer(lay) )
			return ast2table(e, (AST e){return eval(e,lay,ctx);});
		else
			return createNewFunction(e, ctx);
	}
	
	Value eval( Lay e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
	{
		if( isNoLayerChangeLayer(lay) )
			return ast2table(e, (AST e){return eval(e,lay,ctx);});
		else
			return eval(e.expr, e.layer, ctx);
	}
	
	Value eval( Let e, Layer lay, Table ctx, bool overwriteCtx=CascadeCtx )
	{
		// todo @macro let
		if( isMacroishLayer(lay) )
			return ast2table(e, (AST e){return eval(e,lay,ctx);});
		else
		{
			if( !overwriteCtx )
				ctx = new Table(ctx, Table.Kind.NotPropagateSet);
			Value ri = eval(e.init, lay, ctx);
			string theLayer = e.layer.empty ? lay : e.layer; // neutral 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(lay==RawMacroLayer ? MacroLayer : lay, newCtx, pos);
		}
		throw genex!RuntimeException(pos, text("tried to call non-function: ",_f));
	}

	Value lift(Value v, Layer lay, Table ctx, LexPosition pos=null)
	{
		assert( !isMacroishLayer(lay), "lift to the @macro layer should not happen" );

		// 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(ValueLayer, newCtx, pos);
			}
			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(Layer lay, Table ctx, LexPosition pos)
			{
				if( lay == MacroLayer )
					return eval(ast.funbody, lay, ctx);
				if( afterMacroAST is null )
					afterMacroAST = tableToAST(ValueLayer, eval(e.funbody, RawMacroLayer, ctx));
				return eval(afterMacroAST, lay, ctx);
			}

			AST afterMacroAST;
		}
		return new UserDefinedFunValue(e,ctx);
	}

public:
	/// Add primitive function to the global context
	void addPrimitive(R,T...)(string name, Layer defLay, R delegate (T) dg)
	{
		class NativeFunValue : FunValue
		{
			override const(Parameter[]) params() { return params_data; }
			override Table definitionContext()   { return theContext; }

			override string toString() { return sprintf!"(native:%x)"(dg.funcptr); }
			override int opCmp(Object rhs) {
				if(auto r = cast(NativeFunValue)rhs) return typeid(typeof(dg)).compare(&dg,&r.dg);
				if(auto r = cast(Value)rhs)          return typeid(this).opCmp(typeid(r));
				throw genex!RuntimeException(LexPosition.dummy, "comparison with value and somithing other");
			}
			mixin SimpleToHash;

			R delegate(T) dg;
			Parameter[] params_data;

			this(R delegate(T) dg)
			{
				this.dg = dg;
				foreach(i, Ti; T)
					params_data ~= new Parameter(text(i), []);
			}

			override Value invoke(Layer lay, Table ctx, LexPosition pos)
			{
				if( lay != defLay )
					throw genex!RuntimeException(pos, text("only ", defLay, " layer can call native function: ", name));
				T typed_args;
				foreach(i, Ti; T) {
					typed_args[i] = cast(Ti) ctx.get(text(i), ValueLayer, pos);
					if( typed_args[i] is null )
						throw genex!RuntimeException(pos, sprintf!"type mismatch on the argument %d of native function: %s"(i+1,name));
				}
				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, defLay, new NativeFunValue(dg));
	}
}

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()`) );
}
*/