#region Copyright
// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (C) 2015 Ian Horswill
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in the
// Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// --------------------------------------------------------------------------------------------------------------------
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Prolog
{
///
/// Represents a function or predicate expression in the logic programming system.
///
[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Structure")]
public sealed class Structure : AlphaConvertibleTerm
{
#region Constructor
///
/// Creates a new term with the specified functor (predicate or function name) and arguments.
///
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "functor")]
public Structure(Symbol functor, params object[] functorArguments)
{
if (functorArguments == null)
throw new ArgumentNullException("functorArguments");
Functor = functor;
Arguments = functorArguments;
}
///
/// Creates a new term with the specified functor (predicate or function name) and arguments.
///
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "functor")]
public Structure(string functor, params object[] args)
: this(Symbol.Intern(functor), args)
{
}
///
/// Returns the Term whose functor is the first element of the list and whose arguments are the other elements of the list.
/// Functional version of =..
///
public static Structure FromList(Structure listExpression)
{
if (listExpression == null || !listExpression.IsFunctor(Symbol.PrologListConstructor, 2)) throw new ArgumentException("Argument must be a prolog list");
object functorArg = listExpression.Argument(0);
if (functorArg == null) throw new ArgumentException("First element of list (functor) must be a symbol.");
var functor = functorArg as Symbol;
if (functor == null) throw new ArgumentException("First element of list (functor) must be a symbol.");
return new Structure(functor, Prolog.PrologListToArray(listExpression.Arguments[1]));
}
#endregion
///
/// Returns a Prolog list whose first element is the functor of the term and whose tail is its arguments.
/// Functional version of =..
///
public Structure ToPrologList()
{
return new Structure(Symbol.PrologListConstructor, Functor, Prolog.ArrayToPrologList(Arguments));
}
#region Instance fields and properties
///
/// Returns the predicate indicator for this structure.
///
public PredicateIndicator PredicateIndicator
{
get
{
return new PredicateIndicator(this);
}
}
///
/// The name of the function or predicate.
///
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Functor")]
public Symbol Functor { get; private set; }
///
/// Arguments to the function or predicate.
///
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
public object[] Arguments { get; private set; }
///
/// Returns the canonicalized version of the ith argument
///
public object Argument(int argumentIndex)
{
return Deref(Arguments[argumentIndex]);
}
///
/// Returns the canonicalized version of the ith argument
///
public T Argument(int argumentIndex)
{
return (T)Deref(Arguments[argumentIndex]);
}
///
/// The arity (number of arguments) of this structure.
///
public int Arity
{
get { return Arguments.Length; }
}
#endregion
#region Derived properties and predicates
///
/// True if neither this term nor its subterms contain and LogicVariables
///
public bool IsGroundInstance
{
get
{
foreach (var o in Arguments)
{
var t = o as Structure;
if (t != null && !t.IsGroundInstance)
return false;
}
return true;
}
}
///
/// True if the term has the specified functor and arity
///
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "arity"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Functor")]
public bool IsFunctor(Symbol name, int arity)
{
return Functor == name && Arguments.Length == arity;
}
///
/// True if the term has the specified functor and arity
///
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "arity"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Functor")]
public static bool IsFunctor(object term, Symbol name, int arity)
{
var s = term as Structure;
return s != null && s.IsFunctor(name, arity);
}
#endregion
#region Unification
internal override IEnumerable UnifyWithTerm(Term term)
{
return term.UnifyWithStructure(this);
}
internal override IEnumerable UnifyWithStructure(Structure value)
{
if (value == null || value.Functor != Functor || value.Arguments.Length != Arguments.Length)
return FailEnumerator;
return UnifyArrays(Arguments, value.Arguments);
}
internal override bool UnifyWithTerm(Term term, PrologContext context)
{
return term.UnifyWithStructure(this, context);
}
internal override bool UnifyWithStructure(Structure value, PrologContext context)
{
if (value == null || value.Functor != Functor || value.Arguments.Length != Arguments.Length)
return false;
return UnifyArrays(Arguments, value.Arguments, context);
}
#endregion
#region Alpha conversion and copying
///
/// Appends arguments to the end of Term, returning a new Term.
///
public Structure AddArguments(params object[] args)
{
if (args == null) throw new ArgumentNullException("args", "Argument list to add to term is null.");
var newArgs = new object[Arguments.Length + args.Length];
Arguments.CopyTo(newArgs, 0);
args.CopyTo(newArgs, Arguments.Length);
return new Structure(Functor, newArgs);
}
///
/// Returns a Term identical to this one except for replacing any variables appearing in oldVars
/// with their corresponding variables from newVars. Returns the original Term object if no oldVars
/// appear within it (e.g. if it's a ground instance).
///
[SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")]
public override object AlphaConvert(List oldVars, LogicVariable[] newVars, PrologContext context, bool evalIndexicals)
{
var newArgs = AlphaConvertArglist(Arguments, oldVars, newVars, context, evalIndexicals);
return newArgs == this.Arguments ? this : new Structure(this.Functor, newArgs);
}
#endregion
#region Macroexpansion
///
/// Macroexpands term (e.g. for definite clause grammars
///
/// Expanded version of term (or original object if no expansion necessary)
public Structure Expand()
{
if (IsFunctor(Symbol.GrammarRule, 2))
return ExpandDCGRule();
return this;
}
// ReSharper disable InconsistentNaming
private Structure ExpandDCGRule()
{
var start = new LogicVariable(StartSym);
if (Argument(1) == null) // name --> [].
return ExpandDCGRuleHead(Argument(0), start, start);
LogicVariable rest;
Structure body = ExpandDCGRuleBody(Argument(1), start, out rest);
return new Structure(Symbol.Implication,
ExpandDCGRuleHead(Argument(0), start, rest),
body);
}
private static readonly Symbol StartSym = Symbol.Intern("Start");
private static readonly Symbol RestSym = Symbol.Intern("Rest");
private static readonly Symbol LSym = Symbol.Intern("L");
private static Structure ExpandDCGRuleBody(object goal, LogicVariable start, out LogicVariable rest)
{
var t = goal as Structure;
if (t != null && t.IsFunctor(Symbol.Comma, 2))
{
var t2 = t.Argument(0) as Structure;
if (t2 != null && t2.IsFunctor(Symbol.PrologListConstructor, 2))
return ExpandDCGLiteral(t2, t.Argument(1), start, out rest);
if (t2 != null && t2.IsFunctor(Symbol.CurlyBrackets, 1))
return ExpandDCGCurlyBracketExpression(t2.Argument(0), t.Argument(1), start, out rest);
var intermediate = new LogicVariable(LSym);
return new Structure(Symbol.Comma,
ExpandDCGRuleGoal(t.Argument(0), start, intermediate),
ExpandDCGRuleBody(t.Argument(1), intermediate, out rest));
}
if (t != null && t.IsFunctor(Symbol.CurlyBrackets, 1))
{
Structure cg = t.Argument(0) as Structure ?? new Structure(Symbol.Call, t.Argument(0));
rest = start;
return cg;
}
return ExpandDCGRuleTrailingGoal(goal, start, out rest);
}
private static readonly Symbol DefiniteClauseCapitalC = Symbol.Intern("C");
private static Structure ExpandDCGLiteral(Structure literal, object afterward, LogicVariable start, out LogicVariable rest)
{
if (literal == null)
return ExpandDCGRuleBody(afterward, start, out rest);
if (literal.Argument(1) == null && afterward == null)
{
rest = new LogicVariable(RestSym);
return new Structure(DefiniteClauseCapitalC, start, literal.Argument(0), rest);
}
var intermediate = new LogicVariable(LSym);
return new Structure(Symbol.Comma,
new Structure(DefiniteClauseCapitalC, start, literal.Argument(0), intermediate),
ExpandDCGLiteral((Structure)literal.Argument(1), afterward, intermediate, out rest));
}
private static Structure ExpandDCGCurlyBracketExpression(object goalExpression, object afterward, LogicVariable start, out LogicVariable rest)
{
var t = goalExpression as Structure;
if (afterward == null)
{
if (t == null)
t = new Structure(Symbol.Call, goalExpression);
rest = start;
return t;
}
if (t != null && t.IsFunctor(Symbol.Comma, 2))
return new Structure(Symbol.Comma,
t.Argument(0),
ExpandDCGCurlyBracketExpression(t.Argument(1), afterward, start, out rest));
return new Structure(Symbol.Comma,
goalExpression,
ExpandDCGRuleBody(afterward, start, out rest));
}
private static Structure ExpandDCGRuleTrailingGoal(object expression, LogicVariable start, out LogicVariable rest)
{
var t = expression as Structure;
var s = expression as Symbol;
if (t != null)
{
if (t.IsFunctor(Symbol.PrologListConstructor, 2))
return ExpandDCGLiteral(t, null, start, out rest);
rest = new LogicVariable(RestSym);
return t.AddArguments(start, rest);
}
if (s != null)
{
rest = new LogicVariable(RestSym);
return new Structure(s, start, rest);
}
throw new ArgumentException("Invalid expression in grammar rule: "+ToStringInPrologFormat(expression), "expression");
}
private static Structure ExpandDCGRuleGoal(object expression, LogicVariable start, LogicVariable rest)
{
var t = expression as Structure;
var s = expression as Symbol;
if (t != null)
{
if (t.IsFunctor(Symbol.PrologListConstructor, 2))
return ExpandDCGLiteral(t, null, start, out rest);
return t.AddArguments(start, rest);
}
if (s != null)
return new Structure(s, start, rest);
throw new ArgumentException("Invalid expression in grammar rule: " + ToStringInPrologFormat(expression), "expression");
}
private static Structure ExpandDCGRuleHead(object expression, LogicVariable start, LogicVariable rest)
{
var t = expression as Structure;
var s = expression as Symbol;
if (t != null)
return t.AddArguments(start, rest);
if (s != null)
return new Structure(s, start, rest);
throw new ArgumentException("Invalid expression in grammar rule: " + ToStringInPrologFormat(expression), "expression");
}
#endregion
#region Prolog-format output
#if !OldPrologWriter
///
/// Renders term in Prolog format
///
public override string ToString()
{
return ISOPrologWriter.WriteToString(this);
}
#else
///
/// Renders term in Prolog format
///
public override string ToString()
{
StringBuilder s = new StringBuilder();
ToStringBuilder(s);
return s.ToString();
}
internal void ToStringBuilder(StringBuilder s)
{
if (IsFunctor(Symbol.PrologListConstructor, 2))
WritePrologList(s);
else if (IsFunctor(Symbol.Comma, 2) || IsFunctor(Symbol.Implication, 2))
{
Write(s, Argument(0));
s.Append(Functor.Name);
s.Append(' ');
Write(s, Argument(1));
}
else if (Arguments.Length == 2 && PrologReader.IsBinaryOperator(Functor))
{
Write(s, Argument(0));
//s.Append(' ');
s.Append(Functor.Name);
//s.Append(' ');
Write(s, Argument(1));
}
else
WriteNormalPrologTerm(s);
}
private void WriteNormalPrologTerm(StringBuilder s)
{
Write(s, Functor);
s.Append('(');
bool firstOne = true;
foreach (var arg in Arguments)
{
if (firstOne)
firstOne = false;
else
s.Append(", ");
WriteAndPossiblyParenthesize(s, arg);
}
s.Append(')');
}
private void WritePrologList(StringBuilder s)
{
s.Append('[');
bool first = true;
object current = Canonicalize(this);
LogicVariable l = current as LogicVariable;
if (l != null && l.IsBound)
current = l.Value;
while (current != null)
{
Structure t = current as Structure;
if (first)
first = false;
else if (t == null)
s.Append(" | ");
else
s.Append(", ");
if (t == null)
{
Write(s, current);
current = null;
}
else
{
if (t.IsFunctor(Symbol.PrologListConstructor, 2))
{
Write(s, t.Argument(0));
current = t.Argument(1);
}
else
{
WriteAndPossiblyParenthesize(s, current);
current = null;
}
}
}
s.Append(']');
}
#endif
#endregion
}
}