Newer
Older
TheVengeance-Project-IADE-Unity2D / Assets / Ink / InkLibs / InkRuntime / NativeFunctionCall.cs
  1. using System;
  2. using System.Collections.Generic;
  3. namespace Ink.Runtime
  4. {
  5. public class NativeFunctionCall : Runtime.Object
  6. {
  7. public const string Add = "+";
  8. public const string Subtract = "-";
  9. public const string Divide = "/";
  10. public const string Multiply = "*";
  11. public const string Mod = "%";
  12. public const string Negate = "_"; // distinguish from "-" for subtraction
  13. public const string Equal = "==";
  14. public const string Greater = ">";
  15. public const string Less = "<";
  16. public const string GreaterThanOrEquals = ">=";
  17. public const string LessThanOrEquals = "<=";
  18. public const string NotEquals = "!=";
  19. public const string Not = "!";
  20. public const string And = "&&";
  21. public const string Or = "||";
  22. public const string Min = "MIN";
  23. public const string Max = "MAX";
  24. public const string Pow = "POW";
  25. public const string Floor = "FLOOR";
  26. public const string Ceiling = "CEILING";
  27. public const string Int = "INT";
  28. public const string Float = "FLOAT";
  29. public const string Has = "?";
  30. public const string Hasnt = "!?";
  31. public const string Intersect = "^";
  32. public const string ListMin = "LIST_MIN";
  33. public const string ListMax = "LIST_MAX";
  34. public const string All = "LIST_ALL";
  35. public const string Count = "LIST_COUNT";
  36. public const string ValueOfList = "LIST_VALUE";
  37. public const string Invert = "LIST_INVERT";
  38. public static NativeFunctionCall CallWithName(string functionName)
  39. {
  40. return new NativeFunctionCall (functionName);
  41. }
  42. public static bool CallExistsWithName(string functionName)
  43. {
  44. GenerateNativeFunctionsIfNecessary ();
  45. return _nativeFunctions.ContainsKey (functionName);
  46. }
  47. public string name {
  48. get {
  49. return _name;
  50. }
  51. protected set {
  52. _name = value;
  53. if( !_isPrototype )
  54. _prototype = _nativeFunctions [_name];
  55. }
  56. }
  57. string _name;
  58. public int numberOfParameters {
  59. get {
  60. if (_prototype) {
  61. return _prototype.numberOfParameters;
  62. } else {
  63. return _numberOfParameters;
  64. }
  65. }
  66. protected set {
  67. _numberOfParameters = value;
  68. }
  69. }
  70. int _numberOfParameters;
  71. public Runtime.Object Call(List<Runtime.Object> parameters)
  72. {
  73. if (_prototype) {
  74. return _prototype.Call(parameters);
  75. }
  76. if (numberOfParameters != parameters.Count) {
  77. throw new System.Exception ("Unexpected number of parameters");
  78. }
  79. bool hasList = false;
  80. foreach (var p in parameters) {
  81. if (p is Void)
  82. throw new StoryException ("Attempting to perform operation on a void value. Did you forget to 'return' a value from a function you called here?");
  83. if (p is ListValue)
  84. hasList = true;
  85. }
  86. // Binary operations on lists are treated outside of the standard coerscion rules
  87. if( parameters.Count == 2 && hasList )
  88. return CallBinaryListOperation (parameters);
  89. var coercedParams = CoerceValuesToSingleType (parameters);
  90. ValueType coercedType = coercedParams[0].valueType;
  91. if (coercedType == ValueType.Int) {
  92. return Call<int> (coercedParams);
  93. } else if (coercedType == ValueType.Float) {
  94. return Call<float> (coercedParams);
  95. } else if (coercedType == ValueType.String) {
  96. return Call<string> (coercedParams);
  97. } else if (coercedType == ValueType.DivertTarget) {
  98. return Call<Path> (coercedParams);
  99. } else if (coercedType == ValueType.List) {
  100. return Call<InkList> (coercedParams);
  101. }
  102. return null;
  103. }
  104. Value Call<T>(List<Value> parametersOfSingleType)
  105. {
  106. Value param1 = (Value) parametersOfSingleType [0];
  107. ValueType valType = param1.valueType;
  108. var val1 = (Value<T>)param1;
  109. int paramCount = parametersOfSingleType.Count;
  110. if (paramCount == 2 || paramCount == 1) {
  111. object opForTypeObj = null;
  112. if (!_operationFuncs.TryGetValue (valType, out opForTypeObj)) {
  113. throw new StoryException ("Cannot perform operation '"+this.name+"' on "+valType);
  114. }
  115. // Binary
  116. if (paramCount == 2) {
  117. Value param2 = (Value) parametersOfSingleType [1];
  118. var val2 = (Value<T>)param2;
  119. var opForType = (BinaryOp<T>)opForTypeObj;
  120. // Return value unknown until it's evaluated
  121. object resultVal = opForType (val1.value, val2.value);
  122. return Value.Create (resultVal);
  123. }
  124. // Unary
  125. else {
  126. var opForType = (UnaryOp<T>)opForTypeObj;
  127. var resultVal = opForType (val1.value);
  128. return Value.Create (resultVal);
  129. }
  130. }
  131. else {
  132. throw new System.Exception ("Unexpected number of parameters to NativeFunctionCall: " + parametersOfSingleType.Count);
  133. }
  134. }
  135. Value CallBinaryListOperation (List<Runtime.Object> parameters)
  136. {
  137. // List-Int addition/subtraction returns a List (e.g. "alpha" + 1 = "beta")
  138. if ((name == "+" || name == "-") && parameters [0] is ListValue && parameters [1] is IntValue)
  139. return CallListIncrementOperation (parameters);
  140. var v1 = parameters [0] as Value;
  141. var v2 = parameters [1] as Value;
  142. // And/or with any other type requires coerscion to bool (int)
  143. if ((name == "&&" || name == "||") && (v1.valueType != ValueType.List || v2.valueType != ValueType.List)) {
  144. var op = _operationFuncs [ValueType.Int] as BinaryOp<int>;
  145. var result = (bool)op (v1.isTruthy ? 1 : 0, v2.isTruthy ? 1 : 0);
  146. return new BoolValue (result);
  147. }
  148. // Normal (list • list) operation
  149. if (v1.valueType == ValueType.List && v2.valueType == ValueType.List)
  150. return Call<InkList> (new List<Value> { v1, v2 });
  151. throw new StoryException ("Can not call use '" + name + "' operation on " + v1.valueType + " and " + v2.valueType);
  152. }
  153. Value CallListIncrementOperation (List<Runtime.Object> listIntParams)
  154. {
  155. var listVal = (ListValue)listIntParams [0];
  156. var intVal = (IntValue)listIntParams [1];
  157. var resultRawList = new InkList ();
  158. foreach (var listItemWithValue in listVal.value) {
  159. var listItem = listItemWithValue.Key;
  160. var listItemValue = listItemWithValue.Value;
  161. // Find + or - operation
  162. var intOp = (BinaryOp<int>)_operationFuncs [ValueType.Int];
  163. // Return value unknown until it's evaluated
  164. int targetInt = (int) intOp (listItemValue, intVal.value);
  165. // Find this item's origin (linear search should be ok, should be short haha)
  166. ListDefinition itemOrigin = null;
  167. foreach (var origin in listVal.value.origins) {
  168. if (origin.name == listItem.originName) {
  169. itemOrigin = origin;
  170. break;
  171. }
  172. }
  173. if (itemOrigin != null) {
  174. InkListItem incrementedItem;
  175. if (itemOrigin.TryGetItemWithValue (targetInt, out incrementedItem))
  176. resultRawList.Add (incrementedItem, targetInt);
  177. }
  178. }
  179. return new ListValue (resultRawList);
  180. }
  181. List<Value> CoerceValuesToSingleType(List<Runtime.Object> parametersIn)
  182. {
  183. ValueType valType = ValueType.Int;
  184. ListValue specialCaseList = null;
  185. // Find out what the output type is
  186. // "higher level" types infect both so that binary operations
  187. // use the same type on both sides. e.g. binary operation of
  188. // int and float causes the int to be casted to a float.
  189. foreach (var obj in parametersIn) {
  190. var val = (Value)obj;
  191. if (val.valueType > valType) {
  192. valType = val.valueType;
  193. }
  194. if (val.valueType == ValueType.List) {
  195. specialCaseList = val as ListValue;
  196. }
  197. }
  198. // Coerce to this chosen type
  199. var parametersOut = new List<Value> ();
  200. // Special case: Coercing to Ints to Lists
  201. // We have to do it early when we have both parameters
  202. // to hand - so that we can make use of the List's origin
  203. if (valType == ValueType.List) {
  204. foreach (Value val in parametersIn) {
  205. if (val.valueType == ValueType.List) {
  206. parametersOut.Add (val);
  207. } else if (val.valueType == ValueType.Int) {
  208. int intVal = (int)val.valueObject;
  209. var list = specialCaseList.value.originOfMaxItem;
  210. InkListItem item;
  211. if (list.TryGetItemWithValue (intVal, out item)) {
  212. var castedValue = new ListValue (item, intVal);
  213. parametersOut.Add (castedValue);
  214. } else
  215. throw new StoryException ("Could not find List item with the value " + intVal + " in " + list.name);
  216. } else
  217. throw new StoryException ("Cannot mix Lists and " + val.valueType + " values in this operation");
  218. }
  219. }
  220. // Normal Coercing (with standard casting)
  221. else {
  222. foreach (Value val in parametersIn) {
  223. var castedValue = val.Cast (valType);
  224. parametersOut.Add (castedValue);
  225. }
  226. }
  227. return parametersOut;
  228. }
  229. public NativeFunctionCall(string name)
  230. {
  231. GenerateNativeFunctionsIfNecessary ();
  232. this.name = name;
  233. }
  234. // Require default constructor for serialisation
  235. public NativeFunctionCall() {
  236. GenerateNativeFunctionsIfNecessary ();
  237. }
  238. // Only called internally to generate prototypes
  239. NativeFunctionCall (string name, int numberOfParameters)
  240. {
  241. _isPrototype = true;
  242. this.name = name;
  243. this.numberOfParameters = numberOfParameters;
  244. }
  245. // For defining operations that do nothing to the specific type
  246. // (but are still supported), such as floor/ceil on int and float
  247. // cast on float.
  248. static object Identity<T>(T t) {
  249. return t;
  250. }
  251. static void GenerateNativeFunctionsIfNecessary()
  252. {
  253. if (_nativeFunctions == null) {
  254. _nativeFunctions = new Dictionary<string, NativeFunctionCall> ();
  255. // Why no bool operations?
  256. // Before evaluation, all bools are coerced to ints in
  257. // CoerceValuesToSingleType (see default value for valType at top).
  258. // So, no operations are ever directly done in bools themselves.
  259. // This also means that 1 == true works, since true is always converted
  260. // to 1 first.
  261. // However, many operations return a "native" bool (equals, etc).
  262. // Int operations
  263. AddIntBinaryOp(Add, (x, y) => x + y);
  264. AddIntBinaryOp(Subtract, (x, y) => x - y);
  265. AddIntBinaryOp(Multiply, (x, y) => x * y);
  266. AddIntBinaryOp(Divide, (x, y) => x / y);
  267. AddIntBinaryOp(Mod, (x, y) => x % y);
  268. AddIntUnaryOp (Negate, x => -x);
  269. AddIntBinaryOp(Equal, (x, y) => x == y);
  270. AddIntBinaryOp(Greater, (x, y) => x > y);
  271. AddIntBinaryOp(Less, (x, y) => x < y);
  272. AddIntBinaryOp(GreaterThanOrEquals, (x, y) => x >= y);
  273. AddIntBinaryOp(LessThanOrEquals, (x, y) => x <= y);
  274. AddIntBinaryOp(NotEquals, (x, y) => x != y);
  275. AddIntUnaryOp (Not, x => x == 0);
  276. AddIntBinaryOp(And, (x, y) => x != 0 && y != 0);
  277. AddIntBinaryOp(Or, (x, y) => x != 0 || y != 0);
  278. AddIntBinaryOp(Max, (x, y) => Math.Max(x, y));
  279. AddIntBinaryOp(Min, (x, y) => Math.Min(x, y));
  280. // Have to cast to float since you could do POW(2, -1)
  281. AddIntBinaryOp (Pow, (x, y) => (float) Math.Pow(x, y));
  282. AddIntUnaryOp(Floor, Identity);
  283. AddIntUnaryOp(Ceiling, Identity);
  284. AddIntUnaryOp(Int, Identity);
  285. AddIntUnaryOp (Float, x => (float)x);
  286. // Float operations
  287. AddFloatBinaryOp(Add, (x, y) => x + y);
  288. AddFloatBinaryOp(Subtract, (x, y) => x - y);
  289. AddFloatBinaryOp(Multiply, (x, y) => x * y);
  290. AddFloatBinaryOp(Divide, (x, y) => x / y);
  291. AddFloatBinaryOp(Mod, (x, y) => x % y); // TODO: Is this the operation we want for floats?
  292. AddFloatUnaryOp (Negate, x => -x);
  293. AddFloatBinaryOp(Equal, (x, y) => x == y);
  294. AddFloatBinaryOp(Greater, (x, y) => x > y);
  295. AddFloatBinaryOp(Less, (x, y) => x < y);
  296. AddFloatBinaryOp(GreaterThanOrEquals, (x, y) => x >= y);
  297. AddFloatBinaryOp(LessThanOrEquals, (x, y) => x <= y);
  298. AddFloatBinaryOp(NotEquals, (x, y) => x != y);
  299. AddFloatUnaryOp (Not, x => (x == 0.0f));
  300. AddFloatBinaryOp(And, (x, y) => x != 0.0f && y != 0.0f);
  301. AddFloatBinaryOp(Or, (x, y) => x != 0.0f || y != 0.0f);
  302. AddFloatBinaryOp(Max, (x, y) => Math.Max(x, y));
  303. AddFloatBinaryOp(Min, (x, y) => Math.Min(x, y));
  304. AddFloatBinaryOp (Pow, (x, y) => (float)Math.Pow(x, y));
  305. AddFloatUnaryOp(Floor, x => (float)Math.Floor(x));
  306. AddFloatUnaryOp(Ceiling, x => (float)Math.Ceiling(x));
  307. AddFloatUnaryOp(Int, x => (int)x);
  308. AddFloatUnaryOp(Float, Identity);
  309. // String operations
  310. AddStringBinaryOp(Add, (x, y) => x + y); // concat
  311. AddStringBinaryOp(Equal, (x, y) => x.Equals(y));
  312. AddStringBinaryOp (NotEquals, (x, y) => !x.Equals (y));
  313. AddStringBinaryOp (Has, (x, y) => x.Contains(y));
  314. AddStringBinaryOp (Hasnt, (x, y) => !x.Contains(y));
  315. // List operations
  316. AddListBinaryOp (Add, (x, y) => x.Union (y));
  317. AddListBinaryOp (Subtract, (x, y) => x.Without(y));
  318. AddListBinaryOp (Has, (x, y) => x.Contains (y));
  319. AddListBinaryOp (Hasnt, (x, y) => !x.Contains (y));
  320. AddListBinaryOp (Intersect, (x, y) => x.Intersect (y));
  321. AddListBinaryOp (Equal, (x, y) => x.Equals(y));
  322. AddListBinaryOp (Greater, (x, y) => x.GreaterThan(y));
  323. AddListBinaryOp (Less, (x, y) => x.LessThan(y));
  324. AddListBinaryOp (GreaterThanOrEquals, (x, y) => x.GreaterThanOrEquals(y));
  325. AddListBinaryOp (LessThanOrEquals, (x, y) => x.LessThanOrEquals(y));
  326. AddListBinaryOp (NotEquals, (x, y) => !x.Equals(y));
  327. AddListBinaryOp (And, (x, y) => x.Count > 0 && y.Count > 0);
  328. AddListBinaryOp (Or, (x, y) => x.Count > 0 || y.Count > 0);
  329. AddListUnaryOp (Not, x => x.Count == 0 ? (int)1 : (int)0);
  330. // Placeholders to ensure that these special case functions can exist,
  331. // since these function is never actually run, and is special cased in Call
  332. AddListUnaryOp (Invert, x => x.inverse);
  333. AddListUnaryOp (All, x => x.all);
  334. AddListUnaryOp (ListMin, (x) => x.MinAsList());
  335. AddListUnaryOp (ListMax, (x) => x.MaxAsList());
  336. AddListUnaryOp (Count, (x) => x.Count);
  337. AddListUnaryOp (ValueOfList, (x) => x.maxItem.Value);
  338. // Special case: The only operations you can do on divert target values
  339. BinaryOp<Path> divertTargetsEqual = (Path d1, Path d2) => {
  340. return d1.Equals (d2);
  341. };
  342. BinaryOp<Path> divertTargetsNotEqual = (Path d1, Path d2) => {
  343. return !d1.Equals (d2);
  344. };
  345. AddOpToNativeFunc (Equal, 2, ValueType.DivertTarget, divertTargetsEqual);
  346. AddOpToNativeFunc (NotEquals, 2, ValueType.DivertTarget, divertTargetsNotEqual);
  347. }
  348. }
  349. void AddOpFuncForType(ValueType valType, object op)
  350. {
  351. if (_operationFuncs == null) {
  352. _operationFuncs = new Dictionary<ValueType, object> ();
  353. }
  354. _operationFuncs [valType] = op;
  355. }
  356. static void AddOpToNativeFunc(string name, int args, ValueType valType, object op)
  357. {
  358. NativeFunctionCall nativeFunc = null;
  359. if (!_nativeFunctions.TryGetValue (name, out nativeFunc)) {
  360. nativeFunc = new NativeFunctionCall (name, args);
  361. _nativeFunctions [name] = nativeFunc;
  362. }
  363. nativeFunc.AddOpFuncForType (valType, op);
  364. }
  365. static void AddIntBinaryOp(string name, BinaryOp<int> op)
  366. {
  367. AddOpToNativeFunc (name, 2, ValueType.Int, op);
  368. }
  369. static void AddIntUnaryOp(string name, UnaryOp<int> op)
  370. {
  371. AddOpToNativeFunc (name, 1, ValueType.Int, op);
  372. }
  373. static void AddFloatBinaryOp(string name, BinaryOp<float> op)
  374. {
  375. AddOpToNativeFunc (name, 2, ValueType.Float, op);
  376. }
  377. static void AddStringBinaryOp(string name, BinaryOp<string> op)
  378. {
  379. AddOpToNativeFunc (name, 2, ValueType.String, op);
  380. }
  381. static void AddListBinaryOp (string name, BinaryOp<InkList> op)
  382. {
  383. AddOpToNativeFunc (name, 2, ValueType.List, op);
  384. }
  385. static void AddListUnaryOp (string name, UnaryOp<InkList> op)
  386. {
  387. AddOpToNativeFunc (name, 1, ValueType.List, op);
  388. }
  389. static void AddFloatUnaryOp(string name, UnaryOp<float> op)
  390. {
  391. AddOpToNativeFunc (name, 1, ValueType.Float, op);
  392. }
  393. public override string ToString ()
  394. {
  395. return "Native '" + name + "'";
  396. }
  397. delegate object BinaryOp<T>(T left, T right);
  398. delegate object UnaryOp<T>(T val);
  399. NativeFunctionCall _prototype;
  400. bool _isPrototype;
  401. // Operations for each data type, for a single operation (e.g. "+")
  402. Dictionary<ValueType, object> _operationFuncs;
  403. static Dictionary<string, NativeFunctionCall> _nativeFunctions;
  404. }
  405. }