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 )
{
2020-05-05 03:21:37 +02:00
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" +
2020-05-05 03:21:37 +02:00
$" 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
}
}
}
2020-05-05 03:21:37 +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 ) ;
2020-05-05 03:21:37 +02:00
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-06-10 01:02:17 +02:00
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 ;
}
2020-06-10 01:02:17 +02:00
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 )
{
2020-06-17 17:23:48 +02:00
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 ;
}
2020-05-05 03:21:37 +02:00
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 = "" ;
2020-05-05 03:21:37 +02:00
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-06-10 01:02:17 +02:00
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>
2020-06-15 18:09:41 +02:00
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 ] ;
2020-06-15 18:09:41 +02:00
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 ;
} ) ;
2020-06-15 18:09:41 +02:00
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
}
}