Add documentation output
This commit is contained in:
parent
df63111009
commit
b877fff2b5
19 changed files with 643 additions and 441 deletions
|
@ -7,14 +7,14 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0"/>
|
||||
<PackageReference Include="xunit" Version="2.4.0"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0"/>
|
||||
<PackageReference Include="coverlet.collector" Version="1.0.1"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AspectedRouting\AspectedRouting.csproj" />
|
||||
<ProjectReference Include="..\AspectedRouting\AspectedRouting.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -11,6 +11,21 @@ namespace AspectedRouting.Test
|
|||
{
|
||||
public class FunctionsTest
|
||||
{
|
||||
private readonly string constString = "{\"$const\": \"a\"}";
|
||||
|
||||
private readonly string IfDottedConditionJson
|
||||
= "{" +
|
||||
"\"$ifdotted\": {\"$eq\": \"yes\"}," +
|
||||
"\"then\":{\"$const\": \"a\"}," +
|
||||
"\"else\": {\"$const\": \"b\"}" +
|
||||
"}";
|
||||
|
||||
private readonly string IfSimpleConditionJson
|
||||
= "{" +
|
||||
"\"$if\": true," +
|
||||
"\"then\":\"thenResult\"," +
|
||||
"\"else\": \"elseResult\"}";
|
||||
|
||||
private IExpression MustMatchJson()
|
||||
{
|
||||
var json = "{" +
|
||||
|
@ -32,10 +47,9 @@ namespace AspectedRouting.Test
|
|||
[Fact]
|
||||
public void TestAll_AllTags_Yes()
|
||||
{
|
||||
var tagsAx = new Dictionary<string, string>
|
||||
{
|
||||
{"a", "b"},
|
||||
{"x", "y"}
|
||||
var tagsAx = new Dictionary<string, string> {
|
||||
{ "a", "b" },
|
||||
{ "x", "y" }
|
||||
};
|
||||
|
||||
var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize();
|
||||
|
@ -46,9 +60,8 @@ namespace AspectedRouting.Test
|
|||
[Fact]
|
||||
public void TestAll_NoMatch_No()
|
||||
{
|
||||
var tagsAx = new Dictionary<string, string>
|
||||
{
|
||||
{"a", "b"},
|
||||
var tagsAx = new Dictionary<string, string> {
|
||||
{ "a", "b" }
|
||||
};
|
||||
|
||||
var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize();
|
||||
|
@ -59,10 +72,9 @@ namespace AspectedRouting.Test
|
|||
[Fact]
|
||||
public void TestAll_NoMatchDifferent_No()
|
||||
{
|
||||
var tagsAx = new Dictionary<string, string>
|
||||
{
|
||||
{"a", "b"},
|
||||
{"x", "someRandomValue"}
|
||||
var tagsAx = new Dictionary<string, string> {
|
||||
{ "a", "b" },
|
||||
{ "x", "someRandomValue" }
|
||||
};
|
||||
|
||||
var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize();
|
||||
|
@ -70,19 +82,6 @@ namespace AspectedRouting.Test
|
|||
Assert.Equal("no", result);
|
||||
}
|
||||
|
||||
private string IfDottedConditionJson
|
||||
= "{" +
|
||||
"\"$ifdotted\": {\"$eq\": \"yes\"}," +
|
||||
"\"then\":{\"$const\": \"a\"}," +
|
||||
"\"else\": {\"$const\": \"b\"}" +
|
||||
"}";
|
||||
|
||||
private string IfSimpleConditionJson
|
||||
= "{" +
|
||||
"\"$if\": true," +
|
||||
"\"then\":\"thenResult\"," +
|
||||
"\"else\": \"elseResult\"}";
|
||||
|
||||
[Fact]
|
||||
public void TestParsing_SimpleIf_CorrectExpression()
|
||||
{
|
||||
|
@ -129,9 +128,6 @@ namespace AspectedRouting.Test
|
|||
Assert.Equal("b", resultF);
|
||||
}
|
||||
|
||||
|
||||
private string constString = "{\"$const\": \"a\"}";
|
||||
|
||||
[Fact]
|
||||
public void Parse_ConstString_TypeIsFree()
|
||||
{
|
||||
|
@ -231,8 +227,7 @@ namespace AspectedRouting.Test
|
|||
|
||||
var e = new Var("e");
|
||||
var f = new Var("f");
|
||||
var newTypes = Funcs.Const.Types.RenameVars(new[]
|
||||
{
|
||||
var newTypes = Funcs.Const.Types.RenameVars(new[] {
|
||||
new Curry(e, e),
|
||||
new Curry(new Curry(b, f), new Curry(new Curry(a, b), new Curry(a, f)))
|
||||
}).ToList();
|
||||
|
@ -308,9 +303,9 @@ namespace AspectedRouting.Test
|
|||
var unifB = tags2pdouble.Unify(tags2double, true);
|
||||
Assert.NotNull(unifB);
|
||||
|
||||
var unifC = tags2double.Unify(tags2pdouble, false);
|
||||
var unifC = tags2double.Unify(tags2pdouble);
|
||||
Assert.NotNull(unifC);
|
||||
var unifD = tags2pdouble.Unify(tags2double, false);
|
||||
var unifD = tags2pdouble.Unify(tags2double);
|
||||
Assert.Null(unifD);
|
||||
}
|
||||
|
||||
|
@ -323,7 +318,7 @@ namespace AspectedRouting.Test
|
|||
Typs.String,
|
||||
new Curry(Typs.String, Typs.Bool));
|
||||
var f0 = f.Specialize(strstrb);
|
||||
Assert.Equal(new[] {strstrb}, f0.Types);
|
||||
Assert.Equal(new[] { strstrb }, f0.Types);
|
||||
|
||||
var strstrstr = new Curry(
|
||||
Typs.String,
|
||||
|
@ -331,7 +326,7 @@ namespace AspectedRouting.Test
|
|||
|
||||
var f1 = f.Specialize(strstrstr);
|
||||
|
||||
Assert.Equal(new[] {strstrb, strstrstr}, f1.Types);
|
||||
Assert.Equal(new[] { strstrb, strstrstr }, f1.Types);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -341,11 +336,11 @@ namespace AspectedRouting.Test
|
|||
var p1 = Funcs.Const.Apply(new Constant(1.0)).Specialize(
|
||||
new Curry(new Var("a"), Typs.Double));
|
||||
|
||||
var exprs = new[] {p0, p1};
|
||||
var exprs = new[] { p0, p1 };
|
||||
var newTypes = exprs.SpecializeToCommonTypes(out var _);
|
||||
Assert.Single(newTypes);
|
||||
|
||||
exprs = new[] {p1, p0};
|
||||
exprs = new[] { p1, p0 };
|
||||
newTypes = exprs.SpecializeToCommonTypes(out var _);
|
||||
Assert.Single(newTypes);
|
||||
}
|
||||
|
@ -360,7 +355,7 @@ namespace AspectedRouting.Test
|
|||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void ParseFunction_Duration_TotalMinutes()
|
||||
{
|
||||
|
@ -376,10 +371,8 @@ namespace AspectedRouting.Test
|
|||
{
|
||||
var e = new Apply(new Apply(Funcs.Default, new Constant("a")), Funcs.Id);
|
||||
Assert.Single(e.Types);
|
||||
|
||||
|
||||
Assert.Equal("string -> string", e.Types.First().ToString());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using AspectedRouting.IO.itinero1;
|
||||
using AspectedRouting.IO.LuaSkeleton;
|
||||
using AspectedRouting.Language;
|
||||
using AspectedRouting.Language.Functions;
|
||||
|
@ -13,12 +12,11 @@ namespace AspectedRouting.Test
|
|||
public void ToLua_SimpleMapping_Table()
|
||||
{
|
||||
var mapping = new Mapping(
|
||||
new[] {"a", "b", "c"},
|
||||
new[]
|
||||
{
|
||||
new[] { "a", "b", "c" },
|
||||
new[] {
|
||||
new Constant(5),
|
||||
new Constant(6),
|
||||
new Constant(7),
|
||||
new Constant(7)
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -34,13 +32,11 @@ namespace AspectedRouting.Test
|
|||
public void ToLua_NestedMapping_Table()
|
||||
{
|
||||
var mapping = new Mapping(
|
||||
new[] {"a"},
|
||||
new[]
|
||||
{
|
||||
new Mapping(new[] {"b"},
|
||||
new[]
|
||||
{
|
||||
new Constant(42),
|
||||
new[] { "a" },
|
||||
new[] {
|
||||
new Mapping(new[] { "b" },
|
||||
new[] {
|
||||
new Constant(42)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -54,10 +50,8 @@ namespace AspectedRouting.Test
|
|||
public void Sanity_EveryBasicFunction_HasDescription()
|
||||
{
|
||||
var missing = new List<string>();
|
||||
foreach (var (_, f) in Funcs.Builtins)
|
||||
{
|
||||
if (string.IsNullOrEmpty(f.Description))
|
||||
{
|
||||
foreach (var (_, f) in Funcs.Builtins) {
|
||||
if (string.IsNullOrEmpty(f.Description)) {
|
||||
missing.Add(f.Name);
|
||||
}
|
||||
}
|
||||
|
@ -65,15 +59,13 @@ namespace AspectedRouting.Test
|
|||
Assert.True(0 == missing.Count,
|
||||
"These functions do not have a description: " + string.Join(", ", missing));
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void Sanity_EveryBasicFunction_HasArgNames()
|
||||
{
|
||||
var missing = new List<string>();
|
||||
foreach (var (_, f) in Funcs.Builtins)
|
||||
{
|
||||
if (f.ArgNames == null)
|
||||
{
|
||||
foreach (var (_, f) in Funcs.Builtins) {
|
||||
if (f.ArgNames == null) {
|
||||
missing.Add(f.Name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,18 +9,18 @@ namespace AspectedRouting.Test
|
|||
[Fact]
|
||||
public static void SimpleMapping_SimpleHighway_GivesResult()
|
||||
{
|
||||
var maxspeed = new Mapping(new[] {"residential", "living_street"},
|
||||
var maxspeed = new Mapping(new[] { "residential", "living_street" },
|
||||
new[] {
|
||||
new Constant(30),
|
||||
new Constant(20)
|
||||
}
|
||||
);
|
||||
var resMaxspeed= maxspeed.Evaluate(new Context(), new Constant("residential"));
|
||||
Assert.Equal(30, resMaxspeed);
|
||||
var livingStreetMaxspeed= maxspeed.Evaluate(new Context(), new Constant("living_street"));
|
||||
Assert.Equal(20, livingStreetMaxspeed);
|
||||
var undefinedSpeed = maxspeed.Evaluate(new Context(), new Constant("some_unknown_highway_type"));
|
||||
Assert.Null(undefinedSpeed);
|
||||
var resMaxspeed = maxspeed.Evaluate(new Context(), new Constant("residential"));
|
||||
Assert.Equal(30, resMaxspeed);
|
||||
var livingStreetMaxspeed = maxspeed.Evaluate(new Context(), new Constant("living_street"));
|
||||
Assert.Equal(20, livingStreetMaxspeed);
|
||||
var undefinedSpeed = maxspeed.Evaluate(new Context(), new Constant("some_unknown_highway_type"));
|
||||
Assert.Null(undefinedSpeed);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,37 +10,37 @@ namespace AspectedRouting.Test
|
|||
[Fact]
|
||||
public void MustMatch_SimpleInput()
|
||||
{
|
||||
var mapValue = new Mapping(new[] {"residential", "living_street"},
|
||||
var mapValue = new Mapping(new[] { "residential", "living_street" },
|
||||
new[] {
|
||||
new Constant("yes"),
|
||||
new Constant("no")
|
||||
});
|
||||
var mapTag = new Mapping(new[] {"highway"}, new[] {mapValue});
|
||||
var mapTag = new Mapping(new[] { "highway" }, new[] { mapValue });
|
||||
var mm = Funcs.MustMatch
|
||||
.Apply(
|
||||
new Constant(new[] {new Constant("highway")}),
|
||||
new Constant(new[] { new Constant("highway") }),
|
||||
Funcs.StringStringToTags.Apply(mapTag)
|
||||
)
|
||||
;
|
||||
|
||||
|
||||
var residential = mm.Apply(new Constant(new Dictionary<string, string> {
|
||||
{"highway", "residential"}
|
||||
{ "highway", "residential" }
|
||||
})).Evaluate(new Context());
|
||||
Assert.Equal("yes", residential);
|
||||
|
||||
var living = mm.Apply(new Constant(new Dictionary<string, string> {
|
||||
{"highway", "living_street"}
|
||||
{ "highway", "living_street" }
|
||||
})).Evaluate(new Context());
|
||||
Assert.Equal("no", living);
|
||||
|
||||
var unknown = mm.Apply(new Constant(new Dictionary<string, string> {
|
||||
{"highway", "unknown_type"}
|
||||
{ "highway", "unknown_type" }
|
||||
})).Evaluate(new Context());
|
||||
Assert.Equal("yes", unknown);
|
||||
|
||||
var missing = mm.Apply(new Constant(new Dictionary<string, string> {
|
||||
{"proposed:highway", "unknown_type"}
|
||||
{ "proposed:highway", "unknown_type" }
|
||||
})).Evaluate(new Context());
|
||||
Assert.Equal("no", missing);
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace AspectedRouting.Test.Snippets
|
|||
var func = new Apply(
|
||||
Funcs.StringStringToTags,
|
||||
new Mapping(
|
||||
new[] {"bicycle", "access"},
|
||||
new[] { "bicycle", "access" },
|
||||
new IExpression[] {
|
||||
Funcs.Id,
|
||||
Funcs.Id
|
||||
|
@ -51,7 +51,7 @@ namespace AspectedRouting.Test.Snippets
|
|||
)
|
||||
);
|
||||
|
||||
var tags = new LuaLiteral(new[] {Typs.Tags}, "tags");
|
||||
var tags = new LuaLiteral(new[] { Typs.Tags }, "tags");
|
||||
|
||||
var code = gen.Convert(lua, "result",
|
||||
new List<IExpression> {
|
||||
|
@ -71,7 +71,7 @@ namespace AspectedRouting.Test.Snippets
|
|||
public void SimpleMappingSnippet_SimpleMapping_GeneratesLua()
|
||||
{
|
||||
var mapping = new Mapping(
|
||||
new[] {"1", "-1"},
|
||||
new[] { "1", "-1" },
|
||||
new IExpression[] {
|
||||
new Constant("with"),
|
||||
new Constant("against")
|
||||
|
@ -86,6 +86,5 @@ namespace AspectedRouting.Test.Snippets
|
|||
"local v\nv = tags.oneway\n\nif (v == \"1\") then\n result = \"with\"\nelseif (v == \"-1\") then\n result = \"against\"\nend";
|
||||
Assert.Equal(expected, code);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -18,11 +18,10 @@ namespace AspectedRouting.Test
|
|||
"{\"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\",\"$max\": {\"maxspeed\": \"$parse\",\"highway\": {\"residential\": 30},\"ferry\":5}}";
|
||||
|
||||
var aspect = JsonParser.AspectFromJson(null, json, null);
|
||||
var tags = new Dictionary<string, string>
|
||||
{
|
||||
{"maxspeed", "42"},
|
||||
{"highway", "residential"},
|
||||
{"ferry", "yes"}
|
||||
var tags = new Dictionary<string, string> {
|
||||
{ "maxspeed", "42" },
|
||||
{ "highway", "residential" },
|
||||
{ "ferry", "yes" }
|
||||
};
|
||||
|
||||
Assert.Equal("tags -> pdouble", string.Join(", ", aspect.Types));
|
||||
|
@ -40,11 +39,10 @@ namespace AspectedRouting.Test
|
|||
var aspect = JsonParser.AspectFromJson(null, json, null);
|
||||
|
||||
Assert.Equal(
|
||||
new Dictionary<string, HashSet<string>>
|
||||
{
|
||||
{"maxspeed", new HashSet<string>()},
|
||||
{"highway", new HashSet<string> {"residential"}},
|
||||
{"ferry", new HashSet<string>()}
|
||||
new Dictionary<string, HashSet<string>> {
|
||||
{ "maxspeed", new HashSet<string>() },
|
||||
{ "highway", new HashSet<string> { "residential" } },
|
||||
{ "ferry", new HashSet<string>() }
|
||||
},
|
||||
aspect.PossibleTags());
|
||||
}
|
||||
|
@ -79,7 +77,7 @@ namespace AspectedRouting.Test
|
|||
public void EitherFunc_SpecializeToString_Const()
|
||||
{
|
||||
var a = new Constant("a");
|
||||
|
||||
|
||||
var mconst = new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.Const);
|
||||
var specialized = new Apply(mconst, a).Specialize(Typs.String);
|
||||
|
||||
|
@ -125,7 +123,7 @@ namespace AspectedRouting.Test
|
|||
public void MaxTest()
|
||||
{
|
||||
var ls = new Constant(new ListType(Typs.Double),
|
||||
new[] {1.1, 2.0, 3.0}.Select(d => (object) d));
|
||||
new[] { 1.1, 2.0, 3.0 }.Select(d => (object)d));
|
||||
Assert.Equal("[1.1, 2, 3] : list (double)",
|
||||
ls.Evaluate(null).Pretty() + " : " + string.Join(", ", ls.Types));
|
||||
var mx = Funcs.Max.Apply(ls);
|
||||
|
@ -164,7 +162,7 @@ namespace AspectedRouting.Test
|
|||
[Fact]
|
||||
public void TestStringGeneration()
|
||||
{
|
||||
var v = Var.Fresh(new HashSet<string> {"$a", "$b"});
|
||||
var v = Var.Fresh(new HashSet<string> { "$a", "$b" });
|
||||
Assert.Equal("$c", v.Name);
|
||||
}
|
||||
|
||||
|
@ -176,9 +174,9 @@ namespace AspectedRouting.Test
|
|||
var app = new Apply(
|
||||
new Apply(
|
||||
new Apply(Funcs.Id, Funcs.Id), Funcs.Id), a);
|
||||
var (f, args ) = app.DeconstructApply().Value;
|
||||
Assert.Equal(Funcs.Id.Name, ((Function) f).Name);
|
||||
Assert.Equal(new List<IExpression> {Funcs.Id, Funcs.Id, a}.Select(e => e.ToString()),
|
||||
var (f, args) = app.DeconstructApply().Value;
|
||||
Assert.Equal(Funcs.Id.Name, ((Function)f).Name);
|
||||
Assert.Equal(new List<IExpression> { Funcs.Id, Funcs.Id, a }.Select(e => e.ToString()),
|
||||
args.Select(e => e.ToString()));
|
||||
}
|
||||
|
||||
|
|
|
@ -52,8 +52,8 @@ namespace AspectedRouting.Test
|
|||
new Curry(Typs.Tags, Typs.Double), x
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[Fact]
|
||||
public void WidestCommonGround_StringAndString_String()
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/PencilsConfiguration/ActualSeverity/@EntryValue">ERROR</s:String>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=3112587d_002D1c06_002D4ad3_002Da845_002D73105d4b723a/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" Name="DefaultSnippet_SimpleDefault_GetsLua" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<TestAncestor>
|
||||
<TestId>xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.Snippets.SnippetTests</TestId>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Examples\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -9,19 +9,17 @@ namespace AspectedRouting.IO.LuaSkeleton
|
|||
public class LuaLiteral : IExpression
|
||||
{
|
||||
public readonly string Lua;
|
||||
public IEnumerable<Type> Types { get; }
|
||||
|
||||
public LuaLiteral(Type type, string lua):this(new [] {type}, lua)
|
||||
{
|
||||
|
||||
}
|
||||
public LuaLiteral(Type type, string lua) : this(new[] { type }, lua) { }
|
||||
|
||||
public LuaLiteral(IEnumerable<Type> types, string lua)
|
||||
{
|
||||
Lua = lua;
|
||||
Types = types;
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<Type> Types { get; }
|
||||
|
||||
public object Evaluate(Context c, params IExpression[] arguments)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
@ -32,11 +30,11 @@ namespace AspectedRouting.IO.LuaSkeleton
|
|||
return this;
|
||||
}
|
||||
|
||||
public IExpression PruneTypes(Func<Type, bool> allowedTypes)
|
||||
public IExpression PruneTypes(System.Func<Type, bool> allowedTypes)
|
||||
{
|
||||
var passed = this.Types.Where(allowedTypes);
|
||||
var passed = Types.Where(allowedTypes);
|
||||
if (passed.Any()) {
|
||||
return new LuaLiteral(passed, this.Lua);
|
||||
return new LuaLiteral(passed, Lua);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -44,13 +42,12 @@ namespace AspectedRouting.IO.LuaSkeleton
|
|||
|
||||
public IExpression Optimize()
|
||||
{
|
||||
return this;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Visit(Func<IExpression, bool> f)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -6,8 +6,8 @@ namespace AspectedRouting.IO.itinero1
|
|||
{
|
||||
public class LuaParameterPrinter
|
||||
{
|
||||
private ProfileMetaData _profile;
|
||||
private LuaSkeleton.LuaSkeleton _skeleton;
|
||||
private readonly ProfileMetaData _profile;
|
||||
private readonly LuaSkeleton.LuaSkeleton _skeleton;
|
||||
|
||||
public LuaParameterPrinter(ProfileMetaData profile, LuaSkeleton.LuaSkeleton skeleton)
|
||||
{
|
||||
|
@ -18,8 +18,7 @@ namespace AspectedRouting.IO.itinero1
|
|||
|
||||
public string GenerateDefaultParameters()
|
||||
{
|
||||
var impl = new List<string>()
|
||||
{
|
||||
var impl = new List<string> {
|
||||
"function default_parameters()",
|
||||
" local parameters = {}",
|
||||
DeclareParametersFor(_profile.DefaultParameters),
|
||||
|
@ -31,34 +30,26 @@ namespace AspectedRouting.IO.itinero1
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a piece of code of the following format:
|
||||
///
|
||||
/// parameters["x"] = a;
|
||||
/// parameters["y"] = b:
|
||||
/// ...
|
||||
///
|
||||
/// Where x=a and y=b are defined in the profile
|
||||
///
|
||||
/// Dependencies are added.
|
||||
///
|
||||
/// Note that the caller should still add `local paramaters = default_parameters()`
|
||||
///
|
||||
/// Generates a piece of code of the following format:
|
||||
/// parameters["x"] = a;
|
||||
/// parameters["y"] = b:
|
||||
/// ...
|
||||
/// Where x=a and y=b are defined in the profile
|
||||
/// Dependencies are added.
|
||||
/// Note that the caller should still add `local paramaters = default_parameters()`
|
||||
/// </summary>
|
||||
/// <param name="behaviour"></param>
|
||||
/// <returns></returns>
|
||||
public string DeclareParametersFor(Dictionary<string, IExpression> subParams)
|
||||
{
|
||||
var impl = "";
|
||||
foreach (var (paramName, value) in subParams)
|
||||
{
|
||||
if (paramName.Equals("description"))
|
||||
{
|
||||
foreach (var (paramName, value) in subParams) {
|
||||
if (paramName.Equals("description")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var paramNameTrimmed = paramName.TrimStart('#').AsLuaIdentifier();
|
||||
if (!string.IsNullOrEmpty(paramNameTrimmed))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(paramNameTrimmed)) {
|
||||
impl += $" parameters.{paramNameTrimmed} = {_skeleton.ToLua(value)}\n";
|
||||
}
|
||||
}
|
||||
|
|
225
AspectedRouting/IO/md/ProfileToMD.cs
Normal file
225
AspectedRouting/IO/md/ProfileToMD.cs
Normal file
|
@ -0,0 +1,225 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Language;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Tests;
|
||||
|
||||
namespace AspectedRouting.IO.md
|
||||
{
|
||||
internal class MarkDownSection
|
||||
{
|
||||
private readonly List<string> parts = new List<string>();
|
||||
|
||||
public string ToString()
|
||||
{
|
||||
return string.Join("\n\n", parts);
|
||||
}
|
||||
|
||||
public void AddTitle(string title, int level)
|
||||
{
|
||||
var str = "";
|
||||
for (var i = 0; i < level; i++) {
|
||||
str += "#";
|
||||
}
|
||||
|
||||
str += " " + title;
|
||||
parts.Add(str);
|
||||
}
|
||||
|
||||
public void Add(params string[] paragraph)
|
||||
{
|
||||
parts.Add(string.Join("\n", paragraph));
|
||||
}
|
||||
|
||||
public void AddList(List<string> items)
|
||||
{
|
||||
parts.Add(string.Join("\n", items.Select(i => " - " + i)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class ProfileToMD
|
||||
{
|
||||
private readonly string _behaviour;
|
||||
private readonly Context _c;
|
||||
private readonly ProfileMetaData _profile;
|
||||
private readonly MarkDownSection md = new MarkDownSection();
|
||||
|
||||
public ProfileToMD(ProfileMetaData profile, string behaviour, Context c)
|
||||
{
|
||||
_profile = profile;
|
||||
_behaviour = behaviour;
|
||||
_c = c.WithAspectName(behaviour);
|
||||
_c.DefinedFunctions["speed"] = new AspectMetadata(profile.Speed, "speed", "The speed this vehicle is going",
|
||||
"", "km/h", "", true);
|
||||
if (!profile.Behaviours.ContainsKey(behaviour)) {
|
||||
throw new ArgumentException("Profile does not contain behaviour " + behaviour);
|
||||
}
|
||||
}
|
||||
|
||||
private decimal R(double d)
|
||||
{
|
||||
return Math.Round((decimal)d, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates an entry with `speed`, `priority` for the profile
|
||||
*/
|
||||
public string TableEntry(string msg, Dictionary<string, string> tags, ProfileResult? reference,
|
||||
bool nullOnSame = false)
|
||||
{
|
||||
var profile = _profile.Run(_c, _behaviour, tags);
|
||||
if (!reference.HasValue) {
|
||||
return "| " + msg + " | " + profile.Speed + " | " + profile.Priority + " | ";
|
||||
}
|
||||
|
||||
if (reference.Equals(profile) && nullOnSame) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "| " + msg + " | " + R(profile.Speed) + " | " +
|
||||
R(profile.Speed / reference.Value.Speed) + " | " +
|
||||
R(profile.Priority) + " | " + R(profile.Priority / reference.Value.Priority) + " | " +
|
||||
profile.Access + " | " + profile.Oneway;
|
||||
}
|
||||
|
||||
public void addTagsTable(ProfileResult reference, Dictionary<string, HashSet<string>> usedTags)
|
||||
{
|
||||
var p = _profile;
|
||||
var b = _profile.Behaviours[_behaviour];
|
||||
|
||||
var tableEntries = new List<string>();
|
||||
foreach (var (key, vals) in usedTags) {
|
||||
var values = vals;
|
||||
if (values.Count == 0 && key == "maxspeed") {
|
||||
tableEntries.Add($" | {key}=* (example values below)");
|
||||
values = new HashSet<string> {
|
||||
"20", "30", "50", "70", "90", "120", "150"
|
||||
};
|
||||
}
|
||||
|
||||
if (values.Count == 0) {
|
||||
tableEntries.Add($" | {key}=*");
|
||||
}
|
||||
|
||||
if (values.Count > 0) {
|
||||
foreach (var value in values) {
|
||||
var tags = new Dictionary<string, string> {
|
||||
[key] = value
|
||||
};
|
||||
var entry = TableEntry($"{key}={value} ", tags, reference);
|
||||
if (entry == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tableEntries.Add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
md.Add("| Tags | Speed (km/h) | speedfactor | Priority | priorityfactor | access | oneway | ",
|
||||
"| ---- | ------------ | ----------- | -------- | --------------- | ----- | ------ |",
|
||||
string.Join("\n", tableEntries));
|
||||
}
|
||||
|
||||
public Dictionary<string, IExpression> TagsWithPriorityInfluence()
|
||||
{
|
||||
|
||||
var p = _profile;
|
||||
var parameters = _profile.ParametersFor(_behaviour);
|
||||
var withInfluence = new Dictionary<string, IExpression>();
|
||||
|
||||
foreach (var kv in p.Priority) {
|
||||
if (parameters[kv.Key].Equals(0.0) || parameters[kv.Key].Equals(0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
withInfluence[kv.Key] = kv.Value;
|
||||
}
|
||||
|
||||
return withInfluence;
|
||||
}
|
||||
|
||||
|
||||
public string MainFormula()
|
||||
{
|
||||
var p = _profile;
|
||||
var b = _profile.Behaviours[_behaviour];
|
||||
|
||||
var overridenParams = new HashSet<string>();
|
||||
var paramValues = new Dictionary<string, object>();
|
||||
foreach (var kv in p.DefaultParameters) {
|
||||
paramValues[kv.Key] = kv.Value.Evaluate(_c);
|
||||
}
|
||||
|
||||
foreach (var kv in b) {
|
||||
paramValues[kv.Key] = kv.Value.Evaluate(_c);
|
||||
overridenParams.Add(kv.Key);
|
||||
}
|
||||
|
||||
var mainFormulaParts = p.Priority.Select(delegate(KeyValuePair<string, IExpression> kv) {
|
||||
var key = kv.Key;
|
||||
var param = paramValues[key];
|
||||
if (param.Equals(0) || param.Equals(0.0)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (overridenParams.Contains(key)) {
|
||||
param = "**" + param + "**";
|
||||
}
|
||||
|
||||
|
||||
var called = kv.Value.DirectlyCalled();
|
||||
return param + " * `" + string.Join("", called.calledFunctionNames) + "`";
|
||||
});
|
||||
|
||||
var mainFormula = string.Join(" + ", mainFormulaParts.Where(p => p != ""));
|
||||
return mainFormula;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var p = _profile;
|
||||
var b = _profile.Behaviours[_behaviour];
|
||||
md.AddTitle(_profile.Name + "." + _behaviour, 1);
|
||||
|
||||
md.Add(p.Description);
|
||||
|
||||
if (b.ContainsKey("description")) {
|
||||
md.Add(b["description"].Evaluate(_c).ToString());
|
||||
}
|
||||
|
||||
|
||||
md.Add("This profile is calculated as following (non-default keys are bold):", MainFormula());
|
||||
|
||||
var residentialTags = new Dictionary<string, string> {
|
||||
["highway"] = "residential"
|
||||
};
|
||||
|
||||
md.Add("| Tags | Speed (km/h) | Priority",
|
||||
"| ---- | ----- | ---------- | ",
|
||||
TableEntry("Residential highway (reference)", residentialTags, null));
|
||||
var reference = _profile.Run(_c, _behaviour, residentialTags);
|
||||
md.AddTitle("Tags influencing priority", 2);
|
||||
md.Add(
|
||||
"Priority is what influences which road to take. The routeplanner will search a way where `1/priority` is minimal.");
|
||||
addTagsTable(reference, TagsWithPriorityInfluence().Values.PossibleTagsRecursive(_c));
|
||||
|
||||
md.AddTitle("Tags influencing speed", 2);
|
||||
md.Add(
|
||||
"Speed is used to calculate how long the trip will take, but does _not_ influence which route is taken. Some profiles do use speed as a factor in priority too - in this case, these tags will be mentioned above too.");
|
||||
addTagsTable(reference, _profile.Speed.PossibleTagsRecursive(_c));
|
||||
|
||||
md.AddTitle("Tags influencing access", 2);
|
||||
md.Add("These tags influence whether or not this road can be taken with this vehicle or behaviour");
|
||||
addTagsTable(reference, _profile.Access.PossibleTagsRecursive(_c));
|
||||
md.AddTitle("Tags influencing oneway", 2);
|
||||
md.Add("These tags influence whether or not this road can be taken in all directions or not");
|
||||
addTagsTable(reference, _profile.Oneway.PossibleTagsRecursive(_c));
|
||||
|
||||
|
||||
return md.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
## 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:
|
||||
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
|
||||
|
@ -10,13 +11,18 @@ Generating a good route for travellers is hard; especially for cyclists. They ca
|
|||
- 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, ...
|
||||
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.
|
||||
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.
|
||||
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:
|
||||
|
||||
|
@ -38,9 +44,12 @@ Even though this repository is heavily inspired on OpenStreetMap, it can be gene
|
|||
|
||||
## 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, ...)
|
||||
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.
|
||||
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
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
- string
|
||||
- tags
|
||||
- bool
|
||||
|
||||
## Builtin functions
|
||||
|
||||
- eq
|
||||
|
@ -37,7 +38,6 @@
|
|||
- eitherFunc
|
||||
- stringToTags
|
||||
|
||||
|
||||
### Function overview
|
||||
|
||||
#### eq
|
||||
|
@ -49,8 +49,6 @@ $a | $a | string |
|
|||
|
||||
Returns 'yes' if both values _are_ the same
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -64,7 +62,6 @@ end
|
|||
|
||||
````
|
||||
|
||||
|
||||
#### notEq
|
||||
|
||||
a | b | returns |
|
||||
|
@ -75,8 +72,6 @@ bool | bool |
|
|||
|
||||
OVerloaded function, either boolean not or returns 'yes' if the two passed in values are _not_ the same;
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -93,7 +88,6 @@ function notEq(a, b)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### not
|
||||
|
||||
a | b | returns |
|
||||
|
@ -104,8 +98,6 @@ bool | bool |
|
|||
|
||||
OVerloaded function, either boolean not or returns 'yes' if the two passed in values are _not_ the same;
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -122,7 +114,6 @@ function notEq(a, b)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### inv
|
||||
|
||||
d | returns |
|
||||
|
@ -132,8 +123,6 @@ double | double |
|
|||
|
||||
Calculates `1/d`
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -142,7 +131,6 @@ function inv(n)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### default
|
||||
|
||||
defaultValue | f | returns |
|
||||
|
@ -151,8 +139,6 @@ $a | $b -> $a | $b | $a |
|
|||
|
||||
Calculates function `f` for the given argument. If the result is `null`, the default value is returned instead
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -164,7 +150,6 @@ function default(defaultValue, realValue)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### parse
|
||||
|
||||
s | returns |
|
||||
|
@ -174,8 +159,6 @@ string | pdouble |
|
|||
|
||||
Parses a string into a numerical value
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -208,7 +191,6 @@ function parse(string)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### to_string
|
||||
|
||||
obj | returns |
|
||||
|
@ -217,8 +199,6 @@ $a | string |
|
|||
|
||||
Converts a value into a human readable string
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -227,7 +207,6 @@ function to_string(o)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### concat
|
||||
|
||||
a | b | returns |
|
||||
|
@ -236,8 +215,6 @@ string | string | string |
|
|||
|
||||
Concatenates two strings
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -246,7 +223,6 @@ function concat(a, b)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### containedIn
|
||||
|
||||
list | a | returns |
|
||||
|
@ -255,8 +231,6 @@ list ($a) | $a | bool |
|
|||
|
||||
Given a list of values, checks if the argument is contained in the list.
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -271,7 +245,6 @@ function containedIn(list, a)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### min
|
||||
|
||||
list | returns |
|
||||
|
@ -284,8 +257,6 @@ list (bool) | 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
|
||||
|
@ -303,7 +274,6 @@ function min(list)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### and
|
||||
|
||||
list | returns |
|
||||
|
@ -316,8 +286,6 @@ list (bool) | 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
|
||||
|
@ -335,7 +303,6 @@ function min(list)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### max
|
||||
|
||||
list | returns |
|
||||
|
@ -348,8 +315,6 @@ list (bool) | bool |
|
|||
|
||||
Returns the biggest value in the list. For a list of booleans, this acts as 'or'
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -367,7 +332,6 @@ function max(list)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### or
|
||||
|
||||
list | returns |
|
||||
|
@ -380,8 +344,6 @@ list (bool) | bool |
|
|||
|
||||
Returns the biggest value in the list. For a list of booleans, this acts as 'or'
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -399,7 +361,6 @@ function max(list)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### sum
|
||||
|
||||
list | returns |
|
||||
|
@ -412,8 +373,6 @@ list (bool) | 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
|
||||
|
@ -429,7 +388,6 @@ function sum(list)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### multiply
|
||||
|
||||
list | returns |
|
||||
|
@ -442,8 +400,6 @@ list (bool) | bool |
|
|||
|
||||
Multiplies all the values in a given list. On a list of booleans, this acts as 'and' or 'all'
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -456,7 +412,6 @@ function multiply(list)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### firstMatchOf
|
||||
|
||||
s | returns |
|
||||
|
@ -465,8 +420,6 @@ list (string) | tags -> list ($a) | tags | $a |
|
|||
|
||||
Parses a string into a numerical value
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -492,18 +445,15 @@ function first_match_of(tags, result, order_of_keys, table)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### mustMatch
|
||||
|
||||
neededKeys (filled in by parser) | f | returns |
|
||||
--- | --- | --- |
|
||||
list (string) | tags -> list (bool) | 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.
|
||||
|
||||
|
||||
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:
|
||||
|
||||
|
@ -572,7 +522,6 @@ function must_match(tags, result, needed_keys, table)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### memberOf
|
||||
|
||||
f | tags | returns |
|
||||
|
@ -586,15 +535,18 @@ In order to use this for itinero 1.0, the membership _must_ be the top level exp
|
|||
Conceptually, when the aspect is executed for a way, every relation will be used as argument in the subfunction `f`
|
||||
If this subfunction returns 'true', the entire aspect will return true.
|
||||
|
||||
In the lua implementation for itinero 1.0, this is implemented slightly different: a flag `_relation:<aspect_name>="yes"` will be set if the aspect matches on every way for where this aspect matches.
|
||||
However, this plays poorly with parameters (e.g.: what if we want to cycle over a highway which is part of a certain cycling network with a certain `#network_name`?) Luckily, parameters can only be simple values. To work around this problem, an extra tag is introduced for _every single profile_:`_relation:<profile_name>:<aspect_name>=yes'. The subfunction is thus executed `countOr(relations) * countOf(profiles)` time, yielding `countOf(profiles)` tags. The profile function then picks the tags for himself and strips the `<profile_name>:` away from the key.
|
||||
|
||||
|
||||
In the lua implementation for itinero 1.0, this is implemented slightly different: a
|
||||
flag `_relation:<aspect_name>="yes"` will be set if the aspect matches on every way for where this aspect matches.
|
||||
However, this plays poorly with parameters (e.g.: what if we want to cycle over a highway which is part of a certain
|
||||
cycling network with a certain `#network_name`?) Luckily, parameters can only be simple values. To work around this
|
||||
problem, an extra tag is introduced for _every single
|
||||
profile_:`_relation:<profile_name>:<aspect_name>=yes'. The subfunction is thus executed `countOr(relations) * countOf(
|
||||
profiles)` time, yielding `countOf(
|
||||
profiles)` tags. The profile function then picks the tags for himself and strips the `<profile_name>:` away from the
|
||||
key.
|
||||
|
||||
In the test.csv, one can simply use `_relation:<aspect_name>=yes` to mimic relations in your tests
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -610,7 +562,6 @@ function member_of(calledIn, parameters, tags, result)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### if_then_else
|
||||
|
||||
condition | then | else | returns |
|
||||
|
@ -618,9 +569,8 @@ condition | then | else | returns |
|
|||
bool | $a | $a | $a |
|
||||
bool | $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.
|
||||
|
||||
|
||||
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:
|
||||
|
||||
|
@ -634,7 +584,6 @@ function if_then_else(condition, thn, els)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### if
|
||||
|
||||
condition | then | else | returns |
|
||||
|
@ -642,9 +591,8 @@ condition | then | else | returns |
|
|||
bool | $a | $a | $a |
|
||||
bool | $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.
|
||||
|
||||
|
||||
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:
|
||||
|
||||
|
@ -658,7 +606,6 @@ function if_then_else(condition, thn, els)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### id
|
||||
|
||||
a | returns |
|
||||
|
@ -667,8 +614,6 @@ $a | $a |
|
|||
|
||||
Returns the argument unchanged - the identity function. Seems useless at first sight, but useful in parsing
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -677,7 +622,6 @@ function id(v)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### const
|
||||
|
||||
a | b | returns |
|
||||
|
@ -686,8 +630,6 @@ $a | $b | $a |
|
|||
|
||||
Small utility function, which takes two arguments `a` and `b` and returns `a`. Used extensively to insert freedom
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
@ -696,7 +638,6 @@ function const(a, b)
|
|||
end
|
||||
````
|
||||
|
||||
|
||||
#### constRight
|
||||
|
||||
a | b | returns |
|
||||
|
@ -705,24 +646,20 @@ $a | $b | $b |
|
|||
|
||||
Small utility function, which takes two arguments `a` and `b` and returns `b`. Used extensively to insert freedom
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
||||
````
|
||||
|
||||
|
||||
#### dot
|
||||
|
||||
f | g | a | returns |
|
||||
--- | --- | --- | --- |
|
||||
$b -> $c | $a -> $b | $a | $c |
|
||||
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
|
||||
|
@ -730,16 +667,14 @@ Lua implementation:
|
|||
|
||||
````
|
||||
|
||||
|
||||
#### listDot
|
||||
|
||||
list | a | returns |
|
||||
--- | --- | --- |
|
||||
list ($a -> $b) | $a | 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.
|
||||
|
||||
|
||||
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:
|
||||
|
||||
|
@ -748,7 +683,6 @@ Lua implementation:
|
|||
-- listDot
|
||||
````
|
||||
|
||||
|
||||
#### eitherFunc
|
||||
|
||||
f | g | a | returns |
|
||||
|
@ -756,21 +690,24 @@ f | g | a | returns |
|
|||
$a -> $b | $c -> $d | $a | $b |
|
||||
$a -> $b | $c -> $d | $c | $d |
|
||||
|
||||
EitherFunc is a small utility function, mostly used in the parser. It allows the compiler to choose a function, based on the types.
|
||||
EitherFunc is a small utility function, mostly used in the parser. It allows the compiler to choose a function, based on
|
||||
the types.
|
||||
|
||||
Consider the mapping `{'someKey':'someValue'}`. Under normal circumstances, this acts as a pointwise-function, converting the string `someKey` into `someValue`, just like an ordinary dictionary would do. However, in the context of `mustMatch`, we would prefer this to act as a _check_, that the highway _has_ a key `someKey` which is `someValue`, thus acting as `{'someKey': {'$eq':'someValue'}}. Both behaviours are automatically supported in parsing, by parsing the string as `(eitherFunc id eq) 'someValue'`. The type system is then able to figure out which implementation is needed.
|
||||
Consider the mapping `{'someKey':'someValue'}`. Under normal circumstances, this acts as a pointwise-function,
|
||||
converting the string `someKey` into `someValue`, just like an ordinary dictionary would do. However, in the context
|
||||
of `mustMatch`, we would prefer this to act as a _check_, that the highway _has_ a key `someKey` which is `someValue`,
|
||||
thus acting
|
||||
as `{'someKey': {'$eq':'someValue'}}. Both behaviours are automatically supported in parsing, by parsing the string as `(
|
||||
eitherFunc id eq) 'someValue'`. The type system is then able to figure out which implementation is needed.
|
||||
|
||||
Disclaimer: _you should never ever need this in your profiles_
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
||||
````
|
||||
|
||||
|
||||
#### stringToTags
|
||||
|
||||
f | tags | returns |
|
||||
|
@ -779,8 +716,6 @@ string -> string -> $a | tags | list ($a) |
|
|||
|
||||
stringToTags converts a function `string -> string -> a` into a function `tags -> [a]`
|
||||
|
||||
|
||||
|
||||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
|
|
@ -10,7 +10,6 @@ namespace AspectedRouting.Language
|
|||
{
|
||||
public static class Analysis
|
||||
{
|
||||
|
||||
public static Dictionary<string, (List<Type> Types, string inFunction)> UsedParameters(
|
||||
this ProfileMetaData profile, Context context)
|
||||
{
|
||||
|
@ -20,23 +19,19 @@ namespace AspectedRouting.Language
|
|||
void AddParams(IExpression e, string inFunction)
|
||||
{
|
||||
var parms = e.UsedParameters();
|
||||
foreach (var param in parms)
|
||||
{
|
||||
if (parameters.TryGetValue(param.ParamName, out var typesOldUsage))
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (unified == null) {
|
||||
throw new ArgumentException("Inconsistent parameter usage: the paremeter " +
|
||||
param.ParamName + " is used\n" +
|
||||
$" in {oldUsage} as {string.Join(",", types)}\n" +
|
||||
$" in {inFunction} as {string.Join(",", param.Types)}\n" +
|
||||
$"which can not be unified");
|
||||
"which can not be unified");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
parameters[param.ParamName] = (param.Types.ToList(), inFunction);
|
||||
}
|
||||
}
|
||||
|
@ -47,19 +42,16 @@ namespace AspectedRouting.Language
|
|||
AddParams(profile.Oneway, "profile definition for " + profile.Name + ".oneway");
|
||||
AddParams(profile.Speed, "profile definition for " + profile.Name + ".speed");
|
||||
|
||||
foreach (var (key, expr) in profile.Priority)
|
||||
{
|
||||
foreach (var (key, expr) in profile.Priority) {
|
||||
AddParams(new Parameter(key), profile.Name + ".priority.lefthand");
|
||||
AddParams(expr, profile.Name + ".priority");
|
||||
}
|
||||
|
||||
var calledFunctions = profile.CalledFunctionsRecursive(context).Values
|
||||
.SelectMany(ls => ls).ToHashSet();
|
||||
foreach (var calledFunction in calledFunctions)
|
||||
{
|
||||
foreach (var calledFunction in calledFunctions) {
|
||||
var func = context.GetFunction(calledFunction);
|
||||
if (func is AspectMetadata meta && meta.ProfileInternal)
|
||||
{
|
||||
if (func is AspectMetadata meta && meta.ProfileInternal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -74,10 +66,8 @@ namespace AspectedRouting.Language
|
|||
public static HashSet<Parameter> UsedParameters(this IExpression e)
|
||||
{
|
||||
var result = new HashSet<Parameter>();
|
||||
e.Visit(expr =>
|
||||
{
|
||||
if (expr is Parameter p)
|
||||
{
|
||||
e.Visit(expr => {
|
||||
if (expr is Parameter p) {
|
||||
result.Add(p);
|
||||
}
|
||||
|
||||
|
@ -98,18 +88,14 @@ namespace AspectedRouting.Language
|
|||
|
||||
void ScanExpression(IExpression e, string inFunction)
|
||||
{
|
||||
if (!result.ContainsKey(inFunction))
|
||||
{
|
||||
if (!result.ContainsKey(inFunction)) {
|
||||
result.Add(inFunction, new List<string>());
|
||||
}
|
||||
|
||||
e.Visit(x =>
|
||||
{
|
||||
if (x is FunctionCall fc)
|
||||
{
|
||||
e.Visit(x => {
|
||||
if (x is FunctionCall fc) {
|
||||
result[inFunction].Add(fc.CalledFunctionName);
|
||||
if (!result.ContainsKey(fc.CalledFunctionName))
|
||||
{
|
||||
if (!result.ContainsKey(fc.CalledFunctionName)) {
|
||||
calledFunctions.Enqueue(fc.CalledFunctionName);
|
||||
}
|
||||
}
|
||||
|
@ -123,14 +109,12 @@ namespace AspectedRouting.Language
|
|||
ScanExpression(profile.Oneway, profile.Name + ".oneway");
|
||||
ScanExpression(profile.Speed, profile.Name + ".speed");
|
||||
|
||||
foreach (var (key, expr) in profile.Priority)
|
||||
{
|
||||
foreach (var (key, expr) in profile.Priority) {
|
||||
ScanExpression(new Parameter(key), $"{profile.Name}.priority.{key}.lefthand");
|
||||
ScanExpression(expr, $"{profile.Name}.priority.{key}");
|
||||
}
|
||||
|
||||
while (calledFunctions.TryDequeue(out var calledFunction))
|
||||
{
|
||||
while (calledFunctions.TryDequeue(out var calledFunction)) {
|
||||
var func = c.GetFunction(calledFunction);
|
||||
ScanExpression(func, calledFunction);
|
||||
}
|
||||
|
@ -148,16 +132,14 @@ namespace AspectedRouting.Language
|
|||
var queue = new Queue<IExpression>();
|
||||
exprs.ForEach(queue.Enqueue);
|
||||
|
||||
while (queue.TryDequeue(out var next))
|
||||
{
|
||||
while (queue.TryDequeue(out var next)) {
|
||||
var (p, deps) = next.DirectlyCalled();
|
||||
parameters.UnionWith(p);
|
||||
var toCheck = deps.Except(dependencies);
|
||||
dependencies.UnionWith(deps);
|
||||
|
||||
foreach (var fName in toCheck)
|
||||
{
|
||||
queue.Enqueue(ctx.GetFunction(fName));
|
||||
foreach (var fName in toCheck) {
|
||||
queue.Enqueue(ctx.GetFunction(fName));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,7 +148,8 @@ namespace AspectedRouting.Language
|
|||
|
||||
|
||||
/// <summary>
|
||||
/// Generates an overview of the dependencies of the expression, both which parameters it needs and what other functions (builtin or defined) it needs.
|
||||
/// Generates an overview of the dependencies of the expression, both which parameters it needs and what other
|
||||
/// functions (builtin or defined) it needs.
|
||||
/// </summary>
|
||||
/// <param name="expr"></param>
|
||||
/// <returns></returns>
|
||||
|
@ -176,15 +159,12 @@ namespace AspectedRouting.Language
|
|||
var parameters = new HashSet<string>();
|
||||
var dependencies = new HashSet<string>();
|
||||
|
||||
expr.Visit(e =>
|
||||
{
|
||||
if (e is FunctionCall fc)
|
||||
{
|
||||
expr.Visit(e => {
|
||||
if (e is FunctionCall fc) {
|
||||
dependencies.Add(fc.CalledFunctionName);
|
||||
}
|
||||
|
||||
if (e is Parameter p)
|
||||
{
|
||||
if (e is Parameter p) {
|
||||
parameters.Add(p.ParamName);
|
||||
}
|
||||
|
||||
|
@ -197,8 +177,7 @@ namespace AspectedRouting.Language
|
|||
|
||||
public static string TypeBreakdown(this IExpression e)
|
||||
{
|
||||
|
||||
return e.ToString() + " : "+string.Join(" ; ", e.Types);
|
||||
return e + " : " + string.Join(" ; ", e.Types);
|
||||
}
|
||||
|
||||
public static void SanityCheckProfile(this ProfileMetaData pmd, Context context)
|
||||
|
@ -212,8 +191,7 @@ namespace AspectedRouting.Language
|
|||
string MetaList(IEnumerable<string> paramNames)
|
||||
{
|
||||
var metaInfo = "";
|
||||
foreach (var paramName in paramNames)
|
||||
{
|
||||
foreach (var paramName in paramNames) {
|
||||
var _ = usedMetadata.TryGetValue(paramName, out var inFunction) ||
|
||||
usedMetadata.TryGetValue('#' + paramName, out inFunction);
|
||||
metaInfo += $"\n - {paramName} (used in {inFunction.inFunction})";
|
||||
|
@ -225,43 +203,36 @@ namespace AspectedRouting.Language
|
|||
var usedParameters = usedMetadata.Keys.Select(key => key.TrimStart('#')).ToList();
|
||||
|
||||
var diff = usedParameters.ToHashSet().Except(defaultParameters).ToList();
|
||||
if (diff.Any())
|
||||
{
|
||||
if (diff.Any()) {
|
||||
throw new ArgumentException("No default value set for parameter: " + MetaList(diff));
|
||||
}
|
||||
|
||||
var unused = defaultParameters.Except(usedParameters);
|
||||
if (unused.Any())
|
||||
{
|
||||
if (unused.Any()) {
|
||||
Console.WriteLine("[WARNING] A default value is set for parameter, but it is unused: " +
|
||||
string.Join(", ", unused));
|
||||
string.Join(", ", unused));
|
||||
}
|
||||
|
||||
var paramsUsedInBehaviour = new HashSet<string>();
|
||||
|
||||
foreach (var (behaviourName, behaviourParams) in pmd.Behaviours)
|
||||
{
|
||||
foreach (var (behaviourName, behaviourParams) in pmd.Behaviours) {
|
||||
var sum = 0.0;
|
||||
var explanation = "";
|
||||
paramsUsedInBehaviour.UnionWith(behaviourParams.Keys.Select(k => k.Trim('#')));
|
||||
foreach (var (paramName, _) in pmd.Priority)
|
||||
{
|
||||
if (!pmd.DefaultParameters.ContainsKey(paramName))
|
||||
{
|
||||
foreach (var (paramName, _) in pmd.Priority) {
|
||||
if (!pmd.DefaultParameters.ContainsKey(paramName)) {
|
||||
throw new ArgumentException(
|
||||
$"The behaviour {behaviourName} uses a parameter for which no default is set: {paramName}");
|
||||
}
|
||||
|
||||
if (!behaviourParams.TryGetValue(paramName, out var weight))
|
||||
{
|
||||
if (!behaviourParams.TryGetValue(paramName, out var weight)) {
|
||||
explanation += $"\n - {paramName} = default (not set)";
|
||||
continue;
|
||||
}
|
||||
|
||||
var weightObj = weight.Evaluate(context);
|
||||
|
||||
if (!(weightObj is double d))
|
||||
{
|
||||
if (!(weightObj is double d)) {
|
||||
throw new ArgumentException(
|
||||
$"The parameter {paramName} is not a numeric value in profile {behaviourName}");
|
||||
}
|
||||
|
@ -270,8 +241,7 @@ namespace AspectedRouting.Language
|
|||
explanation += $"\n - {paramName} = {d}";
|
||||
}
|
||||
|
||||
if (Math.Abs(sum) < 0.0001)
|
||||
{
|
||||
if (Math.Abs(sum) < 0.0001) {
|
||||
throw new ArgumentException("Profile " + behaviourName +
|
||||
": the summed parameters to calculate the weight are zero or very low:" +
|
||||
explanation);
|
||||
|
@ -280,8 +250,7 @@ namespace AspectedRouting.Language
|
|||
|
||||
|
||||
var defaultOnly = defaultParameters.Except(paramsUsedInBehaviour).ToList();
|
||||
if (defaultOnly.Any())
|
||||
{
|
||||
if (defaultOnly.Any()) {
|
||||
Console.WriteLine(
|
||||
$"[{pmd.Name}] WARNING: Some parameters only have a default value: {string.Join(", ", defaultOnly)}");
|
||||
}
|
||||
|
@ -289,39 +258,32 @@ namespace AspectedRouting.Language
|
|||
|
||||
public static void SanityCheck(this IExpression e)
|
||||
{
|
||||
e.Visit(expr =>
|
||||
{
|
||||
e.Visit(expr => {
|
||||
var order = new List<IExpression>();
|
||||
var mapping = new List<IExpression>();
|
||||
if (Deconstruct.UnApply(
|
||||
Deconstruct.UnApply(Deconstruct.IsFunc(Funcs.FirstOf), Deconstruct.Assign(order)),
|
||||
Deconstruct.Assign(mapping)
|
||||
).Invoke(expr))
|
||||
{
|
||||
var expectedKeys = ((IEnumerable<object>) order.First().Evaluate(null)).Select(o =>
|
||||
{
|
||||
if (o is IExpression x)
|
||||
{
|
||||
return (string) x.Evaluate(null);
|
||||
Deconstruct.UnApply(Deconstruct.IsFunc(Funcs.FirstOf), Deconstruct.Assign(order)),
|
||||
Deconstruct.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;
|
||||
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())
|
||||
{
|
||||
if (missingInOrder.Any() || missingInMapping.Any()) {
|
||||
var missingInOrderMsg = "";
|
||||
if (missingInOrder.Any())
|
||||
{
|
||||
if (missingInOrder.Any()) {
|
||||
missingInOrderMsg = $"The order misses keys {string.Join(",", missingInOrder)}\n";
|
||||
}
|
||||
|
||||
var missingInMappingMsg = "";
|
||||
if (missingInMapping.Any())
|
||||
{
|
||||
if (missingInMapping.Any()) {
|
||||
missingInMappingMsg =
|
||||
$"The mapping misses mappings for keys {string.Join(", ", missingInMapping)}\n";
|
||||
}
|
||||
|
@ -347,49 +309,95 @@ namespace AspectedRouting.Language
|
|||
public static Dictionary<string, HashSet<string>> PossibleTags(this IEnumerable<IExpression> exprs)
|
||||
{
|
||||
var usedTags = new Dictionary<string, HashSet<string>>();
|
||||
foreach (var expr in exprs)
|
||||
{
|
||||
foreach (var expr in exprs) {
|
||||
var possible = expr.PossibleTags();
|
||||
if (possible == null)
|
||||
{
|
||||
if (possible == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var (key, values) in possible)
|
||||
{
|
||||
if (!usedTags.TryGetValue(key, out var collection))
|
||||
{
|
||||
foreach (var (key, values) in possible) {
|
||||
if (!usedTags.TryGetValue(key, out var collection)) {
|
||||
// This is the first time we see this collection
|
||||
collection = new HashSet<string>();
|
||||
usedTags[key] = collection;
|
||||
|
||||
}
|
||||
foreach (var v in values)
|
||||
{
|
||||
|
||||
foreach (var v in values) {
|
||||
collection.Add(v);
|
||||
}
|
||||
|
||||
if (values.Count == 0) {
|
||||
collection.Add("*");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return usedTags;
|
||||
}
|
||||
|
||||
public static Dictionary<string, HashSet<string>> PossibleTagsRecursive(this IEnumerable<IExpression> exprs, Context c)
|
||||
{ var usedTags = new Dictionary<string, HashSet<string>>();
|
||||
foreach (var e in exprs) {
|
||||
var possibleTags = e.PossibleTagsRecursive(c);
|
||||
|
||||
if (possibleTags != null) {
|
||||
foreach (var tag in possibleTags) {
|
||||
usedTags[tag.Key] = tag.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return usedTags;
|
||||
}
|
||||
|
||||
public static Dictionary<string, HashSet<string>> PossibleTagsRecursive(this IExpression e, Context c)
|
||||
{
|
||||
var allExpr = new List<IExpression>();
|
||||
var queue = new Queue<IExpression>();
|
||||
queue.Enqueue(e);
|
||||
do {
|
||||
var next = queue.Dequeue();
|
||||
allExpr.Add(next);
|
||||
next.Visit(expression => {
|
||||
if (expression is FunctionCall fc) {
|
||||
var called = c.GetFunction(fc.CalledFunctionName);
|
||||
queue.Enqueue(called);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
} while (queue.Any());
|
||||
|
||||
var result = new Dictionary<string, HashSet<string>>();
|
||||
|
||||
foreach (var expression in allExpr) {
|
||||
var subTags = expression.PossibleTags();
|
||||
if (subTags == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var kv in subTags) {
|
||||
if (!result.ContainsKey(kv.Key)) {
|
||||
result[kv.Key] = new HashSet<string>();
|
||||
}
|
||||
|
||||
foreach (var val in kv.Value) {
|
||||
result[kv.Key].Add(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns which tags are used in this calculation
|
||||
///
|
||||
/// Returns which tags are used in this calculation
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
/// <returns>A dictionary containing all possible values. An entry with an empty list indicates a wildcard</returns>
|
||||
public static Dictionary<string, HashSet<string>> PossibleTags(this IExpression e)
|
||||
{
|
||||
var mappings = new List<Mapping>();
|
||||
e.Visit(x =>
|
||||
{
|
||||
e.Visit(x => {
|
||||
/*
|
||||
var networkMapping = new List<IExpression>();
|
||||
if (Deconstruct.UnApply(
|
||||
|
@ -402,16 +410,14 @@ namespace AspectedRouting.Language
|
|||
return false;
|
||||
}*/
|
||||
|
||||
if (x is Mapping m)
|
||||
{
|
||||
if (x is Mapping m) {
|
||||
mappings.Add(m);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (mappings.Count == 0)
|
||||
{
|
||||
if (mappings.Count == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -419,13 +425,10 @@ namespace AspectedRouting.Language
|
|||
var rootMapping = mappings[0];
|
||||
var result = new Dictionary<string, HashSet<string>>();
|
||||
|
||||
foreach (var (key, expr) in rootMapping.StringToResultFunctions)
|
||||
{
|
||||
foreach (var (key, expr) in rootMapping.StringToResultFunctions) {
|
||||
var values = new List<string>();
|
||||
expr.Visit(x =>
|
||||
{
|
||||
if (x is Mapping m)
|
||||
{
|
||||
expr.Visit(x => {
|
||||
if (x is Mapping m) {
|
||||
values.AddRange(m.StringToResultFunctions.Keys);
|
||||
}
|
||||
|
||||
|
@ -451,19 +454,16 @@ namespace AspectedRouting.Language
|
|||
|
||||
void HandleExpression(IExpression e, string calledIn)
|
||||
{
|
||||
e.Visit(f =>
|
||||
{
|
||||
e.Visit(f => {
|
||||
var mapping = new List<IExpression>();
|
||||
if (Deconstruct.UnApply(Deconstruct.IsFunc(Funcs.MemberOf),
|
||||
Deconstruct.Assign(mapping)
|
||||
).Invoke(f))
|
||||
{
|
||||
Deconstruct.Assign(mapping)
|
||||
).Invoke(f)) {
|
||||
memberships.Add(calledIn, mapping.First());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (f is FunctionCall fc)
|
||||
{
|
||||
if (f is FunctionCall fc) {
|
||||
calledFunctionQueue.Enqueue(fc.CalledFunctionName);
|
||||
}
|
||||
|
||||
|
@ -471,15 +471,12 @@ namespace AspectedRouting.Language
|
|||
});
|
||||
}
|
||||
|
||||
foreach (var e in calledFunctions)
|
||||
{
|
||||
foreach (var e in calledFunctions) {
|
||||
HandleExpression(e, "profile_root");
|
||||
}
|
||||
|
||||
while (calledFunctionQueue.TryDequeue(out var functionName))
|
||||
{
|
||||
if (alreadyAnalysedFunctions.Contains(functionName))
|
||||
{
|
||||
while (calledFunctionQueue.TryDequeue(out var functionName)) {
|
||||
if (alreadyAnalysedFunctions.Contains(functionName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -96,14 +96,8 @@ namespace AspectedRouting.Language.Expression
|
|||
}
|
||||
|
||||
|
||||
public ProfileResult Run(Context c, string behaviour, Dictionary<string, string> tags)
|
||||
public Dictionary<string, IExpression> ParametersFor(string behaviour)
|
||||
{
|
||||
if (!Behaviours.ContainsKey(behaviour))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Profile {Name} does not contain the behaviour {behaviour}\nTry one of {string.Join(",", Behaviours.Keys)}");
|
||||
}
|
||||
|
||||
var parameters = new Dictionary<string, IExpression>();
|
||||
|
||||
foreach (var (k, v) in DefaultParameters)
|
||||
|
@ -116,7 +110,18 @@ namespace AspectedRouting.Language.Expression
|
|||
parameters[k.TrimStart('#')] = v;
|
||||
}
|
||||
|
||||
c = c.WithParameters(parameters)
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public ProfileResult Run(Context c, string behaviour, Dictionary<string, string> tags)
|
||||
{
|
||||
if (!Behaviours.ContainsKey(behaviour))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Profile {Name} does not contain the behaviour {behaviour}\nTry one of {string.Join(",", Behaviours.Keys)}");
|
||||
}
|
||||
|
||||
c = c.WithParameters(ParametersFor(behaviour))
|
||||
.WithAspectName(this.Name);
|
||||
tags = new Dictionary<string, string>(tags);
|
||||
var canAccess = Access.Run(c, tags);
|
||||
|
|
|
@ -6,6 +6,7 @@ using AspectedRouting.IO;
|
|||
using AspectedRouting.IO.itinero1;
|
||||
using AspectedRouting.IO.itinero2;
|
||||
using AspectedRouting.IO.jsonParser;
|
||||
using AspectedRouting.IO.md;
|
||||
using AspectedRouting.Language;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Tests;
|
||||
|
@ -18,19 +19,21 @@ namespace AspectedRouting
|
|||
this IEnumerable<string> jsonFileNames, List<string> testFileNames, Context context)
|
||||
{
|
||||
var aspects = new List<(AspectMetadata aspect, AspectTestSuite tests)>();
|
||||
foreach (var file in jsonFileNames)
|
||||
{
|
||||
foreach (var file in jsonFileNames) {
|
||||
var fi = new FileInfo(file);
|
||||
|
||||
var aspect = JsonParser.AspectFromJson(context, File.ReadAllText(file), fi.Name);
|
||||
if (aspect == null) continue;
|
||||
if (aspect == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var testName = aspect.Name + ".test.csv";
|
||||
var testPath = testFileNames.FindTest(testName);
|
||||
AspectTestSuite tests = null;
|
||||
if (!string.IsNullOrEmpty(testPath) && File.Exists(testPath))
|
||||
if (!string.IsNullOrEmpty(testPath) && File.Exists(testPath)) {
|
||||
tests = AspectTestSuite.FromString(aspect, File.ReadAllText(testPath));
|
||||
}
|
||||
|
||||
aspects.Add((aspect, tests));
|
||||
}
|
||||
|
@ -41,10 +44,13 @@ namespace AspectedRouting
|
|||
private static string FindTest(this IEnumerable<string> testFileNames, string testName)
|
||||
{
|
||||
var testPaths = testFileNames.Where(nm => nm.EndsWith(testName)).ToList();
|
||||
if (testPaths.Count > 1)
|
||||
if (testPaths.Count > 1) {
|
||||
Console.WriteLine("[WARNING] Multiple tests found for " + testName + ", using only one arbitrarily");
|
||||
}
|
||||
|
||||
if (testPaths.Count > 0) return testPaths.First();
|
||||
if (testPaths.Count > 0) {
|
||||
return testPaths.First();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -54,39 +60,37 @@ namespace AspectedRouting
|
|||
IEnumerable<string> jsonFiles, IReadOnlyCollection<string> testFiles, Context context, DateTime lastChange)
|
||||
{
|
||||
var result = new List<(ProfileMetaData profile, List<BehaviourTestSuite> profileTests)>();
|
||||
foreach (var jsonFile in jsonFiles)
|
||||
try
|
||||
{
|
||||
foreach (var jsonFile in jsonFiles) {
|
||||
try {
|
||||
var profile =
|
||||
JsonParser.ProfileFromJson(context, File.ReadAllText(jsonFile), new FileInfo(jsonFile),
|
||||
lastChange);
|
||||
if (profile == null) continue;
|
||||
if (profile == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
profile.SanityCheckProfile(context);
|
||||
|
||||
var profileTests = new List<BehaviourTestSuite>();
|
||||
foreach (var behaviourName in profile.Behaviours.Keys)
|
||||
{
|
||||
foreach (var behaviourName in profile.Behaviours.Keys) {
|
||||
var path = testFiles.FindTest($"{profile.Name}.{behaviourName}.behaviour_test.csv");
|
||||
if (path != null && File.Exists(path))
|
||||
{
|
||||
if (path != null && File.Exists(path)) {
|
||||
var test = BehaviourTestSuite.FromString(context, profile, behaviourName,
|
||||
File.ReadAllText(path));
|
||||
profileTests.Add(test);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
Console.WriteLine($"[{profile.Name}] WARNING: no test found for behaviour {behaviourName}");
|
||||
}
|
||||
}
|
||||
|
||||
result.Add((profile, profileTests));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
catch (Exception e) {
|
||||
// PrintError(jsonFile, e);
|
||||
throw new Exception("In the file " + jsonFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -95,43 +99,44 @@ namespace AspectedRouting
|
|||
{
|
||||
var profile = profiles["emergency_vehicle"];
|
||||
var behaviour = profile.Behaviours.Keys.First();
|
||||
do
|
||||
{
|
||||
do {
|
||||
Console.Write(profile.Name + "." + behaviour + " > ");
|
||||
var read = Console.ReadLine();
|
||||
if (read == null) return; // End of stream has been reached
|
||||
if (read == null) {
|
||||
return; // End of stream has been reached
|
||||
}
|
||||
|
||||
if (read == "")
|
||||
{
|
||||
if (read == "") {
|
||||
Console.WriteLine("looƆ sᴉ dɐWʇǝǝɹʇSuǝdO");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read.Equals("quit")) return;
|
||||
if (read.Equals("quit")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (read.Equals("help")) {
|
||||
Console.WriteLine(
|
||||
Utils.Lines("select <behaviourName> to change behaviour or <vehicle.behaviourName> to change vehicle",
|
||||
Utils.Lines(
|
||||
"select <behaviourName> to change behaviour or <vehicle.behaviourName> to change vehicle",
|
||||
""));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read.Equals("clear"))
|
||||
{
|
||||
for (var i = 0; i < 80; i++) Console.WriteLine();
|
||||
if (read.Equals("clear")) {
|
||||
for (var i = 0; i < 80; i++) {
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read.StartsWith("select"))
|
||||
{
|
||||
if (read.StartsWith("select")) {
|
||||
var beh = read.Substring("select".Length + 1).Trim();
|
||||
|
||||
if (beh.Contains("."))
|
||||
{
|
||||
if (beh.Contains(".")) {
|
||||
var profileName = beh.Split(".")[0];
|
||||
if (!profiles.TryGetValue(profileName, out var newProfile))
|
||||
{
|
||||
if (!profiles.TryGetValue(profileName, out var newProfile)) {
|
||||
Console.Error.WriteLine("Profile " + profileName + " not found, ignoring");
|
||||
continue;
|
||||
}
|
||||
|
@ -140,13 +145,11 @@ namespace AspectedRouting
|
|||
beh = beh.Substring(beh.IndexOf(".") + 1);
|
||||
}
|
||||
|
||||
if (profile.Behaviours.ContainsKey(beh))
|
||||
{
|
||||
if (profile.Behaviours.ContainsKey(beh)) {
|
||||
behaviour = beh;
|
||||
Console.WriteLine("Switched to " + beh);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
Console.WriteLine("Behaviour not found. Known behaviours are:\n " +
|
||||
string.Join("\n ", profile.Behaviours.Keys));
|
||||
}
|
||||
|
@ -157,9 +160,10 @@ namespace AspectedRouting
|
|||
|
||||
var tagsRaw = read.Split(";").Select(s => s.Trim());
|
||||
var tags = new Dictionary<string, string>();
|
||||
foreach (var str in tagsRaw)
|
||||
{
|
||||
if (str == "") continue;
|
||||
foreach (var str in tagsRaw) {
|
||||
if (str == "") {
|
||||
continue;
|
||||
}
|
||||
|
||||
var strSplit = str.Split("=");
|
||||
var k = strSplit[0].Trim();
|
||||
|
@ -167,13 +171,11 @@ namespace AspectedRouting
|
|||
tags[k] = v;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
var result = profile.Run(c, behaviour, tags);
|
||||
Console.WriteLine(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
catch (Exception e) {
|
||||
Console.WriteLine(e);
|
||||
Console.WriteLine(e.Message);
|
||||
}
|
||||
|
@ -183,8 +185,7 @@ namespace AspectedRouting
|
|||
private static void PrintError(string file, Exception exception)
|
||||
{
|
||||
var msg = exception.Message;
|
||||
while (exception.InnerException != null)
|
||||
{
|
||||
while (exception.InnerException != null) {
|
||||
exception = exception.InnerException;
|
||||
msg += "\n " + exception.Message;
|
||||
}
|
||||
|
@ -195,10 +196,11 @@ namespace AspectedRouting
|
|||
private static void PrintUsedTags(ProfileMetaData profile, Context context)
|
||||
{
|
||||
Console.WriteLine("\n\n\n---------- " + profile.Name + " --------------");
|
||||
foreach (var (key, values) in profile.AllExpressions(context).PossibleTags())
|
||||
{
|
||||
foreach (var (key, values) in profile.AllExpressions(context).PossibleTags()) {
|
||||
var vs = "*";
|
||||
if (values.Any()) vs = string.Join(", ", values);
|
||||
if (values.Any()) {
|
||||
vs = string.Join(", ", values);
|
||||
}
|
||||
|
||||
Console.WriteLine(key + ": " + vs);
|
||||
}
|
||||
|
@ -209,18 +211,23 @@ namespace AspectedRouting
|
|||
private static void Main(string[] args)
|
||||
{
|
||||
var errMessage = MainWithError(args);
|
||||
if (errMessage != null) Console.WriteLine(errMessage);
|
||||
if (errMessage != null) {
|
||||
Console.WriteLine(errMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public static string MainWithError(string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
if (args.Length < 2) {
|
||||
return "Usage: <directory where all aspects and profiles can be found> <outputdirectory>";
|
||||
}
|
||||
|
||||
var inputDir = args[0];
|
||||
var outputDir = args[1];
|
||||
|
||||
if (!Directory.Exists(outputDir)) Directory.CreateDirectory(outputDir);
|
||||
if (!Directory.Exists(outputDir)) {
|
||||
Directory.CreateDirectory(outputDir);
|
||||
}
|
||||
|
||||
MdPrinter.GenerateHelpText(outputDir + "helpText.md");
|
||||
|
||||
|
@ -232,9 +239,10 @@ namespace AspectedRouting
|
|||
.ToList();
|
||||
tests.Sort();
|
||||
|
||||
foreach (var test in tests)
|
||||
{
|
||||
if (test.EndsWith(".test.csv") || test.EndsWith(".behaviour_test.csv")) continue;
|
||||
foreach (var test in tests) {
|
||||
if (test.EndsWith(".test.csv") || test.EndsWith(".behaviour_test.csv")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new ArgumentException(
|
||||
$"Invalid name for csv file ${test}, should end with either '.behaviour_test.csv' or '.test.csv'");
|
||||
|
@ -244,14 +252,14 @@ namespace AspectedRouting
|
|||
|
||||
var aspects = ParseAspects(files, tests, context);
|
||||
|
||||
foreach (var (aspect, _) in aspects) context.AddFunction(aspect.Name, aspect);
|
||||
foreach (var (aspect, _) in aspects) {
|
||||
context.AddFunction(aspect.Name, aspect);
|
||||
}
|
||||
|
||||
var lastChange = DateTime.UnixEpoch;
|
||||
foreach (var file in files)
|
||||
{
|
||||
foreach (var file in files) {
|
||||
var time = new FileInfo(file).LastWriteTimeUtc;
|
||||
if (lastChange < time)
|
||||
{
|
||||
if (lastChange < time) {
|
||||
lastChange = time;
|
||||
}
|
||||
}
|
||||
|
@ -261,21 +269,34 @@ namespace AspectedRouting
|
|||
|
||||
// With everything parsed and typechecked, time for tests
|
||||
var testsOk = true;
|
||||
foreach (var (aspect, t) in aspects)
|
||||
if (t == null)
|
||||
foreach (var (aspect, t) in aspects) {
|
||||
if (t == null) {
|
||||
Console.WriteLine($"[{aspect.Name}] WARNING: no tests found: please add {aspect.Name}.test.csv");
|
||||
else
|
||||
}
|
||||
else {
|
||||
testsOk &= t.Run();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var (profile, profileTests) in profiles)
|
||||
foreach (var test in profileTests)
|
||||
foreach (var test in profileTests) {
|
||||
testsOk &= test.Run(context);
|
||||
}
|
||||
|
||||
if (!testsOk) return "Some tests failed, quitting now without generating output";
|
||||
if (!testsOk) {
|
||||
return "Some tests failed, quitting now without generating output";
|
||||
}
|
||||
|
||||
foreach (var (profile, profileTests) in profiles)
|
||||
{
|
||||
if (!Directory.Exists($"{outputDir}/profile-documentation/")) {
|
||||
Directory.CreateDirectory($"{outputDir}/profile-documentation/");
|
||||
}
|
||||
|
||||
if (!Directory.Exists($"{outputDir}/itinero2/")) {
|
||||
Directory.CreateDirectory($"{outputDir}/itinero2/");
|
||||
}
|
||||
|
||||
foreach (var (profile, profileTests) in profiles) {
|
||||
PrintUsedTags(profile, context);
|
||||
|
||||
var aspectTests = aspects.Select(a => a.tests).ToList();
|
||||
|
@ -285,8 +306,23 @@ namespace AspectedRouting
|
|||
).ToLua();
|
||||
File.WriteAllText(outputDir + "/" + profile.Name + ".lua", luaProfile);
|
||||
|
||||
foreach (var (behaviourName, _) in profile.Behaviours)
|
||||
{
|
||||
var profileMd = new MarkDownSection();
|
||||
profileMd.AddTitle(profile.Name, 1);
|
||||
|
||||
profileMd.Add(profile.Description);
|
||||
profileMd.AddTitle("Default parameters", 4);
|
||||
profileMd.Add("| name | value | ", "| ---- | ---- | ",
|
||||
string.Join("\n",
|
||||
profile.DefaultParameters.Select(delegate(KeyValuePair<string, IExpression> kv) {
|
||||
var v = kv.Value.Evaluate(context);
|
||||
if (!(v is string || v is int || v is double)) {
|
||||
v = "_special value_";
|
||||
}
|
||||
return $" | {kv.Key} | {v} |";
|
||||
}))
|
||||
);
|
||||
|
||||
foreach (var (behaviourName, vars) in profile.Behaviours) {
|
||||
var lua2behaviour = new LuaPrinter2(
|
||||
profile,
|
||||
behaviourName,
|
||||
|
@ -295,12 +331,24 @@ namespace AspectedRouting
|
|||
profileTests.Where(testsSuite => testsSuite.BehaviourName == behaviourName),
|
||||
lastChange
|
||||
).ToLua();
|
||||
if (!Directory.Exists($"{outputDir}/itinero2/"))
|
||||
Directory.CreateDirectory($"{outputDir}/itinero2/");
|
||||
|
||||
File.WriteAllText(
|
||||
$"{outputDir}/itinero2/{profile.Name}.{behaviourName}.lua",
|
||||
lua2behaviour);
|
||||
|
||||
var behaviourMd = new ProfileToMD(profile, behaviourName, context);
|
||||
|
||||
File.WriteAllText(
|
||||
$"{outputDir}/profile-documentation/{profile.Name}.{behaviourName}.md",
|
||||
behaviourMd.ToString());
|
||||
profileMd.AddTitle($"[{behaviourName}](./{behaviourName}.md)", 2);
|
||||
profileMd.Add(vars["description"].Evaluate(context).ToString());
|
||||
profileMd.Add(behaviourMd.MainFormula());
|
||||
}
|
||||
|
||||
File.WriteAllText(
|
||||
$"{outputDir}/profile-documentation/{profile.Name}.md",
|
||||
profileMd.ToString());
|
||||
}
|
||||
|
||||
File.WriteAllText($"{outputDir}/ProfileMetadata.json",
|
||||
|
@ -310,12 +358,16 @@ namespace AspectedRouting
|
|||
Utils.GenerateTagsOverview(profiles.Select(p => p.profile), context)
|
||||
);
|
||||
|
||||
if (!args.Contains("--no-repl"))
|
||||
|
||||
if (!args.Contains("--no-repl")) {
|
||||
Repl(context, profiles
|
||||
.Select(p => p.profile)
|
||||
.ToDictionary(p => p.Name, p => p));
|
||||
else
|
||||
}
|
||||
else {
|
||||
Console.WriteLine("Not starting REPL as --no-repl is specified");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,5 +42,13 @@ namespace AspectedRouting.Tests
|
|||
"because \n "+str(PriorityExplanation)
|
||||
);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (!(obj is ProfileResult other)) {
|
||||
return false;
|
||||
}
|
||||
return other.Access == this.Access && other.Oneway == this.Oneway && other.Priority == this.Priority && other.Speed == this.Speed;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue