diff --git a/AspectedRouting.Test/FunctionsTest.cs b/AspectedRouting.Test/FunctionsTest.cs index e519c19..955b8dd 100644 --- a/AspectedRouting.Test/FunctionsTest.cs +++ b/AspectedRouting.Test/FunctionsTest.cs @@ -1,8 +1,10 @@ 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 @@ -59,5 +61,270 @@ namespace AspectedRouting.Test var result = expr.Evaluate(new Context()); Assert.Equal("no", result); } + + private string IfDottedConditionJson + = "{" + + "\"$ifdotted\": {\"$eq\": \"yes\"}," + + "\"then\":{\"$const\": \"a\"}," + + "\"else\": {\"$const\": \"b\"}" + + "}"; + + private string IfSimpleConditionJson + = "{" + + "\"$if\": true," + + "\"then\":\"thenResult\"," + + "\"else\": \"elseResult\"}"; + + [Fact] + public void TestParsing_SimpleIf_CorrectExpression() + { + 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); + } + + + private string constString = "{\"$const\": \"a\"}"; + + [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, false); + Assert.NotNull(unifC); + var unifD = tags2pdouble.Unify(tags2double, false); + 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); + + } } } \ No newline at end of file diff --git a/AspectedRouting.Test/TestAnalysis.cs b/AspectedRouting.Test/TestAnalysis.cs index 2f51e0f..7996dd5 100644 --- a/AspectedRouting.Test/TestAnalysis.cs +++ b/AspectedRouting.Test/TestAnalysis.cs @@ -8,43 +8,6 @@ namespace AspectedRouting.Test { public class TestAnalysis { - [Fact] - public void OnAll_SmallTagSet_AllCombinations() - { - var possibleTags = new Dictionary> - { - {"a", new List {"x", "y"}} - }; - var all = possibleTags.OnAllCombinations(dict => ObjectExtensions.Pretty(dict), new List()).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> - { - {"a", new List {"x", "y"}}, - {"b", new List {"u", "v"}} - }; - - var all = possibleTags.OnAllCombinations(dict => dict.Pretty(), new List()).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); - } } } \ No newline at end of file diff --git a/AspectedRouting.Test/TestInterpreter.cs b/AspectedRouting.Test/TestInterpreter.cs index 0062f21..1a3012b 100644 --- a/AspectedRouting.Test/TestInterpreter.cs +++ b/AspectedRouting.Test/TestInterpreter.cs @@ -25,7 +25,7 @@ namespace AspectedRouting.Test {"ferry", "yes"} }; - Assert.Equal("tags -> pdouble", string.Join(", ", aspect.Types)); + Assert.Equal("tags -> double", string.Join(", ", aspect.Types)); Assert.Equal(42d, new Apply(aspect, new Constant(tags)).Evaluate(null)); } @@ -33,16 +33,18 @@ namespace AspectedRouting.Test 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}}"; + "{\"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> + new Dictionary> { - {"maxspeed", new List()}, - {"highway", new List {"residential"}}, - {"ferry", new List()} + {"maxspeed", new HashSet()}, + {"highway", new HashSet {"residential"}}, + {"ferry", new HashSet()} }, aspect.PossibleTags()); } @@ -178,5 +180,14 @@ namespace AspectedRouting.Test Assert.Equal(new List {Funcs.Id, Funcs.Id, a}.Select(e => e.ToString()), args.Select(e => e.ToString())); } + + [Fact] + public void SpecializeToSmallest_IsSmallestType() + { + var app = new Apply(Funcs.Id, Funcs.Parse); + Assert.Equal(2, app.Types.Count()); + var smaller = app.SpecializeToSmallestType(); + Assert.Single(smaller.Types); + } } } \ No newline at end of file diff --git a/AspectedRouting/AspectedRouting.csproj b/AspectedRouting/AspectedRouting.csproj index 5f918b6..16b890c 100644 --- a/AspectedRouting/AspectedRouting.csproj +++ b/AspectedRouting/AspectedRouting.csproj @@ -15,6 +15,9 @@ PreserveNewest + + PreserveNewest + diff --git a/AspectedRouting/IO/LuaSkeleton/LuaLiteral.cs b/AspectedRouting/IO/LuaSkeleton/LuaLiteral.cs new file mode 100644 index 0000000..0bd1449 --- /dev/null +++ b/AspectedRouting/IO/LuaSkeleton/LuaLiteral.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using AspectedRouting.Language; +using Type = AspectedRouting.Language.Typ.Type; + +namespace AspectedRouting.IO.LuaSkeleton +{ + public class LuaLiteral : IExpression + { + public readonly string Lua; + public IEnumerable Types { get; } + + public LuaLiteral(IEnumerable types, string lua) + { + Lua = lua; + Types = types; + } + + public object Evaluate(Context c, params IExpression[] arguments) + { + throw new NotImplementedException(); + } + + public IExpression Specialize(IEnumerable allowedTypes) + { + return this; + } + + public IExpression Optimize() + { + return this; + } + + public IExpression OptimizeWithArgument(IExpression argument) + { + return this.Apply(argument); + } + + public void Visit(Func f) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Expressions.cs b/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Expressions.cs index aab59d6..e8a1056 100644 --- a/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Expressions.cs +++ b/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Expressions.cs @@ -143,6 +143,14 @@ namespace AspectedRouting.IO.LuaSkeleton { var (f, args) = fArgs.Value; var baseFunc = (Function) f; + + if (baseFunc.Name.Equals(Funcs.Id.Name) || + baseFunc.Name.Equals(Funcs.Dot.Name)) + { + // This is an ugly hack + return ToLua(args.First()); + } + AddDep(baseFunc.Name); return baseFunc.Name + "(" + string.Join(", ", args.Select(arg => ToLua(arg, key))) + ")"; @@ -152,6 +160,8 @@ namespace AspectedRouting.IO.LuaSkeleton var collected = new List(); switch (bare) { + case LuaLiteral lua: + return lua.Lua; case FunctionCall fc: var called = _context.DefinedFunctions[fc.CalledFunctionName]; if (called.ProfileInternal) @@ -226,6 +236,8 @@ namespace AspectedRouting.IO.LuaSkeleton var o = c.Evaluate(_context); switch (o) { + case LuaLiteral lua: + return lua.Lua; case IExpression e: return ConstantToLua(new Constant(e.Types.First(), e.Evaluate(null))); case int i: diff --git a/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Function.cs b/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Function.cs index 3e616db..9797642 100644 --- a/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Function.cs +++ b/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Function.cs @@ -19,7 +19,7 @@ namespace AspectedRouting.IO.LuaSkeleton _alreadyAddedFunctions.Add(meta.Name); - var possibleTags = meta.PossibleTags() ?? new Dictionary>(); + var possibleTags = meta.PossibleTags() ?? new Dictionary>(); int numberOfCombinations; numberOfCombinations = possibleTags.Values.Select(lst => 1 + lst.Count).Multiply(); diff --git a/AspectedRouting/IO/itinero1/LuaPrinter1.MainFunction.cs b/AspectedRouting/IO/itinero1/LuaPrinter1.MainFunction.cs index ce10936..cd41ac3 100644 --- a/AspectedRouting/IO/itinero1/LuaPrinter1.MainFunction.cs +++ b/AspectedRouting/IO/itinero1/LuaPrinter1.MainFunction.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using AspectedRouting.IO.LuaSkeleton; using AspectedRouting.Language; using AspectedRouting.Language.Functions; using AspectedRouting.Language.Typ; @@ -12,6 +13,11 @@ namespace AspectedRouting.IO.itinero1 private string GenerateMainProfileFunction() { + + var access = _skeleton.ToLua(_profile.Access); + var oneway = _skeleton.ToLua(_profile.Oneway); + var speed = _skeleton.ToLua(_profile.Speed); + var impl = string.Join("\n", "", "", @@ -33,14 +39,14 @@ namespace AspectedRouting.IO.itinero1 " result.canstop = true", " result.attributes_to_keep = {}", "", - " local access = " + _skeleton.ToLua(_profile.Access), + " local access = " + access, " if (access == nil or access == \"no\") then", " return", " end", " tags.access = access", - " local oneway = " + _skeleton.ToLua(_profile.Oneway), + " local oneway = " + oneway, " tags.oneway = oneway", - " local speed = " + _skeleton.ToLua(_profile.Speed), + " local speed = " + speed, " tags.speed = speed", " local distance = 1 -- the weight per meter for distance travelled is, well, 1m/m", ""); diff --git a/AspectedRouting/IO/itinero1/LuaPrinter1.cs b/AspectedRouting/IO/itinero1/LuaPrinter1.cs index 55e4271..18f1fb0 100644 --- a/AspectedRouting/IO/itinero1/LuaPrinter1.cs +++ b/AspectedRouting/IO/itinero1/LuaPrinter1.cs @@ -53,7 +53,7 @@ namespace AspectedRouting.IO.itinero1 "normalize = false", "vehicle_type = {" + string.Join(", ", _profile.VehicleTyps.Select(s => "\"" + s + "\"")) + "}", // meta_whitelist is defined in the profile file, these are tags that are included in the generated route, but are not relevant for determining weights - "meta_whitelist = {\n \"cycle_network_colour\"," // cycle network colour is sneaked in here for legacy reasons + "meta_whitelist = {\n \"cycle_network_colour\",\n " // cycle network colour is sneaked in here for legacy reasons + string.Join("\n , ", _profile.Metadata.Select(s => "\"" + s + "\"")) + " }", "profile_whitelist = {\n " + string.Join("\n , ", keys) + "\n }", diff --git a/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs b/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs index 123cb4f..18f6665 100644 --- a/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs +++ b/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs @@ -23,6 +23,8 @@ namespace AspectedRouting.IO.jsonParser // this is a profile return null; } + + Console.WriteLine("Parsing " + fileName); return doc.RootElement.ParseAspect(fileName, c); } @@ -51,6 +53,15 @@ namespace AspectedRouting.IO.jsonParser } } + /** + * Mostly used in the unit tests + */ + [Obsolete] + public static IExpression ParseExpression(Context c, string json) + { + return JsonDocument.Parse(json).RootElement.ParseExpression(c); + } + private static ProfileMetaData ParseProfile(this JsonElement e, Context context, FileInfo filepath) { @@ -113,7 +124,7 @@ namespace AspectedRouting.IO.jsonParser var weights = new Dictionary(); - + var weightProperty = GetTopProperty("priority"); foreach (var prop in weightProperty.EnumerateObject()) { @@ -188,10 +199,10 @@ namespace AspectedRouting.IO.jsonParser if (keys.Count == 1) { // We can interpret this directly without going through a list - - wrapped = Funcs.Either(Funcs.Id, - new Apply(Funcs.Dot, Funcs.Head), - wrapped); + + wrapped = Funcs.Either(Funcs.Id, + new Apply(Funcs.Dot, Funcs.Head), + wrapped); } return wrapped; @@ -229,7 +240,8 @@ namespace AspectedRouting.IO.jsonParser if (e.ValueKind == JsonValueKind.Array) { var exprs = e.EnumerateArray().Select(json => - Funcs.Either(Funcs.Id, Funcs.Const, json.ParseExpression(context))); + Funcs.Either(Funcs.Id, Funcs.Const, json.ParseExpression(context))) + .ToList(); var list = new Constant(exprs); return Funcs.Either(Funcs.Id, Funcs.ListDot, list); } @@ -318,20 +330,21 @@ namespace AspectedRouting.IO.jsonParser 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.GetRange(1, func.ArgNames.Count - 2)) + + foreach (var argName in func.ArgNames.GetRange(1, func.ArgNames.Count - 1)) // We skip the first argument, that one is already added { args.Add(allExprs[argName]); @@ -363,14 +376,14 @@ namespace AspectedRouting.IO.jsonParser var f = (IExpression) Funcs.BuiltinByName(prop.Name); - if (f == null) + if (f == null || f.Types.Count() == 0) { throw new KeyNotFoundException($"The builtin function {prop.Name} was not found"); } var fArg = prop.Value.ParseExpression(context); - if (fArg == null) + if (fArg == null || fArg.Types.Count() == 0) { throw new ArgumentException("Could not type expression " + prop); } @@ -465,6 +478,9 @@ namespace AspectedRouting.IO.jsonParser throw new NullReferenceException("The finalized form of expression `" + exprSpec + "` is null"); } + expr = expr.SpecializeToSmallestType(); + + var name = e.Get("name"); if (expr.Types.Count() > 1) { diff --git a/AspectedRouting/IO/jsonParser/JsonParser.cs b/AspectedRouting/IO/jsonParser/JsonParser.cs index aaa10dc..d5903f0 100644 --- a/AspectedRouting/IO/jsonParser/JsonParser.cs +++ b/AspectedRouting/IO/jsonParser/JsonParser.cs @@ -12,17 +12,23 @@ namespace AspectedRouting.IO.jsonParser { private static IExpression ParseProfileProperty(JsonElement e, Context c, string property) { - if (!e.TryGetProperty(property, out var prop)) { throw new ArgumentException("Not a valid profile: the declaration expression for '" + property + "' is missing"); } + try { - var expr = ParseExpression(prop, c).Optimize(); + var expr = ParseExpression(prop, c); + if (expr.Types.Count() == 0) + { + throw new Exception($"Could not parse field {property}, no valid typing for expression found"); + } + + expr = expr.Optimize(); expr = Funcs.Either(Funcs.Id, Funcs.Const, expr); - + var specialized = expr.Specialize(new Curry(Typs.Tags, new Var("a"))); if (specialized == null) { @@ -30,6 +36,7 @@ namespace AspectedRouting.IO.jsonParser " hasn't the right type of 'Tags -> a'; it has types " + string.Join(",", expr.Types) + "\n" + expr.TypeBreakdown()); } + return specialized.Optimize(); } catch (Exception exc) @@ -69,7 +76,7 @@ namespace AspectedRouting.IO.jsonParser break; case JsonValueKind.Array: var list = obj.Value.EnumerateArray().Select(x => x.ToString()).ToList(); - ps[nm] = new Constant(new ListType(Typs.String),list); + ps[nm] = new Constant(new ListType(Typs.String), list); break; default: throw new ArgumentException( @@ -81,8 +88,6 @@ namespace AspectedRouting.IO.jsonParser return ps; } - - private static string Get(this JsonElement json, string key) { diff --git a/AspectedRouting/IO/lua/if_then_else.lua b/AspectedRouting/IO/lua/if_then_else.lua index 1250f28..02fad15 100644 --- a/AspectedRouting/IO/lua/if_then_else.lua +++ b/AspectedRouting/IO/lua/if_then_else.lua @@ -1,5 +1,5 @@ function if_then_else(condition, thn, els) - if (condition) then + if (condition ~= nil and (condition == "yes" or condition == true or condition == "true") then return thn else return els -- if no third parameter is given, 'els' will be nil diff --git a/AspectedRouting/IO/lua/if_then_else_dotted.lua b/AspectedRouting/IO/lua/if_then_else_dotted.lua new file mode 100644 index 0000000..9b0fe42 --- /dev/null +++ b/AspectedRouting/IO/lua/if_then_else_dotted.lua @@ -0,0 +1,11 @@ +function if_then_else_dotted(conditionf, thnf, elsef, arg) + local condition = conditionf(arg); + if (condition) then + return thnf(arg) + else + if(elsef == nil) then + return nil + end + return elsef(arg) -- if no third parameter is given, 'els' will be nil + end +end \ No newline at end of file diff --git a/AspectedRouting/Language/Analysis.cs b/AspectedRouting/Language/Analysis.cs index 88030a3..5d600ef 100644 --- a/AspectedRouting/Language/Analysis.cs +++ b/AspectedRouting/Language/Analysis.cs @@ -10,121 +10,6 @@ namespace AspectedRouting.Language { public static class Analysis { - public static string GenerateFullOutputCsv(Context c, IExpression e) - { - var possibleTags = e.PossibleTags(); - - var defaultValues = new List - { - "0", - "30", - "50", - "yes", - "no", - "SomeName" - }; - - Console.WriteLine(e); - var keys = e.PossibleTags().Keys.ToList(); - - - var results = possibleTags.OnAllCombinations( - tags => - { - Console.WriteLine(tags.Pretty()); - return (new Apply(e, new Constant(tags)).Evaluate(c), tags); - }, defaultValues).ToList(); - - var csv = "result, " + string.Join(", ", keys) + "\n"; - - foreach (var (result, tags) in results) - { - csv += result + ", " + - string.Join(", ", - keys.Select(key => - { - if (tags.ContainsKey(key)) - { - return tags[key]; - } - else - { - return ""; - } - })); - csv += "\n"; - } - - return csv; - } - - public static IEnumerable OnAllCombinations(this Dictionary> possibleTags, - Func, T> f, List defaultValues) - { - var newDict = new Dictionary>(); - foreach (var (key, value) in possibleTags) - { - if (value.Count == 0) - { - // This value is a list of possible values, e.g. a double - // We replace them with various other - newDict[key] = defaultValues; - } - else - { - newDict[key] = value; - } - } - - possibleTags = newDict; - - var keys = possibleTags.Keys.ToList(); - var currentKeyIndex = new int[possibleTags.Count]; - for (int i = 0; i < currentKeyIndex.Length; i++) - { - currentKeyIndex[i] = -1; - } - - bool SelectNext() - { - var j = 0; - while (j < currentKeyIndex.Length) - { - currentKeyIndex[j]++; - if (currentKeyIndex[j] == - possibleTags[keys[j]].Count) - { - // This index rolls over - currentKeyIndex[j] = -1; - j++; - } - else - { - return true; - } - } - - return false; - } - - - do - { - var tags = new Dictionary(); - for (int i = 0; i < keys.Count(); i++) - { - var key = keys[i]; - var j = currentKeyIndex[i]; - if (j >= 0) - { - var value = possibleTags[key][j]; - tags.Add(key, value); - } - } - - yield return f(tags); - } while (SelectNext()); - } public static Dictionary Types, string inFunction)> UsedParameters( this ProfileMetaData profile, Context context) @@ -492,7 +377,7 @@ namespace AspectedRouting.Language /// /// /// A dictionary containing all possible values. An entry with an empty list indicates a wildcard - public static Dictionary> PossibleTags(this IExpression e) + public static Dictionary> PossibleTags(this IExpression e) { var mappings = new List(); e.Visit(x => @@ -524,7 +409,7 @@ namespace AspectedRouting.Language // Visit will have the main mapping at the first position var rootMapping = mappings[0]; - var result = new Dictionary>(); + var result = new Dictionary>(); foreach (var (key, expr) in rootMapping.StringToResultFunctions) { @@ -538,7 +423,7 @@ namespace AspectedRouting.Language return true; }); - result[key] = values; + result[key] = values.ToHashSet(); } return result; diff --git a/AspectedRouting/Language/Expression/Apply.cs b/AspectedRouting/Language/Expression/Apply.cs index ada9cec..a13a072 100644 --- a/AspectedRouting/Language/Expression/Apply.cs +++ b/AspectedRouting/Language/Expression/Apply.cs @@ -38,8 +38,7 @@ namespace AspectedRouting.Language.Expression FunctionApplications = new Dictionary(); - var argTypesCleaned = argument.Types.RenameVars(f.Types); - var typesCleaned = argTypesCleaned.ToList(); + var typesCleaned = argument.Types.RenameVars(f.Types).ToList(); foreach (var funcType in f.Types) { if (!(funcType is Curry c)) @@ -133,19 +132,24 @@ namespace AspectedRouting.Language.Expression { foreach (var (resultType, (funExpr, argExpr)) in FunctionApplications) { - var substitutions = resultType.UnificationTable(allowedType); + var substitutions = resultType.UnificationTable(allowedType, true); if (substitutions == null) { continue; } - var actualResultType = resultType.Substitute(substitutions); + var actualResultType = allowedType.Substitute(substitutions); + + // f : a -> b + // actualResultType = b (b which was retrieved after a reverse substitution) + var actualFunction = funExpr.Specialize(substitutions); var actualArgument = argExpr.Specialize(substitutions); if (actualFunction == null || actualArgument == null) { - continue; + // One of the subexpressions can't be optimized + return null; } newArgs[actualResultType] = (actualFunction, actualArgument); @@ -162,6 +166,11 @@ namespace AspectedRouting.Language.Expression public IExpression Optimize() { + if (Types.Count() == 0) + { + throw new ArgumentException("This application contain no valid types, so cannot be optimized" + this); + } + // (eitherfunc dot id) id // => (const dot _) id => dot id => id // or => (constRight _ id) id => id id => id @@ -185,8 +194,6 @@ namespace AspectedRouting.Language.Expression return Funcs.Id; } - - if (Types.Count() > 1) { @@ -200,7 +207,7 @@ namespace AspectedRouting.Language.Expression return new Apply(_debugInfo, optimized); } - + { var arg = new List(); if ( @@ -231,8 +238,8 @@ namespace AspectedRouting.Language.Expression IExpression a) { f = f.Optimize(); - a = a.Optimize(); + a = a.Optimize(); switch (f) { @@ -309,7 +316,13 @@ namespace AspectedRouting.Language.Expression return arg.ToString(); } - return $"({f} {arg.ToString().Indent()})"; + var extra = ""; + if (FunctionApplications.Count() > 1) + { + extra = " [" + FunctionApplications.Count + " IMPLEMENTATIONS]"; + } + + return $"({f} {arg.ToString().Indent()})" + extra; } } } \ No newline at end of file diff --git a/AspectedRouting/Language/Expression/Function.cs b/AspectedRouting/Language/Expression/Function.cs index 042d1a9..cf36a7d 100644 --- a/AspectedRouting/Language/Expression/Function.cs +++ b/AspectedRouting/Language/Expression/Function.cs @@ -40,6 +40,11 @@ namespace AspectedRouting.Language.Expression return this; } + public IExpression OptimizeWithArgument(IExpression argument) + { + return this.Apply(argument); + } + public virtual void Visit(Func f) { f(this); @@ -50,10 +55,6 @@ namespace AspectedRouting.Language.Expression return $"${Name}"; } - public IExpression Apply(params IExpression[] args) - { - return this.Apply(args.ToList()); - } /// /// Gives an overview per argument what the possible types are. diff --git a/AspectedRouting/Language/Expression/FunctionCall.cs b/AspectedRouting/Language/Expression/FunctionCall.cs index 845b57f..ea47712 100644 --- a/AspectedRouting/Language/Expression/FunctionCall.cs +++ b/AspectedRouting/Language/Expression/FunctionCall.cs @@ -54,6 +54,21 @@ namespace AspectedRouting.Language.Expression public IExpression Optimize() { return this; + } + + public IExpression OptimizeWithArgument(IExpression argument) + { + + if (_name.Equals(Funcs.Id.Name)) + { + return argument; + } + + return this.Apply(argument); + + + + } public void Visit(Func f) diff --git a/AspectedRouting/Language/Expression/ProfileMetaData.cs b/AspectedRouting/Language/Expression/ProfileMetaData.cs index 4e4f26d..20e80b5 100644 --- a/AspectedRouting/Language/Expression/ProfileMetaData.cs +++ b/AspectedRouting/Language/Expression/ProfileMetaData.cs @@ -91,8 +91,8 @@ namespace AspectedRouting.Language.Expression parameters[k.TrimStart('#')] = v; } - c = c.WithParameters(parameters); - + c = c.WithParameters(parameters) + .WithAspectName(this.Name); tags = new Dictionary(tags); var canAccess = Access.Run(c, tags); tags["access"] = "" + canAccess; diff --git a/AspectedRouting/Language/Funcs.cs b/AspectedRouting/Language/Funcs.cs index 0df6f1e..e6b5933 100644 --- a/AspectedRouting/Language/Funcs.cs +++ b/AspectedRouting/Language/Funcs.cs @@ -14,17 +14,17 @@ namespace AspectedRouting.Language public static readonly Dictionary Builtins = new Dictionary(); public static IEnumerable BuiltinNames = Builtins.Keys; - + public static readonly Function Eq = new Eq(); public static readonly Function NotEq = new NotEq(); public static readonly Function Inv = new Inv(); - + public static readonly Function Default = new Default(); - + public static readonly Function Parse = new Parse(); public static readonly Function ToStringFunc = new ToString(); public static readonly Function Concat = new Concat(); - + public static readonly Function ContainedIn = new ContainedIn(); public static readonly Function Min = new Min(); public static readonly Function Max = new Max(); @@ -36,10 +36,10 @@ namespace AspectedRouting.Language public static readonly Function MustMatch = new MustMatch(); public static readonly Function MemberOf = new MemberOf(); - - - + + public static readonly Function If = new If(); + public static readonly Function IfDotted = new IfDotted(); public static readonly Function Id = new Id(); public static readonly Function Const = new Const(); @@ -58,7 +58,7 @@ namespace AspectedRouting.Language public static IExpression Either(IExpression a, IExpression b, IExpression arg) { - return new Apply(new Apply(new Apply(EitherFunc, a), b), arg); + return EitherFunc.Apply(a, b, arg); } public static Function BuiltinByName(string funcName) @@ -78,13 +78,23 @@ namespace AspectedRouting.Language { return null; } - e = e.SpecializeToSmallestType().Optimize(); + + e = e.SpecializeToSmallestType(); + // TODO FIX THIS so that it works + // An argument 'optimizes' it's types from 'string -> bool' to 'string -> string' + e = e.Optimize(); + // e.SanityCheck(); return e; } - + public static IExpression SpecializeToSmallestType(this IExpression e) { + if (e.Types.Count() == 1) + { + return e; + } + Type smallest = null; foreach (var t in e.Types) { @@ -97,6 +107,7 @@ namespace AspectedRouting.Language var smallestIsSuperset = smallest.IsSuperSet(t); if (!t.IsSuperSet(smallest) && !smallestIsSuperset) { + // Neither one is smaller then the other, we can not compare them return e; } @@ -114,6 +125,11 @@ namespace AspectedRouting.Language return function.Apply(args.ToList()); } + public static IExpression Apply(this IExpression function, params IExpression[] args) + { + return function.Apply(args.ToList()); + } + public static IExpression Apply(this IExpression function, List args) { if (args.Count == 0) diff --git a/AspectedRouting/Language/Functions/Constant.cs b/AspectedRouting/Language/Functions/Constant.cs index aa34029..a1e0b66 100644 --- a/AspectedRouting/Language/Functions/Constant.cs +++ b/AspectedRouting/Language/Functions/Constant.cs @@ -14,41 +14,54 @@ namespace AspectedRouting.Language.Functions public Constant(IEnumerable types, object o) { - Types = types; + Types = types.ToList(); + if (o is IEnumerable enumerable) + { + o = enumerable.ToList(); + } + _o = o; } public Constant(Type t, object o) { - Types = new[] {t}; + Types = new List {t}; + if (o is IEnumerable enumerable) + { + o = enumerable.ToList(); + } + _o = o; } public Constant(IExpression[] exprs) { - Types = exprs.SpecializeToCommonTypes(out var specializedVersions).Select(t => new ListType(t)); + var tps = exprs + .SpecializeToCommonTypes(out var specializedVersions) + .Select(t => new ListType(t)); + Types = tps.ToList(); _o = specializedVersions; } - public Constant(IEnumerable xprs) + public Constant(List exprs) { - var exprs = xprs.ToList(); try { - Types = exprs.SpecializeToCommonTypes(out var specializedVersions).Select(t => new ListType(t)); + Types = exprs + .SpecializeToCommonTypes(out var specializedVersions).Select(t => new ListType(t)).ToList(); _o = specializedVersions.ToList(); } catch (Exception e) { - throw new Exception($"While creating a list with members "+ - string.Join(", ", exprs.Select(x => x.Optimize()))+ - $" {e.Message}", e); + throw new Exception($"While creating a list with members " + + string.Join(", ", exprs.Select(x => x.Optimize())) + + $" {e.Message}", e); } } public Constant(string s) { - Types = new[] {Typs.String}; + Types = new List {Typs.String}; _o = s; } @@ -121,10 +134,10 @@ namespace AspectedRouting.Language.Functions addTo.Add(this); } - public IExpression Specialize(IEnumerable allowedTypes) + public IExpression Specialize(IEnumerable allowedTypesEnumerable) { - var enumerable = allowedTypes.ToList(); - var unified = Types.SpecializeTo(enumerable); + var allowedTypes = allowedTypesEnumerable.ToList(); + var unified = Types.SpecializeTo(allowedTypes); if (unified == null) { return null; @@ -133,15 +146,39 @@ namespace AspectedRouting.Language.Functions var newO = _o; if (_o is IExpression e) { - newO = e.Specialize(enumerable); + newO = e.Specialize(allowedTypes); } if (_o is IEnumerable es) { - var innerTypes = enumerable - .Where(t => t is ListType) - .Select(t => ((ListType) t).InnerType); - newO = es.Select(x => x.Specialize(innerTypes)).Where(x => x != null); + var innerTypes = new List(); + foreach (var allowedType in allowedTypes) + { + if (allowedType is ListType listType) + { + innerTypes.Add(listType.InnerType); + } + } + + var specializedExpressions = new List(); + foreach (var expr in es) + { + if (expr == null) + { + throw new NullReferenceException("Subexpression is null"); + } + + var specialized = expr.Specialize(innerTypes); + if (specialized == null) + { + // If a subexpression can not be specialized, this list cannot be specialized + return null; + } + + specializedExpressions.Add(specialized); + } + + newO = specializedExpressions; } return new Constant(unified, newO); @@ -151,17 +188,36 @@ namespace AspectedRouting.Language.Functions { if (_o is IEnumerable exprs) { - return new Constant(exprs.Select(x => x.Optimize())); + // This is a list + var optExprs = new List(); + foreach (var expression in exprs) + { + var exprOpt = expression.Optimize(); + if (exprOpt == null || exprOpt.Types.Count() == 0) + { + throw new ArgumentException("Non-optimizable expression:" + expression); + } + + optExprs.Add(exprOpt); + } + + return new Constant(optExprs.ToList()); } if (_o is IExpression expr) { + // This is a list return new Constant(expr.Types, expr.Optimize()); } return this; } + public IExpression OptimizeWithArgument(IExpression argument) + { + return this.Apply(argument); + } + public void Visit(Func f) { if (_o is IExpression e) @@ -185,7 +241,7 @@ namespace AspectedRouting.Language.Functions return _o.Pretty(); } } - + public static class ObjectExtensions { public static string Pretty(this object o, Context context = null) diff --git a/AspectedRouting/Language/Functions/If.cs b/AspectedRouting/Language/Functions/If.cs index 9a93d6f..925d08a 100644 --- a/AspectedRouting/Language/Functions/If.cs +++ b/AspectedRouting/Language/Functions/If.cs @@ -10,6 +10,7 @@ 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` or the boolean `true`" + "If the `else` branch is not set, `null` is returned in the condition is false."; public override List ArgNames { get; } = new List {"condition", "then", "else"}; @@ -18,12 +19,8 @@ namespace AspectedRouting.Language.Functions { Curry.ConstructFrom(a, Typs.Bool, a, a), Curry.ConstructFrom(a, Typs.Bool, a), - - Curry.ConstructFrom(a, - new Curry(b, Typs.Bool), - new Curry(b, a), - new Curry(b, a), - b) + Curry.ConstructFrom(a, Typs.String, a, a), + Curry.ConstructFrom(a, Typs.String, a) } ) { @@ -38,19 +35,19 @@ namespace AspectedRouting.Language.Functions { var condition = arguments[0].Evaluate(c); var then = arguments[1]; - IExpression _else = null; + IExpression @else = null; if (arguments.Length > 2) { - _else = arguments[2]; + @else = arguments[2]; } - if (condition != null && (condition.Equals("yes") || condition.Equals("true"))) + if (condition != null && (condition.Equals("yes") || condition.Equals("true") || condition.Equals(true))) { return then.Evaluate(c); } else { - return _else?.Evaluate(c); + return @else?.Evaluate(c); } } diff --git a/AspectedRouting/Language/Functions/IfDotted.cs b/AspectedRouting/Language/Functions/IfDotted.cs new file mode 100644 index 0000000..d823d9a --- /dev/null +++ b/AspectedRouting/Language/Functions/IfDotted.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using AspectedRouting.Language.Expression; +using AspectedRouting.Language.Typ; + +namespace AspectedRouting.Language.Functions +{ + public class IfDotted : Function + { + private static Var a = new Var("a"); + private static Var b = new Var("b"); + + // TODO OPTIMIZE AWAY + + public override string Description { get; } = "An if_then_else, but one which takes an extra argument and applies it on the condition, then and else.\n" + + "Consider `fc`, `fthen` and `felse` are all functions taking an `a`, then:\n" + + "`(ifDotted fc fthen felse) a` === `(if (fc a) (fthen a) (felse a)" + + "Selects either one of the branches, depending on the condition." + + " The 'then' branch is returned if the condition returns the string `yes` or `true` or the boolean `true`" + + "If the `else` branch is not set, `null` is returned in the condition is false."; + public override List ArgNames { get; } = new List {"condition", "then", "else"}; + + public IfDotted() : base("if_then_else_dotted", true, + new[] + { + + Curry.ConstructFrom(a, + new Curry(b, Typs.Bool), + new Curry(b, a), + b), + Curry.ConstructFrom(a, + new Curry(b, Typs.String), + new Curry(b, a), + b), + Curry.ConstructFrom(a, + new Curry(b, Typs.Bool), + new Curry(b, a), + new Curry(b, a), + b), + Curry.ConstructFrom(a, + new Curry(b, Typs.String), + new Curry(b, a), + new Curry(b, a), + b) + } + ) + { + Funcs.AddBuiltin(this, "ifdotted"); + Funcs.AddBuiltin(this, "ifDotted"); + } + + private IfDotted(IEnumerable types) : base("if_then_else_dotted", types) + { + } + + public override object Evaluate(Context c, params IExpression[] arguments) + { + var conditionfunc = arguments[0]; + var thenfunc = arguments[1]; + IExpression elsefunc = null; + IExpression argument = arguments[2]; + + if (arguments.Length == 4) + { + elsefunc = arguments[2]; + argument = arguments[3]; + } + + var condition = ((IExpression) conditionfunc).Apply(argument).Evaluate(c); + + if (condition != null && (condition.Equals("yes") || condition.Equals("true") || condition.Equals(true))) + { + return thenfunc.Apply(argument).Evaluate(c); + } + else + { + return elsefunc.Apply(argument).Evaluate(c); + } + } + + public override IExpression Specialize(IEnumerable allowedTypes) + { + var unified = Types.SpecializeTo(allowedTypes); + if (unified == null) + { + return null; + } + + return new IfDotted(unified); + } + } +} \ No newline at end of file diff --git a/AspectedRouting/Language/Functions/Mapping.cs b/AspectedRouting/Language/Functions/Mapping.cs index 5a21511..dcda7c2 100644 --- a/AspectedRouting/Language/Functions/Mapping.cs +++ b/AspectedRouting/Language/Functions/Mapping.cs @@ -55,9 +55,13 @@ namespace AspectedRouting.Language.Functions var exprSpecialized = expr.Specialize(enumerable); if (exprSpecialized == null) { - throw new Exception($"Could not specialize a mapping of type {string.Join(",", Types)}\n" + + /*throw new Exception($"Could not specialize a mapping of type {string.Join(",", Types)}\n" + $"to types {string.Join(", ", allowedTypesList)};\n" + - $"Expression {expr} could not be specialized to {functionType}"); + $"Expression {expr}:{string.Join(" ; ", expr.Types)} could not be specialized to {string.Join("; ",functionType)}"); + + +*/ + return null; } newFunctions[k] = exprSpecialized; diff --git a/AspectedRouting/Language/Functions/Max.cs b/AspectedRouting/Language/Functions/Max.cs index 0727437..31281f9 100644 --- a/AspectedRouting/Language/Functions/Max.cs +++ b/AspectedRouting/Language/Functions/Max.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using AspectedRouting.Language.Expression; using AspectedRouting.Language.Typ; @@ -56,11 +57,19 @@ namespace AspectedRouting.Language.Functions } return "no"; - case DoubleType _: - case PDoubleType _: - return ls.Select(o => (double) o).Max(); default: - return ls.Select(o => (int) o).Max(); + return ls.Select(o => + { + switch (o) + { + case double d: + return d; + case int i: + return i; + default: + throw new SwitchExpressionException("Unknwon type: " + o); + } + }).Max(); } } } diff --git a/AspectedRouting/Language/Functions/MemberOf.cs b/AspectedRouting/Language/Functions/MemberOf.cs index 4f9df61..7832cd4 100644 --- a/AspectedRouting/Language/Functions/MemberOf.cs +++ b/AspectedRouting/Language/Functions/MemberOf.cs @@ -46,8 +46,6 @@ namespace AspectedRouting.Language.Functions public override object Evaluate(Context c, params IExpression[] arguments) { - // var subFunction = arguments[0]; - var tags =(Dictionary) arguments[1].Evaluate(c); var name = c.AspectName.TrimStart('$'); diff --git a/AspectedRouting/Language/Functions/MustMatch.cs b/AspectedRouting/Language/Functions/MustMatch.cs index 3860a8b..8ed286d 100644 --- a/AspectedRouting/Language/Functions/MustMatch.cs +++ b/AspectedRouting/Language/Functions/MustMatch.cs @@ -9,7 +9,9 @@ namespace AspectedRouting.Language.Functions public class MustMatch : Function { public override string Description { get; } = - "Every key that is used in the subfunction must be present.\n" + + "Checks that every specified key is present and gives a non-false value\n." + + "" + + "\n" + "If, on top, a value is present with a mapping, every key/value will be executed and must return a value that is not 'no' or 'false'\n" + "Note that this is a privileged builtin function, as the parser will automatically inject the keys used in the called function."; @@ -22,10 +24,10 @@ namespace AspectedRouting.Language.Functions public MustMatch() : base("mustMatch", true, new[] { - // [String] -> (Tags -> [a]) -> Tags -> a + // [String] -> (Tags -> [string]) -> Tags -> bool Curry.ConstructFrom(Typs.Bool, // Result type on top! new ListType(Typs.String), // List of keys to check for - new Curry(Typs.Tags, new ListType(Typs.Bool)), // The function to execute on every key + new Curry(Typs.Tags, new ListType(Typs.String)), // The function to execute on every key Typs.Tags // The tags to apply this on ) }) diff --git a/AspectedRouting/Language/Functions/Parameter.cs b/AspectedRouting/Language/Functions/Parameter.cs index 904a25b..92fc72d 100644 --- a/AspectedRouting/Language/Functions/Parameter.cs +++ b/AspectedRouting/Language/Functions/Parameter.cs @@ -44,6 +44,11 @@ namespace AspectedRouting.Language.Functions { return this; } + + public IExpression OptimizeWithArgument(IExpression arg) + { + throw new NotSupportedException("Trying to invoke a parameter"); + } public void Visit(Func f) { diff --git a/AspectedRouting/Language/IExpression.cs b/AspectedRouting/Language/IExpression.cs index 0acfea2..22dfe30 100644 --- a/AspectedRouting/Language/IExpression.cs +++ b/AspectedRouting/Language/IExpression.cs @@ -25,8 +25,21 @@ namespace AspectedRouting.Language /// IExpression Specialize(IEnumerable allowedTypes); + /// + /// Optimize a single expression, eventually recursively (e.g. a list can optimize all the contents) + /// + /// IExpression Optimize(); + /// + /// Optimize with the given argument, e.g. listdot can become a list of applied arguments. + /// + /// By default, this should return 'this.Apply(argument)' + /// + /// + /// + // IExpression OptimizeWithArgument(IExpression argument); + void Visit(Func f); } @@ -97,7 +110,9 @@ namespace AspectedRouting.Language continue; } - var specialized = specializedTypes.SpecializeTo(f.Types.RenameVars(specializedTypes)); + // TODO FIXME + // EITHER THE TYPES HAVE TO BE FROM SPECIFIC TO NON-SPECIFIC ORDER OR VICE VERSA + var specialized = f.Types.RenameVars(specializedTypes).SpecializeTo(specializedTypes); // ReSharper disable once JoinNullCheckWithUsage if (specialized == null) { diff --git a/AspectedRouting/Language/Typ/Typs.cs b/AspectedRouting/Language/Typ/Typs.cs index dddc5e6..66572b0 100644 --- a/AspectedRouting/Language/Typ/Typs.cs +++ b/AspectedRouting/Language/Typ/Typs.cs @@ -23,19 +23,19 @@ namespace AspectedRouting.Language.Typ } - public static HashSet SpecializeTo(this IEnumerable types0, IEnumerable types1) + public static HashSet SpecializeTo(this IEnumerable types0, IEnumerable allowedTypes) { var results = new HashSet(); - var enumerable = types1.ToList(); + allowedTypes = allowedTypes.ToList(); foreach (var t0 in types0) { - foreach (var t1 in enumerable) + foreach (var allowed in allowedTypes) { - var unification = t0.Unify(t1); - if (unification != null) + var unified = t0.Unify(allowed, true); + if (unified != null) { - results.Add(unification); + results.Add(unified); } } } @@ -49,15 +49,54 @@ namespace AspectedRouting.Language.Typ } - public static Type Unify(this Type t0, Type t1) + /// + /// Unifies the two types, where t0 is the allowed, wider type and t1 is the actual, smaller type (unless reverseSuperset is true). + /// Unification will attempt to keep the type as small as possible + /// + /// The expected, wider type + /// The actual type + /// True if the supertyping relationship should be reversed + /// + public static Type Unify(this Type t0, Type t1, bool reverseSuperset = false) { - var table = t0.UnificationTable(t1); + var table = t0.UnificationTable(t1, reverseSuperset); if (table == null) { return null; } - return t0.Substitute(table); + var subbed = t0.Substitute(table); + if (reverseSuperset) + { + return SelectSmallestUnion(t1, subbed); + } + return SelectSmallestUnion(subbed, t1); + } + + private static Type SelectSmallestUnion(this Type wider, Type smaller, bool reverse = false) + { + switch (wider) + { + case Var a: + return a; + case ListType l when smaller is ListType lsmaller: + return new ListType( + l.InnerType.SelectSmallestUnion( + l.InnerType.SelectSmallestUnion(lsmaller.InnerType, reverse))); + case Curry cWider when smaller is Curry cSmaller: + var arg = + cWider.ArgType.SelectSmallestUnion(cSmaller.ArgType, !reverse); + var result = + cWider.ResultType.SelectSmallestUnion(cSmaller.ResultType, reverse); + return new Curry(arg, result); + default: + if (wider.IsSuperSet(smaller) && !(smaller.IsSuperSet(wider))) + { + return smaller; + } + + return wider; + } } /// @@ -69,7 +108,7 @@ namespace AspectedRouting.Language.Typ /// public static IEnumerable UnifyAny(this Type t0, IEnumerable t1) { - var result = t1.Select(t0.Unify).Where(unification => unification != null).ToHashSet(); + var result = t1.Select(t => t0.Unify(t)).Where(unification => unification != null).ToHashSet(); if (!result.Any()) { return null; @@ -87,7 +126,7 @@ namespace AspectedRouting.Language.Typ /// public static IEnumerable UnifyAll(this Type t0, IEnumerable t1) { - var result = t1.Select(t0.Unify).ToHashSet(); + var result = t1.Select(t => t0.Unify(t)).ToHashSet(); if (result.Any(x => x == null)) { return null; @@ -96,6 +135,8 @@ namespace AspectedRouting.Language.Typ return result; } + + public static Type Substitute(this Type t0, Dictionary substitutions) { switch (t0) @@ -111,7 +152,16 @@ namespace AspectedRouting.Language.Typ } } - public static Dictionary UnificationTable(this Type t0, Type t1) + /// + /// The unification table is built when the type of an argument is introspected to see if it fits in the excpect type + /// t0 here is the **expected** (wider) type, whereas t1 is the **actual** argument type. + /// In other words, if we expect a `double`, a `pdouble` fits in there too. + /// If we expect a function capable of handling pdoubles and giving strings, a function capable of handling doubles and giving bools will work just as well + /// + /// + /// + /// + public static Dictionary UnificationTable(this Type t0, Type t1, bool reverseSupersetRelation = false) { var substitutionsOn0 = new Dictionary(); @@ -156,7 +206,7 @@ namespace AspectedRouting.Language.Typ break; case ListType l0 when t1 is ListType l1: { - var table = l0.InnerType.UnificationTable(l1.InnerType); + var table = l0.InnerType.UnificationTable(l1.InnerType, reverseSupersetRelation); if (!AddAllSubs(table)) { return null; @@ -167,8 +217,9 @@ namespace AspectedRouting.Language.Typ case Curry curry0 when t1 is Curry curry1: { - var tableA = curry0.ArgType.UnificationTable(curry1.ArgType); - var tableB = curry0.ResultType.UnificationTable(curry1.ResultType); + // contravariance for arguments: reversed + var tableA = curry0.ArgType.UnificationTable(curry1.ArgType, !reverseSupersetRelation); + var tableB = curry0.ResultType.UnificationTable(curry1.ResultType, reverseSupersetRelation); if (!(AddAllSubs(tableA) && AddAllSubs(tableB))) { return null; @@ -182,8 +233,15 @@ namespace AspectedRouting.Language.Typ if (t1 is Var v) { AddSubs(v.Name, t0); + break; } - else if (!t0.Equals(t1)) + + if (!reverseSupersetRelation && !t0.IsSuperSet(t1)) + { + return null; + } + + if (reverseSupersetRelation && !t1.IsSuperSet(t0)) { return null; } @@ -197,7 +255,7 @@ namespace AspectedRouting.Language.Typ public static HashSet UsedVariables(this Type t0, HashSet addTo = null) { - addTo ??=new HashSet(); + addTo ??= new HashSet(); switch (t0) { case Var a: @@ -256,16 +314,23 @@ namespace AspectedRouting.Language.Typ public static IEnumerable RenameVars(this IEnumerable toRename, IEnumerable noUseVar) { - var usedToRename = toRename.SelectMany(t => UsedVariables(t)); + // Variables used in 'toRename' + var usedToRename = toRename.SelectMany(t => UsedVariables(t)).ToHashSet(); + // Variables that should not be used var blacklist = noUseVar.SelectMany(t => UsedVariables(t)).ToHashSet(); - var alreadyUsed = blacklist.Concat(usedToRename).ToHashSet(); + // variables that should be renamed var variablesToRename = usedToRename.Intersect(blacklist).ToHashSet(); + + // All variables that are used and thus not free anymore, sum of 'usedToRename' and the blacklist + var alreadyUsed = blacklist.Concat(usedToRename).ToHashSet(); + + // The substitution table var subsTable = new Dictionary(); foreach (var v in variablesToRename) { var newValue = Var.Fresh(alreadyUsed); subsTable.Add(v, newValue); - blacklist.Add(newValue.Name); + alreadyUsed.Add(newValue.Name); } return toRename.Select(t => t.Substitute(subsTable)); diff --git a/AspectedRouting/Program.cs b/AspectedRouting/Program.cs index 6d132a4..9ac6756 100644 --- a/AspectedRouting/Program.cs +++ b/AspectedRouting/Program.cs @@ -21,7 +21,7 @@ namespace AspectedRouting foreach (var file in jsonFileNames) { var fi = new FileInfo(file); - Console.WriteLine("Parsing " + file); + var aspect = JsonParser.AspectFromJson(context, File.ReadAllText(file), fi.Name); if (aspect == null) continue; @@ -63,6 +63,11 @@ namespace AspectedRouting var result = new List<(ProfileMetaData profile, List profileTests)>(); foreach (var jsonFile in jsonFiles) { + if (!jsonFile.Contains("bicycle")) + { + continue; + } + try { var profile = @@ -251,7 +256,7 @@ namespace AspectedRouting ).ToLua(); File.WriteAllText(outputDir + "/" + profile.Name + ".lua", luaProfile); - foreach (var (behaviourName, behaviourParameters) in profile.Behaviours) + foreach (var (behaviourName, _) in profile.Behaviours) { var lua2behaviour = new LuaPrinter2( profile, diff --git a/Profiles/bicycle/bicycle.json b/Profiles/bicycle/bicycle.json index ce1ba08..62fdebe 100644 --- a/Profiles/bicycle/bicycle.json +++ b/Profiles/bicycle/bicycle.json @@ -15,7 +15,15 @@ "status", "network" ], - "access": "$bicycle.legal_access", + "access": { + "$ifDotted": { + "$dot": {"$notEq": "no"}, + "f": "$bicycle.legal_access" + }, + + "then": "$bicycle.legal_access", + "else": "$bicycle.network_is_bicycle_network" + }, "oneway": "$bicycle.oneway", "speed": { "$min": [