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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Functions;
|
||||
using AspectedRouting.Typ;
|
||||
using AspectedRouting.Language;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
|
||||
namespace AspectedRouting.IO
|
||||
{
|
||||
|
@ -32,7 +32,7 @@ namespace AspectedRouting.IO
|
|||
get
|
||||
{
|
||||
var txt = "## Builtin functions\n\n";
|
||||
foreach (var biFunc in Functions.Funcs.BuiltinNames)
|
||||
foreach (var biFunc in Funcs.BuiltinNames)
|
||||
{
|
||||
txt += "- " + biFunc + "\n";
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ namespace AspectedRouting.IO
|
|||
{
|
||||
var args = f.ArgBreakdown();
|
||||
var header = "Argument name | ";
|
||||
var line = "--------------| - ";
|
||||
var line = "-------------- | - ";
|
||||
for (int i = 0; i < f.Types.Count(); i++)
|
||||
{
|
||||
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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using AspectedRouting.Functions;
|
||||
using AspectedRouting.Typ;
|
||||
using static AspectedRouting.Functions.Funcs;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.IO
|
||||
{
|
||||
public static class JsonParser
|
||||
{
|
||||
public static AspectMetadata AspectFromJson(Context c, string json, string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var doc = JsonDocument.Parse(json);
|
||||
if (doc.RootElement.TryGetProperty("defaults", out _))
|
||||
{
|
||||
// this is a profile
|
||||
return null;
|
||||
}
|
||||
|
||||
return doc.RootElement.ParseAspect(fileName, c);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("In the file " + fileName, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ProfileMetaData ProfileFromJson(Context c, string json, FileInfo f)
|
||||
{
|
||||
try
|
||||
{
|
||||
var doc = JsonDocument.Parse(json);
|
||||
if (!doc.RootElement.TryGetProperty("defaults", out _))
|
||||
{
|
||||
return null;
|
||||
// this is an aspect
|
||||
}
|
||||
|
||||
return ParseProfile(doc.RootElement, c, f);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("In the file " + f, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static readonly IExpression _mconst =
|
||||
new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.Const);
|
||||
|
||||
private static readonly IExpression _mappingWrapper =
|
||||
new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), StringStringToTags);
|
||||
|
||||
private static IExpression ParseMapping(IEnumerable<JsonProperty> allArgs, Context context)
|
||||
{
|
||||
var keys = new List<string>();
|
||||
var exprs = new List<IExpression>();
|
||||
|
||||
foreach (var prop in allArgs)
|
||||
{
|
||||
if (prop.Name.Equals("#"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
keys.Add(prop.Name);
|
||||
var argExpr = ParseExpression(prop.Value, context);
|
||||
IExpression mappingWithOptArg;
|
||||
if (argExpr.Types.Count() == 1 && argExpr.Types.First().Equals(Typs.String))
|
||||
{
|
||||
mappingWithOptArg =
|
||||
Either(Funcs.Id, Funcs.Eq, argExpr);
|
||||
}
|
||||
else
|
||||
{
|
||||
mappingWithOptArg = new Apply(_mconst, argExpr);
|
||||
}
|
||||
|
||||
exprs.Add(mappingWithOptArg);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var simpleMapping = new Mapping(keys, exprs);
|
||||
return new Apply(_mappingWrapper, simpleMapping);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("While constructing a mapping with members " + string.Join(", ", exprs) +
|
||||
": " + e.Message, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static IExpression ParseExpression(this JsonElement e, Context context)
|
||||
{
|
||||
if (e.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
// Parse an actual function
|
||||
var funcCall = e.EnumerateObject().Where(v => v.Name.StartsWith("$")).ToList();
|
||||
var allArgs = e.EnumerateObject().Where(v => !v.Name.StartsWith("$")).ToList();
|
||||
|
||||
if (funcCall.Count > 2)
|
||||
{
|
||||
throw new ArgumentException("Multiple calls defined in object " + e);
|
||||
}
|
||||
|
||||
if (funcCall.Count == 1)
|
||||
{
|
||||
return ParseFunctionCall(context, funcCall, allArgs);
|
||||
}
|
||||
|
||||
// Funccall has no elements: this is a mapping of strings or tags onto a value
|
||||
|
||||
return ParseMapping(allArgs, context);
|
||||
}
|
||||
|
||||
if (e.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
var exprs = e.EnumerateArray().Select(json =>
|
||||
Either(Funcs.Id, Funcs.Const, json.ParseExpression(context)));
|
||||
var list = new Constant(exprs);
|
||||
return Either(Funcs.Id, Funcs.ListDot, list);
|
||||
}
|
||||
|
||||
if (e.ValueKind == JsonValueKind.Number)
|
||||
{
|
||||
if (e.TryGetDouble(out var d))
|
||||
{
|
||||
return new Constant(d);
|
||||
}
|
||||
|
||||
if (e.TryGetInt32(out var i))
|
||||
{
|
||||
return new Constant(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.ValueKind == JsonValueKind.True)
|
||||
{
|
||||
return new Constant(Typs.Bool, "yes");
|
||||
}
|
||||
|
||||
if (e.ValueKind == JsonValueKind.False)
|
||||
{
|
||||
return new Constant(Typs.Bool, "no");
|
||||
}
|
||||
|
||||
if (e.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var s = e.GetString();
|
||||
if (s.StartsWith("$"))
|
||||
{
|
||||
var bi = BuiltinByName(s);
|
||||
|
||||
if (bi != null)
|
||||
{
|
||||
return Either(Funcs.Dot, Funcs.Id, bi);
|
||||
}
|
||||
|
||||
var definedFunc = context.GetFunction(s);
|
||||
return Either(Funcs.Dot, Funcs.Id, new FunctionCall(s, definedFunc.Types));
|
||||
}
|
||||
|
||||
if (s.StartsWith("#"))
|
||||
{
|
||||
// This is a parameter, the type of it is free
|
||||
return new Parameter(s);
|
||||
}
|
||||
|
||||
return new Constant(s);
|
||||
}
|
||||
|
||||
|
||||
throw new Exception("Could not parse " + e);
|
||||
}
|
||||
|
||||
private static IExpression ParseFunctionCall(Context context, IReadOnlyCollection<JsonProperty> funcCall,
|
||||
IEnumerable<JsonProperty> allArgs)
|
||||
{
|
||||
var funcName = funcCall.First().Name;
|
||||
|
||||
var func = BuiltinByName(funcName);
|
||||
|
||||
// The list where all the arguments are collected
|
||||
var args = new List<IExpression>();
|
||||
|
||||
|
||||
// First argument of the function is the value of this property, e.g.
|
||||
// { "$f": "xxx", "a2":"yyy", "a3":"zzz" }
|
||||
var firstArgument = ParseExpression(funcCall.First().Value, context);
|
||||
|
||||
|
||||
// Cheat for the very special case 'mustMatch'
|
||||
if (func.Equals(Funcs.MustMatch))
|
||||
{
|
||||
// It gets an extra argument injected
|
||||
var neededKeys = firstArgument.PossibleTags().Keys.ToList();
|
||||
var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
|
||||
args.Add(neededKeysArg);
|
||||
args.Add(firstArgument);
|
||||
return func.Apply(args);
|
||||
}
|
||||
|
||||
args.Add(firstArgument);
|
||||
|
||||
var allExprs = allArgs
|
||||
.Where(kv => !kv.NameEquals("#")) // Leave out comments
|
||||
.ToDictionary(kv => kv.Name, kv => kv.Value.ParseExpression(context));
|
||||
|
||||
|
||||
if (allExprs.Count > 1)
|
||||
{
|
||||
if (func.ArgNames == null || func.ArgNames.Count < 2)
|
||||
throw new ArgumentException("{funcName} does not specify argument names");
|
||||
|
||||
foreach (var argName in func.ArgNames)
|
||||
{
|
||||
args.Add(allExprs[argName]);
|
||||
}
|
||||
}
|
||||
else if (allExprs.Count == 1)
|
||||
{
|
||||
args.Add(allExprs.Single().Value);
|
||||
}
|
||||
|
||||
return Either(Funcs.Id, Funcs.Dot, func).Apply(args);
|
||||
}
|
||||
|
||||
private static IExpression GetTopLevelExpression(this JsonElement root, Context context)
|
||||
{
|
||||
IExpression mapping = null;
|
||||
if (root.TryGetProperty("value", out var j))
|
||||
{
|
||||
// The expression is placed in the default 'value' location
|
||||
mapping = j.ParseExpression(context);
|
||||
}
|
||||
|
||||
|
||||
// We search for the function call with '$'
|
||||
foreach (var prop in root.EnumerateObject())
|
||||
{
|
||||
if (!prop.Name.StartsWith("$")) continue;
|
||||
|
||||
|
||||
var f = (IExpression) BuiltinByName(prop.Name);
|
||||
if (f == null)
|
||||
{
|
||||
throw new KeyNotFoundException("The builtin function " + f + " was not found");
|
||||
}
|
||||
|
||||
var fArg = prop.Value.ParseExpression(context);
|
||||
|
||||
if (fArg == null)
|
||||
{
|
||||
throw new ArgumentException("Could not type expression " + prop);
|
||||
}
|
||||
|
||||
|
||||
if (mapping != null)
|
||||
{
|
||||
// This is probably a firstOrderedVersion, a default, or some other function that should be applied
|
||||
return
|
||||
new Apply(
|
||||
Either(Funcs.Id, Funcs.Dot, new Apply(f, fArg)), mapping
|
||||
);
|
||||
}
|
||||
|
||||
// Cheat for the very special case 'mustMatch'
|
||||
if (f.Equals(Funcs.MustMatch))
|
||||
{
|
||||
// It gets an extra argument injected
|
||||
var neededKeys = fArg.PossibleTags().Keys.ToList();
|
||||
var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
|
||||
f = f.Apply(new[] {neededKeysArg});
|
||||
}
|
||||
|
||||
var appliedDot = new Apply(new Apply(Funcs.Dot, f), fArg);
|
||||
var appliedDirect = new Apply(f, fArg);
|
||||
|
||||
if (!appliedDot.Types.Any())
|
||||
{
|
||||
// Applied dot doesn't work out, so we return the other one
|
||||
return appliedDirect;
|
||||
}
|
||||
|
||||
if (!appliedDirect.Types.Any())
|
||||
{
|
||||
return appliedDot;
|
||||
}
|
||||
|
||||
var eithered = new Apply(new Apply(Funcs.EitherFunc, appliedDot), appliedDirect);
|
||||
|
||||
|
||||
// We apply the builtin function through a dot
|
||||
return eithered;
|
||||
}
|
||||
|
||||
|
||||
throw new Exception(
|
||||
"No top level reducer found. Did you forget the '$' in the reducing function? Did your forget 'value' to add the mapping?");
|
||||
}
|
||||
|
||||
private static IExpression ParseProfileProperty(JsonElement e, Context c, string property)
|
||||
{
|
||||
try
|
||||
{
|
||||
var prop = e.GetProperty(property);
|
||||
return ParseExpression(prop, c)
|
||||
.Specialize(new Curry(Typs.Tags, new Var("a")))
|
||||
.Optimize();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new Exception("While parsing the property " + property, exc);
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, object> ParseParameters(this JsonElement e)
|
||||
{
|
||||
var ps = new Dictionary<string, object>();
|
||||
foreach (var obj in e.EnumerateObject())
|
||||
{
|
||||
var nm = obj.Name.TrimStart('#');
|
||||
switch (obj.Value.ValueKind)
|
||||
{
|
||||
case JsonValueKind.String:
|
||||
ps[nm] = obj.Value.ToString();
|
||||
break;
|
||||
case JsonValueKind.Number:
|
||||
ps[nm] = obj.Value.GetDouble();
|
||||
break;
|
||||
case JsonValueKind.True:
|
||||
ps[nm] = "yes";
|
||||
break;
|
||||
case JsonValueKind.False:
|
||||
ps[nm] = "no";
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
public static ProfileMetaData ParseProfile(this JsonElement e, Context context, FileInfo filepath)
|
||||
{
|
||||
var name = e.Get("name");
|
||||
var author = e.TryGet("author");
|
||||
if (filepath != null && !(name + ".json").ToLower().Equals(filepath.Name.ToLower()))
|
||||
{
|
||||
throw new ArgumentException($"Filename does not match the defined name: " +
|
||||
$"filename is {filepath.Name}, declared name is {name}");
|
||||
}
|
||||
|
||||
var vehicleTypes = e.GetProperty("vehicletypes").EnumerateArray().Select(
|
||||
el => el.GetString()).ToList();
|
||||
var metadata = e.GetProperty("metadata").EnumerateArray().Select(
|
||||
el => el.GetString()).ToList();
|
||||
|
||||
|
||||
var access = ParseProfileProperty(e, context, "access").Finalize();
|
||||
var oneway = ParseProfileProperty(e, context, "oneway").Finalize();
|
||||
var speed = ParseProfileProperty(e, context, "speed").Finalize();
|
||||
|
||||
|
||||
IExpression TagsApplied(IExpression x)
|
||||
{
|
||||
return new Apply(x, new Constant(new Dictionary<string, string>()));
|
||||
}
|
||||
|
||||
context.AddFunction("speed",
|
||||
new AspectMetadata(TagsApplied(speed), "speed", "The speed of this profile", author, "", filepath.Name,
|
||||
true));
|
||||
context.AddFunction("access",
|
||||
new AspectMetadata(TagsApplied(access), "access", "The access of this profile", author, "",
|
||||
filepath.Name,
|
||||
true));
|
||||
context.AddFunction("oneway",
|
||||
new AspectMetadata(TagsApplied(oneway), "oneway", "The oneway of this profile", author, "",
|
||||
filepath.Name,
|
||||
true));
|
||||
context.AddFunction("distance",
|
||||
new AspectMetadata(new Constant(1), "distance", "The distance travelled of this profile", author, "",
|
||||
filepath.Name,
|
||||
true));
|
||||
|
||||
|
||||
var weights = new Dictionary<string, IExpression>();
|
||||
var weightProperty = e.GetProperty("weight");
|
||||
foreach (var prop in weightProperty.EnumerateObject())
|
||||
{
|
||||
var parameter = prop.Name.TrimStart('#');
|
||||
var factor = ParseExpression(prop.Value, context).Finalize();
|
||||
weights[parameter] = factor;
|
||||
}
|
||||
|
||||
var profiles = new Dictionary<string, Dictionary<string, object>>();
|
||||
|
||||
foreach (var profile in e.GetProperty("profiles").EnumerateObject())
|
||||
{
|
||||
profiles[profile.Name] = ParseParameters(profile.Value);
|
||||
}
|
||||
|
||||
return new ProfileMetaData(
|
||||
name,
|
||||
e.Get("description"),
|
||||
author,
|
||||
filepath?.DirectoryName ?? "unknown",
|
||||
vehicleTypes,
|
||||
e.GetProperty("defaults").ParseParameters(),
|
||||
profiles,
|
||||
access,
|
||||
oneway,
|
||||
speed,
|
||||
weights,
|
||||
metadata
|
||||
);
|
||||
}
|
||||
|
||||
private static AspectMetadata ParseAspect(this JsonElement e, string filepath, Context context)
|
||||
{
|
||||
var expr = GetTopLevelExpression(e, context);
|
||||
|
||||
|
||||
var targetTypes = new List<Type>();
|
||||
foreach (var t in expr.Types)
|
||||
{
|
||||
var a = Var.Fresh(t);
|
||||
var b = Var.Fresh(new Curry(a, t));
|
||||
|
||||
if (t.Unify(new Curry(Typs.Tags, a)) != null &&
|
||||
t.Unify(new Curry(Typs.Tags, new Curry(a, b))) == null
|
||||
) // Second should not match
|
||||
{
|
||||
// The target type is 'Tags -> a', where a is NOT a curry
|
||||
targetTypes.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetTypes.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("The top level expression has types:\n" +
|
||||
string.Join("\n ", expr.Types) +
|
||||
"\nwhich can not be specialized into a form suiting `tags -> a`\n" + expr);
|
||||
}
|
||||
|
||||
var exprSpec = expr.Specialize(targetTypes);
|
||||
if (expr == null)
|
||||
{
|
||||
throw new Exception("Could not specialize the expression " + expr + " to one of the target types " +
|
||||
string.Join(", ", targetTypes));
|
||||
}
|
||||
|
||||
expr = exprSpec.Finalize();
|
||||
|
||||
if (expr.Finalize() == null)
|
||||
{
|
||||
throw new NullReferenceException("The finalized form of expression `" + exprSpec + "` is null");
|
||||
}
|
||||
|
||||
var name = e.Get("name");
|
||||
if (expr.Types.Count() > 1)
|
||||
{
|
||||
throw new ArgumentException("The aspect " + name + " is ambigous, it matches multiple types: " +
|
||||
string.Join(", ", expr.Types));
|
||||
}
|
||||
|
||||
if (filepath != null && !(name + ".json").ToLower().Equals(filepath.ToLower()))
|
||||
{
|
||||
throw new ArgumentException($"Filename does not match the defined name: " +
|
||||
$"filename is {filepath}, declared name is {name}");
|
||||
}
|
||||
|
||||
return new AspectMetadata(
|
||||
expr,
|
||||
name,
|
||||
e.Get("description"),
|
||||
e.TryGet("author"),
|
||||
e.TryGet("unit"),
|
||||
filepath ?? "unknown"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private static string Get(this JsonElement json, string key)
|
||||
{
|
||||
if (json.TryGetProperty(key, out var p))
|
||||
{
|
||||
return p.GetString();
|
||||
}
|
||||
|
||||
throw new ArgumentException($"The obligated property {key} is missing");
|
||||
}
|
||||
|
||||
private static string TryGet(this JsonElement json, string key)
|
||||
{
|
||||
if (json.TryGetProperty(key, out var p))
|
||||
{
|
||||
return p.GetString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
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;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.IO.jsonParser
|
||||
{
|
||||
public static partial class JsonParser
|
||||
{
|
||||
public static AspectMetadata AspectFromJson(Context c, string json, string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var doc = JsonDocument.Parse(json);
|
||||
if (doc.RootElement.TryGetProperty("defaults", out _))
|
||||
{
|
||||
// this is a profile
|
||||
return null;
|
||||
}
|
||||
|
||||
return doc.RootElement.ParseAspect(fileName, c);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("In the file " + fileName, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ProfileMetaData ProfileFromJson(Context c, string json, FileInfo f)
|
||||
{
|
||||
try
|
||||
{
|
||||
var doc = JsonDocument.Parse(json);
|
||||
if (!doc.RootElement.TryGetProperty("defaults", out _))
|
||||
{
|
||||
return null;
|
||||
// this is an aspect
|
||||
}
|
||||
|
||||
return ParseProfile(doc.RootElement, c, f);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("In the file " + f, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static ProfileMetaData ParseProfile(this JsonElement e, Context context, FileInfo filepath)
|
||||
{
|
||||
var name = e.Get("name");
|
||||
var author = e.TryGet("author");
|
||||
if (filepath != null && !(name + ".json").ToLower().Equals(filepath.Name.ToLower()))
|
||||
{
|
||||
throw new ArgumentException($"Filename does not match the defined name: " +
|
||||
$"filename is {filepath.Name}, declared name is {name}");
|
||||
}
|
||||
|
||||
var vehicleTypes = e.GetProperty("vehicletypes").EnumerateArray().Select(
|
||||
el => el.GetString()).ToList();
|
||||
var metadata = e.GetProperty("metadata").EnumerateArray().Select(
|
||||
el => el.GetString()).ToList();
|
||||
|
||||
|
||||
var access = ParseProfileProperty(e, context, "access").Finalize();
|
||||
var oneway = ParseProfileProperty(e, context, "oneway").Finalize();
|
||||
var speed = ParseProfileProperty(e, context, "speed").Finalize();
|
||||
|
||||
|
||||
IExpression TagsApplied(IExpression x)
|
||||
{
|
||||
return new Apply(x, new Constant(new Dictionary<string, string>()));
|
||||
}
|
||||
|
||||
context.AddFunction("speed",
|
||||
new AspectMetadata(TagsApplied(speed), "speed", "The speed of this profile", author, "", filepath.Name,
|
||||
true));
|
||||
context.AddFunction("access",
|
||||
new AspectMetadata(TagsApplied(access), "access", "The access of this profile", author, "",
|
||||
filepath.Name,
|
||||
true));
|
||||
context.AddFunction("oneway",
|
||||
new AspectMetadata(TagsApplied(oneway), "oneway", "The oneway of this profile", author, "",
|
||||
filepath.Name,
|
||||
true));
|
||||
context.AddFunction("distance",
|
||||
new AspectMetadata(new Constant(1), "distance", "The distance travelled of this profile", author, "",
|
||||
filepath.Name,
|
||||
true));
|
||||
|
||||
|
||||
var weights = new Dictionary<string, IExpression>();
|
||||
var weightProperty = e.GetProperty("priority");
|
||||
foreach (var prop in weightProperty.EnumerateObject())
|
||||
{
|
||||
var parameter = prop.Name.TrimStart('#');
|
||||
var factor = ParseExpression(prop.Value, context).Finalize();
|
||||
weights[parameter] = factor;
|
||||
}
|
||||
|
||||
var profiles = new Dictionary<string, Dictionary<string, IExpression>>();
|
||||
|
||||
foreach (var profile in e.GetProperty("behaviours").EnumerateObject())
|
||||
{
|
||||
profiles[profile.Name] = ParseParameters(profile.Value);
|
||||
}
|
||||
|
||||
return new ProfileMetaData(
|
||||
name,
|
||||
e.Get("description"),
|
||||
author,
|
||||
filepath?.DirectoryName ?? "unknown",
|
||||
vehicleTypes,
|
||||
e.GetProperty("defaults").ParseParameters(),
|
||||
profiles,
|
||||
access,
|
||||
oneway,
|
||||
speed,
|
||||
weights,
|
||||
metadata
|
||||
);
|
||||
}
|
||||
|
||||
private static readonly IExpression _mconst =
|
||||
new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.Const);
|
||||
|
||||
private static readonly IExpression _mappingWrapper =
|
||||
new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.StringStringToTags);
|
||||
|
||||
private static IExpression ParseMapping(IEnumerable<JsonProperty> allArgs, Context context)
|
||||
{
|
||||
var keys = new List<string>();
|
||||
var exprs = new List<IExpression>();
|
||||
|
||||
foreach (var prop in allArgs)
|
||||
{
|
||||
if (prop.Name.Equals("#"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
keys.Add(prop.Name);
|
||||
var argExpr = ParseExpression(prop.Value, context);
|
||||
IExpression mappingWithOptArg;
|
||||
if (argExpr.Types.Count() == 1 && argExpr.Types.First().Equals(Typs.String))
|
||||
{
|
||||
mappingWithOptArg =
|
||||
Funcs.Either(Funcs.Id, Funcs.Eq, argExpr);
|
||||
}
|
||||
else
|
||||
{
|
||||
mappingWithOptArg = new Apply(_mconst, argExpr);
|
||||
}
|
||||
|
||||
exprs.Add(mappingWithOptArg);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var simpleMapping = new Mapping(keys, exprs);
|
||||
return new Apply(_mappingWrapper, simpleMapping);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("While constructing a mapping with members " + string.Join(", ", exprs) +
|
||||
": " + e.Message, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static IExpression ParseExpression(this JsonElement e, Context context)
|
||||
{
|
||||
if (e.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
// Parse an actual function
|
||||
var funcCall = e.EnumerateObject().Where(v => v.Name.StartsWith("$")).ToList();
|
||||
var allArgs = e.EnumerateObject().Where(v => !v.Name.StartsWith("$")).ToList();
|
||||
|
||||
if (funcCall.Count > 2)
|
||||
{
|
||||
throw new ArgumentException("Multiple calls defined in object " + e);
|
||||
}
|
||||
|
||||
if (funcCall.Count == 1)
|
||||
{
|
||||
return ParseFunctionCall(context, funcCall, allArgs);
|
||||
}
|
||||
|
||||
// Funccall has no elements: this is a mapping of strings or tags onto a value
|
||||
|
||||
return ParseMapping(allArgs, context);
|
||||
}
|
||||
|
||||
if (e.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
var exprs = e.EnumerateArray().Select(json =>
|
||||
Funcs.Either(Funcs.Id, Funcs.Const, json.ParseExpression(context)));
|
||||
var list = new Constant(exprs);
|
||||
return Funcs.Either(Funcs.Id, Funcs.ListDot, list);
|
||||
}
|
||||
|
||||
if (e.ValueKind == JsonValueKind.Number)
|
||||
{
|
||||
if (e.TryGetDouble(out var d))
|
||||
{
|
||||
return new Constant(d);
|
||||
}
|
||||
|
||||
if (e.TryGetInt32(out var i))
|
||||
{
|
||||
return new Constant(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.ValueKind == JsonValueKind.True)
|
||||
{
|
||||
return new Constant(Typs.Bool, "yes");
|
||||
}
|
||||
|
||||
if (e.ValueKind == JsonValueKind.False)
|
||||
{
|
||||
return new Constant(Typs.Bool, "no");
|
||||
}
|
||||
|
||||
if (e.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
var s = e.GetString();
|
||||
if (s.StartsWith("$"))
|
||||
{
|
||||
var bi = Funcs.BuiltinByName(s);
|
||||
|
||||
if (bi != null)
|
||||
{
|
||||
return Funcs.Either(Funcs.Dot, Funcs.Id, bi);
|
||||
}
|
||||
|
||||
var definedFunc = context.GetFunction(s);
|
||||
return Funcs.Either(Funcs.Dot, Funcs.Id, new FunctionCall(s, definedFunc.Types));
|
||||
}
|
||||
|
||||
if (s.StartsWith("#"))
|
||||
{
|
||||
// This is a parameter, the type of it is free
|
||||
return new Parameter(s);
|
||||
}
|
||||
|
||||
return new Constant(s);
|
||||
}
|
||||
|
||||
|
||||
throw new Exception("Could not parse " + e);
|
||||
}
|
||||
|
||||
private static IExpression ParseFunctionCall(Context context, IReadOnlyCollection<JsonProperty> funcCall,
|
||||
IEnumerable<JsonProperty> allArgs)
|
||||
{
|
||||
var funcName = funcCall.First().Name;
|
||||
|
||||
var func = Funcs.BuiltinByName(funcName);
|
||||
|
||||
if (func == null)
|
||||
{
|
||||
throw new ArgumentException($"The function with name {funcName} is not found");
|
||||
}
|
||||
|
||||
// The list where all the arguments are collected
|
||||
var args = new List<IExpression>();
|
||||
|
||||
|
||||
// First argument of the function is the value of this property, e.g.
|
||||
// { "$f": "xxx", "a2":"yyy", "a3":"zzz" }
|
||||
var firstArgument = ParseExpression(funcCall.First().Value, context);
|
||||
|
||||
|
||||
// Cheat for the very special case 'mustMatch'
|
||||
if (func.Equals(Funcs.MustMatch))
|
||||
{
|
||||
// It gets an extra argument injected
|
||||
var neededKeys = firstArgument.PossibleTags().Keys.ToList();
|
||||
var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
|
||||
args.Add(neededKeysArg);
|
||||
args.Add(firstArgument);
|
||||
return func.Apply(args);
|
||||
}
|
||||
|
||||
args.Add(firstArgument);
|
||||
|
||||
var allExprs = allArgs
|
||||
.Where(kv => !kv.NameEquals("#")) // Leave out comments
|
||||
.ToDictionary(kv => kv.Name, kv => kv.Value.ParseExpression(context));
|
||||
|
||||
|
||||
if (allExprs.Count > 1)
|
||||
{
|
||||
if (func.ArgNames == null || func.ArgNames.Count < 2)
|
||||
throw new ArgumentException("{funcName} does not specify argument names");
|
||||
|
||||
foreach (var argName in func.ArgNames)
|
||||
{
|
||||
args.Add(allExprs[argName]);
|
||||
}
|
||||
}
|
||||
else if (allExprs.Count == 1)
|
||||
{
|
||||
args.Add(allExprs.Single().Value);
|
||||
}
|
||||
|
||||
return Funcs.Either(Funcs.Id, Funcs.Dot, func).Apply(args);
|
||||
}
|
||||
|
||||
|
||||
private static IExpression GetTopLevelExpression(this JsonElement root, Context context)
|
||||
{
|
||||
IExpression mapping = null;
|
||||
if (root.TryGetProperty("value", out var j))
|
||||
{
|
||||
// The expression is placed in the default 'value' location
|
||||
mapping = j.ParseExpression(context);
|
||||
}
|
||||
|
||||
|
||||
// We search for the function call with '$'
|
||||
foreach (var prop in root.EnumerateObject())
|
||||
{
|
||||
if (!prop.Name.StartsWith("$")) continue;
|
||||
|
||||
|
||||
var f = (IExpression) Funcs.BuiltinByName(prop.Name);
|
||||
if (f == null)
|
||||
{
|
||||
throw new KeyNotFoundException($"The builtin function {prop.Name} was not found");
|
||||
}
|
||||
|
||||
var fArg = prop.Value.ParseExpression(context);
|
||||
|
||||
if (fArg == null)
|
||||
{
|
||||
throw new ArgumentException("Could not type expression " + prop);
|
||||
}
|
||||
|
||||
|
||||
if (mapping != null)
|
||||
{
|
||||
// This is probably a firstOrderedVersion, a default, or some other function that should be applied
|
||||
return
|
||||
new Apply(
|
||||
Funcs.Either(Funcs.Id, Funcs.Dot, new Apply(f, fArg)), mapping
|
||||
);
|
||||
}
|
||||
|
||||
// Cheat for the very special case 'mustMatch'
|
||||
if (f.Equals(Funcs.MustMatch))
|
||||
{
|
||||
// It gets an extra argument injected
|
||||
var neededKeys = fArg.PossibleTags().Keys.ToList();
|
||||
var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
|
||||
f = f.Apply(new[] {neededKeysArg});
|
||||
}
|
||||
|
||||
var appliedDot = new Apply(new Apply(Funcs.Dot, f), fArg);
|
||||
var appliedDirect = new Apply(f, fArg);
|
||||
|
||||
if (!appliedDot.Types.Any())
|
||||
{
|
||||
// Applied dot doesn't work out, so we return the other one
|
||||
return appliedDirect;
|
||||
}
|
||||
|
||||
if (!appliedDirect.Types.Any())
|
||||
{
|
||||
return appliedDot;
|
||||
}
|
||||
|
||||
var eithered = new Apply(new Apply(Funcs.EitherFunc, appliedDot), appliedDirect);
|
||||
|
||||
|
||||
// We apply the builtin function through a dot
|
||||
return eithered;
|
||||
}
|
||||
|
||||
|
||||
throw new Exception(
|
||||
"No top level reducer found. Did you forget the '$' in the reducing function? Did your forget 'value' to add the mapping?");
|
||||
}
|
||||
|
||||
private static AspectMetadata ParseAspect(this JsonElement e, string filepath, Context context)
|
||||
{
|
||||
var expr = GetTopLevelExpression(e, context);
|
||||
|
||||
|
||||
var targetTypes = new List<Type>();
|
||||
foreach (var t in expr.Types)
|
||||
{
|
||||
var a = Var.Fresh(t);
|
||||
var b = Var.Fresh(new Curry(a, t));
|
||||
|
||||
if (t.Unify(new Curry(Typs.Tags, a)) != null &&
|
||||
t.Unify(new Curry(Typs.Tags, new Curry(a, b))) == null
|
||||
) // Second should not match
|
||||
{
|
||||
// The target type is 'Tags -> a', where a is NOT a curry
|
||||
targetTypes.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetTypes.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("The top level expression has types:\n" +
|
||||
string.Join("\n ", expr.Types) +
|
||||
"\nwhich can not be specialized into a form suiting `tags -> a`\n" + expr);
|
||||
}
|
||||
|
||||
var exprSpec = expr.Specialize(targetTypes);
|
||||
if (expr == null)
|
||||
{
|
||||
throw new Exception("Could not specialize the expression " + expr + " to one of the target types " +
|
||||
string.Join(", ", targetTypes));
|
||||
}
|
||||
|
||||
expr = exprSpec.Finalize();
|
||||
|
||||
if (expr.Finalize() == null)
|
||||
{
|
||||
throw new NullReferenceException("The finalized form of expression `" + exprSpec + "` is null");
|
||||
}
|
||||
|
||||
var name = e.Get("name");
|
||||
if (expr.Types.Count() > 1)
|
||||
{
|
||||
throw new ArgumentException("The aspect " + name + " is ambigous, it matches multiple types: " +
|
||||
string.Join(", ", expr.Types));
|
||||
}
|
||||
|
||||
if (filepath != null && !(name + ".json").ToLower().Equals(filepath.ToLower()))
|
||||
{
|
||||
throw new ArgumentException($"Filename does not match the defined name: " +
|
||||
$"filename is {filepath}, declared name is {name}");
|
||||
}
|
||||
|
||||
var keys = (IEnumerable<string>) expr.PossibleTags()?.Keys ?? new List<string>();
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (!key.Trim().Equals(key))
|
||||
{
|
||||
Console.WriteLine($"Warning: a key can be trimmed: '{key}'");
|
||||
}
|
||||
}
|
||||
|
||||
return new AspectMetadata(
|
||||
expr,
|
||||
name,
|
||||
e.Get("description"),
|
||||
e.TryGet("author"),
|
||||
e.TryGet("unit"),
|
||||
filepath ?? "unknown"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
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)
|
||||
if (b == nil) then
|
||||
return false
|
||||
end
|
||||
|
||||
if (type(a) ~= "number") then
|
||||
a = parse(a)
|
||||
end
|
||||
|
@ -6,5 +10,9 @@ function double_compare(a, b)
|
|||
if(type(b) ~= "number") then
|
||||
b = parse(b)
|
||||
end
|
||||
return math.abs(a - b) > 0.001
|
||||
if (a == b) then
|
||||
return true
|
||||
end
|
||||
|
||||
return math.abs(a - b) < 0.0001
|
||||
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
|
|
@ -1,5 +1,4 @@
|
|||
function must_match(tags, result, needed_keys, table)
|
||||
local result_list = {}
|
||||
for _, key in ipairs(needed_keys) do
|
||||
local v = tags[key]
|
||||
if (v == nil) then
|
||||
|
@ -9,17 +8,30 @@ function must_match(tags, result, needed_keys, table)
|
|||
local mapping = table[key]
|
||||
if (type(mapping) == "table") then
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
result.attributes_to_keep[key] = v
|
||||
error("MustMatch got a string value it can't handle: " .. bool)
|
||||
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
|
||||
|
||||
-- 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;
|
||||
end
|
|
@ -1,4 +1,8 @@
|
|||
function notEq(a, b)
|
||||
if (b == nil) then
|
||||
b = "yes"
|
||||
end
|
||||
|
||||
if (a ~= b) then
|
||||
return "yes"
|
||||
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'
|
||||
]]
|
||||
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)
|
||||
|
||||
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)
|
||||
profile_failed = true
|
||||
failed_profile_tests = true
|
||||
end
|
||||
|
||||
if (result.access == 0) then
|
||||
if (expected.access == "no") then
|
||||
-- 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
|
||||
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)
|
||||
failed_profile_tests = true
|
||||
profile_failed = true
|
||||
end
|
||||
|
||||
if (double_compare(result.oneway, expected.oneway)) then
|
||||
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but got " .. result.oneway)
|
||||
failed_profile_tests = true
|
||||
|
||||
local actualOneway = result.oneway;
|
||||
if (result.oneway == 0) then
|
||||
actualOneway = "both"
|
||||
elseif (result.oneway == 1) then
|
||||
actualOneway = "with"
|
||||
elseif (result.oneway == 2) then
|
||||
actualOneway = "against"
|
||||
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)
|
||||
if (expected.oneway ~= actualOneway) then
|
||||
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but got " .. actualOneway)
|
||||
failed_profile_tests = true
|
||||
profile_failed = true
|
||||
end
|
||||
|
||||
if (double_compare(inv(result.factor), 0.033333)) then
|
||||
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".factor: expected " .. expected.weight .. " but got " .. inv(result.factor))
|
||||
|
||||
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
|
||||
profile_failed = true
|
||||
end
|
||||
|
||||
if (profile_failed == true) then
|
||||
print("The used tags for test " .. tostring(index) .. " are:")
|
||||
debug_table(tags)
|
||||
end
|
||||
end
|
|
@ -17,6 +17,7 @@
|
|||
- parse
|
||||
- to_string
|
||||
- concat
|
||||
- containedIn
|
||||
- min
|
||||
- max
|
||||
- sum
|
||||
|
@ -40,7 +41,7 @@
|
|||
#### eq
|
||||
|
||||
Argument name | | |
|
||||
--------------| - | - | -
|
||||
-------------- | - | - | -
|
||||
**a** | $a | $a |
|
||||
**b** | $a | $a |
|
||||
_return type_ | bool | string |
|
||||
|
@ -66,14 +67,14 @@ end
|
|||
|
||||
#### notEq
|
||||
|
||||
Argument name | | |
|
||||
--------------| - | - | -
|
||||
**a** | $a | $a |
|
||||
**b** | $a | $a |
|
||||
_return type_ | bool | string |
|
||||
Argument name | | | |
|
||||
-------------- | - | - | - | -
|
||||
**a** | $a | $a | bool |
|
||||
**b** | $a | $a | _none_ |
|
||||
_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
|
||||
function notEq(a, b)
|
||||
if (b == nil) then
|
||||
b = "yes"
|
||||
end
|
||||
|
||||
if (a ~= b) then
|
||||
return "yes"
|
||||
else
|
||||
|
@ -92,14 +97,14 @@ end
|
|||
|
||||
#### not
|
||||
|
||||
Argument name | | |
|
||||
--------------| - | - | -
|
||||
**a** | $a | $a |
|
||||
**b** | $a | $a |
|
||||
_return type_ | bool | string |
|
||||
Argument name | | | |
|
||||
-------------- | - | - | - | -
|
||||
**a** | $a | $a | bool |
|
||||
**b** | $a | $a | _none_ |
|
||||
_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
|
||||
function notEq(a, b)
|
||||
if (b == nil) then
|
||||
b = "yes"
|
||||
end
|
||||
|
||||
if (a ~= b) then
|
||||
return "yes"
|
||||
else
|
||||
|
@ -119,7 +128,7 @@ end
|
|||
#### inv
|
||||
|
||||
Argument name | | |
|
||||
--------------| - | - | -
|
||||
-------------- | - | - | -
|
||||
**d** | pdouble | double |
|
||||
_return type_ | pdouble | double |
|
||||
|
||||
|
@ -140,7 +149,7 @@ end
|
|||
#### default
|
||||
|
||||
Argument name | |
|
||||
--------------| - | -
|
||||
-------------- | - | -
|
||||
**defaultValue** | $a |
|
||||
**f** | $b -> $a |
|
||||
_return type_ | $b -> $a |
|
||||
|
@ -164,10 +173,10 @@ end
|
|||
|
||||
#### parse
|
||||
|
||||
Argument name | |
|
||||
--------------| - | -
|
||||
**s** | string |
|
||||
_return type_ | double |
|
||||
Argument name | | |
|
||||
-------------- | - | - | -
|
||||
**s** | string | string |
|
||||
_return type_ | double | pdouble |
|
||||
|
||||
|
||||
Parses a string into a numerical value
|
||||
|
@ -210,7 +219,7 @@ end
|
|||
#### to_string
|
||||
|
||||
Argument name | |
|
||||
--------------| - | -
|
||||
-------------- | - | -
|
||||
**obj** | $a |
|
||||
_return type_ | string |
|
||||
|
||||
|
@ -231,7 +240,7 @@ end
|
|||
#### concat
|
||||
|
||||
Argument name | |
|
||||
--------------| - | -
|
||||
-------------- | - | -
|
||||
**a** | string |
|
||||
**b** | 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
|
||||
|
||||
Argument name | | | | | |
|
||||
--------------| - | - | - | - | - | -
|
||||
-------------- | - | - | - | - | - | -
|
||||
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
||||
_return type_ | nat | int | pdouble | double | bool |
|
||||
|
||||
|
@ -283,7 +320,7 @@ end
|
|||
#### max
|
||||
|
||||
Argument name | | | | | |
|
||||
--------------| - | - | - | - | - | -
|
||||
-------------- | - | - | - | - | - | -
|
||||
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
||||
_return type_ | nat | int | pdouble | double | bool |
|
||||
|
||||
|
@ -313,7 +350,7 @@ end
|
|||
#### sum
|
||||
|
||||
Argument name | | | | | |
|
||||
--------------| - | - | - | - | - | -
|
||||
-------------- | - | - | - | - | - | -
|
||||
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
||||
_return type_ | nat | int | pdouble | double | int |
|
||||
|
||||
|
@ -341,7 +378,7 @@ end
|
|||
#### multiply
|
||||
|
||||
Argument name | | | | | |
|
||||
--------------| - | - | - | - | - | -
|
||||
-------------- | - | - | - | - | - | -
|
||||
**list** | list (nat) | list (int) | list (pdouble) | list (double) | list (bool) |
|
||||
_return type_ | nat | int | pdouble | double | bool |
|
||||
|
||||
|
@ -366,7 +403,7 @@ end
|
|||
#### firstMatchOf
|
||||
|
||||
Argument name | |
|
||||
--------------| - | -
|
||||
-------------- | - | -
|
||||
**s** | list (string) |
|
||||
_return type_ | (tags -> list ($a)) -> tags -> $a |
|
||||
|
||||
|
@ -404,7 +441,7 @@ end
|
|||
#### mustMatch
|
||||
|
||||
Argument name | |
|
||||
--------------| - | -
|
||||
-------------- | - | -
|
||||
**neededKeys (filled in by parser)** | list (string) |
|
||||
**f** | tags -> list (bool) |
|
||||
_return type_ | tags -> bool |
|
||||
|
@ -420,7 +457,6 @@ Lua implementation:
|
|||
|
||||
````lua
|
||||
function must_match(tags, result, needed_keys, table)
|
||||
local result_list = {}
|
||||
for _, key in ipairs(needed_keys) do
|
||||
local v = tags[key]
|
||||
if (v == nil) then
|
||||
|
@ -430,18 +466,31 @@ function must_match(tags, result, needed_keys, table)
|
|||
local mapping = table[key]
|
||||
if (type(mapping) == "table") then
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
result.attributes_to_keep[key] = v
|
||||
error("MustMatch got a string value it can't handle: " .. bool)
|
||||
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
|
||||
|
||||
-- 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;
|
||||
end
|
||||
````
|
||||
|
@ -449,22 +498,41 @@ end
|
|||
|
||||
#### 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'
|
||||
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.
|
||||
This function returns true, if the way is member of a relation matching the specified function.
|
||||
|
||||
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
|
||||
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
|
||||
````
|
||||
|
||||
|
@ -472,7 +540,7 @@ end
|
|||
#### if_then_else
|
||||
|
||||
Argument name | | |
|
||||
--------------| - | - | -
|
||||
-------------- | - | - | -
|
||||
**condition** | bool | bool |
|
||||
**then** | $a | $a |
|
||||
**else** | $a | _none_ |
|
||||
|
@ -499,7 +567,7 @@ end
|
|||
#### if
|
||||
|
||||
Argument name | | |
|
||||
--------------| - | - | -
|
||||
-------------- | - | - | -
|
||||
**condition** | bool | bool |
|
||||
**then** | $a | $a |
|
||||
**else** | $a | _none_ |
|
||||
|
@ -526,7 +594,7 @@ end
|
|||
#### id
|
||||
|
||||
Argument name | |
|
||||
--------------| - | -
|
||||
-------------- | - | -
|
||||
**a** | $a |
|
||||
_return type_ | $a |
|
||||
|
||||
|
@ -547,7 +615,7 @@ end
|
|||
#### const
|
||||
|
||||
Argument name | |
|
||||
--------------| - | -
|
||||
-------------- | - | -
|
||||
**a** | $a |
|
||||
**b** | $b |
|
||||
_return type_ | $a |
|
||||
|
@ -560,14 +628,16 @@ Small utility function, which takes two arguments `a` and `b` and returns `a`. U
|
|||
Lua implementation:
|
||||
|
||||
````lua
|
||||
|
||||
function const(a, b)
|
||||
return a
|
||||
end
|
||||
````
|
||||
|
||||
|
||||
#### constRight
|
||||
|
||||
Argument name | |
|
||||
--------------| - | -
|
||||
-------------- | - | -
|
||||
**a** | $a |
|
||||
**b** | $b |
|
||||
_return type_ | $b |
|
||||
|
@ -587,11 +657,11 @@ Lua implementation:
|
|||
#### dot
|
||||
|
||||
Argument name | |
|
||||
--------------| - | -
|
||||
**f** | $gType -> $arg |
|
||||
**g** | $fType -> $gType |
|
||||
**a** | $fType |
|
||||
_return type_ | $arg |
|
||||
-------------- | - | -
|
||||
**f** | $b -> $c |
|
||||
**g** | $a -> $b |
|
||||
**a** | $a |
|
||||
_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
|
||||
|
@ -608,7 +678,7 @@ Lua implementation:
|
|||
#### listDot
|
||||
|
||||
Argument name | |
|
||||
--------------| - | -
|
||||
-------------- | - | -
|
||||
**list** | list ($a -> $b) |
|
||||
**a** | $a |
|
||||
_return type_ | list ($b) |
|
||||
|
@ -628,10 +698,19 @@ Lua implementation:
|
|||
|
||||
#### eitherFunc
|
||||
|
||||
- ($a -> $b) -> ($c -> $d) -> $a -> $b
|
||||
- ($a -> $b) -> ($c -> $d) -> $c -> $d
|
||||
Argument name | | |
|
||||
-------------- | - | - | -
|
||||
**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
|
||||
|
||||
- (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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Functions;
|
||||
using AspectedRouting.Typ;
|
||||
using static AspectedRouting.Deconstruct;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Functions;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting
|
||||
namespace AspectedRouting.Language
|
||||
{
|
||||
public static class Analysis
|
||||
{
|
||||
|
@ -26,6 +27,7 @@ namespace AspectedRouting
|
|||
Console.WriteLine(e);
|
||||
var keys = e.PossibleTags().Keys.ToList();
|
||||
|
||||
|
||||
var results = possibleTags.OnAllCombinations(
|
||||
tags =>
|
||||
{
|
||||
|
@ -124,10 +126,10 @@ namespace AspectedRouting
|
|||
} 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -149,7 +151,7 @@ namespace AspectedRouting
|
|||
}
|
||||
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.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)
|
||||
{
|
||||
AddParams(expr, name);
|
||||
|
@ -194,11 +202,11 @@ namespace AspectedRouting
|
|||
return text;
|
||||
}
|
||||
|
||||
public static void SanityCheckProfile(this ProfileMetaData pmd)
|
||||
public static void SanityCheckProfile(this ProfileMetaData pmd, Context context)
|
||||
{
|
||||
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();
|
||||
if (diff.Any())
|
||||
|
@ -206,28 +214,48 @@ namespace AspectedRouting
|
|||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
explanation += $"\n - {paramName} = {d}";
|
||||
}
|
||||
|
||||
if (Math.Abs(sum) < 0.0001)
|
||||
{
|
||||
throw new ArgumentException("Profile " + profileName +
|
||||
": the summed parameters to calculate the weight are zero or very low");
|
||||
throw new ArgumentException("Profile " + behaviourName +
|
||||
": 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 mapping = new List<IExpression>();
|
||||
if (UnApply(
|
||||
UnApply(IsFunc(Funcs.FirstOf), Assign(order)),
|
||||
Assign(mapping)
|
||||
if (Deconstruct.UnApply(
|
||||
Deconstruct.UnApply(Deconstruct.IsFunc(Funcs.FirstOf), Deconstruct.Assign(order)),
|
||||
Deconstruct.Assign(mapping)
|
||||
).Invoke(expr))
|
||||
{
|
||||
var expectedKeys = ((IEnumerable<object>) order.First().Evaluate(null)).Select(o =>
|
||||
|
@ -339,5 +367,61 @@ namespace AspectedRouting
|
|||
|
||||
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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Functions;
|
||||
using AspectedRouting.Language.Expression;
|
||||
|
||||
namespace AspectedRouting
|
||||
namespace AspectedRouting.Language
|
||||
{
|
||||
public static class Deconstruct
|
||||
{
|
||||
|
@ -99,9 +99,7 @@ namespace AspectedRouting
|
|||
};
|
||||
}
|
||||
|
||||
public static Func<IExpression, bool> Any()
|
||||
{
|
||||
return e => true;
|
||||
}
|
||||
public static readonly Func<IExpression, bool> Any = e => true;
|
||||
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Typ;
|
||||
using static AspectedRouting.Deconstruct;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Functions;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Expression
|
||||
{
|
||||
public class Apply : IExpression
|
||||
{
|
||||
|
@ -88,7 +88,7 @@ namespace AspectedRouting.Functions
|
|||
$"is applied on an argument with types:" +
|
||||
$"{string.Join(", ", argument.Optimize().Types)}";
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
_debugInfo = $"\n{f.TypeBreakdown()}\n" +
|
||||
$"{argument.TypeBreakdown()}";
|
||||
|
@ -99,9 +99,9 @@ namespace AspectedRouting.Functions
|
|||
|
||||
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();
|
||||
|
@ -166,20 +166,20 @@ namespace AspectedRouting.Functions
|
|||
// => (const dot _) id => dot id => id
|
||||
// or => (constRight _ id) id => id id => id
|
||||
if (
|
||||
UnApplyAny(
|
||||
UnApplyAny(
|
||||
UnApplyAny(
|
||||
IsFunc(Funcs.Const),
|
||||
IsFunc(Funcs.Dot)),
|
||||
Any()),
|
||||
IsFunc(Funcs.Id)
|
||||
Deconstruct.UnApplyAny(
|
||||
Deconstruct.UnApplyAny(
|
||||
Deconstruct.UnApplyAny(
|
||||
Deconstruct.IsFunc(Funcs.Const),
|
||||
Deconstruct.IsFunc(Funcs.Dot)),
|
||||
Deconstruct.Any),
|
||||
Deconstruct.IsFunc(Funcs.Id)
|
||||
).Invoke(this)
|
||||
&& UnApplyAny(UnApplyAny(
|
||||
UnApplyAny(
|
||||
IsFunc(Funcs.ConstRight),
|
||||
Any()),
|
||||
IsFunc(Funcs.Id)),
|
||||
IsFunc(Funcs.Id)
|
||||
&& Deconstruct.UnApplyAny(Deconstruct.UnApplyAny(
|
||||
Deconstruct.UnApplyAny(
|
||||
Deconstruct.IsFunc(Funcs.ConstRight),
|
||||
Deconstruct.Any),
|
||||
Deconstruct.IsFunc(Funcs.Id)),
|
||||
Deconstruct.IsFunc(Funcs.Id)
|
||||
).Invoke(this))
|
||||
{
|
||||
return Funcs.Id;
|
||||
|
@ -247,12 +247,12 @@ namespace AspectedRouting.Functions
|
|||
|
||||
// ((dot f0) f1)
|
||||
// ((dot f0) f1) arg is the actual expression, but arg is already split of
|
||||
if (UnApply(
|
||||
UnApply(
|
||||
IsFunc(Funcs.Dot),
|
||||
Assign(f0)
|
||||
if (Deconstruct.UnApply(
|
||||
Deconstruct.UnApply(
|
||||
Deconstruct.IsFunc(Funcs.Dot),
|
||||
Deconstruct.Assign(f0)
|
||||
),
|
||||
Assign(f1)).Invoke(f)
|
||||
Deconstruct.Assign(f1)).Invoke(f)
|
||||
)
|
||||
{
|
||||
// f0 (f1 arg)
|
|
@ -1,8 +1,8 @@
|
|||
using System;
|
||||
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
|
||||
{
|
||||
|
@ -32,6 +32,12 @@ namespace AspectedRouting.Functions
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Typ;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Expression
|
||||
{
|
||||
public abstract class Function : IExpression
|
||||
{
|
|
@ -1,9 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AspectedRouting.Typ;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Expression
|
||||
{
|
||||
public class FunctionCall : IExpression
|
||||
{
|
||||
|
@ -34,7 +34,10 @@ namespace AspectedRouting.Functions
|
|||
|
||||
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)
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Expression
|
||||
{
|
||||
public class ProfileMetaData
|
||||
{
|
||||
|
@ -12,19 +12,19 @@ namespace AspectedRouting.Functions
|
|||
public List<string> VehicleTyps { get; }
|
||||
public List<string> Metadata { get; }
|
||||
|
||||
public Dictionary<string, object> DefaultParameters { get; }
|
||||
public Dictionary<string, Dictionary<string, object>> Profiles { get; }
|
||||
public Dictionary<string, IExpression> DefaultParameters { get; }
|
||||
public Dictionary<string, Dictionary<string, IExpression>> Behaviours { get; }
|
||||
|
||||
public IExpression Access { get; }
|
||||
public IExpression Oneway { 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,
|
||||
List<string> vehicleTyps, Dictionary<string, object> defaultParameters,
|
||||
Dictionary<string, Dictionary<string, object>> profiles,
|
||||
List<string> vehicleTyps, Dictionary<string, IExpression> defaultParameters,
|
||||
Dictionary<string, Dictionary<string, IExpression>> behaviours,
|
||||
IExpression access, IExpression oneway, IExpression speed,
|
||||
Dictionary<string, IExpression> weights, List<string> metadata)
|
||||
Dictionary<string, IExpression> priority, List<string> metadata)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
|
@ -34,16 +34,16 @@ namespace AspectedRouting.Functions
|
|||
Access = access;
|
||||
Oneway = oneway;
|
||||
Speed = speed;
|
||||
Weights = weights;
|
||||
Priority = priority;
|
||||
Metadata = metadata;
|
||||
DefaultParameters = defaultParameters;
|
||||
Profiles = profiles;
|
||||
Behaviours = behaviours;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Profile: {Name} {Filename}\naccess={Access}\noneway={Oneway}\nspeed={Speed}\n" +
|
||||
$"weights ={string.Join(" + ", Weights.Select(kv => "#" + kv.Key + " * " + kv.Value))} ";
|
||||
$"priorities = {string.Join(" + ", Priority.Select(kv => "#" + kv.Key + " * " + kv.Value))} ";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Typ;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Functions;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
// ReSharper disable UnusedMember.Global
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language
|
||||
{
|
||||
public static class Funcs
|
||||
{
|
||||
|
@ -23,12 +25,13 @@ namespace AspectedRouting.Functions
|
|||
public static readonly Function ToStringFunc = new ToString();
|
||||
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 Max = new Max();
|
||||
public static readonly Function Sum = new Sum();
|
||||
public static readonly Function Multiply = new Multiply();
|
||||
|
||||
|
||||
|
||||
|
||||
public static readonly Function FirstOf = new FirstMatchOf();
|
||||
public static readonly Function MustMatch = new MustMatch();
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
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
|
||||
{
|
|
@ -1,7 +1,8 @@
|
|||
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
|
||||
{
|
|
@ -1,8 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using AspectedRouting.Typ;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
public class Const : Function
|
||||
{
|
|
@ -1,8 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using AspectedRouting.Typ;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
public class ConstRight : Function
|
||||
{ public override string Description { get; } =
|
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Typ;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
public class Constant : IExpression
|
||||
{
|
||||
|
@ -40,7 +40,7 @@ namespace AspectedRouting.Functions
|
|||
}
|
||||
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.Linq;
|
||||
using AspectedRouting.Typ;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
public class Default : Function
|
||||
{
|
|
@ -1,18 +1,19 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Typ;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
public class Dot : Function
|
||||
{
|
||||
public override string Description { get; } = "Higher order function: converts `f (g a)` into `(dot f g) a`. In other words, this fuses `f` and `g` in a new function, which allows the argument to be lifted out of the expression ";
|
||||
|
||||
public override List<string> ArgNames { get; } = new List<string>{"f","g","a"};
|
||||
public static readonly Var A = new Var("fType");
|
||||
public static readonly Var B = new Var("gType");
|
||||
public static readonly Var C = new Var("arg");
|
||||
public static readonly Var A = new Var("a");
|
||||
public static readonly Var B = new Var("b");
|
||||
public static readonly Var C = new Var("c");
|
||||
|
||||
public Dot() : base("dot", true, new[]
|
||||
{
|
||||
|
@ -34,7 +35,7 @@ namespace AspectedRouting.Functions
|
|||
{
|
||||
var f0 = arguments[0];
|
||||
var f1 = arguments[1];
|
||||
var resultType = (f1.Types.First() as Curry).ResultType;
|
||||
var resultType = ((Curry) f1.Types.First()).ResultType;
|
||||
var a = arguments[2];
|
||||
return f0.Evaluate(c, new Constant(resultType, f1.Evaluate(c, a)));
|
||||
}
|
|
@ -1,12 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AspectedRouting.Typ;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
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 b = new Var("b");
|
||||
private static Var c = new Var("c");
|
|
@ -1,7 +1,8 @@
|
|||
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 override string Description { get; } = "Returns 'yes' if both values _are_ the same";
|
|
@ -1,9 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Typ;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
public class FirstMatchOf : Function
|
||||
{
|
|
@ -1,8 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
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 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 AspectedRouting.Typ;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
public class If : Function
|
||||
{
|
|
@ -1,7 +1,8 @@
|
|||
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
|
||||
{
|
|
@ -1,7 +1,8 @@
|
|||
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
|
||||
{
|
|
@ -1,10 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Typ;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
public class Mapping : Function
|
||||
{
|
||||
|
@ -78,7 +79,6 @@ namespace AspectedRouting.Functions
|
|||
var otherARgs = arguments.ToList().GetRange(1, arguments.Length - 1);
|
||||
if (!StringToResultFunctions.TryGetValue(key, out var resultFunction))
|
||||
{
|
||||
Console.WriteLine($"Warning: key {key} not found in mapping {this}");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ namespace AspectedRouting.Functions
|
|||
|
||||
var typeOptStr = string.Join(";", opt.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: " +
|
||||
$"\n{typeEStr}" +
|
|
@ -1,8 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
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
|
||||
{
|
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.Linq;
|
||||
using AspectedRouting.Typ;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
public class Min : Function
|
||||
{
|
||||
public override string Description { get; } = "Out of a list of values, gets the smallest value. IN case of a list of bools, this acts as `and`";
|
||||
public override List<string> ArgNames { get; } = new List<string>{"list"};
|
||||
public override string Description { get; } =
|
||||
"Out of a list of values, gets the smallest value. IN case of a list of bools, this acts as `and`";
|
||||
|
||||
public override List<string> ArgNames { get; } = new List<string> {"list"};
|
||||
|
||||
public Min() : base("min", true,
|
||||
new[]
|
||||
|
@ -17,7 +20,6 @@ namespace AspectedRouting.Functions
|
|||
new Curry(new ListType(Typs.PDouble), Typs.PDouble),
|
||||
new Curry(new ListType(Typs.Double), Typs.Double),
|
||||
new Curry(new ListType(Typs.Bool), Typs.Bool),
|
||||
|
||||
})
|
||||
{
|
||||
}
|
||||
|
@ -41,11 +43,12 @@ namespace AspectedRouting.Functions
|
|||
|
||||
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||
{
|
||||
var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o!=null);
|
||||
var expectedType = (Types.First() as Curry).ResultType;
|
||||
var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o != null);
|
||||
var expectedType = ((Curry) Types.First()).ResultType;
|
||||
|
||||
switch (expectedType)
|
||||
{ case BoolType _:
|
||||
{
|
||||
case BoolType _:
|
||||
if (ls.Select(o => o.Equals("yes") || o.Equals("true")).All(b => b))
|
||||
{
|
||||
return "yes";
|
||||
|
@ -54,9 +57,27 @@ namespace AspectedRouting.Functions
|
|||
return "no";
|
||||
case DoubleType _:
|
||||
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:
|
||||
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.Linq;
|
||||
using AspectedRouting.Typ;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
public class Multiply : Function
|
||||
{
|
||||
|
||||
public override string Description { get; } = "Multiplies all the values in a given list. On a list of booleans, this acts as 'and' or 'all'";
|
||||
public override string Description { get; } =
|
||||
"Multiplies all the values in a given list. On a list of booleans, this acts as 'and' or 'all'";
|
||||
|
||||
public override List<string> ArgNames { get; } = new List<string> {"list"};
|
||||
|
||||
|
||||
|
||||
public Multiply() : base("multiply", true,
|
||||
new[]
|
||||
{
|
||||
|
@ -42,19 +44,26 @@ namespace AspectedRouting.Functions
|
|||
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||
{
|
||||
var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o != null);
|
||||
var expectedType = (Types.First() as Curry).ResultType;
|
||||
var expectedType = ((Curry) Types.First()).ResultType;
|
||||
|
||||
|
||||
switch (expectedType)
|
||||
{
|
||||
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";
|
||||
}
|
||||
if(!(o.Equals("yes") || o.Equals("true")))
|
||||
|
||||
if (!(s.Equals("yes") || s.Equals("true")))
|
||||
{
|
||||
return "no";
|
||||
}
|
||||
|
@ -64,16 +73,28 @@ namespace AspectedRouting.Functions
|
|||
case DoubleType _:
|
||||
case PDoubleType _:
|
||||
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;
|
||||
}
|
||||
|
||||
return mult;
|
||||
default:
|
||||
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;
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Typ;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
public class MustMatch : Function
|
||||
{
|
|
@ -1,18 +1,20 @@
|
|||
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 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 NotEq() : base("notEq", true,
|
||||
new[]
|
||||
{
|
||||
Curry.ConstructFrom(Typs.Bool, new Var("a"), new Var("a")),
|
||||
Curry.ConstructFrom(Typs.String, new Var("a"), new Var("a"))
|
||||
Curry.ConstructFrom(Typs.String, new Var("a"), new Var("a")),
|
||||
new Curry(Typs.Bool, Typs.Bool),
|
||||
})
|
||||
{
|
||||
Funcs.AddBuiltin(this, "not");
|
||||
|
@ -35,6 +37,13 @@ namespace AspectedRouting.Functions
|
|||
|
||||
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 arg1 = arguments[1].Evaluate(c);
|
||||
if ((!(arg0?.Equals(arg1) ?? false)))
|
|
@ -1,9 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AspectedRouting.Typ;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Typ;
|
||||
using Type = AspectedRouting.Language.Typ.Type;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
public class Parameter : IExpression
|
||||
{
|
||||
|
@ -25,16 +25,8 @@ namespace AspectedRouting.Functions
|
|||
|
||||
public object Evaluate(Context c, params IExpression[] args)
|
||||
{
|
||||
return c?.Parameters?.GetValueOrDefault(ParamName, null);
|
||||
}
|
||||
|
||||
public void EvaluateAll(Context c, HashSet<IExpression> addTo)
|
||||
{
|
||||
var v = Evaluate(c);
|
||||
if (v != null)
|
||||
{
|
||||
addTo.Add(this);
|
||||
}
|
||||
var paramName = ParamName.TrimStart('#'); // context saves paramnames without '#'
|
||||
return c?.Parameters?.GetValueOrDefault(paramName, null);
|
||||
}
|
||||
|
||||
public IExpression Specialize(IEnumerable<Type> allowedTypes)
|
|
@ -1,8 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
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
|
||||
{
|
||||
|
@ -13,6 +14,7 @@ namespace AspectedRouting.Functions
|
|||
new[]
|
||||
{
|
||||
new Curry(Typs.String, Typs.Double),
|
||||
new Curry(Typs.String, Typs.PDouble),
|
||||
})
|
||||
{
|
||||
}
|
|
@ -1,13 +1,18 @@
|
|||
using System.Collections.Generic;
|
||||
using AspectedRouting.Typ;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a function 'string -> string -> a' onto a function 'tags -> [a]'
|
||||
/// </summary>
|
||||
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 =
|
||||
Curry.ConstructFrom(new Var("a"), Typs.String, Typs.String);
|
||||
|
||||
|
@ -15,7 +20,8 @@ namespace AspectedRouting.Functions
|
|||
public StringStringToTagsFunction() : base("stringToTags", true,
|
||||
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.Linq;
|
||||
using AspectedRouting.Typ;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Language.Typ;
|
||||
|
||||
namespace AspectedRouting.Functions
|
||||
namespace AspectedRouting.Language.Functions
|
||||
{
|
||||
public class Sum : Function
|
||||
{
|
|
@ -1,7 +1,8 @@
|
|||
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
|
||||
{
|
|
@ -1,60 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Functions;
|
||||
using AspectedRouting.Typ;
|
||||
using Type = AspectedRouting.Typ.Type;
|
||||
using AspectedRouting.Language.Functions;
|
||||
using AspectedRouting.Language.Typ;
|
||||
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
|
||||
{
|
||||
IEnumerable<Type> Types { get; }
|
||||
|
@ -79,6 +32,11 @@ namespace AspectedRouting
|
|||
|
||||
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)
|
||||
{
|
||||
if (t == null)
|
||||
|
@ -125,6 +83,7 @@ namespace AspectedRouting
|
|||
/// THen specializes every expression onto this common ground
|
||||
/// </summary>
|
||||
/// <returns>The common ground of types</returns>
|
||||
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
|
||||
public static IEnumerable<IExpression> SpecializeToCommonTypes(this IEnumerable<IExpression> exprs,
|
||||
out IEnumerable<Type> specializedTypes, out IEnumerable<IExpression> specializedExpressions)
|
||||
{
|
||||
|
@ -139,12 +98,13 @@ namespace AspectedRouting
|
|||
}
|
||||
|
||||
var specialized = specializedTypes.SpecializeTo(f.Types.RenameVars(specializedTypes));
|
||||
// ReSharper disable once JoinNullCheckWithUsage
|
||||
if (specialized == null)
|
||||
{
|
||||
throw new ArgumentException("Could not unify\n "
|
||||
+ "<previous items>: " + string.Join(", ", specializedTypes) +
|
||||
"\nwith\n "
|
||||
+ f + ": " + string.Join(", ", f.Types));
|
||||
+ f.Optimize() + ": " + string.Join(", ", f.Types));
|
||||
}
|
||||
|
||||
specializedTypes = specialized;
|
|
@ -1,4 +1,4 @@
|
|||
namespace AspectedRouting.Typ
|
||||
namespace AspectedRouting.Language.Typ
|
||||
{
|
||||
public class BoolType : Type
|
||||
{
|
|
@ -2,7 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace AspectedRouting.Typ
|
||||
namespace AspectedRouting.Language.Typ
|
||||
{
|
||||
public class Curry : Type
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace AspectedRouting.Typ
|
||||
namespace AspectedRouting.Language.Typ
|
||||
{
|
||||
public class DoubleType : Type
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace AspectedRouting.Typ
|
||||
namespace AspectedRouting.Language.Typ
|
||||
{
|
||||
public class IntType : Type
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace AspectedRouting.Typ
|
||||
namespace AspectedRouting.Language.Typ
|
||||
{
|
||||
public class ListType : Type
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace AspectedRouting.Typ
|
||||
namespace AspectedRouting.Language.Typ
|
||||
{
|
||||
public class NatType : Type
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace AspectedRouting.Typ
|
||||
namespace AspectedRouting.Language.Typ
|
||||
{
|
||||
public class PDoubleType : Type
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace AspectedRouting.Typ
|
||||
namespace AspectedRouting.Language.Typ
|
||||
{
|
||||
public class StringType : Type
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace AspectedRouting.Typ
|
||||
namespace AspectedRouting.Language.Typ
|
||||
{
|
||||
public class TagsType : Type
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace AspectedRouting.Typ
|
||||
namespace AspectedRouting.Language.Typ
|
||||
{
|
||||
public abstract class Type
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace AspectedRouting.Typ
|
||||
namespace AspectedRouting.Language.Typ
|
||||
{
|
||||
public static class Typs
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AspectedRouting.Typ
|
||||
namespace AspectedRouting.Language.Typ
|
||||
{
|
||||
public class Var : Type
|
||||
{
|
|
@ -1,12 +1,7 @@
|
|||
-- The different profiles
|
||||
profiles = {
|
||||
|
||||
{
|
||||
name = "comfort_safety_speed",
|
||||
description = "A route which aims to be both safe and comfortable without sacrificing too much of speed",
|
||||
function_name = "determine_weights_balanced",
|
||||
metric = "custom"
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
name = "b2w",
|
||||
|
@ -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
|
||||
function determine_permissive_score(attributes, result)
|
||||
if (attributes.access == "permissive") then
|
||||
|
@ -674,30 +528,6 @@ end
|
|||
function unit_tests()
|
||||
|
||||
|
||||
-- Speed test are with default_speed = 100, in order to efficiently determine the factor
|
||||
unit_test_speed({ highway = "residential" }, 100.0)
|
||||
unit_test_speed({ highway = "residential", surface = "sett" }, 90.0)
|
||||
unit_test_speed({ highway = "residential", incline = "up" }, 75.0)
|
||||
unit_test_speed({ highway = "residential", incline = "up", surface = "mud" }, 30.0)
|
||||
unit_test_speed({ highway = "residential", incline = "down" }, 125.0)
|
||||
unit_test_speed({ highway = "residential", incline = "down", surface = "sett" }, 112.5)
|
||||
|
||||
|
||||
unit_test_comfort({ highway = "residential" }, 1.0)
|
||||
unit_test_comfort({ highway = "residential", cyclestreet = "yes" }, 1.1)
|
||||
unit_test_comfort({ highway = "cycleway" }, 1.2)
|
||||
unit_test_comfort({ highway = "cycleway", foot = "designated" }, 1.2)
|
||||
unit_test_comfort({ highway = "path", bicycle = "designated", foot = "designated" }, 0.5)
|
||||
unit_test_comfort({ highway = "path", bicycle = "designated" }, 0.5)
|
||||
unit_test_comfort({ highway = "footway", foot = "designated" }, 0.95)
|
||||
unit_test_comfort({ highway = "primary", cycleway = "no" }, 0.3)
|
||||
unit_test_comfort({ highway = "primary", cycleway = "yes" }, 0.3)
|
||||
unit_test_comfort({ highway = "primary", cycleway = "track" }, 0.36)
|
||||
|
||||
unit_test_comfort({ highway = "secondary", cycleway = "lane" }, 0.4)
|
||||
unit_test_comfort({ ["cycleway:right"] = "lane", highway = "secondary", surface = "asphalt" }, 0.4)
|
||||
unit_test_comfort({ highway = "residential", surface = "asphalt", cyclestreet = "yes" }, 1.1)
|
||||
|
||||
|
||||
unit_test_relation_tag_processor({ route = "bicycle", operator = "Stad Genk", color = "red", type = "route" },
|
||||
{ StadGenk = "yes", cycle_network_colour = "red", cycle_network = "yes" });
|
||||
|
|
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",
|
||||
"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]",
|
||||
"$default": 1,
|
||||
"value": {
|
||||
"$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": {
|
||||
"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
|
||||
0,0,0,0,,
|
||||
1,0,30,0.03333,cycleway,
|
||||
0,0,0,0,primary,
|
||||
1,0,15,0.06666,pedestrian,
|
||||
1,0,15,0.0666,pedestrian,yes
|
||||
1,0,30,0.033333,residential,
|
||||
access,oneway,speed,priority,highway,bicycle
|
||||
no,both,0,0,,
|
||||
designated,both,15,15,cycleway,
|
||||
no,both,0,0,primary,
|
||||
dismount,both,2.25,2.25,pedestrian,
|
||||
yes,both,15,15,pedestrian,yes
|
||||
yes,both,15,15,residential,
|
||||
|
|
|
|
@ -16,25 +16,27 @@
|
|||
"network"
|
||||
],
|
||||
"defaults": {
|
||||
"#max_speed": 30,
|
||||
"#defaultSpeed": 15,
|
||||
"#speed": 0,
|
||||
"#maxspeed": 30,
|
||||
"#timeNeeded": 0,
|
||||
"#distance": 0,
|
||||
"#comfort": 0,
|
||||
"#safety": 0,
|
||||
"#withDirection": "yes",
|
||||
"#againstDirection": "yes",
|
||||
"network": 0
|
||||
"#network": 0,
|
||||
"#networkOperator": []
|
||||
},
|
||||
"profiles": {
|
||||
"behaviours": {
|
||||
|
||||
"fastest": {
|
||||
"description": "The fastest route to your destination",
|
||||
"#time_needed": 1
|
||||
"#timeNeeded": 1
|
||||
},
|
||||
"shortest": {
|
||||
"description": "The shortest route, independent of of speed",
|
||||
"#distance": 1
|
||||
},
|
||||
|
||||
|
||||
"safety": {
|
||||
"description": "A defensive route shying away from big roads with lots of cars",
|
||||
"#safety": 1
|
||||
|
@ -48,10 +50,12 @@
|
|||
"#comfort": 1,
|
||||
"#safety": 1
|
||||
},
|
||||
"node_networks": {
|
||||
"description": "A route which prefers cycling over cycling node networks. Non-network parts prefer some comfort and safety. The on-network-part is considered flat (meaning that only distance on the node network counts)",
|
||||
|
||||
|
||||
"brussels": {
|
||||
"description": "A route preferring the cycling network by operator 'Brussels Mobility'",
|
||||
"#network": 20,
|
||||
"#network-node-network-only":true,
|
||||
"#networkOperator": ["Brussels Mobility"],
|
||||
"#comfort": 1,
|
||||
"#safety": 1
|
||||
}
|
||||
|
@ -61,16 +65,20 @@
|
|||
"speed": {
|
||||
"$min": [
|
||||
"$legal_maxspeed_be",
|
||||
"#defaultSpeed"
|
||||
"#maxspeed",
|
||||
{
|
||||
"$multiply": [
|
||||
"#defaultSpeed",
|
||||
"$bicycle.speed_factor"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"weight": {
|
||||
"priority": {
|
||||
"#comfort": "$bicycle.comfort",
|
||||
"#safety": "$bicycle.safety",
|
||||
"#network": "$bicycle.network_score",
|
||||
"#time_needed": {
|
||||
"$inv": "$speed"
|
||||
},
|
||||
"#network": "$bicycle.network_by_operator",
|
||||
"#timeNeeded": "$speed",
|
||||
"#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",
|
||||
"description": "The 'bicycle.network_score' is a bit of a catch-all for bicycle networks and indicates wether or not the road is part of a matching cycling network.",
|
||||
"$mustMatch": {
|
||||
"type": "route",
|
||||
"route": "bicycle"
|
||||
"description": "The 'bicycle.network_score' returns true if the way is part of a cycling network",
|
||||
"$memberOf": {
|
||||
"$mustMatch": {
|
||||
"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.IO;
|
||||
using System.Linq;
|
||||
using AspectedRouting.Functions;
|
||||
using AspectedRouting.IO;
|
||||
using AspectedRouting.IO.itinero1;
|
||||
using AspectedRouting.IO.jsonParser;
|
||||
using AspectedRouting.Language;
|
||||
using AspectedRouting.Language.Expression;
|
||||
using AspectedRouting.Tests;
|
||||
|
||||
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)
|
||||
{
|
||||
MdPrinter.GenerateHelpText("IO/md/helpText.md");
|
||||
|
||||
|
||||
var files = Directory.EnumerateFiles("Profiles", "*.json", SearchOption.AllDirectories)
|
||||
.ToList();
|
||||
|
||||
|
||||
var context = new Context();
|
||||
|
||||
var aspects = ParseAspects(files, context);
|
||||
|
||||
MdPrinter.GenerateHelpText("IO/md/helpText.md");
|
||||
|
||||
var testSuites = new List<FunctionTestSuite>();
|
||||
var aspects = new List<AspectMetadata>();
|
||||
foreach (var file in files)
|
||||
foreach (var (aspect, t) in aspects)
|
||||
{
|
||||
var fi = new FileInfo(file);
|
||||
var aspect = JsonParser.AspectFromJson(context, File.ReadAllText(file), fi.Name);
|
||||
if (aspect != null)
|
||||
if (t == null)
|
||||
{
|
||||
aspects.Add(aspect);
|
||||
|
||||
var testPath = fi.DirectoryName + "/" + aspect.Name + ".test.csv";
|
||||
if (File.Exists(testPath))
|
||||
{
|
||||
var tests = FunctionTestSuite.FromString(aspect, File.ReadAllText(testPath));
|
||||
testSuites.Add(tests);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[{aspect.Name}] No tests found: to directory" );
|
||||
}
|
||||
Console.WriteLine($"[{aspect.Name}] WARNING: no tests found: please add {aspect.Name}.test.csv");
|
||||
}
|
||||
else
|
||||
{
|
||||
t.Run();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var aspect in aspects)
|
||||
{
|
||||
context.AddFunction(aspect.Name, aspect);
|
||||
}
|
||||
|
||||
|
||||
var profilePath = "Profiles/bicycle/bicycle.json";
|
||||
var profile = JsonParser.ProfileFromJson(context, File.ReadAllText(profilePath), new FileInfo(profilePath));
|
||||
var (profile, profileTests) = ParseProfile(profilePath, context);
|
||||
|
||||
var profileFi = new FileInfo(profilePath);
|
||||
var profileTests = new List<ProfileTestSuite>();
|
||||
foreach (var profileName in profile.Profiles.Keys)
|
||||
foreach (var test in profileTests)
|
||||
{
|
||||
var testPath = profileFi.DirectoryName + "/" + profile.Name + "." + profileName + ".csv";
|
||||
if (File.Exists(testPath))
|
||||
{
|
||||
var test = ProfileTestSuite.FromString(profile, profileName, File.ReadAllText(testPath));
|
||||
profileTests.Add(test);
|
||||
}
|
||||
}
|
||||
|
||||
profile.SanityCheckProfile();
|
||||
|
||||
var luaPrinter = new LuaPrinter();
|
||||
luaPrinter.CreateProfile(profile, context);
|
||||
|
||||
|
||||
var testCode = "\n\n\n\n\n\n\n\n--------------------------- Test code -------------------------\n\n\n";
|
||||
|
||||
|
||||
foreach (var testSuite in testSuites)
|
||||
{
|
||||
testSuite.Run();
|
||||
testCode += testSuite.ToLua() + "\n";
|
||||
}
|
||||
|
||||
foreach (var testSuite in profileTests)
|
||||
{
|
||||
testCode += testSuite.ToLua() + "\n";
|
||||
test.Run(context);
|
||||
}
|
||||
|
||||
|
||||
// Compatibility code, itinero-transit doesn't know 'print'
|
||||
testCode += string.Join("\n",
|
||||
"",
|
||||
"if (itinero == nil) then",
|
||||
" itinero = {}",
|
||||
" itinero.log = print",
|
||||
"",
|
||||
" -- Itinero is not defined -> we are running from a lua interpreter -> the tests are intended",
|
||||
" runTests = true",
|
||||
"",
|
||||
"",
|
||||
"else",
|
||||
" print = itinero.log",
|
||||
"end",
|
||||
"",
|
||||
"if (not failed_tests and not failed_profile_tests) then",
|
||||
" print(\"Tests OK\")",
|
||||
"end"
|
||||
);
|
||||
var luaPrinter = GenerateLua(context, aspects, profile, profileTests);
|
||||
|
||||
File.WriteAllText("output.lua", luaPrinter.ToLua() + testCode);
|
||||
File.WriteAllText("output.lua", luaPrinter.ToLua());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
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
|
||||
{
|
||||
private readonly AspectMetadata _functionToApply;
|
||||
private readonly IEnumerable<(string expected, Dictionary<string, string> tags)> _tests;
|
||||
public readonly AspectMetadata FunctionToApply;
|
||||
public readonly IEnumerable<(string expected, Dictionary<string, string> tags)> Tests;
|
||||
|
||||
public static FunctionTestSuite FromString(AspectMetadata function, string csvContents)
|
||||
{
|
||||
|
@ -47,47 +49,23 @@ namespace AspectedRouting.IO
|
|||
AspectMetadata functionToApply,
|
||||
IEnumerable<(string expected, Dictionary<string, string> tags)> tests)
|
||||
{
|
||||
_functionToApply = functionToApply;
|
||||
_tests = tests;
|
||||
}
|
||||
|
||||
|
||||
public string ToLua()
|
||||
{
|
||||
|
||||
|
||||
var tests = string.Join("\n", _tests.Select((test, i) => ToLua(i, test.expected, test.tags)));
|
||||
return "\n" + tests + "\n";
|
||||
}
|
||||
|
||||
private string ToLua(int index, string expected, Dictionary<string, string> tags)
|
||||
{
|
||||
var parameters = new Dictionary<string, string>();
|
||||
|
||||
|
||||
foreach (var (key, value) in tags)
|
||||
if (functionToApply == null)
|
||||
{
|
||||
if (key.StartsWith("#"))
|
||||
{
|
||||
parameters[key.TrimStart('#')] = value;
|
||||
}
|
||||
throw new NullReferenceException("functionToApply is null");
|
||||
}
|
||||
|
||||
foreach (var (paramName, _) in parameters)
|
||||
{
|
||||
tags.Remove("#" + paramName);
|
||||
}
|
||||
|
||||
var funcName = _functionToApply.Name.Replace(" ", "_").Replace(".", "_");
|
||||
return
|
||||
$"unit_test({funcName}, \"{_functionToApply.Name}\", {index}, \"{expected}\", {parameters.ToLuaTable()}, {tags.ToLuaTable()})";
|
||||
FunctionToApply = functionToApply;
|
||||
Tests = tests;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void Run()
|
||||
{
|
||||
var failed = false;
|
||||
var testCase = 0;
|
||||
foreach (var test in _tests)
|
||||
foreach (var test in Tests)
|
||||
{
|
||||
testCase++;
|
||||
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) &&
|
||||
!(actual is double actualD && Math.Abs(double.Parse(test.expected) - actualD) < 0.0001)
|
||||
)
|
||||
{
|
||||
failed = true;
|
||||
Console.WriteLine(
|
||||
$"[{_functionToApply.Name}] Testcase {testCase} failed:\n Expected: {test.expected}\n actual: {actual}\n tags: {test.tags.Pretty()}");
|
||||
$"[{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");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
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