/* $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