mirror of
https://github.com/Ratstail91/Mementos.git
synced 2025-11-29 10:34:27 +11:00
Committed everything
This commit is contained in:
@@ -0,0 +1,508 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ink.Runtime
|
||||
{
|
||||
public class NativeFunctionCall : Runtime.Object
|
||||
{
|
||||
public const string Add = "+";
|
||||
public const string Subtract = "-";
|
||||
public const string Divide = "/";
|
||||
public const string Multiply = "*";
|
||||
public const string Mod = "%";
|
||||
public const string Negate = "_"; // distinguish from "-" for subtraction
|
||||
|
||||
public const string Equal = "==";
|
||||
public const string Greater = ">";
|
||||
public const string Less = "<";
|
||||
public const string GreaterThanOrEquals = ">=";
|
||||
public const string LessThanOrEquals = "<=";
|
||||
public const string NotEquals = "!=";
|
||||
public const string Not = "!";
|
||||
|
||||
|
||||
|
||||
public const string And = "&&";
|
||||
public const string Or = "||";
|
||||
|
||||
public const string Min = "MIN";
|
||||
public const string Max = "MAX";
|
||||
|
||||
public const string Pow = "POW";
|
||||
public const string Floor = "FLOOR";
|
||||
public const string Ceiling = "CEILING";
|
||||
public const string Int = "INT";
|
||||
public const string Float = "FLOAT";
|
||||
|
||||
public const string Has = "?";
|
||||
public const string Hasnt = "!?";
|
||||
public const string Intersect = "^";
|
||||
|
||||
public const string ListMin = "LIST_MIN";
|
||||
public const string ListMax = "LIST_MAX";
|
||||
public const string All = "LIST_ALL";
|
||||
public const string Count = "LIST_COUNT";
|
||||
public const string ValueOfList = "LIST_VALUE";
|
||||
public const string Invert = "LIST_INVERT";
|
||||
|
||||
public static NativeFunctionCall CallWithName(string functionName)
|
||||
{
|
||||
return new NativeFunctionCall (functionName);
|
||||
}
|
||||
|
||||
public static bool CallExistsWithName(string functionName)
|
||||
{
|
||||
GenerateNativeFunctionsIfNecessary ();
|
||||
return _nativeFunctions.ContainsKey (functionName);
|
||||
}
|
||||
|
||||
public string name {
|
||||
get {
|
||||
return _name;
|
||||
}
|
||||
protected set {
|
||||
_name = value;
|
||||
if( !_isPrototype )
|
||||
_prototype = _nativeFunctions [_name];
|
||||
}
|
||||
}
|
||||
string _name;
|
||||
|
||||
public int numberOfParameters {
|
||||
get {
|
||||
if (_prototype) {
|
||||
return _prototype.numberOfParameters;
|
||||
} else {
|
||||
return _numberOfParameters;
|
||||
}
|
||||
}
|
||||
protected set {
|
||||
_numberOfParameters = value;
|
||||
}
|
||||
}
|
||||
|
||||
int _numberOfParameters;
|
||||
|
||||
public Runtime.Object Call(List<Runtime.Object> parameters)
|
||||
{
|
||||
if (_prototype) {
|
||||
return _prototype.Call(parameters);
|
||||
}
|
||||
|
||||
if (numberOfParameters != parameters.Count) {
|
||||
throw new System.Exception ("Unexpected number of parameters");
|
||||
}
|
||||
|
||||
bool hasList = false;
|
||||
foreach (var p in parameters) {
|
||||
if (p is Void)
|
||||
throw new StoryException ("Attempting to perform operation on a void value. Did you forget to 'return' a value from a function you called here?");
|
||||
if (p is ListValue)
|
||||
hasList = true;
|
||||
}
|
||||
|
||||
// Binary operations on lists are treated outside of the standard coerscion rules
|
||||
if( parameters.Count == 2 && hasList )
|
||||
return CallBinaryListOperation (parameters);
|
||||
|
||||
var coercedParams = CoerceValuesToSingleType (parameters);
|
||||
ValueType coercedType = coercedParams[0].valueType;
|
||||
|
||||
if (coercedType == ValueType.Int) {
|
||||
return Call<int> (coercedParams);
|
||||
} else if (coercedType == ValueType.Float) {
|
||||
return Call<float> (coercedParams);
|
||||
} else if (coercedType == ValueType.String) {
|
||||
return Call<string> (coercedParams);
|
||||
} else if (coercedType == ValueType.DivertTarget) {
|
||||
return Call<Path> (coercedParams);
|
||||
} else if (coercedType == ValueType.List) {
|
||||
return Call<InkList> (coercedParams);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Value Call<T>(List<Value> parametersOfSingleType)
|
||||
{
|
||||
Value param1 = (Value) parametersOfSingleType [0];
|
||||
ValueType valType = param1.valueType;
|
||||
|
||||
var val1 = (Value<T>)param1;
|
||||
|
||||
int paramCount = parametersOfSingleType.Count;
|
||||
|
||||
if (paramCount == 2 || paramCount == 1) {
|
||||
|
||||
object opForTypeObj = null;
|
||||
if (!_operationFuncs.TryGetValue (valType, out opForTypeObj)) {
|
||||
throw new StoryException ("Cannot perform operation '"+this.name+"' on "+valType);
|
||||
}
|
||||
|
||||
// Binary
|
||||
if (paramCount == 2) {
|
||||
Value param2 = (Value) parametersOfSingleType [1];
|
||||
|
||||
var val2 = (Value<T>)param2;
|
||||
|
||||
var opForType = (BinaryOp<T>)opForTypeObj;
|
||||
|
||||
// Return value unknown until it's evaluated
|
||||
object resultVal = opForType (val1.value, val2.value);
|
||||
|
||||
return Value.Create (resultVal);
|
||||
}
|
||||
|
||||
// Unary
|
||||
else {
|
||||
|
||||
var opForType = (UnaryOp<T>)opForTypeObj;
|
||||
|
||||
var resultVal = opForType (val1.value);
|
||||
|
||||
return Value.Create (resultVal);
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
throw new System.Exception ("Unexpected number of parameters to NativeFunctionCall: " + parametersOfSingleType.Count);
|
||||
}
|
||||
}
|
||||
|
||||
Value CallBinaryListOperation (List<Runtime.Object> parameters)
|
||||
{
|
||||
// List-Int addition/subtraction returns a List (e.g. "alpha" + 1 = "beta")
|
||||
if ((name == "+" || name == "-") && parameters [0] is ListValue && parameters [1] is IntValue)
|
||||
return CallListIncrementOperation (parameters);
|
||||
|
||||
var v1 = parameters [0] as Value;
|
||||
var v2 = parameters [1] as Value;
|
||||
|
||||
// And/or with any other type requires coerscion to bool (int)
|
||||
if ((name == "&&" || name == "||") && (v1.valueType != ValueType.List || v2.valueType != ValueType.List)) {
|
||||
var op = _operationFuncs [ValueType.Int] as BinaryOp<int>;
|
||||
var result = (bool)op (v1.isTruthy ? 1 : 0, v2.isTruthy ? 1 : 0);
|
||||
return new BoolValue (result);
|
||||
}
|
||||
|
||||
// Normal (list • list) operation
|
||||
if (v1.valueType == ValueType.List && v2.valueType == ValueType.List)
|
||||
return Call<InkList> (new List<Value> { v1, v2 });
|
||||
|
||||
throw new StoryException ("Can not call use '" + name + "' operation on " + v1.valueType + " and " + v2.valueType);
|
||||
}
|
||||
|
||||
Value CallListIncrementOperation (List<Runtime.Object> listIntParams)
|
||||
{
|
||||
var listVal = (ListValue)listIntParams [0];
|
||||
var intVal = (IntValue)listIntParams [1];
|
||||
|
||||
|
||||
var resultRawList = new InkList ();
|
||||
|
||||
foreach (var listItemWithValue in listVal.value) {
|
||||
var listItem = listItemWithValue.Key;
|
||||
var listItemValue = listItemWithValue.Value;
|
||||
|
||||
// Find + or - operation
|
||||
var intOp = (BinaryOp<int>)_operationFuncs [ValueType.Int];
|
||||
|
||||
// Return value unknown until it's evaluated
|
||||
int targetInt = (int) intOp (listItemValue, intVal.value);
|
||||
|
||||
// Find this item's origin (linear search should be ok, should be short haha)
|
||||
ListDefinition itemOrigin = null;
|
||||
foreach (var origin in listVal.value.origins) {
|
||||
if (origin.name == listItem.originName) {
|
||||
itemOrigin = origin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (itemOrigin != null) {
|
||||
InkListItem incrementedItem;
|
||||
if (itemOrigin.TryGetItemWithValue (targetInt, out incrementedItem))
|
||||
resultRawList.Add (incrementedItem, targetInt);
|
||||
}
|
||||
}
|
||||
|
||||
return new ListValue (resultRawList);
|
||||
}
|
||||
|
||||
List<Value> CoerceValuesToSingleType(List<Runtime.Object> parametersIn)
|
||||
{
|
||||
ValueType valType = ValueType.Int;
|
||||
|
||||
ListValue specialCaseList = null;
|
||||
|
||||
// Find out what the output type is
|
||||
// "higher level" types infect both so that binary operations
|
||||
// use the same type on both sides. e.g. binary operation of
|
||||
// int and float causes the int to be casted to a float.
|
||||
foreach (var obj in parametersIn) {
|
||||
var val = (Value)obj;
|
||||
if (val.valueType > valType) {
|
||||
valType = val.valueType;
|
||||
}
|
||||
|
||||
if (val.valueType == ValueType.List) {
|
||||
specialCaseList = val as ListValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Coerce to this chosen type
|
||||
var parametersOut = new List<Value> ();
|
||||
|
||||
// Special case: Coercing to Ints to Lists
|
||||
// We have to do it early when we have both parameters
|
||||
// to hand - so that we can make use of the List's origin
|
||||
if (valType == ValueType.List) {
|
||||
|
||||
foreach (Value val in parametersIn) {
|
||||
if (val.valueType == ValueType.List) {
|
||||
parametersOut.Add (val);
|
||||
} else if (val.valueType == ValueType.Int) {
|
||||
int intVal = (int)val.valueObject;
|
||||
var list = specialCaseList.value.originOfMaxItem;
|
||||
|
||||
InkListItem item;
|
||||
if (list.TryGetItemWithValue (intVal, out item)) {
|
||||
var castedValue = new ListValue (item, intVal);
|
||||
parametersOut.Add (castedValue);
|
||||
} else
|
||||
throw new StoryException ("Could not find List item with the value " + intVal + " in " + list.name);
|
||||
} else
|
||||
throw new StoryException ("Cannot mix Lists and " + val.valueType + " values in this operation");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Normal Coercing (with standard casting)
|
||||
else {
|
||||
foreach (Value val in parametersIn) {
|
||||
var castedValue = val.Cast (valType);
|
||||
parametersOut.Add (castedValue);
|
||||
}
|
||||
}
|
||||
|
||||
return parametersOut;
|
||||
}
|
||||
|
||||
public NativeFunctionCall(string name)
|
||||
{
|
||||
GenerateNativeFunctionsIfNecessary ();
|
||||
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
// Require default constructor for serialisation
|
||||
public NativeFunctionCall() {
|
||||
GenerateNativeFunctionsIfNecessary ();
|
||||
}
|
||||
|
||||
// Only called internally to generate prototypes
|
||||
NativeFunctionCall (string name, int numberOfParameters)
|
||||
{
|
||||
_isPrototype = true;
|
||||
this.name = name;
|
||||
this.numberOfParameters = numberOfParameters;
|
||||
}
|
||||
|
||||
// For defining operations that do nothing to the specific type
|
||||
// (but are still supported), such as floor/ceil on int and float
|
||||
// cast on float.
|
||||
static object Identity<T>(T t) {
|
||||
return t;
|
||||
}
|
||||
|
||||
static void GenerateNativeFunctionsIfNecessary()
|
||||
{
|
||||
if (_nativeFunctions == null) {
|
||||
_nativeFunctions = new Dictionary<string, NativeFunctionCall> ();
|
||||
|
||||
// Why no bool operations?
|
||||
// Before evaluation, all bools are coerced to ints in
|
||||
// CoerceValuesToSingleType (see default value for valType at top).
|
||||
// So, no operations are ever directly done in bools themselves.
|
||||
// This also means that 1 == true works, since true is always converted
|
||||
// to 1 first.
|
||||
// However, many operations return a "native" bool (equals, etc).
|
||||
|
||||
// Int operations
|
||||
AddIntBinaryOp(Add, (x, y) => x + y);
|
||||
AddIntBinaryOp(Subtract, (x, y) => x - y);
|
||||
AddIntBinaryOp(Multiply, (x, y) => x * y);
|
||||
AddIntBinaryOp(Divide, (x, y) => x / y);
|
||||
AddIntBinaryOp(Mod, (x, y) => x % y);
|
||||
AddIntUnaryOp (Negate, x => -x);
|
||||
|
||||
AddIntBinaryOp(Equal, (x, y) => x == y);
|
||||
AddIntBinaryOp(Greater, (x, y) => x > y);
|
||||
AddIntBinaryOp(Less, (x, y) => x < y);
|
||||
AddIntBinaryOp(GreaterThanOrEquals, (x, y) => x >= y);
|
||||
AddIntBinaryOp(LessThanOrEquals, (x, y) => x <= y);
|
||||
AddIntBinaryOp(NotEquals, (x, y) => x != y);
|
||||
AddIntUnaryOp (Not, x => x == 0);
|
||||
|
||||
AddIntBinaryOp(And, (x, y) => x != 0 && y != 0);
|
||||
AddIntBinaryOp(Or, (x, y) => x != 0 || y != 0);
|
||||
|
||||
AddIntBinaryOp(Max, (x, y) => Math.Max(x, y));
|
||||
AddIntBinaryOp(Min, (x, y) => Math.Min(x, y));
|
||||
|
||||
// Have to cast to float since you could do POW(2, -1)
|
||||
AddIntBinaryOp (Pow, (x, y) => (float) Math.Pow(x, y));
|
||||
AddIntUnaryOp(Floor, Identity);
|
||||
AddIntUnaryOp(Ceiling, Identity);
|
||||
AddIntUnaryOp(Int, Identity);
|
||||
AddIntUnaryOp (Float, x => (float)x);
|
||||
|
||||
// Float operations
|
||||
AddFloatBinaryOp(Add, (x, y) => x + y);
|
||||
AddFloatBinaryOp(Subtract, (x, y) => x - y);
|
||||
AddFloatBinaryOp(Multiply, (x, y) => x * y);
|
||||
AddFloatBinaryOp(Divide, (x, y) => x / y);
|
||||
AddFloatBinaryOp(Mod, (x, y) => x % y); // TODO: Is this the operation we want for floats?
|
||||
AddFloatUnaryOp (Negate, x => -x);
|
||||
|
||||
AddFloatBinaryOp(Equal, (x, y) => x == y);
|
||||
AddFloatBinaryOp(Greater, (x, y) => x > y);
|
||||
AddFloatBinaryOp(Less, (x, y) => x < y);
|
||||
AddFloatBinaryOp(GreaterThanOrEquals, (x, y) => x >= y);
|
||||
AddFloatBinaryOp(LessThanOrEquals, (x, y) => x <= y);
|
||||
AddFloatBinaryOp(NotEquals, (x, y) => x != y);
|
||||
AddFloatUnaryOp (Not, x => (x == 0.0f));
|
||||
|
||||
AddFloatBinaryOp(And, (x, y) => x != 0.0f && y != 0.0f);
|
||||
AddFloatBinaryOp(Or, (x, y) => x != 0.0f || y != 0.0f);
|
||||
|
||||
AddFloatBinaryOp(Max, (x, y) => Math.Max(x, y));
|
||||
AddFloatBinaryOp(Min, (x, y) => Math.Min(x, y));
|
||||
|
||||
AddFloatBinaryOp (Pow, (x, y) => (float)Math.Pow(x, y));
|
||||
AddFloatUnaryOp(Floor, x => (float)Math.Floor(x));
|
||||
AddFloatUnaryOp(Ceiling, x => (float)Math.Ceiling(x));
|
||||
AddFloatUnaryOp(Int, x => (int)x);
|
||||
AddFloatUnaryOp(Float, Identity);
|
||||
|
||||
// String operations
|
||||
AddStringBinaryOp(Add, (x, y) => x + y); // concat
|
||||
AddStringBinaryOp(Equal, (x, y) => x.Equals(y));
|
||||
AddStringBinaryOp (NotEquals, (x, y) => !x.Equals (y));
|
||||
AddStringBinaryOp (Has, (x, y) => x.Contains(y));
|
||||
AddStringBinaryOp (Hasnt, (x, y) => !x.Contains(y));
|
||||
|
||||
// List operations
|
||||
AddListBinaryOp (Add, (x, y) => x.Union (y));
|
||||
AddListBinaryOp (Subtract, (x, y) => x.Without(y));
|
||||
AddListBinaryOp (Has, (x, y) => x.Contains (y));
|
||||
AddListBinaryOp (Hasnt, (x, y) => !x.Contains (y));
|
||||
AddListBinaryOp (Intersect, (x, y) => x.Intersect (y));
|
||||
|
||||
AddListBinaryOp (Equal, (x, y) => x.Equals(y));
|
||||
AddListBinaryOp (Greater, (x, y) => x.GreaterThan(y));
|
||||
AddListBinaryOp (Less, (x, y) => x.LessThan(y));
|
||||
AddListBinaryOp (GreaterThanOrEquals, (x, y) => x.GreaterThanOrEquals(y));
|
||||
AddListBinaryOp (LessThanOrEquals, (x, y) => x.LessThanOrEquals(y));
|
||||
AddListBinaryOp (NotEquals, (x, y) => !x.Equals(y));
|
||||
|
||||
AddListBinaryOp (And, (x, y) => x.Count > 0 && y.Count > 0);
|
||||
AddListBinaryOp (Or, (x, y) => x.Count > 0 || y.Count > 0);
|
||||
|
||||
AddListUnaryOp (Not, x => x.Count == 0 ? (int)1 : (int)0);
|
||||
|
||||
// Placeholders to ensure that these special case functions can exist,
|
||||
// since these function is never actually run, and is special cased in Call
|
||||
AddListUnaryOp (Invert, x => x.inverse);
|
||||
AddListUnaryOp (All, x => x.all);
|
||||
AddListUnaryOp (ListMin, (x) => x.MinAsList());
|
||||
AddListUnaryOp (ListMax, (x) => x.MaxAsList());
|
||||
AddListUnaryOp (Count, (x) => x.Count);
|
||||
AddListUnaryOp (ValueOfList, (x) => x.maxItem.Value);
|
||||
|
||||
// Special case: The only operations you can do on divert target values
|
||||
BinaryOp<Path> divertTargetsEqual = (Path d1, Path d2) => {
|
||||
return d1.Equals (d2);
|
||||
};
|
||||
BinaryOp<Path> divertTargetsNotEqual = (Path d1, Path d2) => {
|
||||
return !d1.Equals (d2);
|
||||
};
|
||||
AddOpToNativeFunc (Equal, 2, ValueType.DivertTarget, divertTargetsEqual);
|
||||
AddOpToNativeFunc (NotEquals, 2, ValueType.DivertTarget, divertTargetsNotEqual);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void AddOpFuncForType(ValueType valType, object op)
|
||||
{
|
||||
if (_operationFuncs == null) {
|
||||
_operationFuncs = new Dictionary<ValueType, object> ();
|
||||
}
|
||||
|
||||
_operationFuncs [valType] = op;
|
||||
}
|
||||
|
||||
static void AddOpToNativeFunc(string name, int args, ValueType valType, object op)
|
||||
{
|
||||
NativeFunctionCall nativeFunc = null;
|
||||
if (!_nativeFunctions.TryGetValue (name, out nativeFunc)) {
|
||||
nativeFunc = new NativeFunctionCall (name, args);
|
||||
_nativeFunctions [name] = nativeFunc;
|
||||
}
|
||||
|
||||
nativeFunc.AddOpFuncForType (valType, op);
|
||||
}
|
||||
|
||||
static void AddIntBinaryOp(string name, BinaryOp<int> op)
|
||||
{
|
||||
AddOpToNativeFunc (name, 2, ValueType.Int, op);
|
||||
}
|
||||
|
||||
static void AddIntUnaryOp(string name, UnaryOp<int> op)
|
||||
{
|
||||
AddOpToNativeFunc (name, 1, ValueType.Int, op);
|
||||
}
|
||||
|
||||
static void AddFloatBinaryOp(string name, BinaryOp<float> op)
|
||||
{
|
||||
AddOpToNativeFunc (name, 2, ValueType.Float, op);
|
||||
}
|
||||
|
||||
static void AddStringBinaryOp(string name, BinaryOp<string> op)
|
||||
{
|
||||
AddOpToNativeFunc (name, 2, ValueType.String, op);
|
||||
}
|
||||
|
||||
static void AddListBinaryOp (string name, BinaryOp<InkList> op)
|
||||
{
|
||||
AddOpToNativeFunc (name, 2, ValueType.List, op);
|
||||
}
|
||||
|
||||
static void AddListUnaryOp (string name, UnaryOp<InkList> op)
|
||||
{
|
||||
AddOpToNativeFunc (name, 1, ValueType.List, op);
|
||||
}
|
||||
|
||||
static void AddFloatUnaryOp(string name, UnaryOp<float> op)
|
||||
{
|
||||
AddOpToNativeFunc (name, 1, ValueType.Float, op);
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return "Native '" + name + "'";
|
||||
}
|
||||
|
||||
delegate object BinaryOp<T>(T left, T right);
|
||||
delegate object UnaryOp<T>(T val);
|
||||
|
||||
NativeFunctionCall _prototype;
|
||||
bool _isPrototype;
|
||||
|
||||
// Operations for each data type, for a single operation (e.g. "+")
|
||||
Dictionary<ValueType, object> _operationFuncs;
|
||||
|
||||
static Dictionary<string, NativeFunctionCall> _nativeFunctions;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user