Initial commit
This commit is contained in:
parent
d6895e025c
commit
2c2a28d30a
105 changed files with 10038 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.idea/*
|
||||||
|
**/bin/**
|
||||||
|
**/obj/**
|
343
AspectedRouting/Analysis.cs
Normal file
343
AspectedRouting/Analysis.cs
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Functions;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using static AspectedRouting.Deconstruct;
|
||||||
|
|
||||||
|
namespace AspectedRouting
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<string, (IEnumerable<Typ.Type> Types, string inFunction)> UsedParameters(
|
||||||
|
this ProfileMetaData profile, Context context)
|
||||||
|
{
|
||||||
|
var parameters = new Dictionary<string, (IEnumerable<Typ.Type> Types, string inFunction)>();
|
||||||
|
|
||||||
|
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 function {oldUsage} as {string.Join(",", types)}\n" +
|
||||||
|
$" in function {inFunction} as {string.Join(",", param.Types)}\n" +
|
||||||
|
$"which can not be unified");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parameters[param.ParamName] = (param.Types, inFunction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AddParams(profile.Access, profile.Name + ".access");
|
||||||
|
AddParams(profile.Oneway, profile.Name + ".oneway");
|
||||||
|
AddParams(profile.Speed, profile.Name + ".speed");
|
||||||
|
|
||||||
|
foreach (var (name, expr) in context.DefinedFunctions)
|
||||||
|
{
|
||||||
|
AddParams(expr, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SanityCheckProfile(this ProfileMetaData pmd)
|
||||||
|
{
|
||||||
|
var defaultParameters = pmd.DefaultParameters.Keys;
|
||||||
|
|
||||||
|
var usedParameters = pmd.UsedParameters(new Context()).Keys.Select(key => key.TrimStart('#'));
|
||||||
|
|
||||||
|
var diff = usedParameters.ToHashSet().Except(defaultParameters).ToList();
|
||||||
|
if (diff.Any())
|
||||||
|
{
|
||||||
|
throw new ArgumentException("No default value set for parameter " + string.Join(", ", diff));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (profileName, profileParams) in pmd.Profiles)
|
||||||
|
{
|
||||||
|
var sum = 0.0;
|
||||||
|
foreach (var (paramName, _) in pmd.Weights)
|
||||||
|
{
|
||||||
|
if (!profileParams.TryGetValue(paramName, out var weight))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(weight is double d))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sum += Math.Abs(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.Abs(sum) < 0.0001)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Profile " + profileName +
|
||||||
|
": the summed parameters to calculate the weight are zero or very low");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SanityCheck(this IExpression e)
|
||||||
|
{
|
||||||
|
e.Visit(expr =>
|
||||||
|
{
|
||||||
|
var order = new List<IExpression>();
|
||||||
|
var mapping = new List<IExpression>();
|
||||||
|
if (UnApply(
|
||||||
|
UnApply(IsFunc(Funcs.FirstOf), Assign(order)),
|
||||||
|
Assign(mapping)
|
||||||
|
).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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
AspectedRouting/AspectedRouting.csproj
Normal file
17
AspectedRouting/AspectedRouting.csproj
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
<LangVersion>8</LangVersion>
|
||||||
|
<AssemblyName>AspectedRouting</AssemblyName>
|
||||||
|
<RootNamespace>AspectedRouting</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="IO\lua\**">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
107
AspectedRouting/Deconstruct.cs
Normal file
107
AspectedRouting/Deconstruct.cs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Functions;
|
||||||
|
|
||||||
|
namespace AspectedRouting
|
||||||
|
{
|
||||||
|
public static class Deconstruct
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fully deconstruct nested applies, used to convert from ((f a0) a1) ... an) to f(a0, a1, a2, a3
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="e"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static (IExpression f, List<IExpression> args)? DeconstructApply(this IExpression e)
|
||||||
|
{
|
||||||
|
if (!(e is Apply _))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var argss = new List<IExpression>();
|
||||||
|
|
||||||
|
var fs = new List<IExpression>();
|
||||||
|
|
||||||
|
while (UnApply(Assign(fs), Assign(argss)).Invoke(e))
|
||||||
|
{
|
||||||
|
e = fs.First();
|
||||||
|
fs.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
argss.Reverse();
|
||||||
|
|
||||||
|
return (e, argss);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Func<IExpression, bool> Assign(List<IExpression> collect)
|
||||||
|
{
|
||||||
|
return e =>
|
||||||
|
{
|
||||||
|
collect.Add(e);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Func<IExpression, bool> IsFunc(Function f)
|
||||||
|
{
|
||||||
|
return e =>
|
||||||
|
{
|
||||||
|
if (!(e is Function fe))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.Name.Equals(fe.Name);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Func<IExpression, bool> UnApplyAny(
|
||||||
|
Func<IExpression, bool> matchFunc,
|
||||||
|
Func<IExpression, bool> matchArg)
|
||||||
|
{
|
||||||
|
return UnApply(matchFunc, matchArg, true);
|
||||||
|
}
|
||||||
|
public static Func<IExpression, bool> UnApply(
|
||||||
|
Func<IExpression, bool> matchFunc,
|
||||||
|
Func<IExpression, bool> matchArg,
|
||||||
|
bool matchAny = false)
|
||||||
|
{
|
||||||
|
return e =>
|
||||||
|
{
|
||||||
|
if (!(e is Apply apply))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (_, (f, a)) in apply.FunctionApplications)
|
||||||
|
{
|
||||||
|
var doesMatch = matchFunc.Invoke(f) && matchArg.Invoke(a);
|
||||||
|
if (matchAny)
|
||||||
|
{
|
||||||
|
if (doesMatch)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!doesMatch)
|
||||||
|
{
|
||||||
|
// All must match - otherwise we might have registered a wrong collectiin
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !matchAny;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Func<IExpression, bool> Any()
|
||||||
|
{
|
||||||
|
return e => true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
AspectedRouting/Functions/All.cs
Normal file
47
AspectedRouting/Functions/All.cs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class All : Function
|
||||||
|
{
|
||||||
|
public All() : base("all", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Curry(new ListType(Typs.Bool), Typs.Bool)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public All(IEnumerable<Type> types) : base("all", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var arg = ((IEnumerable<object>) arguments[0].Evaluate(c)).Select(o => (string) o);
|
||||||
|
|
||||||
|
|
||||||
|
if (arg.Any(str => str == null || str.Equals("no") || str.Equals("false")))
|
||||||
|
{
|
||||||
|
return "no";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return "yes";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new All(unified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
300
AspectedRouting/Functions/Apply.cs
Normal file
300
AspectedRouting/Functions/Apply.cs
Normal file
|
@ -0,0 +1,300 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using static AspectedRouting.Deconstruct;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Apply : IExpression
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Maps the expected return type onto the argument needed for that.
|
||||||
|
/// The argument is specialized for this return type
|
||||||
|
/// </summary>
|
||||||
|
public readonly Dictionary<Type, (IExpression f, IExpression a)> FunctionApplications;
|
||||||
|
|
||||||
|
// Only used for when there is no typechecking possible
|
||||||
|
private readonly string _debugInfo;
|
||||||
|
|
||||||
|
public IExpression F => FunctionApplications.Values.First().f;
|
||||||
|
public IExpression A => FunctionApplications.Values.First().a;
|
||||||
|
|
||||||
|
public IEnumerable<Type> Types => FunctionApplications.Keys;
|
||||||
|
|
||||||
|
private Apply(string debugInfo, Dictionary<Type, (IExpression f, IExpression a)> argument)
|
||||||
|
{
|
||||||
|
_debugInfo = debugInfo;
|
||||||
|
FunctionApplications = argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Apply(IExpression f, IExpression argument)
|
||||||
|
{
|
||||||
|
if (f == null || argument == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException();
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionApplications = new Dictionary<Type, (IExpression f, IExpression a)>();
|
||||||
|
|
||||||
|
var argTypesCleaned = argument.Types.RenameVars(f.Types);
|
||||||
|
var typesCleaned = argTypesCleaned.ToList();
|
||||||
|
foreach (var funcType in f.Types)
|
||||||
|
{
|
||||||
|
if (!(funcType is Curry c))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedArgType = c.ArgType;
|
||||||
|
var expectedResultType = c.ResultType;
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var argType in typesCleaned)
|
||||||
|
{
|
||||||
|
// we try to unify the argType with the expected type
|
||||||
|
var substitutions = expectedArgType.UnificationTable(argType);
|
||||||
|
if (substitutions == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var actualArgType = expectedArgType.Substitute(substitutions);
|
||||||
|
var actualResultType = expectedResultType.Substitute(substitutions);
|
||||||
|
|
||||||
|
var actualFunction = f.Specialize(new Curry(actualArgType, actualResultType));
|
||||||
|
var actualArgument = argument.Specialize(actualArgType);
|
||||||
|
|
||||||
|
if (actualFunction == null || actualArgument == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FunctionApplications.ContainsKey(actualResultType))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionApplications.Add(actualResultType, (actualFunction, actualArgument));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FunctionApplications.Any())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_debugInfo = $"\n{f.Optimize().TypeBreakdown()}\n" +
|
||||||
|
$"is applied on an argument with types:" +
|
||||||
|
$"{string.Join(", ", argument.Optimize().Types)}";
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_debugInfo = $"\n{f.TypeBreakdown()}\n" +
|
||||||
|
$"{argument.TypeBreakdown()}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
if (Types.Count() > 1)
|
||||||
|
{
|
||||||
|
// We try to select the smallest type
|
||||||
|
}
|
||||||
|
|
||||||
|
var type = Types.First();
|
||||||
|
var (fExpr, argExpr) = FunctionApplications[type];
|
||||||
|
|
||||||
|
|
||||||
|
var arg = argExpr;
|
||||||
|
var allArgs = new IExpression[arguments.Length + 1];
|
||||||
|
allArgs[0] = arg;
|
||||||
|
for (var i = 0; i < arguments.Length; i++)
|
||||||
|
{
|
||||||
|
allArgs[i + 1] = arguments[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return fExpr.Evaluate(c, allArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IExpression IExpression.Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
return Specialize(allowedTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Apply Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var newArgs = new Dictionary<Type, (IExpression f, IExpression a)>();
|
||||||
|
|
||||||
|
foreach (var allowedType in allowedTypes)
|
||||||
|
{
|
||||||
|
foreach (var (resultType, (funExpr, argExpr)) in FunctionApplications)
|
||||||
|
{
|
||||||
|
var substitutions = resultType.UnificationTable(allowedType);
|
||||||
|
if (substitutions == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var actualResultType = resultType.Substitute(substitutions);
|
||||||
|
var actualFunction = funExpr.Specialize(substitutions);
|
||||||
|
var actualArgument = argExpr.Specialize(substitutions);
|
||||||
|
|
||||||
|
if (actualFunction == null || actualArgument == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newArgs[actualResultType] = (actualFunction, actualArgument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newArgs.Any())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Apply(_debugInfo, newArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IExpression Optimize()
|
||||||
|
{
|
||||||
|
// (eitherfunc dot id) id
|
||||||
|
// => (const dot _) id => dot id => id
|
||||||
|
// or => (constRight _ id) id => id id => id
|
||||||
|
if (
|
||||||
|
UnApplyAny(
|
||||||
|
UnApplyAny(
|
||||||
|
UnApplyAny(
|
||||||
|
IsFunc(Funcs.Const),
|
||||||
|
IsFunc(Funcs.Dot)),
|
||||||
|
Any()),
|
||||||
|
IsFunc(Funcs.Id)
|
||||||
|
).Invoke(this)
|
||||||
|
&& UnApplyAny(UnApplyAny(
|
||||||
|
UnApplyAny(
|
||||||
|
IsFunc(Funcs.ConstRight),
|
||||||
|
Any()),
|
||||||
|
IsFunc(Funcs.Id)),
|
||||||
|
IsFunc(Funcs.Id)
|
||||||
|
).Invoke(this))
|
||||||
|
{
|
||||||
|
return Funcs.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (Types.Count() > 1)
|
||||||
|
{
|
||||||
|
var optimized = new Dictionary<Type, (IExpression f, IExpression a)>();
|
||||||
|
foreach (var (resultType, (f, a)) in FunctionApplications)
|
||||||
|
{
|
||||||
|
var fOpt = f.Optimize();
|
||||||
|
var aOpt = a.Optimize();
|
||||||
|
optimized.Add(resultType, (fOpt, aOpt));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Apply(_debugInfo, optimized);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var (f, a) = FunctionApplications.Values.First();
|
||||||
|
|
||||||
|
var (newFa, expr) = OptimizeApplicationPair(f, a);
|
||||||
|
if (expr != null)
|
||||||
|
{
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
(f, a) = newFa.Value;
|
||||||
|
return new Apply(f, a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ((IExpression fOpt, IExpression fArg)?, IExpression result) OptimizeApplicationPair(IExpression f,
|
||||||
|
IExpression a)
|
||||||
|
{
|
||||||
|
f = f.Optimize();
|
||||||
|
a = a.Optimize();
|
||||||
|
|
||||||
|
|
||||||
|
switch (f)
|
||||||
|
{
|
||||||
|
case Id _:
|
||||||
|
return (null, a);
|
||||||
|
|
||||||
|
case Apply apply:
|
||||||
|
|
||||||
|
// const x y -> y
|
||||||
|
var (subF, subArg) = apply.FunctionApplications.Values.First();
|
||||||
|
|
||||||
|
|
||||||
|
if (subF is Const _)
|
||||||
|
{
|
||||||
|
return (null, subArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subF is ConstRight _)
|
||||||
|
{
|
||||||
|
return (null, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
var f0 = new List<IExpression>();
|
||||||
|
var f1 = new List<IExpression>();
|
||||||
|
|
||||||
|
|
||||||
|
// ((dot f0) f1)
|
||||||
|
// ((dot f0) f1) arg is the actual expression, but arg is already split of
|
||||||
|
if (UnApply(
|
||||||
|
UnApply(
|
||||||
|
IsFunc(Funcs.Dot),
|
||||||
|
Assign(f0)
|
||||||
|
),
|
||||||
|
Assign(f1)).Invoke(f)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// f0 (f1 arg)
|
||||||
|
// which used to be (f0 . f1) arg
|
||||||
|
return ((f0.First(), new Apply(f1.First(), subArg)), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((f, a), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Visit(Func<IExpression, bool> visitor)
|
||||||
|
{
|
||||||
|
var continueVisit = visitor(this);
|
||||||
|
if (!continueVisit)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (var (_, (f, a)) in FunctionApplications)
|
||||||
|
{
|
||||||
|
f.Visit(visitor);
|
||||||
|
a.Visit(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (!FunctionApplications.Any())
|
||||||
|
{
|
||||||
|
return "NOT-TYPECHECKABLE APPLICATION: " + _debugInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (f, arg) = FunctionApplications.Values.First();
|
||||||
|
if (f is Id _)
|
||||||
|
{
|
||||||
|
return arg.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"({f} {arg.ToString().Indent()})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
AspectedRouting/Functions/Concat.cs
Normal file
40
AspectedRouting/Functions/Concat.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Concat : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } = "Concatenates two strings";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"a","b"};
|
||||||
|
public Concat() : base("concat", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
Curry.ConstructFrom(Typs.String, Typs.String, Typs.String)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private Concat(IEnumerable<Type> types) : base("concat", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Concat(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var arg0 = (string) arguments[0].Evaluate(c);
|
||||||
|
var arg1 = (string) arguments[1].Evaluate(c);
|
||||||
|
return arg0 + arg1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
AspectedRouting/Functions/Const.cs
Normal file
56
AspectedRouting/Functions/Const.cs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Const : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } =
|
||||||
|
"Small utility function, which takes two arguments `a` and `b` and returns `a`. Used extensively to insert freedom";
|
||||||
|
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"a","b"};
|
||||||
|
|
||||||
|
public Const() : base("const", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
Curry.ConstructFrom(new Var("a"), new Var("a"), new Var("b"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private Const(IEnumerable<Type> types) : base("const", types
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c,params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
if (arguments.Length == 1)
|
||||||
|
{
|
||||||
|
return arguments[0].Evaluate(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
var f = arguments[0];
|
||||||
|
var args = new IExpression [arguments.Length - 2];
|
||||||
|
for (var i = 2; i < arguments.Length; i++)
|
||||||
|
{
|
||||||
|
args[i - 2] = arguments[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.Evaluate(c,args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Const(unified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
AspectedRouting/Functions/ConstRight.cs
Normal file
49
AspectedRouting/Functions/ConstRight.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class ConstRight : Function
|
||||||
|
{ public override string Description { get; } =
|
||||||
|
"Small utility function, which takes two arguments `a` and `b` and returns `b`. Used extensively to insert freedom";
|
||||||
|
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"a","b"};
|
||||||
|
|
||||||
|
public ConstRight() : base("constRight", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
Curry.ConstructFrom(new Var("b"), new Var("a"), new Var("b"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConstRight(IEnumerable<Type> types) : base("constRight", types
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c,params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var argsFor1 = new IExpression[arguments.Length - 2];
|
||||||
|
for (var i = 2; i < arguments.Length; i++)
|
||||||
|
{
|
||||||
|
argsFor1[i - 2] = arguments[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return arguments[1].Evaluate(c, argsFor1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ConstRight(unified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
233
AspectedRouting/Functions/Constant.cs
Normal file
233
AspectedRouting/Functions/Constant.cs
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Constant : IExpression
|
||||||
|
{
|
||||||
|
public IEnumerable<Type> Types { get; }
|
||||||
|
private readonly object _o;
|
||||||
|
|
||||||
|
|
||||||
|
public Constant(IEnumerable<Type> types, object o)
|
||||||
|
{
|
||||||
|
Types = types;
|
||||||
|
_o = o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Constant(Type t, object o)
|
||||||
|
{
|
||||||
|
Types = new[] {t};
|
||||||
|
_o = o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Constant(IExpression[] exprs)
|
||||||
|
{
|
||||||
|
Types = exprs.SpecializeToCommonTypes(out var specializedVersions).Select(t => new ListType(t));
|
||||||
|
_o = specializedVersions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Constant(IEnumerable<IExpression> xprs)
|
||||||
|
{
|
||||||
|
var exprs = xprs.ToList();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Types = exprs.SpecializeToCommonTypes(out var specializedVersions).Select(t => new ListType(t));
|
||||||
|
_o = specializedVersions.ToList();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception($"While creating a list with members {string.Join(", ", exprs)} {e.Message}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Constant(string s)
|
||||||
|
{
|
||||||
|
Types = new[] {Typs.String};
|
||||||
|
_o = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Constant(double d)
|
||||||
|
{
|
||||||
|
if (d >= 0)
|
||||||
|
{
|
||||||
|
Types = new[] {Typs.Double, Typs.PDouble};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Types = new[] {Typs.Double};
|
||||||
|
}
|
||||||
|
|
||||||
|
_o = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Constant(int i)
|
||||||
|
{
|
||||||
|
if (i >= 0)
|
||||||
|
{
|
||||||
|
Types = new[] {Typs.Double, Typs.Nat, Typs.Nat, Typs.PDouble};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Types = new[] {Typs.Double, Typs.Int};
|
||||||
|
}
|
||||||
|
|
||||||
|
_o = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Constant(Dictionary<string, string> tags)
|
||||||
|
{
|
||||||
|
Types = new[] {Typs.Tags};
|
||||||
|
_o = tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Evaluate(Context c, params IExpression[] args)
|
||||||
|
{
|
||||||
|
if (_o is IExpression e)
|
||||||
|
{
|
||||||
|
return e.Evaluate(c).Pretty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (Types.Count() > 1) return _o;
|
||||||
|
|
||||||
|
var t = Types.First();
|
||||||
|
switch (t)
|
||||||
|
{
|
||||||
|
case DoubleType _:
|
||||||
|
case PDoubleType _:
|
||||||
|
if (_o is int i)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
(double) i; // I know, it seems absurd having to write this as it is nearly the same as the return beneath, but it _is_ needed
|
||||||
|
}
|
||||||
|
|
||||||
|
return (double) _o;
|
||||||
|
case IntType _:
|
||||||
|
case NatType _:
|
||||||
|
return (int) _o;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EvaluateAll(Context c, HashSet<IExpression> addTo)
|
||||||
|
{
|
||||||
|
addTo.Add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var enumerable = allowedTypes.ToList();
|
||||||
|
var unified = Types.SpecializeTo(enumerable);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newO = _o;
|
||||||
|
if (_o is IExpression e)
|
||||||
|
{
|
||||||
|
newO = e.Specialize(enumerable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_o is IEnumerable<IExpression> es)
|
||||||
|
{
|
||||||
|
var innerTypes = enumerable
|
||||||
|
.Where(t => t is ListType)
|
||||||
|
.Select(t => ((ListType) t).InnerType);
|
||||||
|
newO = es.Select(x => x.Specialize(innerTypes)).Where(x => x != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Constant(unified, newO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IExpression Optimize()
|
||||||
|
{
|
||||||
|
if (_o is IEnumerable<IExpression> exprs)
|
||||||
|
{
|
||||||
|
return new Constant(exprs.Select(x => x.Optimize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_o is IExpression expr)
|
||||||
|
{
|
||||||
|
return new Constant(expr.Types, expr.Optimize());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Visit(Func<IExpression, bool> f)
|
||||||
|
{
|
||||||
|
if (_o is IExpression e)
|
||||||
|
{
|
||||||
|
e.Visit(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_o is IEnumerable<IExpression> es)
|
||||||
|
{
|
||||||
|
foreach (var x in es)
|
||||||
|
{
|
||||||
|
x.Visit(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return _o.Pretty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ObjectExtensions
|
||||||
|
{
|
||||||
|
public static string Pretty(this object o, Context context = null)
|
||||||
|
{
|
||||||
|
switch (o)
|
||||||
|
{
|
||||||
|
case Dictionary<string, string> d:
|
||||||
|
var txt = "";
|
||||||
|
foreach (var (k, v) in d)
|
||||||
|
{
|
||||||
|
txt += $"{k}={v};";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{{{txt}}}";
|
||||||
|
case Dictionary<string, List<string>> d:
|
||||||
|
var t = "";
|
||||||
|
foreach (var (k, v) in d)
|
||||||
|
{
|
||||||
|
var values = v.Pretty();
|
||||||
|
if (!v.Any())
|
||||||
|
{
|
||||||
|
values = "*";
|
||||||
|
}
|
||||||
|
|
||||||
|
t += k + "=" + values + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
|
||||||
|
case Constant c:
|
||||||
|
return "<" + c.Evaluate(context).Pretty() + ">";
|
||||||
|
case List<object> ls:
|
||||||
|
return "[" + string.Join(", ", ls.Select(obj => obj.Pretty(context))) + "]";
|
||||||
|
case object[] arr:
|
||||||
|
return arr.ToList().Pretty();
|
||||||
|
case double[] arr:
|
||||||
|
return arr.Select(d => (object) d).ToList().Pretty();
|
||||||
|
case string s:
|
||||||
|
return "\"" + s.Replace("\"", "\\\"") + "\"";
|
||||||
|
case IEnumerable<object> ls:
|
||||||
|
return "[" + string.Join(", ", ls.Select(obj => obj.Pretty(context))) + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
AspectedRouting/Functions/Default.cs
Normal file
54
AspectedRouting/Functions/Default.cs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Default : Function
|
||||||
|
{
|
||||||
|
|
||||||
|
public override string Description { get; } = "Calculates function `f` for the given argument. If the result is `null`, the default value is returned instead";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string> {"defaultValue", "f"};
|
||||||
|
|
||||||
|
|
||||||
|
private static Var a = new Var("a");
|
||||||
|
private static Var b = new Var("b");
|
||||||
|
public Default() : base("default", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
Curry.ConstructFrom(a, a, new Curry(b, a), b)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private Default(IEnumerable<Type> types) : base("default", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Default(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c,params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var defaultValue = arguments[0];
|
||||||
|
var func = arguments[1];
|
||||||
|
var args= arguments.ToList().GetRange(2, arguments.Length - 2).ToArray();
|
||||||
|
|
||||||
|
var calculated = func.Evaluate(c,args);
|
||||||
|
if (calculated == null)
|
||||||
|
{
|
||||||
|
return defaultValue.Evaluate(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
AspectedRouting/Functions/Dot.cs
Normal file
53
AspectedRouting/Functions/Dot.cs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Dot : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } = "Higher order function: converts `f (g a)` into `(dot f g) a`. In other words, this fuses `f` and `g` in a new function, which allows the argument to be lifted out of the expression ";
|
||||||
|
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"f","g","a"};
|
||||||
|
public static readonly Var A = new Var("fType");
|
||||||
|
public static readonly Var B = new Var("gType");
|
||||||
|
public static readonly Var C = new Var("arg");
|
||||||
|
|
||||||
|
public Dot() : base("dot", true, new[]
|
||||||
|
{
|
||||||
|
// (.) : (b -> c) -> (a -> b) -> a -> c
|
||||||
|
Curry.ConstructFrom(C,
|
||||||
|
new Curry(B, C),
|
||||||
|
new Curry(A, B),
|
||||||
|
A
|
||||||
|
)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dot(IEnumerable<Type> types) : base("dot", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var f0 = arguments[0];
|
||||||
|
var f1 = arguments[1];
|
||||||
|
var resultType = (f1.Types.First() as Curry).ResultType;
|
||||||
|
var a = arguments[2];
|
||||||
|
return f0.Evaluate(c, new Constant(resultType, f1.Evaluate(c, a)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Dot(unified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
AspectedRouting/Functions/EitherFunc.cs
Normal file
78
AspectedRouting/Functions/EitherFunc.cs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class EitherFunc : Function
|
||||||
|
{
|
||||||
|
private static Var a = new Var("a");
|
||||||
|
private static Var b = new Var("b");
|
||||||
|
private static Var c = new Var("c");
|
||||||
|
private static Var d = new Var("d");
|
||||||
|
|
||||||
|
|
||||||
|
// (a -> b) -> (c -> d) -> (a -> b)
|
||||||
|
private static Curry formAB = new Curry(
|
||||||
|
new Curry(a, b),
|
||||||
|
new Curry(
|
||||||
|
new Curry(c, d),
|
||||||
|
new Curry(a, b))
|
||||||
|
);
|
||||||
|
// (a -> b) -> (c -> d) -> (c -> d)
|
||||||
|
|
||||||
|
private static Curry formCD = new Curry(
|
||||||
|
new Curry(a, b),
|
||||||
|
new Curry(
|
||||||
|
new Curry(c, d),
|
||||||
|
new Curry(c, d))
|
||||||
|
);
|
||||||
|
|
||||||
|
public EitherFunc() : base("eitherFunc", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
formAB, formCD
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private EitherFunc(IEnumerable<Type> unified) : base("eitherFunc", unified)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context _,params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("EitherFunc not sufficiently specialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isFormAb = formAB.UnifyAll(unified) != null;
|
||||||
|
var isFormCd = formCD.UnifyAll(unified) != null;
|
||||||
|
|
||||||
|
if (isFormAb && isFormCd)
|
||||||
|
{
|
||||||
|
return new EitherFunc(unified); // Can't make a decision yet
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFormAb)
|
||||||
|
{
|
||||||
|
return Funcs.Const.Specialize(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFormCd)
|
||||||
|
{
|
||||||
|
return Funcs.ConstRight.Specialize(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
AspectedRouting/Functions/Eq.cs
Normal file
50
AspectedRouting/Functions/Eq.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Eq : Function
|
||||||
|
{ public override string Description { get; } = "Returns 'yes' if both values _are_ the same";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"a","b"};
|
||||||
|
public Eq() : base("eq", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
Curry.ConstructFrom(Typs.Bool, new Var("a"), new Var("a")),
|
||||||
|
Curry.ConstructFrom(Typs.String, new Var("a"), new Var("a"))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private Eq(IEnumerable<Type> types) : base("eq", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Eq(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var arg0 = arguments[0].Evaluate(c);
|
||||||
|
var arg1 = arguments[1].Evaluate(c);
|
||||||
|
if (arg0 == null || arg1 == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg0.Equals(arg1))
|
||||||
|
{
|
||||||
|
return "yes";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "no";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
AspectedRouting/Functions/FirstMatchOf.cs
Normal file
78
AspectedRouting/Functions/FirstMatchOf.cs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class FirstMatchOf : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } = "Parses a string into a numerical value";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string> {"s"};
|
||||||
|
|
||||||
|
public FirstMatchOf() : base("firstMatchOf", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
// [String] -> (Tags -> [a]) -> Tags -> a
|
||||||
|
Curry.ConstructFrom(new Var("a"), // Result type on top!
|
||||||
|
new ListType(Typs.String),
|
||||||
|
new Curry(Typs.Tags, new ListType(new Var("a"))),
|
||||||
|
Typs.Tags
|
||||||
|
)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private FirstMatchOf(IEnumerable<Type> types) : base("firstMatchOf", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FirstMatchOf(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var order = ((IEnumerable<object>) arguments[0].Evaluate(c))
|
||||||
|
.Select(o =>
|
||||||
|
{
|
||||||
|
if (o is string s)
|
||||||
|
{
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (string) ((IExpression) o).Evaluate(c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var function = arguments[1];
|
||||||
|
var tags = (Dictionary<string, string>) arguments[2].Evaluate(c);
|
||||||
|
|
||||||
|
var singletonDict = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
foreach (var tagKey in order)
|
||||||
|
{
|
||||||
|
if (!tags.TryGetValue(tagKey, out var tagValue)) continue;
|
||||||
|
singletonDict.Clear();
|
||||||
|
singletonDict[tagKey] = tagValue;
|
||||||
|
var result = function.Evaluate(c, new Constant(singletonDict));
|
||||||
|
|
||||||
|
if (result == null || (result is List<object> ls && !ls.Any()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((List<object>) result).First();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
AspectedRouting/Functions/Funcs.cs
Normal file
130
AspectedRouting/Functions/Funcs.cs
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public static class Funcs
|
||||||
|
{
|
||||||
|
public static readonly Dictionary<string, Function> Builtins = new Dictionary<string, Function>();
|
||||||
|
public static IEnumerable<string> BuiltinNames = Builtins.Keys;
|
||||||
|
|
||||||
|
|
||||||
|
public static readonly Function Eq = new Eq();
|
||||||
|
public static readonly Function NotEq = new NotEq();
|
||||||
|
public static readonly Function Inv = new Inv();
|
||||||
|
|
||||||
|
public static readonly Function Default = new Default();
|
||||||
|
|
||||||
|
public static readonly Function Parse = new Parse();
|
||||||
|
public static readonly Function ToStringFunc = new ToString();
|
||||||
|
public static readonly Function Concat = new Concat();
|
||||||
|
|
||||||
|
public static readonly Function Min = new Min();
|
||||||
|
public static readonly Function Max = new Max();
|
||||||
|
public static readonly Function Sum = new Sum();
|
||||||
|
public static readonly Function Multiply = new Multiply();
|
||||||
|
|
||||||
|
|
||||||
|
public static readonly Function FirstOf = new FirstMatchOf();
|
||||||
|
public static readonly Function MustMatch = new MustMatch();
|
||||||
|
|
||||||
|
public static readonly Function MemberOf = new MemberOf();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static readonly Function If = new If();
|
||||||
|
|
||||||
|
public static readonly Function Id = new Id();
|
||||||
|
public static readonly Function Const = new Const();
|
||||||
|
public static readonly Function ConstRight = new ConstRight();
|
||||||
|
public static readonly Function Dot = new Dot();
|
||||||
|
public static readonly Function ListDot = new ListDot();
|
||||||
|
public static readonly Function EitherFunc = new EitherFunc();
|
||||||
|
|
||||||
|
public static readonly Function StringStringToTags = new StringStringToTagsFunction();
|
||||||
|
|
||||||
|
public static void AddBuiltin(Function f, string name = null)
|
||||||
|
{
|
||||||
|
Builtins.Add(name ?? f.Name, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IExpression Either(IExpression a, IExpression b, IExpression arg)
|
||||||
|
{
|
||||||
|
return new Apply(new Apply(new Apply(EitherFunc, a), b), arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Function BuiltinByName(string funcName)
|
||||||
|
{
|
||||||
|
if (funcName.StartsWith("$"))
|
||||||
|
{
|
||||||
|
funcName = funcName.Substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Builtins.TryGetValue(funcName, out var f)) return f;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IExpression Finalize(this IExpression e)
|
||||||
|
{
|
||||||
|
if (e == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
e = e.SpecializeToSmallestType().Optimize();
|
||||||
|
e.SanityCheck();
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IExpression SpecializeToSmallestType(this IExpression e)
|
||||||
|
{
|
||||||
|
Type smallest = null;
|
||||||
|
foreach (var t in e.Types)
|
||||||
|
{
|
||||||
|
if (smallest == null)
|
||||||
|
{
|
||||||
|
smallest = t;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var smallestIsSuperset = smallest.IsSuperSet(t);
|
||||||
|
if (!t.IsSuperSet(smallest) && !smallestIsSuperset)
|
||||||
|
{
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (smallestIsSuperset)
|
||||||
|
{
|
||||||
|
smallest = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.Specialize(new[] {smallest});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IExpression Apply(this IExpression function, IEnumerable<IExpression> args)
|
||||||
|
{
|
||||||
|
return function.Apply(args.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IExpression Apply(this IExpression function, List<IExpression> args)
|
||||||
|
{
|
||||||
|
if (args.Count == 0)
|
||||||
|
{
|
||||||
|
return function;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Count == 1)
|
||||||
|
{
|
||||||
|
return new Apply(function, args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastArg = args[args.Count - 1];
|
||||||
|
var firstArgs = args.GetRange(0, args.Count - 1);
|
||||||
|
return new Apply(Apply(function, firstArgs), lastArg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
AspectedRouting/Functions/Function.cs
Normal file
99
AspectedRouting/Functions/Function.cs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public abstract class Function : IExpression
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public virtual List<string> ArgNames { get; } = null;
|
||||||
|
|
||||||
|
public virtual string Description { get; } = "";
|
||||||
|
|
||||||
|
protected Function(string name, bool isBuiltin, IEnumerable<Curry> types)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
if (isBuiltin)
|
||||||
|
{
|
||||||
|
Funcs.AddBuiltin(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Types = types;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Function(string name, IEnumerable<Type> types)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Types = types;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Type> Types { get; }
|
||||||
|
|
||||||
|
public abstract object Evaluate(Context c, params IExpression[] arguments);
|
||||||
|
public abstract IExpression Specialize(IEnumerable<Type> allowedTypes);
|
||||||
|
|
||||||
|
public virtual IExpression Optimize()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Visit(Func<IExpression, bool> f)
|
||||||
|
{
|
||||||
|
f(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"${Name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public IExpression Apply(params IExpression[] args)
|
||||||
|
{
|
||||||
|
return this.Apply(args.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gives an overview per argument what the possible types are.
|
||||||
|
/// The return type has an empty string as key
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Dictionary<string, List<Type>> ArgBreakdown()
|
||||||
|
{
|
||||||
|
var dict = new Dictionary<string, List<Type>>();
|
||||||
|
|
||||||
|
if (ArgNames == null)
|
||||||
|
{
|
||||||
|
throw new Exception("ArgNames not set for "+Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var n in ArgNames)
|
||||||
|
{
|
||||||
|
dict[n] = new List<Type>();
|
||||||
|
}
|
||||||
|
|
||||||
|
dict[""] = new List<Type>();
|
||||||
|
foreach (var type in Types)
|
||||||
|
{
|
||||||
|
var restType = type;
|
||||||
|
foreach (var n in ArgNames)
|
||||||
|
{
|
||||||
|
if (!(restType is Curry c))
|
||||||
|
{
|
||||||
|
dict[n].Add(null);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
dict[n].Add(c.ArgType);
|
||||||
|
restType = c.ResultType;
|
||||||
|
}
|
||||||
|
|
||||||
|
dict[""].Add(restType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
AspectedRouting/Functions/FunctionCall.cs
Normal file
66
AspectedRouting/Functions/FunctionCall.cs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class FunctionCall : IExpression
|
||||||
|
{
|
||||||
|
private readonly string _name;
|
||||||
|
|
||||||
|
public string CalledFunctionName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_name.StartsWith("$"))
|
||||||
|
{
|
||||||
|
return _name.Substring(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Type> Types { get; }
|
||||||
|
|
||||||
|
public FunctionCall(string name, IEnumerable<Type> types)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
Types = types;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
return c.GetFunction(_name).Evaluate(c, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FunctionCall(_name, unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IExpression Optimize()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Visit(Func<IExpression, bool> f)
|
||||||
|
{
|
||||||
|
f(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"${_name}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
AspectedRouting/Functions/Id.cs
Normal file
38
AspectedRouting/Functions/Id.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Id : Function
|
||||||
|
{ public override string Description { get; } = "Returns the argument unchanged - the identity function. Seems useless at first sight, but useful in parsing";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"a"};
|
||||||
|
public Id() : base("id", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Curry(new Var("a"), new Var("a")),
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private Id(IEnumerable<Type> types) : base("id", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Id(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c,params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
return arguments[0].Evaluate(c,arguments.ToList().GetRange(1, arguments.Length-1).ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
AspectedRouting/Functions/If.cs
Normal file
60
AspectedRouting/Functions/If.cs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class If : Function
|
||||||
|
{
|
||||||
|
private static Var a = new Var("a");
|
||||||
|
|
||||||
|
public override string Description { get; } = "Selects either one of the branches, depending on the condition." +
|
||||||
|
"If the `else` branch is not set, `null` is returned in the condition is false.";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string> {"condition", "then", "else"};
|
||||||
|
|
||||||
|
public If() : base("if_then_else", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
Curry.ConstructFrom(a, Typs.Bool, a, a),
|
||||||
|
Curry.ConstructFrom(a, Typs.Bool, a)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Funcs.AddBuiltin(this, "if");
|
||||||
|
}
|
||||||
|
|
||||||
|
private If(IEnumerable<Type> types) : base("if_then_else", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var condition = arguments[0].Evaluate(c);
|
||||||
|
var then = arguments[1];
|
||||||
|
IExpression _else = null;
|
||||||
|
if (arguments.Length > 2)
|
||||||
|
{
|
||||||
|
_else = arguments[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (condition != null && (condition.Equals("yes") || condition.Equals("true")))
|
||||||
|
{
|
||||||
|
return then.Evaluate(c);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _else?.Evaluate(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new If(unified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
AspectedRouting/Functions/Inv.cs
Normal file
40
AspectedRouting/Functions/Inv.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Inv : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } = "Calculates `1/d`";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string> {"d"};
|
||||||
|
|
||||||
|
public Inv() : base("inv", true, new[]
|
||||||
|
{
|
||||||
|
new Curry(Typs.PDouble, Typs.PDouble),
|
||||||
|
new Curry(Typs.Double, Typs.Double),
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Inv(IEnumerable<Type> types) : base("inv", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var arg = (double) arguments[0].Evaluate(c);
|
||||||
|
return 1 / arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Inv(unified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
AspectedRouting/Functions/ListDot.cs
Normal file
57
AspectedRouting/Functions/ListDot.cs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class ListDot : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } =
|
||||||
|
"Listdot takes a list of functions `[f, g, h]` and and an argument `a`. It applies the argument on every single function." +
|
||||||
|
"It conveniently lifts the argument out of the list.";
|
||||||
|
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"list","a"};
|
||||||
|
|
||||||
|
public ListDot() : base("listDot", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
// [a -> b] -> a -> [b]
|
||||||
|
new Curry(
|
||||||
|
new ListType(new Curry(new Var("a"), new Var("b"))),
|
||||||
|
new Curry(new Var("a"),
|
||||||
|
new ListType(new Var("b")))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListDot(IEnumerable<Type> types) : base("listDot", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var listOfFuncs = (IEnumerable<IExpression>) arguments[0].Evaluate(c);
|
||||||
|
var arg = arguments[1];
|
||||||
|
|
||||||
|
var result = new List<object>();
|
||||||
|
foreach (var f in listOfFuncs)
|
||||||
|
{
|
||||||
|
result.Add(f.Evaluate(c, arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ListDot(unified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
154
AspectedRouting/Functions/Mapping.cs
Normal file
154
AspectedRouting/Functions/Mapping.cs
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Mapping : Function
|
||||||
|
{
|
||||||
|
public readonly Dictionary<string, IExpression> StringToResultFunctions;
|
||||||
|
|
||||||
|
|
||||||
|
public Mapping(IReadOnlyList<string> keys, IEnumerable<IExpression> expressions) :
|
||||||
|
base(
|
||||||
|
$"mapping ({MappingToString(keys, expressions.SpecializeToCommonTypes(out var specializedTypes, out var specializedExpressions))})",
|
||||||
|
false,
|
||||||
|
specializedTypes
|
||||||
|
.Select(returns => new Curry(Typs.String, returns)))
|
||||||
|
|
||||||
|
{
|
||||||
|
StringToResultFunctions = new Dictionary<string, IExpression>();
|
||||||
|
var ls = specializedExpressions.ToList();
|
||||||
|
for (var i = 0; i < keys.Count; i++)
|
||||||
|
{
|
||||||
|
StringToResultFunctions[keys[i]] = ls[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mapping(Dictionary<string, IExpression> newFunctions) :
|
||||||
|
base($"mapping {MappingToString(newFunctions)}", false,
|
||||||
|
newFunctions.SelectMany(nf => nf.Value.Types).Select(tp => new Curry(Typs.String, tp)).ToHashSet()
|
||||||
|
)
|
||||||
|
{
|
||||||
|
StringToResultFunctions = newFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var allowedTypesList = allowedTypes.ToList();
|
||||||
|
var unified = Types.SpecializeTo(allowedTypesList);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var newFunctions = new Dictionary<string, IExpression>();
|
||||||
|
var functionType = unified.Select(c => ((Curry) c).ResultType);
|
||||||
|
var enumerable = functionType.ToList();
|
||||||
|
|
||||||
|
foreach (var (k, expr) in StringToResultFunctions)
|
||||||
|
{
|
||||||
|
var exprSpecialized = expr.Specialize(enumerable);
|
||||||
|
if (exprSpecialized == null)
|
||||||
|
{
|
||||||
|
throw new Exception($"Could not specialize a mapping of type {string.Join(",", Types)}\n" +
|
||||||
|
$"to types {string.Join(", ", allowedTypesList)};\n" +
|
||||||
|
$"Expression {expr} could not be specialized to {functionType}");
|
||||||
|
}
|
||||||
|
|
||||||
|
newFunctions[k] = exprSpecialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Mapping(newFunctions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var s = arguments[0].Evaluate(c);
|
||||||
|
while (s is Constant constant)
|
||||||
|
{
|
||||||
|
s = constant.Evaluate(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = (string) s;
|
||||||
|
var otherARgs = arguments.ToList().GetRange(1, arguments.Length - 1);
|
||||||
|
if (!StringToResultFunctions.TryGetValue(key, out var resultFunction))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Warning: key {key} not found in mapping {this}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultFunction.Evaluate(c, otherARgs.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Optimize()
|
||||||
|
{
|
||||||
|
var optimizedFunctions = new Dictionary<string, IExpression>();
|
||||||
|
|
||||||
|
foreach (var (k, e) in StringToResultFunctions)
|
||||||
|
{
|
||||||
|
var opt = e.Optimize();
|
||||||
|
|
||||||
|
var typeOptStr = string.Join(";", opt.Types);
|
||||||
|
var typeEStr = string.Join("; ", e.Types);
|
||||||
|
if (opt == null || !opt.Types.Any())
|
||||||
|
{
|
||||||
|
throw new NullReferenceException($"Optimized version is null, has different or empty types: " +
|
||||||
|
$"\n{typeEStr}" +
|
||||||
|
$"\n{typeOptStr}");
|
||||||
|
}
|
||||||
|
|
||||||
|
optimizedFunctions[k] = opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Mapping(optimizedFunctions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Mapping Construct(params (string key, IExpression e)[] exprs)
|
||||||
|
{
|
||||||
|
return new Mapping(exprs.Select(e => e.key).ToList(),
|
||||||
|
exprs.Select(e => e.e).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string MappingToString(Dictionary<string, IExpression> dict)
|
||||||
|
{
|
||||||
|
var txt = "";
|
||||||
|
foreach (var (k, v) in dict)
|
||||||
|
{
|
||||||
|
txt += "\n " + k + ": " + v?.ToString()?.Indent() + ",";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{" + txt + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string MappingToString(IReadOnlyList<string> keys, IEnumerable<IExpression> expressions)
|
||||||
|
{
|
||||||
|
var txt = "";
|
||||||
|
var exprs = expressions.ToList();
|
||||||
|
for (int i = 0; i < keys.Count; i++)
|
||||||
|
{
|
||||||
|
txt += "\n" + keys[i] + ": " + exprs[i].ToString().Indent() + ",";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "{" + txt + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Visit(Func<IExpression, bool> f)
|
||||||
|
{
|
||||||
|
var continueVisit = f(this);
|
||||||
|
if (!continueVisit)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (_, e) in StringToResultFunctions)
|
||||||
|
{
|
||||||
|
e.Visit(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
AspectedRouting/Functions/Max.cs
Normal file
65
AspectedRouting/Functions/Max.cs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Max : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } =
|
||||||
|
"Returns the biggest value in the list. For a list of booleans, this acts as 'or'";
|
||||||
|
|
||||||
|
public override List<string> ArgNames { get; } = new List<string> {"list"};
|
||||||
|
|
||||||
|
public Max() : base("max", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Curry(new ListType(Typs.Nat), Typs.Nat),
|
||||||
|
new Curry(new ListType(Typs.Int), Typs.Int),
|
||||||
|
new Curry(new ListType(Typs.PDouble), Typs.PDouble),
|
||||||
|
new Curry(new ListType(Typs.Double), Typs.Double),
|
||||||
|
new Curry(new ListType(Typs.Bool), Typs.Bool),
|
||||||
|
})
|
||||||
|
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private Max(IEnumerable<Type> specializedTypes) : base("max", specializedTypes)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Max(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o != null);
|
||||||
|
|
||||||
|
var expectedType = (Types.First() as Curry).ResultType;
|
||||||
|
switch (expectedType)
|
||||||
|
{
|
||||||
|
case BoolType _:
|
||||||
|
if (ls.Select(o => o.Equals("yes") || o.Equals("true")).Any(b => b))
|
||||||
|
{
|
||||||
|
return "yes";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "no";
|
||||||
|
case DoubleType _:
|
||||||
|
case PDoubleType _:
|
||||||
|
return ls.Select(o => (double) o).Max();
|
||||||
|
default:
|
||||||
|
return ls.Select(o => (int) o).Max();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
AspectedRouting/Functions/MemberOf.cs
Normal file
99
AspectedRouting/Functions/MemberOf.cs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class MemberOf : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } =
|
||||||
|
"This function uses memberships of relations to calculate values.\n" +
|
||||||
|
"\n" +
|
||||||
|
"Consider all the relations the scrutinized way is part of." +
|
||||||
|
"The enclosed function is executed for every single relation which is part of it, generating a list of results." +
|
||||||
|
"This list of results is in turn returned by 'memberOf'" +
|
||||||
|
"\n" +
|
||||||
|
"In itinero 1/lua, this is implemented by converting the matching relations and by adding the tags of the relations to the dictionary (or table) with the highway tags." +
|
||||||
|
"The prefix is '_relation:n:key=value', where 'n' is a value between 0 and the number of matching relations (implying that all of these numbers are scanned)." +
|
||||||
|
"The matching relations can be extracted by the compiler for the preprocessing.\n\n" +
|
||||||
|
"For testing, the relation can be emulated by using e.g. '_relation:0:key=value'";
|
||||||
|
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>
|
||||||
|
{
|
||||||
|
"f","tags"
|
||||||
|
};
|
||||||
|
|
||||||
|
public MemberOf() : base(
|
||||||
|
"memberOf", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Curry(
|
||||||
|
new Curry(Typs.Tags, new Var("a")),
|
||||||
|
new Curry(Typs.Tags, new ListType(new Var("a"))))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemberOf(IEnumerable<Type> types) : base("memberOf", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var f = arguments[0];
|
||||||
|
var tags = (Dictionary<string, string>) arguments[1].Evaluate(c);
|
||||||
|
|
||||||
|
|
||||||
|
var prefixes = new Dictionary<int, Dictionary<string, string>>();
|
||||||
|
foreach (var (k, v) in tags)
|
||||||
|
{
|
||||||
|
if (!k.StartsWith("_relation:")) continue;
|
||||||
|
var s = k.Split(":")[1];
|
||||||
|
if (int.TryParse(s, out var i))
|
||||||
|
{
|
||||||
|
var key = k.Substring(("_relation:" + i + ":").Length);
|
||||||
|
if (prefixes.TryGetValue(i, out var relationTags))
|
||||||
|
{
|
||||||
|
relationTags[key] = v;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prefixes[i] = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{key, v}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// At this point, we have all the tags of all the relations
|
||||||
|
// Time to run the function on all of them
|
||||||
|
|
||||||
|
var result = new List<object>();
|
||||||
|
foreach (var relationTags in prefixes.Values)
|
||||||
|
{
|
||||||
|
var o = f.Evaluate(c, new Constant(relationTags));
|
||||||
|
if (o != null)
|
||||||
|
{
|
||||||
|
result.Add(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MemberOf(unified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
AspectedRouting/Functions/Metadata.cs
Normal file
67
AspectedRouting/Functions/Metadata.cs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class AspectMetadata : IExpression
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public string Description { get; }
|
||||||
|
public string Author { get; }
|
||||||
|
public string Unit { get; }
|
||||||
|
public string Filepath { get; }
|
||||||
|
public readonly IExpression ExpressionImplementation;
|
||||||
|
|
||||||
|
public readonly bool ProfileInternal;
|
||||||
|
|
||||||
|
public AspectMetadata(IExpression expressionImplementation,
|
||||||
|
string name, string description, string author, string unit, string filepath, bool profileInternal = false)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Description = description;
|
||||||
|
Author = author;
|
||||||
|
Unit = unit;
|
||||||
|
Filepath = filepath;
|
||||||
|
ExpressionImplementation = expressionImplementation;
|
||||||
|
ProfileInternal = profileInternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public IEnumerable<Type> Types => ExpressionImplementation.Types;
|
||||||
|
|
||||||
|
public object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
return ExpressionImplementation.Evaluate(c, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
return new AspectMetadata(
|
||||||
|
ExpressionImplementation.Specialize(allowedTypes),
|
||||||
|
Name, Description, Author, Unit, Filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IExpression Optimize()
|
||||||
|
{
|
||||||
|
return new AspectMetadata(ExpressionImplementation.Optimize(),
|
||||||
|
Name, Description, Author, Unit, Filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Visit(Func<IExpression, bool> f)
|
||||||
|
{
|
||||||
|
var continueVisit = f(this);
|
||||||
|
if (!continueVisit)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpressionImplementation.Visit(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"# {Name}; {Unit} by {Author}\n\n# by {Author}\n# {Description}\n{ExpressionImplementation}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
AspectedRouting/Functions/Min.cs
Normal file
63
AspectedRouting/Functions/Min.cs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Min : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } = "Out of a list of values, gets the smallest value. IN case of a list of bools, this acts as `and`";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"list"};
|
||||||
|
|
||||||
|
public Min() : base("min", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Curry(new ListType(Typs.Nat), Typs.Nat),
|
||||||
|
new Curry(new ListType(Typs.Int), Typs.Int),
|
||||||
|
new Curry(new ListType(Typs.PDouble), Typs.PDouble),
|
||||||
|
new Curry(new ListType(Typs.Double), Typs.Double),
|
||||||
|
new Curry(new ListType(Typs.Bool), Typs.Bool),
|
||||||
|
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private Min(IEnumerable<Type> specializedTypes) : base("min",
|
||||||
|
specializedTypes)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Min(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o!=null);
|
||||||
|
var expectedType = (Types.First() as Curry).ResultType;
|
||||||
|
|
||||||
|
switch (expectedType)
|
||||||
|
{ case BoolType _:
|
||||||
|
if (ls.Select(o => o.Equals("yes") || o.Equals("true")).All(b => b))
|
||||||
|
{
|
||||||
|
return "yes";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "no";
|
||||||
|
case DoubleType _:
|
||||||
|
case PDoubleType _:
|
||||||
|
return ls.Select(o => (double) o).Min();
|
||||||
|
default:
|
||||||
|
return ls.Select(o => (int) o).Min();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
AspectedRouting/Functions/Multiply.cs
Normal file
84
AspectedRouting/Functions/Multiply.cs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Multiply : Function
|
||||||
|
{
|
||||||
|
|
||||||
|
public override string Description { get; } = "Multiplies all the values in a given list. On a list of booleans, this acts as 'and' or 'all'";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string> {"list"};
|
||||||
|
|
||||||
|
|
||||||
|
public Multiply() : base("multiply", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Curry(new ListType(Typs.Nat), Typs.Nat),
|
||||||
|
new Curry(new ListType(Typs.Int), Typs.Int),
|
||||||
|
new Curry(new ListType(Typs.PDouble), Typs.PDouble),
|
||||||
|
new Curry(new ListType(Typs.Double), Typs.Double),
|
||||||
|
new Curry(new ListType(Typs.Bool), Typs.Bool)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private Multiply(IEnumerable<Type> specializedTypes) : base("multiply", specializedTypes)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Multiply(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o != null);
|
||||||
|
var expectedType = (Types.First() as Curry).ResultType;
|
||||||
|
|
||||||
|
|
||||||
|
switch (expectedType)
|
||||||
|
{
|
||||||
|
case BoolType _:
|
||||||
|
foreach (var o in ls)
|
||||||
|
{
|
||||||
|
if(!(o is string s))
|
||||||
|
{
|
||||||
|
return "no";
|
||||||
|
}
|
||||||
|
if(!(o.Equals("yes") || o.Equals("true")))
|
||||||
|
{
|
||||||
|
return "no";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "yes";
|
||||||
|
case DoubleType _:
|
||||||
|
case PDoubleType _:
|
||||||
|
var mult = 1.0;
|
||||||
|
foreach (var o in ls)
|
||||||
|
{
|
||||||
|
mult *= (double) o;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mult;
|
||||||
|
default:
|
||||||
|
var multI = 1;
|
||||||
|
foreach (var o in ls)
|
||||||
|
{
|
||||||
|
multI *= (int) o;
|
||||||
|
}
|
||||||
|
|
||||||
|
return multI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
83
AspectedRouting/Functions/MustMatch.cs
Normal file
83
AspectedRouting/Functions/MustMatch.cs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class MustMatch : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } =
|
||||||
|
"Every key that is used in the subfunction must be present.\n" +
|
||||||
|
"If, on top, a value is present with a mapping, every key/value will be executed and must return a value that is not 'no' or 'false'\n" +
|
||||||
|
"Note that this is a privileged builtin function, as the parser will automatically inject the keys used in the called function.";
|
||||||
|
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>
|
||||||
|
{
|
||||||
|
"neededKeys (filled in by parser)",
|
||||||
|
"f"
|
||||||
|
};
|
||||||
|
|
||||||
|
public MustMatch() : base("mustMatch", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
// [String] -> (Tags -> [a]) -> Tags -> a
|
||||||
|
Curry.ConstructFrom(Typs.Bool, // Result type on top!
|
||||||
|
new ListType(Typs.String), // List of keys to check for
|
||||||
|
new Curry(Typs.Tags, new ListType(Typs.Bool)), // The function to execute on every key
|
||||||
|
Typs.Tags // The tags to apply this on
|
||||||
|
)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private MustMatch(IEnumerable<Type> types) : base("mustMatch", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MustMatch(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var neededKeys = (IEnumerable<object>) arguments[0].Evaluate(c);
|
||||||
|
var function = arguments[1];
|
||||||
|
|
||||||
|
var tags = (Dictionary<string, string>) arguments[2].Evaluate(c);
|
||||||
|
|
||||||
|
foreach (var oo in neededKeys)
|
||||||
|
{
|
||||||
|
var o = oo;
|
||||||
|
while (o is IExpression e)
|
||||||
|
{
|
||||||
|
o = e.Evaluate(c);
|
||||||
|
}
|
||||||
|
if (!(o is string tagKey))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tags.ContainsKey(tagKey)) return "no";
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = (IEnumerable<object>) function.Evaluate(c, new Constant(tags));
|
||||||
|
|
||||||
|
if (!result.Any(o =>
|
||||||
|
o == null ||
|
||||||
|
(o is string s && (s.Equals("no") || s.Equals("false")))))
|
||||||
|
{
|
||||||
|
return "yes";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "no";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
AspectedRouting/Functions/NotEq.cs
Normal file
50
AspectedRouting/Functions/NotEq.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class NotEq : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } = "Returns 'yes' if the two passed in values are _not_ the same";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string> {"a", "b"};
|
||||||
|
|
||||||
|
public NotEq() : base("notEq", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
Curry.ConstructFrom(Typs.Bool, new Var("a"), new Var("a")),
|
||||||
|
Curry.ConstructFrom(Typs.String, new Var("a"), new Var("a"))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Funcs.AddBuiltin(this, "not");
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotEq(IEnumerable<Type> types) : base("notEq", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new NotEq(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var arg0 = arguments[0].Evaluate(c);
|
||||||
|
var arg1 = arguments[1].Evaluate(c);
|
||||||
|
if ((!(arg0?.Equals(arg1) ?? false)))
|
||||||
|
{
|
||||||
|
return "yes";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "no";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
AspectedRouting/Functions/Parameter.cs
Normal file
66
AspectedRouting/Functions/Parameter.cs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Parameter : IExpression
|
||||||
|
{
|
||||||
|
public IEnumerable<Type> Types { get; }
|
||||||
|
public readonly string ParamName;
|
||||||
|
|
||||||
|
|
||||||
|
public Parameter(string s)
|
||||||
|
{
|
||||||
|
Types = new[] {new Var("parameter") };
|
||||||
|
ParamName = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Parameter(IEnumerable<Type> unified, string paramName)
|
||||||
|
{
|
||||||
|
Types = unified;
|
||||||
|
ParamName = paramName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Evaluate(Context c, params IExpression[] args)
|
||||||
|
{
|
||||||
|
return c?.Parameters?.GetValueOrDefault(ParamName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EvaluateAll(Context c, HashSet<IExpression> addTo)
|
||||||
|
{
|
||||||
|
var v = Evaluate(c);
|
||||||
|
if (v != null)
|
||||||
|
{
|
||||||
|
addTo.Add(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Parameter(unified, ParamName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IExpression Optimize()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Visit(Func<IExpression, bool> f)
|
||||||
|
{
|
||||||
|
f(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ParamName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
AspectedRouting/Functions/Parse.cs
Normal file
48
AspectedRouting/Functions/Parse.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Parse : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } = "Parses a string into a numerical value";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"s"};
|
||||||
|
|
||||||
|
public Parse() : base("parse", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Curry(Typs.String, Typs.Double),
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private Parse(IEnumerable<Type> specializedTypes) : base("parse", specializedTypes)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Parse(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var arg = (string) arguments[0].Evaluate(c);
|
||||||
|
var expectedType = ((Curry) Types.First()).ResultType;
|
||||||
|
switch (expectedType)
|
||||||
|
{
|
||||||
|
case PDoubleType _:
|
||||||
|
case DoubleType _:
|
||||||
|
return double.Parse(arg);
|
||||||
|
default: return int.Parse(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
AspectedRouting/Functions/ProfileMetaData.cs
Normal file
49
AspectedRouting/Functions/ProfileMetaData.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class ProfileMetaData
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public string Description { get; }
|
||||||
|
public string Author { get; }
|
||||||
|
public string Filename { get; }
|
||||||
|
public List<string> VehicleTyps { get; }
|
||||||
|
public List<string> Metadata { get; }
|
||||||
|
|
||||||
|
public Dictionary<string, object> DefaultParameters { get; }
|
||||||
|
public Dictionary<string, Dictionary<string, object>> Profiles { get; }
|
||||||
|
|
||||||
|
public IExpression Access { get; }
|
||||||
|
public IExpression Oneway { get; }
|
||||||
|
public IExpression Speed { get; }
|
||||||
|
public Dictionary<string, IExpression> Weights { get; }
|
||||||
|
|
||||||
|
public ProfileMetaData(string name, string description, string author, string filename,
|
||||||
|
List<string> vehicleTyps, Dictionary<string, object> defaultParameters,
|
||||||
|
Dictionary<string, Dictionary<string, object>> profiles,
|
||||||
|
IExpression access, IExpression oneway, IExpression speed,
|
||||||
|
Dictionary<string, IExpression> weights, List<string> metadata)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Description = description;
|
||||||
|
Author = author;
|
||||||
|
Filename = filename;
|
||||||
|
VehicleTyps = vehicleTyps;
|
||||||
|
Access = access;
|
||||||
|
Oneway = oneway;
|
||||||
|
Speed = speed;
|
||||||
|
Weights = weights;
|
||||||
|
Metadata = metadata;
|
||||||
|
DefaultParameters = defaultParameters;
|
||||||
|
Profiles = profiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"Profile: {Name} {Filename}\naccess={Access}\noneway={Oneway}\nspeed={Speed}\n" +
|
||||||
|
$"weights ={string.Join(" + ", Weights.Select(kv => "#" + kv.Key + " * " + kv.Value))} ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
AspectedRouting/Functions/StringStringToTagsFunction.cs
Normal file
52
AspectedRouting/Functions/StringStringToTagsFunction.cs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a function 'string -> string -> a' onto a function 'tags -> [a]'
|
||||||
|
/// </summary>
|
||||||
|
public class StringStringToTagsFunction : Function
|
||||||
|
{
|
||||||
|
private static Type baseFunction =
|
||||||
|
Curry.ConstructFrom(new Var("a"), Typs.String, Typs.String);
|
||||||
|
|
||||||
|
|
||||||
|
public StringStringToTagsFunction() : base("stringToTags", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Curry(baseFunction, new Curry(Typs.Tags, new ListType(new Var("a"))))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringStringToTagsFunction(IEnumerable<Type> unified) : base("stringToTags", unified)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var f = arguments[0];
|
||||||
|
var tags = (Dictionary<string, string>) arguments[1].Evaluate(c);
|
||||||
|
var result = new List<object>();
|
||||||
|
foreach (var (k, v) in tags)
|
||||||
|
{
|
||||||
|
result.Add(f.Evaluate(c, new Constant(k), new Constant(v)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StringStringToTagsFunction(unified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
79
AspectedRouting/Functions/Sum.cs
Normal file
79
AspectedRouting/Functions/Sum.cs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class Sum : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } = "Sums all the numbers in the given list. If the list contains bool, `yes` or `true` will be considered to equal `1`";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"list"};
|
||||||
|
|
||||||
|
public Sum() : base("sum", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Curry(new ListType(Typs.Nat), Typs.Nat),
|
||||||
|
new Curry(new ListType(Typs.Int), Typs.Int),
|
||||||
|
new Curry(new ListType(Typs.PDouble), Typs.PDouble),
|
||||||
|
new Curry(new ListType(Typs.Double), Typs.Double),
|
||||||
|
new Curry(new ListType(Typs.Bool), Typs.Int),
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Sum(IEnumerable<Type> specializedTypes) : base("max", specializedTypes)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Sum(unified);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o!=null);
|
||||||
|
var expectedType = (Types.First() as Curry).ResultType;
|
||||||
|
|
||||||
|
switch (expectedType)
|
||||||
|
{
|
||||||
|
case BoolType _:
|
||||||
|
var sumB= 0;
|
||||||
|
foreach (var o in ls)
|
||||||
|
{
|
||||||
|
if (o.Equals("yes") || o.Equals("true"))
|
||||||
|
{
|
||||||
|
sumB++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sumB;
|
||||||
|
case DoubleType _:
|
||||||
|
case PDoubleType _:
|
||||||
|
var sum = 0.0;
|
||||||
|
foreach (var o in ls)
|
||||||
|
{
|
||||||
|
sum += (double) o;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
default:
|
||||||
|
var sumI = 1;
|
||||||
|
foreach (var o in ls)
|
||||||
|
{
|
||||||
|
sumI += (int) o;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sumI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
AspectedRouting/Functions/ToString.cs
Normal file
37
AspectedRouting/Functions/ToString.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Functions
|
||||||
|
{
|
||||||
|
public class ToString : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } = "Converts a value into a human readable string";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"obj"};
|
||||||
|
|
||||||
|
public ToString() : base("to_string", true,
|
||||||
|
new[] {new Curry(new Var("a"), Typs.String)})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ToString(IEnumerable<Type> types) : base("to_string", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var a = arguments[0];
|
||||||
|
return a.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ToString(unified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
157
AspectedRouting/IExpression.cs
Normal file
157
AspectedRouting/IExpression.cs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Functions;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting
|
||||||
|
{
|
||||||
|
public class Context
|
||||||
|
{
|
||||||
|
public Dictionary<string, IExpression> Parameters = new Dictionary<string, IExpression>();
|
||||||
|
public Dictionary<string, AspectMetadata> DefinedFunctions = new Dictionary<string, AspectMetadata>();
|
||||||
|
|
||||||
|
public void AddParameter(string name, string value)
|
||||||
|
{
|
||||||
|
Parameters.Add(name, new Constant(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddFunction(string name, AspectMetadata function)
|
||||||
|
{
|
||||||
|
if (Funcs.Builtins.ContainsKey(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Function " + name + " already exists, it is a builtin function");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DefinedFunctions.ContainsKey(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Function " + name + " already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
DefinedFunctions.Add(name, function);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IExpression GetFunction(string name)
|
||||||
|
{
|
||||||
|
if (name.StartsWith("$"))
|
||||||
|
{
|
||||||
|
name = name.Substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Funcs.Builtins.ContainsKey(name))
|
||||||
|
{
|
||||||
|
return Funcs.Builtins[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DefinedFunctions.ContainsKey(name))
|
||||||
|
{
|
||||||
|
return DefinedFunctions[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"The function {name} is not a defined nor builtin function. Known functions are " +
|
||||||
|
string.Join(", ", DefinedFunctions.Keys));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IExpression
|
||||||
|
{
|
||||||
|
IEnumerable<Type> Types { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates the expression.
|
||||||
|
/// Gives null if the expression should not give a value
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
object Evaluate(Context c, params IExpression[] arguments);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a copy of this expression, but only execution paths of the given types are kept
|
||||||
|
/// Return null if no execution paths are found
|
||||||
|
/// </summary>
|
||||||
|
IExpression Specialize(IEnumerable<Type> allowedTypes);
|
||||||
|
|
||||||
|
IExpression Optimize();
|
||||||
|
|
||||||
|
void Visit(Func<IExpression, bool> f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExpressionExtensions
|
||||||
|
{
|
||||||
|
public static IExpression Specialize(this IExpression e, Type t)
|
||||||
|
{
|
||||||
|
if (t == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("Cannot specialize to null");
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.Specialize(new[] {t});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IExpression Specialize(this IExpression e, Dictionary<string, Type> substitutions)
|
||||||
|
{
|
||||||
|
var newTypes = new HashSet<Type>();
|
||||||
|
|
||||||
|
foreach (var oldType in e.Types)
|
||||||
|
{
|
||||||
|
var newType = oldType.Substitute(substitutions);
|
||||||
|
if (newType == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
newTypes.Add(newType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newTypes.Any())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.Specialize(newTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Type> SpecializeToCommonTypes(this IEnumerable<IExpression> exprs,
|
||||||
|
out IEnumerable<IExpression> specializedExpressions)
|
||||||
|
{
|
||||||
|
exprs.SpecializeToCommonTypes(out var types, out specializedExpressions);
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs over all expresions, determines a common ground by unifications
|
||||||
|
/// THen specializes every expression onto this common ground
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The common ground of types</returns>
|
||||||
|
public static IEnumerable<IExpression> SpecializeToCommonTypes(this IEnumerable<IExpression> exprs,
|
||||||
|
out IEnumerable<Type> specializedTypes, out IEnumerable<IExpression> specializedExpressions)
|
||||||
|
{
|
||||||
|
specializedTypes = null;
|
||||||
|
var expressions = exprs.ToList();
|
||||||
|
foreach (var f in expressions)
|
||||||
|
{
|
||||||
|
if (specializedTypes == null)
|
||||||
|
{
|
||||||
|
specializedTypes = f.Types.ToHashSet();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var specialized = specializedTypes.SpecializeTo(f.Types.RenameVars(specializedTypes));
|
||||||
|
if (specialized == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Could not unify\n "
|
||||||
|
+ "<previous items>: " + string.Join(", ", specializedTypes) +
|
||||||
|
"\nwith\n "
|
||||||
|
+ f + ": " + string.Join(", ", f.Types));
|
||||||
|
}
|
||||||
|
|
||||||
|
specializedTypes = specialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tps = specializedTypes;
|
||||||
|
return specializedExpressions = expressions.Select(expr => expr.Specialize(tps));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
121
AspectedRouting/IO/FunctionTestSuite.cs
Normal file
121
AspectedRouting/IO/FunctionTestSuite.cs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Functions;
|
||||||
|
|
||||||
|
namespace AspectedRouting.IO
|
||||||
|
{
|
||||||
|
public class FunctionTestSuite
|
||||||
|
{
|
||||||
|
private readonly AspectMetadata _functionToApply;
|
||||||
|
private readonly IEnumerable<(string expected, Dictionary<string, string> tags)> _tests;
|
||||||
|
|
||||||
|
public static FunctionTestSuite FromString(AspectMetadata function, string csvContents)
|
||||||
|
{
|
||||||
|
var all = csvContents.Split("\n").ToList();
|
||||||
|
var keys = all[0].Split(",").ToList();
|
||||||
|
keys = keys.GetRange(1, keys.Count - 1);
|
||||||
|
|
||||||
|
var tests = new List<(string, Dictionary<string, string>)>();
|
||||||
|
|
||||||
|
foreach (var test in all.GetRange(1, all.Count - 1))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(test.Trim()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var testData = test.Split(",").ToList();
|
||||||
|
var expected = testData[0];
|
||||||
|
var vals = testData.GetRange(1, testData.Count - 1);
|
||||||
|
var tags = new Dictionary<string, string>();
|
||||||
|
for (int i = 0; i < keys.Count; i++)
|
||||||
|
{
|
||||||
|
if (i < vals.Count && !string.IsNullOrEmpty(vals[i]))
|
||||||
|
{
|
||||||
|
tags[keys[i]] = vals[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tests.Add((expected, tags));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FunctionTestSuite(function, tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FunctionTestSuite(
|
||||||
|
AspectMetadata functionToApply,
|
||||||
|
IEnumerable<(string expected, Dictionary<string, string> tags)> tests)
|
||||||
|
{
|
||||||
|
_functionToApply = functionToApply;
|
||||||
|
_tests = tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string ToLua()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
var tests = string.Join("\n", _tests.Select((test, i) => ToLua(i, test.expected, test.tags)));
|
||||||
|
return "\n" + tests + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ToLua(int index, string expected, Dictionary<string, string> tags)
|
||||||
|
{
|
||||||
|
var parameters = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var (key, value) in tags)
|
||||||
|
{
|
||||||
|
if (key.StartsWith("#"))
|
||||||
|
{
|
||||||
|
parameters[key.TrimStart('#')] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (paramName, _) in parameters)
|
||||||
|
{
|
||||||
|
tags.Remove("#" + paramName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var funcName = _functionToApply.Name.Replace(" ", "_").Replace(".", "_");
|
||||||
|
return
|
||||||
|
$"unit_test({funcName}, \"{_functionToApply.Name}\", {index}, \"{expected}\", {parameters.ToLuaTable()}, {tags.ToLuaTable()})";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
var failed = false;
|
||||||
|
var testCase = 0;
|
||||||
|
foreach (var test in _tests)
|
||||||
|
{
|
||||||
|
testCase++;
|
||||||
|
var context = new Context();
|
||||||
|
foreach (var (key, value) in test.tags)
|
||||||
|
{
|
||||||
|
if (key.StartsWith("#"))
|
||||||
|
{
|
||||||
|
context.AddParameter(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var actual = _functionToApply.Evaluate(context, new Constant(test.tags));
|
||||||
|
if (!actual.ToString().Equals(test.expected) &&
|
||||||
|
!(actual is double actualD && Math.Abs(double.Parse(test.expected) - actualD) < 0.0001)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
failed = true;
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[{_functionToApply.Name}] Testcase {testCase} failed:\n Expected: {test.expected}\n actual: {actual}\n tags: {test.tags.Pretty()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Some test failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"[{_functionToApply.Name}] {testCase} tests successful");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
512
AspectedRouting/IO/JsonParser.cs
Normal file
512
AspectedRouting/IO/JsonParser.cs
Normal file
|
@ -0,0 +1,512 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using AspectedRouting.Functions;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using static AspectedRouting.Functions.Funcs;
|
||||||
|
using Type = AspectedRouting.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.IO
|
||||||
|
{
|
||||||
|
public static class JsonParser
|
||||||
|
{
|
||||||
|
public static AspectMetadata AspectFromJson(Context c, string json, string fileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var doc = JsonDocument.Parse(json);
|
||||||
|
if (doc.RootElement.TryGetProperty("defaults", out _))
|
||||||
|
{
|
||||||
|
// this is a profile
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc.RootElement.ParseAspect(fileName, c);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception("In the file " + fileName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProfileMetaData ProfileFromJson(Context c, string json, FileInfo f)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var doc = JsonDocument.Parse(json);
|
||||||
|
if (!doc.RootElement.TryGetProperty("defaults", out _))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
// this is an aspect
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseProfile(doc.RootElement, c, f);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception("In the file " + f, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static readonly IExpression _mconst =
|
||||||
|
new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.Const);
|
||||||
|
|
||||||
|
private static readonly IExpression _mappingWrapper =
|
||||||
|
new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), StringStringToTags);
|
||||||
|
|
||||||
|
private static IExpression ParseMapping(IEnumerable<JsonProperty> allArgs, Context context)
|
||||||
|
{
|
||||||
|
var keys = new List<string>();
|
||||||
|
var exprs = new List<IExpression>();
|
||||||
|
|
||||||
|
foreach (var prop in allArgs)
|
||||||
|
{
|
||||||
|
if (prop.Name.Equals("#"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
keys.Add(prop.Name);
|
||||||
|
var argExpr = ParseExpression(prop.Value, context);
|
||||||
|
IExpression mappingWithOptArg;
|
||||||
|
if (argExpr.Types.Count() == 1 && argExpr.Types.First().Equals(Typs.String))
|
||||||
|
{
|
||||||
|
mappingWithOptArg =
|
||||||
|
Either(Funcs.Id, Funcs.Eq, argExpr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mappingWithOptArg = new Apply(_mconst, argExpr);
|
||||||
|
}
|
||||||
|
|
||||||
|
exprs.Add(mappingWithOptArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var simpleMapping = new Mapping(keys, exprs);
|
||||||
|
return new Apply(_mappingWrapper, simpleMapping);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception("While constructing a mapping with members " + string.Join(", ", exprs) +
|
||||||
|
": " + e.Message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IExpression ParseExpression(this JsonElement e, Context context)
|
||||||
|
{
|
||||||
|
if (e.ValueKind == JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
// Parse an actual function
|
||||||
|
var funcCall = e.EnumerateObject().Where(v => v.Name.StartsWith("$")).ToList();
|
||||||
|
var allArgs = e.EnumerateObject().Where(v => !v.Name.StartsWith("$")).ToList();
|
||||||
|
|
||||||
|
if (funcCall.Count > 2)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Multiple calls defined in object " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (funcCall.Count == 1)
|
||||||
|
{
|
||||||
|
return ParseFunctionCall(context, funcCall, allArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funccall has no elements: this is a mapping of strings or tags onto a value
|
||||||
|
|
||||||
|
return ParseMapping(allArgs, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.ValueKind == JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
var exprs = e.EnumerateArray().Select(json =>
|
||||||
|
Either(Funcs.Id, Funcs.Const, json.ParseExpression(context)));
|
||||||
|
var list = new Constant(exprs);
|
||||||
|
return Either(Funcs.Id, Funcs.ListDot, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.ValueKind == JsonValueKind.Number)
|
||||||
|
{
|
||||||
|
if (e.TryGetDouble(out var d))
|
||||||
|
{
|
||||||
|
return new Constant(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.TryGetInt32(out var i))
|
||||||
|
{
|
||||||
|
return new Constant(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.ValueKind == JsonValueKind.True)
|
||||||
|
{
|
||||||
|
return new Constant(Typs.Bool, "yes");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.ValueKind == JsonValueKind.False)
|
||||||
|
{
|
||||||
|
return new Constant(Typs.Bool, "no");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.ValueKind == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
var s = e.GetString();
|
||||||
|
if (s.StartsWith("$"))
|
||||||
|
{
|
||||||
|
var bi = BuiltinByName(s);
|
||||||
|
|
||||||
|
if (bi != null)
|
||||||
|
{
|
||||||
|
return Either(Funcs.Dot, Funcs.Id, bi);
|
||||||
|
}
|
||||||
|
|
||||||
|
var definedFunc = context.GetFunction(s);
|
||||||
|
return Either(Funcs.Dot, Funcs.Id, new FunctionCall(s, definedFunc.Types));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.StartsWith("#"))
|
||||||
|
{
|
||||||
|
// This is a parameter, the type of it is free
|
||||||
|
return new Parameter(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Constant(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
throw new Exception("Could not parse " + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IExpression ParseFunctionCall(Context context, IReadOnlyCollection<JsonProperty> funcCall,
|
||||||
|
IEnumerable<JsonProperty> allArgs)
|
||||||
|
{
|
||||||
|
var funcName = funcCall.First().Name;
|
||||||
|
|
||||||
|
var func = BuiltinByName(funcName);
|
||||||
|
|
||||||
|
// The list where all the arguments are collected
|
||||||
|
var args = new List<IExpression>();
|
||||||
|
|
||||||
|
|
||||||
|
// First argument of the function is the value of this property, e.g.
|
||||||
|
// { "$f": "xxx", "a2":"yyy", "a3":"zzz" }
|
||||||
|
var firstArgument = ParseExpression(funcCall.First().Value, context);
|
||||||
|
|
||||||
|
|
||||||
|
// Cheat for the very special case 'mustMatch'
|
||||||
|
if (func.Equals(Funcs.MustMatch))
|
||||||
|
{
|
||||||
|
// It gets an extra argument injected
|
||||||
|
var neededKeys = firstArgument.PossibleTags().Keys.ToList();
|
||||||
|
var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
|
||||||
|
args.Add(neededKeysArg);
|
||||||
|
args.Add(firstArgument);
|
||||||
|
return func.Apply(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Add(firstArgument);
|
||||||
|
|
||||||
|
var allExprs = allArgs
|
||||||
|
.Where(kv => !kv.NameEquals("#")) // Leave out comments
|
||||||
|
.ToDictionary(kv => kv.Name, kv => kv.Value.ParseExpression(context));
|
||||||
|
|
||||||
|
|
||||||
|
if (allExprs.Count > 1)
|
||||||
|
{
|
||||||
|
if (func.ArgNames == null || func.ArgNames.Count < 2)
|
||||||
|
throw new ArgumentException("{funcName} does not specify argument names");
|
||||||
|
|
||||||
|
foreach (var argName in func.ArgNames)
|
||||||
|
{
|
||||||
|
args.Add(allExprs[argName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (allExprs.Count == 1)
|
||||||
|
{
|
||||||
|
args.Add(allExprs.Single().Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Either(Funcs.Id, Funcs.Dot, func).Apply(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IExpression GetTopLevelExpression(this JsonElement root, Context context)
|
||||||
|
{
|
||||||
|
IExpression mapping = null;
|
||||||
|
if (root.TryGetProperty("value", out var j))
|
||||||
|
{
|
||||||
|
// The expression is placed in the default 'value' location
|
||||||
|
mapping = j.ParseExpression(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// We search for the function call with '$'
|
||||||
|
foreach (var prop in root.EnumerateObject())
|
||||||
|
{
|
||||||
|
if (!prop.Name.StartsWith("$")) continue;
|
||||||
|
|
||||||
|
|
||||||
|
var f = (IExpression) BuiltinByName(prop.Name);
|
||||||
|
if (f == null)
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException("The builtin function " + f + " was not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
var fArg = prop.Value.ParseExpression(context);
|
||||||
|
|
||||||
|
if (fArg == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Could not type expression " + prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (mapping != null)
|
||||||
|
{
|
||||||
|
// This is probably a firstOrderedVersion, a default, or some other function that should be applied
|
||||||
|
return
|
||||||
|
new Apply(
|
||||||
|
Either(Funcs.Id, Funcs.Dot, new Apply(f, fArg)), mapping
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cheat for the very special case 'mustMatch'
|
||||||
|
if (f.Equals(Funcs.MustMatch))
|
||||||
|
{
|
||||||
|
// It gets an extra argument injected
|
||||||
|
var neededKeys = fArg.PossibleTags().Keys.ToList();
|
||||||
|
var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
|
||||||
|
f = f.Apply(new[] {neededKeysArg});
|
||||||
|
}
|
||||||
|
|
||||||
|
var appliedDot = new Apply(new Apply(Funcs.Dot, f), fArg);
|
||||||
|
var appliedDirect = new Apply(f, fArg);
|
||||||
|
|
||||||
|
if (!appliedDot.Types.Any())
|
||||||
|
{
|
||||||
|
// Applied dot doesn't work out, so we return the other one
|
||||||
|
return appliedDirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!appliedDirect.Types.Any())
|
||||||
|
{
|
||||||
|
return appliedDot;
|
||||||
|
}
|
||||||
|
|
||||||
|
var eithered = new Apply(new Apply(Funcs.EitherFunc, appliedDot), appliedDirect);
|
||||||
|
|
||||||
|
|
||||||
|
// We apply the builtin function through a dot
|
||||||
|
return eithered;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
throw new Exception(
|
||||||
|
"No top level reducer found. Did you forget the '$' in the reducing function? Did your forget 'value' to add the mapping?");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IExpression ParseProfileProperty(JsonElement e, Context c, string property)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var prop = e.GetProperty(property);
|
||||||
|
return ParseExpression(prop, c)
|
||||||
|
.Specialize(new Curry(Typs.Tags, new Var("a")))
|
||||||
|
.Optimize();
|
||||||
|
}
|
||||||
|
catch (Exception exc)
|
||||||
|
{
|
||||||
|
throw new Exception("While parsing the property " + property, exc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, object> ParseParameters(this JsonElement e)
|
||||||
|
{
|
||||||
|
var ps = new Dictionary<string, object>();
|
||||||
|
foreach (var obj in e.EnumerateObject())
|
||||||
|
{
|
||||||
|
var nm = obj.Name.TrimStart('#');
|
||||||
|
switch (obj.Value.ValueKind)
|
||||||
|
{
|
||||||
|
case JsonValueKind.String:
|
||||||
|
ps[nm] = obj.Value.ToString();
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Number:
|
||||||
|
ps[nm] = obj.Value.GetDouble();
|
||||||
|
break;
|
||||||
|
case JsonValueKind.True:
|
||||||
|
ps[nm] = "yes";
|
||||||
|
break;
|
||||||
|
case JsonValueKind.False:
|
||||||
|
ps[nm] = "no";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProfileMetaData ParseProfile(this JsonElement e, Context context, FileInfo filepath)
|
||||||
|
{
|
||||||
|
var name = e.Get("name");
|
||||||
|
var author = e.TryGet("author");
|
||||||
|
if (filepath != null && !(name + ".json").ToLower().Equals(filepath.Name.ToLower()))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Filename does not match the defined name: " +
|
||||||
|
$"filename is {filepath.Name}, declared name is {name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var vehicleTypes = e.GetProperty("vehicletypes").EnumerateArray().Select(
|
||||||
|
el => el.GetString()).ToList();
|
||||||
|
var metadata = e.GetProperty("metadata").EnumerateArray().Select(
|
||||||
|
el => el.GetString()).ToList();
|
||||||
|
|
||||||
|
|
||||||
|
var access = ParseProfileProperty(e, context, "access").Finalize();
|
||||||
|
var oneway = ParseProfileProperty(e, context, "oneway").Finalize();
|
||||||
|
var speed = ParseProfileProperty(e, context, "speed").Finalize();
|
||||||
|
|
||||||
|
|
||||||
|
IExpression TagsApplied(IExpression x)
|
||||||
|
{
|
||||||
|
return new Apply(x, new Constant(new Dictionary<string, string>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.AddFunction("speed",
|
||||||
|
new AspectMetadata(TagsApplied(speed), "speed", "The speed of this profile", author, "", filepath.Name,
|
||||||
|
true));
|
||||||
|
context.AddFunction("access",
|
||||||
|
new AspectMetadata(TagsApplied(access), "access", "The access of this profile", author, "",
|
||||||
|
filepath.Name,
|
||||||
|
true));
|
||||||
|
context.AddFunction("oneway",
|
||||||
|
new AspectMetadata(TagsApplied(oneway), "oneway", "The oneway of this profile", author, "",
|
||||||
|
filepath.Name,
|
||||||
|
true));
|
||||||
|
context.AddFunction("distance",
|
||||||
|
new AspectMetadata(new Constant(1), "distance", "The distance travelled of this profile", author, "",
|
||||||
|
filepath.Name,
|
||||||
|
true));
|
||||||
|
|
||||||
|
|
||||||
|
var weights = new Dictionary<string, IExpression>();
|
||||||
|
var weightProperty = e.GetProperty("weight");
|
||||||
|
foreach (var prop in weightProperty.EnumerateObject())
|
||||||
|
{
|
||||||
|
var parameter = prop.Name.TrimStart('#');
|
||||||
|
var factor = ParseExpression(prop.Value, context).Finalize();
|
||||||
|
weights[parameter] = factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
var profiles = new Dictionary<string, Dictionary<string, object>>();
|
||||||
|
|
||||||
|
foreach (var profile in e.GetProperty("profiles").EnumerateObject())
|
||||||
|
{
|
||||||
|
profiles[profile.Name] = ParseParameters(profile.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ProfileMetaData(
|
||||||
|
name,
|
||||||
|
e.Get("description"),
|
||||||
|
author,
|
||||||
|
filepath?.DirectoryName ?? "unknown",
|
||||||
|
vehicleTypes,
|
||||||
|
e.GetProperty("defaults").ParseParameters(),
|
||||||
|
profiles,
|
||||||
|
access,
|
||||||
|
oneway,
|
||||||
|
speed,
|
||||||
|
weights,
|
||||||
|
metadata
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AspectMetadata ParseAspect(this JsonElement e, string filepath, Context context)
|
||||||
|
{
|
||||||
|
var expr = GetTopLevelExpression(e, context);
|
||||||
|
|
||||||
|
|
||||||
|
var targetTypes = new List<Type>();
|
||||||
|
foreach (var t in expr.Types)
|
||||||
|
{
|
||||||
|
var a = Var.Fresh(t);
|
||||||
|
var b = Var.Fresh(new Curry(a, t));
|
||||||
|
|
||||||
|
if (t.Unify(new Curry(Typs.Tags, a)) != null &&
|
||||||
|
t.Unify(new Curry(Typs.Tags, new Curry(a, b))) == null
|
||||||
|
) // Second should not match
|
||||||
|
{
|
||||||
|
// The target type is 'Tags -> a', where a is NOT a curry
|
||||||
|
targetTypes.Add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetTypes.Count == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("The top level expression has types:\n" +
|
||||||
|
string.Join("\n ", expr.Types) +
|
||||||
|
"\nwhich can not be specialized into a form suiting `tags -> a`\n" + expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
var exprSpec = expr.Specialize(targetTypes);
|
||||||
|
if (expr == null)
|
||||||
|
{
|
||||||
|
throw new Exception("Could not specialize the expression " + expr + " to one of the target types " +
|
||||||
|
string.Join(", ", targetTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
expr = exprSpec.Finalize();
|
||||||
|
|
||||||
|
if (expr.Finalize() == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException("The finalized form of expression `" + exprSpec + "` is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = e.Get("name");
|
||||||
|
if (expr.Types.Count() > 1)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("The aspect " + name + " is ambigous, it matches multiple types: " +
|
||||||
|
string.Join(", ", expr.Types));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filepath != null && !(name + ".json").ToLower().Equals(filepath.ToLower()))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Filename does not match the defined name: " +
|
||||||
|
$"filename is {filepath}, declared name is {name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AspectMetadata(
|
||||||
|
expr,
|
||||||
|
name,
|
||||||
|
e.Get("description"),
|
||||||
|
e.TryGet("author"),
|
||||||
|
e.TryGet("unit"),
|
||||||
|
filepath ?? "unknown"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static string Get(this JsonElement json, string key)
|
||||||
|
{
|
||||||
|
if (json.TryGetProperty(key, out var p))
|
||||||
|
{
|
||||||
|
return p.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException($"The obligated property {key} is missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string TryGet(this JsonElement json, string key)
|
||||||
|
{
|
||||||
|
if (json.TryGetProperty(key, out var p))
|
||||||
|
{
|
||||||
|
return p.GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
AspectedRouting/IO/JsonPrinter.cs
Normal file
42
AspectedRouting/IO/JsonPrinter.cs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Functions;
|
||||||
|
|
||||||
|
namespace AspectedRouting.IO
|
||||||
|
{
|
||||||
|
public static class JsonPrinter
|
||||||
|
{
|
||||||
|
private static string Objs(params (string k, string v)[] stuff)
|
||||||
|
{
|
||||||
|
return "{\n" + string.Join("\n", stuff.Where(kv => kv.v != null).Select(kv => kv.k.Dq() + ": " + kv.v)).Indent() + "\n}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Arr(IEnumerable<string> strs)
|
||||||
|
{
|
||||||
|
return "[" + string.Join(", ", strs) + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Dq(this string s)
|
||||||
|
{
|
||||||
|
if (s == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '"' + s + '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Print(AspectMetadata meta)
|
||||||
|
{
|
||||||
|
return Objs(
|
||||||
|
("name", meta.Name.Dq()),
|
||||||
|
("description", meta.Description.Dq()),
|
||||||
|
("unit", meta.Unit.Dq()),
|
||||||
|
("author", meta.Author.Dq()),
|
||||||
|
("file", meta.Filepath.Dq()),
|
||||||
|
("type", Arr(meta.Types.Select(tp => tp.ToString().Dq()))),
|
||||||
|
("value", meta.ExpressionImplementation.ToString())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
535
AspectedRouting/IO/LuaPrinter.cs
Normal file
535
AspectedRouting/IO/LuaPrinter.cs
Normal file
|
@ -0,0 +1,535 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using AspectedRouting.Functions;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
using static AspectedRouting.Deconstruct;
|
||||||
|
|
||||||
|
namespace AspectedRouting.IO
|
||||||
|
{
|
||||||
|
public class LuaPrinter
|
||||||
|
{
|
||||||
|
public static Dictionary<string, string> BasicFunctions = _basicFunctions();
|
||||||
|
|
||||||
|
|
||||||
|
private readonly HashSet<string> _deps = new HashSet<string>();
|
||||||
|
private readonly List<string> _code = new List<string>();
|
||||||
|
private readonly HashSet<string> _neededKeys = new HashSet<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A dictionary containing the implementation of basic functions
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static Dictionary<string, string> _basicFunctions()
|
||||||
|
{
|
||||||
|
var imps = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
var functionsToFetch = Funcs.BuiltinNames.ToList();
|
||||||
|
// These functions should be loaded from disk, but are not necessarily included
|
||||||
|
functionsToFetch.Add("table_to_list");
|
||||||
|
functionsToFetch.Add("debug_table");
|
||||||
|
functionsToFetch.Add("unitTest");
|
||||||
|
functionsToFetch.Add("unitTestProfile");
|
||||||
|
functionsToFetch.Add("double_compare");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var name in functionsToFetch)
|
||||||
|
{
|
||||||
|
var path = $"IO/lua/{name}.lua";
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
imps[name] = File.ReadAllText(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return imps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LuaPrinter()
|
||||||
|
{
|
||||||
|
_deps.Add("debug_table");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private string ToLua(IExpression bare, Context context, string key = "nil")
|
||||||
|
{
|
||||||
|
var collectedMapping = new List<IExpression>();
|
||||||
|
var order = new List<IExpression>();
|
||||||
|
|
||||||
|
|
||||||
|
if (UnApply(
|
||||||
|
UnApply(
|
||||||
|
IsFunc(Funcs.FirstOf),
|
||||||
|
Assign(order))
|
||||||
|
, UnApply(
|
||||||
|
IsFunc(Funcs.StringStringToTags),
|
||||||
|
Assign(collectedMapping))
|
||||||
|
).Invoke(bare))
|
||||||
|
{
|
||||||
|
_deps.Add(Funcs.FirstOf.Name);
|
||||||
|
return "first_match_of(tags, result, \n" +
|
||||||
|
" " + ToLua(order.First(), context, key) + "," +
|
||||||
|
("\n" + MappingToLua((Mapping) collectedMapping.First(), context)).Indent().Indent() +
|
||||||
|
")";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UnApply(
|
||||||
|
UnApply(
|
||||||
|
IsFunc(Funcs.MustMatch),
|
||||||
|
Assign(order))
|
||||||
|
, UnApply(
|
||||||
|
IsFunc(Funcs.StringStringToTags),
|
||||||
|
Assign(collectedMapping))
|
||||||
|
).Invoke(bare))
|
||||||
|
{
|
||||||
|
_deps.Add(Funcs.MustMatch.Name);
|
||||||
|
return "must_match(tags, result, \n" +
|
||||||
|
" " + ToLua(order.First(), context, key) + "," +
|
||||||
|
("\n" + MappingToLua((Mapping) collectedMapping.First(), context)).Indent().Indent() +
|
||||||
|
")";
|
||||||
|
}
|
||||||
|
|
||||||
|
var collectedList = new List<IExpression>();
|
||||||
|
var func = new List<IExpression>();
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
UnApply(
|
||||||
|
UnApply(IsFunc(Funcs.Dot), Assign(func)),
|
||||||
|
UnApply(IsFunc(Funcs.ListDot),
|
||||||
|
Assign(collectedList))).Invoke(bare))
|
||||||
|
{
|
||||||
|
var exprs = (IEnumerable<IExpression>) ((Constant) collectedList.First()).Evaluate(context);
|
||||||
|
var luaExprs = new List<string>();
|
||||||
|
var funcName = func.First().ToString().TrimStart('$');
|
||||||
|
_deps.Add(funcName);
|
||||||
|
foreach (var expr in exprs)
|
||||||
|
{
|
||||||
|
var c = new List<IExpression>();
|
||||||
|
if (UnApply(IsFunc(Funcs.Const), Assign(c)).Invoke(expr))
|
||||||
|
{
|
||||||
|
luaExprs.Add(ToLua(c.First(), context, key));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expr.Types.First() is Curry curry
|
||||||
|
&& curry.ArgType.Equals(Typs.Tags))
|
||||||
|
{
|
||||||
|
var lua = ToLua(expr, context, key);
|
||||||
|
luaExprs.Add(lua);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "\n " + funcName + "({\n " + string.Join(",\n ", luaExprs) +
|
||||||
|
"\n })";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
collectedMapping.Clear();
|
||||||
|
var dottedFunction = new List<IExpression>();
|
||||||
|
|
||||||
|
|
||||||
|
dottedFunction.Clear();
|
||||||
|
|
||||||
|
if (UnApply(
|
||||||
|
UnApply(
|
||||||
|
IsFunc(Funcs.Dot),
|
||||||
|
Assign(dottedFunction)
|
||||||
|
),
|
||||||
|
UnApply(
|
||||||
|
IsFunc(Funcs.StringStringToTags),
|
||||||
|
Assign(collectedMapping))).Invoke(bare)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var mapping = (Mapping) collectedMapping.First();
|
||||||
|
var baseFunc = (Function) dottedFunction.First();
|
||||||
|
_deps.Add(baseFunc.Name);
|
||||||
|
_deps.Add("table_to_list");
|
||||||
|
|
||||||
|
return baseFunc.Name +
|
||||||
|
"(table_to_list(tags, result, " +
|
||||||
|
("\n" + MappingToLua(mapping, context)).Indent().Indent() +
|
||||||
|
"))";
|
||||||
|
}
|
||||||
|
|
||||||
|
// The expression might be a function which still expects a string (the value from the tag) as argument
|
||||||
|
if (!(bare is Mapping) &&
|
||||||
|
bare.Types.First() is Curry curr &&
|
||||||
|
curr.ArgType.Equals(Typs.String))
|
||||||
|
{
|
||||||
|
var applied = new Apply(bare, new Constant(curr.ArgType, ("tags", "\"" + key + "\"")));
|
||||||
|
return ToLua(applied.Optimize(), context, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The expression might consist of multiple nested functions
|
||||||
|
var fArgs = bare.DeconstructApply();
|
||||||
|
if (fArgs != null)
|
||||||
|
{
|
||||||
|
var (f, args) = fArgs.Value;
|
||||||
|
var baseFunc = (Function) f;
|
||||||
|
_deps.Add(baseFunc.Name);
|
||||||
|
|
||||||
|
return baseFunc.Name + "(" + string.Join(", ", args.Select(arg => ToLua(arg, context, key))) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var collected = new List<IExpression>();
|
||||||
|
switch (bare)
|
||||||
|
{
|
||||||
|
case FunctionCall fc:
|
||||||
|
var called = context.DefinedFunctions[fc.CalledFunctionName];
|
||||||
|
if (called.ProfileInternal)
|
||||||
|
{
|
||||||
|
return called.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddFunction(called, context);
|
||||||
|
return $"{fc.CalledFunctionName.FunctionName()}(parameters, tags, result)";
|
||||||
|
case Constant c:
|
||||||
|
return ConstantToLua(c, context);
|
||||||
|
case Mapping m:
|
||||||
|
return MappingToLua(m, context).Indent();
|
||||||
|
case Function f:
|
||||||
|
var fName = f.Name.TrimStart('$');
|
||||||
|
|
||||||
|
if (Funcs.Builtins.ContainsKey(fName))
|
||||||
|
{
|
||||||
|
_deps.Add(f.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var definedFunc = context.DefinedFunctions[fName];
|
||||||
|
if (definedFunc.ProfileInternal)
|
||||||
|
{
|
||||||
|
return f.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddFunction(definedFunc, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.Name;
|
||||||
|
case Apply a when UnApply(IsFunc(Funcs.Const), Assign(collected)).Invoke(a):
|
||||||
|
return ToLua(collected.First(), context, key);
|
||||||
|
|
||||||
|
case Parameter p:
|
||||||
|
return $"parameters[\"{p.ParamName.FunctionName()}\"]";
|
||||||
|
default:
|
||||||
|
throw new Exception("Could not convert " + bare + " to a lua expression");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static string ConstantToLua(Constant c, Context context)
|
||||||
|
{
|
||||||
|
var o = c.Evaluate(context);
|
||||||
|
switch (o)
|
||||||
|
{
|
||||||
|
case IExpression e:
|
||||||
|
return ConstantToLua(new Constant(e.Types.First(), e.Evaluate(null)), context);
|
||||||
|
case int i:
|
||||||
|
return "" + i;
|
||||||
|
case double d:
|
||||||
|
return "" + d;
|
||||||
|
case string s:
|
||||||
|
return '"' + s.Replace("\"", "\\\"") + '"';
|
||||||
|
case ValueTuple<string, string> unpack:
|
||||||
|
return unpack.Item1 + "[" + unpack.Item2 + "]";
|
||||||
|
case IEnumerable<object> ls:
|
||||||
|
var t = (c.Types.First() as ListType).InnerType;
|
||||||
|
return "{" + string.Join(", ", ls.Select(obj =>
|
||||||
|
{
|
||||||
|
var objInConstant = new Constant(t, obj);
|
||||||
|
if (obj is Constant asConstant)
|
||||||
|
{
|
||||||
|
objInConstant = asConstant;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConstantToLua(objInConstant, context);
|
||||||
|
})) + "}";
|
||||||
|
default:
|
||||||
|
return o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MappingToLua(Mapping m, Context context)
|
||||||
|
{
|
||||||
|
var contents = m.StringToResultFunctions.Select(kv =>
|
||||||
|
{
|
||||||
|
var (key, expr) = kv;
|
||||||
|
var left = "[\"" + key + "\"]";
|
||||||
|
|
||||||
|
if (Regex.IsMatch(key, "^[a-zA-Z][_a-zA-Z-9]*$"))
|
||||||
|
{
|
||||||
|
left = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return left + " = " + ToLua(expr, context, key);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return
|
||||||
|
"{\n " +
|
||||||
|
string.Join(",\n ", contents) +
|
||||||
|
"\n}";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private HashSet<string> addFunctions = new HashSet<string>();
|
||||||
|
|
||||||
|
public void AddFunction(AspectMetadata meta, Context context)
|
||||||
|
{
|
||||||
|
if (addFunctions.Contains(meta.Name))
|
||||||
|
{
|
||||||
|
// already added
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addFunctions.Add(meta.Name);
|
||||||
|
|
||||||
|
var possibleTags = meta.PossibleTags();
|
||||||
|
var usedParams = meta.UsedParameters();
|
||||||
|
var numberOfCombinations = possibleTags.Values.Select(lst => 1 + lst.Count).Multiply();
|
||||||
|
var impl = string.Join("\n",
|
||||||
|
"--[[",
|
||||||
|
meta.Description,
|
||||||
|
"",
|
||||||
|
"Unit: " + meta.Unit,
|
||||||
|
"Created by " + meta.Author,
|
||||||
|
"Originally defined in " + meta.Filepath,
|
||||||
|
"Uses tags: " + string.Join(", ", possibleTags.Keys),
|
||||||
|
"Used parameters: " + string.Join(", ", usedParams),
|
||||||
|
"Number of combintations: " + numberOfCombinations,
|
||||||
|
"Returns values: ",
|
||||||
|
"]]",
|
||||||
|
"function " + meta.Name.FunctionName() + "(parameters, tags, result)",
|
||||||
|
" return " + ToLua(meta.ExpressionImplementation, context),
|
||||||
|
"end"
|
||||||
|
);
|
||||||
|
|
||||||
|
_code.Add(impl);
|
||||||
|
foreach (var k in possibleTags.Keys)
|
||||||
|
{
|
||||||
|
_neededKeys.Add(k); // To generate a whitelist of OSM-keys that should be kept
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CreateMembershipPreprocessor()
|
||||||
|
{
|
||||||
|
|
||||||
|
return "";
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the necessary called functions and the profile main entry point
|
||||||
|
/// </summary>
|
||||||
|
public void CreateProfile(ProfileMetaData profile, Context context)
|
||||||
|
{
|
||||||
|
var defaultParameters = "\n";
|
||||||
|
foreach (var (name, (types, inFunction)) in profile.UsedParameters(context))
|
||||||
|
{
|
||||||
|
defaultParameters += $"{name}: {string.Join(", ", types)}\n" +
|
||||||
|
$" Used in {inFunction}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var impl = string.Join("\n",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
$"name = \"{profile.Name}\"",
|
||||||
|
"normalize = false",
|
||||||
|
"vehicle_type = {" + string.Join(", ", profile.VehicleTyps.Select(s => "\"" + s + "\"")) + "}",
|
||||||
|
"meta_whitelist = {" + string.Join(", ", profile.Metadata.Select(s => "\"" + s + "\"")) + "}",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"--[[",
|
||||||
|
profile.Name,
|
||||||
|
"This is the main function called to calculate the access, oneway and speed.",
|
||||||
|
"Comfort is calculated as well, based on the parameters which are padded in",
|
||||||
|
"",
|
||||||
|
"Created by " + profile.Author,
|
||||||
|
"Originally defined in " + profile.Filename,
|
||||||
|
"Used parameters: " + defaultParameters.Indent(),
|
||||||
|
"]]",
|
||||||
|
"function " + profile.Name + "(parameters, tags, result)",
|
||||||
|
"",
|
||||||
|
" -- initialize the result table on the default values",
|
||||||
|
" result.access = 0",
|
||||||
|
" result.speed = 0",
|
||||||
|
" result.factor = 1",
|
||||||
|
" result.direction = 0",
|
||||||
|
" result.canstop = true",
|
||||||
|
" result.attributes_to_keep = {}",
|
||||||
|
"",
|
||||||
|
" local access = " + ToLua(profile.Access, context),
|
||||||
|
" if (access == nil or access == \"no\") then",
|
||||||
|
" return",
|
||||||
|
" end",
|
||||||
|
" local oneway = " + ToLua(profile.Oneway, context),
|
||||||
|
" local speed = " + ToLua(profile.Speed, context),
|
||||||
|
" local distance = 1 -- the weight per meter for distance travelled is, well, 1m/m",
|
||||||
|
"");
|
||||||
|
|
||||||
|
impl +=
|
||||||
|
"\n local weight = \n ";
|
||||||
|
|
||||||
|
var weightParts = new List<string>();
|
||||||
|
foreach (var (parameterName, expression) in profile.Weights)
|
||||||
|
{
|
||||||
|
var weightPart = ToLua(new Parameter(parameterName), context) + " * ";
|
||||||
|
|
||||||
|
var subs = new Curry(Typs.Tags, new Var(("a"))).UnificationTable(expression.Types.First());
|
||||||
|
if (subs != null && subs.TryGetValue("$a", out var resultType) &&
|
||||||
|
(resultType.Equals(Typs.Bool) || resultType.Equals(Typs.String)))
|
||||||
|
{
|
||||||
|
weightPart += "parse(" + ToLua(expression, context) + ")";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
weightPart += ToLua(expression, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
weightParts.Add(weightPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl += string.Join(" + \n ", weightParts);
|
||||||
|
|
||||||
|
impl += string.Join("\n",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
" -- put all the values into the result-table, as needed for itinero",
|
||||||
|
" result.access = 1",
|
||||||
|
" result.speed = speed",
|
||||||
|
" result.factor = 1/weight",
|
||||||
|
"",
|
||||||
|
" if (oneway == \"both\") then",
|
||||||
|
" result.oneway = 0",
|
||||||
|
" elseif (oneway == \"with\") then",
|
||||||
|
" result.oneway = 1",
|
||||||
|
" else",
|
||||||
|
" result.oneway = 2",
|
||||||
|
" end",
|
||||||
|
"",
|
||||||
|
"end",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"function default_parameters()",
|
||||||
|
" return " + profile.DefaultParameters.ToLuaTable(),
|
||||||
|
"end",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
impl += "\n\n" + CreateMembershipPreprocessor() + "\n\n";
|
||||||
|
|
||||||
|
|
||||||
|
var profiles = new List<string>();
|
||||||
|
foreach (var (name, subParams) in profile.Profiles)
|
||||||
|
{
|
||||||
|
var functionName = profile.Name + "_" + name;
|
||||||
|
|
||||||
|
subParams.TryGetValue("description", out var description);
|
||||||
|
profiles.Add(
|
||||||
|
string.Join(",\n ",
|
||||||
|
$" name = \"{name}\"",
|
||||||
|
" function_name = \"profile_" + functionName + "\"",
|
||||||
|
" metric = \"custom\""
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
impl += string.Join("\n",
|
||||||
|
"",
|
||||||
|
"--[[",
|
||||||
|
description,
|
||||||
|
"]]",
|
||||||
|
"function profile_" + functionName + "(tags, result)",
|
||||||
|
" local parameters = default_parameters()",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (var (paramName, value) in subParams)
|
||||||
|
{
|
||||||
|
impl += $" parameters.{paramName.TrimStart('#').FunctionName()} = {value.Pretty()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
impl += " " + profile.Name + "(parameters, tags, result)\n";
|
||||||
|
impl += "end\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
impl += "\n\n\n";
|
||||||
|
impl += "profiles = {\n {\n" +
|
||||||
|
string.Join("\n },\n {\n ", profiles) + "\n }\n}";
|
||||||
|
|
||||||
|
_code.Add(impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToLua()
|
||||||
|
{
|
||||||
|
var deps = _deps.ToList();
|
||||||
|
deps.Add("unitTest");
|
||||||
|
deps.Add("unitTestProfile");
|
||||||
|
deps.Add("inv");
|
||||||
|
deps.Add("double_compare");
|
||||||
|
deps.Sort();
|
||||||
|
var code = deps.Select(d => BasicFunctions[d]).ToList();
|
||||||
|
|
||||||
|
var keys = _neededKeys.Select(key => "\"" + key + "\"");
|
||||||
|
code.Add("\n\nprofile_whitelist = {" + string.Join(", ", keys) + "}");
|
||||||
|
|
||||||
|
code.AddRange(_code);
|
||||||
|
|
||||||
|
|
||||||
|
return string.Join("\n\n\n", code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class StringExtensions
|
||||||
|
{
|
||||||
|
public static string ToLuaTable(this Dictionary<string, string> tags)
|
||||||
|
{
|
||||||
|
var contents = tags.Select(kv =>
|
||||||
|
{
|
||||||
|
var (key, value) = kv;
|
||||||
|
var left = "[\"" + key + "\"]";
|
||||||
|
|
||||||
|
if (Regex.IsMatch(key, "^[a-zA-Z][_a-zA-Z-9]*$"))
|
||||||
|
{
|
||||||
|
left = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{left} = \"{value}\"";
|
||||||
|
});
|
||||||
|
return "{" + string.Join(", ", contents) + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToLuaTable(this Dictionary<string, object> tags)
|
||||||
|
{
|
||||||
|
var contents = tags.Select(kv =>
|
||||||
|
{
|
||||||
|
var (key, value) = kv;
|
||||||
|
var left = "[\"" + key + "\"]";
|
||||||
|
|
||||||
|
if (Regex.IsMatch(key, "^[a-zA-Z][_a-zA-Z-9]*$"))
|
||||||
|
{
|
||||||
|
left = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{left} = {value.Pretty()}";
|
||||||
|
});
|
||||||
|
return "{" + string.Join(", ", contents) + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string FunctionName(this string s)
|
||||||
|
{
|
||||||
|
return s.Replace(" ", "_").Replace(".", "_").Replace("-", "_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
122
AspectedRouting/IO/MdPrinter.cs
Normal file
122
AspectedRouting/IO/MdPrinter.cs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Functions;
|
||||||
|
using AspectedRouting.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.IO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Prints function signatures and profile explanations to file
|
||||||
|
/// </summary>
|
||||||
|
public static class MdPrinter
|
||||||
|
{
|
||||||
|
public static void GenerateHelpText(string saveTo = null)
|
||||||
|
{
|
||||||
|
var helpText = TypeOverview +
|
||||||
|
FunctionOverview;
|
||||||
|
|
||||||
|
if (saveTo == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine(helpText);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
File.WriteAllText(saveTo, helpText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string FunctionOverview
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var txt = "## Builtin functions\n\n";
|
||||||
|
foreach (var biFunc in Functions.Funcs.BuiltinNames)
|
||||||
|
{
|
||||||
|
txt += "- " + biFunc + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
txt += "\n\n";
|
||||||
|
txt += "### Function overview\n\n";
|
||||||
|
|
||||||
|
foreach (var (name, func) in Funcs.Builtins)
|
||||||
|
{
|
||||||
|
txt += "#### " + name + "\n\n";
|
||||||
|
txt += func.ArgTable();
|
||||||
|
|
||||||
|
txt += "\n\n" + func.Description + "\n\n";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var lua = File.ReadAllText("IO/lua/" + func.Name + ".lua");
|
||||||
|
txt += $"\n\nLua implementation:\n\n````lua\n{lua}\n````\n\n\n";
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Could not load lua for {func.Name}: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return txt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ArgTable(this Function f)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var args = f.ArgBreakdown();
|
||||||
|
var header = "Argument name | ";
|
||||||
|
var line = "--------------| - ";
|
||||||
|
for (int i = 0; i < f.Types.Count(); i++)
|
||||||
|
{
|
||||||
|
header += "| ";
|
||||||
|
line += "| - ";
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = "";
|
||||||
|
foreach (var n in f.ArgNames)
|
||||||
|
{
|
||||||
|
lines += $"**{n}** | ";
|
||||||
|
foreach (var t in args[n])
|
||||||
|
{
|
||||||
|
lines += (t?.ToString() ?? "_none_") + "\t | ";
|
||||||
|
}
|
||||||
|
|
||||||
|
lines += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
lines += $"_return type_ | ";
|
||||||
|
foreach (var t in args[""])
|
||||||
|
{
|
||||||
|
lines += (t.ToString() ?? "_none_") + "\t | ";
|
||||||
|
}
|
||||||
|
|
||||||
|
lines += "\n";
|
||||||
|
|
||||||
|
return $"{header}\n{line}\n{lines}";
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(f.Name + ": " + e.Message);
|
||||||
|
return string.Join("\n", f.Types.Select(t => "- " + t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static string TypeOverview
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var txt = "## Types\n\n";
|
||||||
|
foreach (var biType in Typs.BuiltinTypes)
|
||||||
|
{
|
||||||
|
txt += "- " + biType.Name + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return txt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
135
AspectedRouting/IO/ProfileTestSuite.cs
Normal file
135
AspectedRouting/IO/ProfileTestSuite.cs
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Functions;
|
||||||
|
|
||||||
|
namespace AspectedRouting.IO
|
||||||
|
{
|
||||||
|
public struct Expected
|
||||||
|
{
|
||||||
|
public int Access, Oneway;
|
||||||
|
public double Speed, Weight;
|
||||||
|
|
||||||
|
public Expected(int access, int oneway, double speed, double weight)
|
||||||
|
{
|
||||||
|
Access = access;
|
||||||
|
Oneway = oneway;
|
||||||
|
Speed = speed;
|
||||||
|
Weight = weight;
|
||||||
|
if (Access == 0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProfileTestSuite
|
||||||
|
{
|
||||||
|
private readonly ProfileMetaData _profile;
|
||||||
|
private readonly string _profileName;
|
||||||
|
private readonly IEnumerable<(Expected, Dictionary<string, string> tags)> _tests;
|
||||||
|
|
||||||
|
public static ProfileTestSuite FromString(ProfileMetaData function, string profileName, string csvContents)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var all = csvContents.Split("\n").ToList();
|
||||||
|
var keys = all[0].Split(",").ToList();
|
||||||
|
keys = keys.GetRange(4, keys.Count - 4).Select(k => k.Trim()).ToList();
|
||||||
|
|
||||||
|
var tests = new List<(Expected, Dictionary<string, string>)>();
|
||||||
|
|
||||||
|
var line = 1;
|
||||||
|
foreach (var test in all.GetRange(1, all.Count - 1))
|
||||||
|
{
|
||||||
|
line++;
|
||||||
|
if (string.IsNullOrEmpty(test.Trim()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var testData = test.Split(",").ToList();
|
||||||
|
var expected = new Expected(
|
||||||
|
int.Parse(testData[0]),
|
||||||
|
int.Parse(testData[1]),
|
||||||
|
double.Parse(testData[2]),
|
||||||
|
double.Parse(testData[3])
|
||||||
|
);
|
||||||
|
var vals = testData.GetRange(4, testData.Count - 4);
|
||||||
|
var tags = new Dictionary<string, string>();
|
||||||
|
for (int i = 0; i < keys.Count; i++)
|
||||||
|
{
|
||||||
|
if (i < vals.Count && !string.IsNullOrEmpty(vals[i]))
|
||||||
|
{
|
||||||
|
tags[keys[i]] = vals[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tests.Add((expected, tags));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception("On line " + line, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ProfileTestSuite(function, profileName, tests);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception("In the profile test file for " + profileName, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProfileTestSuite(
|
||||||
|
ProfileMetaData profile,
|
||||||
|
string profileName,
|
||||||
|
IEnumerable<(Expected, Dictionary<string, string> tags)> tests)
|
||||||
|
{
|
||||||
|
_profile = profile;
|
||||||
|
_profileName = profileName;
|
||||||
|
_tests = tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string ToLua()
|
||||||
|
{
|
||||||
|
var tests = string.Join("\n",
|
||||||
|
_tests.Select((test, i) => ToLua(i, test.Item1, test.tags)));
|
||||||
|
return tests + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ToLua(int index, Expected expected, Dictionary<string, string> tags)
|
||||||
|
{
|
||||||
|
var parameters = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var (key, value) in tags)
|
||||||
|
{
|
||||||
|
if (key.StartsWith("#"))
|
||||||
|
{
|
||||||
|
parameters[key.TrimStart('#')] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (paramName, _) in parameters)
|
||||||
|
{
|
||||||
|
tags.Remove("#" + paramName);
|
||||||
|
}
|
||||||
|
// function unit_test_profile(profile_function, profile_name, index, expected, tags)
|
||||||
|
|
||||||
|
return $"unit_test_profile(profile_bicycle_{_profileName.FunctionName()}, " +
|
||||||
|
$"\"{_profileName}\", " +
|
||||||
|
$"{index}, " +
|
||||||
|
$"{{access = {expected.Access}, speed = {expected.Speed}, oneway = {expected.Oneway}, weight = {expected.Weight} }}, " +
|
||||||
|
tags.ToLuaTable() +
|
||||||
|
")";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
AspectedRouting/IO/lua/all.lua
Normal file
13
AspectedRouting/IO/lua/all.lua
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
function all(list)
|
||||||
|
for _, value in ipairs(list) do
|
||||||
|
if (value == nil) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if(value ~= "yes" and value ~= "true") then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true;
|
||||||
|
end
|
4
AspectedRouting/IO/lua/asNumber.lua
Normal file
4
AspectedRouting/IO/lua/asNumber.lua
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
function as_number(a)
|
||||||
|
|
||||||
|
|
||||||
|
end
|
3
AspectedRouting/IO/lua/concat.lua
Normal file
3
AspectedRouting/IO/lua/concat.lua
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
function concat(a, b)
|
||||||
|
return a .. b
|
||||||
|
end
|
0
AspectedRouting/IO/lua/const.lua
Normal file
0
AspectedRouting/IO/lua/const.lua
Normal file
0
AspectedRouting/IO/lua/constRight.lua
Normal file
0
AspectedRouting/IO/lua/constRight.lua
Normal file
14
AspectedRouting/IO/lua/debug_table.lua
Normal file
14
AspectedRouting/IO/lua/debug_table.lua
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
function debug_table(table, prefix)
|
||||||
|
if (prefix == nil) then
|
||||||
|
prefix = ""
|
||||||
|
end
|
||||||
|
for k, v in pairs(table) do
|
||||||
|
|
||||||
|
if (type(v) == "table") then
|
||||||
|
debug_table(v, " ")
|
||||||
|
else
|
||||||
|
print(prefix .. tostring(k) .. " = " .. tostring(v))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print("")
|
||||||
|
end
|
6
AspectedRouting/IO/lua/default.lua
Normal file
6
AspectedRouting/IO/lua/default.lua
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
function default(defaultValue, realValue)
|
||||||
|
if(realValue ~= nil) then
|
||||||
|
return realValue
|
||||||
|
end
|
||||||
|
return defaultValue
|
||||||
|
end
|
0
AspectedRouting/IO/lua/dot.lua
Normal file
0
AspectedRouting/IO/lua/dot.lua
Normal file
10
AspectedRouting/IO/lua/double_compare.lua
Normal file
10
AspectedRouting/IO/lua/double_compare.lua
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
function double_compare(a, b)
|
||||||
|
if (type(a) ~= "number") then
|
||||||
|
a = parse(a)
|
||||||
|
end
|
||||||
|
|
||||||
|
if(type(b) ~= "number") then
|
||||||
|
b = parse(b)
|
||||||
|
end
|
||||||
|
return math.abs(a - b) > 0.001
|
||||||
|
end
|
0
AspectedRouting/IO/lua/eitherFunc.lua
Normal file
0
AspectedRouting/IO/lua/eitherFunc.lua
Normal file
7
AspectedRouting/IO/lua/eq.lua
Normal file
7
AspectedRouting/IO/lua/eq.lua
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
function eq(a, b)
|
||||||
|
if (a == b) then
|
||||||
|
return "yes"
|
||||||
|
else
|
||||||
|
return "no"
|
||||||
|
end
|
||||||
|
end
|
20
AspectedRouting/IO/lua/firstMatchOf.lua
Normal file
20
AspectedRouting/IO/lua/firstMatchOf.lua
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
function first_match_of(tags, result, order_of_keys, table)
|
||||||
|
for _, key in ipairs(order_of_keys) do
|
||||||
|
local v = tags[key]
|
||||||
|
if (v ~= nil) then
|
||||||
|
|
||||||
|
local mapping = table[key]
|
||||||
|
if (type(mapping) == "table") then
|
||||||
|
local resultValue = mapping[v]
|
||||||
|
if (v ~= nil) then
|
||||||
|
result.attributes_to_keep[key] = v
|
||||||
|
return resultValue
|
||||||
|
end
|
||||||
|
else
|
||||||
|
result.attributes_to_keep[key] = v
|
||||||
|
return mapping
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil;
|
||||||
|
end
|
3
AspectedRouting/IO/lua/id.lua
Normal file
3
AspectedRouting/IO/lua/id.lua
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
function id(v)
|
||||||
|
return v
|
||||||
|
end
|
1
AspectedRouting/IO/lua/if.lua
Normal file
1
AspectedRouting/IO/lua/if.lua
Normal file
|
@ -0,0 +1 @@
|
||||||
|
-- not actually used, should be if_then_else
|
7
AspectedRouting/IO/lua/if_then_else.lua
Normal file
7
AspectedRouting/IO/lua/if_then_else.lua
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
function if_then_else(condition, thn, els)
|
||||||
|
if (condition) then
|
||||||
|
return thn
|
||||||
|
else
|
||||||
|
return els -- if no third parameter is given, 'els' will be nil
|
||||||
|
end
|
||||||
|
end
|
3
AspectedRouting/IO/lua/inv.lua
Normal file
3
AspectedRouting/IO/lua/inv.lua
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
function inv(n)
|
||||||
|
return 1/n
|
||||||
|
end
|
2
AspectedRouting/IO/lua/listDot.lua
Normal file
2
AspectedRouting/IO/lua/listDot.lua
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
-- TODO
|
||||||
|
-- listDot
|
12
AspectedRouting/IO/lua/max.lua
Normal file
12
AspectedRouting/IO/lua/max.lua
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
function max(list)
|
||||||
|
local max
|
||||||
|
for _, value in ipairs(list) do
|
||||||
|
if (max == nil) then
|
||||||
|
max = value
|
||||||
|
elseif (max < value) then
|
||||||
|
max = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return max;
|
||||||
|
end
|
3
AspectedRouting/IO/lua/memberOf.lua
Normal file
3
AspectedRouting/IO/lua/memberOf.lua
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
function member_of()
|
||||||
|
???
|
||||||
|
end
|
12
AspectedRouting/IO/lua/min.lua
Normal file
12
AspectedRouting/IO/lua/min.lua
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
function min(list)
|
||||||
|
local min
|
||||||
|
for _, value in ipairs(list) do
|
||||||
|
if (min == nil) then
|
||||||
|
min = value
|
||||||
|
elseif (min > value) then
|
||||||
|
min = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return min;
|
||||||
|
end
|
7
AspectedRouting/IO/lua/multiply.lua
Normal file
7
AspectedRouting/IO/lua/multiply.lua
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
function multiply(list)
|
||||||
|
local factor = 1
|
||||||
|
for _, value in ipairs(list) do
|
||||||
|
factor = factor * value
|
||||||
|
end
|
||||||
|
return factor;
|
||||||
|
end
|
25
AspectedRouting/IO/lua/mustMatch.lua
Normal file
25
AspectedRouting/IO/lua/mustMatch.lua
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
function must_match(tags, result, needed_keys, table)
|
||||||
|
local result_list = {}
|
||||||
|
for _, key in ipairs(needed_keys) do
|
||||||
|
local v = tags[key]
|
||||||
|
if (v == nil) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local mapping = table[key]
|
||||||
|
if (type(mapping) == "table") then
|
||||||
|
local resultValue = mapping[v]
|
||||||
|
if (v == nil or v == false) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if (v == "no" or v == "false") then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
result.attributes_to_keep[key] = v
|
||||||
|
else
|
||||||
|
error("The mapping is not a table. This is not supported")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true;
|
||||||
|
end
|
2
AspectedRouting/IO/lua/not.lua
Normal file
2
AspectedRouting/IO/lua/not.lua
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
-- 'not' is actually called 'notEq'
|
||||||
|
print("Compiler error - not was actually loaded. If you see this in your profile, please report a bug with the full profile")
|
7
AspectedRouting/IO/lua/notEq.lua
Normal file
7
AspectedRouting/IO/lua/notEq.lua
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
function notEq(a, b)
|
||||||
|
if (a ~= b) then
|
||||||
|
return "yes"
|
||||||
|
else
|
||||||
|
return "no"
|
||||||
|
end
|
||||||
|
end
|
27
AspectedRouting/IO/lua/parse.lua
Normal file
27
AspectedRouting/IO/lua/parse.lua
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
function parse(string)
|
||||||
|
if (string == nil) then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
if (type(string) == "number") then
|
||||||
|
return string
|
||||||
|
end
|
||||||
|
|
||||||
|
if (string == "yes" or string == "true") then
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if (string == "no" or string == "false") then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if (type(string) == "boolean") then
|
||||||
|
if (string) then
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return tonumber(string)
|
||||||
|
end
|
1
AspectedRouting/IO/lua/stringToTags.lua
Normal file
1
AspectedRouting/IO/lua/stringToTags.lua
Normal file
|
@ -0,0 +1 @@
|
||||||
|
print("ERROR: stringToTag is needed. This should not happen")
|
10
AspectedRouting/IO/lua/sum.lua
Normal file
10
AspectedRouting/IO/lua/sum.lua
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
function sum(list)
|
||||||
|
local sum = 1
|
||||||
|
for _, value in ipairs(list) do
|
||||||
|
if(value == 'yes' or value == 'true') then
|
||||||
|
value = 1
|
||||||
|
end
|
||||||
|
sum = sum + value
|
||||||
|
end
|
||||||
|
return sum;
|
||||||
|
end
|
20
AspectedRouting/IO/lua/table_to_list.lua
Normal file
20
AspectedRouting/IO/lua/table_to_list.lua
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
function table_to_list(tags, result, factor_table)
|
||||||
|
local list = {}
|
||||||
|
for key, mapping in pairs(factor_table) do
|
||||||
|
local v = tags[key]
|
||||||
|
if (v ~= nil) then
|
||||||
|
if (type(mapping) == "table") then
|
||||||
|
local f = mapping[v]
|
||||||
|
if (f ~= nil) then
|
||||||
|
table.insert(list, f);
|
||||||
|
result.attributes_to_keep[key] = v
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(list, mapping);
|
||||||
|
result.attributes_to_keep[key] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return list;
|
||||||
|
end
|
3
AspectedRouting/IO/lua/to_string.lua
Normal file
3
AspectedRouting/IO/lua/to_string.lua
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
function to_string(o)
|
||||||
|
return o;
|
||||||
|
end
|
9
AspectedRouting/IO/lua/unitTest.lua
Normal file
9
AspectedRouting/IO/lua/unitTest.lua
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
failed_tests = false
|
||||||
|
function unit_test(f, fname, index, expected, parameters, tags)
|
||||||
|
local result = {attributes_to_keep = {}}
|
||||||
|
local actual = f(parameters, tags, result)
|
||||||
|
if (tostring(actual) ~= expected) then
|
||||||
|
print("[" .. fname .. "] " .. index .. " failed: expected " .. expected .. " but got " .. tostring(actual))
|
||||||
|
failed_tests = true
|
||||||
|
end
|
||||||
|
end
|
38
AspectedRouting/IO/lua/unitTestProfile.lua
Normal file
38
AspectedRouting/IO/lua/unitTestProfile.lua
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
failed_profile_tests = false
|
||||||
|
--[[
|
||||||
|
expected should be a table containing 'access', 'speed' and 'weight'
|
||||||
|
]]
|
||||||
|
function unit_test_profile(profile_function, profile_name, index, expected, tags)
|
||||||
|
result = {attributes_to_keep = {}}
|
||||||
|
profile_function(tags, result)
|
||||||
|
|
||||||
|
if (result.access ~= expected.access) then
|
||||||
|
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".access: expected " .. expected.access .. " but got " .. result.access)
|
||||||
|
failed_profile_tests = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if (result.access == 0) then
|
||||||
|
-- we cannot access this road, the other results are irrelevant
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if (double_compare(result.speed, expected.speed)) then
|
||||||
|
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".speed: expected " .. expected.speed .. " but got " .. result.speed)
|
||||||
|
failed_profile_tests = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if (double_compare(result.oneway, expected.oneway)) then
|
||||||
|
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but got " .. result.oneway)
|
||||||
|
failed_profile_tests = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if (double_compare(result.oneway, expected.oneway)) then
|
||||||
|
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but got " .. result.oneway)
|
||||||
|
failed_profile_tests = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if (double_compare(inv(result.factor), 0.033333)) then
|
||||||
|
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".factor: expected " .. expected.weight .. " but got " .. inv(result.factor))
|
||||||
|
failed_profile_tests = true
|
||||||
|
end
|
||||||
|
end
|
46
AspectedRouting/IO/md/Readme.part0.md
Normal file
46
AspectedRouting/IO/md/Readme.part0.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Aspected Routing
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Generating a good route for travellers is hard; especially for cyclists. They can be very picky and the driving style and purposes are diverse. Think about:
|
||||||
|
|
||||||
|
- A lightweight food delivery driver, who wants to be at their destination as soon as possible
|
||||||
|
- A cargo bike, possibly with a cart or electrically supported, doing heavy delivery
|
||||||
|
- Someone commuting from an to their work on a high-speed electrical bicycle
|
||||||
|
- Grandma cycling along a canal on sunday afternoon
|
||||||
|
- Someone bringing their kids to school
|
||||||
|
|
||||||
|
It is clear that these persona's on top have very different wishes for their route. A road with a high car pressure won't pose a problem for the food delivery, whereas grandma wouldn't even think about going there. And this is without mentioning the speed these cyclists drive, where they are allowed to drive, ...
|
||||||
|
|
||||||
|
Generating a cycle route for these persons is thus clearly far from simply picking the shortest possible path. On top of that, a consumer expects the route calculations to be both customizable and to be blazingly fast.
|
||||||
|
|
||||||
|
In order to simplify the generation of these routing profiles, this repository introduces _aspected routing_.
|
||||||
|
|
||||||
|
In _aspected routing_, one does not try to tackle the routing problem all at once, but one tries to dissassemble the preferences of the travellers into multiple, orthogonal aspects. These aspects can then be combined in a linear way, giving a fast and flexible system.
|
||||||
|
|
||||||
|
Some aspects can be:
|
||||||
|
|
||||||
|
- Can we enter this road _legally_ with this vehicle?
|
||||||
|
- Can we enter this road _physically_ with this vehicle?
|
||||||
|
- What is the legal maximum speed here?
|
||||||
|
- What is the physical maximum speed here?
|
||||||
|
- Is this road oneway?
|
||||||
|
- How comfortable is this road?
|
||||||
|
- How safe is this road?
|
||||||
|
- Is this road lit?
|
||||||
|
- ...
|
||||||
|
|
||||||
|
One can come up with a zillion aspects, each relevant to a small subset of people.
|
||||||
|
|
||||||
|
# The data model
|
||||||
|
|
||||||
|
Even though this repository is heavily inspired on OpenStreetMap, it can be generalized to other road networks.
|
||||||
|
|
||||||
|
## Road network assumptions
|
||||||
|
|
||||||
|
The only assumptions made are that roads have a **length** and a collection of **tags**, this is a dictionary mapping strings onto strings. These tags encode the properties of the road (e.g. road classification, name, surface, ...)
|
||||||
|
|
||||||
|
OpenStreetMap also has a concept of **relations**. A special function is available for that. However, in a preprocessing step, the relations that a road is a member of, are converted into tags on every way with a `_network:i:key=value` format, where `i` is the number of the relation, and `key`=`value` is a tag present on the relation.
|
||||||
|
|
||||||
|
## Describing an aspect
|
||||||
|
|
659
AspectedRouting/IO/md/helpText.md
Normal file
659
AspectedRouting/IO/md/helpText.md
Normal file
|
@ -0,0 +1,659 @@
|
||||||
|
## Types
|
||||||
|
|
||||||
|
- double
|
||||||
|
- pdouble
|
||||||
|
- nat
|
||||||
|
- int
|
||||||
|
- string
|
||||||
|
- tags
|
||||||
|
- bool
|
||||||
|
## Builtin functions
|
||||||
|
|
||||||
|
- eq
|
||||||
|
- notEq
|
||||||
|
- not
|
||||||
|
- inv
|
||||||
|
- default
|
||||||
|
- parse
|
||||||
|
- to_string
|
||||||
|
- concat
|
||||||
|
- min
|
||||||
|
- max
|
||||||
|
- sum
|
||||||
|
- multiply
|
||||||
|
- firstMatchOf
|
||||||
|
- mustMatch
|
||||||
|
- memberOf
|
||||||
|
- if_then_else
|
||||||
|
- if
|
||||||
|
- id
|
||||||
|
- const
|
||||||
|
- constRight
|
||||||
|
- dot
|
||||||
|
- listDot
|
||||||
|
- eitherFunc
|
||||||
|
- stringToTags
|
||||||
|
|
||||||
|
|
||||||
|
### Function overview
|
||||||
|
|
||||||
|
#### eq
|
||||||
|
|
||||||
|
Argument name | | |
|
||||||
|
--------------| - | - | -
|
||||||
|
**a** | $a | $a |
|
||||||
|
**b** | $a | $a |
|
||||||
|
_return type_ | bool | string |
|
||||||
|
|
||||||
|
|
||||||
|
Returns 'yes' if both values _are_ the same
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function eq(a, b)
|
||||||
|
if (a == b) then
|
||||||
|
return "yes"
|
||||||
|
else
|
||||||
|
return "no"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### notEq
|
||||||
|
|
||||||
|
Argument name | | |
|
||||||
|
--------------| - | - | -
|
||||||
|
**a** | $a | $a |
|
||||||
|
**b** | $a | $a |
|
||||||
|
_return type_ | bool | string |
|
||||||
|
|
||||||
|
|
||||||
|
Returns 'yes' if the two passed in values are _not_ the same
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function notEq(a, b)
|
||||||
|
if (a ~= b) then
|
||||||
|
return "yes"
|
||||||
|
else
|
||||||
|
return "no"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### not
|
||||||
|
|
||||||
|
Argument name | | |
|
||||||
|
--------------| - | - | -
|
||||||
|
**a** | $a | $a |
|
||||||
|
**b** | $a | $a |
|
||||||
|
_return type_ | bool | string |
|
||||||
|
|
||||||
|
|
||||||
|
Returns 'yes' if the two passed in values are _not_ the same
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function notEq(a, b)
|
||||||
|
if (a ~= b) then
|
||||||
|
return "yes"
|
||||||
|
else
|
||||||
|
return "no"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### inv
|
||||||
|
|
||||||
|
Argument name | | |
|
||||||
|
--------------| - | - | -
|
||||||
|
**d** | pdouble | double |
|
||||||
|
_return type_ | pdouble | double |
|
||||||
|
|
||||||
|
|
||||||
|
Calculates `1/d`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function inv(n)
|
||||||
|
return 1/n
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### default
|
||||||
|
|
||||||
|
Argument name | |
|
||||||
|
--------------| - | -
|
||||||
|
**defaultValue** | $a |
|
||||||
|
**f** | $b -> $a |
|
||||||
|
_return type_ | $b -> $a |
|
||||||
|
|
||||||
|
|
||||||
|
Calculates function `f` for the given argument. If the result is `null`, the default value is returned instead
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function default(defaultValue, realValue)
|
||||||
|
if(realValue ~= nil) then
|
||||||
|
return realValue
|
||||||
|
end
|
||||||
|
return defaultValue
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### parse
|
||||||
|
|
||||||
|
Argument name | |
|
||||||
|
--------------| - | -
|
||||||
|
**s** | string |
|
||||||
|
_return type_ | double |
|
||||||
|
|
||||||
|
|
||||||
|
Parses a string into a numerical value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function parse(string)
|
||||||
|
if (string == nil) then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
if (type(string) == "number") then
|
||||||
|
return string
|
||||||
|
end
|
||||||
|
|
||||||
|
if (string == "yes" or string == "true") then
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if (string == "no" or string == "false") then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if (type(string) == "boolean") then
|
||||||
|
if (string) then
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return tonumber(string)
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### to_string
|
||||||
|
|
||||||
|
Argument name | |
|
||||||
|
--------------| - | -
|
||||||
|
**obj** | $a |
|
||||||
|
_return type_ | string |
|
||||||
|
|
||||||
|
|
||||||
|
Converts a value into a human readable string
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function to_string(o)
|
||||||
|
return o;
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### concat
|
||||||
|
|
||||||
|
Argument name | |
|
||||||
|
--------------| - | -
|
||||||
|
**a** | string |
|
||||||
|
**b** | string |
|
||||||
|
_return type_ | string |
|
||||||
|
|
||||||
|
|
||||||
|
Concatenates two strings
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function concat(a, b)
|
||||||
|
return a .. b
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### min
|
||||||
|
|
||||||
|
Argument name | | | | | |
|
||||||
|
--------------| - | - | - | - | - | -
|
||||||
|
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
||||||
|
_return type_ | nat | int | pdouble | double | bool |
|
||||||
|
|
||||||
|
|
||||||
|
Out of a list of values, gets the smallest value. IN case of a list of bools, this acts as `and`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function min(list)
|
||||||
|
local min
|
||||||
|
for _, value in ipairs(list) do
|
||||||
|
if (min == nil) then
|
||||||
|
min = value
|
||||||
|
elseif (min > value) then
|
||||||
|
min = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return min;
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### max
|
||||||
|
|
||||||
|
Argument name | | | | | |
|
||||||
|
--------------| - | - | - | - | - | -
|
||||||
|
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
||||||
|
_return type_ | nat | int | pdouble | double | bool |
|
||||||
|
|
||||||
|
|
||||||
|
Returns the biggest value in the list. For a list of booleans, this acts as 'or'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function max(list)
|
||||||
|
local max
|
||||||
|
for _, value in ipairs(list) do
|
||||||
|
if (max == nil) then
|
||||||
|
max = value
|
||||||
|
elseif (max < value) then
|
||||||
|
max = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return max;
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### sum
|
||||||
|
|
||||||
|
Argument name | | | | | |
|
||||||
|
--------------| - | - | - | - | - | -
|
||||||
|
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
||||||
|
_return type_ | nat | int | pdouble | double | int |
|
||||||
|
|
||||||
|
|
||||||
|
Sums all the numbers in the given list. If the list contains bool, `yes` or `true` will be considered to equal `1`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function sum(list)
|
||||||
|
local sum = 1
|
||||||
|
for _, value in ipairs(list) do
|
||||||
|
if(value == 'yes' or value == 'true') then
|
||||||
|
value = 1
|
||||||
|
end
|
||||||
|
sum = sum + value
|
||||||
|
end
|
||||||
|
return sum;
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### multiply
|
||||||
|
|
||||||
|
Argument name | | | | | |
|
||||||
|
--------------| - | - | - | - | - | -
|
||||||
|
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
||||||
|
_return type_ | nat | int | pdouble | double | bool |
|
||||||
|
|
||||||
|
|
||||||
|
Multiplies all the values in a given list. On a list of booleans, this acts as 'and' or 'all'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function multiply(list)
|
||||||
|
local factor = 1
|
||||||
|
for _, value in ipairs(list) do
|
||||||
|
factor = factor * value
|
||||||
|
end
|
||||||
|
return factor;
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### firstMatchOf
|
||||||
|
|
||||||
|
Argument name | |
|
||||||
|
--------------| - | -
|
||||||
|
**s** | list (string) |
|
||||||
|
_return type_ | (tags -> list ($a)) -> tags -> $a |
|
||||||
|
|
||||||
|
|
||||||
|
Parses a string into a numerical value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function first_match_of(tags, result, order_of_keys, table)
|
||||||
|
for _, key in ipairs(order_of_keys) do
|
||||||
|
local v = tags[key]
|
||||||
|
if (v ~= nil) then
|
||||||
|
|
||||||
|
local mapping = table[key]
|
||||||
|
if (type(mapping) == "table") then
|
||||||
|
local resultValue = mapping[v]
|
||||||
|
if (v ~= nil) then
|
||||||
|
result.attributes_to_keep[key] = v
|
||||||
|
return resultValue
|
||||||
|
end
|
||||||
|
else
|
||||||
|
result.attributes_to_keep[key] = v
|
||||||
|
return mapping
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### mustMatch
|
||||||
|
|
||||||
|
Argument name | |
|
||||||
|
--------------| - | -
|
||||||
|
**neededKeys (filled in by parser)** | list (string) |
|
||||||
|
**f** | tags -> list (bool) |
|
||||||
|
_return type_ | tags -> bool |
|
||||||
|
|
||||||
|
|
||||||
|
Every key that is used in the subfunction must be present.
|
||||||
|
If, on top, a value is present with a mapping, every key/value will be executed and must return a value that is not 'no' or 'false'
|
||||||
|
Note that this is a privileged builtin function, as the parser will automatically inject the keys used in the called function.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function must_match(tags, result, needed_keys, table)
|
||||||
|
local result_list = {}
|
||||||
|
for _, key in ipairs(needed_keys) do
|
||||||
|
local v = tags[key]
|
||||||
|
if (v == nil) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local mapping = table[key]
|
||||||
|
if (type(mapping) == "table") then
|
||||||
|
local resultValue = mapping[v]
|
||||||
|
if (v == nil or v == false) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if (v == "no" or v == "false") then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
result.attributes_to_keep[key] = v
|
||||||
|
else
|
||||||
|
error("The mapping is not a table. This is not supported")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### memberOf
|
||||||
|
|
||||||
|
- (tags -> $a) -> tags -> list ($a)
|
||||||
|
|
||||||
|
This function uses memberships of relations to calculate values.
|
||||||
|
|
||||||
|
Consider all the relations the scrutinized way is part of.The enclosed function is executed for every single relation which is part of it, generating a list of results.This list of results is in turn returned by 'memberOf'
|
||||||
|
In itinero 1/lua, this is implemented by converting the matching relations and by adding the tags of the relations to the dictionary (or table) with the highway tags.The prefix is '_relation:n:key=value', where 'n' is a value between 0 and the number of matching relations (implying that all of these numbers are scanned).The matching relations can be extracted by the compiler for the preprocessing.
|
||||||
|
|
||||||
|
For testing, the relation can be emulated by using e.g. '_relation:0:key=value'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function member_of()
|
||||||
|
???
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### if_then_else
|
||||||
|
|
||||||
|
Argument name | | |
|
||||||
|
--------------| - | - | -
|
||||||
|
**condition** | bool | bool |
|
||||||
|
**then** | $a | $a |
|
||||||
|
**else** | $a | _none_ |
|
||||||
|
_return type_ | $a | $a |
|
||||||
|
|
||||||
|
|
||||||
|
Selects either one of the branches, depending on the condition.If the `else` branch is not set, `null` is returned in the condition is false.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function if_then_else(condition, thn, els)
|
||||||
|
if (condition) then
|
||||||
|
return thn
|
||||||
|
else
|
||||||
|
return els -- if no third parameter is given, 'els' will be nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### if
|
||||||
|
|
||||||
|
Argument name | | |
|
||||||
|
--------------| - | - | -
|
||||||
|
**condition** | bool | bool |
|
||||||
|
**then** | $a | $a |
|
||||||
|
**else** | $a | _none_ |
|
||||||
|
_return type_ | $a | $a |
|
||||||
|
|
||||||
|
|
||||||
|
Selects either one of the branches, depending on the condition.If the `else` branch is not set, `null` is returned in the condition is false.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function if_then_else(condition, thn, els)
|
||||||
|
if (condition) then
|
||||||
|
return thn
|
||||||
|
else
|
||||||
|
return els -- if no third parameter is given, 'els' will be nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### id
|
||||||
|
|
||||||
|
Argument name | |
|
||||||
|
--------------| - | -
|
||||||
|
**a** | $a |
|
||||||
|
_return type_ | $a |
|
||||||
|
|
||||||
|
|
||||||
|
Returns the argument unchanged - the identity function. Seems useless at first sight, but useful in parsing
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function id(v)
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### const
|
||||||
|
|
||||||
|
Argument name | |
|
||||||
|
--------------| - | -
|
||||||
|
**a** | $a |
|
||||||
|
**b** | $b |
|
||||||
|
_return type_ | $a |
|
||||||
|
|
||||||
|
|
||||||
|
Small utility function, which takes two arguments `a` and `b` and returns `a`. Used extensively to insert freedom
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### constRight
|
||||||
|
|
||||||
|
Argument name | |
|
||||||
|
--------------| - | -
|
||||||
|
**a** | $a |
|
||||||
|
**b** | $b |
|
||||||
|
_return type_ | $b |
|
||||||
|
|
||||||
|
|
||||||
|
Small utility function, which takes two arguments `a` and `b` and returns `b`. Used extensively to insert freedom
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### dot
|
||||||
|
|
||||||
|
Argument name | |
|
||||||
|
--------------| - | -
|
||||||
|
**f** | $gType -> $arg |
|
||||||
|
**g** | $fType -> $gType |
|
||||||
|
**a** | $fType |
|
||||||
|
_return type_ | $arg |
|
||||||
|
|
||||||
|
|
||||||
|
Higher order function: converts `f (g a)` into `(dot f g) a`. In other words, this fuses `f` and `g` in a new function, which allows the argument to be lifted out of the expression
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### listDot
|
||||||
|
|
||||||
|
Argument name | |
|
||||||
|
--------------| - | -
|
||||||
|
**list** | list ($a -> $b) |
|
||||||
|
**a** | $a |
|
||||||
|
_return type_ | list ($b) |
|
||||||
|
|
||||||
|
|
||||||
|
Listdot takes a list of functions `[f, g, h]` and and an argument `a`. It applies the argument on every single function.It conveniently lifts the argument out of the list.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
-- TODO
|
||||||
|
-- listDot
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### eitherFunc
|
||||||
|
|
||||||
|
- ($a -> $b) -> ($c -> $d) -> $a -> $b
|
||||||
|
- ($a -> $b) -> ($c -> $d) -> $c -> $d
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### stringToTags
|
||||||
|
|
||||||
|
- (string -> string -> $a) -> tags -> list ($a)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
print("ERROR: stringToTag is needed. This should not happen")
|
||||||
|
````
|
||||||
|
|
||||||
|
|
23
AspectedRouting/Profiles/Format.md
Normal file
23
AspectedRouting/Profiles/Format.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
|
||||||
|
- `$funcName` indicates a builtin function
|
||||||
|
- `#parameter` indicates a configurable parameter
|
||||||
|
|
||||||
|
A profile is a function which maps a set of tags onto a value. It is basically Haskell, except that functions can have _multiple_ types. During typechecking, some types will turn out not to be possible and they will dissappear, potentially fixing the implementation of the function itself.
|
||||||
|
|
||||||
|
|
||||||
|
# Profile
|
||||||
|
|
||||||
|
- Metdata: these tags will be copied to the routerdb, but can not be used for routeplanning. A prime example is `name` (the streetname), as it is useful for routeplanning but very useful for navigation afterwards
|
||||||
|
- vehciletypes: used for turn restrictions, legacy for use with itinero 1.0
|
||||||
|
- defaults: a dictionary of `{"#paramName": "value"}`, used in determining the weight of an edge. note: the `#` has to be included
|
||||||
|
- `access`, `oneway`, `speed`: three expressions indicating respectively if access is allowed (if not equals to no), in what direction one can drive (one of `with`, `against` or `both`) and how fast one will go there. (Hint: this should be capped on legal_max_speed)
|
||||||
|
- `weights`: a table of `{'#paramName', expression}` determining the weight (aka COST) of a way, per meter. The formula used is `paramName * expression + paramName0 * expression0 + ...` (`$speed`, `$access` and `$oneway` can be used here to indicate the earlier defined respective aspects). Use a weight == 1 to get the shortest route or `$inv: $speed` to get the fastest route
|
||||||
|
|
||||||
|
|
||||||
|
# Pitfalls
|
||||||
|
|
||||||
|
"$all" should not be used together with a mapping: it checks if all _present_ keys return true or yes (or some other value); it does _not_ check that all the specified keys in the mapping are present.
|
||||||
|
|
||||||
|
For this, an additional 'mustHaveKeys' should be added added
|
763
AspectedRouting/Profiles/bicycle-manual.lua
Normal file
763
AspectedRouting/Profiles/bicycle-manual.lua
Normal file
|
@ -0,0 +1,763 @@
|
||||||
|
-- The different profiles
|
||||||
|
profiles = {
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "comfort_safety_speed",
|
||||||
|
description = "A route which aims to be both safe and comfortable without sacrificing too much of speed",
|
||||||
|
function_name = "determine_weights_balanced",
|
||||||
|
metric = "custom"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "b2w",
|
||||||
|
description = "[Custom] Route for bike2work. Same as 'commute' ATM. Migth diverge in the future. In use.",
|
||||||
|
function_name = "determine_weights_commute",
|
||||||
|
metric = "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "networks",
|
||||||
|
description = "A recreative route following existing cycling networks. Might make longer detours",
|
||||||
|
function_name = "determine_weights_networks",
|
||||||
|
metric = "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "node_network",
|
||||||
|
description = "A recreative route following existing cycle node networks. Might make longer detours",
|
||||||
|
function_name = "determine_weights_networks_node_network",
|
||||||
|
metric = "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "genk",
|
||||||
|
description = "[Custom] A route following the Genk cycle network",
|
||||||
|
function_name = "determine_weights_genk",
|
||||||
|
metric = "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "brussels",
|
||||||
|
description = "[Custom] A route following the Brussels cycle network",
|
||||||
|
function_name = "determine_weights_brussels",
|
||||||
|
metric = "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "cycle_highway",
|
||||||
|
description = "A functional route, preferring cycle_highways or the Brussels Mobility network. If none are availale, will favour speed",
|
||||||
|
function_name = "determine_weights_cycle_highways",
|
||||||
|
metric = "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "commute",
|
||||||
|
description = "A functional route which is aimed to commuters. It is a mix of safety, comfort, a pinch of speed and cycle_highway",
|
||||||
|
function_name = "determine_weights_commute",
|
||||||
|
metric = "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "anyways.network", -- prefers the anyways network, thus where `operator=Anyways` -
|
||||||
|
description = "A route following the cycle network with the operator 'Anyways'. This is for use in ShortCut/Impact as there are no such networks existing in OSM.",
|
||||||
|
function_name = "determine_weights_anyways_network",
|
||||||
|
metric = "custom"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "commute.race",
|
||||||
|
description = "[Deprecated] Same as commute, please use that one instead. Might be unused",
|
||||||
|
function_name = "determine_weights_cycle_highways", -- TODO tweak this profile
|
||||||
|
metric = "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "opa",
|
||||||
|
description = "[Deprecated][Custom] Same as fastest, please use that one instead. Might be unused. Note: all profiles take anyways:* tags into account",
|
||||||
|
function_name = "determine_weights",
|
||||||
|
metric = "custom"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
--[[
|
||||||
|
What is a relative speedup/slowdown for each key?
|
||||||
|
]]
|
||||||
|
speed_bonuses = {
|
||||||
|
access = {
|
||||||
|
dismount = 0.2
|
||||||
|
},
|
||||||
|
highway = {
|
||||||
|
path = 0.5, -- A small path through a forest is typically very slow to go through
|
||||||
|
track = 0.7 -- an unmaintained track (in Belgium: tractor path) is slower as well
|
||||||
|
},
|
||||||
|
surface = {
|
||||||
|
paved = 0.99,
|
||||||
|
asphalt = 1, -- default speed is kept
|
||||||
|
concrete = 1,
|
||||||
|
metal = 1,
|
||||||
|
wood = 1,
|
||||||
|
["concrete:lanes"] = 0.95,
|
||||||
|
["concrete:plates"] = 1,
|
||||||
|
paving_stones = 1,
|
||||||
|
sett = 0.9, -- sett slows us down with 10%
|
||||||
|
unhewn_cobblestone = 0.75,
|
||||||
|
cobblestone = 0.8,
|
||||||
|
unpaved = 0.75,
|
||||||
|
compacted = 0.99,
|
||||||
|
fine_gravel = 0.99,
|
||||||
|
gravel = 0.9,
|
||||||
|
dirt = 0.6,
|
||||||
|
earth = 0.6,
|
||||||
|
grass = 0.6,
|
||||||
|
grass_paver = 0.9,
|
||||||
|
ground = 0.7,
|
||||||
|
sand = 0.5,
|
||||||
|
woodchips = 0.5,
|
||||||
|
snow = 0.5,
|
||||||
|
pebblestone = 0.5,
|
||||||
|
mud = 0.4
|
||||||
|
},
|
||||||
|
tracktype = {
|
||||||
|
grade1 = 0.99,
|
||||||
|
grade2 = 0.8,
|
||||||
|
grade3 = 0.6,
|
||||||
|
grade4 = 0.3,
|
||||||
|
grade5 = 0.1
|
||||||
|
},
|
||||||
|
incline = {
|
||||||
|
up = 0.75,
|
||||||
|
down = 1.25,
|
||||||
|
[0] = 1,
|
||||||
|
["0%"] = 1,
|
||||||
|
["10%"] = 0.9,
|
||||||
|
["-10%"] = 1.1,
|
||||||
|
["20%"] = 0.8,
|
||||||
|
["-20%"] = 1.2,
|
||||||
|
["30%"] = 0.7,
|
||||||
|
["-30%"] = 1.3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Determine-speed determines the average speed for the given segment based on _physical properties_,
|
||||||
|
such as surface, road incline, ...
|
||||||
|
It is _not_ concerned with a legal (or socially accepted) speed - this is capped in the calling procedure
|
||||||
|
]]
|
||||||
|
function determine_speed(attributes, result)
|
||||||
|
local factor = calculate_factor(attributes, speed_bonuses, result)
|
||||||
|
result.speed = factor * attributes.settings.default_speed
|
||||||
|
return result.speed
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--[[
|
||||||
|
How comfortable and nice is this road?
|
||||||
|
Note: this is quite subjective as well!
|
||||||
|
|
||||||
|
Takes a guess on how _comfortable_ this road is to travel.
|
||||||
|
|
||||||
|
Here, aspects are:
|
||||||
|
- road surface and smoothness
|
||||||
|
- estimated greenery (abandoned=railway for the win)
|
||||||
|
|
||||||
|
]]
|
||||||
|
comfort_bonuses = {
|
||||||
|
highway = {
|
||||||
|
cycleway = 1.2,
|
||||||
|
primary = 0.3,
|
||||||
|
secondary = 0.4,
|
||||||
|
tertiary = 0.5,
|
||||||
|
unclassified = 0.8,
|
||||||
|
track = 0.95,
|
||||||
|
residential = 1.0,
|
||||||
|
living_street = 1.1,
|
||||||
|
footway = 0.95,
|
||||||
|
path = 0.5
|
||||||
|
},
|
||||||
|
railway = {
|
||||||
|
abandoned = 2.0
|
||||||
|
},
|
||||||
|
cycleway = {
|
||||||
|
track = 1.2
|
||||||
|
},
|
||||||
|
access = {
|
||||||
|
designated = 1.2,
|
||||||
|
dismount = 0.5
|
||||||
|
},
|
||||||
|
cyclestreet = {
|
||||||
|
yes = 1.1
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
-- Quite, but not entirely the same as in speed
|
||||||
|
surface = {
|
||||||
|
paved = 0.99,
|
||||||
|
["concrete:lanes"] = 0.8,
|
||||||
|
["concrete:plates"] = 1,
|
||||||
|
sett = 0.9, -- sett slows us down with 10%
|
||||||
|
unhewn_cobblestone = 0.75,
|
||||||
|
cobblestone = 0.8,
|
||||||
|
unpaved = 0.75,
|
||||||
|
compacted = 1.1,
|
||||||
|
fine_gravel = 0.99,
|
||||||
|
gravel = 0.9,
|
||||||
|
dirt = 0.6,
|
||||||
|
earth = 0.6,
|
||||||
|
grass = 0.6,
|
||||||
|
grass_paver = 0.9,
|
||||||
|
ground = 0.7,
|
||||||
|
sand = 0.5,
|
||||||
|
woodchips = 0.5,
|
||||||
|
snow = 0.5,
|
||||||
|
pebblestone = 0.5,
|
||||||
|
mud = 0.4
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function determine_comfort(attributes, result)
|
||||||
|
return calculate_factor(attributes, comfort_bonuses, result)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns 1 if no access restrictions, 0 if it is permissive
|
||||||
|
function determine_permissive_score(attributes, result)
|
||||||
|
if (attributes.access == "permissive") then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--[[ Gives 1 if this is on a cycling network.
|
||||||
|
If attributes.settings.cycle_network_operator is defined, then only follow these networks
|
||||||
|
If attributes.settings.cycle_network_highway is defined, then only follow the 'Fietssnelwegen'
|
||||||
|
|
||||||
|
The colour _will only be copied_ if the score is one
|
||||||
|
]]
|
||||||
|
cycle_network_attributes_to_match = { "cycle_network_highway", "cycle_network_node_network" }
|
||||||
|
function determine_network_score(attributes, result)
|
||||||
|
|
||||||
|
if (attributes.cycle_network == nil) then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #cycle_network_attributes_to_match do
|
||||||
|
|
||||||
|
local key = cycle_network_attributes_to_match[i]
|
||||||
|
local expected = attributes.settings[key]
|
||||||
|
|
||||||
|
|
||||||
|
if (expected ~= nil) then
|
||||||
|
-- we have to check that this tag matches
|
||||||
|
local value = attributes[key]
|
||||||
|
if (value == nil) then
|
||||||
|
-- the waysegment doesn't have this attribute - abort
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if (value ~= expected) then
|
||||||
|
-- the way segment doesn't have the expected value - abort
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- hooray, we have a match!
|
||||||
|
result.attributes_to_keep[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if (attributes.settings.cycle_network_operator ~= nil) then
|
||||||
|
local expected = attributes.settings.cycle_network_operator;
|
||||||
|
expected = expected:gsub(" ", "");
|
||||||
|
if (attributes[expected] ~= "yes") then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
result.attributes_to_keep.cycle_network = "yes";
|
||||||
|
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function determine_weights_comfort_safety(attributes, result)
|
||||||
|
|
||||||
|
-- we add a 'settings' element to attributes, they can be used by other profiles
|
||||||
|
if (attributes.settings == nil) then
|
||||||
|
attributes.settings = {}
|
||||||
|
attributes.settings.default_speed = 15
|
||||||
|
attributes.settings.min_speed = 3
|
||||||
|
attributes.settings.max_speed = 30
|
||||||
|
|
||||||
|
attributes.settings.safety_weight = 1
|
||||||
|
attributes.settings.time_weight = 0
|
||||||
|
attributes.settings.comfort_weight = 1
|
||||||
|
attributes.settings.network_weight = 0
|
||||||
|
attributes.settings.clear_restrictions_preference = 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
determine_weights(attributes, result)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function determine_weights_commute(attributes, result)
|
||||||
|
-- lots of safety and comfort, but also slightly prefers 'fietssnelwegen' and 'Brussels Mobility' 'route cyclable', as they are functional
|
||||||
|
|
||||||
|
if (attributes.settings == nil) then
|
||||||
|
attributes.settings = {}
|
||||||
|
attributes.settings.default_speed = 15
|
||||||
|
attributes.settings.min_speed = 3
|
||||||
|
attributes.settings.max_speed = 30
|
||||||
|
|
||||||
|
attributes.settings.clear_restrictions_preference = 1
|
||||||
|
attributes.settings.safety_weight = 3
|
||||||
|
attributes.settings.time_weight = 1
|
||||||
|
attributes.settings.comfort_weight = 2
|
||||||
|
|
||||||
|
attributes.settings.network_weight = 3
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
determine_weights(attributes, result)
|
||||||
|
|
||||||
|
-- commute is a big exception to the other profiles, as in that we overwrite the result.factor here
|
||||||
|
-- this is in order to support _two_ cycling networks
|
||||||
|
|
||||||
|
|
||||||
|
local safety = determine_safety(attributes, result);
|
||||||
|
local comfort = determine_comfort(attributes, result);
|
||||||
|
|
||||||
|
attributes.cycle_network = "yes"
|
||||||
|
attributes.settings.cycle_network_highway = "yes"
|
||||||
|
local cycle_highway_score = determine_network_score(attributes, result);
|
||||||
|
attributes.settings.cycle_network_highway = nil
|
||||||
|
|
||||||
|
|
||||||
|
attributes.cycle_network = "yes"
|
||||||
|
attributes.settings.cycle_network_operator = "Brussels Mobility"
|
||||||
|
local brussels_mobility_score = determine_network_score(attributes, result);
|
||||||
|
attributes.settings.cycle_network_operator = nil
|
||||||
|
|
||||||
|
local network = math.min(1, cycle_highway_score + brussels_mobility_score);
|
||||||
|
|
||||||
|
|
||||||
|
local clear_restrictions = determine_permissive_score(attributes, result);
|
||||||
|
|
||||||
|
|
||||||
|
result.factor = 1 /
|
||||||
|
(safety * attributes.settings.safety_weight +
|
||||||
|
result.speed * attributes.settings.time_weight +
|
||||||
|
comfort * attributes.settings.comfort_weight +
|
||||||
|
network * attributes.settings.network_weight +
|
||||||
|
clear_restrictions * attributes.settings.clear_restrictions_preference);
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function determine_weights_cycle_highways(attributes, result)
|
||||||
|
-- heavily prefers 'fietssnelwegen' and 'Brussels Mobility' 'route cyclable', as they are functional
|
||||||
|
|
||||||
|
if (attributes.settings == nil) then
|
||||||
|
attributes.settings = {}
|
||||||
|
attributes.settings.default_speed = 15
|
||||||
|
attributes.settings.min_speed = 3
|
||||||
|
attributes.settings.max_speed = 30
|
||||||
|
|
||||||
|
attributes.settings.clear_restrictions_preference = 1
|
||||||
|
attributes.settings.safety_weight = 1
|
||||||
|
attributes.settings.time_weight = 0
|
||||||
|
attributes.settings.comfort_weight = 0
|
||||||
|
|
||||||
|
attributes.settings.network_weight = 20
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
determine_weights(attributes, result)
|
||||||
|
|
||||||
|
-- commute is a big exception to the other profiles, as in that we overwrite the result.factor here
|
||||||
|
-- this is in order to support _two_ cycling networks
|
||||||
|
|
||||||
|
|
||||||
|
local safety = determine_safety(attributes, result);
|
||||||
|
local comfort = determine_comfort(attributes, result);
|
||||||
|
|
||||||
|
attributes.cycle_network = "yes"
|
||||||
|
attributes.settings.cycle_network_highway = "yes"
|
||||||
|
local cycle_highway_score = determine_network_score(attributes, result);
|
||||||
|
attributes.settings.cycle_network_highway = nil
|
||||||
|
|
||||||
|
|
||||||
|
attributes.cycle_network = "yes"
|
||||||
|
attributes.settings.cycle_network_operator = "Brussels Mobility"
|
||||||
|
local brussels_mobility_score = determine_network_score(attributes, result);
|
||||||
|
attributes.settings.cycle_network_operator = nil
|
||||||
|
|
||||||
|
local network = math.min(1, cycle_highway_score + brussels_mobility_score);
|
||||||
|
|
||||||
|
|
||||||
|
local clear_restrictions = determine_permissive_score(attributes, result);
|
||||||
|
|
||||||
|
|
||||||
|
result.factor = 1 /
|
||||||
|
(safety * attributes.settings.safety_weight +
|
||||||
|
result.speed * attributes.settings.time_weight +
|
||||||
|
comfort * attributes.settings.comfort_weight +
|
||||||
|
network * attributes.settings.network_weight +
|
||||||
|
clear_restrictions * attributes.settings.clear_restrictions_preference);
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function determine_weights_networks(attributes, result)
|
||||||
|
|
||||||
|
-- we add a 'settings' element to attributes, they can be used by other profiles
|
||||||
|
if (attributes.settings == nil) then
|
||||||
|
attributes.settings = {}
|
||||||
|
attributes.settings.default_speed = 15
|
||||||
|
attributes.settings.min_speed = 3
|
||||||
|
attributes.settings.max_speed = 30
|
||||||
|
|
||||||
|
attributes.settings.safety_weight = 1
|
||||||
|
attributes.settings.time_weight = 0
|
||||||
|
attributes.settings.comfort_weight = 0
|
||||||
|
attributes.settings.network_weight = 3
|
||||||
|
attributes.settings.clear_restrictions_preference = 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
determine_weights(attributes, result)
|
||||||
|
end
|
||||||
|
|
||||||
|
function determine_weights_networks_node_network(attributes, result)
|
||||||
|
|
||||||
|
-- we add a 'settings' element to attributes, they can be used by other profiles
|
||||||
|
if (attributes.settings == nil) then
|
||||||
|
attributes.settings = {}
|
||||||
|
attributes.settings.default_speed = 15
|
||||||
|
attributes.settings.min_speed = 3
|
||||||
|
attributes.settings.max_speed = 30
|
||||||
|
|
||||||
|
attributes.settings.safety_weight = 1
|
||||||
|
attributes.settings.time_weight = 0
|
||||||
|
attributes.settings.comfort_weight = 0
|
||||||
|
attributes.settings.network_weight = 10
|
||||||
|
attributes.settings.clear_restrictions_preference = 1
|
||||||
|
|
||||||
|
attributes.cycle_network = "yes"
|
||||||
|
attributes.settings.cycle_network_node_network = "yes"
|
||||||
|
end
|
||||||
|
|
||||||
|
determine_weights(attributes, result)
|
||||||
|
end
|
||||||
|
|
||||||
|
function determine_weights_genk(attributes, result)
|
||||||
|
|
||||||
|
-- we add a 'settings' element to attributes, they can be used by other profiles
|
||||||
|
if (attributes.settings == nil) then
|
||||||
|
attributes.settings = {}
|
||||||
|
attributes.settings.default_speed = 15
|
||||||
|
attributes.settings.min_speed = 3
|
||||||
|
attributes.settings.max_speed = 30
|
||||||
|
|
||||||
|
attributes.settings.safety_weight = 1
|
||||||
|
attributes.settings.time_weight = 0
|
||||||
|
attributes.settings.comfort_weight = 0
|
||||||
|
attributes.settings.network_weight = 3
|
||||||
|
attributes.settings.clear_restrictions_preference = 1
|
||||||
|
|
||||||
|
attributes.cycle_network = "yes"
|
||||||
|
attributes.settings.cycle_network_operator = "Stad Genk"
|
||||||
|
end
|
||||||
|
|
||||||
|
determine_weights(attributes, result)
|
||||||
|
end
|
||||||
|
|
||||||
|
function determine_weights_brussels(attributes, result)
|
||||||
|
|
||||||
|
|
||||||
|
-- we add a 'settings' element to attributes, they can be used by other profiles
|
||||||
|
if (attributes.settings == nil) then
|
||||||
|
attributes.settings = {}
|
||||||
|
attributes.settings.default_speed = 15
|
||||||
|
attributes.settings.min_speed = 3
|
||||||
|
attributes.settings.max_speed = 30
|
||||||
|
|
||||||
|
attributes.settings.safety_weight = 1
|
||||||
|
attributes.settings.time_weight = 0
|
||||||
|
attributes.settings.comfort_weight = 1
|
||||||
|
attributes.settings.network_weight = 5
|
||||||
|
attributes.settings.clear_restrictions_preference = 1;
|
||||||
|
|
||||||
|
attributes.settings.cycle_network_operator = "Brussels Mobility"
|
||||||
|
end
|
||||||
|
|
||||||
|
determine_weights(attributes, result)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function determine_weights_anyways_network(attributes, result)
|
||||||
|
|
||||||
|
|
||||||
|
-- we add a 'settings' element to attributes, they can be used by other profiles
|
||||||
|
if (attributes.settings == nil) then
|
||||||
|
attributes.settings = {}
|
||||||
|
attributes.settings.default_speed = 15
|
||||||
|
attributes.settings.min_speed = 3
|
||||||
|
attributes.settings.max_speed = 30
|
||||||
|
|
||||||
|
attributes.settings.safety_weight = 0
|
||||||
|
attributes.settings.time_weight = 0
|
||||||
|
attributes.settings.comfort_weight = 0
|
||||||
|
attributes.settings.network_weight = 10
|
||||||
|
attributes.settings.clear_restrictions_preference = 1;
|
||||||
|
|
||||||
|
attributes.settings.cycle_network_operator = "Anyways"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
attributes.access = nil -- little hack: remove ALL access restrictions in order to go onto private parts
|
||||||
|
|
||||||
|
determine_weights(attributes, result)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function determine_weights(attributes, result)
|
||||||
|
|
||||||
|
-- we add a 'settings' element to attributes, they can be used by other profiles
|
||||||
|
if (attributes.settings == nil) then
|
||||||
|
attributes.settings = {}
|
||||||
|
attributes.settings.default_speed = 15
|
||||||
|
attributes.settings.min_speed = 3
|
||||||
|
attributes.settings.max_speed = 30
|
||||||
|
|
||||||
|
attributes.settings.safety_weight = 0
|
||||||
|
attributes.settings.time_weight = 0
|
||||||
|
attributes.settings.comfort_weight = 0
|
||||||
|
attributes.settings.network_weight = 0
|
||||||
|
attributes.settings.clear_restrictions_preference = 1; -- if 1: discourage 'permissive' access tags
|
||||||
|
|
||||||
|
attributes.settings.cycle_network_operator = nil -- e.g. "Stad Genk" of "Brussels Mobility"
|
||||||
|
attributes.settings.cycle_network_highway = nil -- if "yes", will only follow the 'fietsostrades'
|
||||||
|
attributes.settings.cycle_network_node_network = nil -- if "yes", will only follow the 'node_networks'
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Init default values
|
||||||
|
result.access = 0
|
||||||
|
result.speed = 0
|
||||||
|
result.factor = 1
|
||||||
|
result.direction = 0
|
||||||
|
result.canstop = true
|
||||||
|
result.attributes_to_keep = {}
|
||||||
|
|
||||||
|
-- Do misc preprocessing, such as handling the ferry case
|
||||||
|
preprocess(attributes, result);
|
||||||
|
|
||||||
|
|
||||||
|
-- 1) Can we enter this segment legally?
|
||||||
|
if (not can_access_legally(attributes)) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
result.access = 1;
|
||||||
|
result.attributes_to_keep.highway = attributes.highway
|
||||||
|
result.attributes_to_keep.access = attributes.access
|
||||||
|
|
||||||
|
|
||||||
|
-- 2) Is this a oneway?
|
||||||
|
determine_oneway(attributes, result)
|
||||||
|
|
||||||
|
-- 3) How fast would one drive on average on this segment?
|
||||||
|
determine_speed(attributes, result);
|
||||||
|
-- Cap using settings and legal max speed
|
||||||
|
result.speed = math.max(attributes.settings.min_speed, result.speed)
|
||||||
|
result.speed = math.min(attributes.settings.max_speed, result.speed)
|
||||||
|
local legal_max_speed = highway_types[attributes.highway].speed
|
||||||
|
result.speed = math.min(legal_max_speed, result.speed)
|
||||||
|
|
||||||
|
|
||||||
|
-- 4) What is the factor of this segment?
|
||||||
|
--[[
|
||||||
|
This is determined by multiple factors and the weight that is given to them by the settings
|
||||||
|
Factors are:
|
||||||
|
- safety
|
||||||
|
- comfort
|
||||||
|
- ...
|
||||||
|
]]
|
||||||
|
|
||||||
|
local safety = determine_safety(attributes, result);
|
||||||
|
local comfort = determine_comfort(attributes, result);
|
||||||
|
local network = determine_network_score(attributes, result);
|
||||||
|
local clear_restrictions = determine_permissive_score(attributes, result);
|
||||||
|
|
||||||
|
result.factor = 1 /
|
||||||
|
(safety * attributes.settings.safety_weight +
|
||||||
|
result.speed * attributes.settings.time_weight +
|
||||||
|
comfort * attributes.settings.comfort_weight +
|
||||||
|
network * attributes.settings.network_weight +
|
||||||
|
clear_restrictions * attributes.settings.clear_restrictions_preference);
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- Unit test: are all tags in profile_whitelist
|
||||||
|
cycling_network_operators_to_tag = { "Stad Genk", "Anyways", "Brussels Mobility" }
|
||||||
|
cycling_network_tags = { "cycle_network", "cycle_network_highway", "cycle_network_operator", "cycle_network_colour", "cycle_network_node_network", "operator", "StadGenk", "Anyways", "BrusselsMobility" }
|
||||||
|
|
||||||
|
|
||||||
|
--[[ Copies all relevant relation tags onto the way
|
||||||
|
All these tags start with 'cycle_network', e.g. cycle_network_colour
|
||||||
|
|
||||||
|
Some of them are exclusively for the metadata (cyclecolour as prime example)
|
||||||
|
|
||||||
|
Note that all the tags used are listed in 'cycling_network_tags' as well, for unit testing purposes
|
||||||
|
|
||||||
|
]]
|
||||||
|
function cycling_network_tag_processor(attributes, result)
|
||||||
|
result.attributes_to_keep.cycle_network = "yes"
|
||||||
|
|
||||||
|
if (attributes.cycle_network == "cycle_highway"
|
||||||
|
and attributes.state ~= "proposed") then
|
||||||
|
result.attributes_to_keep.cycle_network_highway = "yes"
|
||||||
|
end
|
||||||
|
|
||||||
|
if (attributes.cycle_network == "node_network") then
|
||||||
|
result.cycle_network_node_network = "yes"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if (attributes.operator ~= nil) then
|
||||||
|
for k, v in pairs(cycling_network_operators_to_tag) do
|
||||||
|
v = v:gsub(" ", "") -- remove spaces from the operator as lua can't handle them
|
||||||
|
result.attributes_to_keep[v] = "yes"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
if (attributes.colour ~= nil) then
|
||||||
|
result.attributes_to_keep.cycle_network_colour = attributes.colour
|
||||||
|
end
|
||||||
|
|
||||||
|
if (attributes.color ~= nil) then
|
||||||
|
-- for the americans!
|
||||||
|
result.attributes_to_keep.cycle_network_colour = attributes.color
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Processes the relation. All tags which are added to result.attributes_to_keep will be copied to 'attributes' of each individual way
|
||||||
|
function relation_tag_processor(attributes, result)
|
||||||
|
result.attributes_to_keep = {}
|
||||||
|
|
||||||
|
|
||||||
|
if (attributes.route == "bicycle") then
|
||||||
|
-- This is a cycling network!
|
||||||
|
cycling_network_tag_processor(attributes, result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function unit_tests()
|
||||||
|
|
||||||
|
|
||||||
|
-- Speed test are with default_speed = 100, in order to efficiently determine the factor
|
||||||
|
unit_test_speed({ highway = "residential" }, 100.0)
|
||||||
|
unit_test_speed({ highway = "residential", surface = "sett" }, 90.0)
|
||||||
|
unit_test_speed({ highway = "residential", incline = "up" }, 75.0)
|
||||||
|
unit_test_speed({ highway = "residential", incline = "up", surface = "mud" }, 30.0)
|
||||||
|
unit_test_speed({ highway = "residential", incline = "down" }, 125.0)
|
||||||
|
unit_test_speed({ highway = "residential", incline = "down", surface = "sett" }, 112.5)
|
||||||
|
|
||||||
|
|
||||||
|
unit_test_comfort({ highway = "residential" }, 1.0)
|
||||||
|
unit_test_comfort({ highway = "residential", cyclestreet = "yes" }, 1.1)
|
||||||
|
unit_test_comfort({ highway = "cycleway" }, 1.2)
|
||||||
|
unit_test_comfort({ highway = "cycleway", foot = "designated" }, 1.2)
|
||||||
|
unit_test_comfort({ highway = "path", bicycle = "designated", foot = "designated" }, 0.5)
|
||||||
|
unit_test_comfort({ highway = "path", bicycle = "designated" }, 0.5)
|
||||||
|
unit_test_comfort({ highway = "footway", foot = "designated" }, 0.95)
|
||||||
|
unit_test_comfort({ highway = "primary", cycleway = "no" }, 0.3)
|
||||||
|
unit_test_comfort({ highway = "primary", cycleway = "yes" }, 0.3)
|
||||||
|
unit_test_comfort({ highway = "primary", cycleway = "track" }, 0.36)
|
||||||
|
|
||||||
|
unit_test_comfort({ highway = "secondary", cycleway = "lane" }, 0.4)
|
||||||
|
unit_test_comfort({ ["cycleway:right"] = "lane", highway = "secondary", surface = "asphalt" }, 0.4)
|
||||||
|
unit_test_comfort({ highway = "residential", surface = "asphalt", cyclestreet = "yes" }, 1.1)
|
||||||
|
|
||||||
|
|
||||||
|
unit_test_relation_tag_processor({ route = "bicycle", operator = "Stad Genk", color = "red", type = "route" },
|
||||||
|
{ StadGenk = "yes", cycle_network_colour = "red", cycle_network = "yes" });
|
||||||
|
unit_test_relation_tag_processor({ route = "bicycle", cycle_network = "cycle_highway", color = "red", type = "route" },
|
||||||
|
{ cycle_network_colour = "red", cycle_network = "yes", cycle_network_highway = "yes" });
|
||||||
|
|
||||||
|
|
||||||
|
unit_test_cycle_networks({ highway = "residential", settings = {} }, 0)
|
||||||
|
unit_test_cycle_networks({ highway = "residential", cycle_network = "yes", settings = {} }, 1)
|
||||||
|
unit_test_cycle_networks({
|
||||||
|
highway = "residential",
|
||||||
|
cycle_network = "yes",
|
||||||
|
settings = { cycle_network_operator = "Stad Genk" }
|
||||||
|
}, 0 --[[Not the right network, not Genk]])
|
||||||
|
|
||||||
|
unit_test_weights(determine_weights_speed_first, { highway = "residential" }, 0.0625);
|
||||||
|
unit_test_weights(determine_weights_speed_first, { highway = "cycleway" }, 0.0625); -- unit_test_weights({ highway = "residential", access = "destination", surface = "sett" });
|
||||||
|
unit_test_weights(determine_weights_speed_first, { highway = "primary", bicycle="yes", surface = "sett" }, 0.068965517241379);
|
||||||
|
|
||||||
|
unit_test_weights(determine_weights_speed_first, { highway = "primary" , bicycle="yes"}, 0.0625);
|
||||||
|
unit_test_weights(determine_weights_safety_first, { highway = "primary" , bicycle="yes"}, 0.76923076923077);
|
||||||
|
|
||||||
|
-- regression test
|
||||||
|
unit_test_weights(determine_weights_safety_first, { ["cycleway:left"] = "track", ["cycleway:left:oneway"] = "no", highway="unclassified", oneway="yes", ["oneway:bicycle"]=no}, 0.45454545454545);
|
||||||
|
unit_test_weights(determine_weights_speed_first, { ["cycleway:left"] = "track", ["cycleway:left:oneway"] = "no", highway="unclassified", oneway="yes", ["oneway:bicycle"]=no}, 0.0625);
|
||||||
|
|
||||||
|
|
||||||
|
unit_test_weights(determine_weights_genk, { highway = "residential" }, 0.52631578947368);
|
||||||
|
unit_test_weights(determine_weights_genk,
|
||||||
|
{ highway = "residential", StadGenk = "yes", cycle_network = "yes" },
|
||||||
|
0.20408163265306); -- factor = 1 / preference, should be lower
|
||||||
|
unit_test_weights(determine_weights_genk,
|
||||||
|
{ highway = "residential", cycle_network_operator = nil, cycle_network = "yes" },
|
||||||
|
0.52631578947368);
|
||||||
|
unit_test_weights(determine_weights_genk, {
|
||||||
|
highway = "residential",
|
||||||
|
cycle_network = "yes",
|
||||||
|
cycle_network_operator = "Niet Stad Genk"
|
||||||
|
}, 0.52631578947368);
|
||||||
|
|
||||||
|
unit_test_weights(determine_weights_networks_node_network, {
|
||||||
|
highway = "residential",
|
||||||
|
cycle_network = "yes",
|
||||||
|
cycle_network_operator = "Niet Stad Genk"
|
||||||
|
}, 0.52631578947368);
|
||||||
|
|
||||||
|
unit_test_weights(determine_weights_networks_node_network, {
|
||||||
|
highway = "residential",
|
||||||
|
cycle_network = "yes",
|
||||||
|
cycle_network_node_network = "yes",
|
||||||
|
}, 0.084033613445378);
|
||||||
|
|
||||||
|
unit_test_weights(determine_weights_anyways_network, { highway = "residential", cycle_network = "yes", Anyways = "yes" }, 0.090909090909091);
|
||||||
|
unit_test_weights(determine_weights_anyways_network, { highway = "residential" }, 1.0);
|
||||||
|
|
||||||
|
|
||||||
|
unit_test_weights(determine_weights_networks_node_network, { highway = "residential" }, 0.52631578947368);
|
||||||
|
unit_test_weights(determine_weights_networks_node_network, { highway = "residential", cycle_network="yes", cycle_network_node_network="yes" }, 0.084033613445378);
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
13
AspectedRouting/Profiles/bicycle/bicycle.comfort.json
Normal file
13
AspectedRouting/Profiles/bicycle/bicycle.comfort.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "bicycle.comfort",
|
||||||
|
"description": "Gives a comfort factor, purely based on physical aspects of the road",
|
||||||
|
"unit": "[0, 2]",
|
||||||
|
"$default": 1,
|
||||||
|
"value": {
|
||||||
|
"$multiply": {
|
||||||
|
"cyclestreet": {
|
||||||
|
"yes": 1.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
AspectedRouting/Profiles/bicycle/bicycle.fastest.csv
Normal file
7
AspectedRouting/Profiles/bicycle/bicycle.fastest.csv
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
access,oneway,speed,weight,highway,bicycle
|
||||||
|
0,0,0,0,,
|
||||||
|
1,0,30,0.03333,cycleway,
|
||||||
|
0,0,0,0,primary,
|
||||||
|
1,0,15,0.06666,pedestrian,
|
||||||
|
1,0,15,0.0666,pedestrian,yes
|
||||||
|
1,0,30,0.033333,residential,
|
|
76
AspectedRouting/Profiles/bicycle/bicycle.json
Normal file
76
AspectedRouting/Profiles/bicycle/bicycle.json
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"name": "bicycle",
|
||||||
|
"description": "A simple profile which routes a normal bicycle",
|
||||||
|
"vehicletypes": [
|
||||||
|
"vehicle",
|
||||||
|
"bicycle"
|
||||||
|
],
|
||||||
|
"metadata": [
|
||||||
|
"name",
|
||||||
|
"bridge",
|
||||||
|
"tunnel",
|
||||||
|
"colour",
|
||||||
|
"cycle_network_colour",
|
||||||
|
"ref",
|
||||||
|
"status",
|
||||||
|
"network"
|
||||||
|
],
|
||||||
|
"defaults": {
|
||||||
|
"#max_speed": 30,
|
||||||
|
"#defaultSpeed": 15,
|
||||||
|
"#speed": 0,
|
||||||
|
"#distance": 0,
|
||||||
|
"#comfort": 0,
|
||||||
|
"#safety": 0,
|
||||||
|
"#withDirection": "yes",
|
||||||
|
"#againstDirection": "yes",
|
||||||
|
"network": 0
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"fastest": {
|
||||||
|
"description": "The fastest route to your destination",
|
||||||
|
"#time_needed": 1
|
||||||
|
},
|
||||||
|
"shortest": {
|
||||||
|
"description": "The shortest route, independent of of speed",
|
||||||
|
"#distance": 1
|
||||||
|
},
|
||||||
|
"safety": {
|
||||||
|
"description": "A defensive route shying away from big roads with lots of cars",
|
||||||
|
"#safety": 1
|
||||||
|
},
|
||||||
|
"comfort": {
|
||||||
|
"description": "A comfortable route preferring well-paved roads, smaller roads and a bit of scenery at the cost of speed",
|
||||||
|
"#comfort": 1
|
||||||
|
},
|
||||||
|
"comfort_safety": {
|
||||||
|
"description": "A route which aims to be both safe and comfortable at the cost of speed",
|
||||||
|
"#comfort": 1,
|
||||||
|
"#safety": 1
|
||||||
|
},
|
||||||
|
"node_networks": {
|
||||||
|
"description": "A route which prefers cycling over cycling node networks. Non-network parts prefer some comfort and safety. The on-network-part is considered flat (meaning that only distance on the node network counts)",
|
||||||
|
"#network": 20,
|
||||||
|
"#network-node-network-only":true,
|
||||||
|
"#comfort": 1,
|
||||||
|
"#safety": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"access": "$bicycle.legal_access",
|
||||||
|
"oneway": "$bicycle.oneway",
|
||||||
|
"speed": {
|
||||||
|
"$min": [
|
||||||
|
"$legal_maxspeed_be",
|
||||||
|
"#defaultSpeed"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"weight": {
|
||||||
|
"#comfort": "$bicycle.comfort",
|
||||||
|
"#safety": "$bicycle.safety",
|
||||||
|
"#network": "$bicycle.network_score",
|
||||||
|
"#time_needed": {
|
||||||
|
"$inv": "$speed"
|
||||||
|
},
|
||||||
|
"#distance": "$distance"
|
||||||
|
}
|
||||||
|
}
|
80
AspectedRouting/Profiles/bicycle/bicycle.legal_access.json
Normal file
80
AspectedRouting/Profiles/bicycle/bicycle.legal_access.json
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
{
|
||||||
|
"name": "bicycle.legal_access",
|
||||||
|
"description": "Gives, for each type of highway, whether or not a normal bicycle can enter legally.\nNote that legal access is a bit 'grey' in the case of roads marked private and permissive, in which case these values are returned ",
|
||||||
|
"unit": "'designated': Access is allowed and even specifically for bicycles\n'yes': bicycles are allowed here\n'permissive': bicycles are allowed here, but this might be a private road or service where usage is allowed, but uncommon\n'dismount': cycling here is not allowed, but walking with the bicycle is\n'destination': cycling is allowed here, but only if truly necessary to reach the destination\n'private': this is a private road, only go here if the destination is here\n'no': do not cycle here",
|
||||||
|
"$default": "no",
|
||||||
|
"value": {
|
||||||
|
"$firstMatchOf": [
|
||||||
|
"anyways:bicycle",
|
||||||
|
"anyways:access",
|
||||||
|
"anyways:construction",
|
||||||
|
"bicycle",
|
||||||
|
"access",
|
||||||
|
"cycleway:right",
|
||||||
|
"cycleway:left",
|
||||||
|
"cycleway",
|
||||||
|
"highway"
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"access": {
|
||||||
|
"no": "no",
|
||||||
|
"customers": "no",
|
||||||
|
"private": "no",
|
||||||
|
"#": "Note that we leave out 'yes', as it is meaningless - the default assumption is that everything on OSM is accessible! This forces to fall through to the road type, in order to force `highway=motorway; access=yes` as not to trigger access for bicycles",
|
||||||
|
"permissive": "permissive",
|
||||||
|
"destination": "destination",
|
||||||
|
"delivery": "destination",
|
||||||
|
"service": "destination"
|
||||||
|
},
|
||||||
|
"highway": {
|
||||||
|
"cycleway": "designated",
|
||||||
|
"residential": "yes",
|
||||||
|
"living_street": "yes",
|
||||||
|
"service": "permissive",
|
||||||
|
"services": "permissive",
|
||||||
|
"track": "yes",
|
||||||
|
"crossing": "dismount",
|
||||||
|
"footway": "dismount",
|
||||||
|
"pedestrian": "dismount",
|
||||||
|
"corridor": "dismount",
|
||||||
|
"path": "permissive",
|
||||||
|
"primary": "no",
|
||||||
|
"primary_link": "no",
|
||||||
|
"secondary": "yes",
|
||||||
|
"secondary_link": "yes",
|
||||||
|
"tertiary": "yes",
|
||||||
|
"tertiary_link": "yes",
|
||||||
|
"unclassified": "yes",
|
||||||
|
"road": "yes"
|
||||||
|
},
|
||||||
|
"bicycle": {
|
||||||
|
"yes": "yes",
|
||||||
|
"no": "no",
|
||||||
|
"use_sidepath": "no",
|
||||||
|
"designated": "designated",
|
||||||
|
"permissive": "permissive",
|
||||||
|
"private": "private",
|
||||||
|
"official": "designated",
|
||||||
|
"dismount": "dismount"
|
||||||
|
},
|
||||||
|
"cycleway:right": {
|
||||||
|
"$not": "no"
|
||||||
|
},
|
||||||
|
"cycleway:left": {
|
||||||
|
"$not": "no"
|
||||||
|
},
|
||||||
|
"cycleway": {
|
||||||
|
"$not": "no"
|
||||||
|
},
|
||||||
|
"anyways:bicycle": "$id",
|
||||||
|
"anyways:access": {
|
||||||
|
"no": "no",
|
||||||
|
"destination": "destination",
|
||||||
|
"yes": "yes"
|
||||||
|
},
|
||||||
|
"anyways:construction": {
|
||||||
|
"yes": "no"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
expected,highway,bicycle,access,anyways:access,cycleway:right,cycleway:right
|
||||||
|
no,,,,,,
|
||||||
|
no,,,no,,,
|
||||||
|
yes,,yes,no,,,
|
||||||
|
permissive,path,,,,,
|
||||||
|
yes,pedestrian,yes,,,,
|
||||||
|
dismount,pedestrian,,,,,
|
||||||
|
designated,cycleway,,,,,
|
||||||
|
destination,residential,,destination,,,
|
||||||
|
no,residential,,private,,,
|
||||||
|
designated,residential,designated,,,,
|
||||||
|
designated,motorway,designated,,,,
|
||||||
|
no,residential,use_sidepath,,,,
|
||||||
|
yes,residential,,no,yes,,
|
||||||
|
no,primary,,,,,
|
||||||
|
yes,primary,,,,yes,
|
||||||
|
yes,primary,,,,,yes
|
||||||
|
yes,secondary,,,,track,
|
||||||
|
destination,service,,destination,,,
|
||||||
|
no,residential,use_sidepath,,,,,
|
Can't render this file because it has a wrong number of fields in line 20.
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "bicycle.network_score",
|
||||||
|
"description": "The 'bicycle.network_score' is a bit of a catch-all for bicycle networks and indicates wether or not the road is part of a matching cycling network.",
|
||||||
|
"$mustMatch": {
|
||||||
|
"type": "route",
|
||||||
|
"route": "bicycle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
50
AspectedRouting/Profiles/bicycle/bicycle.oneway.json
Normal file
50
AspectedRouting/Profiles/bicycle/bicycle.oneway.json
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
{
|
||||||
|
"name": "bicycle.oneway",
|
||||||
|
"description": "Determines wether or not a bicycle can go in both ways in this street, and if it is oneway, in what direction",
|
||||||
|
"unit": "both: direction is allowed in both direction\nwith: this is a oneway street with direction allowed with the grain of the way\nagainst: oneway street with direction against the way",
|
||||||
|
"$default": "both",
|
||||||
|
"value": {
|
||||||
|
"$firstMatchOf": [
|
||||||
|
"oneway:bicycle",
|
||||||
|
"junction",
|
||||||
|
"cycleway",
|
||||||
|
"cycleway:left",
|
||||||
|
"oneway"
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"oneway": {
|
||||||
|
"yes": "with",
|
||||||
|
"no": "both",
|
||||||
|
"1": "with",
|
||||||
|
"-1": "against"
|
||||||
|
},
|
||||||
|
"oneway:bicycle": {
|
||||||
|
"yes": "with",
|
||||||
|
"no": "both",
|
||||||
|
"1": "with",
|
||||||
|
"-1": "against"
|
||||||
|
},
|
||||||
|
"junction": {
|
||||||
|
"roundabout": "with"
|
||||||
|
},
|
||||||
|
"cycleway": {
|
||||||
|
"right": "against",
|
||||||
|
"#": "We ignore 'no' as it has no meaning and is the default assumption",
|
||||||
|
"opposite_lane": "both",
|
||||||
|
"track": "both",
|
||||||
|
"lane": "both",
|
||||||
|
"opposite": "both",
|
||||||
|
"opposite_share_busway": "both",
|
||||||
|
"opposite_track": "both"
|
||||||
|
},
|
||||||
|
"cycleway:left": {
|
||||||
|
"no": "with",
|
||||||
|
"yes": "both",
|
||||||
|
"lane": "both",
|
||||||
|
"track": "both",
|
||||||
|
"shared_lane": "both",
|
||||||
|
"share_busway": "both"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
AspectedRouting/Profiles/bicycle/bicycle.oneway.test.csv
Normal file
19
AspectedRouting/Profiles/bicycle/bicycle.oneway.test.csv
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
expected,highway,oneway,oneway:bicycle,junction,cycleway,cycleway:right,cycleway:left
|
||||||
|
both,,,,,,,
|
||||||
|
both,,no,,,,,
|
||||||
|
with,,no,yes,,,,
|
||||||
|
with,,,,roundabout,,,
|
||||||
|
both,,yes,,,opposite,,
|
||||||
|
against,,yes,-1,,,,
|
||||||
|
with,residential,yes,,,,,
|
||||||
|
both,residential,no,,,,,
|
||||||
|
both,residential,yes,no,,,,
|
||||||
|
with,residential,,,roundabout,,,
|
||||||
|
both,residential,,no,roundabout,,,
|
||||||
|
against,residential,,-1,,,,
|
||||||
|
both,residential,invalidKey,no,,,,
|
||||||
|
with,secondary,yes,,,,track,
|
||||||
|
both,secondary,yes,,,,,track
|
||||||
|
both,secondary,yes,,,track,,
|
||||||
|
with,,yes,,,,,no
|
||||||
|
both,residential,yes,,,,,lane
|
|
70
AspectedRouting/Profiles/bicycle/bicycle.safety.json
Normal file
70
AspectedRouting/Profiles/bicycle/bicycle.safety.json
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
{
|
||||||
|
"name": "bicycle.safety",
|
||||||
|
"description": "Determines how safe a cyclist feels on a certain road, mostly based on car pressure. This is a relatively ",
|
||||||
|
"unit": "safety",
|
||||||
|
"$default": 1,
|
||||||
|
"value": {
|
||||||
|
"$multiply": {
|
||||||
|
"access": {
|
||||||
|
"no": 1.1
|
||||||
|
},
|
||||||
|
"motor": {
|
||||||
|
"no": 1.5
|
||||||
|
},
|
||||||
|
"foot": {
|
||||||
|
"designated": 0.95
|
||||||
|
},
|
||||||
|
"bicycle": {
|
||||||
|
"designated": 1.5
|
||||||
|
},
|
||||||
|
"cyclestreet": {
|
||||||
|
"yes": 1.5
|
||||||
|
},
|
||||||
|
"towpath": {
|
||||||
|
"yes": 1.1
|
||||||
|
},
|
||||||
|
"designation": {
|
||||||
|
"towpath": 1.5
|
||||||
|
},
|
||||||
|
"highway": {
|
||||||
|
"cycleway": 1.0,
|
||||||
|
"primary": 0.3,
|
||||||
|
"secondary": 0.4,
|
||||||
|
"tertiary": 0.5,
|
||||||
|
"unclassified": 0.8,
|
||||||
|
"track": 0.95,
|
||||||
|
"residential": 0.9,
|
||||||
|
"living_street": 1,
|
||||||
|
"footway": 1,
|
||||||
|
"path": 1
|
||||||
|
},
|
||||||
|
"cycleway": {
|
||||||
|
"yes": 0.95,
|
||||||
|
"no": 0.5,
|
||||||
|
"lane": 1,
|
||||||
|
"shared": 0.8,
|
||||||
|
"shared_lane": 0.8,
|
||||||
|
"share_busway": 0.9,
|
||||||
|
"track": 1.5
|
||||||
|
},
|
||||||
|
"cycleway:left": {
|
||||||
|
"yes": 0.95,
|
||||||
|
"no": 0.5,
|
||||||
|
"lane": 1,
|
||||||
|
"shared": 0.8,
|
||||||
|
"shared_lane": 0.8,
|
||||||
|
"share_busway": 0.9,
|
||||||
|
"track": 1.5
|
||||||
|
},
|
||||||
|
"cycleway:right": {
|
||||||
|
"yes": 0.95,
|
||||||
|
"no": 0.5,
|
||||||
|
"lane": 1,
|
||||||
|
"shared": 0.8,
|
||||||
|
"shared_lane": 0.8,
|
||||||
|
"share_busway": 0.9,
|
||||||
|
"track": 1.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
AspectedRouting/Profiles/bicycle/bicycle.safety.test.csv
Normal file
13
AspectedRouting/Profiles/bicycle/bicycle.safety.test.csv
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
expected,highway,cycleway,cyclestreet,foot,bicycle,cycleway:right
|
||||||
|
0.15,primary,no,,,,
|
||||||
|
0.285,primary,yes,,,,
|
||||||
|
0.45,primary,track,,,,
|
||||||
|
0.4,secondary,lane,,,,
|
||||||
|
0.9,residential,,,,,
|
||||||
|
1,cycleway,,,,,
|
||||||
|
1.35,residential,,yes,,,
|
||||||
|
0.95,cycleway,,,designated,,
|
||||||
|
0.95,footway,,,designated,,
|
||||||
|
1.425,path,,,designated,designated,
|
||||||
|
1.5,path,,,,designated,
|
||||||
|
0.4,secondary,,,,,lane
|
|
44
AspectedRouting/Profiles/general/legal_maxspeed_be.json
Normal file
44
AspectedRouting/Profiles/general/legal_maxspeed_be.json
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"name": "legal_maxspeed_be",
|
||||||
|
"description": "Gives, for each type of highway, which the default legal maxspeed is in Belgium. This file is intended to be reused for in all vehicles, from pedestrian to car. In some cases, a legal maxspeed is not really defined (e.g. on footways). In that case, a socially acceptable speed should be taken (e.g.: a bicycle on a pedestrian path will go say around 12km/h)",
|
||||||
|
"unit": "km/h",
|
||||||
|
"$default": 30,
|
||||||
|
"value": {
|
||||||
|
"$firstMatchOf": [
|
||||||
|
"maxspeed",
|
||||||
|
"designation",
|
||||||
|
"highway",
|
||||||
|
"ferry"
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"maxspeed": "$parse",
|
||||||
|
"highway": {
|
||||||
|
"cycleway": 30,
|
||||||
|
"footway": 20,
|
||||||
|
"crossing": 20,
|
||||||
|
"pedestrian": 15,
|
||||||
|
"path": 15,
|
||||||
|
"corridor": 5,
|
||||||
|
"residential": 30,
|
||||||
|
"living_street": 20,
|
||||||
|
"service": 30,
|
||||||
|
"services": 30,
|
||||||
|
"track": 50,
|
||||||
|
"unclassified": 50,
|
||||||
|
"road": 50,
|
||||||
|
"motorway": 120,
|
||||||
|
"motorway_link": 120,
|
||||||
|
"primary": 90,
|
||||||
|
"primary_link": 90,
|
||||||
|
"secondary": 50,
|
||||||
|
"secondary_link": 50,
|
||||||
|
"tertiary": 50,
|
||||||
|
"tertiary_link": 50
|
||||||
|
},
|
||||||
|
"designation": {
|
||||||
|
"towpath": 30
|
||||||
|
},
|
||||||
|
"ferry": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
AspectedRouting/Program.cs
Normal file
111
AspectedRouting/Program.cs
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Functions;
|
||||||
|
using AspectedRouting.IO;
|
||||||
|
|
||||||
|
namespace AspectedRouting
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var files = Directory.EnumerateFiles("Profiles", "*.json", SearchOption.AllDirectories)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
|
||||||
|
var context = new Context();
|
||||||
|
|
||||||
|
|
||||||
|
MdPrinter.GenerateHelpText("IO/md/helpText.md");
|
||||||
|
|
||||||
|
var testSuites = new List<FunctionTestSuite>();
|
||||||
|
var aspects = new List<AspectMetadata>();
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
var fi = new FileInfo(file);
|
||||||
|
var aspect = JsonParser.AspectFromJson(context, File.ReadAllText(file), fi.Name);
|
||||||
|
if (aspect != null)
|
||||||
|
{
|
||||||
|
aspects.Add(aspect);
|
||||||
|
|
||||||
|
var testPath = fi.DirectoryName + "/" + aspect.Name + ".test.csv";
|
||||||
|
if (File.Exists(testPath))
|
||||||
|
{
|
||||||
|
var tests = FunctionTestSuite.FromString(aspect, File.ReadAllText(testPath));
|
||||||
|
testSuites.Add(tests);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[{aspect.Name}] No tests found: to directory" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var aspect in aspects)
|
||||||
|
{
|
||||||
|
context.AddFunction(aspect.Name, aspect);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var profilePath = "Profiles/bicycle/bicycle.json";
|
||||||
|
var profile = JsonParser.ProfileFromJson(context, File.ReadAllText(profilePath), new FileInfo(profilePath));
|
||||||
|
|
||||||
|
var profileFi = new FileInfo(profilePath);
|
||||||
|
var profileTests = new List<ProfileTestSuite>();
|
||||||
|
foreach (var profileName in profile.Profiles.Keys)
|
||||||
|
{
|
||||||
|
var testPath = profileFi.DirectoryName + "/" + profile.Name + "." + profileName + ".csv";
|
||||||
|
if (File.Exists(testPath))
|
||||||
|
{
|
||||||
|
var test = ProfileTestSuite.FromString(profile, profileName, File.ReadAllText(testPath));
|
||||||
|
profileTests.Add(test);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.SanityCheckProfile();
|
||||||
|
|
||||||
|
var luaPrinter = new LuaPrinter();
|
||||||
|
luaPrinter.CreateProfile(profile, context);
|
||||||
|
|
||||||
|
|
||||||
|
var testCode = "\n\n\n\n\n\n\n\n--------------------------- Test code -------------------------\n\n\n";
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var testSuite in testSuites)
|
||||||
|
{
|
||||||
|
testSuite.Run();
|
||||||
|
testCode += testSuite.ToLua() + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var testSuite in profileTests)
|
||||||
|
{
|
||||||
|
testCode += testSuite.ToLua() + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Compatibility code, itinero-transit doesn't know 'print'
|
||||||
|
testCode += string.Join("\n",
|
||||||
|
"",
|
||||||
|
"if (itinero == nil) then",
|
||||||
|
" itinero = {}",
|
||||||
|
" itinero.log = print",
|
||||||
|
"",
|
||||||
|
" -- Itinero is not defined -> we are running from a lua interpreter -> the tests are intended",
|
||||||
|
" runTests = true",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"else",
|
||||||
|
" print = itinero.log",
|
||||||
|
"end",
|
||||||
|
"",
|
||||||
|
"if (not failed_tests and not failed_profile_tests) then",
|
||||||
|
" print(\"Tests OK\")",
|
||||||
|
"end"
|
||||||
|
);
|
||||||
|
|
||||||
|
File.WriteAllText("output.lua", luaPrinter.ToLua() + testCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
AspectedRouting/Typ/BoolType.cs
Normal file
9
AspectedRouting/Typ/BoolType.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace AspectedRouting.Typ
|
||||||
|
{
|
||||||
|
public class BoolType : Type
|
||||||
|
{
|
||||||
|
public BoolType() : base("bool", true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
AspectedRouting/Typ/Curry.cs
Normal file
51
AspectedRouting/Typ/Curry.cs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Typ
|
||||||
|
{
|
||||||
|
public class Curry : Type
|
||||||
|
{
|
||||||
|
public readonly Type ArgType;
|
||||||
|
public readonly Type ResultType;
|
||||||
|
|
||||||
|
public Curry(Type argType, Type resultType) : base(ToString(argType, resultType), false)
|
||||||
|
{
|
||||||
|
ArgType = argType;
|
||||||
|
ResultType = resultType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToString(Type argType, Type resultType)
|
||||||
|
{
|
||||||
|
var arg = argType.ToString();
|
||||||
|
if (argType is Curry)
|
||||||
|
{
|
||||||
|
arg = $"({arg})";
|
||||||
|
}
|
||||||
|
|
||||||
|
return arg + " -> " + resultType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Curry ConstructFrom(Type resultType, params Type[] types)
|
||||||
|
{
|
||||||
|
return ConstructFrom(resultType, types.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Curry ConstructFrom(Type resultType, List<Type> types)
|
||||||
|
{
|
||||||
|
if (types.Count == 0)
|
||||||
|
{
|
||||||
|
throw new Exception("No argument types given");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types.Count == 1)
|
||||||
|
{
|
||||||
|
return new Curry(types[0], resultType);
|
||||||
|
}
|
||||||
|
|
||||||
|
var arg = types[0];
|
||||||
|
var rest = types.GetRange(1, types.Count - 1);
|
||||||
|
return new Curry(arg, ConstructFrom(resultType, rest));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
AspectedRouting/Typ/DoubleType.cs
Normal file
9
AspectedRouting/Typ/DoubleType.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace AspectedRouting.Typ
|
||||||
|
{
|
||||||
|
public class DoubleType : Type
|
||||||
|
{
|
||||||
|
public DoubleType() : base("double", true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
AspectedRouting/Typ/IntType.cs
Normal file
9
AspectedRouting/Typ/IntType.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace AspectedRouting.Typ
|
||||||
|
{
|
||||||
|
public class IntType : Type
|
||||||
|
{
|
||||||
|
public IntType() : base("int", true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
AspectedRouting/Typ/ListType.cs
Normal file
12
AspectedRouting/Typ/ListType.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace AspectedRouting.Typ
|
||||||
|
{
|
||||||
|
public class ListType : Type
|
||||||
|
{
|
||||||
|
public Type InnerType { get; }
|
||||||
|
|
||||||
|
public ListType(Type of) : base($"list ({of})", false)
|
||||||
|
{
|
||||||
|
InnerType = of;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
AspectedRouting/Typ/NatType.cs
Normal file
9
AspectedRouting/Typ/NatType.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace AspectedRouting.Typ
|
||||||
|
{
|
||||||
|
public class NatType : Type
|
||||||
|
{
|
||||||
|
public NatType() : base("nat", true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
AspectedRouting/Typ/PDoubleType.cs
Normal file
9
AspectedRouting/Typ/PDoubleType.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace AspectedRouting.Typ
|
||||||
|
{
|
||||||
|
public class PDoubleType : Type
|
||||||
|
{
|
||||||
|
public PDoubleType() : base("pdouble", true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
AspectedRouting/Typ/StringType.cs
Normal file
9
AspectedRouting/Typ/StringType.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace AspectedRouting.Typ
|
||||||
|
{
|
||||||
|
public class StringType : Type
|
||||||
|
{
|
||||||
|
public StringType() : base("string", true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
AspectedRouting/Typ/TagsType.cs
Normal file
9
AspectedRouting/Typ/TagsType.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
namespace AspectedRouting.Typ
|
||||||
|
{
|
||||||
|
public class TagsType : Type
|
||||||
|
{
|
||||||
|
public TagsType() : base("tags", true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
AspectedRouting/Typ/Type.cs
Normal file
38
AspectedRouting/Typ/Type.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
namespace AspectedRouting.Typ
|
||||||
|
{
|
||||||
|
public abstract class Type
|
||||||
|
{
|
||||||
|
protected Type(string name, bool isBuiltin)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
if (isBuiltin)
|
||||||
|
{
|
||||||
|
Typs.AddBuiltin(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is Type t && t.Name.Equals(Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool Equals(Type other)
|
||||||
|
{
|
||||||
|
return string.Equals(Name, other.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return (Name != null ? Name.GetHashCode() : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue