#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 Object = UnityEngine.Object;
namespace Prolog
{
///
/// Static checker for Prolog code. Runs over KBs looking for inconsistencies.
///
internal class PrologChecker
{
public static void Check()
{
new PrologChecker().ReallyCheck();
}
void ReallyCheck()
{
WalkKB(KnowledgeBase.Global);
foreach (var component in Object.FindObjectsOfType())
{
WalkKB(component.KnowledgeBase);
}
foreach (var pair in checkerInfoTable)
{
var checkerInfo = pair.Value;
if (!checkerInfo.Referenced)
{
KnowledgeBaseRule rule = checkerInfo.DefiningRule;
PredicateInfo global = KnowledgeBase.Global.CheckForPredicateInfo(new PredicateIndicator(rule.HeadFunctor, rule.HeadArity));
if (global == null || !global.External)
rule.PrintWarning("{0}/{1} is never used.", rule.HeadFunctor, rule.HeadArity);
}
}
}
///
/// Scans all rules for references to undefined predicates.
///
public void WalkKB(KnowledgeBase kb)
{
foreach (var predicate in kb.Predicates)
if (predicate != null && predicate.Entries != null)
{
if (predicate.Public || predicate.Shadow)
MarkReferenced(predicate);
bool firstOne = true;
foreach (var kbEntry in predicate.Entries)
{
var rule = kbEntry as KnowledgeBaseRule;
if (rule != null)
{
if (firstOne)
{
MarkDefined(predicate, rule);
firstOne = false;
}
WalkRule(kb, rule);
if (predicate.HigherOrderArguments != null)
foreach (var arg in predicate.HigherOrderArguments)
WalkGoal(kb, rule, rule.HeadArgs[arg]);
}
}
}
}
private void WalkRule(KnowledgeBase kb, KnowledgeBaseRule rule)
{
foreach (Structure goal in rule.BodyGoals)
WalkGoal(kb, rule, goal);
}
private void WalkGoal(KnowledgeBase kb, KnowledgeBaseRule rule, object goal)
{
goal = Term.Deref(goal);
var atom = goal as Symbol;
if (atom != null)
{
var p = new PredicateIndicator(atom, 0);
if (PrologPrimitives.IsDefined(p))
return;
var predicate = kb.CheckForPredicateInfo(p);
if (predicate == null)
rule.PrintWarning("undefined predicate {0}", p);
else
MarkReferenced(predicate);
}
else
{
var s = goal as Structure;
if (s != null)
WalkGoal(kb, rule, s);
else if (!(goal is LogicVariable) && !(goal is bool))
rule.PrintWarning("malformed goal: {0}", goal);
}
}
private void WalkGoal(KnowledgeBase kb, KnowledgeBaseRule rule, Structure goal)
{
var predicateIndicator = goal.PredicateIndicator;
Symbol functor = goal.Functor;
int arity = goal.Arity;
switch (functor.Name)
{
case "begin":
foreach (var arg in goal.Arguments)
WalkGoal(kb, rule, arg);
break;
case "once":
case "check":
case "randomize":
case "not":
case "\\+":
if (arity == 1)
{
WalkGoal(kb, rule, goal.Argument(0));
}
else
WarnUndefined(rule, functor, arity);
break;
case ",":
case ";":
case "->":
if (arity == 2)
{
WalkGoal(kb, rule, goal.Argument(0));
WalkGoal(kb, rule, goal.Argument(1));
}
else
WarnUndefined(rule, functor, arity);
break;
case "call":
case "maplist":
if (arity < 1)
WarnUndefined(rule, functor, arity);
else
{
object goalToCall = goal.Argument(0);
var goalToCallAsStructure = goalToCall as Structure;
if (goalToCallAsStructure != null)
{
var newArgs = new object[arity - 1 + goalToCallAsStructure.Arity];
goalToCallAsStructure.Arguments.CopyTo(newArgs, 0);
WalkGoal(kb, rule, new Structure(goalToCallAsStructure.Functor, newArgs));
}
else
{
var call = goalToCall as Symbol;
if (call != null)
{
this.WalkGoal(kb, rule, new Structure(call, new object[arity - 1]));
}
}
}
break;
case "arg_min":
case "arg_max":
if (arity == 3)
{
WalkGoal(kb, rule, goal.Argument(2));
}
else
WarnUndefined(rule, functor, arity);
break;
case "find_all":
if (arity == 3)
{
WalkGoal(kb, rule, goal.Argument(1));
}
else
WarnUndefined(rule, functor, arity);
break;
default:
if (PrologPrimitives.IsDefined(predicateIndicator))
{
var arglist = PrologPrimitives.Arglist(predicateIndicator.Functor);
for (int i = 0; i < Math.Min(predicateIndicator.Arity,arglist.Count); i++)
{
var argSym = arglist[i] as Symbol;
if (argSym != null)
{
var arg = argSym.Name;
if (arg[0] == ':')
WalkGoal(kb, rule, goal.Argument(i));
else if (arg == "..." && arglist[i - 1] is string && ((string)arglist[i - 1])[0] == ':')
{
// Predicate accepts a rest arg of goals
for (int j = i; j < predicateIndicator.Arity; j++)
WalkGoal(kb, rule, goal.Argument(j));
}
}
}
}
else
{
var predicate = kb.CheckForPredicateInfo(predicateIndicator);
if (predicate == null)
WarnUndefined(rule, functor, arity);
else
{
MarkReferenced(predicate);
if (predicate.HigherOrderArguments != null)
foreach (int argIndex in predicate.HigherOrderArguments)
WalkGoal(kb, rule, goal.Argument(argIndex));
}
}
break;
}
}
private void WarnUndefined(KnowledgeBaseRule rule,Symbol functor,int arity)
{
rule.PrintWarning("{0}/{1} undefined", functor, arity);
}
private readonly Dictionary checkerInfoTable =
new Dictionary();
private class CheckerInfo
{
public KnowledgeBaseRule DefiningRule;
public bool Referenced;
}
private CheckerInfo PredicateCheckerInfo(PredicateInfo predicate)
{
CheckerInfo result;
if (checkerInfoTable.TryGetValue(predicate, out result))
return result;
return checkerInfoTable[predicate] = new CheckerInfo();
}
private void MarkDefined(PredicateInfo predicate, KnowledgeBaseRule rule)
{
CheckerInfo predicateCheckerInfo = PredicateCheckerInfo(predicate);
if (predicateCheckerInfo.DefiningRule == null)
predicateCheckerInfo.DefiningRule = rule;
}
private void MarkReferenced(PredicateInfo predicate)
{
PredicateCheckerInfo(predicate).Referenced = true;
}
}
}