PageRenderTime 165ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/YieldProlog/Modules/IndexedAnswers.cs

https://bitbucket.org/VirtualReality/optional-modules
C# | 382 lines | 257 code | 33 blank | 92 comment | 49 complexity | 439ebd63a78afb5263ca52bb37f7c316 MD5 | raw file
  1. /*
  2. * Copyright (c) Contributors, http://aurora-sim.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the Aurora-Sim Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections;
  29. using System.Collections.Generic;
  30. namespace OpenSim.Region.ScriptEngine.Shared.YieldProlog
  31. {
  32. /// <summary>
  33. /// An IndexedAnswers holds answers to a query based on the values of index arguments.
  34. /// </summary>
  35. public class IndexedAnswers : YP.IClause
  36. {
  37. private int _arity;
  38. // addAnswer adds the answer here and indexes it later.
  39. private List<object[]> _allAnswers = new List<object[]>();
  40. // The key has the arity of answers with non-null values for each indexed arg. The value
  41. // is a list of the matching answers. The signature is implicit in the pattern on non-null index args.
  42. private Dictionary<HashedList, List<object[]>> _indexedAnswers =
  43. new Dictionary<HashedList, List<object[]>>();
  44. // Keeps track of whether we have started adding entries to _indexedAnswers for the signature.
  45. private Dictionary<int, object> _gotAnswersForSignature = new Dictionary<int, object>();
  46. private const int MAX_INDEX_ARGS = 31;
  47. public IndexedAnswers(int arity)
  48. {
  49. _arity = arity;
  50. }
  51. /// <summary>
  52. /// Append the answer to the list and update the indexes, if any.
  53. /// Elements of answer must be ground, since arguments with unbound variables make this
  54. /// into a dynamic rule which we don't index.
  55. /// </summary>
  56. /// <param name="answer"></param>
  57. public void addAnswer(object[] answer)
  58. {
  59. addOrPrependAnswer(answer, false);
  60. }
  61. /// <summary>
  62. /// Prepend the answer to the list and clear the indexes so that they must be re-computed
  63. /// on the next call to match. (Only addAnswer will maintain the indexes while adding answers.)
  64. /// Elements of answer must be ground, since arguments with unbound variables make this
  65. /// into a dynamic rule which we don't index.
  66. /// </summary>
  67. /// <param name="answer"></param>
  68. public void prependAnswer(object[] answer)
  69. {
  70. addOrPrependAnswer(answer, true);
  71. }
  72. /// <summary>
  73. /// Do the work of addAnswer or prependAnswer.
  74. /// </summary>
  75. /// <param name="answer"></param>
  76. private void addOrPrependAnswer(object[] answer, bool prepend)
  77. {
  78. if (answer.Length != _arity)
  79. return;
  80. // Store a copy of the answer array.
  81. object[] answerCopy = new object[answer.Length];
  82. Variable.CopyStore copyStore = new Variable.CopyStore();
  83. for (int i = 0; i < answer.Length; ++i)
  84. answerCopy[i] = YP.makeCopy(answer[i], copyStore);
  85. if (copyStore.getNUniqueVariables() > 0)
  86. throw new InvalidOperationException
  87. ("Elements of answer must be ground, but found " + copyStore.getNUniqueVariables() +
  88. " unbound variables");
  89. if (prepend)
  90. {
  91. _allAnswers.Insert(0, answerCopy);
  92. clearIndexes();
  93. }
  94. else
  95. {
  96. _allAnswers.Add(answerCopy);
  97. // If match has already indexed answers for a signature, we need to add
  98. // this to the existing indexed answers.
  99. foreach (int signature in _gotAnswersForSignature.Keys)
  100. indexAnswerForSignature(answerCopy, signature);
  101. }
  102. }
  103. private void indexAnswerForSignature(object[] answer, int signature)
  104. {
  105. // First find out which of the answer values can be used as an index.
  106. object[] indexValues = new object[answer.Length];
  107. for (int i = 0; i < answer.Length; ++i)
  108. {
  109. // We limit the number of indexed args in a 32-bit signature.
  110. if (i >= MAX_INDEX_ARGS)
  111. indexValues[i] = null;
  112. else
  113. indexValues[i] = getIndexValue(YP.getValue(answer[i]));
  114. }
  115. // We need an entry in indexArgs from indexValues for each 1 bit in signature.
  116. HashedList indexArgs = new HashedList(indexValues.Length);
  117. for (int i = 0; i < indexValues.Length; ++i)
  118. {
  119. if ((signature & (1 << i)) == 0)
  120. indexArgs.Add(null);
  121. else
  122. {
  123. if (indexValues[i] == null)
  124. // The signature wants an index value here, but we don't have one so
  125. // we can't add it as an answer for this signature.
  126. return;
  127. else
  128. indexArgs.Add(indexValues[i]);
  129. }
  130. }
  131. // Add the answer to the answers list for indexArgs, creating the entry if needed.
  132. List<object[]> answers;
  133. if (!_indexedAnswers.TryGetValue(indexArgs, out answers))
  134. {
  135. answers = new List<object[]>();
  136. _indexedAnswers[indexArgs] = answers;
  137. }
  138. answers.Add(answer);
  139. }
  140. public IEnumerable<bool> match(object[] arguments)
  141. {
  142. if (arguments.Length != _arity)
  143. yield break;
  144. // Set up indexArgs, up to arg position MAX_INDEX_ARGS. The signature has a 1 bit for
  145. // each non-null index arg.
  146. HashedList indexArgs = new HashedList(arguments.Length);
  147. bool gotAllIndexArgs = true;
  148. int signature = 0;
  149. for (int i = 0; i < arguments.Length; ++i)
  150. {
  151. object indexValue = null;
  152. if (i < MAX_INDEX_ARGS)
  153. {
  154. // We limit the number of args in a 32-bit signature.
  155. indexValue = getIndexValue(YP.getValue(arguments[i]));
  156. if (indexValue != null)
  157. signature += (1 << i);
  158. }
  159. if (indexValue == null)
  160. gotAllIndexArgs = false;
  161. indexArgs.Add(indexValue);
  162. }
  163. List<object[]> answers;
  164. if (signature == 0)
  165. // No index args, so we have to match from _allAnswers.
  166. answers = _allAnswers;
  167. else
  168. {
  169. if (!_gotAnswersForSignature.ContainsKey(signature))
  170. {
  171. // We need to create the entry in _indexedAnswers.
  172. foreach (object[] answer in _allAnswers)
  173. indexAnswerForSignature(answer, signature);
  174. // Mark that we did this signature.
  175. _gotAnswersForSignature[signature] = null;
  176. }
  177. if (!_indexedAnswers.TryGetValue(indexArgs, out answers))
  178. yield break;
  179. }
  180. if (gotAllIndexArgs)
  181. {
  182. // All the arguments were already bound, so we don't need to do bindings.
  183. yield return false;
  184. yield break;
  185. }
  186. // Find matches in answers.
  187. IEnumerator<bool>[] iterators = new IEnumerator<bool>[arguments.Length];
  188. // Debug: If the caller asserts another answer into this same predicate during yield, the iterator
  189. // over clauses will be corrupted. Should we take the time to copy answers?
  190. foreach (object[] answer in answers)
  191. {
  192. bool gotMatch = true;
  193. int nIterators = 0;
  194. // Try to bind all the arguments.
  195. for (int i = 0; i < arguments.Length; ++i)
  196. {
  197. if (indexArgs[i] != null)
  198. // We already matched this argument by looking up _indexedAnswers.
  199. continue;
  200. IEnumerator<bool> iterator = YP.unify(arguments[i], answer[i]).GetEnumerator();
  201. iterators[nIterators++] = iterator;
  202. // MoveNext() is true if YP.unify succeeds.
  203. if (!iterator.MoveNext())
  204. {
  205. gotMatch = false;
  206. break;
  207. }
  208. }
  209. try
  210. {
  211. if (gotMatch)
  212. yield return false;
  213. }
  214. finally
  215. {
  216. // Manually finalize all the iterators.
  217. for (int i = 0; i < nIterators; ++i)
  218. iterators[i].Dispose();
  219. }
  220. }
  221. }
  222. public IEnumerable<bool> clause(object Head, object Body)
  223. {
  224. Head = YP.getValue(Head);
  225. if (Head is Variable)
  226. throw new PrologException("instantiation_error", "Head is an unbound variable");
  227. object[] arguments = YP.getFunctorArgs(Head);
  228. // We always match Head from _allAnswers, and the Body is Atom.a("true").
  229. #pragma warning disable 0168, 0219
  230. foreach (bool l1 in YP.unify(Body, Atom.a("true")))
  231. {
  232. // The caller can assert another answer into this same predicate during yield, so we have to
  233. // make a copy of the answers.
  234. foreach (object[] answer in _allAnswers.ToArray())
  235. {
  236. foreach (bool l2 in YP.unifyArrays(arguments, answer))
  237. yield return false;
  238. }
  239. }
  240. #pragma warning restore 0168, 0219
  241. }
  242. public IEnumerable<bool> retract(object Head, object Body)
  243. {
  244. Head = YP.getValue(Head);
  245. if (Head is Variable)
  246. throw new PrologException("instantiation_error", "Head is an unbound variable");
  247. object[] arguments = YP.getFunctorArgs(Head);
  248. // We always match Head from _allAnswers, and the Body is Atom.a("true").
  249. #pragma warning disable 0168, 0219
  250. foreach (bool l1 in YP.unify(Body, Atom.a("true")))
  251. {
  252. // The caller can assert another answer into this same predicate during yield, so we have to
  253. // make a copy of the answers.
  254. foreach (object[] answer in _allAnswers.ToArray())
  255. {
  256. foreach (bool l2 in YP.unifyArrays(arguments, answer))
  257. {
  258. _allAnswers.Remove(answer);
  259. clearIndexes();
  260. yield return false;
  261. }
  262. }
  263. }
  264. #pragma warning restore 0168, 0219
  265. }
  266. /// <summary>
  267. /// After retracting or prepending an answer in _allAnswers, the indexes are invalid, so clear them.
  268. /// </summary>
  269. private void clearIndexes()
  270. {
  271. _indexedAnswers.Clear();
  272. _gotAnswersForSignature.Clear();
  273. }
  274. /// <summary>
  275. /// A HashedList extends an ArrayList with methods to get a hash and to check equality
  276. /// based on the elements of the list.
  277. /// </summary>
  278. public class HashedList : ArrayList
  279. {
  280. private bool _gotHashCode = false;
  281. private int _hashCode;
  282. public HashedList()
  283. : base()
  284. {
  285. }
  286. public HashedList(int capacity)
  287. : base(capacity)
  288. {
  289. }
  290. public HashedList(ICollection c)
  291. : base(c)
  292. {
  293. }
  294. // Debug: Should override all the other methods that change this.
  295. public override int Add(object value)
  296. {
  297. _gotHashCode = false;
  298. return base.Add(value);
  299. }
  300. public override int GetHashCode()
  301. {
  302. if (!_gotHashCode)
  303. {
  304. int hashCode = 1;
  305. foreach (object obj in this)
  306. hashCode = 31 * hashCode + (obj == null ? 0 : obj.GetHashCode());
  307. _hashCode = hashCode;
  308. _gotHashCode = true;
  309. }
  310. return _hashCode;
  311. }
  312. public override bool Equals(object obj)
  313. {
  314. if (!(obj is ArrayList))
  315. return false;
  316. ArrayList objList = (ArrayList)obj;
  317. if (objList.Count != Count)
  318. return false;
  319. for (int i = 0; i < Count; ++i)
  320. {
  321. object value = objList[i];
  322. if (value == null)
  323. {
  324. if (this[i] != null)
  325. return false;
  326. }
  327. else
  328. {
  329. if (!value.Equals(this[i]))
  330. return false;
  331. }
  332. }
  333. return true;
  334. }
  335. }
  336. /// <summary>
  337. /// If we keep an index on value, return the value, or null if we don't index it.
  338. /// </summary>
  339. /// <param name="value">the term to examine. Assume you already called YP.getValue(value)</param>
  340. /// <returns></returns>
  341. public static object getIndexValue(object value)
  342. {
  343. if (value is Atom || value is string || value is Int32 || value is DateTime)
  344. return value;
  345. else
  346. return null;
  347. }
  348. }
  349. }