/**
* Authors: k.inaba
* License: NYSL 0.9982 http://www.kmonos.net/nysl/
*
* Runtime data structures for Polemy programming language.
*/
module polemy.value;
import polemy._common;
import polemy.failure;
import polemy.ast;
import polemy.layer;
import std.string;
/// Runtime values of Polemy
abstract class Value
{
override bool opEquals(Object rhs) { return 0==opCmp(rhs); }
}
///
class IntValue : Value
{
BigInt data;
this(int n) { this.data = n; }
this(long n) { this.data = n; }
this(BigInt n) { this.data = n; }
this(string n) { this.data = BigInt(n); }
override string toString() const { return toDecimalString(cast(BigInt)data); }
override int opCmp(Object rhs) {
if(auto r = cast(IntValue)rhs) return data.opCmp(r.data);
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;
}
///
class StrValue : Value
{
string data;
mixin SimpleConstructor;
override string toString() const { return data; }
override int opCmp(Object rhs) {
if(auto r = cast(StrValue)rhs) return typeid(string).compare(&data, &r.data);
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;
}
///
class UndefinedValue : Value
{
mixin SimpleConstructor;
override string toString() const { return "<undefined>"; }
override int opCmp(Object rhs) {
if(auto r = cast(StrValue)rhs) return 0;
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;
}
///
abstract class FunValue : Value
{
const(Parameter[]) params();
Table definitionContext();
Value invoke(Layer lay, Table ctx, LexPosition pos);
}
/// Context (variable environment)
/// Simlar to prototype chain of ECMAScript etc.
/// But extended with the notion of "Layer"
class Table : Value
{
enum Kind {PropagateSet, NotPropagateSet};
bool kill = false; // to refactor
this( Table proto=null, Kind k = Kind.PropagateSet )
{ this.prototype = proto; this.kind = k; }
void set(string i, Layer lay, Value v, LexPosition pos=null)
{
if( setIfExist(i, lay, v) )
return;
data[i][lay] = v;
}
bool has(string i, Layer lay) const
{
if( i in data ) {
if( lay !in data[i] )
return false;
if(kill)
return false;
return true;
}
if( prototype is null )
return false;
return prototype.has(i, lay);
}
Value get(string i, Layer lay, LexPosition pos=null)
{
if( i in data ) {
// [TODO] consider forwarding to proto also in this case
if( lay !in data[i] )
throw genex!RuntimeException(pos, sprintf!"'%s' is not set in layer %s"(i,lay));
if(kill)
throw genex!RuntimeException(pos, sprintf!"'%s' is killed in macro"(i));
return data[i][lay];
}
if( prototype is null )
throw new RuntimeException(pos, sprintf!"'%s' not found"(i));
return prototype.get(i, lay, pos);
}
T access(T,S...)( Layer lay, string path, S rest )
{
static if( rest.length == 0 )
{
if( this.has(path, lay) )
return cast(T) this.get(path, lay);
}
else
{
if(auto next = this.access!Table(lay,path))
return next.access!T(lay,rest);
}
return null;
}
string toStringWithoutParen() const
{
string result;
bool first = true;
foreach(k, l2d; data)
foreach(l,d; l2d)
{
if(first) first=false; else result~=", ";
result ~= k;
if( l.empty )
result ~= "(emptylayer)";
else if( l != ValueLayer )
result ~= l;
result ~= ":";
result ~= text(cast(Value)d);
}
if( prototype !is null )
{
result ~= " / ";
result ~= prototype.toStringWithoutParen();
}
return result;
}
string toString() const
{
return "{" ~ toStringWithoutParen() ~ "}";
}
private:
Table prototype;
Kind kind;
Value[Layer][string] data;
bool setIfExist(string i, Layer lay, Value v)
{
if( i in data )
{
data[i][lay] = v;
return true;
}
if( kind==Kind.PropagateSet && prototype !is null )
return prototype.setIfExist(i, lay, v);
return false;
}
}
unittest
{
Table c0 = new Table;
Table c01 = new Table(c0, Table.Kind.NotPropagateSet);
Table c012 = new Table(c01, Table.Kind.PropagateSet);
Table c013 = new Table(c01, Table.Kind.PropagateSet);
assert_nothrow( c012.set("x", ValueLayer, new IntValue(BigInt(12))) );
assert_throw!RuntimeException( c013.get("x", ValueLayer) );
assert_nothrow( c013.set("x", ValueLayer, new IntValue(BigInt(13))) );
assert_eq( c013.get("x", ValueLayer), new IntValue(BigInt(13)) );
assert_eq( c012.get("x", ValueLayer), new IntValue(BigInt(12)) );
assert_throw!RuntimeException( c01.get("x", ValueLayer) );
assert_nothrow( c01.set("y", ValueLayer, new IntValue(BigInt(1))) );
assert_eq( c013.get("y", ValueLayer), new IntValue(BigInt(1)) );
assert_eq( c012.get("y", ValueLayer), new IntValue(BigInt(1)) );
assert_eq( c01.get("y", ValueLayer), new IntValue(BigInt(1)) );
assert_nothrow( c0.set("z", ValueLayer, new IntValue(BigInt(0))) );
assert_eq( c013.get("z", ValueLayer), new IntValue(BigInt(0)) );
assert_eq( c012.get("z", ValueLayer), new IntValue(BigInt(0)) );
assert_eq( c01.get("z", ValueLayer), new IntValue(BigInt(0)) );
assert_eq( c0.get("z", ValueLayer), new IntValue(BigInt(0)) );
assert_nothrow( c012.set("y", ValueLayer, new IntValue(BigInt(444))) );
assert_eq( c013.get("y", ValueLayer), new IntValue(BigInt(444)) );
assert_eq( c012.get("y", ValueLayer), new IntValue(BigInt(444)) );
assert_eq( c01.get("y", ValueLayer), new IntValue(BigInt(444)) );
assert_nothrow( c012.set("z", ValueLayer, new IntValue(BigInt(555))) );
assert_eq( c013.get("z", ValueLayer), new IntValue(BigInt(0)) );
assert_eq( c012.get("z", ValueLayer), new IntValue(BigInt(555)) );
assert_eq( c01.get("z", ValueLayer), new IntValue(BigInt(0)) );
assert_eq( c0.get("z", ValueLayer), new IntValue(BigInt(0)) );
// [TODO] define the semantics and test @layers
}
immutable(LexPosition) extractPos( Table t )
{
Layer theLayer = ValueLayer;
if(auto tt = t.access!Table(theLayer, "pos"))
{
auto fn = tt.access!StrValue(theLayer, "filename");
auto ln = tt.access!IntValue(theLayer, "lineno");
auto cl = tt.access!IntValue(theLayer, "column");
if(fn !is null && ln !is null && cl !is null)
return new immutable(LexPosition)(fn.data,cast(int)ln.data.toInt,cast(int)cl.data.toInt);
}
return null;
}
Value[] tableAsConsList( Layer theLayer, Table t )
{
Value[] result;
while(t)
if(auto v = t.access!Value(theLayer, "car"))
{
result ~= v;
t = t.access!Table(theLayer, "cdr");
}
else
break;
return result;
}
AST[] tableToASTList( Layer theLayer, Table t )
{
AST[] result;
foreach(v; tableAsConsList(theLayer, t))
if(auto t = cast(Table)v)
result ~= tableToAST(theLayer,t);
else
throw genex!RuntimeException(cast(LexPosition)null, "Invalid AST (non-table in cons-list)");
return result;
}
AST tableToAST( Layer theLayer, Value vvvv )
{
Table t = cast(Table)vvvv;
if( t is null )
throw genex!RuntimeException(cast(LexPosition)null, "Invalid AST (not a table)");
auto nodeType = t.access!StrValue(theLayer, "is");
if( nodeType is null )
throw genex!RuntimeException(cast(LexPosition)null, "Invalid AST {is:(not string)}");
auto pos = extractPos(t);
switch(nodeType.data)
{
case "int":
if(auto v = t.access!IntValue(theLayer, "data"))
return new Int(pos, v.data);
throw genex!RuntimeException(cast(LexPosition)null, `Invalid AST {is:"int", data:(not int)}`);
case "str":
if(auto v = t.access!StrValue(theLayer, "data"))
return new Str(pos, v.data);
throw genex!RuntimeException(cast(LexPosition)null, `Invalid AST {is:"str", data:(not string)}`);
case "var":
if(auto v = t.access!StrValue(theLayer, "name"))
return new Var(pos, v.data);
throw genex!RuntimeException(cast(LexPosition)null, `Invalid AST {is:"var", name:(not string)}`);
case "lay":
if(auto v = t.access!StrValue(theLayer, "layer"))
if(auto e = t.access!Table(theLayer, "expr"))
return new Lay(pos, v.data, tableToAST(theLayer,e));
else
throw genex!RuntimeException(cast(LexPosition)null, `Invalid AST {is:"lay", expr:(not table)}`);
throw genex!RuntimeException(cast(LexPosition)null, `Invalid AST {is:"lay", layer:(not string)}`);
case "let":
if(auto n = t.access!StrValue(theLayer, "name"))
if(auto e = t.access!Table(theLayer, "init"))
if(auto b = t.access!Table(theLayer, "expr"))
{
string nn = n.data;
auto ee = tableToAST(theLayer, e);
auto bb = tableToAST(theLayer, b);
Layer lay="";
if(auto l = t.access!StrValue(theLayer, "layer"))
lay = l.data;
return new Let(pos, nn, lay, ee, bb);
}
throw genex!RuntimeException(cast(LexPosition)null, `Invalid AST {is:"let", name:"???", init:"???", expr:"???"}`);
case "app":
if(auto f = t.access!Table(theLayer, "fun"))
if(auto a = t.access!Table(theLayer, "args"))
return new App(pos, tableToAST(theLayer,f), tableToASTList(theLayer,a));
throw genex!RuntimeException(cast(LexPosition)null, `Invalid AST {is:"app", fun:???, args:???}`);
case "fun":
if(auto p = t.access!Table(theLayer, "params"))
if(auto b = t.access!Table(theLayer, "funbody"))
{
Parameter[] ps;
foreach(v; tableAsConsList(theLayer, p))
{
if(auto tt = cast(Table)v)
if(auto ss = tt.access!StrValue(theLayer, "name"))
if(auto ll = tt.access!Table(theLayer, "layers"))
{
Layer[] ls;
foreach(lll; tableAsConsList(theLayer, ll))
if(auto l = cast(StrValue)lll)
ls ~= l.data;
else
throw genex!RuntimeException(cast(LexPosition)null, sprintf!`Invalid AST {bad fun params %s}`(lll));
ps ~= new Parameter(ss.data, ls);
continue;
}
else
{
Layer[] emp;
ps ~= new Parameter(ss.data, emp);
continue;
}
throw genex!RuntimeException(cast(LexPosition)null, sprintf!`Invalid AST {bad fun params %s}`(v));
}
auto bb = tableToAST(theLayer, b);
return new Fun(pos,ps,bb);
}
throw genex!RuntimeException(cast(LexPosition)null, `Invalid AST {is:"fun", param:???, body:???}`);
default:
throw genex!RuntimeException(cast(LexPosition)null, sprintf!`Invalid AST {is: "%s"} unknown`(nodeType.data));
}
}
Table makeCons(Value a, Value d)
{
Table t = new Table;
t.set("car", ValueLayer, a);
t.set("cdr", ValueLayer, d);
return t;
}
Table fromPos(LexPosition pos)
{
Table t = new Table;
if( pos !is null ) {
t.set("filename", ValueLayer, new StrValue(pos.filename));
t.set("lineno", ValueLayer, new IntValue(pos.lineno));
t.set("column", ValueLayer, new IntValue(pos.column));
} else {
t.set("filename", ValueLayer, new StrValue("nullpos"));
t.set("lineno", ValueLayer, new IntValue(0));
t.set("column", ValueLayer, new IntValue(0));
}
return t;
}
/// Convert AST to Table so that it can be used in Polemy
/// TODO: generalize to DValue2PolemyValue
Value ast2table(T)(T e, Value delegate(AST) rec)
{
assert( typeid(e) == typeid(T) );
static if(is(T==BigInt) || is(T==long) || is(T==int))
return new IntValue(e);
else
static if(is(T==string))
return new StrValue(e);
else
static if(is(T S : S[]))
{
Table lst = new Table;
foreach_reverse(a; e)
static if(is(S : AST))
lst = makeCons(rec(a), lst);
else
lst = makeCons(ast2table(a,rec), lst);
return lst;
}
else
static if(is(T : AST))
{
auto t = new Table;
t.set("pos", ValueLayer, fromPos(e.pos));
t.set("is" , ValueLayer, new StrValue(typeid(e).name.split(".")[$-1].tolower()));
foreach(i,m; e.tupleof)
static if(is(typeof(m) : AST))
t.set(e.tupleof[i].stringof[2..$], ValueLayer, rec(m));
else
t.set(e.tupleof[i].stringof[2..$], ValueLayer, ast2table(m,rec));
return t;
}
else
static if(is(T == class))
{
auto t = new Table;
foreach(i,m; e.tupleof)
static if(is(typeof(m) : AST))
t.set(e.tupleof[i].stringof[2..$], ValueLayer, rec(m));
else
t.set(e.tupleof[i].stringof[2..$], ValueLayer, ast2table(m,rec));
return t;
}
else
static assert(false, "unknown type <"~T.stringof~"> during AST encoding");
}