From 6739dd8b250851bd8e3ecbd9537d8eb9ca5ebb7e Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Mon, 15 Mar 2021 20:33:19 +0100 Subject: [PATCH] Proper calculation of the type of a list of expressions - turns out it was broken --- AspectedRouting.Test/Snippets/SnippetTests.cs | 23 +- AspectedRouting.Test/TypingTests.cs | 76 +++++ AspectedRouting.sln.DotSettings.user | 6 +- AspectedRouting/IO/LuaSkeleton/LuaLiteral.cs | 12 + .../JsonParser.ParseAspectProfile.cs | 1 + AspectedRouting/IO/jsonParser/JsonParser.cs | 60 ++-- AspectedRouting/Language/Expression/Apply.cs | 9 + .../Language/Expression/AspectMetadata.cs | 10 + .../Language/Expression/Function.cs | 14 +- .../Language/Expression/FunctionCall.cs | 11 + .../Language/Expression/ProfileMetaData.cs | 14 + .../Language/Functions/Constant.cs | 159 +++++----- .../Language/Functions/Parameter.cs | 11 + AspectedRouting/Language/IExpression.cs | 91 +++--- AspectedRouting/Language/Typ/Typs.cs | 290 ++++++++++++------ 15 files changed, 511 insertions(+), 276 deletions(-) diff --git a/AspectedRouting.Test/Snippets/SnippetTests.cs b/AspectedRouting.Test/Snippets/SnippetTests.cs index 438edbe..f0c2a10 100644 --- a/AspectedRouting.Test/Snippets/SnippetTests.cs +++ b/AspectedRouting.Test/Snippets/SnippetTests.cs @@ -60,8 +60,9 @@ namespace AspectedRouting.Test.Snippets tags } ); + // First the more general ones! Assert.Equal( - "if (tags[\"bicycle\"] ~= nil) then\n result = tags[\"bicycle\"]\nelseif (tags[\"access\"] ~= nil) then\n result = tags[\"access\"]\nend\n", + "if (tags[\"access\"] ~= nil) then\n result = tags[\"access\"]\n \nend\nif (tags[\"bicycle\"] ~= nil) then\n result = tags[\"bicycle\"]\n \nend\n", code); } @@ -82,27 +83,9 @@ namespace AspectedRouting.Test.Snippets }); var expected = - "local v\nv = tags.oneway\nif (v == \"1\") then\n result = \"with\"\nelseif (v == \"-1\") then\n result = \"against\"\nend"; + "local v\nv = tags.oneway\n\nif (v == \"1\") then\n result = \"with\"\nelseif (v == \"-1\") then\n result = \"against\"\nend"; Assert.Equal(expected, code); } - [Fact] - public void MultiplySnippet_SimpleMultiply_GeneratesLua() - { - var gen = new MultiplySnippet(); - var f = new Apply( - Funcs.Multiply, - null - ); - - - var code = gen.Convert(new LuaSkeleton(new Context(), true), - "result", new List() - ); - - var expected = - "local v\nv = tags.oneway\nif (v == \"1\") then\n result = \"with\"\nelseif (v == \"-1\") then\n result = \"against\"\nend"; - Assert.Equal(expected, code); - } } } \ No newline at end of file diff --git a/AspectedRouting.Test/TypingTests.cs b/AspectedRouting.Test/TypingTests.cs index 09d5483..c1f6a3f 100644 --- a/AspectedRouting.Test/TypingTests.cs +++ b/AspectedRouting.Test/TypingTests.cs @@ -1,13 +1,89 @@ +using System; +using System.Collections.Generic; using System.Linq; using AspectedRouting.Language; +using AspectedRouting.Language.Functions; using AspectedRouting.Language.Typ; using Xunit; +using Type = AspectedRouting.Language.Typ.Type; namespace AspectedRouting.Test { public class TypingTests { + [Fact] + public void SpecializeToCommonTypes_X2PDouble_Y2Double_Gives_X2Double() + { + // Regression test: + // [ x : (x -> pdouble), y : (y -> double)] is wrongly types as (x -> pdouble), hence killing y in the subsequent typing + var exprs = new List { + new Constant(new List { + new Curry(Typs.Tags, Typs.Double), + new Curry(new Var("b"), new Curry(Typs.Tags, Typs.Double)) + }, "x"), + + new Constant( + new List { + Typs.PDouble, + new Curry(new Var("b"), Typs.PDouble) + } + , "y") + }; + + + exprs.SpecializeToCommonTypes(out var specializedTypes, out var specializedExpressions); + Assert.All(specializedTypes, Assert.NotNull); + Assert.All(specializedExpressions, Assert.NotNull); + Assert.Single(specializedTypes); + Assert.Equal(new Curry(Typs.Tags, Typs.Double), specializedTypes.First()); + } + + + [Fact] + public void WidestCommonGround_A2PdoubleAndT2Double_T2Double() + { + var v = Typs.WidestCommonType(new Curry(new Var("a"), Typs.PDouble), + new Curry(Typs.Tags, Typs.Double) + ); + Assert.NotNull(v); + var (x, subsTable) = v.Value; + Assert.NotNull(x); + Assert.Equal( + new Curry(Typs.Tags, Typs.Double), x + ); + } + + [Fact] + public void WidestCommonGround_StringAndString_String() + { + var v = Typs.WidestCommonType(Typs.String, Typs.String); + Assert.NotNull(v); + var (x, subsTable) = v.Value; + Assert.NotNull(x); + Assert.Equal( + Typs.String, x + ); + } + + [Fact] + public void SpecializeToCommonTypes_ValueAndFuncType_ShouldFail() + { + // Regression test: + // [ x : (x -> pdouble), y : (y -> double)] is wrongly types as (x -> pdouble), hence killing y in the subsequent typing + var exprs = new List { + new Constant( + new Curry(Typs.Tags, Typs.Double), + "x"), + new Constant( + Typs.PDouble, "y") + }; + + Assert.Throws(new ArgumentException().GetType(), + () => exprs.SpecializeToCommonTypes(out _, out _)); + } + + [Fact] public void JoinApply_Id() { diff --git a/AspectedRouting.sln.DotSettings.user b/AspectedRouting.sln.DotSettings.user index 20c3d26..116d60a 100644 --- a/AspectedRouting.sln.DotSettings.user +++ b/AspectedRouting.sln.DotSettings.user @@ -1,7 +1,8 @@  - <SessionState ContinuousTestingMode="0" IsActive="True" Name="DefaultSnippet_SimpleDefault_GetsLua" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <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"> @@ -13,5 +14,8 @@ <SessionState ContinuousTestingMode="0" 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" IsActive="True" 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 diff --git a/AspectedRouting/IO/LuaSkeleton/LuaLiteral.cs b/AspectedRouting/IO/LuaSkeleton/LuaLiteral.cs index 7a96115..e36ccf0 100644 --- a/AspectedRouting/IO/LuaSkeleton/LuaLiteral.cs +++ b/AspectedRouting/IO/LuaSkeleton/LuaLiteral.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using AspectedRouting.Language; using Type = AspectedRouting.Language.Typ.Type; @@ -31,6 +32,16 @@ namespace AspectedRouting.IO.LuaSkeleton return this; } + public IExpression PruneTypes(Func allowedTypes) + { + var passed = this.Types.Where(allowedTypes); + if (passed.Any()) { + return new LuaLiteral(passed, this.Lua); + } + + return null; + } + public IExpression Optimize() { return this; @@ -40,5 +51,6 @@ namespace AspectedRouting.IO.LuaSkeleton { throw new NotImplementedException(); } + } } \ No newline at end of file diff --git a/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs b/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs index d007c34..ac5fda2 100644 --- a/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs +++ b/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs @@ -248,6 +248,7 @@ namespace AspectedRouting.IO.jsonParser var exprs = e.EnumerateArray().Select(json => Funcs.Either(Funcs.Id, Funcs.Const, json.ParseExpression(context))) .ToList(); + var list = new Constant(exprs); return Funcs.Either(Funcs.Id, Funcs.ListDot, list); } diff --git a/AspectedRouting/IO/jsonParser/JsonParser.cs b/AspectedRouting/IO/jsonParser/JsonParser.cs index d44c66a..b757519 100644 --- a/AspectedRouting/IO/jsonParser/JsonParser.cs +++ b/AspectedRouting/IO/jsonParser/JsonParser.cs @@ -12,35 +12,49 @@ namespace AspectedRouting.IO.jsonParser { private static IExpression ParseProfileProperty(JsonElement e, Context c, string property) { - if (!e.TryGetProperty(property, out var prop)) - { + if (!e.TryGetProperty(property, out var prop)) { throw new ArgumentException("Not a valid profile: the declaration expression for '" + property + "' is missing"); } - try - { + try { var expr = ParseExpression(prop, c); - if (!expr.Types.Any()) - { + if (!expr.Types.Any()) { 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) - { + + if (specialized == null) { throw new ArgumentException("The expression for " + property + " hasn't the right type of 'Tags -> a'; it has types " + string.Join(",", expr.Types) + "\n" + expr.TypeBreakdown()); } - return specialized.Optimize(); + var pruned = specialized.PruneTypes(type => { + if (!(type is Curry c)) { + return false; + } + + if (!Equals(c.ArgType, Typs.Tags)) { + return false; + } + if (c.ResultType is Curry) { + return false; + } + return true; + }); + if (pruned.SpecializeToSmallestType().Types.Count() != 1) { + throw new ArgumentException("The expression for " + property + + " hasn't the right type of 'Tags -> a'; it has multiple types " + + string.Join(",", pruned.Types) + "\n" + pruned.TypeBreakdown()); + } + + return pruned.Optimize(); } - catch (Exception exc) - { + catch (Exception exc) { throw new Exception("While parsing the property " + property, exc); } } @@ -48,24 +62,20 @@ namespace AspectedRouting.IO.jsonParser private static Dictionary ParseParameters(this JsonElement e) { var ps = new Dictionary(); - foreach (var obj in e.EnumerateObject()) - { + foreach (var obj in e.EnumerateObject()) { var nm = obj.Name.TrimStart('#'); - if (nm == "") - { + if (nm == "") { // This is a comment - not a parameter! continue; } - switch (obj.Value.ValueKind) - { + + switch (obj.Value.ValueKind) { case JsonValueKind.String: var v = obj.Value.ToString(); - if (v.Equals("yes") || v.Equals("no")) - { + if (v.Equals("yes") || v.Equals("no")) { ps[nm] = new Constant(Typs.Bool, v); } - else - { + else { ps[nm] = new Constant(v); } @@ -96,8 +106,7 @@ namespace AspectedRouting.IO.jsonParser private static string Get(this JsonElement json, string key) { - if (json.TryGetProperty(key, out var p)) - { + if (json.TryGetProperty(key, out var p)) { return p.GetString(); } @@ -106,8 +115,7 @@ namespace AspectedRouting.IO.jsonParser private static string TryGet(this JsonElement json, string key) { - if (json.TryGetProperty(key, out var p)) - { + if (json.TryGetProperty(key, out var p)) { return p.GetString(); } diff --git a/AspectedRouting/Language/Expression/Apply.cs b/AspectedRouting/Language/Expression/Apply.cs index 46c2120..b1d8e37 100644 --- a/AspectedRouting/Language/Expression/Apply.cs +++ b/AspectedRouting/Language/Expression/Apply.cs @@ -114,6 +114,15 @@ namespace AspectedRouting.Language.Expression return Specialize(allowedTypes); } + public IExpression PruneTypes(Func allowedTypes) + { + var passed = this.FunctionApplications.Where(kv => allowedTypes.Invoke(kv.Key)); + if (!passed.Any()) { + return null; + } + return new Apply("pruned", new Dictionary(passed)); + } + public IExpression Optimize() { if (Types.Count() == 0) { diff --git a/AspectedRouting/Language/Expression/AspectMetadata.cs b/AspectedRouting/Language/Expression/AspectMetadata.cs index 1790929..49c09ab 100644 --- a/AspectedRouting/Language/Expression/AspectMetadata.cs +++ b/AspectedRouting/Language/Expression/AspectMetadata.cs @@ -43,6 +43,16 @@ namespace AspectedRouting.Language.Expression Name, Description, Author, Unit, Filepath); } + public IExpression PruneTypes(System.Func allowedTypes) + { + var e = ExpressionImplementation.PruneTypes(allowedTypes); + if (e == null) { + return null; + } + return new AspectMetadata(e, Name, Description, Author, Unit, + Filepath, ProfileInternal); + } + public IExpression Optimize() { return new AspectMetadata(ExpressionImplementation.Optimize(), diff --git a/AspectedRouting/Language/Expression/Function.cs b/AspectedRouting/Language/Expression/Function.cs index cf36a7d..85a9e7b 100644 --- a/AspectedRouting/Language/Expression/Function.cs +++ b/AspectedRouting/Language/Expression/Function.cs @@ -34,17 +34,21 @@ namespace AspectedRouting.Language.Expression public abstract object Evaluate(Context c, params IExpression[] arguments); public abstract IExpression Specialize(IEnumerable allowedTypes); + public IExpression PruneTypes(Func allowedTypes) + { + var passedTypes = this.Types.Where(allowedTypes); + if (!passedTypes.Any()) { + return null; + } + + return new FunctionCall(this.Name, passedTypes); + } public virtual IExpression Optimize() { return this; } - public IExpression OptimizeWithArgument(IExpression argument) - { - return this.Apply(argument); - } - public virtual void Visit(Func f) { f(this); diff --git a/AspectedRouting/Language/Expression/FunctionCall.cs b/AspectedRouting/Language/Expression/FunctionCall.cs index ea47712..0754895 100644 --- a/AspectedRouting/Language/Expression/FunctionCall.cs +++ b/AspectedRouting/Language/Expression/FunctionCall.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using AspectedRouting.Language.Typ; using Type = AspectedRouting.Language.Typ.Type; @@ -51,6 +52,16 @@ namespace AspectedRouting.Language.Expression return new FunctionCall(_name, unified); } + public IExpression PruneTypes(Func allowedTypes) + { + var passedTypes = this.Types.Where(allowedTypes); + if (!passedTypes.Any()) { + return null; + } + + return new FunctionCall(this._name, passedTypes); + } + public IExpression Optimize() { return this; diff --git a/AspectedRouting/Language/Expression/ProfileMetaData.cs b/AspectedRouting/Language/Expression/ProfileMetaData.cs index d2751b5..f01b426 100644 --- a/AspectedRouting/Language/Expression/ProfileMetaData.cs +++ b/AspectedRouting/Language/Expression/ProfileMetaData.cs @@ -53,6 +53,20 @@ namespace AspectedRouting.Language.Expression LastChange = lastChange; DefaultParameters = defaultParameters; Behaviours = behaviours; + + CheckTypes(Access, "access"); + CheckTypes(Oneway, "oneway"); + CheckTypes(Speed, "speed"); + + } + + private static void CheckTypes(IExpression e, string name) + { + if (e.Types.Count() == 1) { + return; + } + + throw new Exception("Insufficient specialization: " + name + " has multiple types left, namely " + e.Types.Pretty()); } public List AllExpressions(Context ctx) diff --git a/AspectedRouting/Language/Functions/Constant.cs b/AspectedRouting/Language/Functions/Constant.cs index 85239f2..e7de7d4 100644 --- a/AspectedRouting/Language/Functions/Constant.cs +++ b/AspectedRouting/Language/Functions/Constant.cs @@ -8,18 +8,15 @@ namespace AspectedRouting.Language.Functions { public class Constant : IExpression { - public IEnumerable Types { get; } private readonly object _o; public Constant(IEnumerable types, object o) { Types = types.ToList(); - if (o is IEnumerable enumerable) - { + if (o is IEnumerable enumerable) { o = enumerable.ToList(); - if (enumerable.Any(x => x == null)) - { + if (enumerable.Any(x => x == null)) { throw new NullReferenceException("Some subexpression is null"); } } @@ -30,11 +27,9 @@ namespace AspectedRouting.Language.Functions public Constant(Type t, object o) { Types = new List {t}; - if (o is IEnumerable enumerable) - { + if (o is IEnumerable enumerable) { o = enumerable.ToList(); - if (enumerable.Any(x => x == null)) - { + if (enumerable.Any(x => x == null)) { throw new NullReferenceException("Some subexpression is null"); } } @@ -47,29 +42,33 @@ namespace AspectedRouting.Language.Functions var tps = exprs .SpecializeToCommonTypes(out var specializedVersions) .Select(t => new ListType(t)); - if(specializedVersions.Any(x => x == null)) - { - throw new Exception("Specializing to common types failed for "+string.Join("," ,exprs.Select(e => e.ToString()))); + if (specializedVersions.Any(x => x == null)) { + throw new Exception("Specializing to common types failed for " + + string.Join(",", exprs.Select(e => e.ToString()))); } + Types = tps.ToList(); _o = specializedVersions; } - public Constant(List exprs) + public Constant(IReadOnlyCollection exprs) { - try - { + try { + + + Types = exprs - .SpecializeToCommonTypes(out var specializedVersions).Select(t => new ListType(t)).ToList(); - if (specializedVersions.Any(x => x == null)) - { + .SpecializeToCommonTypes(out var specializedVersions) + .Select(t => new ListType(t)) + .ToList(); + if (specializedVersions.Any(x => x == null)) { throw new NullReferenceException("Some subexpression is null"); } + _o = specializedVersions.ToList(); } - catch (Exception e) - { - throw new Exception($"While creating a list with members " + + catch (Exception e) { + throw new Exception("While creating a list with members " + string.Join(", ", exprs.Select(x => x.Optimize())) + $" {e.Message}", e); } @@ -83,12 +82,10 @@ namespace AspectedRouting.Language.Functions public Constant(double d) { - if (d >= 0) - { + if (d >= 0) { Types = new[] {Typs.Double, Typs.PDouble}; } - else - { + else { Types = new[] {Typs.Double}; } @@ -97,12 +94,10 @@ namespace AspectedRouting.Language.Functions public Constant(int i) { - if (i >= 0) - { + if (i >= 0) { Types = new[] {Typs.Double, Typs.Nat, Typs.Nat, Typs.PDouble}; } - else - { + else { Types = new[] {Typs.Double, Typs.Int}; } @@ -115,23 +110,34 @@ namespace AspectedRouting.Language.Functions _o = tags; } + public IEnumerable Types { get; } + + public IExpression PruneTypes(System.Func allowedTypes) + { + var passedTypes = Types.Where(allowedTypes); + if (!passedTypes.Any()) { + return null; + } + + return new Constant(passedTypes, _o); + } + public object Evaluate(Context c, params IExpression[] args) { - if (_o is IExpression e) - { + if (_o is IExpression e) { return e.Evaluate(c).Pretty(); } - if (Types.Count() > 1) return _o; + if (Types.Count() > 1) { + return _o; + } var t = Types.First(); - switch (t) - { + switch (t) { case DoubleType _: case PDoubleType _: - if (_o is int i) - { + if (_o is int i) { return (double) i; // I know, it seems absurd having to write this as it is nearly the same as the return beneath, but it _is_ needed } @@ -145,48 +151,35 @@ namespace AspectedRouting.Language.Functions return _o; } - public void EvaluateAll(Context c, HashSet addTo) - { - addTo.Add(this); - } - public IExpression Specialize(IEnumerable allowedTypesEnumerable) { var allowedTypes = allowedTypesEnumerable.ToList(); var unified = Types.SpecializeTo(allowedTypes); - if (unified == null) - { + if (unified == null) { return null; } var newO = _o; - if (_o is IExpression e) - { + if (_o is IExpression e) { newO = e.Specialize(allowedTypes); } - if (_o is IEnumerable es) - { + if (_o is IEnumerable es) { var innerTypes = new List(); - foreach (var allowedType in allowedTypes) - { - if (allowedType is ListType listType) - { + 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) - { + foreach (var expr in es) { + if (expr == null) { throw new NullReferenceException("Subexpression is null"); } var specialized = expr.Specialize(innerTypes); - if (specialized == null) - { + if (specialized == null) { // If a subexpression can not be specialized, this list cannot be specialized return null; } @@ -202,26 +195,22 @@ namespace AspectedRouting.Language.Functions public IExpression Optimize() { - if (_o is IEnumerable exprs) - { + if (_o is IEnumerable exprs) { // This is a list var optExprs = new List(); - foreach (var expression in exprs) - { + foreach (var expression in exprs) { var exprOpt = expression.Optimize(); - if (exprOpt == null || exprOpt.Types.Count() == 0) - { + if (exprOpt == null || exprOpt.Types.Count() == 0) { throw new ArgumentException("Non-optimizable expression:" + expression); } optExprs.Add(exprOpt); } - return new Constant(optExprs.ToList()); + return new Constant(optExprs); } - if (_o is IExpression expr) - { + if (_o is IExpression expr) { // This is a list return new Constant(expr.Types, expr.Optimize()); } @@ -229,22 +218,14 @@ namespace AspectedRouting.Language.Functions return this; } - public IExpression OptimizeWithArgument(IExpression argument) - { - return this.Apply(argument); - } - public void Visit(Func f) { - if (_o is IExpression e) - { + if (_o is IExpression e) { e.Visit(f); } - if (_o is IEnumerable es) - { - foreach (var x in es) - { + if (_o is IEnumerable es) { + foreach (var x in es) { x.Visit(f); } } @@ -252,33 +233,39 @@ namespace AspectedRouting.Language.Functions f(this); } + public void EvaluateAll(Context c, HashSet addTo) + { + addTo.Add(this); + } + + public IExpression OptimizeWithArgument(IExpression argument) + { + return this.Apply(argument); + } + public override string ToString() { return _o.Pretty(); } } - + public static class ObjectExtensions { public static string Pretty(this object o, Context context = null) { - switch (o) - { + switch (o) { case Dictionary d: var txt = ""; - foreach (var (k, v) in d) - { + foreach (var (k, v) in d) { txt += $"{k}={v};"; } return $"{{{txt}}}"; case Dictionary> d: var t = ""; - foreach (var (k, v) in d) - { + foreach (var (k, v) in d) { var values = v.Pretty(); - if (!v.Any()) - { + if (!v.Any()) { values = "*"; } diff --git a/AspectedRouting/Language/Functions/Parameter.cs b/AspectedRouting/Language/Functions/Parameter.cs index bb078cc..d940bbb 100644 --- a/AspectedRouting/Language/Functions/Parameter.cs +++ b/AspectedRouting/Language/Functions/Parameter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using AspectedRouting.Language.Typ; using Type = AspectedRouting.Language.Typ.Type; @@ -44,6 +45,16 @@ namespace AspectedRouting.Language.Functions return new Parameter(unified, ParamName); } + + public IExpression PruneTypes(System.Func allowedTypes) + { + var passedTypes = this.Types.Where(allowedTypes); + if (!passedTypes.Any()) { + return null; + } + + return new Parameter(passedTypes, this.ParamName); + } public IExpression Optimize() { diff --git a/AspectedRouting/Language/IExpression.cs b/AspectedRouting/Language/IExpression.cs index 7eaec69..a47189b 100644 --- a/AspectedRouting/Language/IExpression.cs +++ b/AspectedRouting/Language/IExpression.cs @@ -13,28 +13,29 @@ namespace AspectedRouting.Language IEnumerable Types { get; } /// - /// Evaluates the expression. - /// Gives null if the expression should not give a value + /// Evaluates the expression. + /// Gives null if the expression should not give a value /// /// object Evaluate(Context c, params IExpression[] arguments); /// - /// Creates a copy of this expression, but only execution paths of the given types are kept - /// Return null if no execution paths are found + /// Creates a copy of this expression, but only execution paths of the given types are kept + /// Return null if no execution paths are found /// IExpression Specialize(IEnumerable allowedTypes); + IExpression PruneTypes(System.Func allowedTypes); + /// - /// Optimize a single expression, eventually recursively (e.g. a list can optimize all the contents) + /// 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)' + /// Optimize with the given argument, e.g. listdot can become a list of applied arguments. + /// By default, this should return 'this.Apply(argument)' /// /// /// @@ -46,20 +47,22 @@ namespace AspectedRouting.Language { public static object Run(this IExpression e, Context c, Dictionary tags) { - try - { - return e.Apply(new[] {new Constant(tags)}).Evaluate(c); + try { + var result = e.Apply(new Constant(tags)).Evaluate(c); + while (result is IExpression ex) { + result = ex.Apply(new Constant(tags)).Evaluate(c); + } + + return result; } - catch (Exception err) - { + catch (Exception err) { throw new Exception($"While evaluating the expression {e} with arguments a list of tags", err); } } public static IExpression Specialize(this IExpression e, Type t) { - if (t == null) - { + if (t == null) { throw new NullReferenceException("Cannot specialize to null"); } @@ -70,19 +73,16 @@ namespace AspectedRouting.Language { var newTypes = new HashSet(); - foreach (var oldType in e.Types) - { + foreach (var oldType in e.Types) { var newType = oldType.Substitute(substitutions); - if (newType == null) - { + if (newType == null) { continue; } newTypes.Add(newType); } - if (!newTypes.Any()) - { + if (!newTypes.Any()) { return null; } @@ -96,10 +96,9 @@ namespace AspectedRouting.Language return types; } - /// - /// Runs over all expresions, determines a common ground by unifications - /// THen specializes every expression onto this common ground + /// Runs over all expressions, determines a common ground by unifications + /// Then specializes every expression onto this common ground /// /// The common ground of types [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] @@ -107,41 +106,31 @@ namespace AspectedRouting.Language out IEnumerable specializedTypes, out IEnumerable specializedExpressions) { specializedTypes = null; - var exprsForTypes = exprs.SortWidestToSmallest(); - exprsForTypes.Reverse(); + var allExpressions = new HashSet(); + specializedExpressions = allExpressions; + foreach (var expr in exprs) { + if (specializedTypes == null) { + specializedTypes = expr.Types; + } + else { + var newlySpecialized = Typs.WidestCommonTypes(specializedTypes, expr.Types); + if (!newlySpecialized.Any()) { + throw new ArgumentException("Could not find a common ground for types "+specializedTypes.Pretty()+ " and "+expr.Types.Pretty()); + } - foreach (var f in exprsForTypes) - { - if (specializedTypes == null) - { - specializedTypes = f.Types.ToHashSet(); - continue; + specializedTypes = newlySpecialized; } - var specialized = f.Types.RenameVars(specializedTypes).SpecializeTo(specializedTypes, false); - // ReSharper disable once JoinNullCheckWithUsage - if (specialized == null) - { - throw new ArgumentException("Could not unify\n " - + ": " + string.Join(", ", specializedTypes) + - "\nwith\n " - + f.Optimize() + ": " + string.Join(", ", f.Types)); - } - - specializedTypes = specialized; + } - var tps = specializedTypes; - - var optExprs = new List(); - foreach (var expr in exprs) - { - var exprOpt = expr.Specialize(tps); - optExprs.Add(exprOpt); + foreach (var expr in exprs) { + var e = expr.Specialize(specializedTypes); + allExpressions.Add(e); } - return specializedExpressions = optExprs; + return specializedExpressions = allExpressions; } } } \ No newline at end of file diff --git a/AspectedRouting/Language/Typ/Typs.cs b/AspectedRouting/Language/Typ/Typs.cs index b869be6..f027689 100644 --- a/AspectedRouting/Language/Typ/Typs.cs +++ b/AspectedRouting/Language/Typ/Typs.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using AspectedRouting.Language.Functions; namespace AspectedRouting.Language.Typ { @@ -72,7 +71,13 @@ namespace AspectedRouting.Language.Typ return SelectSmallestUnion(subbed, t1); } - private static Type SelectSmallestUnion(this Type wider, Type smaller, bool reverse = false) + /// + /// Findes the smallest union type between the two types, with the assumption that 'wider' is a supertype of 'smaller' + /// + /// + /// + /// + private static Type SelectSmallestUnion(this Type wider, Type smaller) { switch (wider) { case Var a: @@ -80,12 +85,12 @@ namespace AspectedRouting.Language.Typ case ListType l when smaller is ListType lsmaller: return new ListType( l.InnerType.SelectSmallestUnion( - l.InnerType.SelectSmallestUnion(lsmaller.InnerType, reverse))); + l.InnerType.SelectSmallestUnion(lsmaller.InnerType))); case Curry cWider when smaller is Curry cSmaller: var arg = - cWider.ArgType.SelectSmallestUnion(cSmaller.ArgType, !reverse); + cWider.ArgType.SelectSmallestUnion(cSmaller.ArgType); var result = - cWider.ResultType.SelectSmallestUnion(cSmaller.ResultType, reverse); + cWider.ResultType.SelectSmallestUnion(cSmaller.ResultType); return new Curry(arg, result); default: if (wider.IsSuperSet(smaller) && !smaller.IsSuperSet(wider)) { @@ -96,88 +101,6 @@ namespace AspectedRouting.Language.Typ } } - public static List SortWidestToSmallest(this IEnumerable expressions) - { - var all = expressions.ToHashSet(); - var sorted = new List(); - while (all.Any()) { - var widest = SelectWidestType(all); - if (widest == null) { - throw new ArgumentException("Can not sort widest to smallest"); - } - - all.Remove(widest); - sorted.Add(widest); - } - - return sorted; - } - - public static IExpression SelectSmallestType(IEnumerable expressions) - { - IExpression smallest = null; - foreach (var current in expressions) { - if (smallest == null) { - smallest = current; - continue; - } - - if (smallest.Types.AllAreSuperset(current.Types)) { - smallest = current; - } - } - - - return smallest; - } - - public static IExpression SelectWidestType(IEnumerable expressions) - { - IExpression widest = null; - foreach (var current in expressions) { - if (widest == null) { - widest = current; - continue; - } - - if (current.Types.AllAreSuperset(widest.Types)) { - widest = current; - } - } - - return widest; - } - - public static bool AllAreSuperset(this IEnumerable shouldBeSuper, IEnumerable shouldBeSmaller) - { - foreach (var super in shouldBeSuper) { - foreach (var smaller in shouldBeSmaller) { - if (!super.IsSuperSet(smaller)) { - return false; - } - } - } - - return true; - } - - /// - /// Tries to unify t0 with all t1's. - /// Every match is returned - /// - /// - /// - /// - public static IEnumerable UnifyAny(this Type t0, IEnumerable t1) - { - var result = t1.Select(t => t0.Unify(t)).Where(unification => unification != null).ToHashSet(); - if (!result.Any()) { - return null; - } - - return result; - } - /// /// Tries to unify t0 with all t1's. /// Every match is returned, but only if every unification could be performed @@ -325,6 +248,7 @@ namespace AspectedRouting.Language.Typ // This means that $a is substituted by e.g. ($a -> $x), implying an infinite and contradictory type return null; } + substitutionsOn0[key] = newVal; appliedTransitivity = true; } @@ -426,5 +350,197 @@ namespace AspectedRouting.Language.Typ args.Add(t); return args; } + + + /// + /// If given two sets of types, select all the WidestCommonType-Combinations possible + /// e.g.: + /// { Double, Tags -> PDouble} x {PDouble, a -> Double} will result in: + /// { Double, Tags -> Double} + /// + /// + /// + /// + public static IEnumerable WidestCommonTypes(IEnumerable t0, IEnumerable t1) + { + var widest = new HashSet(); + + foreach (var type0 in t0) { + foreach (var type1 in t1) { + var t = WidestCommonType(type0, type1); + if (t != null) { + var (type, subsTable) = t.Value; + if (subsTable != null) { + type = type.Substitute(subsTable); + } + widest.Add(type); + } + } + } + + return widest; + } + + /// + /// Tries to find the widest type between the two given types, without assuming which one is wider. + /// This is used to find the Union of types, e.g. in a list of expressions + /// e.g.: + /// WidestCommonType(Double, PDouble) == Double + /// WidestCommonType(a -> Double, Double) == null + /// WidestCommonType(a -> Double, Tags -> PDouble) => Tags -> Double + /// WidestCommonType(Double -> a, PDouble -> b) => PDouble -> a + /// + /// + /// + /// + public static (Type, Dictionary? substitutionTable)? WidestCommonType(Type t0, Type t1) + { + // First things first: we try to unify + + if (t0 is Curry c0 && t1 is Curry c1) { + var arg = SmallestCommonType(c0.ArgType, c1.ArgType); + var result = WidestCommonType(c0.ResultType, c1.ResultType); + if (arg == null) { + return null; + } + var (argT, subs0) = arg.Value; + + if (result == null) { + return null; + } + var (resultT, subs1) = result.Value; + return (new Curry(argT, resultT), MergeDicts(subs0, subs1)); + } + + + if (t0 is Var v) { + if (t1 is Var vx) { + if (v.Name == vx.Name) { + return (t0, null); + } + } + return (t1, new Dictionary {{v.Name, t1}}); + } + + if (t1 is Var v1) { + return (t0, new Dictionary {{v1.Name, t1}}); + } + if (t0 == t1 || t0.Equals(t1)) { + return (t0, null); + } + var t0IsSuperT1 = t0.IsSuperSet(t1); + var t1IsSuperT0 = t1.IsSuperSet(t0); + if (t0IsSuperT1 && !t1IsSuperT0) { + return (t0, null); + } + + if (t1IsSuperT0 && !t0IsSuperT1) { + return (t1, null); + } + + return null; + } + + private static Dictionary MergeDicts(Dictionary subs0, + Dictionary subs1) + { + if (subs0 == null && subs1 == null) { + return null; + } + var subsTable = new Dictionary(); + + void AddSubs(Dictionary dict) + { + if (dict == null) { + return; + } + foreach (var kv in dict) { + if (subsTable.TryGetValue(kv.Key, out var t)) { + // We have seen this variable-type-name before... We should check if it matches + if (t.Equals(kv.Value)) { + // Ok! No problem! + // It is already added anyway, so we continue + continue; + } + + // Bruh, we have a problem!!! + throw new Exception(t + " != " + kv.Value); + } + + if (kv.Value is Var v) { + if (v.Name == kv.Key) { + // Well, this is a useless substitution... + continue; + } + } + + subsTable[kv.Key] = kv.Value; + } + } + + AddSubs(subs0); + AddSubs(subs1); + + if (!subsTable.Any()) { + return null; + } + + return subsTable; + } + + /// + /// Tries to find the smallest type between the two given types, without assuming which one is wider. + /// This is used to find the Union of types, e.g. in a list of expressions + /// e.g.: + /// WidestCommonType(Double, PDouble) == PDouble + /// WidestCommonType(a -> Double, Double) == null + /// WidestCommonType(a -> Double, Tags -> PDouble) => Tags -> PDouble + /// WidestCommonType(Double -> a, PDouble -> b) => Double -> a + /// + /// + /// + /// + public static (Type, Dictionary substitutionTable)? SmallestCommonType(Type t0, Type t1) + { + if (t0 is Curry c0 && t1 is Curry c1) { + var arg = WidestCommonType(c0.ArgType, c1.ArgType); + var result = SmallestCommonType(c0.ResultType, c1.ResultType); + if (arg == null) { + return null; + } + var (argT, subs0) = arg.Value; + + if (result == null) { + return null; + } + var (resultT, subs1) = result.Value; + return (new Curry(argT, resultT), MergeDicts(subs0, subs1)); + } + + + if (t0 is Var v) { + return (t1, new Dictionary {{v.Name, t1}}); + } + + if (t1 is Var v1) { + return (t0, new Dictionary {{v1.Name, t1}}); + } + + if (t0 == t1 || t0.Equals(t1)) { + return (t0, null); + } + + var t0IsSuperT1 = t0.IsSuperSet(t1); + var t1IsSuperT0 = t1.IsSuperSet(t0); + if (t0IsSuperT1 && !t1IsSuperT0) { + return (t0, null); + } + + if (t1IsSuperT0 && !t0IsSuperT1) { + return (t1, null); + } + + return null; + } } } \ No newline at end of file