AspectedRouting/AspectedRouting/Language/Analysis.cs

484 lines
17 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
{
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>>();
2020-05-04 17:41:48 +02:00
var calledFunctions = new Queue<string>();
void ScanExpression(IExpression e, string inFunction)
{
2020-05-11 13:40:14 +02:00
if (!result.ContainsKey(inFunction))
{
result.Add(inFunction, new List<string>());
}
2020-05-04 17:41:48 +02:00
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;
}
public static (HashSet<string> parameterName, HashSet<string> calledFunctionNames) DirectlyAndInderectlyCalled(
this List<IExpression> exprs, Context ctx)
{
var parameters = new HashSet<string>();
var dependencies = new HashSet<string>();
var queue = new Queue<IExpression>();
exprs.ForEach(queue.Enqueue);
while (queue.TryDequeue(out var next))
{
var (p, deps) = next.DirectlyCalled();
parameters.UnionWith(p);
var toCheck = deps.Except(dependencies);
dependencies.UnionWith(deps);
foreach (var fName in toCheck)
{
queue.Enqueue(ctx.GetFunction(fName));
}
}
return (parameters, dependencies);
}
/// <summary>
/// Generates an overview of the dependencies of the expression, both which parameters it needs and what other functions (builtin or defined) it needs.
/// </summary>
/// <param name="expr"></param>
/// <returns></returns>
public static (HashSet<string> parameterName, HashSet<string> calledFunctionNames) DirectlyCalled(
this IExpression expr)
{
var parameters = new HashSet<string>();
var dependencies = new HashSet<string>();
expr.Visit(e =>
{
if (e is FunctionCall fc)
{
dependencies.Add(fc.CalledFunctionName);
}
if (e is Parameter p)
{
parameters.Add(p.ParamName);
}
return true;
});
return (parameters, dependencies);
}
2020-04-30 17:23:44 +02:00
public static string TypeBreakdown(this IExpression e)
{
return e.ToString() + " : "+string.Join(" ; ", e.Types);
2020-04-30 17:23:44 +02:00
}
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
{
2020-09-30 13:35:30 +02:00
var defaultParameters = pmd.DefaultParameters.Keys
.Select(k => k.TrimStart('#')).ToList();
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())
{
2020-06-16 17:37:32 +02:00
Console.WriteLine("[WARNING] A default value is set for parameter, but it is unused: " +
2020-05-02 13:09:49 +02:00
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;
});
}
2020-05-28 19:00:15 +02:00
public static Dictionary<string, HashSet<string>> PossibleTags(this IEnumerable<IExpression> exprs)
{
var usedTags = new Dictionary<string, HashSet<string>>();
foreach (var expr in exprs)
{
var possible = expr.PossibleTags();
if (possible == null)
{
continue;
}
2020-05-28 19:00:15 +02:00
foreach (var (key, values) in possible)
{
if (!usedTags.TryGetValue(key, out var collection))
{
collection = new HashSet<string>();
usedTags[key] = collection;
}
foreach (var v in values)
{
collection.Add(v);
}
}
}
return usedTags;
}
2020-04-30 17:23:44 +02:00
/// <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, HashSet<string>> PossibleTags(this IExpression e)
2020-04-30 17:23:44 +02:00
{
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];
var result = new Dictionary<string, HashSet<string>>();
2020-04-30 17:23:44 +02:00
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.ToHashSet();
2020-04-30 17:23:44 +02:00
}
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
}
}