Various improvements and bug fixes to the type system

This commit is contained in:
Pieter Vander Vennet 2020-06-15 18:09:41 +02:00
parent 36c1a4fd7a
commit 537fb2c613
31 changed files with 798 additions and 275 deletions

View file

@ -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);
}
}
}

View file

@ -8,43 +8,6 @@ namespace AspectedRouting.Test
{
public class TestAnalysis
{
[Fact]
public void OnAll_SmallTagSet_AllCombinations()
{
var possibleTags = new Dictionary<string, List<string>>
{
{"a", new List<string> {"x", "y"}}
};
var all = possibleTags.OnAllCombinations(dict => ObjectExtensions.Pretty(dict), new List<string>()).ToList();
Assert.Equal(3, all.Count);
Assert.Contains("{}", all);
Assert.Contains("{a=x;}", all);
Assert.Contains("{a=y;}", all);
}
[Fact]
public void OnAll_TwoTagSet_AllCombinations()
{
var possibleTags = new Dictionary<string, List<string>>
{
{"a", new List<string> {"x", "y"}},
{"b", new List<string> {"u", "v"}}
};
var all = possibleTags.OnAllCombinations(dict => dict.Pretty(), new List<string>()).ToList();
Assert.Equal(9, all.Count);
Assert.Contains("{}", all);
Assert.Contains("{a=x;}", all);
Assert.Contains("{a=y;}", all);
Assert.Contains("{b=u;}", all);
Assert.Contains("{b=v;}", all);
Assert.Contains("{a=x;b=u;}", all);
Assert.Contains("{a=x;b=v;}", all);
Assert.Contains("{a=y;b=u;}", all);
Assert.Contains("{a=y;b=v;}", all);
}
}
}

View file

@ -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<string, List<string>>
new Dictionary<string, HashSet<string>>
{
{"maxspeed", new List<string>()},
{"highway", new List<string> {"residential"}},
{"ferry", new List<string>()}
{"maxspeed", new HashSet<string>()},
{"highway", new HashSet<string> {"residential"}},
{"ferry", new HashSet<string>()}
},
aspect.PossibleTags());
}
@ -178,5 +180,14 @@ namespace AspectedRouting.Test
Assert.Equal(new List<IExpression> {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);
}
}
}

View file

@ -15,6 +15,9 @@
<None Update="IO\lua\unitTestProfile2.lua">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="IO\lua\if_then_else_dotted.lua">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View file

@ -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<Type> Types { get; }
public LuaLiteral(IEnumerable<Type> types, string lua)
{
Lua = lua;
Types = types;
}
public object Evaluate(Context c, params IExpression[] arguments)
{
throw new NotImplementedException();
}
public IExpression Specialize(IEnumerable<Type> allowedTypes)
{
return this;
}
public IExpression Optimize()
{
return this;
}
public IExpression OptimizeWithArgument(IExpression argument)
{
return this.Apply(argument);
}
public void Visit(Func<IExpression, bool> f)
{
throw new NotImplementedException();
}
}
}

View file

@ -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<IExpression>();
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:

View file

@ -19,7 +19,7 @@ namespace AspectedRouting.IO.LuaSkeleton
_alreadyAddedFunctions.Add(meta.Name);
var possibleTags = meta.PossibleTags() ?? new Dictionary<string, List<string>>();
var possibleTags = meta.PossibleTags() ?? new Dictionary<string, HashSet<string>>();
int numberOfCombinations;
numberOfCombinations = possibleTags.Values.Select(lst => 1 + lst.Count).Multiply();

View file

@ -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",
"");

View file

@ -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 }",

View file

@ -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<string, IExpression>();
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)
{

View file

@ -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)
{

View file

@ -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

View file

@ -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

View file

@ -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<string>
{
"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<T> OnAllCombinations<T>(this Dictionary<string, List<string>> possibleTags,
Func<Dictionary<string, string>, T> f, List<string> defaultValues)
{
var newDict = new Dictionary<string, List<string>>();
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<string, string>();
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<string, (List<Type> Types, string inFunction)> UsedParameters(
this ProfileMetaData profile, Context context)
@ -492,7 +377,7 @@ namespace AspectedRouting.Language
/// </summary>
/// <param name="e"></param>
/// <returns>A dictionary containing all possible values. An entry with an empty list indicates a wildcard</returns>
public static Dictionary<string, List<string>> PossibleTags(this IExpression e)
public static Dictionary<string, HashSet<string>> PossibleTags(this IExpression e)
{
var mappings = new List<Mapping>();
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<string, List<string>>();
var result = new Dictionary<string, HashSet<string>>();
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;

View file

@ -38,8 +38,7 @@ namespace AspectedRouting.Language.Expression
FunctionApplications = new Dictionary<Type, (IExpression f, IExpression a)>();
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<IExpression>();
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;
}
}
}

View file

@ -40,6 +40,11 @@ namespace AspectedRouting.Language.Expression
return this;
}
public IExpression OptimizeWithArgument(IExpression argument)
{
return this.Apply(argument);
}
public virtual void Visit(Func<IExpression, bool> f)
{
f(this);
@ -50,10 +55,6 @@ namespace AspectedRouting.Language.Expression
return $"${Name}";
}
public IExpression Apply(params IExpression[] args)
{
return this.Apply(args.ToList());
}
/// <summary>
/// Gives an overview per argument what the possible types are.

View file

@ -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<IExpression, bool> f)

View file

@ -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<string, string>(tags);
var canAccess = Access.Run(c, tags);
tags["access"] = "" + canAccess;

View file

@ -14,17 +14,17 @@ namespace AspectedRouting.Language
public static readonly Dictionary<string, Function> Builtins = new Dictionary<string, Function>();
public static IEnumerable<string> 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<IExpression> args)
{
if (args.Count == 0)

View file

@ -14,41 +14,54 @@ namespace AspectedRouting.Language.Functions
public Constant(IEnumerable<Type> types, object o)
{
Types = types;
Types = types.ToList();
if (o is IEnumerable<IExpression> enumerable)
{
o = enumerable.ToList();
}
_o = o;
}
public Constant(Type t, object o)
{
Types = new[] {t};
Types = new List<Type> {t};
if (o is IEnumerable<IExpression> 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<IExpression> xprs)
public Constant(List<IExpression> 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<Type> {Typs.String};
_o = s;
}
@ -121,10 +134,10 @@ namespace AspectedRouting.Language.Functions
addTo.Add(this);
}
public IExpression Specialize(IEnumerable<Type> allowedTypes)
public IExpression Specialize(IEnumerable<Type> 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<IExpression> 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<Type>();
foreach (var allowedType in allowedTypes)
{
if (allowedType is ListType listType)
{
innerTypes.Add(listType.InnerType);
}
}
var specializedExpressions = new List<IExpression>();
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<IExpression> exprs)
{
return new Constant(exprs.Select(x => x.Optimize()));
// This is a list
var optExprs = new List<IExpression>();
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<IExpression, bool> 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)

View file

@ -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<string> ArgNames { get; } = new List<string> {"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);
}
}

View file

@ -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<string> ArgNames { get; } = new List<string> {"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<Type> 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<Type> allowedTypes)
{
var unified = Types.SpecializeTo(allowedTypes);
if (unified == null)
{
return null;
}
return new IfDotted(unified);
}
}
}

View file

@ -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;

View file

@ -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();
}
}
}

View file

@ -46,8 +46,6 @@ namespace AspectedRouting.Language.Functions
public override object Evaluate(Context c, params IExpression[] arguments)
{
// var subFunction = arguments[0];
var tags =(Dictionary<string, string>) arguments[1].Evaluate(c);
var name = c.AspectName.TrimStart('$');

View file

@ -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
)
})

View file

@ -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<IExpression, bool> f)
{

View file

@ -25,8 +25,21 @@ namespace AspectedRouting.Language
/// </summary>
IExpression Specialize(IEnumerable<Type> allowedTypes);
/// <summary>
/// Optimize a single expression, eventually recursively (e.g. a list can optimize all the contents)
/// </summary>
/// <returns></returns>
IExpression Optimize();
/// <summary>
/// Optimize with the given argument, e.g. listdot can become a list of applied arguments.
///
/// By default, this should return 'this.Apply(argument)'
/// </summary>
/// <param name="argument"></param>
/// <returns></returns>
// IExpression OptimizeWithArgument(IExpression argument);
void Visit(Func<IExpression, bool> 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)
{

View file

@ -23,19 +23,19 @@ namespace AspectedRouting.Language.Typ
}
public static HashSet<Type> SpecializeTo(this IEnumerable<Type> types0, IEnumerable<Type> types1)
public static HashSet<Type> SpecializeTo(this IEnumerable<Type> types0, IEnumerable<Type> allowedTypes)
{
var results = new HashSet<Type>();
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)
/// <summary>
/// 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
/// </summary>
/// <param name="t0">The expected, wider type</param>
/// <param name="t1">The actual type</param>
/// <param name="reverseSuperset">True if the supertyping relationship should be reversed</param>
/// <returns></returns>
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;
}
}
/// <summary>
@ -69,7 +108,7 @@ namespace AspectedRouting.Language.Typ
/// <returns></returns>
public static IEnumerable<Type> UnifyAny(this Type t0, IEnumerable<Type> 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
/// <returns></returns>
public static IEnumerable<Type> UnifyAll(this Type t0, IEnumerable<Type> 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<string, Type> substitutions)
{
switch (t0)
@ -111,7 +152,16 @@ namespace AspectedRouting.Language.Typ
}
}
public static Dictionary<string, Type> UnificationTable(this Type t0, Type t1)
/// <summary>
/// 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
/// </summary>
/// <param name="t0"></param>
/// <param name="t1"></param>
/// <returns></returns>
public static Dictionary<string, Type> UnificationTable(this Type t0, Type t1, bool reverseSupersetRelation = false)
{
var substitutionsOn0 = new Dictionary<string, Type>();
@ -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<string> UsedVariables(this Type t0, HashSet<string> addTo = null)
{
addTo ??=new HashSet<string>();
addTo ??= new HashSet<string>();
switch (t0)
{
case Var a:
@ -256,16 +314,23 @@ namespace AspectedRouting.Language.Typ
public static IEnumerable<Type> RenameVars(this IEnumerable<Type> toRename, IEnumerable<Type> 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<string, Type>();
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));

View file

@ -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<BehaviourTestSuite> 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,

View file

@ -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": [