Initial commit

This commit is contained in:
Pieter Vander Vennet 2020-04-30 17:23:44 +02:00
parent d6895e025c
commit 2c2a28d30a
105 changed files with 10038 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.idea/*
**/bin/**
**/obj/**

343
AspectedRouting/Analysis.cs Normal file
View 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;
}
}
}

View 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>

View 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;
}
}
}

View 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);
}
}
}

View 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()})";
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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";
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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}";
}
}
}

View 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());
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}
}

View 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();
}
}
}
}

View 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);
}
}
}

View 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}";
}
}
}

View 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();
}
}
}
}

View 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;
}
}
}
}

View 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";
}
}
}

View 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";
}
}
}
}

View 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;
}
}
}

View 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);
}
}
}
}

View 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))} ";
}
}
}

View 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);
}
}
}

View 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;
}
}
}
}

View 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);
}
}
}

View 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));
}
}
}

View 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");
}
}
}

View 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;
}
}
}

View 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())
);
}
}
}

View 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("-", "_");
}
}
}

View 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;
}
}
}
}

View 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()
{
}
}
}

View 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

View file

@ -0,0 +1,4 @@
function as_number(a)
end

View file

@ -0,0 +1,3 @@
function concat(a, b)
return a .. b
end

View file

View file

View 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

View file

@ -0,0 +1,6 @@
function default(defaultValue, realValue)
if(realValue ~= nil) then
return realValue
end
return defaultValue
end

View file

View 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

View file

View file

@ -0,0 +1,7 @@
function eq(a, b)
if (a == b) then
return "yes"
else
return "no"
end
end

View 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

View file

@ -0,0 +1,3 @@
function id(v)
return v
end

View file

@ -0,0 +1 @@
-- not actually used, should be if_then_else

View 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

View file

@ -0,0 +1,3 @@
function inv(n)
return 1/n
end

View file

@ -0,0 +1,2 @@
-- TODO
-- listDot

View 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

View file

@ -0,0 +1,3 @@
function member_of()
???
end

View 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

View file

@ -0,0 +1,7 @@
function multiply(list)
local factor = 1
for _, value in ipairs(list) do
factor = factor * value
end
return factor;
end

View 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

View 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")

View file

@ -0,0 +1,7 @@
function notEq(a, b)
if (a ~= b) then
return "yes"
else
return "no"
end
end

View 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

View file

@ -0,0 +1 @@
print("ERROR: stringToTag is needed. This should not happen")

View 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

View 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

View file

@ -0,0 +1,3 @@
function to_string(o)
return o;
end

View 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

View 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

View 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

View 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")
````

View 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

View 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

View 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
}
}
}
}

View 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,
1 access oneway speed weight highway bicycle
2 0 0 0 0
3 1 0 30 0.03333 cycleway
4 0 0 0 0 primary
5 1 0 15 0.06666 pedestrian
6 1 0 15 0.0666 pedestrian yes
7 1 0 30 0.033333 residential

View 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"
}
}

View 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"
}
}
}
}

View file

@ -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.

View file

@ -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"
}
}

View 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"
}
}
}
}

View 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
1 expected highway oneway oneway:bicycle junction cycleway cycleway:right cycleway:left
2 both
3 both no
4 with no yes
5 with roundabout
6 both yes opposite
7 against yes -1
8 with residential yes
9 both residential no
10 both residential yes no
11 with residential roundabout
12 both residential no roundabout
13 against residential -1
14 both residential invalidKey no
15 with secondary yes track
16 both secondary yes track
17 both secondary yes track
18 with yes no
19 both residential yes lane

View 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
}
}
}
}

View 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
1 expected highway cycleway cyclestreet foot bicycle cycleway:right
2 0.15 primary no
3 0.285 primary yes
4 0.45 primary track
5 0.4 secondary lane
6 0.9 residential
7 1 cycleway
8 1.35 residential yes
9 0.95 cycleway designated
10 0.95 footway designated
11 1.425 path designated designated
12 1.5 path designated
13 0.4 secondary lane

View 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
View 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);
}
}
}

View file

@ -0,0 +1,9 @@
namespace AspectedRouting.Typ
{
public class BoolType : Type
{
public BoolType() : base("bool", true)
{
}
}
}

View 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));
}
}
}

View file

@ -0,0 +1,9 @@
namespace AspectedRouting.Typ
{
public class DoubleType : Type
{
public DoubleType() : base("double", true)
{
}
}
}

View file

@ -0,0 +1,9 @@
namespace AspectedRouting.Typ
{
public class IntType : Type
{
public IntType() : base("int", true)
{
}
}
}

View 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;
}
}
}

View file

@ -0,0 +1,9 @@
namespace AspectedRouting.Typ
{
public class NatType : Type
{
public NatType() : base("nat", true)
{
}
}
}

View file

@ -0,0 +1,9 @@
namespace AspectedRouting.Typ
{
public class PDoubleType : Type
{
public PDoubleType() : base("pdouble", true)
{
}
}
}

View file

@ -0,0 +1,9 @@
namespace AspectedRouting.Typ
{
public class StringType : Type
{
public StringType() : base("string", true)
{
}
}
}

View file

@ -0,0 +1,9 @@
namespace AspectedRouting.Typ
{
public class TagsType : Type
{
public TagsType() : base("tags", true)
{
}
}
}

View 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