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

/// Raised when something went wrong in runtime

class RuntimeException : Exception
{
	mixin ExceptionWithPosition;
}

/// Runtime values of Polemy

abstract class Value
{
}

class IntValue : Value
{
	BigInt data;

	mixin SimpleClass;
	override string toString() const { return std.bigint.toDecimalString(cast(BigInt)data); }
}

class StrValue : Value
{
	string data;

	mixin SimpleClass;
	override string toString() const { return data; }
}

class FunValue : Value
{
	Value delegate(immutable LexPosition pos, string lay, Value[]) data;

	mixin SimpleConstructor;
	alias data call;
	override string toString() const { return sprintf!"(function:%s:%s)"(data.ptr,data.funcptr); }
}

/// Layer ID

alias string Layer;

/// Context (variable environment)
/// Simlar to prototype chain of ECMAScript etc.
/// But extended with the notion of "Layer"

class Table : Value
{
	enum Kind {PropagateSet, NotPropagateSet};

	this( Table proto=null, Kind k = Kind.PropagateSet )
		{ this.prototype = proto; this.kind = k; }

	void set(string i, Layer lay, Value v, in LexPosition pos=null)
	{
		if( setIfExist(i, lay, v) )
			return;
		data[i][lay] = v;
	}

	Value get(string i, Layer lay, in LexPosition pos=null)
	{
		if( i in data )
			return data[i][lay];
		if( prototype is null )
			throw new RuntimeException(pos, sprintf!"variable %s not found"(i));
		return prototype.get(i, lay);
	}

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", "@v", new IntValue(BigInt(12))) );
	assert_throw!RuntimeException( c013.get("x", "@v") );
	assert_nothrow( c013.set("x", "@v", new IntValue(BigInt(13))) );
	assert_eq( c013.get("x", "@v"), new IntValue(BigInt(13)) );
	assert_eq( c012.get("x", "@v"), new IntValue(BigInt(12)) );
	assert_throw!RuntimeException( c01.get("x", "@v") );

	assert_nothrow( c01.set("y", "@v", new IntValue(BigInt(1))) );
	assert_eq( c013.get("y", "@v"), new IntValue(BigInt(1)) );
	assert_eq( c012.get("y", "@v"), new IntValue(BigInt(1)) );
	assert_eq( c01.get("y", "@v"), new IntValue(BigInt(1)) );

	assert_nothrow( c0.set("z", "@v", new IntValue(BigInt(0))) );
	assert_eq( c013.get("z", "@v"), new IntValue(BigInt(0)) );
	assert_eq( c012.get("z", "@v"), new IntValue(BigInt(0)) );
	assert_eq( c01.get("z", "@v"), new IntValue(BigInt(0)) );
	assert_eq( c0.get("z", "@v"), new IntValue(BigInt(0)) );

	assert_nothrow( c012.set("y", "@v", new IntValue(BigInt(444))) );
	assert_eq( c013.get("y", "@v"), new IntValue(BigInt(444)) );
	assert_eq( c012.get("y", "@v"), new IntValue(BigInt(444)) );
	assert_eq( c01.get("y", "@v"), new IntValue(BigInt(444)) );

	assert_nothrow( c012.set("z", "@v", new IntValue(BigInt(555))) );
	assert_eq( c013.get("z", "@v"), new IntValue(BigInt(0)) );
	assert_eq( c012.get("z", "@v"), new IntValue(BigInt(555)) );
	assert_eq( c01.get("z", "@v"), new IntValue(BigInt(0)) );
	assert_eq( c0.get("z", "@v"), new IntValue(BigInt(0)) );

	// [TODO] define the semantics and test @layers
}
