From 7391a10344c1d6e1bba0a2ca1294638f2db16a35 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 5 Jul 2022 11:22:58 +0200 Subject: [PATCH 1/6] Improve output for more user-friendlyness --- AspectedRouting/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AspectedRouting/Program.cs b/AspectedRouting/Program.cs index 416373c..6b8e4ef 100644 --- a/AspectedRouting/Program.cs +++ b/AspectedRouting/Program.cs @@ -195,7 +195,7 @@ namespace AspectedRouting private static void PrintUsedTags(ProfileMetaData profile, Context context) { - Console.WriteLine("\n\n\n---------- " + profile.Name + " --------------"); + Console.WriteLine("\n\n\n---------- " + profile.Name +" : used tags and corresponding values --------------"); foreach (var (key, values) in profile.AllExpressions(context).PossibleTags()) { var vs = "*"; if (values.Any()) { @@ -230,6 +230,7 @@ namespace AspectedRouting } MdPrinter.GenerateHelpText(outputDir + "helpText.md"); + Console.WriteLine("Written helptext to "+outputDir); var files = Directory.EnumerateFiles(inputDir, "*.json", SearchOption.AllDirectories) From ff98d2fcf30ba5516066092e1d92040d9a71a27d Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 5 Jul 2022 11:23:48 +0200 Subject: [PATCH 2/6] Improve documentation and formatting --- AspectedRouting/IO/itinero2/Relations.md | 3 ++ .../JsonParser.ParseAspectProfile.cs | 8 ++--- AspectedRouting/IO/md/helpText.md | 2 +- .../Language/Functions/Constant.cs | 3 +- .../Language/Functions/EitherFunc.cs | 6 ++-- .../Language/Functions/FirstMatchOf.cs | 9 ++++-- AspectedRouting/Language/Functions/If.cs | 5 ++-- .../Language/Functions/MemberOf.cs | 30 +++++++++++-------- 8 files changed, 38 insertions(+), 28 deletions(-) create mode 100644 AspectedRouting/IO/itinero2/Relations.md diff --git a/AspectedRouting/IO/itinero2/Relations.md b/AspectedRouting/IO/itinero2/Relations.md new file mode 100644 index 0000000..2f0f8f2 --- /dev/null +++ b/AspectedRouting/IO/itinero2/Relations.md @@ -0,0 +1,3 @@ +# What about relations in Itinero 2.0? + +Relations have moved to the preprocessor, where they do put a tag on the members of the relation. This is done with a TagsFilter \ No newline at end of file diff --git a/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs b/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs index f681bdc..2d571ed 100644 --- a/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs +++ b/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs @@ -163,11 +163,9 @@ namespace AspectedRouting.IO.jsonParser ); } - private static readonly IExpression _mconst = - new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.Const); + private static readonly IExpression _mconst = Funcs.EitherFunc.Apply( Funcs.Id, Funcs.Const); - private static readonly IExpression _mappingWrapper = - new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.StringStringToTags); + private static readonly IExpression _mappingWrapper = Funcs.EitherFunc.Apply( Funcs.Id, Funcs.StringStringToTags); private static IExpression ParseMapping(IEnumerable allArgs, Context context) { @@ -201,7 +199,7 @@ namespace AspectedRouting.IO.jsonParser { var simpleMapping = new Mapping(keys, exprs); - var wrapped = (IExpression) new Apply(_mappingWrapper, simpleMapping); + var wrapped = _mappingWrapper.Apply(simpleMapping); if (keys.Count == 1) { // We can interpret this directly without going through a list diff --git a/AspectedRouting/IO/md/helpText.md b/AspectedRouting/IO/md/helpText.md index cf334c3..0360cb2 100644 --- a/AspectedRouting/IO/md/helpText.md +++ b/AspectedRouting/IO/md/helpText.md @@ -697,7 +697,7 @@ Consider the mapping `{'someKey':'someValue'}`. Under normal circumstances, this 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 `( +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_ diff --git a/AspectedRouting/Language/Functions/Constant.cs b/AspectedRouting/Language/Functions/Constant.cs index e7de7d4..be699cc 100644 --- a/AspectedRouting/Language/Functions/Constant.cs +++ b/AspectedRouting/Language/Functions/Constant.cs @@ -245,7 +245,7 @@ namespace AspectedRouting.Language.Functions public override string ToString() { - return _o.Pretty(); + return ObjectExtensions.Pretty(_o); } } @@ -254,6 +254,7 @@ namespace AspectedRouting.Language.Functions public static string Pretty(this object o, Context context = null) { switch (o) { + case null: return "null"; case Dictionary d: var txt = ""; foreach (var (k, v) in d) { diff --git a/AspectedRouting/Language/Functions/EitherFunc.cs b/AspectedRouting/Language/Functions/EitherFunc.cs index 27d6b97..5df91c5 100644 --- a/AspectedRouting/Language/Functions/EitherFunc.cs +++ b/AspectedRouting/Language/Functions/EitherFunc.cs @@ -13,9 +13,9 @@ namespace AspectedRouting.Language.Functions "" + "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" + + "`{'someKey': {'$eq':'someValue'}}`. " + + "Both behaviours are automatically supported in parsing, by parsing all string as `(eitherFunc id eq) 'someValue'`. " + + "The type system is then able to figure out which implementation is needed an discards the unneeded implementations.\n\n" + "Disclaimer: _you should never ever need this in your profiles_"; public override List ArgNames { get; } = new List{"f","g","a"}; diff --git a/AspectedRouting/Language/Functions/FirstMatchOf.cs b/AspectedRouting/Language/Functions/FirstMatchOf.cs index 136e407..a2c6c82 100644 --- a/AspectedRouting/Language/Functions/FirstMatchOf.cs +++ b/AspectedRouting/Language/Functions/FirstMatchOf.cs @@ -8,15 +8,18 @@ namespace AspectedRouting.Language.Functions { public class FirstMatchOf : Function { - public override string Description { get; } = "This higherorder function takes a list of keys, a mapping (function over tags) and a collection of tags. It will try the function for the first key (and it's respective value). If the function fails (it gives null), it'll try the next key.\n\n" + - "E.g. `$firstMatchOf ['maxspeed','highway'] {'maxspeed' --> $parse, 'highway' --> {residential --> 30, tertiary --> 50}}` applied on `{maxspeed=70, highway=tertiary}` will yield `70` as that is the first key in the list; `{highway=residential}` will yield `30`."; + public override string Description { get; } = "This higher-order function takes a list of keys, a mapping (function over tags) and a collection of tags." + + "It will try the function for the first key (and it's respective value). If the function fails (it gives null), it'll try the next key.\n\n" + + "E.g. `$firstMatchOf ['maxspeed','highway'] {'maxspeed' --> $parse, 'highway' --> {residential --> 30, tertiary --> 50}}` applied on `{maxspeed=70, highway=tertiary}`" + + " will yield `70` as that is the first key in the list; `{highway=residential}` will yield `30`."; public override List ArgNames { get; } = new List {"s"}; public FirstMatchOf() : base("first_match_of", true, new[] { // [String] -> (Tags -> [a]) -> Tags -> a - Curry.ConstructFrom(new Var("a"), // Result type on top! + Curry.ConstructFrom( + new Var("a"), // Result type on top! new ListType(Typs.String), new Curry(Typs.Tags, new ListType(new Var("a"))), Typs.Tags diff --git a/AspectedRouting/Language/Functions/If.cs b/AspectedRouting/Language/Functions/If.cs index d7cb4e9..a19577c 100644 --- a/AspectedRouting/Language/Functions/If.cs +++ b/AspectedRouting/Language/Functions/If.cs @@ -10,8 +10,9 @@ namespace AspectedRouting.Language.Functions private static Var b = new Var("b"); public override string Description { get; } = "Selects either one of the branches, depending on the condition." + - " The 'then' branch is returned if the condition returns the string `yes` or `true`. Otherwise, the `else` branch is taken (including if the condition returns `null`)" + - "If the `else` branch is not set, `null` is returned in the condition is false."; + " The 'then' branch is returned if the condition returns the string `yes` or `true`." + + " Otherwise, the `else` branch is taken (including if the condition returns `null`)" + + "If the `else` branch is not set, `null` is returned if the condition evaluates to false."; public override List ArgNames { get; } = new List {"condition", "then", "else"}; public If() : base("if_then_else", true, diff --git a/AspectedRouting/Language/Functions/MemberOf.cs b/AspectedRouting/Language/Functions/MemberOf.cs index 28d6c50..b26e67e 100644 --- a/AspectedRouting/Language/Functions/MemberOf.cs +++ b/AspectedRouting/Language/Functions/MemberOf.cs @@ -28,7 +28,7 @@ namespace AspectedRouting.Language.Functions " a flag `_relation:=\"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::=yes'. The subfunction is thus executed `countOr(relations) * countOf(profiles)` time, yielding `countOf(profiles)` tags." + + "`_relation::=yes'. The subfunction is thus executed `countOf(relations) * countOf(profiles)` time, yielding `countOf(profiles)` tags." + " The profile function then picks the tags for himself and strips the `:` away from the key.\n\n" + "\n\n" + "In the test.csv, one can simply use `_relation:=yes` to mimic relations in your tests"; @@ -49,19 +49,23 @@ namespace AspectedRouting.Language.Functions // In the case of tests, relations might be added with "_relation:1:" // So, we create this table as dictionary var relationTags = new Dictionary>(); - foreach (var tag in tags) { - if (tag.Key.StartsWith("_relation:")) { - var keyParts = tag.Key.Split(":"); - if (keyParts.Length != 3) { - continue; - } - var relationName = keyParts[1]; - if (!relationTags.ContainsKey(relationName)) { - relationTags.Add(relationName, new Dictionary()); - } - - relationTags[relationName].Add(keyParts[2], tag.Value); + foreach (var tag in tags) + { + if (!tag.Key.StartsWith("_relation:")) + { + continue; } + + var keyParts = tag.Key.Split(":"); + if (keyParts.Length != 3) { + continue; + } + var relationName = keyParts[1]; + if (!relationTags.ContainsKey(relationName)) { + relationTags.Add(relationName, new Dictionary()); + } + + relationTags[relationName].Add(keyParts[2], tag.Value); } foreach (var relationTagging in relationTags) { From 46897604169722b84bf3e45bae7971fc13e37f86 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 5 Jul 2022 11:25:11 +0200 Subject: [PATCH 3/6] Add check that priority is always between -100 and +100, see https://github.com/itinero/routing2/issues/30 --- AspectedRouting/Tests/AspectTestSuite.cs | 9 ++++++--- AspectedRouting/Tests/ProfileTestSuite.cs | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/AspectedRouting/Tests/AspectTestSuite.cs b/AspectedRouting/Tests/AspectTestSuite.cs index 236bd96..dd1fb5d 100644 --- a/AspectedRouting/Tests/AspectTestSuite.cs +++ b/AspectedRouting/Tests/AspectTestSuite.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using AspectedRouting.Language; using AspectedRouting.Language.Expression; @@ -106,9 +107,11 @@ namespace AspectedRouting.Tests continue; } - if (!actual.ToString().Equals(test.expected) && - !(actual is double actualD && Math.Abs(double.Parse(test.expected) - actualD) < 0.0001) - ) { + + var doesMatch = (actual is double d && Math.Abs(double.Parse(test.expected) - d) < 0.0001) + || actual.ToString().Equals(test.expected); + + if (!doesMatch) { failed = true; Console.WriteLine( $"[{FunctionToApply.Name}] Line {testCase + 1} failed:\n Expected: {test.expected}\n actual: {actual}\n tags: {test.tags.Pretty()}\n"); diff --git a/AspectedRouting/Tests/ProfileTestSuite.cs b/AspectedRouting/Tests/ProfileTestSuite.cs index ab71a11..cc853e3 100644 --- a/AspectedRouting/Tests/ProfileTestSuite.cs +++ b/AspectedRouting/Tests/ProfileTestSuite.cs @@ -183,6 +183,14 @@ namespace AspectedRouting.Tests success = false; } + if (actual.Priority >= 100 || actual.Priority <= -100) + { + Err($"priority is not within range of -100 and +100. This is needed due to a bug in Itinero2.0, see https://github.com/itinero/routing2/issues/30", + actual.Priority + " < 100 && -100 < "+actual.Priority, + actual.Priority); + success = false; + } + if (!success) { From 1bfe2653727d27436900bcf099e4febc953036f5 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 5 Jul 2022 11:25:48 +0200 Subject: [PATCH 4/6] Add test + formatting --- AspectedRouting.Test/FunctionsTest.cs | 763 ++++++++++++++------------ 1 file changed, 416 insertions(+), 347 deletions(-) diff --git a/AspectedRouting.Test/FunctionsTest.cs b/AspectedRouting.Test/FunctionsTest.cs index 2d51717..735f1f9 100644 --- a/AspectedRouting.Test/FunctionsTest.cs +++ b/AspectedRouting.Test/FunctionsTest.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using AspectedRouting.IO.jsonParser; @@ -7,372 +8,440 @@ using AspectedRouting.Language.Functions; using AspectedRouting.Language.Typ; using Xunit; -namespace AspectedRouting.Test +namespace AspectedRouting.Test; + +public class FunctionsTest { - public class FunctionsTest + private readonly string constString = "{\"$const\": \"a\"}"; + + private readonly string IfDottedConditionJson + = "{" + + "\"$ifdotted\": {\"$eq\": \"yes\"}," + + "\"then\":{\"$const\": \"a\"}," + + "\"else\": {\"$const\": \"b\"}" + + "}"; + + private readonly string IfSimpleConditionJson + = "{" + + "\"$if\": true," + + "\"then\":\"thenResult\"," + + "\"else\": \"elseResult\"}"; + + private IExpression MustMatchJson() { - private readonly string constString = "{\"$const\": \"a\"}"; + var json = "{" + + "\"name\":\"test\"," + + "\"description\":\"test\"," + + "\"$mustMatch\":{\"a\":\"b\",\"x\":\"y\"}}"; + return JsonParser.AspectFromJson(new Context(), json, "test.json"); + } - private readonly string IfDottedConditionJson - = "{" + - "\"$ifdotted\": {\"$eq\": \"yes\"}," + - "\"then\":{\"$const\": \"a\"}," + - "\"else\": {\"$const\": \"b\"}" + - "}"; + private IExpression MustMatchJsonWithOr() + { + var json = "{" + + "\"name\":\"test\"," + + "\"description\":\"test\"," + + "\"$mustMatch\":{\"a\":\"b\",\"x\":\"y\"}}"; + return JsonParser.AspectFromJson(new Context(), json, "test.json"); + } - private readonly string IfSimpleConditionJson - = "{" + - "\"$if\": true," + - "\"then\":\"thenResult\"," + - "\"else\": \"elseResult\"}"; - - private IExpression MustMatchJson() + [Fact] + public void TestAll_AllTags_Yes() + { + var tagsAx = new Dictionary { - var json = "{" + - "\"name\":\"test\"," + - "\"description\":\"test\"," + - "\"$mustMatch\":{\"a\":\"b\",\"x\":\"y\"}}"; - return JsonParser.AspectFromJson(new Context(), json, "test.json"); - } + { "a", "b" }, + { "x", "y" } + }; - private IExpression MustMatchJsonWithOr() + 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 { - var json = "{" + - "\"name\":\"test\"," + - "\"description\":\"test\"," + - "\"$mustMatch\":{\"a\":\"b\",\"x\":\"y\"}}"; - return JsonParser.AspectFromJson(new Context(), json, "test.json"); - } + { "a", "b" } + }; - [Fact] - public void TestAll_AllTags_Yes() + 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 { - var tagsAx = new Dictionary { - { "a", "b" }, - { "x", "y" } - }; + { "a", "b" }, + { "x", "someRandomValue" } + }; - var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize(); - var result = expr.Evaluate(new Context()); - Assert.Equal("yes", result); - } + var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize(); + var result = expr.Evaluate(new Context()); + Assert.Equal("no", result); + } - [Fact] - public void TestAll_NoMatch_No() + [Fact] + public void TestParsing_SimpleIf_CorrectExpression() + { + var c = new Context(); + var ifExpr = JsonParser.ParseExpression(c, IfSimpleConditionJson); + + Assert.Single(ifExpr.Types); + Assert.Equal(ifExpr.Types.First(), Typs.String); + + var resultT = ifExpr.Evaluate(c); + Assert.Equal("thenResult", resultT); + resultT = ifExpr.Optimize().Evaluate(c); + Assert.Equal("thenResult", resultT); + } + + [Fact] + public void TestEvaluate_DottedIf_CorrectExpression() + { + var ifExpr = Funcs.IfDotted.Apply( + Funcs.Eq.Apply(new Constant("abc")), + Funcs.Const.Apply(new Constant("a")), + Funcs.Const.Apply(new Constant("b")) + ); + + var c = new Context(); + var ifResultMatch = ifExpr.Evaluate(c, new Constant("abc")); + Assert.Equal("a", ifResultMatch); + + var ifResultNoMatch = ifExpr.Evaluate(c, new Constant("def")); + Assert.Equal("b", ifResultNoMatch); + } + + [Fact] + public void TestParsing_DottedIf_CorrectExpression() + { + var c = new Context(); + var ifExpr = JsonParser.ParseExpression(c, IfDottedConditionJson); + ifExpr = ifExpr.Optimize(); + var resultT = ifExpr.Evaluate(c, + new Constant(Typs.String, "yes")); + var resultF = ifExpr.Evaluate(c, + new Constant(Typs.String, "no")); + Assert.Equal("a", resultT); + Assert.Equal("b", resultF); + } + + [Fact] + public void Parse_ConstString_TypeIsFree() + { + var e = JsonParser.ParseExpression(new Context(), constString); + Assert.Single(e.Types); + Assert.Equal(new Curry(new Var("d"), Typs.String), e.Types.First()); + } + + + [Fact] + public void TypeInference_EitherIdConstConst_CorrectType() + { + /* + * id : a -> a + * dot: (b -> c) -> (a -> b) -> a -> c + * const - throw away b: a -> b -> a + * eitherFunc: (a -> b) -> (c -> d) -> (a -> b) + * eitherFunc: (a -> b) -> (c -> d) -> (c -> d) + + * + * All with free vars: + * id: a -> a + * dot: (b -> c) -> (x -> b) -> x -> c + * const: y -> z -> y + * eitherfunc: (d -> e) -> (f -> g) -> (d -> e) + * (d -> e) -> (f -> g) -> (f -> g) + */ + + /* + * (((eitherfunc id) dot) const) + * + * (eitherfunc id) + * [(d -> e) -> (f -> g) -> (d -> e)] (a -> a) + * [(d -> e) -> (f -> g) -> (f -> g)] (a -> a) + * + * Gives: + * d ~ a + * e ~ a + * thus: + * (f -> g) -> (a -> a) + * (f -> g) -> (f -> g) + * + * ((eitherfunc id) dot) + * [(f -> g) -> (a -> a)] ((b -> c) -> (x -> b) -> x -> c) + * [(f -> g) -> (f -> g)] (b -> c) -> (x -> b) -> (x -> c) + * + * Thus: (f -> g) ~ (b -> c) -> ((x -> b) -> x -> c) + * thus: f ~ (b -> c) + * g ~ ((x -> b) -> (x -> c)) + * thus: + * (a -> a) + * (b -> c) -> ((x -> b) -> (x -> c)) + * + * + * + * (((eitherfunc id) dot) const): + * [(a -> a)] (y -> (z -> y)) + * [(b -> c) -> ((x -> b) -> (x -> c))] (y -> (z -> y)) + * + * Thus: case 1: + * a ~ (y -> (z -> y) + * Type is: (y -> z -> y) === typeof(const) + * case2: + * (b -> c) ~ (y -> (z -> y)) + * thus: b ~ y + * c ~ (z -> y) + * ((x -> y) -> (x -> (z -> y)))) + * = ((x -> y) -> x -> z -> y === mix of dot and const + * + */ + + var a = new Var("a"); + var c = new Var("c"); + var d = new Var("d"); + + + var e = Funcs.Either(Funcs.Id, Funcs.Dot, Funcs.Const); + var types = e.Types.ToList(); + Assert.Equal(Curry.ConstructFrom(c, c, d), types[0]); + Assert.Equal(Curry.ConstructFrom( + c, // RESULT TYPE + new Curry(a, c), + a, d + ), types[1]); + } + + + [Fact] + public void RenameVars_Constant_ConstantType() + { + // Funcs.Const.RenameVars(noUse: ["a","b","d","e","f"] should give something like 'c -> g -> c' + var a = new Var("a"); + var b = new Var("b"); + + var c = new Var("c"); + var d = new Var("d"); + + var e = new Var("e"); + var f = new Var("f"); + var newTypes = Funcs.Const.Types.RenameVars(new[] { - var tagsAx = new Dictionary { - { "a", "b" } - }; + new Curry(e, e), + new Curry(new Curry(b, f), new Curry(new Curry(a, b), new Curry(a, f))) + }).ToList(); + Assert.Single(newTypes); + Assert.Equal(new Curry(c, new Curry(d, c)), + newTypes[0]); + } - var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize(); - var result = expr.Evaluate(new Context()); - Assert.Equal("no", result); - } + [Fact] + public void BuildSubstitution_TagsToStringTagsToBool_ShouldUnify() + { + var biggerType = new Curry(Typs.Tags, Typs.String); + var smallerType = new Curry(Typs.Tags, Typs.Bool); + // The expected type (biggerType) on the left, the argument type on the right (as it should be) + var unificationTable = biggerType.UnificationTable(smallerType); + Assert.NotNull(unificationTable); + unificationTable = smallerType.UnificationTable(biggerType); + Assert.Null(unificationTable); + } - [Fact] - public void TestAll_NoMatchDifferent_No() + [Fact] + public void BuildSubstitution_TagsToDoubleTagsToPDouble_ShouldUnify() + { + var biggerType = new Curry(Typs.Tags, Typs.Double); + var smallerType = new Curry(Typs.Tags, Typs.PDouble); + var unificationTable = biggerType.UnificationTable(smallerType); + Assert.NotNull(unificationTable); + unificationTable = smallerType.UnificationTable(biggerType); + Assert.Null(unificationTable); + } + + [Fact] + public void BuildSubstitution_DoubleToStringPDoubleToString_ShouldUnify() + { + var biggerType = new Curry(Typs.PDouble, Typs.Bool); + var smallerType = new Curry(Typs.Double, Typs.Bool); + // We expect something that is able to handle PDoubles, but it is able to handle the wider doubles - should be fine + var unificationTable = biggerType.UnificationTable(smallerType); + Assert.NotNull(unificationTable); + unificationTable = smallerType.UnificationTable(biggerType); + Assert.Null(unificationTable); + } + + [Fact] + public void Typechecker_EitherFunc_CorrectType() + { + var id = new Apply(Funcs.EitherFunc, Funcs.Id); + Assert.Equal(2, id.Types.Count()); + + var idconst = new Apply(id, Funcs.Const); + Assert.Equal(2, idconst.Types.Count()); + + var e = + new Apply(idconst, new Constant("a")); + Assert.Equal(2, e.Types.Count()); + } + + [Fact] + public void SpecializeToSmallest_Parse_SmallestType() + { + var smallest = Funcs.Parse.SpecializeToSmallestType(); + Assert.Single(smallest.Types); + Assert.Equal(new Curry(Typs.String, Typs.PDouble), smallest.Types.First()); + } + + [Fact] + public void Unify_TwoSubtypes_DoesNotUnify() + { + var tags2double = new Curry(Typs.Tags, Typs.Double); + var tags2pdouble = new Curry(Typs.Tags, Typs.PDouble); + var unifA = tags2double.Unify(tags2pdouble, true); + Assert.Null(unifA); + var unifB = tags2pdouble.Unify(tags2double, true); + Assert.NotNull(unifB); + + var unifC = tags2double.Unify(tags2pdouble); + Assert.NotNull(unifC); + var unifD = tags2pdouble.Unify(tags2double); + Assert.Null(unifD); + } + + + [Fact] + public void Specialize_WiderType_StillSmallerType() + { + var f = Funcs.Eq; + var strstrb = new Curry( + Typs.String, + new Curry(Typs.String, Typs.Bool)); + var f0 = f.Specialize(strstrb); + Assert.Equal(new[] { strstrb }, f0.Types); + + var strstrstr = new Curry( + Typs.String, + new Curry(Typs.String, Typs.String)); + + var f1 = f.Specialize(strstrstr); + + Assert.Equal(new[] { strstrb, strstrstr }, f1.Types); + } + + [Fact] + public void SpecializeToCommonType() + { + var p0 = Funcs.Parse.Specialize(new Curry(Typs.String, Typs.PDouble)); + var p1 = Funcs.Const.Apply(new Constant(1.0)).Specialize( + new Curry(new Var("a"), Typs.Double)); + + var exprs = new[] { p0, p1 }; + var newTypes = exprs.SpecializeToCommonTypes(out var _); + Assert.Single(newTypes); + + exprs = new[] { p1, p0 }; + newTypes = exprs.SpecializeToCommonTypes(out var _); + Assert.Single(newTypes); + } + + + [Fact] + public void ParseFunction_InvalidInput_NullOutput() + { + var f = Funcs.Parse; + var c = new Context(); + var result = f.Evaluate(c, new Constant("abc")); + + Assert.Null(result); + } + + [Fact] + public void ParseFunction_Duration_TotalMinutes() + { + var f = Funcs.Parse; + var c = new Context(); + var result = f.Evaluate(c, new Constant("01:15")); + + Assert.Equal(75.0, result); + } + + [Fact] + public void ApplyDefaultFunctionWithId_ApplicationIsSuccessfull() + { + var e = new Apply(new Apply(Funcs.Default, new Constant("a")), Funcs.Id); + Assert.Single(e.Types); + + Assert.Equal("string -> string", e.Types.First().ToString()); + } + + [Fact] + public void ApplyFirstMatchOf_FirstMatchIsTaken_50() + { + var tags0 = new Constant(new Dictionary { - var tagsAx = new Dictionary { - { "a", "b" }, - { "x", "someRandomValue" } - }; + { "highway", "residential" }, + { "maxspeed", "50" } + }); - var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize(); - var result = expr.Evaluate(new Context()); - Assert.Equal("no", result); - } - - [Fact] - public void TestParsing_SimpleIf_CorrectExpression() + var f = FirstMatchOfWithMaxspeedAndHighway(); + var o = f.Evaluate(new Context(), tags0); + Assert.Equal(50.0, o); + } + + + [Fact] + public void ApplyFirstMatchOf_FirstMatchIsTaken_ResidentialDefault() + { + var tags0 = new Constant(new Dictionary { - var c = new Context(); - var ifExpr = JsonParser.ParseExpression(c, IfSimpleConditionJson); + { "highway", "residential" } + }); - Assert.Single(ifExpr.Types); - Assert.Equal(ifExpr.Types.First(), Typs.String); - - var resultT = ifExpr.Evaluate(c); - Assert.Equal("thenResult", resultT); - resultT = ifExpr.Optimize().Evaluate(c); - Assert.Equal("thenResult", resultT); - } - - [Fact] - public void TestEvaluate_DottedIf_CorrectExpression() + var f = FirstMatchOfWithMaxspeedAndHighway(); + var o = f.Evaluate(new Context(), tags0); + Assert.Equal(30, o); + } + + [Fact] + public void ApplyFirstMatchOf_NoMatchIfFound_Null() + { + var tags0 = new Constant(new Dictionary { - var ifExpr = Funcs.IfDotted.Apply( - Funcs.Eq.Apply(new Constant("abc")), - Funcs.Const.Apply(new Constant("a")), - Funcs.Const.Apply(new Constant("b")) + { "highway", "unknown" } + }); + + var f = FirstMatchOfWithMaxspeedAndHighway(); + var o = f.Evaluate(new Context(), tags0); + Assert.Equal(null, o); + } + + public IExpression FirstMatchOfWithMaxspeedAndHighway() + { + var order = new Constant(new ListType(Typs.String), new List + { + new Constant("maxspeed"), + new Constant("highway") + }); + + var mapping = + Funcs.StringStringToTags.Apply( + new Mapping( + new List { "maxspeed", "highway" }, + new List + { + Funcs.Parse, + new Mapping( + new List { "residential", "primary" }, + new List { new Constant(30), new Constant(90) } + ) + }) ); - - var c = new Context(); - var ifResultMatch = ifExpr.Evaluate(c, new Constant("abc")); - Assert.Equal("a", ifResultMatch); - - var ifResultNoMatch = ifExpr.Evaluate(c, new Constant("def")); - Assert.Equal("b", ifResultNoMatch); - } - - [Fact] - public void TestParsing_DottedIf_CorrectExpression() - { - var c = new Context(); - var ifExpr = JsonParser.ParseExpression(c, IfDottedConditionJson); - ifExpr = ifExpr.Optimize(); - var resultT = ifExpr.Evaluate(c, - new Constant(Typs.String, "yes")); - var resultF = ifExpr.Evaluate(c, - new Constant(Typs.String, "no")); - Assert.Equal("a", resultT); - Assert.Equal("b", resultF); - } - - [Fact] - public void Parse_ConstString_TypeIsFree() - { - var e = JsonParser.ParseExpression(new Context(), constString); - Assert.Single(e.Types); - Assert.Equal(new Curry(new Var("d"), Typs.String), e.Types.First()); - } - - - [Fact] - public void TypeInference_EitherIdConstConst_CorrectType() - { - /* - * id : a -> a - * dot: (b -> c) -> (a -> b) -> a -> c - * const - throw away b: a -> b -> a - * eitherFunc: (a -> b) -> (c -> d) -> (a -> b) - * eitherFunc: (a -> b) -> (c -> d) -> (c -> d) - - * - * All with free vars: - * id: a -> a - * dot: (b -> c) -> (x -> b) -> x -> c - * const: y -> z -> y - * eitherfunc: (d -> e) -> (f -> g) -> (d -> e) - * (d -> e) -> (f -> g) -> (f -> g) - */ - - /* - * (((eitherfunc id) dot) const) - * - * (eitherfunc id) - * [(d -> e) -> (f -> g) -> (d -> e)] (a -> a) - * [(d -> e) -> (f -> g) -> (f -> g)] (a -> a) - * - * Gives: - * d ~ a - * e ~ a - * thus: - * (f -> g) -> (a -> a) - * (f -> g) -> (f -> g) - * - * ((eitherfunc id) dot) - * [(f -> g) -> (a -> a)] ((b -> c) -> (x -> b) -> x -> c) - * [(f -> g) -> (f -> g)] (b -> c) -> (x -> b) -> (x -> c) - * - * Thus: (f -> g) ~ (b -> c) -> ((x -> b) -> x -> c) - * thus: f ~ (b -> c) - * g ~ ((x -> b) -> (x -> c)) - * thus: - * (a -> a) - * (b -> c) -> ((x -> b) -> (x -> c)) - * - * - * - * (((eitherfunc id) dot) const): - * [(a -> a)] (y -> (z -> y)) - * [(b -> c) -> ((x -> b) -> (x -> c))] (y -> (z -> y)) - * - * Thus: case 1: - * a ~ (y -> (z -> y) - * Type is: (y -> z -> y) === typeof(const) - * case2: - * (b -> c) ~ (y -> (z -> y)) - * thus: b ~ y - * c ~ (z -> y) - * ((x -> y) -> (x -> (z -> y)))) - * = ((x -> y) -> x -> z -> y === mix of dot and const - * - */ - - var a = new Var("a"); - var c = new Var("c"); - var d = new Var("d"); - - - var e = Funcs.Either(Funcs.Id, Funcs.Dot, Funcs.Const); - var types = e.Types.ToList(); - Assert.Equal(Curry.ConstructFrom(c, c, d), types[0]); - Assert.Equal(Curry.ConstructFrom( - c, // RESULT TYPE - new Curry(a, c), - a, d - ), types[1]); - } - - - [Fact] - public void RenameVars_Constant_ConstantType() - { - // Funcs.Const.RenameVars(noUse: ["a","b","d","e","f"] should give something like 'c -> g -> c' - var a = new Var("a"); - var b = new Var("b"); - - var c = new Var("c"); - var d = new Var("d"); - - var e = new Var("e"); - var f = new Var("f"); - var newTypes = Funcs.Const.Types.RenameVars(new[] { - new Curry(e, e), - new Curry(new Curry(b, f), new Curry(new Curry(a, b), new Curry(a, f))) - }).ToList(); - Assert.Single(newTypes); - Assert.Equal(new Curry(c, new Curry(d, c)), - newTypes[0]); - } - - [Fact] - public void BuildSubstitution_TagsToStringTagsToBool_ShouldUnify() - { - var biggerType = new Curry(Typs.Tags, Typs.String); - var smallerType = new Curry(Typs.Tags, Typs.Bool); - // The expected type (biggerType) on the left, the argument type on the right (as it should be) - var unificationTable = biggerType.UnificationTable(smallerType); - Assert.NotNull(unificationTable); - unificationTable = smallerType.UnificationTable(biggerType); - Assert.Null(unificationTable); - } - - [Fact] - public void BuildSubstitution_TagsToDoubleTagsToPDouble_ShouldUnify() - { - var biggerType = new Curry(Typs.Tags, Typs.Double); - var smallerType = new Curry(Typs.Tags, Typs.PDouble); - var unificationTable = biggerType.UnificationTable(smallerType); - Assert.NotNull(unificationTable); - unificationTable = smallerType.UnificationTable(biggerType); - Assert.Null(unificationTable); - } - - [Fact] - public void BuildSubstitution_DoubleToStringPDoubleToString_ShouldUnify() - { - var biggerType = new Curry(Typs.PDouble, Typs.Bool); - var smallerType = new Curry(Typs.Double, Typs.Bool); - // We expect something that is able to handle PDoubles, but it is able to handle the wider doubles - should be fine - var unificationTable = biggerType.UnificationTable(smallerType); - Assert.NotNull(unificationTable); - unificationTable = smallerType.UnificationTable(biggerType); - Assert.Null(unificationTable); - } - - [Fact] - public void Typechecker_EitherFunc_CorrectType() - { - var id = new Apply(Funcs.EitherFunc, Funcs.Id); - Assert.Equal(2, id.Types.Count()); - - var idconst = new Apply(id, Funcs.Const); - Assert.Equal(2, idconst.Types.Count()); - - var e = - new Apply(idconst, new Constant("a")); - Assert.Equal(2, e.Types.Count()); - } - - [Fact] - public void SpecializeToSmallest_Parse_SmallestType() - { - var smallest = Funcs.Parse.SpecializeToSmallestType(); - Assert.Single(smallest.Types); - Assert.Equal(new Curry(Typs.String, Typs.PDouble), smallest.Types.First()); - } - - [Fact] - public void Unify_TwoSubtypes_DoesNotUnify() - { - var tags2double = new Curry(Typs.Tags, Typs.Double); - var tags2pdouble = new Curry(Typs.Tags, Typs.PDouble); - var unifA = tags2double.Unify(tags2pdouble, true); - Assert.Null(unifA); - var unifB = tags2pdouble.Unify(tags2double, true); - Assert.NotNull(unifB); - - var unifC = tags2double.Unify(tags2pdouble); - Assert.NotNull(unifC); - var unifD = tags2pdouble.Unify(tags2double); - Assert.Null(unifD); - } - - - [Fact] - public void Specialize_WiderType_StillSmallerType() - { - var f = Funcs.Eq; - var strstrb = new Curry( - Typs.String, - new Curry(Typs.String, Typs.Bool)); - var f0 = f.Specialize(strstrb); - Assert.Equal(new[] { strstrb }, f0.Types); - - var strstrstr = new Curry( - Typs.String, - new Curry(Typs.String, Typs.String)); - - var f1 = f.Specialize(strstrstr); - - Assert.Equal(new[] { strstrb, strstrstr }, f1.Types); - } - - [Fact] - public void SpecializeToCommonType() - { - var p0 = Funcs.Parse.Specialize(new Curry(Typs.String, Typs.PDouble)); - var p1 = Funcs.Const.Apply(new Constant(1.0)).Specialize( - new Curry(new Var("a"), Typs.Double)); - - var exprs = new[] { p0, p1 }; - var newTypes = exprs.SpecializeToCommonTypes(out var _); - Assert.Single(newTypes); - - exprs = new[] { p1, p0 }; - newTypes = exprs.SpecializeToCommonTypes(out var _); - Assert.Single(newTypes); - } - - - [Fact] - public void ParseFunction_InvalidInput_NullOutput() - { - var f = Funcs.Parse; - var c = new Context(); - var result = f.Evaluate(c, new Constant("abc")); - - Assert.Null(result); - } - - [Fact] - public void ParseFunction_Duration_TotalMinutes() - { - var f = Funcs.Parse; - var c = new Context(); - var result = f.Evaluate(c, new Constant("01:15")); - - Assert.Equal(75.0, result); - } - - [Fact] - public void ApplyDefaultFunctionWithId_ApplicationIsSuccessfull() - { - var e = new Apply(new Apply(Funcs.Default, new Constant("a")), Funcs.Id); - Assert.Single(e.Types); - - Assert.Equal("string -> string", e.Types.First().ToString()); - } + return Funcs.FirstOf.Apply(order, mapping); } } \ No newline at end of file From 2e5e0208db479a96a183d55ac96b04318490c8cd Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 5 Jul 2022 11:26:33 +0200 Subject: [PATCH 5/6] Update to dotnet 6.0 --- AspectedRouting.Test/AspectedRouting.Test.csproj | 12 ++++++------ AspectedRouting/AspectedRouting.csproj | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/AspectedRouting.Test/AspectedRouting.Test.csproj b/AspectedRouting.Test/AspectedRouting.Test.csproj index af03f82..7c88b82 100644 --- a/AspectedRouting.Test/AspectedRouting.Test.csproj +++ b/AspectedRouting.Test/AspectedRouting.Test.csproj @@ -1,20 +1,20 @@  - net5.0 + net6.0 false - - - - + + + + - + \ No newline at end of file diff --git a/AspectedRouting/AspectedRouting.csproj b/AspectedRouting/AspectedRouting.csproj index 12d4492..f224198 100644 --- a/AspectedRouting/AspectedRouting.csproj +++ b/AspectedRouting/AspectedRouting.csproj @@ -2,7 +2,7 @@ Exe - net5.0 + net6.0 8 AspectedRouting AspectedRouting From 1f27a45037c2fa7614a19f3f3cdc2bda84d1f4c2 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 5 Jul 2022 11:27:40 +0200 Subject: [PATCH 6/6] Ignore usersettigns --- .gitignore | 1 + AspectedRouting.sln.DotSettings.user | 22 ---------------------- 2 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 AspectedRouting.sln.DotSettings.user diff --git a/.gitignore b/.gitignore index db50176..18e5f91 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ .~lock.* output/* AspectedRouting.sln.DotSettings +AspectedRouting.sln.DotSettings.user diff --git a/AspectedRouting.sln.DotSettings.user b/AspectedRouting.sln.DotSettings.user deleted file mode 100644 index 0b2a2e5..0000000 --- a/AspectedRouting.sln.DotSettings.user +++ /dev/null @@ -1,22 +0,0 @@ - - ERROR - <SessionState ContinuousTestingMode="0" Name="DefaultSnippet_SimpleDefault_GetsLua" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <TestAncestor> - <TestId>xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.Snippets.SnippetTests</TestId> - <TestId>xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.TypingTests.SpecializeToCommonTypes_X2PDouble_Y2Double_Gives_X2Double</TestId> - </TestAncestor> -</SessionState> - <SessionState ContinuousTestingMode="0" Name="JoinApply_Id" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <TestAncestor> - <TestId>xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.TypingTests.JoinApply_Id</TestId> - <TestId>xUnit::A1309041-8AAE-42D7-A886-94C9FFC6A28C::.NETCoreApp,Version=v3.1::AspectedRouting.Test.FunctionsTest.ApplyDefaultFunctionWithId_ApplicationIsSuccessfull</TestId> - </TestAncestor> -</SessionState> - - <SessionState ContinuousTestingMode="0" IsActive="True" Name="Integration_TestExamples" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <Project Location="/home/pietervdvn/git/AspectedRouting/AspectedRouting.Test" Presentation="&lt;AspectedRouting.Test&gt;" /> -</SessionState> - <SessionState ContinuousTestingMode="0" Name="SpecializeToCommonTypes_ValueAndFuncType_ShouldFail" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <Project Location="/home/pietervdvn/git/AspectedRouting/AspectedRouting.Test" Presentation="&lt;AspectedRouting.Test&gt;" /> -</SessionState> - \ No newline at end of file