Nearly working version
This commit is contained in:
parent
2c2a28d30a
commit
62584c9189
94 changed files with 4011 additions and 4143 deletions
AspectedRouting.Test
AspectedRouting.slnAspectedRouting
Functions
IO
JsonPrinter.csLuaPrinter.csMdPrinter.csProfileTestSuite.cs
itinero1
LuaPrinter.csLuaStringExtensions.csLuaprinter.Aspect.csLuaprinter.Expressions.csLuaprinter.Profile.csLuaprinter.TestSuites.cs
jsonParser
lua
const.luacontainedIn.luadouble_compare.luamemberOf.luamustMatch.luanotEq.luaremove_relation_prefix.luaunitTestProfile.lua
md
Language
Analysis.csContext.csDeconstruct.cs
Expression
Funcs.csFunctions
All.csConcat.csConst.csConstRight.csConstant.csContainedIn.csDefault.csDot.csEitherFunc.csEq.csFirstMatchOf.csId.csIf.csInv.csListDot.csMapping.csMax.csMemberOf.csMin.csMultiply.csMustMatch.csNotEq.csParameter.csParse.csStringStringToTagsFunction.csSum.csToString.cs
IExpression.csTyp
Profiles
bicycle-manual.lua
Program.csbicycle
Tests
Utils.csbicycle.onewayfull_analysis.csvoutput.lua
20
AspectedRouting.Test/AspectedRouting.Test.csproj
Normal file
20
AspectedRouting.Test/AspectedRouting.Test.csproj
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</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" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\AspectedRouting\AspectedRouting.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
63
AspectedRouting.Test/FunctionsTest.cs
Normal file
63
AspectedRouting.Test/FunctionsTest.cs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.IO.jsonParser;
|
||||||
|
using AspectedRouting.Language;
|
||||||
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Functions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Test
|
||||||
|
{
|
||||||
|
public class FunctionsTest
|
||||||
|
{
|
||||||
|
private IExpression MustMatchJson()
|
||||||
|
{
|
||||||
|
var json = "{" +
|
||||||
|
"\"name\":\"test\"," +
|
||||||
|
"\"description\":\"test\"," +
|
||||||
|
"\"$mustMatch\":{\"a\":\"b\",\"x\":\"y\"}}";
|
||||||
|
return JsonParser.AspectFromJson(new Context(), json, "test.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestAll_AllTags_Yes()
|
||||||
|
{
|
||||||
|
var tagsAx = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{"a", "b"},
|
||||||
|
{"x", "y"}
|
||||||
|
};
|
||||||
|
|
||||||
|
var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize();
|
||||||
|
var result = expr.Evaluate(new Context());
|
||||||
|
Assert.Equal("yes", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestAll_NoMatch_No()
|
||||||
|
{
|
||||||
|
var tagsAx = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{"a", "b"},
|
||||||
|
};
|
||||||
|
|
||||||
|
var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize();
|
||||||
|
var result = expr.Evaluate(new Context());
|
||||||
|
Assert.Equal("no", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestAll_NoMatchDifferent_No()
|
||||||
|
{
|
||||||
|
var tagsAx = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{"a", "b"},
|
||||||
|
{"x", "someRandomValue"}
|
||||||
|
};
|
||||||
|
|
||||||
|
var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize();
|
||||||
|
var result = expr.Evaluate(new Context());
|
||||||
|
Assert.Equal("no", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
AspectedRouting.Test/LuaPrinterTest.cs
Normal file
84
AspectedRouting.Test/LuaPrinterTest.cs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.IO.itinero1;
|
||||||
|
using AspectedRouting.Language;
|
||||||
|
using AspectedRouting.Language.Functions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Test
|
||||||
|
{
|
||||||
|
public class LuaPrinterTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ToLua_SimpleMapping_Table()
|
||||||
|
{
|
||||||
|
var mapping = new Mapping(
|
||||||
|
new[] {"a", "b", "c"},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Constant(5),
|
||||||
|
new Constant(6),
|
||||||
|
new Constant(7),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var luaPrinter = new LuaPrinter(new Context());
|
||||||
|
var result = luaPrinter.MappingToLua(mapping);
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
"{\n a = 5,\n b = 6,\n c = 7\n}"
|
||||||
|
, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToLua_NestedMapping_Table()
|
||||||
|
{
|
||||||
|
var mapping = new Mapping(
|
||||||
|
new[] {"a"},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Mapping(new[] {"b"},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Constant(42),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
var luaPrinter = new LuaPrinter(new Context());
|
||||||
|
var result = luaPrinter.MappingToLua(mapping);
|
||||||
|
Assert.Equal("{\n a = {\n b = 42\n }\n}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Sanity_EveryBasicFunction_HasDescription()
|
||||||
|
{
|
||||||
|
var missing = new List<string>();
|
||||||
|
foreach (var (_, f) in Funcs.Builtins)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(f.Description))
|
||||||
|
{
|
||||||
|
missing.Add(f.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
missing.Add(f.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(0 == missing.Count,
|
||||||
|
"These functions do not have a description: " + string.Join(", ", missing));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
AspectedRouting.Test/TestAnalysis.cs
Normal file
50
AspectedRouting.Test/TestAnalysis.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Language;
|
||||||
|
using AspectedRouting.Language.Functions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Test
|
||||||
|
{
|
||||||
|
public class TestAnalysis
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void OnAll_SmallTagSet_AllCombinations()
|
||||||
|
{
|
||||||
|
var possibleTags = new Dictionary<string, List<string>>
|
||||||
|
{
|
||||||
|
{"a", new List<string> {"x", "y"}}
|
||||||
|
};
|
||||||
|
|
||||||
|
var all = possibleTags.OnAllCombinations(dict => ObjectExtensions.Pretty(dict), new List<string>()).ToList();
|
||||||
|
Assert.Equal(3, all.Count);
|
||||||
|
Assert.Contains("{}", all);
|
||||||
|
Assert.Contains("{a=x;}", all);
|
||||||
|
Assert.Contains("{a=y;}", all);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OnAll_TwoTagSet_AllCombinations()
|
||||||
|
{
|
||||||
|
var possibleTags = new Dictionary<string, List<string>>
|
||||||
|
{
|
||||||
|
{"a", new List<string> {"x", "y"}},
|
||||||
|
{"b", new List<string> {"u", "v"}}
|
||||||
|
};
|
||||||
|
|
||||||
|
var all = possibleTags.OnAllCombinations(dict => dict.Pretty(), new List<string>()).ToList();
|
||||||
|
Assert.Equal(9, all.Count);
|
||||||
|
Assert.Contains("{}", all);
|
||||||
|
Assert.Contains("{a=x;}", all);
|
||||||
|
Assert.Contains("{a=y;}", all);
|
||||||
|
|
||||||
|
Assert.Contains("{b=u;}", all);
|
||||||
|
Assert.Contains("{b=v;}", all);
|
||||||
|
|
||||||
|
Assert.Contains("{a=x;b=u;}", all);
|
||||||
|
Assert.Contains("{a=x;b=v;}", all);
|
||||||
|
Assert.Contains("{a=y;b=u;}", all);
|
||||||
|
Assert.Contains("{a=y;b=v;}", all);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
182
AspectedRouting.Test/TestInterpreter.cs
Normal file
182
AspectedRouting.Test/TestInterpreter.cs
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.IO.jsonParser;
|
||||||
|
using AspectedRouting.Language;
|
||||||
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Functions;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Test
|
||||||
|
{
|
||||||
|
public class TestInterpreter
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void MaxSpeedAspect_Evaluate_CorrectMaxSpeed()
|
||||||
|
{
|
||||||
|
var json =
|
||||||
|
"{\"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"}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.Equal("tags -> double", string.Join(", ", aspect.Types));
|
||||||
|
Assert.Equal(42d, new Apply(aspect, new Constant(tags)).Evaluate(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MaxSpeed_AnalyzeTags_AllTagsReturned()
|
||||||
|
{
|
||||||
|
var json =
|
||||||
|
"{\"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);
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
new Dictionary<string, List<string>>
|
||||||
|
{
|
||||||
|
{"maxspeed", new List<string>()},
|
||||||
|
{"highway", new List<string> {"residential"}},
|
||||||
|
{"ferry", new List<string>()}
|
||||||
|
},
|
||||||
|
aspect.PossibleTags());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void EitherFunc_Const_Id_Specialized_Has_Correct_Type()
|
||||||
|
|
||||||
|
{
|
||||||
|
var b = new Var("b");
|
||||||
|
var c = new Var("c");
|
||||||
|
|
||||||
|
var eitherFunc = new Apply(Funcs.Const, Funcs.Id);
|
||||||
|
Assert.Single(eitherFunc.Types);
|
||||||
|
Assert.Equal(new Curry(b,
|
||||||
|
new Curry(c, c)), eitherFunc.Types.First());
|
||||||
|
|
||||||
|
var (_, (func, arg)) = eitherFunc.FunctionApplications.First();
|
||||||
|
|
||||||
|
|
||||||
|
// func == const : (c -> c) -> b -> (c -> c)
|
||||||
|
var funcType = new Curry(new Curry(c, c),
|
||||||
|
new Curry(b, new Curry(c, c)));
|
||||||
|
Assert.Equal(funcType, func.Types.First());
|
||||||
|
|
||||||
|
// arg == id (but specialized): (c -> c)
|
||||||
|
var argType = new Curry(c, c);
|
||||||
|
Assert.Equal(argType, arg.Types.First());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
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);
|
||||||
|
|
||||||
|
Assert.Equal("((($const $id) $const) \"a\")", specialized.ToString());
|
||||||
|
Assert.Equal("string; $b -> string", string.Join("; ", new Apply(mconst, a).Types));
|
||||||
|
Assert.Equal("\"a\"", specialized.Evaluate(null).Pretty());
|
||||||
|
|
||||||
|
Assert.Equal("\"a\"", new Apply(new Apply(mconst, a), new Constant("42")).Specialize(Typs.String)
|
||||||
|
.Evaluate(null)
|
||||||
|
.Pretty());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_Five_5()
|
||||||
|
{
|
||||||
|
var str = new Constant("5");
|
||||||
|
var parsed = new Apply(Funcs.Parse, str).Specialize(Typs.Double);
|
||||||
|
var o = parsed.Evaluate(null);
|
||||||
|
Assert.Equal(5d, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Concat_TwoString_AB()
|
||||||
|
{
|
||||||
|
var a = new Constant("a");
|
||||||
|
var b = new Constant("b");
|
||||||
|
Assert.Equal("string -> string -> string", Funcs.Concat.Types.First().ToString());
|
||||||
|
var ab = Funcs.Concat.Apply(a, b);
|
||||||
|
Assert.Equal("(($concat \"a\") \"b\")", ab.ToString());
|
||||||
|
Assert.Equal("ab", ab.Evaluate(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Id_Evaluate_ReturnsInput()
|
||||||
|
{
|
||||||
|
var a = new Constant("a");
|
||||||
|
var aId = Funcs.Id.Apply(a);
|
||||||
|
Assert.Equal("\"a\" : string", aId + " : " + string.Join(", ", aId.Types));
|
||||||
|
Assert.Equal("a", aId.Evaluate(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MaxTest()
|
||||||
|
{
|
||||||
|
var ls = new Constant(new ListType(Typs.Double),
|
||||||
|
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);
|
||||||
|
|
||||||
|
|
||||||
|
Assert.Equal("($max [1.1, 2, 3]) : double", mx + " : " + string.Join(", ", mx.Types));
|
||||||
|
Assert.Equal(3d, mx.Evaluate(null));
|
||||||
|
var mxId = Funcs.Id.Apply(mx);
|
||||||
|
// identity function is not printed
|
||||||
|
Assert.Equal("($max [1.1, 2, 3]) : double", mxId + " : " + string.Join(", ", mxId.Types));
|
||||||
|
Assert.Equal(3d, mxId.Evaluate(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Fresh_SomeType_NewVarName()
|
||||||
|
{
|
||||||
|
Assert.Equal("$d", Var.Fresh(
|
||||||
|
Curry.ConstructFrom(new Var("a"),
|
||||||
|
new Var("b"),
|
||||||
|
new Var("b"),
|
||||||
|
new Var("c"),
|
||||||
|
new Var("aa"))).Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Mapping_Evaluate_CorrectMapping()
|
||||||
|
{
|
||||||
|
var mapping = Mapping.Construct(
|
||||||
|
("a", new Constant(5.0)),
|
||||||
|
("b", new Constant(-3))
|
||||||
|
);
|
||||||
|
Assert.Equal("5", mapping.Evaluate(null, new Constant("a")).Pretty());
|
||||||
|
Assert.Equal("-3", mapping.Evaluate(null, new Constant("b")).Pretty());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestStringGeneration()
|
||||||
|
{
|
||||||
|
var v = Var.Fresh(new HashSet<string> {"$a", "$b"});
|
||||||
|
Assert.Equal("$c", v.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestDeconstruct()
|
||||||
|
{
|
||||||
|
var a = new Constant("a");
|
||||||
|
|
||||||
|
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()),
|
||||||
|
args.Select(e => e.ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
AspectedRouting.sln
Normal file
22
AspectedRouting.sln
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspectedRouting", "AspectedRouting\AspectedRouting.csproj", "{4A53D888-6872-4E5D-8022-338CC302473F}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspectedRouting.Test", "AspectedRouting.Test\AspectedRouting.Test.csproj", "{A1309041-8AAE-42D7-A886-94C9FFC6A28C}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{4A53D888-6872-4E5D-8022-338CC302473F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4A53D888-6872-4E5D-8022-338CC302473F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4A53D888-6872-4E5D-8022-338CC302473F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4A53D888-6872-4E5D-8022-338CC302473F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A1309041-8AAE-42D7-A886-94C9FFC6A28C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A1309041-8AAE-42D7-A886-94C9FFC6A28C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A1309041-8AAE-42D7-A886-94C9FFC6A28C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A1309041-8AAE-42D7-A886-94C9FFC6A28C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -1,99 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
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())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,535 +0,0 @@
|
||||||
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("-", "_");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Functions;
|
using AspectedRouting.Language;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.IO
|
namespace AspectedRouting.IO
|
||||||
{
|
{
|
||||||
|
@ -32,7 +32,7 @@ namespace AspectedRouting.IO
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var txt = "## Builtin functions\n\n";
|
var txt = "## Builtin functions\n\n";
|
||||||
foreach (var biFunc in Functions.Funcs.BuiltinNames)
|
foreach (var biFunc in Funcs.BuiltinNames)
|
||||||
{
|
{
|
||||||
txt += "- " + biFunc + "\n";
|
txt += "- " + biFunc + "\n";
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ namespace AspectedRouting.IO
|
||||||
{
|
{
|
||||||
var args = f.ArgBreakdown();
|
var args = f.ArgBreakdown();
|
||||||
var header = "Argument name | ";
|
var header = "Argument name | ";
|
||||||
var line = "--------------| - ";
|
var line = "-------------- | - ";
|
||||||
for (int i = 0; i < f.Types.Count(); i++)
|
for (int i = 0; i < f.Types.Count(); i++)
|
||||||
{
|
{
|
||||||
header += "| ";
|
header += "| ";
|
||||||
|
|
|
@ -1,135 +0,0 @@
|
||||||
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()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
92
AspectedRouting/IO/itinero1/LuaPrinter.cs
Normal file
92
AspectedRouting/IO/itinero1/LuaPrinter.cs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace AspectedRouting.IO.itinero1
|
||||||
|
{
|
||||||
|
public partial class LuaPrinter
|
||||||
|
{
|
||||||
|
private readonly HashSet<string> _dependencies = new HashSet<string>();
|
||||||
|
private readonly HashSet<string> _neededKeys = new HashSet<string>();
|
||||||
|
|
||||||
|
private readonly List<string> _code = new List<string>();
|
||||||
|
private readonly List<string> _tests = new List<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A dictionary containing the implementation of basic functions
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static IEnumerable<string> LoadFunctions(List<string> names)
|
||||||
|
{
|
||||||
|
var imps = new List<string>();
|
||||||
|
|
||||||
|
foreach (var name in names)
|
||||||
|
{
|
||||||
|
var path = $"IO/lua/{name}.lua";
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
imps.Add(File.ReadAllText(path));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return imps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddDep(string name)
|
||||||
|
{
|
||||||
|
_dependencies.Add(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string ToLua()
|
||||||
|
{
|
||||||
|
var deps = _dependencies.ToList();
|
||||||
|
deps.Add("unitTestProfile");
|
||||||
|
deps.Add("inv");
|
||||||
|
deps.Add("double_compare");
|
||||||
|
|
||||||
|
var code = new List<string>();
|
||||||
|
|
||||||
|
code.Add($"-- Itinero 1.0-profile, generated on {DateTime.Now:s}");
|
||||||
|
code.Add("\n\n----------------------------- UTILS ---------------------------");
|
||||||
|
code.AddRange(LoadFunctions(deps).ToList());
|
||||||
|
|
||||||
|
code.Add("\n\n----------------------------- PROFILE ---------------------------");
|
||||||
|
var keys = _neededKeys.Select(key => "\"" + key + "\"");
|
||||||
|
code.Add("\n\nprofile_whitelist = {" + string.Join(", ", keys) + "}");
|
||||||
|
|
||||||
|
code.AddRange(_code);
|
||||||
|
|
||||||
|
code.Add("\n\n ------------------------------- TESTS -------------------------");
|
||||||
|
|
||||||
|
code.AddRange(_tests);
|
||||||
|
|
||||||
|
var compatibility = 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"
|
||||||
|
);
|
||||||
|
code.Add(compatibility);
|
||||||
|
|
||||||
|
return string.Join("\n\n\n", code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
AspectedRouting/IO/itinero1/LuaStringExtensions.cs
Normal file
35
AspectedRouting/IO/itinero1/LuaStringExtensions.cs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace AspectedRouting.IO.itinero1
|
||||||
|
{
|
||||||
|
public static class LuaStringExtensions
|
||||||
|
{
|
||||||
|
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 FunctionName(this string s)
|
||||||
|
{
|
||||||
|
return s.Replace("$", "")
|
||||||
|
.Replace("#", "")
|
||||||
|
.Replace(" ", "_")
|
||||||
|
.Replace(".", "_")
|
||||||
|
.Replace("-", "_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
AspectedRouting/IO/itinero1/Luaprinter.Aspect.cs
Normal file
64
AspectedRouting/IO/itinero1/Luaprinter.Aspect.cs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Language;
|
||||||
|
using AspectedRouting.Language.Expression;
|
||||||
|
|
||||||
|
namespace AspectedRouting.IO.itinero1
|
||||||
|
{
|
||||||
|
public partial class LuaPrinter
|
||||||
|
{
|
||||||
|
private readonly HashSet<string> _alreadyAddedFunctions = new HashSet<string>();
|
||||||
|
|
||||||
|
public void AddFunction(AspectMetadata meta)
|
||||||
|
{
|
||||||
|
if (_alreadyAddedFunctions.Contains(meta.Name))
|
||||||
|
{
|
||||||
|
// already added
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_alreadyAddedFunctions.Add(meta.Name);
|
||||||
|
|
||||||
|
var possibleTags = meta.PossibleTags() ?? new Dictionary<string, List<string>>();
|
||||||
|
var numberOfCombinations = 1;
|
||||||
|
numberOfCombinations = possibleTags.Values.Select(lst => 1 + lst.Count).Multiply();
|
||||||
|
|
||||||
|
var usedParams = meta.UsedParameters();
|
||||||
|
|
||||||
|
var funcNameDeclaration = "";
|
||||||
|
|
||||||
|
meta.Visit(e =>
|
||||||
|
{
|
||||||
|
if (e is Function f && f.Name.Equals(Funcs.MemberOf.Name))
|
||||||
|
{
|
||||||
|
funcNameDeclaration = $"\n local funcName = \"{meta.Name.FunctionName()}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
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)" + funcNameDeclaration,
|
||||||
|
" return " + ToLua(meta.ExpressionImplementation),
|
||||||
|
"end"
|
||||||
|
);
|
||||||
|
|
||||||
|
_code.Add(impl);
|
||||||
|
foreach (var k in possibleTags.Keys)
|
||||||
|
{
|
||||||
|
_neededKeys.Add(k); // To generate a whitelist of OSM-keys that should be kept
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
251
AspectedRouting/IO/itinero1/Luaprinter.Expressions.cs
Normal file
251
AspectedRouting/IO/itinero1/Luaprinter.Expressions.cs
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using AspectedRouting.Language;
|
||||||
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Functions;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
using static AspectedRouting.Language.Deconstruct;
|
||||||
|
|
||||||
|
namespace AspectedRouting.IO.itinero1
|
||||||
|
{
|
||||||
|
public partial class LuaPrinter
|
||||||
|
{
|
||||||
|
private string ToLua(IExpression bare, 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))
|
||||||
|
{
|
||||||
|
AddDep(Funcs.FirstOf.Name);
|
||||||
|
return "first_match_of(tags, result, \n" +
|
||||||
|
" " + ToLua(order.First(), key) + "," +
|
||||||
|
("\n" + MappingToLua((Mapping) collectedMapping.First())).Indent().Indent() +
|
||||||
|
")";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UnApply(
|
||||||
|
UnApply(
|
||||||
|
IsFunc(Funcs.MustMatch),
|
||||||
|
Assign(order))
|
||||||
|
, UnApply(
|
||||||
|
IsFunc(Funcs.StringStringToTags),
|
||||||
|
Assign(collectedMapping))
|
||||||
|
).Invoke(bare))
|
||||||
|
{
|
||||||
|
AddDep(Funcs.MustMatch.Name);
|
||||||
|
return "must_match(tags, result, \n" +
|
||||||
|
" " + ToLua(order.First(), key) + "," +
|
||||||
|
("\n" + MappingToLua((Mapping) collectedMapping.First())).Indent().Indent() +
|
||||||
|
")";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UnApply(
|
||||||
|
IsFunc(Funcs.MemberOf),
|
||||||
|
Any
|
||||||
|
).Invoke(bare))
|
||||||
|
{
|
||||||
|
AddDep("memberOf");
|
||||||
|
return "member_of(funcName, parameters, tags, result)";
|
||||||
|
}
|
||||||
|
|
||||||
|
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('$');
|
||||||
|
AddDep(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(), key));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expr.Types.First() is Curry curry
|
||||||
|
&& curry.ArgType.Equals(Typs.Tags))
|
||||||
|
{
|
||||||
|
var lua = ToLua(expr, 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();
|
||||||
|
AddDep(baseFunc.Name);
|
||||||
|
AddDep("table_to_list");
|
||||||
|
|
||||||
|
return baseFunc.Name +
|
||||||
|
"(table_to_list(tags, result, " +
|
||||||
|
("\n" + MappingToLua(mapping)).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(), 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;
|
||||||
|
AddDep(baseFunc.Name);
|
||||||
|
|
||||||
|
return baseFunc.Name + "(" + string.Join(", ", args.Select(arg => ToLua(arg, 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);
|
||||||
|
return $"{fc.CalledFunctionName.FunctionName()}(parameters, tags, result)";
|
||||||
|
case Constant c:
|
||||||
|
return ConstantToLua(c);
|
||||||
|
case Mapping m:
|
||||||
|
return MappingToLua(m).Indent();
|
||||||
|
case Function f:
|
||||||
|
var fName = f.Name.TrimStart('$');
|
||||||
|
|
||||||
|
if (Funcs.Builtins.ContainsKey(fName))
|
||||||
|
{
|
||||||
|
AddDep(f.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var definedFunc = _context.DefinedFunctions[fName];
|
||||||
|
if (definedFunc.ProfileInternal)
|
||||||
|
{
|
||||||
|
return f.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddFunction(definedFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.Name;
|
||||||
|
case Apply a when UnApply(IsFunc(Funcs.Const), Assign(collected))
|
||||||
|
.Invoke(a):
|
||||||
|
return ToLua(collected.First(), key);
|
||||||
|
|
||||||
|
case Parameter p:
|
||||||
|
return $"parameters[\"{p.ParamName.FunctionName()}\"]";
|
||||||
|
default:
|
||||||
|
throw new Exception("Could not convert " + bare + " to a lua expression");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MappingToLua(Mapping m)
|
||||||
|
{
|
||||||
|
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, key);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return
|
||||||
|
"{\n " +
|
||||||
|
string.Join(",\n ", contents) +
|
||||||
|
"\n}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Neatly creates a value expression in lua, based on a constant
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="c"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private string ConstantToLua(Constant c)
|
||||||
|
{
|
||||||
|
var o = c.Evaluate(_context);
|
||||||
|
switch (o)
|
||||||
|
{
|
||||||
|
case IExpression e:
|
||||||
|
return ConstantToLua(new Constant(e.Types.First(), e.Evaluate(null)));
|
||||||
|
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);
|
||||||
|
})) + "}";
|
||||||
|
default:
|
||||||
|
return o.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
256
AspectedRouting/IO/itinero1/Luaprinter.Profile.cs
Normal file
256
AspectedRouting/IO/itinero1/Luaprinter.Profile.cs
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Language;
|
||||||
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Functions;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.IO.itinero1
|
||||||
|
{
|
||||||
|
public partial class LuaPrinter
|
||||||
|
{
|
||||||
|
private readonly Context _context;
|
||||||
|
|
||||||
|
public LuaPrinter(Context context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateMembershipPreprocessor(ProfileMetaData profile)
|
||||||
|
{
|
||||||
|
var memberships = Analysis.MembershipMappingsFor(profile, _context);
|
||||||
|
|
||||||
|
foreach (var (calledInFunction, membership) in memberships)
|
||||||
|
{
|
||||||
|
var funcMetaData = new AspectMetadata(
|
||||||
|
membership,
|
||||||
|
"relation_preprocessing_for_" + calledInFunction.FunctionName(),
|
||||||
|
"Function preprocessing needed for aspect " + calledInFunction +
|
||||||
|
", called by the relation preprocessor",
|
||||||
|
"Generator", "", "NA"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
AddFunction(funcMetaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var func = new List<string>
|
||||||
|
{
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"-- 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(relation_tags, result)",
|
||||||
|
" local parameters = {}",
|
||||||
|
" local subresult = {}",
|
||||||
|
" local matched = false",
|
||||||
|
" result.attributes_to_keep = {}"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var (calledInFunction, _) in memberships)
|
||||||
|
{
|
||||||
|
foreach (var (behaviourName, parameters) in profile.Behaviours)
|
||||||
|
{
|
||||||
|
var preProcName = "relation_preprocessing_for_" + calledInFunction.FunctionName();
|
||||||
|
|
||||||
|
func.Add("");
|
||||||
|
func.Add("");
|
||||||
|
func.Add(" subresult.attributes_to_keep = {}");
|
||||||
|
func.Add(" parameters = default_parameters()");
|
||||||
|
func.Add(ParametersToLua(parameters));
|
||||||
|
func.Add($" matched = {preProcName}(parameters, relation_tags, subresult)");
|
||||||
|
func.Add(" if (matched) then");
|
||||||
|
var tagKey = "_relation:" + behaviourName.FunctionName() + ":" + calledInFunction.FunctionName();
|
||||||
|
_neededKeys.Add("_relation:"+calledInFunction.FunctionName()); // Slightly different then tagkey!
|
||||||
|
func.Add($" result.attributes_to_keep[\"{tagKey}\"] = \"yes\"");
|
||||||
|
func.Add(" end");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func.Add("end");
|
||||||
|
|
||||||
|
|
||||||
|
_code.Add(string.Join("\n", func));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the necessary called functions and the profile main entry point
|
||||||
|
/// </summary>
|
||||||
|
public void AddProfile(ProfileMetaData profile)
|
||||||
|
{
|
||||||
|
var defaultParameters = "\n";
|
||||||
|
foreach (var (name, (types, inFunction)) in profile.UsedParameters(_context))
|
||||||
|
{
|
||||||
|
defaultParameters += $"{name}: {string.Join(", ", types)}\n" +
|
||||||
|
$" Used in {inFunction}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateMembershipPreprocessor(profile);
|
||||||
|
|
||||||
|
|
||||||
|
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),
|
||||||
|
" if (access == nil or access == \"no\") then",
|
||||||
|
" return",
|
||||||
|
" end",
|
||||||
|
" tags.access = access",
|
||||||
|
" local oneway = " + ToLua(profile.Oneway),
|
||||||
|
" tags.oneway = oneway",
|
||||||
|
" local speed = " + ToLua(profile.Speed),
|
||||||
|
" tags.speed = speed",
|
||||||
|
" local distance = 1 -- the weight per meter for distance travelled is, well, 1m/m",
|
||||||
|
"");
|
||||||
|
|
||||||
|
impl +=
|
||||||
|
"\n local priority = \n ";
|
||||||
|
|
||||||
|
var weightParts = new List<string>();
|
||||||
|
foreach (var (parameterName, expression) in profile.Priority)
|
||||||
|
{
|
||||||
|
var priorityPart = ToLua(new Parameter(parameterName)) + " * ";
|
||||||
|
|
||||||
|
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)))
|
||||||
|
{
|
||||||
|
priorityPart += "parse(" + ToLua(expression) + ")";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
priorityPart += ToLua(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
weightParts.Add(priorityPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = priority",
|
||||||
|
"",
|
||||||
|
" if (oneway == \"both\") then",
|
||||||
|
" result.oneway = 0",
|
||||||
|
" elseif (oneway == \"with\") then",
|
||||||
|
" result.oneway = 1",
|
||||||
|
" else",
|
||||||
|
" result.oneway = 2",
|
||||||
|
" end",
|
||||||
|
"",
|
||||||
|
"end",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"function default_parameters()",
|
||||||
|
" local parameters = {}",
|
||||||
|
ParametersToLua(profile.DefaultParameters),
|
||||||
|
" return parameters",
|
||||||
|
"end",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
var profiles = new List<string>();
|
||||||
|
foreach (var (name, subParams) in profile.Behaviours)
|
||||||
|
{
|
||||||
|
impl += BehaviourFunction(profile, name, subParams, profiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl += "\n\n\n";
|
||||||
|
impl += "profiles = {\n {\n" +
|
||||||
|
string.Join("\n },\n {\n ", profiles) + "\n }\n}";
|
||||||
|
|
||||||
|
_code.Add(impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BehaviourFunction(ProfileMetaData profile,
|
||||||
|
string name,
|
||||||
|
Dictionary<string, IExpression> subParams, List<string> 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\""
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
AddDep("remove_relation_prefix");
|
||||||
|
var impl = string.Join("\n",
|
||||||
|
"",
|
||||||
|
"--[[",
|
||||||
|
description,
|
||||||
|
"]]",
|
||||||
|
"function profile_" + functionName + "(tags, result)",
|
||||||
|
$" tags = remove_relation_prefix(tags, \"{name.FunctionName()}\")",
|
||||||
|
" local parameters = default_parameters()",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
impl += ParametersToLua(subParams);
|
||||||
|
|
||||||
|
impl += " " + profile.Name + "(parameters, tags, result)\n";
|
||||||
|
impl += "end\n";
|
||||||
|
return impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// `local parameters = default_parameters()` must still be invoked by caller!
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subParams"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private string ParametersToLua(Dictionary<string, IExpression> subParams)
|
||||||
|
{
|
||||||
|
|
||||||
|
var impl = "";
|
||||||
|
foreach (var (paramName, value) in subParams)
|
||||||
|
{
|
||||||
|
if (paramName.Equals("description"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
impl += $" parameters.{paramName.TrimStart('#').FunctionName()} = {ToLua(value)}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return impl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
AspectedRouting/IO/itinero1/Luaprinter.TestSuites.cs
Normal file
100
AspectedRouting/IO/itinero1/Luaprinter.TestSuites.cs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Tests;
|
||||||
|
|
||||||
|
namespace AspectedRouting.IO.itinero1
|
||||||
|
{
|
||||||
|
public partial class LuaPrinter
|
||||||
|
{
|
||||||
|
public void AddTestSuite(ProfileTestSuite testSuite)
|
||||||
|
{
|
||||||
|
var tests = string.Join("\n",
|
||||||
|
testSuite.Tests.Select((test, i) => ToLua(testSuite, i, test.Item1, test.tags)));
|
||||||
|
_tests.Add(tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ToLua(ProfileTestSuite testSuite, int index, Expected expected, Dictionary<string, string> tags)
|
||||||
|
{
|
||||||
|
AddDep("debug_table");
|
||||||
|
var parameters = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
|
||||||
|
var keysToCheck = new List<string>();
|
||||||
|
foreach (var (key, value) in tags)
|
||||||
|
{
|
||||||
|
if (key.StartsWith("#"))
|
||||||
|
{
|
||||||
|
parameters[key.TrimStart('#')] = value;
|
||||||
|
}
|
||||||
|
if(key.StartsWith("_relation:"))
|
||||||
|
{
|
||||||
|
keysToCheck.Add(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var key in keysToCheck)
|
||||||
|
{
|
||||||
|
var newKey = key.Replace(".", "_");
|
||||||
|
tags[newKey] = tags[key];
|
||||||
|
tags.Remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
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_{testSuite.BehaviourName.FunctionName()}, " +
|
||||||
|
$"\"{testSuite.BehaviourName}\", " +
|
||||||
|
$"{index}, " +
|
||||||
|
$"{{access = \"{D(expected.Access)}\", speed = {expected.Speed}, oneway = \"{D(expected.Oneway)}\", weight = {expected.Weight} }}, " +
|
||||||
|
tags.ToLuaTable() +
|
||||||
|
")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string D(string s)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(s))
|
||||||
|
{
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void AddTestSuite(FunctionTestSuite testSuite)
|
||||||
|
{
|
||||||
|
var fName = testSuite.FunctionToApply.Name;
|
||||||
|
var tests = string.Join("\n",
|
||||||
|
testSuite.Tests.Select((test, i) => ToLua(fName, i, test.expected, test.tags)));
|
||||||
|
_tests.Add(tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ToLua(string functionToApplyName, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddDep("unitTest");
|
||||||
|
var funcName = functionToApplyName.Replace(" ", "_").Replace(".", "_");
|
||||||
|
return
|
||||||
|
$"unit_test({funcName}, \"{functionToApplyName}\", {index}, \"{expected}\", {parameters.ToLuaTable()}, {tags.ToLuaTable()})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,512 +1,463 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using AspectedRouting.Functions;
|
using AspectedRouting.Language;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
using static AspectedRouting.Functions.Funcs;
|
using AspectedRouting.Language.Functions;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using AspectedRouting.Language.Typ;
|
||||||
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
namespace AspectedRouting.IO
|
|
||||||
{
|
namespace AspectedRouting.IO.jsonParser
|
||||||
public static class JsonParser
|
{
|
||||||
{
|
public static partial class JsonParser
|
||||||
public static AspectMetadata AspectFromJson(Context c, string json, string fileName)
|
{
|
||||||
{
|
public static AspectMetadata AspectFromJson(Context c, string json, string fileName)
|
||||||
try
|
{
|
||||||
{
|
try
|
||||||
var doc = JsonDocument.Parse(json);
|
{
|
||||||
if (doc.RootElement.TryGetProperty("defaults", out _))
|
var doc = JsonDocument.Parse(json);
|
||||||
{
|
if (doc.RootElement.TryGetProperty("defaults", out _))
|
||||||
// this is a profile
|
{
|
||||||
return null;
|
// this is a profile
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
return doc.RootElement.ParseAspect(fileName, c);
|
|
||||||
}
|
return doc.RootElement.ParseAspect(fileName, c);
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
catch (Exception e)
|
||||||
throw new Exception("In the file " + fileName, e);
|
{
|
||||||
}
|
throw new Exception("In the file " + fileName, e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public static ProfileMetaData ProfileFromJson(Context c, string json, FileInfo f)
|
|
||||||
{
|
public static ProfileMetaData ProfileFromJson(Context c, string json, FileInfo f)
|
||||||
try
|
{
|
||||||
{
|
try
|
||||||
var doc = JsonDocument.Parse(json);
|
{
|
||||||
if (!doc.RootElement.TryGetProperty("defaults", out _))
|
var doc = JsonDocument.Parse(json);
|
||||||
{
|
if (!doc.RootElement.TryGetProperty("defaults", out _))
|
||||||
return null;
|
{
|
||||||
// this is an aspect
|
return null;
|
||||||
}
|
// this is an aspect
|
||||||
|
}
|
||||||
return ParseProfile(doc.RootElement, c, f);
|
|
||||||
}
|
return ParseProfile(doc.RootElement, c, f);
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
catch (Exception e)
|
||||||
throw new Exception("In the file " + f, e);
|
{
|
||||||
}
|
throw new Exception("In the file " + f, e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly IExpression _mconst =
|
private static ProfileMetaData ParseProfile(this JsonElement e, Context context, FileInfo filepath)
|
||||||
new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.Const);
|
{
|
||||||
|
var name = e.Get("name");
|
||||||
private static readonly IExpression _mappingWrapper =
|
var author = e.TryGet("author");
|
||||||
new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), StringStringToTags);
|
if (filepath != null && !(name + ".json").ToLower().Equals(filepath.Name.ToLower()))
|
||||||
|
{
|
||||||
private static IExpression ParseMapping(IEnumerable<JsonProperty> allArgs, Context context)
|
throw new ArgumentException($"Filename does not match the defined name: " +
|
||||||
{
|
$"filename is {filepath.Name}, declared name is {name}");
|
||||||
var keys = new List<string>();
|
}
|
||||||
var exprs = new List<IExpression>();
|
|
||||||
|
var vehicleTypes = e.GetProperty("vehicletypes").EnumerateArray().Select(
|
||||||
foreach (var prop in allArgs)
|
el => el.GetString()).ToList();
|
||||||
{
|
var metadata = e.GetProperty("metadata").EnumerateArray().Select(
|
||||||
if (prop.Name.Equals("#"))
|
el => el.GetString()).ToList();
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
var access = ParseProfileProperty(e, context, "access").Finalize();
|
||||||
|
var oneway = ParseProfileProperty(e, context, "oneway").Finalize();
|
||||||
keys.Add(prop.Name);
|
var speed = ParseProfileProperty(e, context, "speed").Finalize();
|
||||||
var argExpr = ParseExpression(prop.Value, context);
|
|
||||||
IExpression mappingWithOptArg;
|
|
||||||
if (argExpr.Types.Count() == 1 && argExpr.Types.First().Equals(Typs.String))
|
IExpression TagsApplied(IExpression x)
|
||||||
{
|
{
|
||||||
mappingWithOptArg =
|
return new Apply(x, new Constant(new Dictionary<string, string>()));
|
||||||
Either(Funcs.Id, Funcs.Eq, argExpr);
|
}
|
||||||
}
|
|
||||||
else
|
context.AddFunction("speed",
|
||||||
{
|
new AspectMetadata(TagsApplied(speed), "speed", "The speed of this profile", author, "", filepath.Name,
|
||||||
mappingWithOptArg = new Apply(_mconst, argExpr);
|
true));
|
||||||
}
|
context.AddFunction("access",
|
||||||
|
new AspectMetadata(TagsApplied(access), "access", "The access of this profile", author, "",
|
||||||
exprs.Add(mappingWithOptArg);
|
filepath.Name,
|
||||||
}
|
true));
|
||||||
|
context.AddFunction("oneway",
|
||||||
try
|
new AspectMetadata(TagsApplied(oneway), "oneway", "The oneway of this profile", author, "",
|
||||||
{
|
filepath.Name,
|
||||||
var simpleMapping = new Mapping(keys, exprs);
|
true));
|
||||||
return new Apply(_mappingWrapper, simpleMapping);
|
context.AddFunction("distance",
|
||||||
}
|
new AspectMetadata(new Constant(1), "distance", "The distance travelled of this profile", author, "",
|
||||||
catch (Exception e)
|
filepath.Name,
|
||||||
{
|
true));
|
||||||
throw new Exception("While constructing a mapping with members " + string.Join(", ", exprs) +
|
|
||||||
": " + e.Message, e);
|
|
||||||
}
|
var weights = new Dictionary<string, IExpression>();
|
||||||
}
|
var weightProperty = e.GetProperty("priority");
|
||||||
|
foreach (var prop in weightProperty.EnumerateObject())
|
||||||
private static IExpression ParseExpression(this JsonElement e, Context context)
|
{
|
||||||
{
|
var parameter = prop.Name.TrimStart('#');
|
||||||
if (e.ValueKind == JsonValueKind.Object)
|
var factor = ParseExpression(prop.Value, context).Finalize();
|
||||||
{
|
weights[parameter] = factor;
|
||||||
// Parse an actual function
|
}
|
||||||
var funcCall = e.EnumerateObject().Where(v => v.Name.StartsWith("$")).ToList();
|
|
||||||
var allArgs = e.EnumerateObject().Where(v => !v.Name.StartsWith("$")).ToList();
|
var profiles = new Dictionary<string, Dictionary<string, IExpression>>();
|
||||||
|
|
||||||
if (funcCall.Count > 2)
|
foreach (var profile in e.GetProperty("behaviours").EnumerateObject())
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Multiple calls defined in object " + e);
|
profiles[profile.Name] = ParseParameters(profile.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (funcCall.Count == 1)
|
return new ProfileMetaData(
|
||||||
{
|
name,
|
||||||
return ParseFunctionCall(context, funcCall, allArgs);
|
e.Get("description"),
|
||||||
}
|
author,
|
||||||
|
filepath?.DirectoryName ?? "unknown",
|
||||||
// Funccall has no elements: this is a mapping of strings or tags onto a value
|
vehicleTypes,
|
||||||
|
e.GetProperty("defaults").ParseParameters(),
|
||||||
return ParseMapping(allArgs, context);
|
profiles,
|
||||||
}
|
access,
|
||||||
|
oneway,
|
||||||
if (e.ValueKind == JsonValueKind.Array)
|
speed,
|
||||||
{
|
weights,
|
||||||
var exprs = e.EnumerateArray().Select(json =>
|
metadata
|
||||||
Either(Funcs.Id, Funcs.Const, json.ParseExpression(context)));
|
);
|
||||||
var list = new Constant(exprs);
|
}
|
||||||
return Either(Funcs.Id, Funcs.ListDot, list);
|
|
||||||
}
|
private static readonly IExpression _mconst =
|
||||||
|
new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.Const);
|
||||||
if (e.ValueKind == JsonValueKind.Number)
|
|
||||||
{
|
private static readonly IExpression _mappingWrapper =
|
||||||
if (e.TryGetDouble(out var d))
|
new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.StringStringToTags);
|
||||||
{
|
|
||||||
return new Constant(d);
|
private static IExpression ParseMapping(IEnumerable<JsonProperty> allArgs, Context context)
|
||||||
}
|
{
|
||||||
|
var keys = new List<string>();
|
||||||
if (e.TryGetInt32(out var i))
|
var exprs = new List<IExpression>();
|
||||||
{
|
|
||||||
return new Constant(i);
|
foreach (var prop in allArgs)
|
||||||
}
|
{
|
||||||
}
|
if (prop.Name.Equals("#"))
|
||||||
|
{
|
||||||
if (e.ValueKind == JsonValueKind.True)
|
continue;
|
||||||
{
|
}
|
||||||
return new Constant(Typs.Bool, "yes");
|
|
||||||
}
|
keys.Add(prop.Name);
|
||||||
|
var argExpr = ParseExpression(prop.Value, context);
|
||||||
if (e.ValueKind == JsonValueKind.False)
|
IExpression mappingWithOptArg;
|
||||||
{
|
if (argExpr.Types.Count() == 1 && argExpr.Types.First().Equals(Typs.String))
|
||||||
return new Constant(Typs.Bool, "no");
|
{
|
||||||
}
|
mappingWithOptArg =
|
||||||
|
Funcs.Either(Funcs.Id, Funcs.Eq, argExpr);
|
||||||
if (e.ValueKind == JsonValueKind.String)
|
}
|
||||||
{
|
else
|
||||||
var s = e.GetString();
|
{
|
||||||
if (s.StartsWith("$"))
|
mappingWithOptArg = new Apply(_mconst, argExpr);
|
||||||
{
|
}
|
||||||
var bi = BuiltinByName(s);
|
|
||||||
|
exprs.Add(mappingWithOptArg);
|
||||||
if (bi != null)
|
}
|
||||||
{
|
|
||||||
return Either(Funcs.Dot, Funcs.Id, bi);
|
try
|
||||||
}
|
{
|
||||||
|
var simpleMapping = new Mapping(keys, exprs);
|
||||||
var definedFunc = context.GetFunction(s);
|
return new Apply(_mappingWrapper, simpleMapping);
|
||||||
return Either(Funcs.Dot, Funcs.Id, new FunctionCall(s, definedFunc.Types));
|
}
|
||||||
}
|
catch (Exception e)
|
||||||
|
{
|
||||||
if (s.StartsWith("#"))
|
throw new Exception("While constructing a mapping with members " + string.Join(", ", exprs) +
|
||||||
{
|
": " + e.Message, e);
|
||||||
// This is a parameter, the type of it is free
|
}
|
||||||
return new Parameter(s);
|
}
|
||||||
}
|
|
||||||
|
private static IExpression ParseExpression(this JsonElement e, Context context)
|
||||||
return new Constant(s);
|
{
|
||||||
}
|
if (e.ValueKind == JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
// Parse an actual function
|
||||||
throw new Exception("Could not parse " + e);
|
var funcCall = e.EnumerateObject().Where(v => v.Name.StartsWith("$")).ToList();
|
||||||
}
|
var allArgs = e.EnumerateObject().Where(v => !v.Name.StartsWith("$")).ToList();
|
||||||
|
|
||||||
private static IExpression ParseFunctionCall(Context context, IReadOnlyCollection<JsonProperty> funcCall,
|
if (funcCall.Count > 2)
|
||||||
IEnumerable<JsonProperty> allArgs)
|
{
|
||||||
{
|
throw new ArgumentException("Multiple calls defined in object " + e);
|
||||||
var funcName = funcCall.First().Name;
|
}
|
||||||
|
|
||||||
var func = BuiltinByName(funcName);
|
if (funcCall.Count == 1)
|
||||||
|
{
|
||||||
// The list where all the arguments are collected
|
return ParseFunctionCall(context, funcCall, allArgs);
|
||||||
var args = new List<IExpression>();
|
}
|
||||||
|
|
||||||
|
// Funccall has no elements: this is a mapping of strings or tags onto a value
|
||||||
// First argument of the function is the value of this property, e.g.
|
|
||||||
// { "$f": "xxx", "a2":"yyy", "a3":"zzz" }
|
return ParseMapping(allArgs, context);
|
||||||
var firstArgument = ParseExpression(funcCall.First().Value, context);
|
}
|
||||||
|
|
||||||
|
if (e.ValueKind == JsonValueKind.Array)
|
||||||
// Cheat for the very special case 'mustMatch'
|
{
|
||||||
if (func.Equals(Funcs.MustMatch))
|
var exprs = e.EnumerateArray().Select(json =>
|
||||||
{
|
Funcs.Either(Funcs.Id, Funcs.Const, json.ParseExpression(context)));
|
||||||
// It gets an extra argument injected
|
var list = new Constant(exprs);
|
||||||
var neededKeys = firstArgument.PossibleTags().Keys.ToList();
|
return Funcs.Either(Funcs.Id, Funcs.ListDot, list);
|
||||||
var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
|
}
|
||||||
args.Add(neededKeysArg);
|
|
||||||
args.Add(firstArgument);
|
if (e.ValueKind == JsonValueKind.Number)
|
||||||
return func.Apply(args);
|
{
|
||||||
}
|
if (e.TryGetDouble(out var d))
|
||||||
|
{
|
||||||
args.Add(firstArgument);
|
return new Constant(d);
|
||||||
|
}
|
||||||
var allExprs = allArgs
|
|
||||||
.Where(kv => !kv.NameEquals("#")) // Leave out comments
|
if (e.TryGetInt32(out var i))
|
||||||
.ToDictionary(kv => kv.Name, kv => kv.Value.ParseExpression(context));
|
{
|
||||||
|
return new Constant(i);
|
||||||
|
}
|
||||||
if (allExprs.Count > 1)
|
}
|
||||||
{
|
|
||||||
if (func.ArgNames == null || func.ArgNames.Count < 2)
|
if (e.ValueKind == JsonValueKind.True)
|
||||||
throw new ArgumentException("{funcName} does not specify argument names");
|
{
|
||||||
|
return new Constant(Typs.Bool, "yes");
|
||||||
foreach (var argName in func.ArgNames)
|
}
|
||||||
{
|
|
||||||
args.Add(allExprs[argName]);
|
if (e.ValueKind == JsonValueKind.False)
|
||||||
}
|
{
|
||||||
}
|
return new Constant(Typs.Bool, "no");
|
||||||
else if (allExprs.Count == 1)
|
}
|
||||||
{
|
|
||||||
args.Add(allExprs.Single().Value);
|
if (e.ValueKind == JsonValueKind.String)
|
||||||
}
|
{
|
||||||
|
var s = e.GetString();
|
||||||
return Either(Funcs.Id, Funcs.Dot, func).Apply(args);
|
if (s.StartsWith("$"))
|
||||||
}
|
{
|
||||||
|
var bi = Funcs.BuiltinByName(s);
|
||||||
private static IExpression GetTopLevelExpression(this JsonElement root, Context context)
|
|
||||||
{
|
if (bi != null)
|
||||||
IExpression mapping = null;
|
{
|
||||||
if (root.TryGetProperty("value", out var j))
|
return Funcs.Either(Funcs.Dot, Funcs.Id, bi);
|
||||||
{
|
}
|
||||||
// The expression is placed in the default 'value' location
|
|
||||||
mapping = j.ParseExpression(context);
|
var definedFunc = context.GetFunction(s);
|
||||||
}
|
return Funcs.Either(Funcs.Dot, Funcs.Id, new FunctionCall(s, definedFunc.Types));
|
||||||
|
}
|
||||||
|
|
||||||
// We search for the function call with '$'
|
if (s.StartsWith("#"))
|
||||||
foreach (var prop in root.EnumerateObject())
|
{
|
||||||
{
|
// This is a parameter, the type of it is free
|
||||||
if (!prop.Name.StartsWith("$")) continue;
|
return new Parameter(s);
|
||||||
|
}
|
||||||
|
|
||||||
var f = (IExpression) BuiltinByName(prop.Name);
|
return new Constant(s);
|
||||||
if (f == null)
|
}
|
||||||
{
|
|
||||||
throw new KeyNotFoundException("The builtin function " + f + " was not found");
|
|
||||||
}
|
throw new Exception("Could not parse " + e);
|
||||||
|
}
|
||||||
var fArg = prop.Value.ParseExpression(context);
|
|
||||||
|
private static IExpression ParseFunctionCall(Context context, IReadOnlyCollection<JsonProperty> funcCall,
|
||||||
if (fArg == null)
|
IEnumerable<JsonProperty> allArgs)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Could not type expression " + prop);
|
var funcName = funcCall.First().Name;
|
||||||
}
|
|
||||||
|
var func = Funcs.BuiltinByName(funcName);
|
||||||
|
|
||||||
if (mapping != null)
|
if (func == null)
|
||||||
{
|
{
|
||||||
// This is probably a firstOrderedVersion, a default, or some other function that should be applied
|
throw new ArgumentException($"The function with name {funcName} is not found");
|
||||||
return
|
}
|
||||||
new Apply(
|
|
||||||
Either(Funcs.Id, Funcs.Dot, new Apply(f, fArg)), mapping
|
// The list where all the arguments are collected
|
||||||
);
|
var args = new List<IExpression>();
|
||||||
}
|
|
||||||
|
|
||||||
// Cheat for the very special case 'mustMatch'
|
// First argument of the function is the value of this property, e.g.
|
||||||
if (f.Equals(Funcs.MustMatch))
|
// { "$f": "xxx", "a2":"yyy", "a3":"zzz" }
|
||||||
{
|
var firstArgument = ParseExpression(funcCall.First().Value, context);
|
||||||
// It gets an extra argument injected
|
|
||||||
var neededKeys = fArg.PossibleTags().Keys.ToList();
|
|
||||||
var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
|
// Cheat for the very special case 'mustMatch'
|
||||||
f = f.Apply(new[] {neededKeysArg});
|
if (func.Equals(Funcs.MustMatch))
|
||||||
}
|
{
|
||||||
|
// It gets an extra argument injected
|
||||||
var appliedDot = new Apply(new Apply(Funcs.Dot, f), fArg);
|
var neededKeys = firstArgument.PossibleTags().Keys.ToList();
|
||||||
var appliedDirect = new Apply(f, fArg);
|
var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
|
||||||
|
args.Add(neededKeysArg);
|
||||||
if (!appliedDot.Types.Any())
|
args.Add(firstArgument);
|
||||||
{
|
return func.Apply(args);
|
||||||
// Applied dot doesn't work out, so we return the other one
|
}
|
||||||
return appliedDirect;
|
|
||||||
}
|
args.Add(firstArgument);
|
||||||
|
|
||||||
if (!appliedDirect.Types.Any())
|
var allExprs = allArgs
|
||||||
{
|
.Where(kv => !kv.NameEquals("#")) // Leave out comments
|
||||||
return appliedDot;
|
.ToDictionary(kv => kv.Name, kv => kv.Value.ParseExpression(context));
|
||||||
}
|
|
||||||
|
|
||||||
var eithered = new Apply(new Apply(Funcs.EitherFunc, appliedDot), appliedDirect);
|
if (allExprs.Count > 1)
|
||||||
|
{
|
||||||
|
if (func.ArgNames == null || func.ArgNames.Count < 2)
|
||||||
// We apply the builtin function through a dot
|
throw new ArgumentException("{funcName} does not specify argument names");
|
||||||
return eithered;
|
|
||||||
}
|
foreach (var argName in func.ArgNames)
|
||||||
|
{
|
||||||
|
args.Add(allExprs[argName]);
|
||||||
throw new Exception(
|
}
|
||||||
"No top level reducer found. Did you forget the '$' in the reducing function? Did your forget 'value' to add the mapping?");
|
}
|
||||||
}
|
else if (allExprs.Count == 1)
|
||||||
|
{
|
||||||
private static IExpression ParseProfileProperty(JsonElement e, Context c, string property)
|
args.Add(allExprs.Single().Value);
|
||||||
{
|
}
|
||||||
try
|
|
||||||
{
|
return Funcs.Either(Funcs.Id, Funcs.Dot, func).Apply(args);
|
||||||
var prop = e.GetProperty(property);
|
}
|
||||||
return ParseExpression(prop, c)
|
|
||||||
.Specialize(new Curry(Typs.Tags, new Var("a")))
|
|
||||||
.Optimize();
|
private static IExpression GetTopLevelExpression(this JsonElement root, Context context)
|
||||||
}
|
{
|
||||||
catch (Exception exc)
|
IExpression mapping = null;
|
||||||
{
|
if (root.TryGetProperty("value", out var j))
|
||||||
throw new Exception("While parsing the property " + property, exc);
|
{
|
||||||
}
|
// The expression is placed in the default 'value' location
|
||||||
}
|
mapping = j.ParseExpression(context);
|
||||||
|
}
|
||||||
private static Dictionary<string, object> ParseParameters(this JsonElement e)
|
|
||||||
{
|
|
||||||
var ps = new Dictionary<string, object>();
|
// We search for the function call with '$'
|
||||||
foreach (var obj in e.EnumerateObject())
|
foreach (var prop in root.EnumerateObject())
|
||||||
{
|
{
|
||||||
var nm = obj.Name.TrimStart('#');
|
if (!prop.Name.StartsWith("$")) continue;
|
||||||
switch (obj.Value.ValueKind)
|
|
||||||
{
|
|
||||||
case JsonValueKind.String:
|
var f = (IExpression) Funcs.BuiltinByName(prop.Name);
|
||||||
ps[nm] = obj.Value.ToString();
|
if (f == null)
|
||||||
break;
|
{
|
||||||
case JsonValueKind.Number:
|
throw new KeyNotFoundException($"The builtin function {prop.Name} was not found");
|
||||||
ps[nm] = obj.Value.GetDouble();
|
}
|
||||||
break;
|
|
||||||
case JsonValueKind.True:
|
var fArg = prop.Value.ParseExpression(context);
|
||||||
ps[nm] = "yes";
|
|
||||||
break;
|
if (fArg == null)
|
||||||
case JsonValueKind.False:
|
{
|
||||||
ps[nm] = "no";
|
throw new ArgumentException("Could not type expression " + prop);
|
||||||
break;
|
}
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
if (mapping != null)
|
||||||
}
|
{
|
||||||
|
// This is probably a firstOrderedVersion, a default, or some other function that should be applied
|
||||||
return ps;
|
return
|
||||||
}
|
new Apply(
|
||||||
|
Funcs.Either(Funcs.Id, Funcs.Dot, new Apply(f, fArg)), mapping
|
||||||
public static ProfileMetaData ParseProfile(this JsonElement e, Context context, FileInfo filepath)
|
);
|
||||||
{
|
}
|
||||||
var name = e.Get("name");
|
|
||||||
var author = e.TryGet("author");
|
// Cheat for the very special case 'mustMatch'
|
||||||
if (filepath != null && !(name + ".json").ToLower().Equals(filepath.Name.ToLower()))
|
if (f.Equals(Funcs.MustMatch))
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Filename does not match the defined name: " +
|
// It gets an extra argument injected
|
||||||
$"filename is {filepath.Name}, declared name is {name}");
|
var neededKeys = fArg.PossibleTags().Keys.ToList();
|
||||||
}
|
var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
|
||||||
|
f = f.Apply(new[] {neededKeysArg});
|
||||||
var vehicleTypes = e.GetProperty("vehicletypes").EnumerateArray().Select(
|
}
|
||||||
el => el.GetString()).ToList();
|
|
||||||
var metadata = e.GetProperty("metadata").EnumerateArray().Select(
|
var appliedDot = new Apply(new Apply(Funcs.Dot, f), fArg);
|
||||||
el => el.GetString()).ToList();
|
var appliedDirect = new Apply(f, fArg);
|
||||||
|
|
||||||
|
if (!appliedDot.Types.Any())
|
||||||
var access = ParseProfileProperty(e, context, "access").Finalize();
|
{
|
||||||
var oneway = ParseProfileProperty(e, context, "oneway").Finalize();
|
// Applied dot doesn't work out, so we return the other one
|
||||||
var speed = ParseProfileProperty(e, context, "speed").Finalize();
|
return appliedDirect;
|
||||||
|
}
|
||||||
|
|
||||||
IExpression TagsApplied(IExpression x)
|
if (!appliedDirect.Types.Any())
|
||||||
{
|
{
|
||||||
return new Apply(x, new Constant(new Dictionary<string, string>()));
|
return appliedDot;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.AddFunction("speed",
|
var eithered = new Apply(new Apply(Funcs.EitherFunc, appliedDot), appliedDirect);
|
||||||
new AspectMetadata(TagsApplied(speed), "speed", "The speed of this profile", author, "", filepath.Name,
|
|
||||||
true));
|
|
||||||
context.AddFunction("access",
|
// We apply the builtin function through a dot
|
||||||
new AspectMetadata(TagsApplied(access), "access", "The access of this profile", author, "",
|
return eithered;
|
||||||
filepath.Name,
|
}
|
||||||
true));
|
|
||||||
context.AddFunction("oneway",
|
|
||||||
new AspectMetadata(TagsApplied(oneway), "oneway", "The oneway of this profile", author, "",
|
throw new Exception(
|
||||||
filepath.Name,
|
"No top level reducer found. Did you forget the '$' in the reducing function? Did your forget 'value' to add the mapping?");
|
||||||
true));
|
}
|
||||||
context.AddFunction("distance",
|
|
||||||
new AspectMetadata(new Constant(1), "distance", "The distance travelled of this profile", author, "",
|
private static AspectMetadata ParseAspect(this JsonElement e, string filepath, Context context)
|
||||||
filepath.Name,
|
{
|
||||||
true));
|
var expr = GetTopLevelExpression(e, context);
|
||||||
|
|
||||||
|
|
||||||
var weights = new Dictionary<string, IExpression>();
|
var targetTypes = new List<Type>();
|
||||||
var weightProperty = e.GetProperty("weight");
|
foreach (var t in expr.Types)
|
||||||
foreach (var prop in weightProperty.EnumerateObject())
|
{
|
||||||
{
|
var a = Var.Fresh(t);
|
||||||
var parameter = prop.Name.TrimStart('#');
|
var b = Var.Fresh(new Curry(a, t));
|
||||||
var factor = ParseExpression(prop.Value, context).Finalize();
|
|
||||||
weights[parameter] = factor;
|
if (t.Unify(new Curry(Typs.Tags, a)) != null &&
|
||||||
}
|
t.Unify(new Curry(Typs.Tags, new Curry(a, b))) == null
|
||||||
|
) // Second should not match
|
||||||
var profiles = new Dictionary<string, Dictionary<string, object>>();
|
{
|
||||||
|
// The target type is 'Tags -> a', where a is NOT a curry
|
||||||
foreach (var profile in e.GetProperty("profiles").EnumerateObject())
|
targetTypes.Add(t);
|
||||||
{
|
}
|
||||||
profiles[profile.Name] = ParseParameters(profile.Value);
|
}
|
||||||
}
|
|
||||||
|
if (targetTypes.Count == 0)
|
||||||
return new ProfileMetaData(
|
{
|
||||||
name,
|
throw new ArgumentException("The top level expression has types:\n" +
|
||||||
e.Get("description"),
|
string.Join("\n ", expr.Types) +
|
||||||
author,
|
"\nwhich can not be specialized into a form suiting `tags -> a`\n" + expr);
|
||||||
filepath?.DirectoryName ?? "unknown",
|
}
|
||||||
vehicleTypes,
|
|
||||||
e.GetProperty("defaults").ParseParameters(),
|
var exprSpec = expr.Specialize(targetTypes);
|
||||||
profiles,
|
if (expr == null)
|
||||||
access,
|
{
|
||||||
oneway,
|
throw new Exception("Could not specialize the expression " + expr + " to one of the target types " +
|
||||||
speed,
|
string.Join(", ", targetTypes));
|
||||||
weights,
|
}
|
||||||
metadata
|
|
||||||
);
|
expr = exprSpec.Finalize();
|
||||||
}
|
|
||||||
|
if (expr.Finalize() == null)
|
||||||
private static AspectMetadata ParseAspect(this JsonElement e, string filepath, Context context)
|
{
|
||||||
{
|
throw new NullReferenceException("The finalized form of expression `" + exprSpec + "` is null");
|
||||||
var expr = GetTopLevelExpression(e, context);
|
}
|
||||||
|
|
||||||
|
var name = e.Get("name");
|
||||||
var targetTypes = new List<Type>();
|
if (expr.Types.Count() > 1)
|
||||||
foreach (var t in expr.Types)
|
{
|
||||||
{
|
throw new ArgumentException("The aspect " + name + " is ambigous, it matches multiple types: " +
|
||||||
var a = Var.Fresh(t);
|
string.Join(", ", expr.Types));
|
||||||
var b = Var.Fresh(new Curry(a, t));
|
}
|
||||||
|
|
||||||
if (t.Unify(new Curry(Typs.Tags, a)) != null &&
|
if (filepath != null && !(name + ".json").ToLower().Equals(filepath.ToLower()))
|
||||||
t.Unify(new Curry(Typs.Tags, new Curry(a, b))) == null
|
{
|
||||||
) // Second should not match
|
throw new ArgumentException($"Filename does not match the defined name: " +
|
||||||
{
|
$"filename is {filepath}, declared name is {name}");
|
||||||
// The target type is 'Tags -> a', where a is NOT a curry
|
}
|
||||||
targetTypes.Add(t);
|
|
||||||
}
|
var keys = (IEnumerable<string>) expr.PossibleTags()?.Keys ?? new List<string>();
|
||||||
}
|
foreach (var key in keys)
|
||||||
|
{
|
||||||
if (targetTypes.Count == 0)
|
if (!key.Trim().Equals(key))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("The top level expression has types:\n" +
|
Console.WriteLine($"Warning: a key can be trimmed: '{key}'");
|
||||||
string.Join("\n ", expr.Types) +
|
}
|
||||||
"\nwhich can not be specialized into a form suiting `tags -> a`\n" + expr);
|
}
|
||||||
}
|
|
||||||
|
return new AspectMetadata(
|
||||||
var exprSpec = expr.Specialize(targetTypes);
|
expr,
|
||||||
if (expr == null)
|
name,
|
||||||
{
|
e.Get("description"),
|
||||||
throw new Exception("Could not specialize the expression " + expr + " to one of the target types " +
|
e.TryGet("author"),
|
||||||
string.Join(", ", targetTypes));
|
e.TryGet("unit"),
|
||||||
}
|
filepath ?? "unknown"
|
||||||
|
);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
96
AspectedRouting/IO/jsonParser/JsonParser.cs
Normal file
96
AspectedRouting/IO/jsonParser/JsonParser.cs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using AspectedRouting.Language;
|
||||||
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Functions;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.IO.jsonParser
|
||||||
|
{
|
||||||
|
public static partial class JsonParser
|
||||||
|
{
|
||||||
|
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, IExpression> ParseParameters(this JsonElement e)
|
||||||
|
{
|
||||||
|
var ps = new Dictionary<string, IExpression>();
|
||||||
|
foreach (var obj in e.EnumerateObject())
|
||||||
|
{
|
||||||
|
var nm = obj.Name.TrimStart('#');
|
||||||
|
switch (obj.Value.ValueKind)
|
||||||
|
{
|
||||||
|
case JsonValueKind.String:
|
||||||
|
var v = obj.Value.ToString();
|
||||||
|
if (v.Equals("yes") || v.Equals("no"))
|
||||||
|
{
|
||||||
|
ps[nm] = new Constant(Typs.Bool, v);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ps[nm] = new Constant(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Number:
|
||||||
|
ps[nm] = new Constant(obj.Value.GetDouble());
|
||||||
|
break;
|
||||||
|
case JsonValueKind.True:
|
||||||
|
ps[nm] = new Constant(Typs.Bool, "yes");
|
||||||
|
break;
|
||||||
|
case JsonValueKind.False:
|
||||||
|
ps[nm] = new Constant(Typs.Bool, "yes");
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Array:
|
||||||
|
var list = obj.Value.EnumerateArray().Select(e => e.ToString()).ToList();
|
||||||
|
ps[nm] = new Constant(new ListType(Typs.String),list);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException(
|
||||||
|
"Parameters are not allowed to be complex expressions, they should be simple values. " +
|
||||||
|
"Simplify the value for parameter " + obj.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ps;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
function const(a, b)
|
||||||
|
return a
|
||||||
|
end
|
9
AspectedRouting/IO/lua/containedIn.lua
Normal file
9
AspectedRouting/IO/lua/containedIn.lua
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
function containedIn(list, a)
|
||||||
|
for _, value in ipairs(list) do
|
||||||
|
if (value == a) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false;
|
||||||
|
end
|
|
@ -1,4 +1,8 @@
|
||||||
function double_compare(a, b)
|
function double_compare(a, b)
|
||||||
|
if (b == nil) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
if (type(a) ~= "number") then
|
if (type(a) ~= "number") then
|
||||||
a = parse(a)
|
a = parse(a)
|
||||||
end
|
end
|
||||||
|
@ -6,5 +10,9 @@ function double_compare(a, b)
|
||||||
if(type(b) ~= "number") then
|
if(type(b) ~= "number") then
|
||||||
b = parse(b)
|
b = parse(b)
|
||||||
end
|
end
|
||||||
return math.abs(a - b) > 0.001
|
if (a == b) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return math.abs(a - b) < 0.0001
|
||||||
end
|
end
|
|
@ -1,3 +1,10 @@
|
||||||
function member_of()
|
function member_of(calledIn, parameters, tags, result)
|
||||||
???
|
local k = "_relation:" .. calledIn
|
||||||
|
-- This tag is conventiently setup by all the preprocessors, which take the parameters into account
|
||||||
|
local doesMatch = tags[k]
|
||||||
|
if (doesMatch == "yes") then
|
||||||
|
result.attributes_to_keep[k] = "yes"
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
end
|
end
|
|
@ -1,5 +1,4 @@
|
||||||
function must_match(tags, result, needed_keys, table)
|
function must_match(tags, result, needed_keys, table)
|
||||||
local result_list = {}
|
|
||||||
for _, key in ipairs(needed_keys) do
|
for _, key in ipairs(needed_keys) do
|
||||||
local v = tags[key]
|
local v = tags[key]
|
||||||
if (v == nil) then
|
if (v == nil) then
|
||||||
|
@ -9,17 +8,30 @@ function must_match(tags, result, needed_keys, table)
|
||||||
local mapping = table[key]
|
local mapping = table[key]
|
||||||
if (type(mapping) == "table") then
|
if (type(mapping) == "table") then
|
||||||
local resultValue = mapping[v]
|
local resultValue = mapping[v]
|
||||||
if (v == nil or v == false) then
|
if (resultValue == nil or
|
||||||
|
resultValue == false or
|
||||||
|
resultValue == "no" or
|
||||||
|
resultValue == "false") then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
if (v == "no" or v == "false") then
|
elseif (type(mapping) == "string") then
|
||||||
|
local bool = mapping
|
||||||
|
if (bool == "yes" or bool == "1") then
|
||||||
|
return true
|
||||||
|
elseif (bool == "no" or bool == "0") then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
error("MustMatch got a string value it can't handle: " .. bool)
|
||||||
result.attributes_to_keep[key] = v
|
|
||||||
else
|
else
|
||||||
error("The mapping is not a table. This is not supported")
|
error("The mapping is not a table. This is not supported. We got " .. mapping)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Now that we know for sure that every key matches, we add them all
|
||||||
|
for _, key in ipairs(needed_keys) do
|
||||||
|
local v = tags[key]
|
||||||
|
result.attributes_to_keep[key] = v
|
||||||
|
end
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
|
@ -1,4 +1,8 @@
|
||||||
function notEq(a, b)
|
function notEq(a, b)
|
||||||
|
if (b == nil) then
|
||||||
|
b = "yes"
|
||||||
|
end
|
||||||
|
|
||||||
if (a ~= b) then
|
if (a ~= b) then
|
||||||
return "yes"
|
return "yes"
|
||||||
else
|
else
|
||||||
|
|
20
AspectedRouting/IO/lua/remove_relation_prefix.lua
Normal file
20
AspectedRouting/IO/lua/remove_relation_prefix.lua
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
function string.start(strt, s)
|
||||||
|
return string.sub(s, 1, string.len(strt)) == strt
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- every key starting with "_relation:<name>:XXX" is rewritten to "_relation:XXX"
|
||||||
|
function remove_relation_prefix(tags, name)
|
||||||
|
|
||||||
|
local new_tags = {}
|
||||||
|
for k, v in pairs(tags) do
|
||||||
|
local prefix = "_relation:" .. name;
|
||||||
|
if (string.start(prefix, k)) then
|
||||||
|
local new_key = "_relation:" .. string.sub(k, string.len(prefix))
|
||||||
|
new_tags[new_key] = v
|
||||||
|
else
|
||||||
|
new_tags[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return new_tags
|
||||||
|
end
|
|
@ -3,36 +3,57 @@ failed_profile_tests = false
|
||||||
expected should be a table containing 'access', 'speed' and 'weight'
|
expected should be a table containing 'access', 'speed' and 'weight'
|
||||||
]]
|
]]
|
||||||
function unit_test_profile(profile_function, profile_name, index, expected, tags)
|
function unit_test_profile(profile_function, profile_name, index, expected, tags)
|
||||||
result = {attributes_to_keep = {}}
|
local result = { attributes_to_keep = {} }
|
||||||
|
local profile_failed = false
|
||||||
profile_function(tags, result)
|
profile_function(tags, result)
|
||||||
|
|
||||||
if (result.access ~= expected.access) then
|
local accessCorrect = (result.access == 0 and expected.access == "no") or result.access == 1
|
||||||
|
if (not accessCorrect) then
|
||||||
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".access: expected " .. expected.access .. " but got " .. result.access)
|
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".access: expected " .. expected.access .. " but got " .. result.access)
|
||||||
|
profile_failed = true
|
||||||
failed_profile_tests = true
|
failed_profile_tests = true
|
||||||
end
|
end
|
||||||
|
|
||||||
if (result.access == 0) then
|
if (expected.access == "no") then
|
||||||
-- we cannot access this road, the other results are irrelevant
|
-- we cannot access this road, the other results are irrelevant
|
||||||
|
if (profile_failed) then
|
||||||
|
print("The used tags for test " .. tostring(index) .. " are:")
|
||||||
|
debug_table(tags)
|
||||||
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if (double_compare(result.speed, expected.speed)) then
|
if (not double_compare(result.speed, expected.speed)) then
|
||||||
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".speed: expected " .. expected.speed .. " but got " .. result.speed)
|
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".speed: expected " .. expected.speed .. " but got " .. result.speed)
|
||||||
failed_profile_tests = true
|
failed_profile_tests = true
|
||||||
|
profile_failed = true
|
||||||
end
|
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)
|
local actualOneway = result.oneway;
|
||||||
failed_profile_tests = true
|
if (result.oneway == 0) then
|
||||||
|
actualOneway = "both"
|
||||||
|
elseif (result.oneway == 1) then
|
||||||
|
actualOneway = "with"
|
||||||
|
elseif (result.oneway == 2) then
|
||||||
|
actualOneway = "against"
|
||||||
end
|
end
|
||||||
|
|
||||||
if (double_compare(result.oneway, expected.oneway)) then
|
if (expected.oneway ~= actualOneway) then
|
||||||
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but got " .. result.oneway)
|
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but got " .. actualOneway)
|
||||||
failed_profile_tests = true
|
failed_profile_tests = true
|
||||||
|
profile_failed = true
|
||||||
end
|
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))
|
if (not double_compare(result.factor, expected.weight)) then
|
||||||
|
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".factor: expected " .. expected.weight .. " but got " .. result.factor)
|
||||||
failed_profile_tests = true
|
failed_profile_tests = true
|
||||||
|
profile_failed = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if (profile_failed == true) then
|
||||||
|
print("The used tags for test " .. tostring(index) .. " are:")
|
||||||
|
debug_table(tags)
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -17,6 +17,7 @@
|
||||||
- parse
|
- parse
|
||||||
- to_string
|
- to_string
|
||||||
- concat
|
- concat
|
||||||
|
- containedIn
|
||||||
- min
|
- min
|
||||||
- max
|
- max
|
||||||
- sum
|
- sum
|
||||||
|
@ -40,7 +41,7 @@
|
||||||
#### eq
|
#### eq
|
||||||
|
|
||||||
Argument name | | |
|
Argument name | | |
|
||||||
--------------| - | - | -
|
-------------- | - | - | -
|
||||||
**a** | $a | $a |
|
**a** | $a | $a |
|
||||||
**b** | $a | $a |
|
**b** | $a | $a |
|
||||||
_return type_ | bool | string |
|
_return type_ | bool | string |
|
||||||
|
@ -66,14 +67,14 @@ end
|
||||||
|
|
||||||
#### notEq
|
#### notEq
|
||||||
|
|
||||||
Argument name | | |
|
Argument name | | | |
|
||||||
--------------| - | - | -
|
-------------- | - | - | - | -
|
||||||
**a** | $a | $a |
|
**a** | $a | $a | bool |
|
||||||
**b** | $a | $a |
|
**b** | $a | $a | _none_ |
|
||||||
_return type_ | bool | string |
|
_return type_ | bool | string | bool |
|
||||||
|
|
||||||
|
|
||||||
Returns 'yes' if the two passed in values are _not_ the same
|
OVerloaded function, either boolean not or returns 'yes' if the two passed in values are _not_ the same;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,6 +82,10 @@ Lua implementation:
|
||||||
|
|
||||||
````lua
|
````lua
|
||||||
function notEq(a, b)
|
function notEq(a, b)
|
||||||
|
if (b == nil) then
|
||||||
|
b = "yes"
|
||||||
|
end
|
||||||
|
|
||||||
if (a ~= b) then
|
if (a ~= b) then
|
||||||
return "yes"
|
return "yes"
|
||||||
else
|
else
|
||||||
|
@ -92,14 +97,14 @@ end
|
||||||
|
|
||||||
#### not
|
#### not
|
||||||
|
|
||||||
Argument name | | |
|
Argument name | | | |
|
||||||
--------------| - | - | -
|
-------------- | - | - | - | -
|
||||||
**a** | $a | $a |
|
**a** | $a | $a | bool |
|
||||||
**b** | $a | $a |
|
**b** | $a | $a | _none_ |
|
||||||
_return type_ | bool | string |
|
_return type_ | bool | string | bool |
|
||||||
|
|
||||||
|
|
||||||
Returns 'yes' if the two passed in values are _not_ the same
|
OVerloaded function, either boolean not or returns 'yes' if the two passed in values are _not_ the same;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -107,6 +112,10 @@ Lua implementation:
|
||||||
|
|
||||||
````lua
|
````lua
|
||||||
function notEq(a, b)
|
function notEq(a, b)
|
||||||
|
if (b == nil) then
|
||||||
|
b = "yes"
|
||||||
|
end
|
||||||
|
|
||||||
if (a ~= b) then
|
if (a ~= b) then
|
||||||
return "yes"
|
return "yes"
|
||||||
else
|
else
|
||||||
|
@ -119,7 +128,7 @@ end
|
||||||
#### inv
|
#### inv
|
||||||
|
|
||||||
Argument name | | |
|
Argument name | | |
|
||||||
--------------| - | - | -
|
-------------- | - | - | -
|
||||||
**d** | pdouble | double |
|
**d** | pdouble | double |
|
||||||
_return type_ | pdouble | double |
|
_return type_ | pdouble | double |
|
||||||
|
|
||||||
|
@ -140,7 +149,7 @@ end
|
||||||
#### default
|
#### default
|
||||||
|
|
||||||
Argument name | |
|
Argument name | |
|
||||||
--------------| - | -
|
-------------- | - | -
|
||||||
**defaultValue** | $a |
|
**defaultValue** | $a |
|
||||||
**f** | $b -> $a |
|
**f** | $b -> $a |
|
||||||
_return type_ | $b -> $a |
|
_return type_ | $b -> $a |
|
||||||
|
@ -164,10 +173,10 @@ end
|
||||||
|
|
||||||
#### parse
|
#### parse
|
||||||
|
|
||||||
Argument name | |
|
Argument name | | |
|
||||||
--------------| - | -
|
-------------- | - | - | -
|
||||||
**s** | string |
|
**s** | string | string |
|
||||||
_return type_ | double |
|
_return type_ | double | pdouble |
|
||||||
|
|
||||||
|
|
||||||
Parses a string into a numerical value
|
Parses a string into a numerical value
|
||||||
|
@ -210,7 +219,7 @@ end
|
||||||
#### to_string
|
#### to_string
|
||||||
|
|
||||||
Argument name | |
|
Argument name | |
|
||||||
--------------| - | -
|
-------------- | - | -
|
||||||
**obj** | $a |
|
**obj** | $a |
|
||||||
_return type_ | string |
|
_return type_ | string |
|
||||||
|
|
||||||
|
@ -231,7 +240,7 @@ end
|
||||||
#### concat
|
#### concat
|
||||||
|
|
||||||
Argument name | |
|
Argument name | |
|
||||||
--------------| - | -
|
-------------- | - | -
|
||||||
**a** | string |
|
**a** | string |
|
||||||
**b** | string |
|
**b** | string |
|
||||||
_return type_ | string |
|
_return type_ | string |
|
||||||
|
@ -250,10 +259,38 @@ end
|
||||||
````
|
````
|
||||||
|
|
||||||
|
|
||||||
|
#### containedIn
|
||||||
|
|
||||||
|
Argument name | |
|
||||||
|
-------------- | - | -
|
||||||
|
**list** | list ($a) |
|
||||||
|
**a** | $a |
|
||||||
|
_return type_ | bool |
|
||||||
|
|
||||||
|
|
||||||
|
Given a list of values, checks if the argument is contained in the list.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Lua implementation:
|
||||||
|
|
||||||
|
````lua
|
||||||
|
function containedIn(list, a)
|
||||||
|
for _, value in ipairs(list) do
|
||||||
|
if (value == a) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
#### min
|
#### min
|
||||||
|
|
||||||
Argument name | | | | | |
|
Argument name | | | | | |
|
||||||
--------------| - | - | - | - | - | -
|
-------------- | - | - | - | - | - | -
|
||||||
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
||||||
_return type_ | nat | int | pdouble | double | bool |
|
_return type_ | nat | int | pdouble | double | bool |
|
||||||
|
|
||||||
|
@ -283,7 +320,7 @@ end
|
||||||
#### max
|
#### max
|
||||||
|
|
||||||
Argument name | | | | | |
|
Argument name | | | | | |
|
||||||
--------------| - | - | - | - | - | -
|
-------------- | - | - | - | - | - | -
|
||||||
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
||||||
_return type_ | nat | int | pdouble | double | bool |
|
_return type_ | nat | int | pdouble | double | bool |
|
||||||
|
|
||||||
|
@ -313,7 +350,7 @@ end
|
||||||
#### sum
|
#### sum
|
||||||
|
|
||||||
Argument name | | | | | |
|
Argument name | | | | | |
|
||||||
--------------| - | - | - | - | - | -
|
-------------- | - | - | - | - | - | -
|
||||||
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
||||||
_return type_ | nat | int | pdouble | double | int |
|
_return type_ | nat | int | pdouble | double | int |
|
||||||
|
|
||||||
|
@ -341,7 +378,7 @@ end
|
||||||
#### multiply
|
#### multiply
|
||||||
|
|
||||||
Argument name | | | | | |
|
Argument name | | | | | |
|
||||||
--------------| - | - | - | - | - | -
|
-------------- | - | - | - | - | - | -
|
||||||
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
||||||
_return type_ | nat | int | pdouble | double | bool |
|
_return type_ | nat | int | pdouble | double | bool |
|
||||||
|
|
||||||
|
@ -366,7 +403,7 @@ end
|
||||||
#### firstMatchOf
|
#### firstMatchOf
|
||||||
|
|
||||||
Argument name | |
|
Argument name | |
|
||||||
--------------| - | -
|
-------------- | - | -
|
||||||
**s** | list (string) |
|
**s** | list (string) |
|
||||||
_return type_ | (tags -> list ($a)) -> tags -> $a |
|
_return type_ | (tags -> list ($a)) -> tags -> $a |
|
||||||
|
|
||||||
|
@ -404,7 +441,7 @@ end
|
||||||
#### mustMatch
|
#### mustMatch
|
||||||
|
|
||||||
Argument name | |
|
Argument name | |
|
||||||
--------------| - | -
|
-------------- | - | -
|
||||||
**neededKeys (filled in by parser)** | list (string) |
|
**neededKeys (filled in by parser)** | list (string) |
|
||||||
**f** | tags -> list (bool) |
|
**f** | tags -> list (bool) |
|
||||||
_return type_ | tags -> bool |
|
_return type_ | tags -> bool |
|
||||||
|
@ -420,7 +457,6 @@ Lua implementation:
|
||||||
|
|
||||||
````lua
|
````lua
|
||||||
function must_match(tags, result, needed_keys, table)
|
function must_match(tags, result, needed_keys, table)
|
||||||
local result_list = {}
|
|
||||||
for _, key in ipairs(needed_keys) do
|
for _, key in ipairs(needed_keys) do
|
||||||
local v = tags[key]
|
local v = tags[key]
|
||||||
if (v == nil) then
|
if (v == nil) then
|
||||||
|
@ -430,18 +466,31 @@ function must_match(tags, result, needed_keys, table)
|
||||||
local mapping = table[key]
|
local mapping = table[key]
|
||||||
if (type(mapping) == "table") then
|
if (type(mapping) == "table") then
|
||||||
local resultValue = mapping[v]
|
local resultValue = mapping[v]
|
||||||
if (v == nil or v == false) then
|
if (resultValue == nil or
|
||||||
|
resultValue == false or
|
||||||
|
resultValue == "no" or
|
||||||
|
resultValue == "false") then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
if (v == "no" or v == "false") then
|
elseif (type(mapping) == "string") then
|
||||||
|
local bool = mapping
|
||||||
|
if (bool == "yes" or bool == "1") then
|
||||||
|
return true
|
||||||
|
elseif (bool == "no" or bool == "0") then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
error("MustMatch got a string value it can't handle: " .. bool)
|
||||||
result.attributes_to_keep[key] = v
|
|
||||||
else
|
else
|
||||||
error("The mapping is not a table. This is not supported")
|
error("The mapping is not a table. This is not supported. We got " .. mapping)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Now that we know for sure that every key matches, we add them all
|
||||||
|
for _, key in ipairs(needed_keys) do
|
||||||
|
local v = tags[key]
|
||||||
|
result.attributes_to_keep[key] = v
|
||||||
|
end
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
end
|
end
|
||||||
````
|
````
|
||||||
|
@ -449,22 +498,41 @@ end
|
||||||
|
|
||||||
#### memberOf
|
#### memberOf
|
||||||
|
|
||||||
- (tags -> $a) -> tags -> list ($a)
|
Argument name | |
|
||||||
|
-------------- | - | -
|
||||||
|
**f** | tags -> bool |
|
||||||
|
**tags** | tags |
|
||||||
|
_return type_ | bool |
|
||||||
|
|
||||||
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'
|
This function returns true, if the way is member of a relation matching the specified function.
|
||||||
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'
|
In order to use this for itinero 1.0, the membership _must_ be the top level expression.
|
||||||
|
|
||||||
|
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 test.csv, one can simply use `_relation:<aspect_name>=yes` to mimic relations in your tests
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Lua implementation:
|
Lua implementation:
|
||||||
|
|
||||||
````lua
|
````lua
|
||||||
function member_of()
|
function member_of(calledIn, parameters, tags, result)
|
||||||
???
|
local k = "_relation:" .. calledIn
|
||||||
|
-- This tag is conventiently setup by all the preprocessors, which take the parameters into account
|
||||||
|
local doesMatch = tags[k]
|
||||||
|
if (doesMatch == "yes") then
|
||||||
|
result.attributes_to_keep[k] = "yes"
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
````
|
````
|
||||||
|
|
||||||
|
@ -472,7 +540,7 @@ end
|
||||||
#### if_then_else
|
#### if_then_else
|
||||||
|
|
||||||
Argument name | | |
|
Argument name | | |
|
||||||
--------------| - | - | -
|
-------------- | - | - | -
|
||||||
**condition** | bool | bool |
|
**condition** | bool | bool |
|
||||||
**then** | $a | $a |
|
**then** | $a | $a |
|
||||||
**else** | $a | _none_ |
|
**else** | $a | _none_ |
|
||||||
|
@ -499,7 +567,7 @@ end
|
||||||
#### if
|
#### if
|
||||||
|
|
||||||
Argument name | | |
|
Argument name | | |
|
||||||
--------------| - | - | -
|
-------------- | - | - | -
|
||||||
**condition** | bool | bool |
|
**condition** | bool | bool |
|
||||||
**then** | $a | $a |
|
**then** | $a | $a |
|
||||||
**else** | $a | _none_ |
|
**else** | $a | _none_ |
|
||||||
|
@ -526,7 +594,7 @@ end
|
||||||
#### id
|
#### id
|
||||||
|
|
||||||
Argument name | |
|
Argument name | |
|
||||||
--------------| - | -
|
-------------- | - | -
|
||||||
**a** | $a |
|
**a** | $a |
|
||||||
_return type_ | $a |
|
_return type_ | $a |
|
||||||
|
|
||||||
|
@ -547,7 +615,7 @@ end
|
||||||
#### const
|
#### const
|
||||||
|
|
||||||
Argument name | |
|
Argument name | |
|
||||||
--------------| - | -
|
-------------- | - | -
|
||||||
**a** | $a |
|
**a** | $a |
|
||||||
**b** | $b |
|
**b** | $b |
|
||||||
_return type_ | $a |
|
_return type_ | $a |
|
||||||
|
@ -560,14 +628,16 @@ Small utility function, which takes two arguments `a` and `b` and returns `a`. U
|
||||||
Lua implementation:
|
Lua implementation:
|
||||||
|
|
||||||
````lua
|
````lua
|
||||||
|
function const(a, b)
|
||||||
|
return a
|
||||||
|
end
|
||||||
````
|
````
|
||||||
|
|
||||||
|
|
||||||
#### constRight
|
#### constRight
|
||||||
|
|
||||||
Argument name | |
|
Argument name | |
|
||||||
--------------| - | -
|
-------------- | - | -
|
||||||
**a** | $a |
|
**a** | $a |
|
||||||
**b** | $b |
|
**b** | $b |
|
||||||
_return type_ | $b |
|
_return type_ | $b |
|
||||||
|
@ -587,11 +657,11 @@ Lua implementation:
|
||||||
#### dot
|
#### dot
|
||||||
|
|
||||||
Argument name | |
|
Argument name | |
|
||||||
--------------| - | -
|
-------------- | - | -
|
||||||
**f** | $gType -> $arg |
|
**f** | $b -> $c |
|
||||||
**g** | $fType -> $gType |
|
**g** | $a -> $b |
|
||||||
**a** | $fType |
|
**a** | $a |
|
||||||
_return type_ | $arg |
|
_return type_ | $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
|
||||||
|
@ -608,7 +678,7 @@ Lua implementation:
|
||||||
#### listDot
|
#### listDot
|
||||||
|
|
||||||
Argument name | |
|
Argument name | |
|
||||||
--------------| - | -
|
-------------- | - | -
|
||||||
**list** | list ($a -> $b) |
|
**list** | list ($a -> $b) |
|
||||||
**a** | $a |
|
**a** | $a |
|
||||||
_return type_ | list ($b) |
|
_return type_ | list ($b) |
|
||||||
|
@ -628,10 +698,19 @@ Lua implementation:
|
||||||
|
|
||||||
#### eitherFunc
|
#### eitherFunc
|
||||||
|
|
||||||
- ($a -> $b) -> ($c -> $d) -> $a -> $b
|
Argument name | | |
|
||||||
- ($a -> $b) -> ($c -> $d) -> $c -> $d
|
-------------- | - | - | -
|
||||||
|
**f** | $a -> $b | $a -> $b |
|
||||||
|
**g** | $c -> $d | $c -> $d |
|
||||||
|
**a** | $a | $c |
|
||||||
|
_return type_ | $b | $d |
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Disclaimer: _you should never ever need this in your profiles_
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -644,9 +723,14 @@ Lua implementation:
|
||||||
|
|
||||||
#### stringToTags
|
#### stringToTags
|
||||||
|
|
||||||
- (string -> string -> $a) -> tags -> list ($a)
|
Argument name | |
|
||||||
|
-------------- | - | -
|
||||||
|
**f** | string -> string -> $a |
|
||||||
|
**tags** | tags |
|
||||||
|
_return type_ | list ($a) |
|
||||||
|
|
||||||
|
|
||||||
|
stringToTags converts a function `string -> string -> a` into a function `tags -> [a]`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Functions;
|
using AspectedRouting.Language.Expression;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Functions;
|
||||||
using static AspectedRouting.Deconstruct;
|
using AspectedRouting.Language.Typ;
|
||||||
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting
|
namespace AspectedRouting.Language
|
||||||
{
|
{
|
||||||
public static class Analysis
|
public static class Analysis
|
||||||
{
|
{
|
||||||
|
@ -26,6 +27,7 @@ namespace AspectedRouting
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
var keys = e.PossibleTags().Keys.ToList();
|
var keys = e.PossibleTags().Keys.ToList();
|
||||||
|
|
||||||
|
|
||||||
var results = possibleTags.OnAllCombinations(
|
var results = possibleTags.OnAllCombinations(
|
||||||
tags =>
|
tags =>
|
||||||
{
|
{
|
||||||
|
@ -124,10 +126,10 @@ namespace AspectedRouting
|
||||||
} while (SelectNext());
|
} while (SelectNext());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dictionary<string, (IEnumerable<Typ.Type> Types, string inFunction)> UsedParameters(
|
public static Dictionary<string, (List<Type> Types, string inFunction)> UsedParameters(
|
||||||
this ProfileMetaData profile, Context context)
|
this ProfileMetaData profile, Context context)
|
||||||
{
|
{
|
||||||
var parameters = new Dictionary<string, (IEnumerable<Typ.Type> Types, string inFunction)>();
|
var parameters = new Dictionary<string, (List<Type> Types, string inFunction)>();
|
||||||
|
|
||||||
void AddParams(IExpression e, string inFunction)
|
void AddParams(IExpression e, string inFunction)
|
||||||
{
|
{
|
||||||
|
@ -149,7 +151,7 @@ namespace AspectedRouting
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
parameters[param.ParamName] = (param.Types, inFunction);
|
parameters[param.ParamName] = (param.Types.ToList(), inFunction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,6 +161,12 @@ namespace AspectedRouting
|
||||||
AddParams(profile.Oneway, profile.Name + ".oneway");
|
AddParams(profile.Oneway, profile.Name + ".oneway");
|
||||||
AddParams(profile.Speed, profile.Name + ".speed");
|
AddParams(profile.Speed, profile.Name + ".speed");
|
||||||
|
|
||||||
|
foreach (var (key, expr) in profile.Priority)
|
||||||
|
{
|
||||||
|
AddParams(new Parameter(key), profile.Name + ".priority.lefthand");
|
||||||
|
AddParams(expr, profile.Name + ".priority");
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var (name, expr) in context.DefinedFunctions)
|
foreach (var (name, expr) in context.DefinedFunctions)
|
||||||
{
|
{
|
||||||
AddParams(expr, name);
|
AddParams(expr, name);
|
||||||
|
@ -194,11 +202,11 @@ namespace AspectedRouting
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SanityCheckProfile(this ProfileMetaData pmd)
|
public static void SanityCheckProfile(this ProfileMetaData pmd, Context context)
|
||||||
{
|
{
|
||||||
var defaultParameters = pmd.DefaultParameters.Keys;
|
var defaultParameters = pmd.DefaultParameters.Keys;
|
||||||
|
|
||||||
var usedParameters = pmd.UsedParameters(new Context()).Keys.Select(key => key.TrimStart('#'));
|
var usedParameters = pmd.UsedParameters(context).Keys.Select(key => key.TrimStart('#'));
|
||||||
|
|
||||||
var diff = usedParameters.ToHashSet().Except(defaultParameters).ToList();
|
var diff = usedParameters.ToHashSet().Except(defaultParameters).ToList();
|
||||||
if (diff.Any())
|
if (diff.Any())
|
||||||
|
@ -206,28 +214,48 @@ namespace AspectedRouting
|
||||||
throw new ArgumentException("No default value set for parameter " + string.Join(", ", diff));
|
throw new ArgumentException("No default value set for parameter " + string.Join(", ", diff));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (profileName, profileParams) in pmd.Profiles)
|
var unused = defaultParameters.Except(usedParameters);
|
||||||
|
if (unused.Any())
|
||||||
|
{
|
||||||
|
throw new ArgumentException("A default value is set for parameter, but it is unused: " +
|
||||||
|
string.Join(", ", unused));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (behaviourName, behaviourParams) in pmd.Behaviours)
|
||||||
{
|
{
|
||||||
var sum = 0.0;
|
var sum = 0.0;
|
||||||
foreach (var (paramName, _) in pmd.Weights)
|
var explanation = "";
|
||||||
|
foreach (var (paramName, _) in pmd.Priority)
|
||||||
{
|
{
|
||||||
if (!profileParams.TryGetValue(paramName, out var weight))
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
explanation += $"\n - {paramName} = default (not set)";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(weight is double d))
|
var weightObj = weight.Evaluate(context);
|
||||||
|
|
||||||
|
if (!(weightObj is double d))
|
||||||
{
|
{
|
||||||
continue;
|
throw new ArgumentException($"The parameter {paramName} is not a numeric value");
|
||||||
}
|
}
|
||||||
|
|
||||||
sum += Math.Abs(d);
|
sum += Math.Abs(d);
|
||||||
|
explanation += $"\n - {paramName} = {d}";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Math.Abs(sum) < 0.0001)
|
if (Math.Abs(sum) < 0.0001)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Profile " + profileName +
|
throw new ArgumentException("Profile " + behaviourName +
|
||||||
": the summed parameters to calculate the weight are zero or very low");
|
": the summed parameters to calculate the weight are zero or very low:" +
|
||||||
|
explanation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,9 +266,9 @@ namespace AspectedRouting
|
||||||
{
|
{
|
||||||
var order = new List<IExpression>();
|
var order = new List<IExpression>();
|
||||||
var mapping = new List<IExpression>();
|
var mapping = new List<IExpression>();
|
||||||
if (UnApply(
|
if (Deconstruct.UnApply(
|
||||||
UnApply(IsFunc(Funcs.FirstOf), Assign(order)),
|
Deconstruct.UnApply(Deconstruct.IsFunc(Funcs.FirstOf), Deconstruct.Assign(order)),
|
||||||
Assign(mapping)
|
Deconstruct.Assign(mapping)
|
||||||
).Invoke(expr))
|
).Invoke(expr))
|
||||||
{
|
{
|
||||||
var expectedKeys = ((IEnumerable<object>) order.First().Evaluate(null)).Select(o =>
|
var expectedKeys = ((IEnumerable<object>) order.First().Evaluate(null)).Select(o =>
|
||||||
|
@ -339,5 +367,61 @@ namespace AspectedRouting
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Dictionary<string, IExpression> MembershipMappingsFor(ProfileMetaData profile, Context context)
|
||||||
|
{
|
||||||
|
var calledFunctions = profile.Priority.Values.ToHashSet();
|
||||||
|
calledFunctions.Add(profile.Speed);
|
||||||
|
calledFunctions.Add(profile.Access);
|
||||||
|
calledFunctions.Add(profile.Oneway);
|
||||||
|
|
||||||
|
|
||||||
|
var calledFunctionQueue = new Queue<string>();
|
||||||
|
var alreadyAnalysedFunctions = new HashSet<string>();
|
||||||
|
var memberships = new Dictionary<string, IExpression>();
|
||||||
|
|
||||||
|
void HandleExpression(IExpression e, string calledIn)
|
||||||
|
{
|
||||||
|
e.Visit(f =>
|
||||||
|
{
|
||||||
|
var mapping = new List<IExpression>();
|
||||||
|
if (Deconstruct.UnApply(Deconstruct.IsFunc(Funcs.MemberOf),
|
||||||
|
Deconstruct.Assign(mapping)
|
||||||
|
).Invoke(f))
|
||||||
|
{
|
||||||
|
memberships.Add(calledIn, mapping.First());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f is FunctionCall fc)
|
||||||
|
{
|
||||||
|
calledFunctionQueue.Enqueue(fc.CalledFunctionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var e in calledFunctions)
|
||||||
|
{
|
||||||
|
HandleExpression(e, "profile_root");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (calledFunctionQueue.TryDequeue(out var functionName))
|
||||||
|
{
|
||||||
|
if (alreadyAnalysedFunctions.Contains(functionName))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
alreadyAnalysedFunctions.Add(functionName);
|
||||||
|
|
||||||
|
var functionImplementation = context.GetFunction(functionName);
|
||||||
|
HandleExpression(functionImplementation, functionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return memberships;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
83
AspectedRouting/Language/Context.cs
Normal file
83
AspectedRouting/Language/Context.cs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Functions;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Language
|
||||||
|
{
|
||||||
|
public class Context
|
||||||
|
{
|
||||||
|
public readonly Dictionary<string, IExpression> Parameters = new Dictionary<string, IExpression>();
|
||||||
|
public readonly Dictionary<string, AspectMetadata> DefinedFunctions = new Dictionary<string, AspectMetadata>();
|
||||||
|
|
||||||
|
public readonly string AspectName;
|
||||||
|
|
||||||
|
public Context()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Context(string aspectName, Dictionary<string, IExpression> parameters,
|
||||||
|
Dictionary<string, AspectMetadata> definedFunctions)
|
||||||
|
{
|
||||||
|
AspectName = aspectName;
|
||||||
|
Parameters = parameters;
|
||||||
|
DefinedFunctions = definedFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context(Context c) : this(c.AspectName, c.Parameters, c.DefinedFunctions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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) && !function.ProfileInternal)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Function " + name + " already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
DefinedFunctions[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 Context WithParameters(Dictionary<string, IExpression> parameters)
|
||||||
|
{
|
||||||
|
return new Context(AspectName, parameters, DefinedFunctions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context WithAspectName(string name)
|
||||||
|
{
|
||||||
|
return new Context(name, Parameters, DefinedFunctions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Functions;
|
using AspectedRouting.Language.Expression;
|
||||||
|
|
||||||
namespace AspectedRouting
|
namespace AspectedRouting.Language
|
||||||
{
|
{
|
||||||
public static class Deconstruct
|
public static class Deconstruct
|
||||||
{
|
{
|
||||||
|
@ -99,9 +99,7 @@ namespace AspectedRouting
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Func<IExpression, bool> Any()
|
public static readonly Func<IExpression, bool> Any = e => true;
|
||||||
{
|
|
||||||
return e => true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Functions;
|
||||||
using static AspectedRouting.Deconstruct;
|
using AspectedRouting.Language.Typ;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Expression
|
||||||
{
|
{
|
||||||
public class Apply : IExpression
|
public class Apply : IExpression
|
||||||
{
|
{
|
||||||
|
@ -88,7 +88,7 @@ namespace AspectedRouting.Functions
|
||||||
$"is applied on an argument with types:" +
|
$"is applied on an argument with types:" +
|
||||||
$"{string.Join(", ", argument.Optimize().Types)}";
|
$"{string.Join(", ", argument.Optimize().Types)}";
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
_debugInfo = $"\n{f.TypeBreakdown()}\n" +
|
_debugInfo = $"\n{f.TypeBreakdown()}\n" +
|
||||||
$"{argument.TypeBreakdown()}";
|
$"{argument.TypeBreakdown()}";
|
||||||
|
@ -99,9 +99,9 @@ namespace AspectedRouting.Functions
|
||||||
|
|
||||||
public object Evaluate(Context c, params IExpression[] arguments)
|
public object Evaluate(Context c, params IExpression[] arguments)
|
||||||
{
|
{
|
||||||
if (Types.Count() > 1)
|
if (!Types.Any())
|
||||||
{
|
{
|
||||||
// We try to select the smallest type
|
throw new ArgumentException("Trying to invoke an invalid expression: " + this);
|
||||||
}
|
}
|
||||||
|
|
||||||
var type = Types.First();
|
var type = Types.First();
|
||||||
|
@ -166,20 +166,20 @@ namespace AspectedRouting.Functions
|
||||||
// => (const dot _) id => dot id => id
|
// => (const dot _) id => dot id => id
|
||||||
// or => (constRight _ id) id => id id => id
|
// or => (constRight _ id) id => id id => id
|
||||||
if (
|
if (
|
||||||
UnApplyAny(
|
Deconstruct.UnApplyAny(
|
||||||
UnApplyAny(
|
Deconstruct.UnApplyAny(
|
||||||
UnApplyAny(
|
Deconstruct.UnApplyAny(
|
||||||
IsFunc(Funcs.Const),
|
Deconstruct.IsFunc(Funcs.Const),
|
||||||
IsFunc(Funcs.Dot)),
|
Deconstruct.IsFunc(Funcs.Dot)),
|
||||||
Any()),
|
Deconstruct.Any),
|
||||||
IsFunc(Funcs.Id)
|
Deconstruct.IsFunc(Funcs.Id)
|
||||||
).Invoke(this)
|
).Invoke(this)
|
||||||
&& UnApplyAny(UnApplyAny(
|
&& Deconstruct.UnApplyAny(Deconstruct.UnApplyAny(
|
||||||
UnApplyAny(
|
Deconstruct.UnApplyAny(
|
||||||
IsFunc(Funcs.ConstRight),
|
Deconstruct.IsFunc(Funcs.ConstRight),
|
||||||
Any()),
|
Deconstruct.Any),
|
||||||
IsFunc(Funcs.Id)),
|
Deconstruct.IsFunc(Funcs.Id)),
|
||||||
IsFunc(Funcs.Id)
|
Deconstruct.IsFunc(Funcs.Id)
|
||||||
).Invoke(this))
|
).Invoke(this))
|
||||||
{
|
{
|
||||||
return Funcs.Id;
|
return Funcs.Id;
|
||||||
|
@ -247,12 +247,12 @@ namespace AspectedRouting.Functions
|
||||||
|
|
||||||
// ((dot f0) f1)
|
// ((dot f0) f1)
|
||||||
// ((dot f0) f1) arg is the actual expression, but arg is already split of
|
// ((dot f0) f1) arg is the actual expression, but arg is already split of
|
||||||
if (UnApply(
|
if (Deconstruct.UnApply(
|
||||||
UnApply(
|
Deconstruct.UnApply(
|
||||||
IsFunc(Funcs.Dot),
|
Deconstruct.IsFunc(Funcs.Dot),
|
||||||
Assign(f0)
|
Deconstruct.Assign(f0)
|
||||||
),
|
),
|
||||||
Assign(f1)).Invoke(f)
|
Deconstruct.Assign(f1)).Invoke(f)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// f0 (f1 arg)
|
// f0 (f1 arg)
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Expression
|
||||||
{
|
{
|
||||||
public class AspectMetadata : IExpression
|
public class AspectMetadata : IExpression
|
||||||
{
|
{
|
||||||
|
@ -32,6 +32,12 @@ namespace AspectedRouting.Functions
|
||||||
|
|
||||||
public object Evaluate(Context c, params IExpression[] arguments)
|
public object Evaluate(Context c, params IExpression[] arguments)
|
||||||
{
|
{
|
||||||
|
if (ProfileInternal && arguments.Length > 0)
|
||||||
|
{
|
||||||
|
var tags = (Dictionary<string, string>) arguments[0].Evaluate(c);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return ExpressionImplementation.Evaluate(c, arguments);
|
return ExpressionImplementation.Evaluate(c, arguments);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Typ;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Expression
|
||||||
{
|
{
|
||||||
public abstract class Function : IExpression
|
public abstract class Function : IExpression
|
||||||
{
|
{
|
|
@ -1,9 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Typ;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Expression
|
||||||
{
|
{
|
||||||
public class FunctionCall : IExpression
|
public class FunctionCall : IExpression
|
||||||
{
|
{
|
||||||
|
@ -34,7 +34,10 @@ namespace AspectedRouting.Functions
|
||||||
|
|
||||||
public object Evaluate(Context c, params IExpression[] arguments)
|
public object Evaluate(Context c, params IExpression[] arguments)
|
||||||
{
|
{
|
||||||
return c.GetFunction(_name).Evaluate(c, arguments);
|
|
||||||
|
var func = c.GetFunction(_name);
|
||||||
|
c = c.WithAspectName(_name);
|
||||||
|
return func.Evaluate(c, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IExpression Specialize(IEnumerable<Type> allowedTypes)
|
public IExpression Specialize(IEnumerable<Type> allowedTypes)
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Expression
|
||||||
{
|
{
|
||||||
public class ProfileMetaData
|
public class ProfileMetaData
|
||||||
{
|
{
|
||||||
|
@ -12,19 +12,19 @@ namespace AspectedRouting.Functions
|
||||||
public List<string> VehicleTyps { get; }
|
public List<string> VehicleTyps { get; }
|
||||||
public List<string> Metadata { get; }
|
public List<string> Metadata { get; }
|
||||||
|
|
||||||
public Dictionary<string, object> DefaultParameters { get; }
|
public Dictionary<string, IExpression> DefaultParameters { get; }
|
||||||
public Dictionary<string, Dictionary<string, object>> Profiles { get; }
|
public Dictionary<string, Dictionary<string, IExpression>> Behaviours { get; }
|
||||||
|
|
||||||
public IExpression Access { get; }
|
public IExpression Access { get; }
|
||||||
public IExpression Oneway { get; }
|
public IExpression Oneway { get; }
|
||||||
public IExpression Speed { get; }
|
public IExpression Speed { get; }
|
||||||
public Dictionary<string, IExpression> Weights { get; }
|
public Dictionary<string, IExpression> Priority { get; }
|
||||||
|
|
||||||
public ProfileMetaData(string name, string description, string author, string filename,
|
public ProfileMetaData(string name, string description, string author, string filename,
|
||||||
List<string> vehicleTyps, Dictionary<string, object> defaultParameters,
|
List<string> vehicleTyps, Dictionary<string, IExpression> defaultParameters,
|
||||||
Dictionary<string, Dictionary<string, object>> profiles,
|
Dictionary<string, Dictionary<string, IExpression>> behaviours,
|
||||||
IExpression access, IExpression oneway, IExpression speed,
|
IExpression access, IExpression oneway, IExpression speed,
|
||||||
Dictionary<string, IExpression> weights, List<string> metadata)
|
Dictionary<string, IExpression> priority, List<string> metadata)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Description = description;
|
Description = description;
|
||||||
|
@ -34,16 +34,16 @@ namespace AspectedRouting.Functions
|
||||||
Access = access;
|
Access = access;
|
||||||
Oneway = oneway;
|
Oneway = oneway;
|
||||||
Speed = speed;
|
Speed = speed;
|
||||||
Weights = weights;
|
Priority = priority;
|
||||||
Metadata = metadata;
|
Metadata = metadata;
|
||||||
DefaultParameters = defaultParameters;
|
DefaultParameters = defaultParameters;
|
||||||
Profiles = profiles;
|
Behaviours = behaviours;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"Profile: {Name} {Filename}\naccess={Access}\noneway={Oneway}\nspeed={Speed}\n" +
|
return $"Profile: {Name} {Filename}\naccess={Access}\noneway={Oneway}\nspeed={Speed}\n" +
|
||||||
$"weights ={string.Join(" + ", Weights.Select(kv => "#" + kv.Key + " * " + kv.Value))} ";
|
$"priorities = {string.Join(" + ", Priority.Select(kv => "#" + kv.Key + " * " + kv.Value))} ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using AspectedRouting.Language.Functions;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
// ReSharper disable UnusedMember.Global
|
// ReSharper disable UnusedMember.Global
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language
|
||||||
{
|
{
|
||||||
public static class Funcs
|
public static class Funcs
|
||||||
{
|
{
|
||||||
|
@ -23,12 +25,13 @@ namespace AspectedRouting.Functions
|
||||||
public static readonly Function ToStringFunc = new ToString();
|
public static readonly Function ToStringFunc = new ToString();
|
||||||
public static readonly Function Concat = new Concat();
|
public static readonly Function Concat = new Concat();
|
||||||
|
|
||||||
|
public static readonly Function ContainedIn = new ContainedIn();
|
||||||
public static readonly Function Min = new Min();
|
public static readonly Function Min = new Min();
|
||||||
public static readonly Function Max = new Max();
|
public static readonly Function Max = new Max();
|
||||||
public static readonly Function Sum = new Sum();
|
public static readonly Function Sum = new Sum();
|
||||||
public static readonly Function Multiply = new Multiply();
|
public static readonly Function Multiply = new Multiply();
|
||||||
|
|
||||||
|
|
||||||
public static readonly Function FirstOf = new FirstMatchOf();
|
public static readonly Function FirstOf = new FirstMatchOf();
|
||||||
public static readonly Function MustMatch = new MustMatch();
|
public static readonly Function MustMatch = new MustMatch();
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class All : Function
|
public class All : Function
|
||||||
{
|
{
|
|
@ -1,7 +1,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Concat : Function
|
public class Concat : Function
|
||||||
{
|
{
|
|
@ -1,8 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using AspectedRouting.Language.Typ;
|
||||||
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Const : Function
|
public class Const : Function
|
||||||
{
|
{
|
|
@ -1,8 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using AspectedRouting.Language.Typ;
|
||||||
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class ConstRight : Function
|
public class ConstRight : Function
|
||||||
{ public override string Description { get; } =
|
{ public override string Description { get; } =
|
|
@ -1,10 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Typ;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Constant : IExpression
|
public class Constant : IExpression
|
||||||
{
|
{
|
||||||
|
@ -40,7 +40,7 @@ namespace AspectedRouting.Functions
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
throw new Exception($"While creating a list with members {string.Join(", ", exprs)} {e.Message}", e);
|
throw new Exception($"While creating a list with members {string.Join(", ", exprs.Select(e => e.Optimize()))} {e.Message}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
64
AspectedRouting/Language/Functions/ContainedIn.cs
Normal file
64
AspectedRouting/Language/Functions/ContainedIn.cs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Language.Functions
|
||||||
|
{
|
||||||
|
public class ContainedIn : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } =
|
||||||
|
"Given a list of values, checks if the argument is contained in the list.";
|
||||||
|
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"list","a"};
|
||||||
|
|
||||||
|
public ContainedIn() : base("containedIn", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
// [a] -> a -> bool
|
||||||
|
new Curry(
|
||||||
|
new ListType(new Var("a")),
|
||||||
|
new Curry(new Var("a"),
|
||||||
|
Typs.Bool))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContainedIn(IEnumerable<Type> types) : base("containedIn", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
var list = (IEnumerable<IExpression>) arguments[0].Evaluate(c);
|
||||||
|
var arg = arguments[1];
|
||||||
|
|
||||||
|
var result = new List<object>();
|
||||||
|
foreach (var f in list)
|
||||||
|
{
|
||||||
|
var o = f.Evaluate(c);
|
||||||
|
while (o is IExpression e)
|
||||||
|
{
|
||||||
|
o = e.Evaluate(c);
|
||||||
|
}
|
||||||
|
if (f.Equals(arg))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ContainedIn(unified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Default : Function
|
public class Default : Function
|
||||||
{
|
{
|
|
@ -1,18 +1,19 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using AspectedRouting.Language.Typ;
|
||||||
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Dot : Function
|
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 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 override List<string> ArgNames { get; } = new List<string>{"f","g","a"};
|
||||||
public static readonly Var A = new Var("fType");
|
public static readonly Var A = new Var("a");
|
||||||
public static readonly Var B = new Var("gType");
|
public static readonly Var B = new Var("b");
|
||||||
public static readonly Var C = new Var("arg");
|
public static readonly Var C = new Var("c");
|
||||||
|
|
||||||
public Dot() : base("dot", true, new[]
|
public Dot() : base("dot", true, new[]
|
||||||
{
|
{
|
||||||
|
@ -34,7 +35,7 @@ namespace AspectedRouting.Functions
|
||||||
{
|
{
|
||||||
var f0 = arguments[0];
|
var f0 = arguments[0];
|
||||||
var f1 = arguments[1];
|
var f1 = arguments[1];
|
||||||
var resultType = (f1.Types.First() as Curry).ResultType;
|
var resultType = ((Curry) f1.Types.First()).ResultType;
|
||||||
var a = arguments[2];
|
var a = arguments[2];
|
||||||
return f0.Evaluate(c, new Constant(resultType, f1.Evaluate(c, a)));
|
return f0.Evaluate(c, new Constant(resultType, f1.Evaluate(c, a)));
|
||||||
}
|
}
|
|
@ -1,12 +1,24 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using AspectedRouting.Language.Typ;
|
||||||
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class EitherFunc : Function
|
public class EitherFunc : Function
|
||||||
{
|
{
|
||||||
|
public override string Description { get; } =
|
||||||
|
"EitherFunc is a small utility function, mostly used in the parser. It allows the compiler to choose a function, based on the types.\n\n" +
|
||||||
|
"" +
|
||||||
|
"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.\n\n" +
|
||||||
|
"Disclaimer: _you should never ever need this in your profiles_";
|
||||||
|
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"f","g","a"};
|
||||||
private static Var a = new Var("a");
|
private static Var a = new Var("a");
|
||||||
private static Var b = new Var("b");
|
private static Var b = new Var("b");
|
||||||
private static Var c = new Var("c");
|
private static Var c = new Var("c");
|
|
@ -1,7 +1,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Eq : Function
|
public class Eq : Function
|
||||||
{ public override string Description { get; } = "Returns 'yes' if both values _are_ the same";
|
{ public override string Description { get; } = "Returns 'yes' if both values _are_ the same";
|
|
@ -1,9 +1,10 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using AspectedRouting.Language.Typ;
|
||||||
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class FirstMatchOf : Function
|
public class FirstMatchOf : Function
|
||||||
{
|
{
|
|
@ -1,8 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Id : Function
|
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 string Description { get; } = "Returns the argument unchanged - the identity function. Seems useless at first sight, but useful in parsing";
|
|
@ -1,7 +1,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class If : Function
|
public class If : Function
|
||||||
{
|
{
|
|
@ -1,7 +1,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Inv : Function
|
public class Inv : Function
|
||||||
{
|
{
|
|
@ -1,7 +1,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class ListDot : Function
|
public class ListDot : Function
|
||||||
{
|
{
|
|
@ -1,10 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using AspectedRouting.Language.Typ;
|
||||||
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Mapping : Function
|
public class Mapping : Function
|
||||||
{
|
{
|
||||||
|
@ -78,7 +79,6 @@ namespace AspectedRouting.Functions
|
||||||
var otherARgs = arguments.ToList().GetRange(1, arguments.Length - 1);
|
var otherARgs = arguments.ToList().GetRange(1, arguments.Length - 1);
|
||||||
if (!StringToResultFunctions.TryGetValue(key, out var resultFunction))
|
if (!StringToResultFunctions.TryGetValue(key, out var resultFunction))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Warning: key {key} not found in mapping {this}");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ namespace AspectedRouting.Functions
|
||||||
|
|
||||||
var typeOptStr = string.Join(";", opt.Types);
|
var typeOptStr = string.Join(";", opt.Types);
|
||||||
var typeEStr = string.Join("; ", e.Types);
|
var typeEStr = string.Join("; ", e.Types);
|
||||||
if (opt == null || !opt.Types.Any())
|
if (!opt.Types.Any())
|
||||||
{
|
{
|
||||||
throw new NullReferenceException($"Optimized version is null, has different or empty types: " +
|
throw new NullReferenceException($"Optimized version is null, has different or empty types: " +
|
||||||
$"\n{typeEStr}" +
|
$"\n{typeEStr}" +
|
|
@ -1,8 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Max : Function
|
public class Max : Function
|
||||||
{
|
{
|
72
AspectedRouting/Language/Functions/MemberOf.cs
Normal file
72
AspectedRouting/Language/Functions/MemberOf.cs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Language.Functions
|
||||||
|
{
|
||||||
|
public class MemberOf : Function
|
||||||
|
{
|
||||||
|
public override string Description { get; } =
|
||||||
|
"This function returns true, if the way is member of a relation matching the specified function.\n" +
|
||||||
|
"\n" +
|
||||||
|
"In order to use this for itinero 1.0, the membership _must_ be the top level expression.\n" +
|
||||||
|
"\n" +
|
||||||
|
"Conceptually, when the aspect is executed for a way, every relation will be used as argument in the subfunction `f`\n" +
|
||||||
|
"If this subfunction returns 'true', the entire aspect will return true.\n\n" +
|
||||||
|
"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.\n" +
|
||||||
|
"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.\n\n" +
|
||||||
|
"\n\n" +
|
||||||
|
"In the test.csv, one can simply use `_relation:<aspect_name>=yes` to mimic relations in your tests";
|
||||||
|
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>
|
||||||
|
{
|
||||||
|
"f","tags"
|
||||||
|
};
|
||||||
|
|
||||||
|
public MemberOf() : base(
|
||||||
|
"memberOf", true,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Curry(
|
||||||
|
new Curry(Typs.Tags, Typs.Bool),
|
||||||
|
new Curry(Typs.Tags, Typs.Bool))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemberOf(IEnumerable<Type> types) : base("memberOf", types)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
|
{
|
||||||
|
// var subFunction = arguments[0];
|
||||||
|
|
||||||
|
var tags =(Dictionary<string, string>) arguments[1].Evaluate(c);
|
||||||
|
var name = c.AspectName.TrimStart('$');
|
||||||
|
|
||||||
|
if(tags.TryGetValue("_relation:"+name, out var v))
|
||||||
|
{
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
return "no";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
|
{
|
||||||
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
|
if (unified == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MemberOf(unified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,16 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Min : Function
|
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 string Description { get; } =
|
||||||
public override List<string> ArgNames { get; } = new List<string>{"list"};
|
"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,
|
public Min() : base("min", true,
|
||||||
new[]
|
new[]
|
||||||
|
@ -17,7 +20,6 @@ namespace AspectedRouting.Functions
|
||||||
new Curry(new ListType(Typs.PDouble), Typs.PDouble),
|
new Curry(new ListType(Typs.PDouble), Typs.PDouble),
|
||||||
new Curry(new ListType(Typs.Double), Typs.Double),
|
new Curry(new ListType(Typs.Double), Typs.Double),
|
||||||
new Curry(new ListType(Typs.Bool), Typs.Bool),
|
new Curry(new ListType(Typs.Bool), Typs.Bool),
|
||||||
|
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -41,11 +43,12 @@ namespace AspectedRouting.Functions
|
||||||
|
|
||||||
public override object Evaluate(Context c, params IExpression[] arguments)
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
{
|
{
|
||||||
var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o!=null);
|
var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o != null);
|
||||||
var expectedType = (Types.First() as Curry).ResultType;
|
var expectedType = ((Curry) Types.First()).ResultType;
|
||||||
|
|
||||||
switch (expectedType)
|
switch (expectedType)
|
||||||
{ case BoolType _:
|
{
|
||||||
|
case BoolType _:
|
||||||
if (ls.Select(o => o.Equals("yes") || o.Equals("true")).All(b => b))
|
if (ls.Select(o => o.Equals("yes") || o.Equals("true")).All(b => b))
|
||||||
{
|
{
|
||||||
return "yes";
|
return "yes";
|
||||||
|
@ -54,9 +57,27 @@ namespace AspectedRouting.Functions
|
||||||
return "no";
|
return "no";
|
||||||
case DoubleType _:
|
case DoubleType _:
|
||||||
case PDoubleType _:
|
case PDoubleType _:
|
||||||
return ls.Select(o => (double) o).Min();
|
|
||||||
|
|
||||||
|
return ls.Select(o =>
|
||||||
|
{
|
||||||
|
while (o is IExpression e)
|
||||||
|
{
|
||||||
|
o = e.Evaluate(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (double) o;
|
||||||
|
}).Min();
|
||||||
default:
|
default:
|
||||||
return ls.Select(o => (int) o).Min();
|
return ls.Select(o =>
|
||||||
|
{
|
||||||
|
while (o is IExpression e)
|
||||||
|
{
|
||||||
|
o = e.Evaluate(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) o;
|
||||||
|
}).Min();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,16 +1,18 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Multiply : Function
|
public class Multiply : Function
|
||||||
{
|
{
|
||||||
|
public override string Description { get; } =
|
||||||
public override string Description { get; } = "Multiplies all the values in a given list. On a list of booleans, this acts as 'and' or 'all'";
|
"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 override List<string> ArgNames { get; } = new List<string> {"list"};
|
||||||
|
|
||||||
|
|
||||||
public Multiply() : base("multiply", true,
|
public Multiply() : base("multiply", true,
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
|
@ -42,19 +44,26 @@ namespace AspectedRouting.Functions
|
||||||
public override object Evaluate(Context c, params IExpression[] arguments)
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
{
|
{
|
||||||
var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o != null);
|
var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o != null);
|
||||||
var expectedType = (Types.First() as Curry).ResultType;
|
var expectedType = ((Curry) Types.First()).ResultType;
|
||||||
|
|
||||||
|
|
||||||
switch (expectedType)
|
switch (expectedType)
|
||||||
{
|
{
|
||||||
case BoolType _:
|
case BoolType _:
|
||||||
foreach (var o in ls)
|
foreach (var oo in ls)
|
||||||
{
|
{
|
||||||
if(!(o is string s))
|
var o = oo;
|
||||||
|
while (o is IExpression e)
|
||||||
|
{
|
||||||
|
o = e.Evaluate(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(o is string s))
|
||||||
{
|
{
|
||||||
return "no";
|
return "no";
|
||||||
}
|
}
|
||||||
if(!(o.Equals("yes") || o.Equals("true")))
|
|
||||||
|
if (!(s.Equals("yes") || s.Equals("true")))
|
||||||
{
|
{
|
||||||
return "no";
|
return "no";
|
||||||
}
|
}
|
||||||
|
@ -64,16 +73,28 @@ namespace AspectedRouting.Functions
|
||||||
case DoubleType _:
|
case DoubleType _:
|
||||||
case PDoubleType _:
|
case PDoubleType _:
|
||||||
var mult = 1.0;
|
var mult = 1.0;
|
||||||
foreach (var o in ls)
|
foreach (var oo in ls)
|
||||||
{
|
{
|
||||||
|
var o = oo;
|
||||||
|
while (o is IExpression e)
|
||||||
|
{
|
||||||
|
o = e.Evaluate(c);
|
||||||
|
}
|
||||||
|
|
||||||
mult *= (double) o;
|
mult *= (double) o;
|
||||||
}
|
}
|
||||||
|
|
||||||
return mult;
|
return mult;
|
||||||
default:
|
default:
|
||||||
var multI = 1;
|
var multI = 1;
|
||||||
foreach (var o in ls)
|
foreach (var oo in ls)
|
||||||
{
|
{
|
||||||
|
var o = oo;
|
||||||
|
while (o is IExpression e)
|
||||||
|
{
|
||||||
|
o = e.Evaluate(c);
|
||||||
|
}
|
||||||
|
|
||||||
multI *= (int) o;
|
multI *= (int) o;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using AspectedRouting.Language.Typ;
|
||||||
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class MustMatch : Function
|
public class MustMatch : Function
|
||||||
{
|
{
|
|
@ -1,18 +1,20 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class NotEq : Function
|
public class NotEq : Function
|
||||||
{
|
{
|
||||||
public override string Description { get; } = "Returns 'yes' if the two passed in values are _not_ the same";
|
public override string Description { get; } = "OVerloaded function, either boolean not or returns 'yes' if the two passed in values are _not_ the same;";
|
||||||
public override List<string> ArgNames { get; } = new List<string> {"a", "b"};
|
public override List<string> ArgNames { get; } = new List<string> {"a", "b"};
|
||||||
|
|
||||||
public NotEq() : base("notEq", true,
|
public NotEq() : base("notEq", true,
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
Curry.ConstructFrom(Typs.Bool, new Var("a"), new Var("a")),
|
Curry.ConstructFrom(Typs.Bool, new Var("a"), new Var("a")),
|
||||||
Curry.ConstructFrom(Typs.String, new Var("a"), new Var("a"))
|
Curry.ConstructFrom(Typs.String, new Var("a"), new Var("a")),
|
||||||
|
new Curry(Typs.Bool, Typs.Bool),
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
Funcs.AddBuiltin(this, "not");
|
Funcs.AddBuiltin(this, "not");
|
||||||
|
@ -35,6 +37,13 @@ namespace AspectedRouting.Functions
|
||||||
|
|
||||||
public override object Evaluate(Context c, params IExpression[] arguments)
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if (arguments.Length == 1)
|
||||||
|
{
|
||||||
|
var booleanArg = arguments[0].Evaluate(c);
|
||||||
|
return booleanArg.Equals("no");
|
||||||
|
}
|
||||||
|
|
||||||
var arg0 = arguments[0].Evaluate(c);
|
var arg0 = arguments[0].Evaluate(c);
|
||||||
var arg1 = arguments[1].Evaluate(c);
|
var arg1 = arguments[1].Evaluate(c);
|
||||||
if ((!(arg0?.Equals(arg1) ?? false)))
|
if ((!(arg0?.Equals(arg1) ?? false)))
|
|
@ -1,9 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Typ;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Parameter : IExpression
|
public class Parameter : IExpression
|
||||||
{
|
{
|
||||||
|
@ -25,16 +25,8 @@ namespace AspectedRouting.Functions
|
||||||
|
|
||||||
public object Evaluate(Context c, params IExpression[] args)
|
public object Evaluate(Context c, params IExpression[] args)
|
||||||
{
|
{
|
||||||
return c?.Parameters?.GetValueOrDefault(ParamName, null);
|
var paramName = ParamName.TrimStart('#'); // context saves paramnames without '#'
|
||||||
}
|
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)
|
public IExpression Specialize(IEnumerable<Type> allowedTypes)
|
|
@ -1,8 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Parse : Function
|
public class Parse : Function
|
||||||
{
|
{
|
||||||
|
@ -13,6 +14,7 @@ namespace AspectedRouting.Functions
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new Curry(Typs.String, Typs.Double),
|
new Curry(Typs.String, Typs.Double),
|
||||||
|
new Curry(Typs.String, Typs.PDouble),
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
}
|
}
|
|
@ -1,13 +1,18 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a function 'string -> string -> a' onto a function 'tags -> [a]'
|
/// Converts a function 'string -> string -> a' onto a function 'tags -> [a]'
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StringStringToTagsFunction : Function
|
public class StringStringToTagsFunction : Function
|
||||||
{
|
{
|
||||||
|
public override string Description { get; } =
|
||||||
|
"stringToTags converts a function `string -> string -> a` into a function `tags -> [a]`";
|
||||||
|
public override List<string> ArgNames { get; } = new List<string>{"f","tags"};
|
||||||
|
|
||||||
private static Type baseFunction =
|
private static Type baseFunction =
|
||||||
Curry.ConstructFrom(new Var("a"), Typs.String, Typs.String);
|
Curry.ConstructFrom(new Var("a"), Typs.String, Typs.String);
|
||||||
|
|
||||||
|
@ -15,7 +20,8 @@ namespace AspectedRouting.Functions
|
||||||
public StringStringToTagsFunction() : base("stringToTags", true,
|
public StringStringToTagsFunction() : base("stringToTags", true,
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new Curry(baseFunction, new Curry(Typs.Tags, new ListType(new Var("a"))))
|
new Curry(baseFunction,
|
||||||
|
new Curry(Typs.Tags, new ListType(new Var("a"))))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
{
|
{
|
|
@ -1,8 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class Sum : Function
|
public class Sum : Function
|
||||||
{
|
{
|
|
@ -1,7 +1,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
namespace AspectedRouting.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class ToString : Function
|
public class ToString : Function
|
||||||
{
|
{
|
|
@ -1,60 +1,13 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Functions;
|
using AspectedRouting.Language.Functions;
|
||||||
using AspectedRouting.Typ;
|
using AspectedRouting.Language.Typ;
|
||||||
using Type = AspectedRouting.Typ.Type;
|
using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting
|
namespace AspectedRouting.Language
|
||||||
{
|
{
|
||||||
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
|
public interface IExpression
|
||||||
{
|
{
|
||||||
IEnumerable<Type> Types { get; }
|
IEnumerable<Type> Types { get; }
|
||||||
|
@ -79,6 +32,11 @@ namespace AspectedRouting
|
||||||
|
|
||||||
public static class ExpressionExtensions
|
public static class ExpressionExtensions
|
||||||
{
|
{
|
||||||
|
public static object Run(this IExpression e, Context c, Dictionary<string, string> tags)
|
||||||
|
{
|
||||||
|
return e.Apply(new []{new Constant(tags)}).Evaluate(c);
|
||||||
|
}
|
||||||
|
|
||||||
public static IExpression Specialize(this IExpression e, Type t)
|
public static IExpression Specialize(this IExpression e, Type t)
|
||||||
{
|
{
|
||||||
if (t == null)
|
if (t == null)
|
||||||
|
@ -125,6 +83,7 @@ namespace AspectedRouting
|
||||||
/// THen specializes every expression onto this common ground
|
/// THen specializes every expression onto this common ground
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The common ground of types</returns>
|
/// <returns>The common ground of types</returns>
|
||||||
|
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
|
||||||
public static IEnumerable<IExpression> SpecializeToCommonTypes(this IEnumerable<IExpression> exprs,
|
public static IEnumerable<IExpression> SpecializeToCommonTypes(this IEnumerable<IExpression> exprs,
|
||||||
out IEnumerable<Type> specializedTypes, out IEnumerable<IExpression> specializedExpressions)
|
out IEnumerable<Type> specializedTypes, out IEnumerable<IExpression> specializedExpressions)
|
||||||
{
|
{
|
||||||
|
@ -139,12 +98,13 @@ namespace AspectedRouting
|
||||||
}
|
}
|
||||||
|
|
||||||
var specialized = specializedTypes.SpecializeTo(f.Types.RenameVars(specializedTypes));
|
var specialized = specializedTypes.SpecializeTo(f.Types.RenameVars(specializedTypes));
|
||||||
|
// ReSharper disable once JoinNullCheckWithUsage
|
||||||
if (specialized == null)
|
if (specialized == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Could not unify\n "
|
throw new ArgumentException("Could not unify\n "
|
||||||
+ "<previous items>: " + string.Join(", ", specializedTypes) +
|
+ "<previous items>: " + string.Join(", ", specializedTypes) +
|
||||||
"\nwith\n "
|
"\nwith\n "
|
||||||
+ f + ": " + string.Join(", ", f.Types));
|
+ f.Optimize() + ": " + string.Join(", ", f.Types));
|
||||||
}
|
}
|
||||||
|
|
||||||
specializedTypes = specialized;
|
specializedTypes = specialized;
|
|
@ -1,4 +1,4 @@
|
||||||
namespace AspectedRouting.Typ
|
namespace AspectedRouting.Language.Typ
|
||||||
{
|
{
|
||||||
public class BoolType : Type
|
public class BoolType : Type
|
||||||
{
|
{
|
|
@ -2,7 +2,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace AspectedRouting.Typ
|
namespace AspectedRouting.Language.Typ
|
||||||
{
|
{
|
||||||
public class Curry : Type
|
public class Curry : Type
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace AspectedRouting.Typ
|
namespace AspectedRouting.Language.Typ
|
||||||
{
|
{
|
||||||
public class DoubleType : Type
|
public class DoubleType : Type
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace AspectedRouting.Typ
|
namespace AspectedRouting.Language.Typ
|
||||||
{
|
{
|
||||||
public class IntType : Type
|
public class IntType : Type
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace AspectedRouting.Typ
|
namespace AspectedRouting.Language.Typ
|
||||||
{
|
{
|
||||||
public class ListType : Type
|
public class ListType : Type
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace AspectedRouting.Typ
|
namespace AspectedRouting.Language.Typ
|
||||||
{
|
{
|
||||||
public class NatType : Type
|
public class NatType : Type
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace AspectedRouting.Typ
|
namespace AspectedRouting.Language.Typ
|
||||||
{
|
{
|
||||||
public class PDoubleType : Type
|
public class PDoubleType : Type
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace AspectedRouting.Typ
|
namespace AspectedRouting.Language.Typ
|
||||||
{
|
{
|
||||||
public class StringType : Type
|
public class StringType : Type
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace AspectedRouting.Typ
|
namespace AspectedRouting.Language.Typ
|
||||||
{
|
{
|
||||||
public class TagsType : Type
|
public class TagsType : Type
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace AspectedRouting.Typ
|
namespace AspectedRouting.Language.Typ
|
||||||
{
|
{
|
||||||
public abstract class Type
|
public abstract class Type
|
||||||
{
|
{
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace AspectedRouting.Typ
|
namespace AspectedRouting.Language.Typ
|
||||||
{
|
{
|
||||||
public static class Typs
|
public static class Typs
|
||||||
{
|
{
|
|
@ -1,7 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace AspectedRouting.Typ
|
namespace AspectedRouting.Language.Typ
|
||||||
{
|
{
|
||||||
public class Var : Type
|
public class Var : Type
|
||||||
{
|
{
|
|
@ -1,12 +1,7 @@
|
||||||
-- The different profiles
|
-- The different profiles
|
||||||
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",
|
name = "b2w",
|
||||||
|
@ -72,151 +67,10 @@ profiles = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
--[[
|
|
||||||
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
|
-- Returns 1 if no access restrictions, 0 if it is permissive
|
||||||
function determine_permissive_score(attributes, result)
|
function determine_permissive_score(attributes, result)
|
||||||
if (attributes.access == "permissive") then
|
if (attributes.access == "permissive") then
|
||||||
|
@ -674,30 +528,6 @@ end
|
||||||
function unit_tests()
|
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" },
|
unit_test_relation_tag_processor({ route = "bicycle", operator = "Stad Genk", color = "red", type = "route" },
|
||||||
{ StadGenk = "yes", cycle_network_colour = "red", cycle_network = "yes" });
|
{ StadGenk = "yes", cycle_network_colour = "red", cycle_network = "yes" });
|
||||||
|
|
4
AspectedRouting/Profiles/bicycle/bicycle.brussels.csv
Normal file
4
AspectedRouting/Profiles/bicycle/bicycle.brussels.csv
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
access,oneway,speed,priority,highway,_relation:bicycle.network_by_operator
|
||||||
|
no,both,0,0,,
|
||||||
|
yes,both,15,1.9,residential,
|
||||||
|
yes,both,15,21.9,residential,yes
|
|
|
@ -1,12 +1,60 @@
|
||||||
{
|
{
|
||||||
"name": "bicycle.comfort",
|
"name": "bicycle.comfort",
|
||||||
"description": "Gives a comfort factor, purely based on physical aspects of the road",
|
"description": "Gives a comfort factor for a road, purely based on physical aspects of the road, which is a bit subjective; this takes a bit of scnery into account with a preference for `railway=abandoned` and `towpath=yes`",
|
||||||
"unit": "[0, 2]",
|
"unit": "[0, 2]",
|
||||||
"$default": 1,
|
"$default": 1,
|
||||||
"value": {
|
"value": {
|
||||||
"$multiply": {
|
"$multiply": {
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
"towpath": {
|
||||||
|
"yes": 2
|
||||||
|
},
|
||||||
|
"cycleway": {
|
||||||
|
"track": 1.2
|
||||||
|
},
|
||||||
"cyclestreet": {
|
"cyclestreet": {
|
||||||
"yes": 1.5
|
"yes": 1.1
|
||||||
|
},
|
||||||
|
"access": {
|
||||||
|
"designated": 1.2,
|
||||||
|
"dismount": 0.5
|
||||||
|
},
|
||||||
|
"surface": {
|
||||||
|
"#": "The surface mapping heavily resembles the one in speed_factor, but it is not entirely the same",
|
||||||
|
"paved": 0.99,
|
||||||
|
"concrete:lanes": 0.8,
|
||||||
|
"concrete:plates": 1.0,
|
||||||
|
"sett": 0.9,
|
||||||
|
"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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
AspectedRouting/Profiles/bicycle/bicycle.comfort.test.csv
Normal file
18
AspectedRouting/Profiles/bicycle/bicycle.comfort.test.csv
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
expected,highway,foot,bicycle,cyclestreet,cycleway,cycleway:right,surface,railway,towpath
|
||||||
|
1,,,,,,,,,
|
||||||
|
1,residential,,,,,,,,
|
||||||
|
1.1,residential,,,yes,,,,,
|
||||||
|
1.2,cycleway,,,,,,,,
|
||||||
|
1.2,cycleway,designated,,,,,,,
|
||||||
|
0.5,path,designated,designated,,,,,,
|
||||||
|
0.5,path,,designated,,,,,,
|
||||||
|
0.95,footway,designated,,,,,,,
|
||||||
|
0.3,primary,,,,no,,,,
|
||||||
|
0.3,primary,,,,yes,,,,
|
||||||
|
0.36,primary,,,,track,,,,
|
||||||
|
0.4,secondary,,,,lane,,,,
|
||||||
|
0.4,secondary,,,,,lane,asphalt,,
|
||||||
|
1.1,residential,,,yes,,,asphalt,,
|
||||||
|
2,,,,,,,,abandoned,
|
||||||
|
2,,,,,,,,,yes
|
||||||
|
4,,,,,,,,abandoned,yes
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
access,oneway,speed,priority,highway,_relation:bicycle.network_score
|
||||||
|
designated,both,15,22,cycleway,yes
|
||||||
|
no,,,,,
|
||||||
|
designated,both,15,2,cycleway,
|
|
|
@ -1,7 +1,7 @@
|
||||||
access,oneway,speed,weight,highway,bicycle
|
access,oneway,speed,priority,highway,bicycle
|
||||||
0,0,0,0,,
|
no,both,0,0,,
|
||||||
1,0,30,0.03333,cycleway,
|
designated,both,15,15,cycleway,
|
||||||
0,0,0,0,primary,
|
no,both,0,0,primary,
|
||||||
1,0,15,0.06666,pedestrian,
|
dismount,both,2.25,2.25,pedestrian,
|
||||||
1,0,15,0.0666,pedestrian,yes
|
yes,both,15,15,pedestrian,yes
|
||||||
1,0,30,0.033333,residential,
|
yes,both,15,15,residential,
|
||||||
|
|
|
|
@ -16,25 +16,27 @@
|
||||||
"network"
|
"network"
|
||||||
],
|
],
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"#max_speed": 30,
|
|
||||||
"#defaultSpeed": 15,
|
"#defaultSpeed": 15,
|
||||||
"#speed": 0,
|
"#maxspeed": 30,
|
||||||
|
"#timeNeeded": 0,
|
||||||
"#distance": 0,
|
"#distance": 0,
|
||||||
"#comfort": 0,
|
"#comfort": 0,
|
||||||
"#safety": 0,
|
"#safety": 0,
|
||||||
"#withDirection": "yes",
|
"#network": 0,
|
||||||
"#againstDirection": "yes",
|
"#networkOperator": []
|
||||||
"network": 0
|
|
||||||
},
|
},
|
||||||
"profiles": {
|
"behaviours": {
|
||||||
|
|
||||||
"fastest": {
|
"fastest": {
|
||||||
"description": "The fastest route to your destination",
|
"description": "The fastest route to your destination",
|
||||||
"#time_needed": 1
|
"#timeNeeded": 1
|
||||||
},
|
},
|
||||||
"shortest": {
|
"shortest": {
|
||||||
"description": "The shortest route, independent of of speed",
|
"description": "The shortest route, independent of of speed",
|
||||||
"#distance": 1
|
"#distance": 1
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
"safety": {
|
"safety": {
|
||||||
"description": "A defensive route shying away from big roads with lots of cars",
|
"description": "A defensive route shying away from big roads with lots of cars",
|
||||||
"#safety": 1
|
"#safety": 1
|
||||||
|
@ -48,10 +50,12 @@
|
||||||
"#comfort": 1,
|
"#comfort": 1,
|
||||||
"#safety": 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)",
|
|
||||||
|
"brussels": {
|
||||||
|
"description": "A route preferring the cycling network by operator 'Brussels Mobility'",
|
||||||
"#network": 20,
|
"#network": 20,
|
||||||
"#network-node-network-only":true,
|
"#networkOperator": ["Brussels Mobility"],
|
||||||
"#comfort": 1,
|
"#comfort": 1,
|
||||||
"#safety": 1
|
"#safety": 1
|
||||||
}
|
}
|
||||||
|
@ -61,16 +65,20 @@
|
||||||
"speed": {
|
"speed": {
|
||||||
"$min": [
|
"$min": [
|
||||||
"$legal_maxspeed_be",
|
"$legal_maxspeed_be",
|
||||||
"#defaultSpeed"
|
"#maxspeed",
|
||||||
|
{
|
||||||
|
"$multiply": [
|
||||||
|
"#defaultSpeed",
|
||||||
|
"$bicycle.speed_factor"
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"weight": {
|
"priority": {
|
||||||
"#comfort": "$bicycle.comfort",
|
"#comfort": "$bicycle.comfort",
|
||||||
"#safety": "$bicycle.safety",
|
"#safety": "$bicycle.safety",
|
||||||
"#network": "$bicycle.network_score",
|
"#network": "$bicycle.network_by_operator",
|
||||||
"#time_needed": {
|
"#timeNeeded": "$speed",
|
||||||
"$inv": "$speed"
|
|
||||||
},
|
|
||||||
"#distance": "$distance"
|
"#distance": "$distance"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "bicycle.network_by_operator",
|
||||||
|
"description": "The 'bicycle.network_score' returns true if the way is part of a cycling network of a certain (group of) operators",
|
||||||
|
"$memberOf": {
|
||||||
|
"$mustMatch": {
|
||||||
|
"type": "route",
|
||||||
|
"route": "bicycle",
|
||||||
|
"state": {"$notEq": "proposed"},
|
||||||
|
"operator": {"$containedIn": "#networkOperator"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "bicycle.network_score",
|
"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.",
|
"description": "The 'bicycle.network_score' returns true if the way is part of a cycling network",
|
||||||
"$mustMatch": {
|
"$memberOf": {
|
||||||
"type": "route",
|
"$mustMatch": {
|
||||||
"route": "bicycle"
|
"type": "route",
|
||||||
|
"route": "bicycle",
|
||||||
|
"state": {"$notEq": "proposed"}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
62
AspectedRouting/Profiles/bicycle/bicycle.speed_factor.json
Normal file
62
AspectedRouting/Profiles/bicycle/bicycle.speed_factor.json
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"name": "bicycle.speed_factor",
|
||||||
|
"description": "Calculates a speed factor for bicycles based on physical features, e.g. a sand surface will slow a cyclist down; going over pedestrian areas even more, ...",
|
||||||
|
"$multiply": {
|
||||||
|
"access": {
|
||||||
|
"#": "We have to go by foot. Default speed of 20km/h * 0.15 = 3km/h",
|
||||||
|
"dismount": 0.15
|
||||||
|
},
|
||||||
|
"highway": {
|
||||||
|
"#": "A small forest path is typically slow",
|
||||||
|
"path": 0.5,
|
||||||
|
"#": "an unmaintained track (in Belgium: tractor path) is slower as well",
|
||||||
|
"track": 0.7
|
||||||
|
},
|
||||||
|
"surface": {
|
||||||
|
"paved": 0.99,
|
||||||
|
"asphalt": 1,
|
||||||
|
"concrete": 1,
|
||||||
|
"metal": 1,
|
||||||
|
"wood": 1,
|
||||||
|
"concrete:lanes": 0.95,
|
||||||
|
"concrete:plates": 1,
|
||||||
|
"paving_stones": 1,
|
||||||
|
"sett": 0.9,
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
expected,incline,surface,highway,access
|
||||||
|
1,,,,
|
||||||
|
1,,,residential,
|
||||||
|
0.75,up,,residential,
|
||||||
|
1.25,down,,residential,
|
||||||
|
0.3,up,mud,residential,
|
||||||
|
1.125,down,sett,residential,
|
||||||
|
0.675,up,sett,residential,
|
||||||
|
0.9,,sett,residential,
|
||||||
|
1,,asphalt,residential,
|
||||||
|
0.15,,,residential,dismount
|
||||||
|
0.0315,up,mud,track,dismount
|
|
|
@ -2,110 +2,132 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Functions;
|
|
||||||
using AspectedRouting.IO;
|
using AspectedRouting.IO;
|
||||||
|
using AspectedRouting.IO.itinero1;
|
||||||
|
using AspectedRouting.IO.jsonParser;
|
||||||
|
using AspectedRouting.Language;
|
||||||
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Tests;
|
||||||
|
|
||||||
namespace AspectedRouting
|
namespace AspectedRouting
|
||||||
{
|
{
|
||||||
class Program
|
static class Program
|
||||||
{
|
{
|
||||||
|
public static List<(AspectMetadata aspect, FunctionTestSuite tests)> ParseAspects(
|
||||||
|
this List<string> jsonFileNames, Context context)
|
||||||
|
{
|
||||||
|
var aspects = new List<(AspectMetadata aspect, FunctionTestSuite tests)>();
|
||||||
|
foreach (var file in jsonFileNames)
|
||||||
|
{
|
||||||
|
var fi = new FileInfo(file);
|
||||||
|
var aspect = JsonParser.AspectFromJson(context, File.ReadAllText(file), fi.Name);
|
||||||
|
if (aspect != null)
|
||||||
|
{
|
||||||
|
var testPath = fi.DirectoryName + "/" + aspect.Name + ".test.csv";
|
||||||
|
FunctionTestSuite tests = null;
|
||||||
|
if (File.Exists(testPath))
|
||||||
|
{
|
||||||
|
tests = FunctionTestSuite.FromString(aspect, File.ReadAllText(testPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
aspects.Add((aspect, tests));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aspects;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LuaPrinter GenerateLua(Context context,
|
||||||
|
List<(AspectMetadata aspect, FunctionTestSuite tests)> aspects,
|
||||||
|
ProfileMetaData profile, List<ProfileTestSuite> profileTests)
|
||||||
|
{
|
||||||
|
var luaPrinter = new LuaPrinter(context);
|
||||||
|
foreach (var (aspect, tests) in aspects)
|
||||||
|
{
|
||||||
|
luaPrinter.AddFunction(aspect);
|
||||||
|
if (tests != null)
|
||||||
|
{
|
||||||
|
luaPrinter.AddTestSuite(tests);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
luaPrinter.AddProfile(profile);
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var testSuite in profileTests)
|
||||||
|
{
|
||||||
|
luaPrinter.AddTestSuite(testSuite);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return luaPrinter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (ProfileMetaData profile, List<ProfileTestSuite> profileTests) ParseProfile(string profilePath,
|
||||||
|
Context context)
|
||||||
|
{
|
||||||
|
var profile = JsonParser.ProfileFromJson(context, File.ReadAllText(profilePath), new FileInfo(profilePath));
|
||||||
|
profile.SanityCheckProfile(context);
|
||||||
|
|
||||||
|
var profileFi = new FileInfo(profilePath);
|
||||||
|
var profileTests = new List<ProfileTestSuite>();
|
||||||
|
foreach (var behaviourName in profile.Behaviours.Keys)
|
||||||
|
{
|
||||||
|
var testPath = profileFi.DirectoryName + "/" + profile.Name + "." + behaviourName + ".csv";
|
||||||
|
if (File.Exists(testPath))
|
||||||
|
{
|
||||||
|
var test = ProfileTestSuite.FromString(context, profile, behaviourName, File.ReadAllText(testPath));
|
||||||
|
profileTests.Add(test);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[{behaviourName}] WARNING: no test found for behaviour");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (profile, profileTests);
|
||||||
|
}
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
MdPrinter.GenerateHelpText("IO/md/helpText.md");
|
||||||
|
|
||||||
|
|
||||||
var files = Directory.EnumerateFiles("Profiles", "*.json", SearchOption.AllDirectories)
|
var files = Directory.EnumerateFiles("Profiles", "*.json", SearchOption.AllDirectories)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
|
||||||
var context = new Context();
|
var context = new Context();
|
||||||
|
|
||||||
|
var aspects = ParseAspects(files, context);
|
||||||
|
|
||||||
MdPrinter.GenerateHelpText("IO/md/helpText.md");
|
foreach (var (aspect, t) in aspects)
|
||||||
|
|
||||||
var testSuites = new List<FunctionTestSuite>();
|
|
||||||
var aspects = new List<AspectMetadata>();
|
|
||||||
foreach (var file in files)
|
|
||||||
{
|
{
|
||||||
var fi = new FileInfo(file);
|
if (t == null)
|
||||||
var aspect = JsonParser.AspectFromJson(context, File.ReadAllText(file), fi.Name);
|
|
||||||
if (aspect != null)
|
|
||||||
{
|
{
|
||||||
aspects.Add(aspect);
|
Console.WriteLine($"[{aspect.Name}] WARNING: no tests found: please add {aspect.Name}.test.csv");
|
||||||
|
}
|
||||||
var testPath = fi.DirectoryName + "/" + aspect.Name + ".test.csv";
|
else
|
||||||
if (File.Exists(testPath))
|
{
|
||||||
{
|
t.Run();
|
||||||
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);
|
context.AddFunction(aspect.Name, aspect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var profilePath = "Profiles/bicycle/bicycle.json";
|
var profilePath = "Profiles/bicycle/bicycle.json";
|
||||||
var profile = JsonParser.ProfileFromJson(context, File.ReadAllText(profilePath), new FileInfo(profilePath));
|
var (profile, profileTests) = ParseProfile(profilePath, context);
|
||||||
|
|
||||||
var profileFi = new FileInfo(profilePath);
|
foreach (var test in profileTests)
|
||||||
var profileTests = new List<ProfileTestSuite>();
|
|
||||||
foreach (var profileName in profile.Profiles.Keys)
|
|
||||||
{
|
{
|
||||||
var testPath = profileFi.DirectoryName + "/" + profile.Name + "." + profileName + ".csv";
|
test.Run(context);
|
||||||
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'
|
var luaPrinter = GenerateLua(context, aspects, profile, profileTests);
|
||||||
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);
|
File.WriteAllText("output.lua", luaPrinter.ToLua());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,14 +1,16 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Functions;
|
using AspectedRouting.Language;
|
||||||
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Functions;
|
||||||
|
|
||||||
namespace AspectedRouting.IO
|
namespace AspectedRouting.Tests
|
||||||
{
|
{
|
||||||
public class FunctionTestSuite
|
public class FunctionTestSuite
|
||||||
{
|
{
|
||||||
private readonly AspectMetadata _functionToApply;
|
public readonly AspectMetadata FunctionToApply;
|
||||||
private readonly IEnumerable<(string expected, Dictionary<string, string> tags)> _tests;
|
public readonly IEnumerable<(string expected, Dictionary<string, string> tags)> Tests;
|
||||||
|
|
||||||
public static FunctionTestSuite FromString(AspectMetadata function, string csvContents)
|
public static FunctionTestSuite FromString(AspectMetadata function, string csvContents)
|
||||||
{
|
{
|
||||||
|
@ -47,47 +49,23 @@ namespace AspectedRouting.IO
|
||||||
AspectMetadata functionToApply,
|
AspectMetadata functionToApply,
|
||||||
IEnumerable<(string expected, Dictionary<string, string> tags)> tests)
|
IEnumerable<(string expected, Dictionary<string, string> tags)> tests)
|
||||||
{
|
{
|
||||||
_functionToApply = functionToApply;
|
|
||||||
_tests = tests;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (functionToApply == null)
|
||||||
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("#"))
|
throw new NullReferenceException("functionToApply is null");
|
||||||
{
|
|
||||||
parameters[key.TrimStart('#')] = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
FunctionToApply = functionToApply;
|
||||||
foreach (var (paramName, _) in parameters)
|
Tests = tests;
|
||||||
{
|
|
||||||
tags.Remove("#" + paramName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var funcName = _functionToApply.Name.Replace(" ", "_").Replace(".", "_");
|
|
||||||
return
|
|
||||||
$"unit_test({funcName}, \"{_functionToApply.Name}\", {index}, \"{expected}\", {parameters.ToLuaTable()}, {tags.ToLuaTable()})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
var failed = false;
|
var failed = false;
|
||||||
var testCase = 0;
|
var testCase = 0;
|
||||||
foreach (var test in _tests)
|
foreach (var test in Tests)
|
||||||
{
|
{
|
||||||
testCase++;
|
testCase++;
|
||||||
var context = new Context();
|
var context = new Context();
|
||||||
|
@ -99,14 +77,14 @@ namespace AspectedRouting.IO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var actual = _functionToApply.Evaluate(context, new Constant(test.tags));
|
var actual = FunctionToApply.Evaluate(context, new Constant(test.tags));
|
||||||
if (!actual.ToString().Equals(test.expected) &&
|
if (!actual.ToString().Equals(test.expected) &&
|
||||||
!(actual is double actualD && Math.Abs(double.Parse(test.expected) - actualD) < 0.0001)
|
!(actual is double actualD && Math.Abs(double.Parse(test.expected) - actualD) < 0.0001)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
failed = true;
|
failed = true;
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
$"[{_functionToApply.Name}] Testcase {testCase} failed:\n Expected: {test.expected}\n actual: {actual}\n tags: {test.tags.Pretty()}");
|
$"[{FunctionToApply.Name}] Testcase {testCase} failed:\n Expected: {test.expected}\n actual: {actual}\n tags: {test.tags.Pretty()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +93,7 @@ namespace AspectedRouting.IO
|
||||||
throw new ArgumentException("Some test failed");
|
throw new ArgumentException("Some test failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine($"[{_functionToApply.Name}] {testCase} tests successful");
|
Console.WriteLine($"[{FunctionToApply.Name}] {testCase} tests successful");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
299
AspectedRouting/Tests/ProfileTestSuite.cs
Normal file
299
AspectedRouting/Tests/ProfileTestSuite.cs
Normal file
|
@ -0,0 +1,299 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using AspectedRouting.Language;
|
||||||
|
using AspectedRouting.Language.Expression;
|
||||||
|
using AspectedRouting.Language.Functions;
|
||||||
|
using AspectedRouting.Language.Typ;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Tests
|
||||||
|
{
|
||||||
|
public struct Expected
|
||||||
|
{
|
||||||
|
public readonly string Access;
|
||||||
|
public readonly string Oneway;
|
||||||
|
public readonly double Speed;
|
||||||
|
public readonly double Weight;
|
||||||
|
|
||||||
|
public Expected(string access, string oneway, double speed, double weight)
|
||||||
|
{
|
||||||
|
Access = access;
|
||||||
|
Oneway = oneway;
|
||||||
|
Speed = speed;
|
||||||
|
Weight = weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProfileTestSuite
|
||||||
|
{
|
||||||
|
public readonly ProfileMetaData Profile;
|
||||||
|
public readonly string BehaviourName;
|
||||||
|
public readonly IEnumerable<(Expected, Dictionary<string, string> tags)> Tests;
|
||||||
|
|
||||||
|
public static ProfileTestSuite FromString(Context c, 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();
|
||||||
|
|
||||||
|
foreach (var k in keys)
|
||||||
|
{
|
||||||
|
if (k.StartsWith("_relations:"))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
"To inject relation memberships, use '_relation:<aspect_name>', without S after relation");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k.StartsWith("_relation:"))
|
||||||
|
{
|
||||||
|
var aspectName = k.Substring("_relation:".Length);
|
||||||
|
if (aspectName.Contains(":"))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
"To inject relation memberships, use '_relation:<aspect_name>', don't add the behaviour name");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!c.DefinedFunctions.ContainsKey(aspectName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"'_relation:<aspect_name>' detected, but the aspect {aspectName} wasn't found. Try one of: " +
|
||||||
|
string.Join(",", c.DefinedFunctions.Keys));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 speed = 0.0;
|
||||||
|
if (!string.IsNullOrEmpty(testData[2]))
|
||||||
|
{
|
||||||
|
speed = double.Parse(testData[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var weight = 0.0;
|
||||||
|
if (!string.IsNullOrEmpty(testData[3]))
|
||||||
|
{
|
||||||
|
weight = double.Parse(testData[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var expected = new Expected(
|
||||||
|
testData[0],
|
||||||
|
testData[1],
|
||||||
|
speed,
|
||||||
|
weight
|
||||||
|
);
|
||||||
|
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;
|
||||||
|
BehaviourName = profileName;
|
||||||
|
Tests = tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static bool Eq(Context c, string value, object result)
|
||||||
|
{
|
||||||
|
var v = Funcs.Eq.Apply(new Constant(value), new Constant(Typs.String, result));
|
||||||
|
|
||||||
|
var o = v.Evaluate(c);
|
||||||
|
return o is string s && s.Equals("yes");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RunTest(Context c, int i, Expected expected, Dictionary<string, string> tags)
|
||||||
|
{
|
||||||
|
c = new Context(c);
|
||||||
|
tags = new Dictionary<string, string>(tags);
|
||||||
|
|
||||||
|
void Err(string message, object exp, object act)
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[{Profile.Name}.{BehaviourName}]: Test {i} failed: {message}; expected {exp} but got {act}\n {{{tags.Pretty()}}}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = true;
|
||||||
|
var canAccess = Profile.Access.Run(c, tags);
|
||||||
|
tags["access"] = "" + canAccess;
|
||||||
|
|
||||||
|
if (!expected.Access.Equals(canAccess))
|
||||||
|
{
|
||||||
|
Err("access value incorrect", expected.Access, canAccess);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (expected.Access.Equals("no"))
|
||||||
|
{
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
var oneway = Profile.Oneway.Run(c, tags);
|
||||||
|
tags["oneway"] = "" + oneway;
|
||||||
|
|
||||||
|
if (!Eq(c, expected.Oneway, oneway))
|
||||||
|
{
|
||||||
|
Err("oneway value incorrect", expected.Oneway, oneway);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var speed = (double) Profile.Speed.Run(c, tags);
|
||||||
|
tags["speed"] = "" + speed;
|
||||||
|
|
||||||
|
c.AddFunction("speed", new AspectMetadata(new Constant(Typs.Double, speed),
|
||||||
|
"speed", "Actual speed of this function", "NA","NA","NA", true));
|
||||||
|
c.AddFunction("oneway", new AspectMetadata(new Constant(Typs.String, oneway),
|
||||||
|
"oneway", "Actual direction of this function", "NA","NA","NA", true));
|
||||||
|
c.AddFunction("access", new AspectMetadata(new Constant(Typs.String, canAccess),
|
||||||
|
"access", "Actual access of this function", "NA","NA","NA", true));
|
||||||
|
|
||||||
|
if (Math.Abs(speed - expected.Speed) > 0.0001)
|
||||||
|
{
|
||||||
|
Err("speed value incorrect", expected.Speed, speed);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var priority = 0.0;
|
||||||
|
foreach (var (paramName, expression) in Profile.Priority)
|
||||||
|
{
|
||||||
|
var aspectInfluence = (double) c.Parameters[paramName].Evaluate(c);
|
||||||
|
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||||
|
if (aspectInfluence == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var aspectWeightObj = new Apply(
|
||||||
|
Funcs.EitherFunc.Apply(Funcs.Id, Funcs.Const, expression)
|
||||||
|
, new Constant((tags))).Evaluate(c);
|
||||||
|
|
||||||
|
double aspectWeight;
|
||||||
|
switch (aspectWeightObj)
|
||||||
|
{
|
||||||
|
case bool b:
|
||||||
|
aspectWeight = b ? 1.0 : 0.0;
|
||||||
|
break;
|
||||||
|
case double d:
|
||||||
|
aspectWeight = d;
|
||||||
|
break;
|
||||||
|
case int j:
|
||||||
|
aspectWeight = j;
|
||||||
|
break;
|
||||||
|
case string s:
|
||||||
|
if (s.Equals("yes"))
|
||||||
|
{
|
||||||
|
aspectWeight = 1.0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (s.Equals("no"))
|
||||||
|
{
|
||||||
|
aspectWeight = 0.0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception($"Invalid value as result for {paramName}: got string {s}");
|
||||||
|
default:
|
||||||
|
throw new Exception($"Invalid value as result for {paramName}: got object {aspectWeightObj}");
|
||||||
|
}
|
||||||
|
|
||||||
|
priority += aspectInfluence * aspectWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.Abs(priority - expected.Weight) > 0.0001)
|
||||||
|
{
|
||||||
|
Err("weight incorrect", expected.Weight, priority);
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run(Context c)
|
||||||
|
|
||||||
|
{
|
||||||
|
var parameters = new Dictionary<string, IExpression>();
|
||||||
|
|
||||||
|
foreach (var (k, v) in Profile.DefaultParameters)
|
||||||
|
{
|
||||||
|
parameters[k] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (k, v) in Profile.Behaviours[BehaviourName])
|
||||||
|
{
|
||||||
|
parameters[k] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = c.WithParameters(parameters);
|
||||||
|
|
||||||
|
var allOk = true;
|
||||||
|
var i = 1;
|
||||||
|
foreach (var (expected, tags) in Tests)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
allOk &= RunTest(c, i, expected, tags);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception("In a test for " + BehaviourName, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allOk)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Some tests failed for " + BehaviourName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[{BehaviourName}] {Tests.Count()} tests successful");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,5 +19,7 @@ namespace AspectedRouting
|
||||||
|
|
||||||
return factor;
|
return factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue