/* $Id$ * * Project: Swicli.Library - Two Way Interface for .NET and MONO to SWI-Prolog * Author: Douglas R. Miles * Uwe Lesta (SbsSW.SwiPlCs classes) * E-mail: logicmoo@gmail.com * WWW: http://www.logicmoo.com * Copyright (C): 2008, Uwe Lesta SBS-Softwaresysteme GmbH, * 2010-2012 LogicMOO Developement * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * *********************************************************/ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using SbsSW.DesignByContract; using SbsSW.SwiPlCs.Exceptions; using SbsSW.SwiPlCs.Streams; using Swicli.Library; namespace SbsSW.SwiPlCs { #region enum PlQuerySwitch /// /// Flags that control for the foreign predicate parameters /// SWI-Prolog Manual - 9.6.16 Querying Prolog. /// /// public enum PlQuerySwitch { /// The default value. None = 0, /// Return an integer holding the number of arguments given to Prolog from Unix. Argc = libpl.PL_QUERY_ARGC, /// Return a char ** holding the argument vector given to Prolog from Unix. Argv = libpl.PL_QUERY_ARGV, /// Read character from terminal. GetChar = libpl.PL_QUERY_GETC, /// Return a long, representing the maximal integer value represented by a Prolog integer. MaxInteger = libpl.PL_QUERY_MAX_INTEGER, /// Return a long, representing the minimal integer value. MinInteger = libpl.PL_QUERY_MIN_INTEGER, /// Return a long, representing the version as 10,000 × M + 100 × m + p, where M is the major, m the minor version number and p the patch-level. For example, 20717 means 2.7.17 Version = libpl.PL_QUERY_VERSION, /// Return the maximum number of threads that can be created in this version. Return values of PL_thread_self() are between 0 and this number. MaxThreads = libpl.PL_QUERY_MAX_THREADS, /// Return the default stream encoding of Prolog (of type IOENC). Encoding = libpl.PL_QUERY_ENCODING, /// Get amount of user CPU time of the process in milliseconds. UserCpu = libpl.PL_QUERY_USER_CPU, } #endregion enum PlQuerySwitch /// /// Represents one variable of a Query result. /// public class PlQueryVar { /// The name of a variable in a Query public string Name { get; set; } /// The ManagedObject (PlTerm) of a variable in a Query public PlTerm Value { get; set; } public PlQueryVar(string name, PlTerm val) { Name = name; Value = val; } } /// /// Represents the set variables of a Query if it was created from a string. /// This class is also used to represent the results of a PlQuery after or was called. /// /// /// This sample shows both is used to unify the variables of two nested queries /// and the result /// /// /// public class PlQueryVariables { private List _vars = new List(); public void Add(PlQueryVar var) { _vars.Add(var); } /// /// Returns the number of elements in the sequence. (Defined by Enumerable of List<PlQueryVar>.) /// public int Count { get { return _vars.Count; } } /// /// Gets the of the given variable name or throw an ArgumentException. /// /// The name of the variable /// The PlTerm (value) of the variable /// Is thrown if the name is not the name of a variable. public PlTerm this[string name] { get { PlQueryVar v = _vars.Find(delegate(PlQueryVar n1) { return n1.Name == name; }); if(v == null) throw new ArgumentException("'" + name + "' is not a variable", "name"); return v.Value; } } public PlQueryVar this[int idx] { get { return _vars[idx]; } } } // class PlQueryVariables /******************************** * PlQuery * ********************************/ #region public class PlQuery /// /// This class allows queries to prolog. /// A query can be created by a string or by constructing compound terms see Constructors for details. /// /// All resources an terms created by a query are reclaimed by . It is recommended to build a query in a scope. /// /// There are four possible opportunities to query Prolog /// /// Query typeDescription /// A static callTo ask prolog for a proof. Return only true or false. /// A To get the first result of a goal /// Construct a PlQuery object by a string.The most convenient way. /// Construct a PlQuery object by compound terms.The most flexible and fast (runtime) way. /// /// /// For examples see and /// /// /// The query will be opened by and will be closed if NextSolution() return false. /// public class PlQuery : IDisposable { #region public members /// /// The List of of this PlQuery. /// /// /// In the following example you see how the query Variables can be used to set a variable. /// /// Here is a more complex sample where the variables of two queries are connected. /// /// /// public PlQueryVariables Variables { get { return _queryVariables; } } //the default public bool DiscardData = true; /// /// Gets a of the variable names if the query was built by a string. /// public Collection VariableNames { get { Collection sl = new Collection(); for(int i = 0; i < _queryVariables.Count; i++) { sl.Add(_queryVariables[i].Name); } return sl; } } #endregion public members #region private members private string _module = "user"; private string _name; private uint _qid; // private PlTermV _av; // Argument vector string _query_string; /// the list of prolog record's (the copies to store the variable bindings over backtracking) private List _records = new List(); private PlQueryVariables _queryVariables = new PlQueryVariables(); private static string _buffer_string; #endregion private members static private long Sread(IntPtr handle, System.IntPtr buffer, long buffersize) { byte[] array = System.Text.Encoding.Unicode.GetBytes(_buffer_string); System.Runtime.InteropServices.Marshal.Copy(array, 0, buffer, array.Length); return array.Length; } private void EraseRecords() { foreach (uint i in _records) { libpl.PL_erase(i); } _records.Clear(); } #region implementing IDisposable // // http://msdn.microsoft.com/de-de/library/b1yfkh5e.aspx // // http://msdn.microsoft.com/de-de/library/fs2xkftw.aspx /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Release all resources from the query /// /// if true all is deleted protected virtual void Dispose(bool disposing) { if (disposing) { // Free other state (managed objects). EraseRecords(); } // Free your own state (unmanaged objects). // Set large fields to null. Free(DiscardData); } #endregion #region implementing Free /// ~PlQuery() { try { Dispose(false); } catch (Exception e) { Embedded.Warn("dispose casued {0}", e); } } /// /// Discards the query, but does not delete any of the data created by the query if discardData is false. /// It just invalidate qid, allowing for a new PlQuery object in this context. /// /// see /// if true all bindings of the query are destroyed private void Free(bool discardData) { if (_qid > 0 && PlEngine.IsInitialized) { try { if (discardData) // <"leider werden dann die gebundenen variablen der query wieder frei e.g. in PlCall(goal)"/> // unfortunately this statement detaches the bound variables of the query e.g. in PlCall(goal) libpl.PL_close_query(_qid); else libpl.PL_cut_query(_qid); } catch (Exception e) { Embedded.Warn("Free caused: " + PrologCLR.ExceptionString(e)); } } _qid = 0; } #endregion Free #region constructors private PlQuery() { } /// /// With these constructors a Prolog query can be created but not opened. To get the results see /// A Query can be created from a string or by a name and PlTermV. The later is a native way and available for compatibility. /// If a Query is created from a string representing arbitrary prolog text /// the helper classes and comes into the game. /// In this case the most convenient way to get the results is to use or . /// /// For examples see . /// /// /// /// With this constructor a query is created from a string. /// Uppercase parameters are interpreted a variables but can't be nested in sub terms. /// If you need a variable in a nested term use . /// See the examples for details. /// /// Muddy Waters sang:"I'am build for comfort, I ain't build for speed" /// /// /// This sample shows a query with two variables. /// /// And the same with named variables. /// /// This sample shows what happens if the argument vector is used with compound terms. /// /// And here how to get the results with named variables with compound terms. /// /// /// A string for a prolog query public PlQuery(string goal) : this("user", goal) { } #pragma warning disable 1573 /// /// locating the predicate in the named module. /// locating the predicate in the named module. public PlQuery(string module, string goal) { _module = module; _query_string = goal; //_query_string = "(" + _query_string + ")"; if (!_query_string.EndsWith(".", StringComparison.Ordinal)) _query_string += "."; _query_string += Environment.NewLine; // redirect read stream DelegateStreamReadFunction old_read_function = PlEngine._function_read; DelegateStreamReadFunction rf = new DelegateStreamReadFunction(Sread); if (Embedded.RedirectStreams) PlEngine.SetStreamFunctionRead(PlStreamType.Input, rf); try { // call read_term(Term_of_query_string, [variable_names(VN)]). PlTerm term = PlTerm.PlVar(); PlTerm option_list = PlTerm.PlVar(); PlTerm variablenames_list = PlTerm.PlVar(); PlTerm l = PlTerm.PlTail(option_list); l.Append(PlTerm.PlCompound("variable_names", variablenames_list)); l.Close(); PlTermV args = new PlTermV(term, option_list); // NOTE: read/1 needs a dot ('.') at the end if (!PlQuery.PlCall("read_term", args)) throw new PlLibException("PlCall read_term fails! goal:" + _query_string); // restore stream function if (Embedded.RedirectStreams) PlEngine.SetStreamFunctionRead(PlStreamType.Input, old_read_function); // set list of variables and variable_names into _queryVariables foreach (PlTerm t in variablenames_list.ToList()) { // t[0]='=' , t[1]='VN', t[2]=_G123 _queryVariables.Add(new PlQueryVar(t[1].ToString(), t[2])); } // Build the query _name = term.Name; // is ok e.g. for listing/0. // Check.Require(term.Arity > 0, "PlQuery(PlTerm t): t.Arity must be greater than 0."); _av = new PlTermV(term.Arity); for (int index = 0; index < term.Arity; index++) { if (0 == libpl.PL_get_arg(index + 1, term.TermRef, _av[index].TermRef)) throw new PlException("PL_get_arg in PlQuery " + term.ToString()); } } #if _DEBUG catch (Exception ex) { PrologCLR.ConsoleTrace(ex.Message); } #endif finally { // NBT } } #pragma warning restore 1573 // public PlQuery(const char *Name, const PlTermV &av) /// /// Create a query where name defines the name of the predicate and av the argument vector. /// The arity is deduced from av. The predicate is located in the Prolog module user. /// /// /// This sample shows a query with a compound term as an argument. /// /// /// the name of the predicate /// the argument vector containing the parameters public PlQuery(string name, PlTermV termV) : this("user", name, termV) { } #pragma warning disable 1573 /// /// locating the predicate in the named module. /// locating the predicate in the named module. public PlQuery(string module, string name, PlTermV termV) { //if (null == termV) // throw new ArgumentNullException("termV"); _av = termV; _name = name; _module = module; } #pragma warning restore 1573 #endregion // TODO: make it public or private /// Provide access to the Argument vector for the query public PlTermV Args { get { return _av; } } /// /// Provide the next solution to the query. Prolog exceptions are mapped to C# exceptions. /// /// return true if successful and false if there are no (more) solutions. /// /// If the query is closed it will be opened. If the last solution was generated the query will be closed. /// If an exception is thrown while parsing (open) the query the _qid is set to zero. /// /// Is thrown if SWI-Prolog Manual PL_next_solution() returns false public bool NextSolution() { EnsureQUID(); int rval = libpl.PL_next_solution(_qid); if (0 == rval) { // error CheckForException(); } if(rval <= 0) Free(false); return rval > 0; } public void EnsureQUID() { if (0 == _qid) { Check.Require(!string.IsNullOrEmpty(_name), "PlQuery.NextSolution() _name is required"); IntPtr p = libpl.PL_predicate(_name, _av.Size, _module); _qid = libpl.PL_open_query((IntPtr)0, libpl.PL_Q_CATCH_EXCEPTION, p, _av.A0); } } private void CheckForException() { uint ex = libpl.PL_exception(_qid); // term_t if (ex > 0) { PlTerm exceptionTerm = new PlTerm(ex); PlException etmp = new PlException(exceptionTerm); _qid = 0; // to avoid an AccessViolationException on Dispose. E.g. if the query is miss spelled. Embedded.Debug("exception from " + _query_string); etmp.Throw(); } } /// /// Enumerate the solutions. /// For examples see /// /// /// Constructors public IEnumerable Solutions { get { while (this.NextSolution()) { yield return this._av; } } } /// /// Enumerate the of one solution. /// /// /// /// /// public IEnumerable SolutionVariables { get { while (this.NextSolution()) { PlQueryVariables qv = new PlQueryVariables(); for (int i = 0; i < _queryVariables.Count; i++) { qv.Add(new PlQueryVar(_queryVariables[i].Name, _queryVariables[i].Value)); } yield return qv; } } } /// /// Create a of . /// If calling ToList() all solutions of the query are generated and stored in the Collection. /// /// A ReadOnlyCollection of PlQueryVariables containing all solutions of the query. /// /// /// public ReadOnlyCollection ToList() { List list = new List(); EraseRecords(); while (this.NextSolution()) { for (int i = 0; i < _queryVariables.Count; i++ ) { _records.Add(libpl.PL_record(_queryVariables[i].Value.TermRef)); // to keep the PlTerms } } PlQueryVariables qv = new PlQueryVariables(); // dummy to make the compiler happy int avIdx = _queryVariables.Count; foreach (uint record_t in _records) { uint term_t = libpl.PL_new_term_ref(); ; libpl.PL_recorded(record_t, term_t); if (avIdx == _queryVariables.Count) { qv = new PlQueryVariables(); list.Add(qv); avIdx = 0; } //qv.Add(new PlQueryVar(GetVariableName(avIdx), new PlTerm(term_t))); // If this line is deleted -> update comment in PlTern(term_ref) qv.Add(new PlQueryVar(_queryVariables[avIdx].Name, new PlTerm(term_t))); // If this line is deleted -> update comment in PlTern(term_ref) avIdx++; //av[avIdx++].TermRef = term_t; } return new ReadOnlyCollection(list); } #region static PlCall /// /// Obtain status information on the Prolog system. The actual argument type depends on the information required. /// The parameter queryType describes what information is wanted. /// Returning pointers and integers as a long is bad style. The signature of this function should be changed. /// PlQuerySwitch /// /// /// This sample shows how to get SWI-Prologs version number /// /// /// A PlQuerySwitch. /// A int depending on the given queryType public static int Query(PlQuerySwitch queryType) { Check.Require(queryType != PlQuerySwitch.None, "PlQuerySwitch (None) is not valid"); return (int)libpl.PL_query((uint)queryType); } /// /// The main purpose of the static PlCall methods is to call a prolog prove or to do some site effects. /// /// /// Assert.IsTrue(PlQuery.PlCall("is_list", new PlTerm("[a,b,c,d]"))); /// /// /// Assert.IsTrue(PlQuery.PlCall("consult", new PlTerm("some_file_name"))); /// // or /// Assert.IsTrue(PlQuery.PlCall("consult('some_file_name')")); /// /// /// /// /// /// Create a PlQuery from the arguments, generates the first solution by NextSolution() and destroys the query. /// /// defines the name of the predicate /// Is a of arguments for the predicate /// Return true or false as the result of NextSolution() or throw an exception. public static bool PlCall(string predicate, PlTermV args) { bool bRet = false; PlQuery q = new PlQuery(predicate, args); bRet = q.NextSolution(); q.Free(false); return bRet; } #pragma warning disable 1573 /// /// As but locating the predicate in the named module. /// locating the predicate in the named module. public static bool PlCall(string module, string predicate, PlTermV args) { bool bRet = false; PlQuery q = new PlQuery(module, predicate, args); bRet = q.NextSolution(); q.Free(false); return bRet; } #pragma warning restore 1573 /// /// Call a goal once. /// /// /// Assert.IsTrue(PlQuery.PlCall("is_list([a,b,c,d])")); /// /// /// Assert.IsTrue(PlQuery.PlCall("consult('some_file_name')")); /// /// /// The complete goal as a string public static bool PlCall(string goal) { // TODO: change to use PlTerm(text) or PlCompound(string) // TODO: bool bRet = false; PlQuery q = new PlQuery("call", new PlTermV(PlTerm.PlCompound(goal))); bRet = q.NextSolution(); q.Free(true); return bRet; } #endregion #region PlCallQuery //TODO: /// /// NOTE:will be changed in the near future. /// return the solution of a query which is called once by call /// Throw an ArgumentException if there is no or more than one variable in the goal /// /// a goal with *one* variable /// the bound variable of the first solution public static PlTerm PlCallQuery(string goal) { PlTerm retVal; PlQuery q = new PlQuery(goal); { // find the variable or throw an exception PlTerm ? t = null; for (int i = 0; i < q._av.Size; i++) { if (q._av[i].IsVar) { if ((object)t == null) { t = new PlTerm(q._av[i].TermRef); } else throw new ArgumentException("More than one Variable in " + goal); } } if ((object)t == null) throw new ArgumentException("No Variable found in " + goal); if (q.NextSolution()) { retVal = (PlTerm)t; } else retVal = new PlTerm(); // null } q.Free(false); return retVal; } #endregion } // class PlQuery #endregion } // namespace SbsSW.SwiPlCs