Skip to content

Commit 1fb7c4a

Browse files
committed
Added linter
Repl now prints warnings for unreferenced/undefined predicates, and for singleton variables
1 parent d92cfc7 commit 1fb7c4a

File tree

12 files changed

+254
-24
lines changed

12 files changed

+254
-24
lines changed

BotL/BotL.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
</ItemGroup>
4141
<ItemGroup>
4242
<Compile Include="Compiler\Builtin.cs" />
43+
<Compile Include="Compiler\Lint.cs" />
4344
<Compile Include="Engine\ArgumentTypeException.cs" />
4445
<Compile Include="Parser\Call.cs" />
4546
<Compile Include="Engine\CallStatus.cs" />

BotL/Compiler/BindingEnvironment.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ class BindingEnvironment
3535
private readonly Dictionary<Variable, VariableInfo> variableInfoTable = new Dictionary<Variable, VariableInfo>();
3636
private ushort nextIndex;
3737

38+
internal IEnumerable<VariableInfo> Variables
39+
{
40+
get
41+
{
42+
foreach (var pair in variableInfoTable)
43+
yield return pair.Value;
44+
}
45+
}
3846

3947
public void IncrementVoidVariableReferences()
4048
{
@@ -91,7 +99,7 @@ public Variable this[Symbol s]
9199
{
92100
v = new Variable(s);
93101
variableTable[s] = v;
94-
variableInfoTable[v] = new VariableInfo();
102+
variableInfoTable[v] = new VariableInfo(v);
95103
}
96104
return v;
97105
}
@@ -106,7 +114,7 @@ public VariableInfo this[Variable v]
106114
VariableInfo i;
107115
if (variableInfoTable.TryGetValue(v, out i))
108116
return i;
109-
variableInfoTable[v] = i = new VariableInfo();
117+
variableInfoTable[v] = i = new VariableInfo(v);
110118
return i;
111119
}
112120
}

BotL/Compiler/CompiledClause.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
// </copyright>
2323
// --------------------------------------------------------------------------------------------------------------------
2424
#endregion
25+
26+
using System.Collections.Generic;
27+
2528
namespace BotL
2629
{
2730
/// <summary>
@@ -61,5 +64,26 @@ public override string ToString()
6164
{
6265
return $"CompiledClause<{Source}>";
6366
}
67+
68+
#region Warning handling
69+
private readonly Dictionary<CompiledClause, List<string>> WarningTable = new Dictionary<CompiledClause, List<string>>();
70+
internal void AddWarning(string format, params object[] args)
71+
{
72+
var warning = string.Format(format, args);
73+
if (WarningTable.TryGetValue(this, out List<string> warningList))
74+
warningList.Add(warning);
75+
else
76+
WarningTable[this] = new List<string> {warning};
77+
}
78+
79+
internal IEnumerable<string> Warnings
80+
{
81+
get
82+
{
83+
if (WarningTable.TryGetValue(this, out List<string> warnings))
84+
foreach (var w in warnings) yield return w;
85+
}
86+
}
87+
#endregion
6488
}
6589
}

BotL/Compiler/Compiler.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ private static void CompileStream(ExpressionParser expressionParser)
6161
}
6262
}
6363

64-
static HashSet<string> LoadedSourceFiles = new HashSet<string>();
64+
static readonly HashSet<string> LoadedSourceFiles = new HashSet<string>();
6565
static string CanonicalizeSourceName(object name)
6666
{
6767
var path = name as string;
@@ -203,20 +203,35 @@ internal static BindingEnvironment CompileInternal(object assertion, bool forceV
203203
if (forceVoidVariables)
204204
e.IncrementVoidVariableReferences();
205205
AllocateVariables(assertion, e);
206+
207+
CompiledClause clause;
206208
if (Call.IsFunctor(assertion, Symbol.Implication, 2))
207209
{
208210
var implication = assertion as Call;
209211
// ReSharper disable once PossibleNullReferenceException
210212
var head = implication.Arguments[0];
211213
var body = implication.Arguments[1];
212-
Predicate.AddClause(new PredicateIndicator(head), CompileRule(assertion, head, body, e));
214+
clause = CompileRule(assertion, head, body, e);
215+
Predicate.AddClause(new PredicateIndicator(head), clause);
213216
}
214217
else
215218
{
216-
Predicate.AddClause(new PredicateIndicator(assertion), CompileFact(assertion, e));
219+
clause = CompileFact(assertion, e);
220+
Predicate.AddClause(new PredicateIndicator(assertion), clause);
217221
}
222+
GenerateSingletonWarnings(clause, e);
218223
return e;
219224
}
225+
226+
private static void GenerateSingletonWarnings(CompiledClause clause, BindingEnvironment e)
227+
{
228+
foreach (var v in e.Variables)
229+
{
230+
if (v.IsSingleton && !v.Variable.IsGenerated && !v.Variable.Name.Name.StartsWith("_"))
231+
clause.AddWarning("Singleton variable {0} - might be a typo", v.Variable.Name);
232+
}
233+
}
234+
220235
#endregion
221236

222237
#region Compiling facts (rules with no body)

BotL/Compiler/Lint.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Text;
7+
8+
namespace BotL.Compiler
9+
{
10+
internal static class Lint
11+
{
12+
public static void Check(TextWriter output)
13+
{
14+
var refs = AllRulePredicateReferences();
15+
WarnUndefined(output, refs);
16+
WarnUnreferenced(output, refs);
17+
PrintClauseWarnings(output);
18+
}
19+
20+
private static void PrintClauseWarnings(TextWriter output)
21+
{
22+
foreach (var p in KB.AllRulePredicates)
23+
if (p.IsUserDefined)
24+
foreach (var c in p.Clauses)
25+
foreach (var w in c.Warnings)
26+
Warn(output, "In rule {0}: {1}", c.Source, w);
27+
}
28+
29+
private static void WarnUnreferenced(TextWriter output, Dictionary<Predicate, List<Predicate>> refs)
30+
{
31+
foreach (var p in KB.AllRulePredicates)
32+
if (p.IsUserDefined && !refs.ContainsKey(p))
33+
Warn(output, "unused predicate {0}", p);
34+
}
35+
36+
private static void WarnUndefined(TextWriter output, Dictionary<Predicate, List<Predicate>> refs)
37+
{
38+
foreach (var pair in refs)
39+
{
40+
var predicate = pair.Key;
41+
var referrers = pair.Value;
42+
if (!predicate.IsDefined)
43+
foreach (var referrer in referrers)
44+
Warn(output, "undefined predicate {0} referenced by {1}", predicate, referrer);
45+
}
46+
}
47+
48+
private static void Warn(TextWriter output, string format, params object[] args)
49+
{
50+
output.Write("Warning: ");
51+
output.WriteLine(format, args);
52+
}
53+
54+
static Dictionary<Predicate, List<Predicate>> AllRulePredicateReferences()
55+
{
56+
var result = new Dictionary<Predicate, List<Predicate>>();
57+
58+
void AddReference(Predicate referrer, Predicate referee)
59+
{
60+
// ReSharper disable once CollectionNeverQueried.Local
61+
if (result.TryGetValue(referee, out List<Predicate> referers))
62+
referers.Add(referrer);
63+
else
64+
result[referee] = new List<Predicate> {referrer};
65+
}
66+
67+
foreach (var rp in KB.AllRulePredicates)
68+
{
69+
foreach (var p in rp.ReferencedUserPredicates)
70+
AddReference(rp, p);
71+
}
72+
return result;
73+
}
74+
}
75+
}

BotL/Compiler/Macros.cs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,21 @@ public static void DeclareMacros()
5858
Or(And(generator,
5959
new Call("%maximize_update_and_repeat", result, score)),
6060
new Call("nonvar", result))));
61+
DeclareMacro("arg_min", 3,
62+
(arg, scoreExpression, result) => {
63+
var score = Variable.MakeGenerated("*score*");
64+
return new Call("arg_min", arg, score, new Call(Symbol.Equal, score, scoreExpression), result);
65+
});
66+
Functions.DeclareFunction("arg_min", 2);
67+
DeclareMacro("arg_max", 3,
68+
(arg, scoreExpression, result) => {
69+
var score = Variable.MakeGenerated("*score*");
70+
return new Call("arg_max", arg, score, new Call(Symbol.Equal, score, scoreExpression), result);
71+
});
72+
Functions.DeclareFunction("arg_max", 2);
6173
DeclareMacro("arg_min", 4,
6274
(arg, score, generator, result) => {
63-
var temp = Variable.MakeGenerated("*temp*");
75+
var temp = Variable.MakeGenerated("*MinScore*");
6476
return Or(And(GenerateArgmaxInit(result),
6577
new Call("%init", temp),
6678
new Call("%init", score),
@@ -70,23 +82,31 @@ public static void DeclareMacros()
7082
Symbol.Fail),
7183
new Call("nonvar", result));
7284
});
85+
Functions.DeclareFunction("arg_min", 3);
7386
DeclareMacro("arg_max", 4,
7487
(arg, score, generator, result) => {
75-
var temp = Variable.MakeGenerated("*temp*");
88+
var temp = Variable.MakeGenerated("*MaxScore*");
7689
return Or(And(GenerateArgmaxInit(result),
7790
new Call("%init", temp),
7891
new Call("%init", score),
7992
generator,
8093
new Call("%maximize_update", temp, score),
8194
GenerateArgmaxUpdate(result, arg),
8295
Symbol.Fail),
83-
new Call("nonvar", result));
96+
new Call("nonvar", temp));
8497
});
98+
Functions.DeclareFunction("arg_max", 3);
8599
DeclareMacro("sum", 3,
86-
(score, generator, result) => And(new Call("%init_zero", result),
87-
Or(And(generator,
88-
new Call("%sum_update_and_repeat", result, score)),
89-
Symbol.TruePredicate)));
100+
(score, generator, result) =>
101+
{
102+
var rtemp = Variable.MakeGenerated("*Sum*");
103+
return And(new Call("%init_zero", rtemp),
104+
Or(And(generator,
105+
new Call("%sum_update_and_repeat", rtemp, score)),
106+
And(new Call("nonvar", rtemp),
107+
new Call(Symbol.Equal, rtemp, result))));
108+
});
109+
Functions.DeclareFunction("sum", 2);
90110

91111
DeclareMacro("set", 1, arg =>
92112
{

BotL/Compiler/Transform.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@ internal static object TransformTopLevel(object assertion)
4848
if (c == null)
4949
return assertion;
5050
if (!c.IsFunctor(Symbol.Implication, 2))
51-
return TransformFact(assertion);
51+
{
52+
assertion = TransformFact(assertion);
53+
if (Call.IsFunctor(assertion, Symbol.Implication, 2))
54+
return TransformTopLevel(assertion);
55+
return assertion;
56+
}
5257
// It's a rule.
5358
var args = c.Arguments;
5459
var head = args[0];
@@ -161,7 +166,7 @@ private static object HoistArguments(object term, bool fullyHoist = false)
161166
{
162167
if (fullyHoist && !(hoisted is Variable))
163168
{
164-
var t = Variable.MakeGenerated("*T*");
169+
var t = Variable.MakeGenerated("*Hoisted*");
165170
residue = new Call(Symbol.Comma, residue, new Call(Symbol.Equal, t, hoisted));
166171
hoisted = t;
167172
}

BotL/Compiler/VariableInfo.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ namespace BotL.Compiler
2929
/// </summary>
3030
class VariableInfo
3131
{
32+
public Variable Variable;
3233
/// <summary>
3334
/// How many times this variable appears in the head
3435
/// </summary>
@@ -45,7 +46,12 @@ class VariableInfo
4546
/// Position within the run-time environment for the call
4647
/// </summary>
4748
public int EnvironmentIndex = -1;
48-
49+
50+
public VariableInfo(Variable variable)
51+
{
52+
Variable = variable;
53+
}
54+
4955
/// <summary>
5056
/// How many times this variable appears in the rule or fact.
5157
/// </summary>

BotL/Engine/Engine.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -838,7 +838,7 @@ bool DoBuiltin(Predicate predicate, byte[] code, ref ushort pc, ushort frameBase
838838
case Opcode.CBuiltin:
839839
if (!DoBuiltin(goalPredicate, goalCode, ref goalPc, EnvironmentStack[goalFrame].Base))
840840
goto fail;
841-
break;
841+
goto continuationLoop;
842842

843843
case Opcode.CCut:
844844
cTop = EnvironmentStack[goalFrame].CallerCTop;

BotL/Engine/Predicate.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
// --------------------------------------------------------------------------------------------------------------------
2424
#endregion
2525
using System;
26+
using System.Collections;
2627
using System.Collections.Generic;
2728
using System.Diagnostics;
2829

@@ -106,6 +107,56 @@ internal Predicate(Symbol name, int arity, Compiler.Compiler.PredicateImplementa
106107
internal readonly Table Table;
107108

108109
public bool IsSpecial => (PrimopImplementation != null || Table != null);
110+
public bool IsRulePredicate => !IsSpecial;
111+
public bool IsDefined => IsLocked || FirstClause != null;
112+
113+
internal IEnumerable<CompiledClause> Clauses
114+
{
115+
get
116+
{
117+
if (FirstClause != null)
118+
yield return FirstClause;
119+
120+
if (ExtraClauses != null)
121+
{
122+
foreach (var c in ExtraClauses)
123+
yield return c;
124+
}
125+
}
126+
}
127+
128+
internal IEnumerable<Predicate> ReferencedPredicates
129+
{
130+
get
131+
{
132+
if (objectConstants == null)
133+
yield break;
134+
135+
foreach (var c in objectConstants)
136+
{
137+
var p = c as Predicate;
138+
if (p != null) yield return p;
139+
}
140+
}
141+
}
142+
143+
internal IEnumerable<Predicate> ReferencedUserPredicates
144+
{
145+
get
146+
{
147+
if (objectConstants == null)
148+
yield break;
149+
150+
foreach (var c in objectConstants)
151+
{
152+
var p = c as Predicate;
153+
if (p != null && p.IsUserDefined) yield return p;
154+
}
155+
}
156+
}
157+
158+
public bool IsUserDefined => (IsRulePredicate && !IsLocked) || IsTable;
159+
public bool IsTable => Table != null;
109160

110161
#endregion
111162

0 commit comments

Comments
 (0)