AspectedRouting/AspectedRouting/Language/Analysis.cs

512 lines
18 KiB
C#
Raw Normal View History

2020-04-30 17:23:44 +02:00
using System;
using System.Collections.Generic;
using System.Linq;
2020-05-02 13:09:49 +02:00
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
using Type = AspectedRouting.Language.Typ.Type;
2020-04-30 17:23:44 +02:00
2020-05-02 13:09:49 +02:00
namespace AspectedRouting.Language
2020-04-30 17:23:44 +02:00
{
public static class Analysis
{
public static string GenerateFullOutputCsv(Context c, IExpression e)
{
var possibleTags = e.PossibleTags();
var defaultValues = new List<string>
{
"0",
"30",
"50",
"yes",
"no",
"SomeName"
};
Console.WriteLine(e);
var keys = e.PossibleTags().Keys.ToList();
2020-05-02 13:09:49 +02:00
2020-04-30 17:23:44 +02:00
var results = possibleTags.OnAllCombinations(
tags =>
{
Console.WriteLine(tags.Pretty());
return (new Apply(e, new Constant(tags)).Evaluate(c), tags);
}, defaultValues).ToList();
var csv = "result, " + string.Join(", ", keys) + "\n";
foreach (var (result, tags) in results)
{
csv += result + ", " +
string.Join(", ",
keys.Select(key =>
{
if (tags.ContainsKey(key))
{
return tags[key];
}
else
{
return "";
}
}));
csv += "\n";
}
return csv;
}
public static IEnumerable<T> OnAllCombinations<T>(this Dictionary<string, List<string>> possibleTags,
Func<Dictionary<string, string>, T> f, List<string> defaultValues)
{
var newDict = new Dictionary<string, List<string>>();
foreach (var (key, value) in possibleTags)
{
if (value.Count == 0)
{
// This value is a list of possible values, e.g. a double
// We replace them with various other
newDict[key] = defaultValues;
}
else
{
newDict[key] = value;
}
}
possibleTags = newDict;
var keys = possibleTags.Keys.ToList();
var currentKeyIndex = new int[possibleTags.Count];
for (int i = 0; i < currentKeyIndex.Length; i++)
{
currentKeyIndex[i] = -1;
}
bool SelectNext()
{
var j = 0;
while (j < currentKeyIndex.Length)
{
currentKeyIndex[j]++;
if (currentKeyIndex[j] ==
possibleTags[keys[j]].Count)
{
// This index rolls over
currentKeyIndex[j] = -1;
j++;
}
else
{
return true;
}
}
return false;
}
do
{
var tags = new Dictionary<string, string>();
for (int i = 0; i < keys.Count(); i++)
{
var key = keys[i];
var j = currentKeyIndex[i];
if (j >= 0)
{
var value = possibleTags[key][j];
tags.Add(key, value);
}
}
yield return f(tags);
} while (SelectNext());
}
2020-05-02 13:09:49 +02:00
public static Dictionary<string, (List<Type> Types, string inFunction)> UsedParameters(
2020-04-30 17:23:44 +02:00
this ProfileMetaData profile, Context context)
{
var parameters = new Dictionary<string, (List<Type> Types, string usageLocation)>();
2020-04-30 17:23:44 +02:00
2020-05-04 17:41:48 +02:00
2020-04-30 17:23:44 +02:00
void AddParams(IExpression e, string inFunction)
{
var parms = e.UsedParameters();
foreach (var param in parms)
{
if (parameters.TryGetValue(param.ParamName, out var typesOldUsage))
{
var (types, oldUsage) = typesOldUsage;
var unified = types.SpecializeTo(param.Types);
if (unified == null)
{
throw new ArgumentException("Inconsistent parameter usage: the paremeter " +
param.ParamName + " is used\n" +
$" in {oldUsage} as {string.Join(",", types)}\n" +
$" in {inFunction} as {string.Join(",", param.Types)}\n" +
2020-04-30 17:23:44 +02:00
$"which can not be unified");
}
}
else
{
2020-05-02 13:09:49 +02:00
parameters[param.ParamName] = (param.Types.ToList(), inFunction);
2020-04-30 17:23:44 +02:00
}
}
}
AddParams(profile.Access, "profile definition for " + profile.Name + ".access");
AddParams(profile.Oneway, "profile definition for " + profile.Name + ".oneway");
AddParams(profile.Speed, "profile definition for " + profile.Name + ".speed");
2020-04-30 17:23:44 +02:00
2020-05-02 13:09:49 +02:00
foreach (var (key, expr) in profile.Priority)
{
AddParams(new Parameter(key), profile.Name + ".priority.lefthand");
AddParams(expr, profile.Name + ".priority");
}
2020-05-04 17:41:48 +02:00
var calledFunctions = profile.CalledFunctionsRecursive(context).Values
.SelectMany(ls => ls).ToHashSet();
foreach (var calledFunction in calledFunctions)
2020-04-30 17:23:44 +02:00
{
2020-05-04 17:41:48 +02:00
var func = context.GetFunction(calledFunction);
if (func is AspectMetadata meta && meta.ProfileInternal)
{
continue;
}
AddParams(func, "function " + calledFunction);
2020-04-30 17:23:44 +02:00
}
2020-05-04 17:41:48 +02:00
2020-04-30 17:23:44 +02:00
return parameters;
}
public static HashSet<Parameter> UsedParameters(this IExpression e)
{
var result = new HashSet<Parameter>();
e.Visit(expr =>
{
if (expr is Parameter p)
{
result.Add(p);
}
return true;
});
return result;
}
2020-05-04 17:41:48 +02:00
public static Dictionary<string, List<string>> CalledFunctionsRecursive(this ProfileMetaData profile,
Context c)
{
// Read as: this function calls the value-function
var result = new Dictionary<string, List<string>>();
var calledFunctions = new Queue<string>();
void ScanExpression(IExpression e, string inFunction)
{
result.Add(inFunction, new List<string>());
e.Visit(x =>
{
if (x is FunctionCall fc)
{
result[inFunction].Add(fc.CalledFunctionName);
if (!result.ContainsKey(fc.CalledFunctionName))
{
calledFunctions.Enqueue(fc.CalledFunctionName);
}
}
return true;
});
}
ScanExpression(profile.Access, profile.Name + ".access");
ScanExpression(profile.Oneway, profile.Name + ".oneway");
ScanExpression(profile.Speed, profile.Name + ".speed");
foreach (var (key, expr) in profile.Priority)
{
ScanExpression(new Parameter(key), $"{profile.Name}.priority.{key}.lefthand");
ScanExpression(expr, $"{profile.Name}.priority.{key}");
}
while (calledFunctions.TryDequeue(out var calledFunction))
{
var func = c.GetFunction(calledFunction);
ScanExpression(func, calledFunction);
}
return result;
}
2020-04-30 17:23:44 +02:00
public static string TypeBreakdown(this IExpression e)
{
var text = "";
e.Visit(x =>
{
text += $"\n\n{x}\n : {string.Join("\n : ", x.Types)}";
return true;
});
return text;
}
2020-05-02 13:09:49 +02:00
public static void SanityCheckProfile(this ProfileMetaData pmd, Context context)
2020-04-30 17:23:44 +02:00
{
var defaultParameters = pmd.DefaultParameters.Keys.Select(k => k.TrimStart('#'));
2020-04-30 17:23:44 +02:00
2020-05-04 17:41:48 +02:00
var usedMetadata = pmd.UsedParameters(context);
string MetaList(IEnumerable<string> paramNames)
{
var metaInfo = "";
foreach (var paramName in paramNames)
{
var _ = usedMetadata.TryGetValue(paramName, out var inFunction) ||
usedMetadata.TryGetValue('#' + paramName, out inFunction);
metaInfo += $"\n - {paramName} (used in {inFunction.inFunction})";
}
return metaInfo;
}
var usedParameters = usedMetadata.Keys.Select(key => key.TrimStart('#')).ToList();
2020-04-30 17:23:44 +02:00
var diff = usedParameters.ToHashSet().Except(defaultParameters).ToList();
if (diff.Any())
{
2020-05-04 17:41:48 +02:00
throw new ArgumentException("No default value set for parameter: " + MetaList(diff));
2020-04-30 17:23:44 +02:00
}
2020-05-02 13:09:49 +02:00
var unused = defaultParameters.Except(usedParameters);
if (unused.Any())
{
throw new ArgumentException("A default value is set for parameter, but it is unused: " +
string.Join(", ", unused));
}
2020-05-04 17:41:48 +02:00
var paramsUsedInBehaviour = new HashSet<string>();
2020-05-02 13:09:49 +02:00
foreach (var (behaviourName, behaviourParams) in pmd.Behaviours)
2020-04-30 17:23:44 +02:00
{
var sum = 0.0;
2020-05-02 13:09:49 +02:00
var explanation = "";
paramsUsedInBehaviour.UnionWith(behaviourParams.Keys.Select(k => k.Trim('#')));
2020-05-02 13:09:49 +02:00
foreach (var (paramName, _) in pmd.Priority)
2020-04-30 17:23:44 +02:00
{
2020-05-02 13:09:49 +02:00
if (!pmd.DefaultParameters.ContainsKey(paramName))
{
throw new ArgumentException(
$"The behaviour {behaviourName} uses a parameter for which no default is set: {paramName}");
}
2020-05-04 17:41:48 +02:00
2020-05-02 13:09:49 +02:00
if (!behaviourParams.TryGetValue(paramName, out var weight))
2020-04-30 17:23:44 +02:00
{
2020-05-02 13:09:49 +02:00
explanation += $"\n - {paramName} = default (not set)";
2020-04-30 17:23:44 +02:00
continue;
}
2020-05-02 13:09:49 +02:00
var weightObj = weight.Evaluate(context);
if (!(weightObj is double d))
2020-04-30 17:23:44 +02:00
{
2020-05-04 17:41:48 +02:00
throw new ArgumentException(
$"The parameter {paramName} is not a numeric value in profile {behaviourName}");
2020-04-30 17:23:44 +02:00
}
sum += Math.Abs(d);
2020-05-02 13:09:49 +02:00
explanation += $"\n - {paramName} = {d}";
2020-04-30 17:23:44 +02:00
}
if (Math.Abs(sum) < 0.0001)
{
2020-05-02 13:09:49 +02:00
throw new ArgumentException("Profile " + behaviourName +
": the summed parameters to calculate the weight are zero or very low:" +
explanation);
2020-04-30 17:23:44 +02:00
}
}
2020-05-04 17:41:48 +02:00
var defaultOnly = defaultParameters.Except(paramsUsedInBehaviour).ToList();
if (defaultOnly.Any())
{
Console.WriteLine(
$"[{pmd.Name}] WARNING: Some parameters only have a default value: {string.Join(", ", defaultOnly)}");
}
2020-04-30 17:23:44 +02:00
}
public static void SanityCheck(this IExpression e)
{
e.Visit(expr =>
{
var order = new List<IExpression>();
var mapping = new List<IExpression>();
2020-05-02 13:09:49 +02:00
if (Deconstruct.UnApply(
Deconstruct.UnApply(Deconstruct.IsFunc(Funcs.FirstOf), Deconstruct.Assign(order)),
Deconstruct.Assign(mapping)
2020-04-30 17:23:44 +02:00
).Invoke(expr))
{
var expectedKeys = ((IEnumerable<object>) order.First().Evaluate(null)).Select(o =>
{
if (o is IExpression x)
{
return (string) x.Evaluate(null);
}
return (string) o;
})
.ToHashSet();
var actualKeys = mapping.First().PossibleTags().Keys;
var missingInOrder = actualKeys.Where(key => !expectedKeys.Contains(key)).ToList();
var missingInMapping = expectedKeys.Where(key => !actualKeys.Contains(key)).ToList();
if (missingInOrder.Any() || missingInMapping.Any())
{
var missingInOrderMsg = "";
if (missingInOrder.Any())
{
missingInOrderMsg = $"The order misses keys {string.Join(",", missingInOrder)}\n";
}
var missingInMappingMsg = "";
if (missingInMapping.Any())
{
missingInMappingMsg =
$"The mapping misses mappings for keys {string.Join(", ", missingInMapping)}\n";
}
throw new ArgumentException(
"Sanity check failed: the specified order of firstMatchOf contains to little or to much keys:\n" +
missingInOrderMsg + missingInMappingMsg
);
}
}
return true;
});
}
/// <summary>
/// Returns which tags are used in this calculation
///
/// </summary>
/// <param name="e"></param>
/// <returns>A dictionary containing all possible values. An entry with an empty list indicates a wildcard</returns>
public static Dictionary<string, List<string>> PossibleTags(this IExpression e)
{
var result = new Dictionary<string, List<string>>();
var mappings = new List<Mapping>();
e.Visit(x =>
{
/*
var networkMapping = new List<IExpression>();
if (Deconstruct.UnApply(
IsFunc(Funcs.MustMatch),
Assign(networkMapping)
).Invoke(x))
{
var possibleTags = x.PossibleTags();
result.
return false;
}*/
if (x is Mapping m)
{
mappings.Add(m);
}
return true;
});
if (mappings.Count == 0)
{
return null;
}
// Visit will have the main mapping at the first position
var rootMapping = mappings[0];
foreach (var (key, expr) in rootMapping.StringToResultFunctions)
{
var values = new List<string>();
expr.Visit(x =>
{
if (x is Mapping m)
{
values.AddRange(m.StringToResultFunctions.Keys);
}
return true;
});
result[key] = values;
}
return result;
}
2020-05-02 13:09:49 +02:00
public static Dictionary<string, IExpression> MembershipMappingsFor(ProfileMetaData profile, Context context)
{
var calledFunctions = profile.Priority.Values.ToHashSet();
calledFunctions.Add(profile.Speed);
calledFunctions.Add(profile.Access);
calledFunctions.Add(profile.Oneway);
var calledFunctionQueue = new Queue<string>();
var alreadyAnalysedFunctions = new HashSet<string>();
var memberships = new Dictionary<string, IExpression>();
void HandleExpression(IExpression e, string calledIn)
{
e.Visit(f =>
{
var mapping = new List<IExpression>();
if (Deconstruct.UnApply(Deconstruct.IsFunc(Funcs.MemberOf),
Deconstruct.Assign(mapping)
).Invoke(f))
{
memberships.Add(calledIn, mapping.First());
return false;
}
if (f is FunctionCall fc)
{
calledFunctionQueue.Enqueue(fc.CalledFunctionName);
}
return true;
});
}
foreach (var e in calledFunctions)
{
HandleExpression(e, "profile_root");
}
while (calledFunctionQueue.TryDequeue(out var functionName))
{
if (alreadyAnalysedFunctions.Contains(functionName))
{
continue;
}
alreadyAnalysedFunctions.Add(functionName);
var functionImplementation = context.GetFunction(functionName);
HandleExpression(functionImplementation, functionName);
}
return memberships;
}
2020-04-30 17:23:44 +02:00
}
}