Rework output system: apply 'Tags', add many rewriting rules, add tests

This commit is contained in:
Pieter Vander Vennet 2022-09-06 18:46:01 +02:00
parent 1f27a45037
commit a84bbceda2
42 changed files with 1384 additions and 424 deletions

View file

@ -54,7 +54,7 @@ public class FunctionsTest
{ "x", "y" }
};
var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize();
var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize(out _);
var result = expr.Evaluate(new Context());
Assert.Equal("yes", result);
}
@ -67,7 +67,7 @@ public class FunctionsTest
{ "a", "b" }
};
var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize();
var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize(out var _);
var result = expr.Evaluate(new Context());
Assert.Equal("no", result);
}
@ -81,7 +81,7 @@ public class FunctionsTest
{ "x", "someRandomValue" }
};
var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize();
var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize(out _);
var result = expr.Evaluate(new Context());
Assert.Equal("no", result);
}
@ -97,7 +97,7 @@ public class FunctionsTest
var resultT = ifExpr.Evaluate(c);
Assert.Equal("thenResult", resultT);
resultT = ifExpr.Optimize().Evaluate(c);
resultT = ifExpr.Optimize(out _).Evaluate(c);
Assert.Equal("thenResult", resultT);
}
@ -123,7 +123,7 @@ public class FunctionsTest
{
var c = new Context();
var ifExpr = JsonParser.ParseExpression(c, IfDottedConditionJson);
ifExpr = ifExpr.Optimize();
ifExpr = ifExpr.Optimize(out _);
var resultT = ifExpr.Evaluate(c,
new Constant(Typs.String, "yes"));
var resultF = ifExpr.Evaluate(c,
@ -393,8 +393,8 @@ public class FunctionsTest
var o = f.Evaluate(new Context(), tags0);
Assert.Equal(50.0, o);
}
[Fact]
public void ApplyFirstMatchOf_FirstMatchIsTaken_ResidentialDefault()
{
@ -407,7 +407,7 @@ public class FunctionsTest
var o = f.Evaluate(new Context(), tags0);
Assert.Equal(30, o);
}
[Fact]
public void ApplyFirstMatchOf_NoMatchIfFound_Null()
{
@ -444,4 +444,36 @@ public class FunctionsTest
);
return Funcs.FirstOf.Apply(order, mapping);
}
[Fact]
/**
* Regression test for a misbehaving ifDotted
*/
public void IfDotted_CorrectExpression()
{
var e = Funcs.IfDotted.Apply(
Funcs.Const.Apply(new Parameter("follow_restrictions")),
Funcs.Head.Apply( Funcs.StringStringToTags.Apply( new Mapping(new[] { "oneway" }, new[] { Funcs.Id }))),
Funcs.Const.Apply(new Constant("dont-care"))
);
var c = new Context();
c.AddParameter("follow_restrictions", "yes");
var tags = new Dictionary<string, string>();
tags["oneway"] = "with";
var r = e.Evaluate(c, new Constant(tags));
Assert.Equal("with", r);
var c0 = new Context();
c0.AddParameter("follow_restrictions", "no");
var r0 = e.Evaluate(c0, new Constant(tags));
Assert.Equal("dont-care", r0);
}
}

View file

@ -0,0 +1,151 @@
using AspectedRouting.IO.LuaSkeleton;
using AspectedRouting.Language;
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
using Xunit;
namespace AspectedRouting.Test;
public class OptimizationsTests
{
[Fact]
public void AppliedListDot_Optimize_ListOfApplications()
{
var lit = new LuaLiteral(Typs.Nat, "tags");
var e0 = Funcs.ListDot.Apply(
new Constant(new[]
{
Funcs.Eq.Apply(new Constant(5)),
Funcs.Eq.Apply(new Constant(42))
}));
var e = e0.Apply(lit).SpecializeToSmallestType();
var x = e.Optimize(out var sc);
Assert.True(sc);
Assert.Equal(
new Constant(new[]
{
Funcs.Eq.Apply(new Constant(5)).Apply(lit),
Funcs.Eq.Apply(new Constant(42)).Apply(lit)
}).SpecializeToSmallestType().ToString(), x.ToString());
}
[Fact]
public void AdvancedApplied_Optimized_ListOfAppliedValues()
{
var legal_access_be = new FunctionCall("$legal_access_be", new Curry(Typs.Tags, Typs.String));
var legal_access_pedestrian = new FunctionCall("$pedestrian.legal_access", new Curry(Typs.Tags, Typs.String));
var tags = new LuaLiteral(Typs.Tags, "tags");
var e = new Apply( // string
Funcs.Head,
new Apply( // list (string)
new Apply( // tags -> list (string)
Funcs.ListDot,
new Constant(new[]
{
legal_access_be,
legal_access_pedestrian
})),
tags));
var eOpt = e.Optimize(out var sc);
Assert.True(sc);
Assert.Equal(
Funcs.Head.Apply(new Constant(
new[]
{
legal_access_be.Apply(tags),
legal_access_pedestrian.Apply(tags)
}
)).ToString(),
eOpt.ToString()
);
}
[Fact]
public void advancedExpr_Optimize_Works()
{
var e = new Apply( // double
new Apply( // tags -> double
new Apply( // (tags -> double) -> tags -> doubleTag
Funcs.Default,
new Constant(0)),
new Apply( // tags -> double
new Apply( // (tags -> list (double)) -> tags -> double
Funcs.Dot,
Funcs.Head),
new Apply( // tags -> list (double)
Funcs.StringStringToTags,
new Mapping(
new[]
{
"access"
},
new[]
{
new Mapping(
new[]
{
"private",
"destination",
"permissive"
},
new[]
{
new Constant(-500), new Constant(-3), new Constant(-1)
}
)
}
)))),
new LuaLiteral(Typs.Tags, "tags"));
var eOpt = e.Optimize(out var sc);
Assert.True(sc);
Assert.NotEmpty(eOpt.Types);
}
[Fact]
public void optimizeListdotAway()
{
var tagsToStr = new Curry(Typs.Tags, Typs.PDouble);
var e = new Apply( // pdouble
new Apply( // tags -> pdouble
Funcs.Id,
new Apply( // tags -> pdouble
new Apply( // (tags -> list (pdouble)) -> tags -> pdouble
Funcs.Dot,
Funcs.Min),
new Apply( // tags -> list (pdouble)
Funcs.ListDot,
new Constant(new IExpression[]
{
new FunctionCall("$legal_maxspeed_be", tagsToStr),
new FunctionCall("$car.practical_max_speed", tagsToStr),
new Apply( // tags -> pdouble
Funcs.Const,
new Parameter("#maxspeed"))
})))),
new LuaLiteral(Typs.Tags, "tags"));
var opt = e.SpecializeToSmallestType().Optimize(out var sc);
Assert.True(sc);
}
[Fact]
public void Regression_ShouldOptimize()
{
var e = new Apply( // nat
new Apply( // tags -> nat
new Apply( // nat -> tags -> nat
new Apply( // (nat -> tags -> nat) -> nat -> tags -> nat
Funcs.ConstRight,
Funcs.Id),
Funcs.Const),
new LuaLiteral(Typs.PDouble, "distance")),
new LuaLiteral(Typs.Tags, "tags")).SpecializeToSmallestType();
var opt = e.Optimize(out var sc);
Assert.True(sc);
}
}

View file

@ -0,0 +1,52 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.Json;
using AspectedRouting.IO.jsonParser;
using AspectedRouting.Language;
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
using Xunit;
namespace AspectedRouting.Test;
public class RegressionTest
{
[Fact]
public void IfDotted_ShouldBeParsed()
{
var carOneway = Funcs.Const.Apply(new Constant("result of car.oneway")).Specialize(new Curry(
Typs.Tags, Typs.String));
var doc = JsonDocument.Parse("{\"oneway\":{\"$ifdotted\":{\"$const\": \"#follow_restrictions\"},\"then\": \"$car.oneway\",\"else\": {\"$const\": \"both-ignored-restrictions\"}}}");
var parsingContext = new Context();
parsingContext .AddFunction("car.oneway", new AspectMetadata(
carOneway, "car.oneway","oneway function", "test", "with|against|both",
"N/A", false
));
parsingContext.AddParameter("follow_restrictions","no");
var aspect = JsonParser.ParseProfileProperty(doc.RootElement,parsingContext, "oneway");
var oneway = new Dictionary<string, string>();
var c = new Context();
c .AddFunction("car.oneway", new AspectMetadata(
carOneway, "car.oneway","oneway function", "test", "with|against|both",
"N/A", false
));
c.AddParameter("follow_restrictions","yes");
var result = aspect.Run(c, oneway);
Assert.Equal("result of car.oneway", result);
var c0 = new Context();
c0.AddFunction("car.oneway", new AspectMetadata(
carOneway, "car.oneway","oneway function", "test", "with|against|both",
"N/A", false
));
c0.AddParameter("follow_restrictions","no");
var result0 = aspect.Run(c0, oneway);
Assert.Equal("both-ignored-restrictions", result0);
}
}

View file

@ -7,84 +7,91 @@ using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
using Xunit;
namespace AspectedRouting.Test.Snippets
namespace AspectedRouting.Test.Snippets;
public class SnippetTests
{
public class SnippetTests
[Fact]
public void DefaultSnippet_SimpleDefault_GetsLua()
{
[Fact]
public void DefaultSnippet_SimpleDefault_GetsLua()
var gen = new DefaultSnippet();
var lua = new LuaSkeleton(new Context(), true);
var code = gen.Convert(lua, "result", new List<IExpression>
{
var gen = new DefaultSnippet();
var lua = new LuaSkeleton(new Context(), true);
var code = gen.Convert(lua, "result", new List<IExpression> {
new Constant("the_default_value"),
Funcs.Id,
new Constant("value")
});
Assert.Contains("if (result == nil) then\n result = \"the_default_value\"", code);
}
[Fact]
public void FirstOfSnippet_SimpleFirstOf_GetLua()
{
var gen = new FirstMatchOfSnippet();
var lua = new LuaSkeleton(new Context(), true);
// FirstMatchOf: [a] -> (Tags -> [a]) -> Tags -> a
// Order: [string]
var order = new Constant(new List<IExpression> {
new Constant("bicycle"),
new Constant("access")
});
// Func: (Tags -> [a])
var func = new Apply(
Funcs.StringStringToTags,
new Mapping(
new[] { "bicycle", "access" },
new IExpression[] {
Funcs.Id,
Funcs.Id
}
)
);
var tags = new LuaLiteral(new[] { Typs.Tags }, "tags");
var code = gen.Convert(lua, "result",
new List<IExpression> {
order,
func,
tags
}
);
// First the more general ones!
Assert.Equal(
"if (tags[\"access\"] ~= nil) then\n result = tags[\"access\"]\n \nend\nif (tags[\"bicycle\"] ~= nil) then\n result = tags[\"bicycle\"]\n \nend\n",
code);
}
[Fact]
public void SimpleMappingSnippet_SimpleMapping_GeneratesLua()
{
var mapping = new Mapping(
new[] { "1", "-1" },
new IExpression[] {
new Constant("with"),
new Constant("against")
}
);
var gen = new SimpleMappingSnippet(mapping);
var code = gen.Convert(new LuaSkeleton(new Context(), true), "result", new List<IExpression> {
new LuaLiteral(Typs.String, "tags.oneway")
});
var expected =
"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);
}
new Constant("the_default_value"),
Funcs.Id,
new Constant("value")
});
Assert.Contains("if (result == nil) then\n result = \"the_default_value\"", code);
}
[Fact]
public void FirstOfSnippet_SimpleFirstOf_GetLua()
{
var gen = new FirstMatchOfSnippet();
var lua = new LuaSkeleton(new Context(), true);
// FirstMatchOf: [a] -> (Tags -> [a]) -> Tags -> a
// Order: [string]
var order = new Constant(new List<IExpression>
{
new Constant("bicycle"),
new Constant("access")
});
// Func: (Tags -> [a])
var func = new Apply(
Funcs.StringStringToTags,
new Mapping(
new[] { "bicycle", "access" },
new IExpression[]
{
Funcs.Id,
Funcs.Id
}
)
);
var tags = new LuaLiteral(new[] { Typs.Tags }, "tags");
var code = gen.Convert(lua, "result",
new List<IExpression>
{
order,
func,
tags
}
);
// First the more general ones!
Assert.Equal(
"if (tags[\"access\"] ~= nil) then\n result = tags[\"access\"]\n \nend\nif (tags[\"bicycle\"] ~= nil) then\n result = tags[\"bicycle\"]\n \nend\n",
code);
}
[Fact]
public void SimpleMappingSnippet_SimpleMapping_GeneratesLua()
{
var mapping = new Mapping(
new[] { "1", "-1" },
new IExpression[]
{
new Constant("with"),
new Constant("against")
}
);
var gen = new SimpleMappingSnippet(mapping);
var code = gen.Convert(new LuaSkeleton(new Context(), true), "result", new List<IExpression>
{
new LuaLiteral(Typs.String, "tags.oneway")
});
var expected =
"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);
}
}

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language;
using AspectedRouting.Language.Typ;
using Type = AspectedRouting.Language.Typ.Type;
namespace AspectedRouting.IO.LuaSkeleton
@ -40,14 +41,35 @@ namespace AspectedRouting.IO.LuaSkeleton
return null;
}
public IExpression Optimize()
public IExpression Optimize(out bool somethingChanged)
{
somethingChanged = false;
return this;
}
public void Visit(Func<IExpression, bool> f)
{
throw new NotImplementedException();
f(this);
}
public bool Equals(IExpression other)
{
if (other is LuaLiteral ll)
{
return ll.Lua.Equals(this.Lua);
}
return false;
}
public string Repr()
{
if (this.Types.Count() == 1 && this.Types.First() == Typs.Tags)
{
return $"new LuaLiteral(Typs.Tags, \"{this.Lua}\")";
}
return $"new LuaLiteral(\"{this.Lua}\")";
}
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text.RegularExpressions;
using AspectedRouting.IO.itinero1;
@ -13,6 +14,12 @@ namespace AspectedRouting.IO.LuaSkeleton
{
public partial class LuaSkeleton
{
internal string ToLuaWithTags(IExpression bare)
{
var opt = bare.Apply(new LuaLiteral(Typs.Tags, "tags")).SpecializeToSmallestType().Optimize(out _);
return this.ToLua(opt);
}
internal string ToLua(IExpression bare, string key = "nil", bool forceFirstArgInDot = false)
{
var collectedMapping = new List<IExpression>();
@ -57,36 +64,29 @@ namespace AspectedRouting.IO.LuaSkeleton
return "memberOf(funcName, parameters, tags, result)";
}
var collectedList = new List<IExpression>();
var func = new List<IExpression>();
if (
UnApply(
UnApply(IsFunc(Funcs.Dot), Assign(func)),
UnApply(IsFunc(Funcs.ListDot),
Assign(collectedList))).Invoke(bare)) {
var exprs = (IEnumerable<IExpression>) ((Constant) collectedList.First()).Evaluate(_context);
var luaExprs = new List<string>();
var funcName = func.First().ToString().TrimStart('$');
AddDep(funcName);
foreach (var expr in exprs) {
var c = new List<IExpression>();
if (UnApply(IsFunc(Funcs.Const), Assign(c)).Invoke(expr)) {
luaExprs.Add(ToLua(c.First(), key));
continue;
{
var name = new List<string>();
var arg = new List<IExpression>();
if (UnApply(
IsFunctionCall(name),
Assign(arg)
).Invoke(bare))
{
var called = _context.DefinedFunctions[name.First()];
if (called.ProfileInternal) {
return called.Name;
}
if (expr.Types.First() is Curry curry
&& curry.ArgType.Equals(Typs.Tags)) {
var lua = ToLua(expr, key);
luaExprs.Add(lua);
AddDependenciesFor(called);
AddFunction(called);
var usesParams = called.ExpressionImplementation.UsedParameters().Any();
if (usesParams)
{
return $"{name.First().Replace(".","_")}({ToLua(arg.First())}, parameters)";
}
return $"{name.First().Replace(".","_")}({ToLua(arg.First())})";
}
return "\n " + funcName + "({\n " + string.Join(",\n ", luaExprs) +
"\n })";
}
collectedMapping.Clear();
var dottedFunction = new List<IExpression>();
dottedFunction.Clear();
@ -116,7 +116,7 @@ namespace AspectedRouting.IO.LuaSkeleton
bare.Types.First() is Curry curr &&
curr.ArgType.Equals(Typs.String)) {
var applied = new Apply(bare, new Constant(curr.ArgType, ("tags", "\"" + key + "\"")));
return ToLua(applied.Optimize(), key);
return ToLua(applied.Optimize(out _), key);
}
@ -139,7 +139,13 @@ namespace AspectedRouting.IO.LuaSkeleton
}
if (baseFunc.Name.Equals(Funcs.Dot.Name)) {
if (args.Count == 1 || forceFirstArgInDot) {
if (args.Count == 1 )
{
return ToLua(args[0]);
}
if (forceFirstArgInDot)
{
return ToLua(args[0]);
}
@ -173,7 +179,7 @@ namespace AspectedRouting.IO.LuaSkeleton
AddDependenciesFor(called);
AddFunction(called);
return $"{fc.CalledFunctionName.AsLuaIdentifier()}(parameters, tags, result)";
return $"{fc.CalledFunctionName.AsLuaIdentifier()}(tags, parameters)";
case Constant c:
return ConstantToLua(c);
case Mapping m:
@ -241,12 +247,12 @@ namespace AspectedRouting.IO.LuaSkeleton
/// <returns></returns>
private string ConstantToLua(Constant c)
{
var o = c.Evaluate(_context);
var o = c.Get();
switch (o) {
case LuaLiteral lua:
return lua.Lua;
case IExpression e:
return ConstantToLua(new Constant(e.Types.First(), e.Evaluate(null)));
return ToLua(e);
case int i:
return "" + i;
case double d:

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.IO.itinero1;
@ -35,17 +36,21 @@ namespace AspectedRouting.IO.LuaSkeleton
return true;
});
var expression = meta.ExpressionImplementation;
var expression = Funcs.Either(Funcs.Id, Funcs.Const, meta.ExpressionImplementation)
.Apply(new LuaLiteral(Typs.Tags, "tags"))
.PruneTypes(t => !(t is Curry))
.SpecializeToSmallestType()
.Optimize(out _);
if (!expression.Types.Any())
{
throw new Exception("Could not optimize expression with applied tags");
}
var ctx = Context;
_context = _context.WithAspectName(meta.Name);
var body = "";
if (_useSnippets) {
if (expression.Types.First() is Curry c) {
expression = expression.Apply(new LuaLiteral(Typs.Tags, "tags"));
}
body = Utils.Lines(
" local r = nil",
" " + Snippets.Convert(this, "r", expression).Indent(),
@ -56,7 +61,6 @@ namespace AspectedRouting.IO.LuaSkeleton
body = " return " + ToLua(expression);
}
var impl = Utils.Lines(
"--[[",
meta.Description,
@ -68,7 +72,7 @@ namespace AspectedRouting.IO.LuaSkeleton
"Number of combintations: " + numberOfCombinations,
"Returns values: ",
"]]",
"function " + meta.Name.AsLuaIdentifier() + "(parameters, tags, result)" + funcNameDeclaration,
"function " + meta.Name.AsLuaIdentifier() + "(tags, parameters)" + funcNameDeclaration,
body,
"end"
);

View file

@ -43,6 +43,7 @@ namespace AspectedRouting.IO.LuaSkeleton
internal void AddDep(string name)
{
if (name.StartsWith("mapping")) {
Console.Error.WriteLine(">>>");
throw new Exception("A mapping was added as dependency - this is a bug");
}

View file

@ -63,7 +63,7 @@ namespace AspectedRouting.IO.LuaSnippets
result += "end\n";
// note: we do not do an 'elseif' as we have to fallthrough
if (result.Contains("tags[\"nil\"]")) {
Console.WriteLine("EUHM");
Console.WriteLine("Warning: FirstMatchOf has a 'nil' in the indexes due to expression "+t.ToString());
}
}

View file

@ -11,7 +11,7 @@ namespace AspectedRouting.IO.LuaSnippets
public override string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, List<IExpression> args)
{
var fCond = args[0].Optimize();
var fCond = args[0].Optimize(out _);
var fValue = args[1];
IExpression fElse = null;
var arg = args[2];

View file

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.Language;
using AspectedRouting.Language.Typ;
using static AspectedRouting.Language.Deconstruct;
namespace AspectedRouting.IO.LuaSnippets
{
@ -11,29 +13,56 @@ namespace AspectedRouting.IO.LuaSnippets
public override string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, List<IExpression> args)
{
var cond = args[0].Optimize();
var cond = args[0].Optimize(out _);
var ifTrue = args[1];
IExpression ifElse = null;
if (args.Count == 3) {
ifElse = args[2];
}
var c = lua.FreeVar("cond");
var result = "";
result += "local " + c+"\n";
var isString = cond.Types.First().Equals(Typs.String);
result += Snippets.Convert(lua, c, cond)+"\n";
result += $"if ( {c} or {c} == \"yes\" ) then \n";
result += " " + Snippets.Convert(lua, assignTo, ifTrue).Indent() ;
if (ifElse != null) {
result += "else\n";
result += " " + Snippets.Convert(lua, assignTo, ifElse).Indent();
{
var fa = new List<IExpression>();
if (UnApply(
IsFunc(Funcs.IsNull),
Assign(fa)
).Invoke(cond))
{
if (fa.First().ToString() == ifElse.ToString())
{
var result = "";
// We calculate the value that we need
result += Snippets.Convert(lua,assignTo , ifElse)+"\n";
result += "if (" + assignTo + " == nil) then\n";
result += " " + Snippets.Convert(lua, assignTo, ifTrue).Indent();
result += "end\n";
return result;
}
throw new Exception("TODO optimize with default");
}
}
result += "end\n";
return result;
{
var c = lua.FreeVar("cond");
var result = "";
result += "local " + c+"\n";
var isString = cond.Types.First().Equals(Typs.String);
result += Snippets.Convert(lua, c, cond)+"\n";
result += $"if ( {c} or {c} == \"yes\" ) then \n";
result += " " + Snippets.Convert(lua, assignTo, ifTrue).Indent() ;
if (ifElse != null) {
result += "else\n";
result += " " + Snippets.Convert(lua, assignTo, ifElse).Indent();
}
result += "end\n";
return result;
}
}
}
}

View file

@ -21,99 +21,150 @@ namespace AspectedRouting.IO.LuaSnippets
public override string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, List<IExpression> args)
{
// Multiply multiplies a list of values - we thus have to handle _each_ arg
// Note: we get a single argument which is an expression resulting in a list of values
var listToMultiply = args[0];
var mappings = new List<Mapping>();
var arg = new List<IExpression>();
if (UnApply(UnApply(
IsFunc(Funcs.StringStringToTags),
IsMapping(mappings)),
Assign(arg)
).Invoke(listToMultiply)) {
var mapping = mappings.First();
var result = assignTo + " = " + _neutralValue + "\n";
var mappingArg = arg.First();
if (!Equals(mappingArg.Types.First(), Typs.Tags)) {
return null;
}
{
var mappings = new List<Mapping>();
var arg = new List<IExpression>();
if (args.Count == 1 && UnApply(UnApply(
IsFunc(Funcs.StringStringToTags),
IsMapping(mappings)),
Assign(arg)
).Invoke(args[0]))
{
var mapping = mappings.First();
string tags;
if (mappingArg is LuaLiteral literal) {
tags = literal.Lua;
}
else {
tags = lua.FreeVar("tags");
result += "local " + tags + " = nil\n";
result += Snippets.Convert(lua, tags, mappingArg);
}
var m = lua.FreeVar("m");
result += " local " + m + " = nil\n";
foreach (var (key, func) in mapping.StringToResultFunctions) {
result += "if (" + tags + "[\"" + key + "\"] ~= nil) then\n";
result += m + " = nil\n";
result += " " +
Snippets.Convert(lua, m,
func.Apply(new LuaLiteral(Typs.String, tags + "[\"" + key + "\"]"))).Indent() + "\n";
result += "\n\n if (" + m + " ~= nil) then\n " +
Combine(assignTo, m) +
"\n end\n";
result += "end\n";
}
return result;
}
var listDotArgs = new List<IExpression>();
if (UnApply(
UnApply(IsFunc(Funcs.ListDot),
Assign(listDotArgs)),
Assign(arg)
).Invoke(listToMultiply)) {
var listDotArg = arg.First();
if (!(listDotArgs.First().Evaluate(lua.Context) is List<IExpression> functionsToApply)) {
return null;
}
var result = " " + assignTo + " = " + _neutralValue + "\n";
string tags;
if (listDotArg is LuaLiteral literal) {
tags = literal.Lua;
}
else {
tags = lua.FreeVar("tags");
result += " local " + tags + "\n";
result += Snippets.Convert(lua, tags, listDotArg);
}
var m = lua.FreeVar("m");
result += " local " + m + "\n";
foreach (var func in functionsToApply) {
result += " " + m + " = nil\n";
var subMapping = ExtractSubMapping(func);
if (subMapping != null) {
var (key, f) = subMapping.Value;
var e = f.Apply(new LuaLiteral(Typs.String, tags + "[\"" + key + "\"]"));
e = e.Optimize();
result += Snippets.Convert(lua, m, e).Indent();
}
else {
result += Snippets.Convert(lua, m, func.Apply(new LuaLiteral(Typs.Tags, "tags")));
var result = assignTo + " = " + _neutralValue + "\n";
var mappingArg = arg.First();
if (!Equals(mappingArg.Types.First(), Typs.Tags))
{
return null;
}
string tags;
if (mappingArg is LuaLiteral literal)
{
tags = literal.Lua;
}
else
{
tags = lua.FreeVar("tags");
result += "local " + tags + " = nil\n";
result += Snippets.Convert(lua, tags, mappingArg);
}
result += "\n\n if (" + m + " ~= nil) then\n " + Combine(assignTo, m) + "\n end\n";
var m = lua.FreeVar("m");
result += " local " + m + " = nil\n";
foreach (var (key, func) in mapping.StringToResultFunctions)
{
result += "if (" + tags + "[\"" + key + "\"] ~= nil) then\n";
result += m + " = nil\n";
result += " " +
Snippets.Convert(lua, m,
func.Apply(new LuaLiteral(Typs.String, tags + "[\"" + key + "\"]"))).Indent() +
"\n";
result += "\n\n if (" + m + " ~= nil) then\n " +
Combine(assignTo, m) +
"\n end\n";
result += "end\n";
}
return result;
}
}
{
// Print a 'listDot', assume 'tags' is the applied argument
var arg = new List<IExpression>();
var listDotArgs = new List<IExpression>();
if (args.Count == 1 && UnApply(
UnApply(IsFunc(Funcs.ListDot),
Assign(listDotArgs)),
Assign(arg)
).Invoke(args[0]))
{
var listDotArg = arg.First();
if (!(listDotArgs.First().Evaluate(lua.Context) is List<IExpression> functionsToApply))
{
return null;
}
var result = " " + assignTo + " = " + _neutralValue + "\n";
string tags;
if (listDotArg is LuaLiteral literal)
{
tags = literal.Lua;
}
else
{
tags = lua.FreeVar("tags");
result += " local " + tags + "\n";
result += Snippets.Convert(lua, tags, listDotArg);
}
var m = lua.FreeVar("m");
result += " local " + m + "\n";
foreach (var func in functionsToApply)
{
result += " " + m + " = nil\n";
var subMapping = ExtractSubMapping(func);
if (subMapping != null)
{
var (key, f) = subMapping.Value;
var e = f.Apply(new LuaLiteral(Typs.String, tags + "[\"" + key + "\"]"));
e = e.Optimize(out _);
result += Snippets.Convert(lua, m, e).Indent();
}
else
{
result += Snippets.Convert(lua, m, func.Apply(new LuaLiteral(Typs.Tags, "tags")));
}
return result;
result += "\n\n if (" + m + " ~= nil) then\n " + Combine(assignTo, m) + "\n end\n";
}
return result;
}
}
{
var constantArgs = new List<Constant>();
if (args.Count == 1 && IsConstant(constantArgs).Invoke(args[0]))
{
if (!(constantArgs.First().Get() is List<IExpression> listItems))
{
return null;
}
var result = " " + assignTo + " = " + _neutralValue + "\n";
var m = lua.FreeVar("m");
result += " local " + m + "\n";
foreach (var listItem in listItems)
{
result += " " + m + " = nil\n";
result += Snippets.Convert(lua, m, listItem).Indent();
result += "\n\n if (" + m + " ~= nil) then\n " + Combine(assignTo, m) + "\n end\n";
}
return result;
}
}
Console.Error.WriteLine("ListFoldingSnippet encountered an unsupported expression");
throw new NotImplementedException();
}

View file

@ -33,7 +33,7 @@ namespace AspectedRouting.IO.LuaSnippets
public static string Convert(LuaSkeleton.LuaSkeleton lua, string assignTo, IExpression e)
{
var opt = e.Optimize();
var opt = e.Optimize(out _);
// Note that optimization might optimize to a _subtype_ of the original expresion - which is fine!
var origType = e.Types.First();
var optType = opt.Types.First();

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.IO.LuaSkeleton;
@ -14,9 +15,9 @@ 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 access = _skeleton.ToLuaWithTags(_profile.Access);
var oneway = _skeleton.ToLuaWithTags(_profile.Oneway);
var speed = _skeleton.ToLuaWithTags(_profile.Speed);
var impl = string.Join("\n",
"",
@ -58,13 +59,16 @@ namespace AspectedRouting.IO.itinero1
var paramInLua = _skeleton.ToLua(new Parameter(parameterName));
var exprInLua = _skeleton.ToLua(expression.Optimize(), forceFirstArgInDot: true);
var resultTypes = expression.Types.Select(t => t.Uncurry().Last());
if (resultTypes.Any(t => t.Name.Equals(Typs.Bool.Name)))
var expr = Funcs.Either(Funcs.Id, Funcs.Const, expression).Apply(new LuaLiteral(Typs.Tags, "tags"))
.SpecializeToSmallestType()
.PruneTypes(t => !(t is Curry))
.Optimize(out _);
if (expr.Types.Any(t => t.Name.Equals(Typs.Bool.Name)))
{
_skeleton. AddDep("parse");
exprInLua = "parse(" + exprInLua + ")";
expr = Funcs.Parse.Apply(expr).SpecializeToSmallestType();
}
var exprInLua = _skeleton.ToLua(expr);
impl += "\n " + string.Join("\n ",
$"if({paramInLua} ~= 0) then",
@ -108,9 +112,15 @@ namespace AspectedRouting.IO.itinero1
var functionName = referenceName.AsLuaIdentifier();
behaviourParameters.TryGetValue("description", out var description);
_skeleton.AddDep("copy_tags");
var usedkeys = _profile.AllExpressionsFor(behaviourName, _context)
.PossibleTagsRecursive(_context)
.Select(t => "\""+ t.Key+"\"")
.ToHashSet();
_skeleton.AddDep("remove_relation_prefix");
var impl = string.Join("\n",
"",
"behaviour_"+functionName+"_used_keys = create_set({"+ string.Join(", " , usedkeys) + "})",
"--[[",
description,
"]]",
@ -124,6 +134,7 @@ namespace AspectedRouting.IO.itinero1
impl += _parameterPrinter.DeclareParametersFor(behaviourParameters);
impl += " " + _profile.Name + "(parameters, tags, result)\n";
impl += " copy_tags(tags, result.attributes_to_keep, behaviour_" + functionName + "_used_keys)\n";
impl += "end\n";
return (functionName, impl);
}

View file

@ -57,7 +57,7 @@ namespace AspectedRouting.IO.itinero1
func.Add("");
func.Add(" subresult.attributes_to_keep = {}");
func.Add(" parameters = default_parameters()");
func.Add($" matched = {preProcName}(parameters, relation_tags, subresult)");
func.Add($" matched = {preProcName}(relation_tags, parameters)");
func.Add(" if (matched) then");
var tagKey = "_relation:" + calledInFunction.AsLuaIdentifier();
extraKeys.Add(tagKey);
@ -90,7 +90,7 @@ namespace AspectedRouting.IO.itinero1
func.Add(" parameters = default_parameters()");
func.Add(_parameterPrinter.DeclareParametersFor(parameters.Where(kv => usedParameters.Contains(kv.Key))
.ToDictionary(kv => kv.Key, kv => kv.Value)));
func.Add($" matched = {preProcName}(parameters, relation_tags, subresult)");
func.Add($" matched = {preProcName}(relation_tags, parameters)");
func.Add(" if (matched) then");
tagKey = "_relation:" + behaviourName.AsLuaIdentifier() + ":" + calledInFunction.AsLuaIdentifier();
extraKeys.Add(tagKey);

View file

@ -27,18 +27,19 @@ namespace AspectedRouting.IO.itinero1
_aspectTestSuites = aspectTestSuites?.Where(suite => suite != null)
?.Select(testSuite => testSuite.WithoutRelationTests())?.ToList();
_profileTests = profileTests;
_skeleton = new LuaSkeleton.LuaSkeleton(context, false);
_skeleton = new LuaSkeleton.LuaSkeleton(context, true);
_parameterPrinter = new LuaParameterPrinter(profile, _skeleton);
}
public string ToLua()
{
_skeleton.AddDep("spoken_instructions");
_skeleton.AddDep("spoken_instructions");
var (membershipFunction, extraKeys) = GenerateMembershipPreprocessor();
var (profileOverview, behaviourFunctions) = GenerateProfileFunctions();
var mainFunction = GenerateMainProfileFunction();
var tests = new LuaTestPrinter(_skeleton, new List<string>{"unitTest","unitTestProfile"}).GenerateFullTestSuite(_profileTests, _aspectTestSuites);
var tests = new LuaTestPrinter(_skeleton, new List<string> { "unitTest", "unitTestProfile" })
.GenerateFullTestSuite(_profileTests, _aspectTestSuites);
var keys = _profile.AllExpressions(_context).PossibleTags().Keys
@ -62,7 +63,15 @@ namespace AspectedRouting.IO.itinero1
"",
profileOverview,
"",
_parameterPrinter.GenerateDefaultParameters()
_parameterPrinter.GenerateDefaultParameters(),
"",
"function create_set(list)",
" local set = {}",
" for _, l in ipairs(list) do " +
" set[l] = true" +
" end",
" return set",
"end"
};
@ -106,7 +115,7 @@ namespace AspectedRouting.IO.itinero1
var behaviourImplementations = new List<string>();
foreach (var (behaviourName, behaviourParameters) in _profile.Behaviours)
{
var (functionName, implementation ) = GenerateBehaviourFunction(behaviourName, behaviourParameters);
var (functionName, implementation) = GenerateBehaviourFunction(behaviourName, behaviourParameters);
behaviourImplementations.Add(implementation);
profiles.Add(
string.Join(",\n ",

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AspectedRouting.IO.LuaSkeleton;
using AspectedRouting.Language;
using AspectedRouting.Language.Typ;
@ -39,23 +40,22 @@ namespace AspectedRouting.IO.itinero2
// The expression might still have multiple typings,
// which take inputs different from 'Tags', so we specialize the expr first
var exprSpecialized = expr;
var resultType = expr.Types.First();
if (exprSpecialized.Types.Count() >=2) {
exprSpecialized = expr.Specialize(new Curry(Typs.Tags, new Var("a")));
if (exprSpecialized == null) {
throw new Exception("Could not specialize expression to type tags -> $a");
}
resultType = (exprSpecialized.Types.First() as Curry).ResultType;
var appliedExpr = Funcs.Either(Funcs.Id, Funcs.Const, expr)
.Apply(new LuaLiteral(Typs.Tags, "tags").SpecializeToSmallestType())
.PruneTypes(tp => !(tp is Curry));
var exprSpecialized = appliedExpr.Optimize(out _);
if (exprSpecialized.Types.First().Equals(Typs.Bool) || exprSpecialized.Types.First().Equals(Typs.String))
{
_skeleton.AddDep("parse");
exprSpecialized = Funcs.Parse.Apply(exprSpecialized);
}
var exprInLua = _skeleton.ToLua(exprSpecialized);
if (resultType.Equals(Typs.Bool) || resultType.Equals(Typs.String))
if (exprInLua.Contains("constRight") || exprInLua.Contains("firstArg"))
{
_skeleton.AddDep("parse");
exprInLua = "parse(" + exprInLua + ")";
throw new Exception("Not optimized properly:" + exprSpecialized.Repr());
}
aspects.Add(weight + " * " + exprInLua);
}
@ -126,7 +126,7 @@ namespace AspectedRouting.IO.itinero2
" local parameters = default_parameters()",
_parameterPrinter.DeclareParametersFor(parameters),
"",
" local oneway = " + _skeleton.ToLua(_profile.Oneway),
" local oneway = " + _skeleton.ToLuaWithTags(_profile.Oneway).Indent(),
" tags.oneway = oneway",
" -- An aspect describing oneway should give either 'both', 'against' or 'width'",
@ -134,7 +134,7 @@ namespace AspectedRouting.IO.itinero2
"",
" -- forward calculation. We set the meta tag '_direction' to 'width' to indicate that we are going forward. The other functions will pick this up",
" tags[\"_direction\"] = \"with\"",
" local access_forward = " + _skeleton.ToLua(_profile.Access),
" local access_forward = " + _skeleton.ToLuaWithTags(_profile.Access).Indent(),
" if(oneway == \"against\") then",
" -- no 'oneway=both' or 'oneway=with', so we can only go back over this segment",
" -- we overwrite the 'access_forward'-value with no; whatever it was...",
@ -142,7 +142,7 @@ namespace AspectedRouting.IO.itinero2
" end",
" if(access_forward ~= nil and access_forward ~= \"no\" and access_forward ~= false) then",
" tags.access = access_forward -- might be relevant, e.g. for 'access=dismount' for bicycles",
" result.forward_speed = " + _skeleton.ToLua(_profile.Speed).Indent(),
" result.forward_speed = " + _skeleton.ToLuaWithTags(_profile.Speed).Indent(),
" tags.speed = result.forward_speed",
" local priority = calculate_priority(parameters, tags, result, access_forward, oneway, result.forward_speed)",
" if (priority <= 0) then",
@ -154,7 +154,7 @@ namespace AspectedRouting.IO.itinero2
"",
" -- backward calculation",
" tags[\"_direction\"] = \"against\" -- indicate the backward direction to priority calculation",
" local access_backward = " + _skeleton.ToLua(_profile.Access),
" local access_backward = " + _skeleton.ToLuaWithTags(_profile.Access).Indent(),
" if(oneway == \"with\") then",
" -- no 'oneway=both' or 'oneway=against', so we can only go forward over this segment",
" -- we overwrite the 'access_forward'-value with no; whatever it was...",
@ -162,7 +162,7 @@ namespace AspectedRouting.IO.itinero2
" end",
" if(access_backward ~= nil and access_backward ~= \"no\" and access_backward ~= false) then",
" tags.access = access_backward",
" result.backward_speed = " + _skeleton.ToLua(_profile.Speed).Indent(),
" result.backward_speed = " + _skeleton.ToLuaWithTags(_profile.Speed).Indent(),
" tags.speed = result.backward_speed",
" local priority = calculate_priority(parameters, tags, result, access_backward, oneway, result.backward_speed)",
" if (priority <= 0) then",

View file

@ -24,9 +24,12 @@ namespace AspectedRouting.IO.jsonParser
return null;
}
Console.WriteLine("Parsing " + fileName);
Console.Write("Parsing " + fileName+"... ");
return doc.RootElement.ParseAspect(fileName, c);
var aspect= doc.RootElement.ParseAspect(fileName, c);
Console.WriteLine($"\rAspect {aspect.Name} has type {string.Join(",", aspect.ExpressionImplementation.Types)}");
return aspect;
}
catch (Exception e)
{
@ -90,15 +93,26 @@ namespace AspectedRouting.IO.jsonParser
filepath + " is not a valid profile; it does not contain the obligated parameter " + name);
}
var defaultParameters = e.GetProperty("defaults").ParseParameters();
var contextWithParameters = new Context(context);
foreach (var (paramName, paramExpression) in defaultParameters)
{
if (!context.Parameters.TryGetValue(paramName, out var previousParam))
{
contextWithParameters.AddParameter(paramName, paramExpression);
}
}
var vehicleTypes = GetTopProperty("vehicletypes").EnumerateArray().Select(
el => el.GetString()).ToList();
var metadata = GetTopProperty("metadata").EnumerateArray().Select(
el => el.GetString()).ToList();
var access = ParseProfileProperty(e, context, "access").Finalize();
var oneway = ParseProfileProperty(e, context, "oneway").Finalize();
var speed = ParseProfileProperty(e, context, "speed").Finalize();
var access = ParseProfileProperty(e, contextWithParameters, "access").Finalize();
var oneway = ParseProfileProperty(e, contextWithParameters, "oneway").Finalize();
var speed = ParseProfileProperty(e, contextWithParameters, "speed").Finalize();
IExpression TagsApplied(IExpression x)
@ -152,7 +166,7 @@ namespace AspectedRouting.IO.jsonParser
author,
filepath?.DirectoryName ?? "unknown",
vehicleTypes,
e.GetProperty("defaults").ParseParameters(),
defaultParameters,
profiles,
access,
oneway,
@ -293,6 +307,12 @@ namespace AspectedRouting.IO.jsonParser
if (s.StartsWith("#"))
{
// This is a parameter, the type of it is free
if (context.Parameters.TryGetValue(s.Substring(1), out var param))
{
return new Parameter(s).Specialize(param.Types);
}
return new Parameter(s);
}
@ -511,7 +531,6 @@ namespace AspectedRouting.IO.jsonParser
}
}
Console.WriteLine($"Aspect {name} has type {string.Join(",", expr.Types)}");
return new AspectMetadata(
expr,
name,

View file

@ -1,16 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json;
using AspectedRouting.Language;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
[assembly: InternalsVisibleTo("AspectedRouting.Test")]
namespace AspectedRouting.IO.jsonParser
{
public static partial class JsonParser
{
private static IExpression ParseProfileProperty(JsonElement e, Context c, string property)
internal 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 +
@ -23,7 +25,7 @@ namespace AspectedRouting.IO.jsonParser
throw new Exception($"Could not parse field {property}, no valid typing for expression found");
}
expr = expr.Optimize();
expr = expr.Optimize(out _);
expr = Funcs.Either(Funcs.Id, Funcs.Const, expr);
var specialized = expr.Specialize(new Curry(Typs.Tags, new Var("a")));
@ -52,7 +54,7 @@ namespace AspectedRouting.IO.jsonParser
string.Join(",", pruned.Types) + "\n" + pruned.TypeBreakdown());
}
return pruned.Optimize();
return pruned.Optimize(out _);
}
catch (Exception exc) {
throw new Exception("While parsing the property " + property, exc);

View file

@ -0,0 +1,12 @@
--[[
copies all attributes from the source-table into the target-table,
but only if the key is listed in 'whitelist' (which is a set)
]]
function copy_tags(source, target, whitelist)
for k, v in pairs(source) do
if whitelist[k] then
target[k] = v
end
end
end

View file

@ -0,0 +1,5 @@
function create_set(list)
local set = {}
for _, l in ipairs(list) do set[l] = true end
return set
end

View file

@ -0,0 +1,3 @@
function is_null(a)
return a == nil;
end

View file

@ -79,7 +79,7 @@ function must_match(needed_keys, table, tags, result)
-- Now that we know for sure that every key matches, we add them all to the 'attributes_to_keep'
if (result == nil) then
-- euhm, well, seems like we don't are about the attributes_to_keep; early return!
-- euhm, well, seems like we don't care about the attributes_to_keep; early return!
return true
end
for _, key in ipairs(needed_keys) do

View file

@ -5,8 +5,7 @@ function unit_test(f, fname, index, expected, parameters, tags)
failed_tests = true
return
end
local result = {attributes_to_keep = {}}
local actual = f(parameters, tags, result)
local actual = f(tags)
if(expected == "null" and actual == nil) then
-- OK!
elseif(tonumber(actual) and tonumber(expected) and math.abs(tonumber(actual) - tonumber(expected)) < 0.1) then

View file

@ -206,7 +206,7 @@ namespace AspectedRouting.Language
if (diff.Any()) {
throw new ArgumentException("No default value set for parameter: " + MetaList(diff));
}
var unused = defaultParameters.Except(usedParameters);
if (unused.Any()) {
Console.WriteLine("[WARNING] A default value is set for parameter, but it is unused: " +
@ -254,6 +254,7 @@ namespace AspectedRouting.Language
Console.WriteLine(
$"[{pmd.Name}] WARNING: Some parameters only have a default value: {string.Join(", ", defaultOnly)}");
}
}
public static void SanityCheck(this IExpression e)

View file

@ -33,6 +33,11 @@ namespace AspectedRouting.Language
{
Parameters.Add(name, new Constant(value));
}
public void AddParameter(string name, IExpression value)
{
Parameters.Add(name, value);
}
public void AddFunction(string name, AspectMetadata function)
{

View file

@ -47,6 +47,19 @@ namespace AspectedRouting.Language
};
}
public static Func<IExpression, bool> IsConstant(List<Constant> collect)
{
return e =>
{
if (!(e is Constant c))
{
return false;
}
collect.Add(c);
return true;
};
}
public static Func<IExpression, bool> Assign(List<IExpression> collect)
{
@ -66,6 +79,19 @@ namespace AspectedRouting.Language
return f.Name.Equals(fe.Name);
};
}
public static Func<IExpression, bool> IsFunctionCall(List<string> names)
{
return e => {
if (!(e is FunctionCall fc)) {
return false;
}
names.Add(fc.CalledFunctionName);
return true;
};
}
public static Func<IExpression, bool> UnApplyAny(
Func<IExpression, bool> matchFunc,
@ -93,7 +119,7 @@ namespace AspectedRouting.Language
}
else {
if (!doesMatch) {
// All must match - otherwise we might have registered a wrong collectiin
// All must match - otherwise we might have registered a wrong collection
return false;
}
}

View file

@ -19,6 +19,8 @@ namespace AspectedRouting.Language.Expression
/// </summary>
public readonly Dictionary<Type, (IExpression f, IExpression a)> FunctionApplications;
private IExpression optimizedForm = null;
private Apply(string debugInfo, Dictionary<Type, (IExpression f, IExpression a)> argument)
{
_debugInfo = debugInfo;
@ -27,15 +29,18 @@ namespace AspectedRouting.Language.Expression
public Apply(IExpression f, IExpression argument)
{
if (f == null || argument == null) {
if (f == null || argument == null)
{
throw new NullReferenceException();
}
FunctionApplications = new Dictionary<Type, (IExpression f, IExpression a)>();
var typesCleaned = argument.Types.RenameVars(f.Types).ToList();
foreach (var funcType in f.Types) {
if (!(funcType is Curry c)) {
foreach (var funcType in f.Types)
{
if (!(funcType is Curry c))
{
continue;
}
@ -43,10 +48,12 @@ namespace AspectedRouting.Language.Expression
var expectedResultType = c.ResultType;
foreach (var argType in typesCleaned) {
foreach (var argType in typesCleaned)
{
// we try to unify the argType with the expected type
var substitutions = expectedArgType.UnificationTable(argType);
if (substitutions == null) {
if (substitutions == null)
{
continue;
}
@ -56,25 +63,30 @@ namespace AspectedRouting.Language.Expression
var actualFunction = f.Specialize(new Curry(actualArgType, actualResultType));
var actualArgument = argument.Specialize(actualArgType);
if (actualFunction == null || actualArgument == null) {
if (actualFunction == null || actualArgument == null)
{
continue;
}
if (FunctionApplications.ContainsKey(actualResultType)) {
if (FunctionApplications.ContainsKey(actualResultType))
{
continue;
}
FunctionApplications.Add(actualResultType, (actualFunction, actualArgument));
}
}
if (!FunctionApplications.Any()) {
try {
_debugInfo = $"\n{f.Optimize().TypeBreakdown().Indent()}\n" +
if (!FunctionApplications.Any())
{
try
{
_debugInfo = $"\n{f.Optimize(out var _).TypeBreakdown().Indent()}\n" +
"is given the argument: " +
"(" + argument.Optimize().TypeBreakdown() + ")";
"(" + argument.Optimize(out var _).TypeBreakdown() + ")";
}
catch (Exception) {
catch (Exception)
{
_debugInfo = $"\n (NO OPT) {f.TypeBreakdown().Indent()}\n" +
"is given the argument: " +
"(" + argument.TypeBreakdown() + ")";
@ -90,7 +102,8 @@ namespace AspectedRouting.Language.Expression
public object Evaluate(Context c, params IExpression[] arguments)
{
if (!Types.Any()) {
if (!Types.Any())
{
throw new ArgumentException("Trying to invoke an invalid expression: " + this);
}
@ -101,7 +114,8 @@ namespace AspectedRouting.Language.Expression
var arg = argExpr;
var allArgs = new IExpression[arguments.Length + 1];
allArgs[0] = arg;
for (var i = 0; i < arguments.Length; i++) {
for (var i = 0; i < arguments.Length; i++)
{
allArgs[i + 1] = arguments[i];
}
@ -114,23 +128,42 @@ namespace AspectedRouting.Language.Expression
return Specialize(allowedTypes);
}
public IExpression PruneTypes(Func<Type, bool> allowedTypes)
public IExpression PruneTypes(System.Func<Type, bool> allowedTypes)
{
var passed = this.FunctionApplications.Where(kv => allowedTypes.Invoke(kv.Key));
if (!passed.Any()) {
var passed = FunctionApplications.Where(kv => allowedTypes.Invoke(kv.Key));
if (!passed.Any())
{
return null;
}
return new Apply("pruned", new Dictionary<Type, (IExpression A, IExpression F)>(passed));
}
public IExpression Optimize()
public IExpression Optimize(out bool somethingChanged)
{
if (Types.Count() == 0) {
if (this.optimizedForm != null)
{
somethingChanged = this.optimizedForm != this;
return this.optimizedForm;
}
this.optimizedForm = this.OptimizeInternal(out somethingChanged);
if (!optimizedForm.Types.Any())
{
throw new Exception("Optimizing " + this.ToString() + " failed: cannot be typechecked anymore");
}
return this.optimizedForm;
}
internal IExpression OptimizeInternal(out bool somethingChanged)
{
if (Types.Count() == 0)
{
throw new ArgumentException("This application contain no valid types, so cannot be optimized" + this);
}
somethingChanged = false;
// (eitherfunc dot id) id
// => (const dot _) id => dot id => id
// => (const dot _) id => dot id => id
// or => (constRight _ id) id => id id => id
if (
UnApplyAny(
@ -147,23 +180,37 @@ namespace AspectedRouting.Language.Expression
Any),
IsFunc(Funcs.Id)),
IsFunc(Funcs.Id)
).Invoke(this)) {
).Invoke(this))
{
somethingChanged = true;
return Funcs.Id;
}
if (Types.Count() > 1) {
// Too much types to optimize
if (Types.Count() > 1)
{
// Too much types to optimize: we optimize the subparts instead
var optimized = new Dictionary<Type, (IExpression f, IExpression a)>();
foreach (var (resultType, (f, a)) in FunctionApplications) {
var fOpt = f.Optimize();
var aOpt = a.Optimize();
foreach (var (resultType, (f, a)) in FunctionApplications)
{
var fOpt = f.Optimize(out var scf);
var aOpt = a.Optimize(out var sca);
somethingChanged |= scf || sca;
optimized.Add(resultType, (fOpt, aOpt));
}
return new Apply(_debugInfo, optimized);
if (somethingChanged)
{
return new Apply(_debugInfo, optimized);
}
return this;
}
// At this point, we know there only is a single type;
// We can safely assume all the 'assign' will only match a single entry
{
// id a => a
var arg = new List<IExpression>();
@ -171,11 +218,40 @@ namespace AspectedRouting.Language.Expression
UnApplyAny(
IsFunc(Funcs.Id),
Assign(arg)
).Invoke(this)) {
return arg.First();
).Invoke(this))
{
var argOpt = arg.First().Optimize(out _);
somethingChanged = true;
return argOpt;
}
}
{
var exprs = new List<Constant>();
var arg = new List<IExpression>();
// listDot ([f0, f1, f2, ...]) arg ---> [f0 arg, f1 arg, f2 arg, ...]
if (UnApply(
UnApply(
IsFunc(Funcs.ListDot),
IsConstant(exprs)
),
Assign(arg)
).Invoke(this))
{
var a = arg.First();
var c = exprs.First();
if (c.Types.All(t => t is ListType))
{
// The constant is a list
var o = (List<IExpression>)c.Get();
somethingChanged = true;
return new Constant(
o.Select(e => e.Apply(a).Optimize(out var _)).ToList()
);
}
// fallthrough!
}
}
{
// ifdotted fcondition fthen felse arg => if (fcondition arg) (fthen arg) (felse arg)
@ -194,38 +270,104 @@ namespace AspectedRouting.Language.Expression
Assign(fthen)),
Assign(felse)),
Assign(arg)
).Invoke(this)) {
).Invoke(this))
{
var a = arg.First();
somethingChanged = true;
return
Funcs.If.Apply(
fcondition.First().Apply(a),
fthen.First().Apply(a),
felse.First().Apply(a)
fcondition.First().Apply(a).Optimize(out var _),
fthen.First().Apply(a).Optimize(out var _),
felse.First().Apply(a).Optimize(out var _)
);
}
}
{
// ifdotted fcondition fthen <null> arg => if (fcondition arg) (fthen arg) (felse arg)
var fcondition = new List<IExpression>();
var fthen = new List<IExpression>();
var arg = new List<IExpression>();
if (
this.Types.Any(t => !(t is Curry)) &&
UnApply(
UnApply(
UnApply(
IsFunc(Funcs.IfDotted),
Assign(fcondition)),
Assign(fthen)),
Assign(arg)
).Invoke(this))
{
var a = arg.First();
somethingChanged = true;
return
Funcs.If.Apply(
fcondition.First().Apply(a).Optimize(out var _),
fthen.First().Apply(a).Optimize(out var _)
).Specialize(this.Types).PruneTypes(t => !(t is Curry)).Optimize(out _);
}
}
{
// (default x f) a --> if (isNull (f a)) x (f a)
var defaultArg = new List<IExpression>();
var f = new List<IExpression>();
var a = new List<IExpression>();
if (
UnApply(
UnApply(
UnApply(
IsFunc(Funcs.Default),
Assign(defaultArg)
),
Assign(f)
), Assign(a)
).Invoke(this))
{
somethingChanged = true;
var fa = f.First().Apply(a.First()).Optimize(out var _);
return Funcs.If.Apply(
Funcs.IsNull.Apply(fa)
).Apply(defaultArg).Apply(fa);
}
}
{
// The fallback case
// We couldn't optimize with a pattern, but the subparts might be optimizable
var (f, a) = FunctionApplications.Values.First();
var (newFa, expr) = OptimizeApplicationPair(f, a);
if (expr != null) {
var (newFa, expr) = OptimizeApplicationPair(f, a, out var changed);
if (expr != null)
{
somethingChanged = true;
return expr;
}
(f, a) = newFa.Value;
return new Apply(f, a);
if (changed)
{
somethingChanged = true;
(f, a) = newFa.Value;
return new Apply(f, a).Optimize(out _);
}
}
return this;
}
public void Visit(Func<IExpression, bool> visitor)
{
var continueVisit = visitor(this);
if (!continueVisit) {
if (!continueVisit)
{
return;
}
foreach (var (_, (f, a)) in FunctionApplications) {
foreach (var (_, (f, a)) in FunctionApplications)
{
f.Visit(visitor);
a.Visit(visitor);
}
@ -235,10 +377,13 @@ namespace AspectedRouting.Language.Expression
{
var newArgs = new Dictionary<Type, (IExpression f, IExpression a)>();
foreach (var allowedType in allowedTypes) {
foreach (var (resultType, (funExpr, argExpr)) in FunctionApplications) {
foreach (var allowedType in allowedTypes)
{
foreach (var (resultType, (funExpr, argExpr)) in FunctionApplications)
{
var substitutions = resultType.UnificationTable(allowedType, true);
if (substitutions == null) {
if (substitutions == null)
{
continue;
}
@ -250,7 +395,8 @@ namespace AspectedRouting.Language.Expression
var actualFunction = funExpr.Specialize(substitutions);
var actualArgument = argExpr.Specialize(substitutions);
if (actualFunction == null || actualArgument == null) {
if (actualFunction == null || actualArgument == null)
{
// One of the subexpressions can't be optimized
return null;
}
@ -259,34 +405,47 @@ namespace AspectedRouting.Language.Expression
}
}
if (!newArgs.Any()) {
if (!newArgs.Any())
{
return null;
}
return new Apply(_debugInfo, newArgs);
}
private ((IExpression fOpt, IExpression fArg)?, IExpression result) OptimizeApplicationPair(IExpression f,
IExpression a)
private ((IExpression fOpt, IExpression fArg)?, IExpression result) OptimizeApplicationPair(
IExpression fRaw,
IExpression a,
out bool somethingChanged)
{
f = f.Optimize();
somethingChanged = false;
var f = fRaw.Optimize(out var scf);
somethingChanged |= scf;
if (f.Types.Count() == 0)
{
throw new ArgumentException("Optimizing " + f + " failed, no types returned. The original expression\n "+fRaw.ToString()+"has types"+string.Join("\n ", fRaw.Types));
}
a = a.Optimize();
switch (f) {
a = a.Optimize(out var sca);
somethingChanged |= sca;
switch (f)
{
case Id _:
return (null, a);
case Apply apply:
if (apply.F is Const _) {
if (apply.F is Const _)
{
// (const x) y -> y
// apply == (const x) thus we return 'x' and ignore 'a'
return (null, apply.A);
}
if (apply.F is ConstRight _) {
if (apply.F is ConstRight _)
{
// constRight x y -> y
// apply == (constRight x) so we return a
return (null, a);
@ -300,12 +459,14 @@ namespace AspectedRouting.Language.Expression
Assign(f0)
),
Assign(f1)).Invoke(apply)
) {
)
{
// apply == ((dot f0) f1)
// ((dot f0) f1) a is the actual expression, but arg is already split of
// f0 (f1 arg)
// which used to be (f0 . f1) arg
somethingChanged = true;
return ((f0.First(), new Apply(f1.First(), a)), null);
}
@ -318,21 +479,82 @@ namespace AspectedRouting.Language.Expression
public override string ToString()
{
if (!FunctionApplications.Any()) {
if (!FunctionApplications.Any())
{
return "NOT-TYPECHECKABLE APPLICATION: " + _debugInfo;
}
var (f, arg) = FunctionApplications.Values.First();
if (f is Id _) {
if (f is Id _)
{
return arg.ToString();
}
var extra = "";
if (FunctionApplications.Count() > 1) {
if (FunctionApplications.Count() > 1)
{
extra = " [" + FunctionApplications.Count + " IMPLEMENTATIONS]";
}
return $"({f} {arg.ToString().Indent()})" + extra;
}
public bool Equals(IExpression other)
{
if (!(other is Apply apply))
{
return false;
}
var otherOptions = apply.FunctionApplications;
if (otherOptions.Count != FunctionApplications.Count)
{
return false;
}
foreach (var (type, (otherF, otherA)) in otherOptions)
{
if (!FunctionApplications.TryGetValue(type, out var tuple))
{
return false;
}
if (!otherF.Equals(tuple.f))
{
return false;
}
if (!otherA.Equals(tuple.a))
{
return false;
}
}
return true;
}
public string Repr()
{
if (this.Types.Count() == 1)
{
var f = this.F.Repr().Replace("\n", "\n ");
var a = this.A.Repr().Replace("\n", "\n ");
return $"new Apply( // {string.Join(" ; ", this.Types)}\n {f},\n {a})";
}
var r = "new Apply(";
foreach (var (type, (f, a)) in this.FunctionApplications)
{
r += "\n // " + type+"\n";
r += " | " + f.Repr().Replace("\n", "\n | ")+",\n";
r += " | " + a.Repr().Replace("\n", "\n | ")+"\n";
}
return r;
}
}
}

View file

@ -32,7 +32,6 @@ namespace AspectedRouting.Language.Expression
public object Evaluate(Context c, params IExpression[] arguments)
{
return ExpressionImplementation.Evaluate(c, arguments);
}
@ -43,20 +42,29 @@ namespace AspectedRouting.Language.Expression
Name, Description, Author, Unit, Filepath);
}
public IExpression PruneTypes(System.Func<Type, bool> allowedTypes)
public IExpression PruneTypes(Func<Type, bool> allowedTypes)
{
var e = ExpressionImplementation.PruneTypes(allowedTypes);
if (e == null) {
if (e == null)
{
return null;
}
return new AspectMetadata(e, Name, Description, Author, Unit,
Filepath, ProfileInternal);
}
public IExpression Optimize()
public IExpression Optimize(out bool somethingChanged)
{
return new AspectMetadata(ExpressionImplementation.Optimize(),
Name, Description, Author, Unit, Filepath);
var optE = ExpressionImplementation.Optimize(out var sc);
somethingChanged = sc;
if (sc)
{
return new AspectMetadata(optE,
Name, Description, Author, Unit, Filepath);
}
return this;
}
public void Visit(Func<IExpression, bool> f)
@ -74,5 +82,20 @@ namespace AspectedRouting.Language.Expression
{
return $"# {Name}; {Unit} by {Author}\n\n# by {Author}\n# {Description}\n{ExpressionImplementation}";
}
public bool Equals(IExpression other)
{
if (other is AspectMetadata amd)
{
return amd.ExpressionImplementation.Equals(ExpressionImplementation);
}
return false;
}
public string Repr()
{
return "Aspect_" + Name;
}
}
}

View file

@ -44,8 +44,9 @@ namespace AspectedRouting.Language.Expression
return new FunctionCall(this.Name, passedTypes);
}
public virtual IExpression Optimize()
public virtual IExpression Optimize(out bool somethingChanged)
{
somethingChanged = false;
return this;
}
@ -100,5 +101,20 @@ namespace AspectedRouting.Language.Expression
return dict;
}
public bool Equals(IExpression other)
{
if (other is Function f)
{
return f.Name.Equals(this.Name);
}
return false;
}
public string Repr()
{
return "Funcs."+this.Name;
}
}
}

View file

@ -33,7 +33,11 @@ namespace AspectedRouting.Language.Expression
Types = types;
}
public object Evaluate(Context c, params IExpression[] arguments)
public FunctionCall(string name, Type type): this(name, new []{type}){
}
public object Evaluate(Context c, params IExpression[] arguments)
{
var func = c.GetFunction(_name);
@ -62,8 +66,9 @@ namespace AspectedRouting.Language.Expression
return new FunctionCall(this._name, passedTypes);
}
public IExpression Optimize()
public IExpression Optimize(out bool somethingChanged)
{
somethingChanged = false;
return this;
}
@ -87,6 +92,21 @@ namespace AspectedRouting.Language.Expression
f(this);
}
public bool Equals(IExpression other)
{
if (other is FunctionCall fc)
{
return fc._name.Equals(this._name);
}
return false;
}
public string Repr()
{
return "new FunctionCall(\"" + this._name + "\")";
}
public override string ToString()
{
return $"${_name}";

View file

@ -14,7 +14,7 @@ namespace AspectedRouting.Language.Expression
public string Author { get; }
public string Filename { get; }
public List<string> VehicleTyps { get; }
/*
* Which tags are included in the routerdb but are _not_ used for routeplanning?
* Typically these are tags that are useful for navigation (name of the road, is this a tunnel, ...)
@ -29,6 +29,7 @@ namespace AspectedRouting.Language.Expression
public IExpression Oneway { get; }
public IExpression Speed { get; }
public Dictionary<string, IExpression> Priority { get; }
/**
* Moment of last change of any upstream file
*/
@ -45,9 +46,9 @@ namespace AspectedRouting.Language.Expression
Author = author;
Filename = filename;
VehicleTyps = vehicleTyps;
Access = access.Optimize();
Oneway = oneway.Optimize();
Speed = speed.Optimize();
Access = access.Optimize(out var _);
Oneway = oneway.Optimize(out var _);
Speed = speed.Optimize(out var _);
Priority = priority;
Metadata = metadata;
LastChange = lastChange;
@ -57,21 +58,22 @@ namespace AspectedRouting.Language.Expression
CheckTypes(Access, "access");
CheckTypes(Oneway, "oneway");
CheckTypes(Speed, "speed");
}
private static void CheckTypes(IExpression e, string name)
{
if (e.Types.Count() == 1) {
if (e.Types.Count() == 1)
{
return;
}
throw new Exception("Insufficient specialization: " + name + " has multiple types left, namely " + e.Types.Pretty());
throw new Exception("Insufficient specialization: " + name + " has multiple types left, namely " +
e.Types.Pretty());
}
public List<IExpression> AllExpressions(Context ctx)
{
var l = new List<IExpression> {Access, Oneway, Speed};
var l = new List<IExpression> { Access, Oneway, Speed };
l.AddRange(DefaultParameters.Values);
l.AddRange(Behaviours.Values.SelectMany(b => b.Values));
l.AddRange(Priority.Values);
@ -88,6 +90,7 @@ namespace AspectedRouting.Language.Expression
var called = ctx.GetFunction(fc.CalledFunctionName);
allExpr.Add(called);
}
return true;
});
}
@ -95,6 +98,41 @@ namespace AspectedRouting.Language.Expression
return allExpr;
}
public List<IExpression> AllExpressionsFor(string behaviourName, Context context)
{
var allExpressions = new List<IExpression>();
allExpressions.Add(Access);
allExpressions.Add(Oneway);
allExpressions.Add(Speed);
var behaviourContext = new Context(context);
var behaviourParameters = ParametersFor(behaviourName);
foreach (var (paramName, valueexpression) in Priority)
{
var weightingFactor = behaviourParameters[paramName].Evaluate(behaviourContext);
if (weightingFactor is double d)
{
if (d == 0.0)
{
continue;
}
}
if (weightingFactor is int i)
{
if (i == 0)
{
continue;
}
}
allExpressions.Add(valueexpression);
}
return allExpressions;
}
public Dictionary<string, IExpression> ParametersFor(string behaviour)
{
@ -122,11 +160,11 @@ namespace AspectedRouting.Language.Expression
}
c = c.WithParameters(ParametersFor(behaviour))
.WithAspectName(this.Name);
.WithAspectName(Name);
tags = new Dictionary<string, string>(tags);
var canAccess = Access.Run(c, tags);
tags["access"] = "" + canAccess;
var speed = (double) Speed.Run(c, tags);
var speed = (double)Speed.Run(c, tags);
tags["speed"] = "" + speed;
var oneway = Oneway.Run(c, tags);
tags["oneway"] = "" + oneway;
@ -143,7 +181,7 @@ namespace AspectedRouting.Language.Expression
var weightExplanation = new List<string>();
foreach (var (paramName, expression) in Priority)
{
var aspectInfluence = (double) c.Parameters[paramName].Evaluate(c);
var aspectInfluence = (double)c.Parameters[paramName].Evaluate(c);
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (aspectInfluence == 0)
{
@ -193,8 +231,16 @@ namespace AspectedRouting.Language.Expression
canAccess = "no";
}
return new ProfileResult((string) canAccess, (string) oneway, speed, priority,
string.Join("\n ", weightExplanation));
if (canAccess is string canAccessString && oneway is string onewayString)
{
return new ProfileResult(canAccessString, onewayString, speed, priority,
string.Join("\n ", weightExplanation));
}
else
{
throw new Exception("CanAccess or oneway are not strings but " + canAccess.GetType().ToString() +
" and " + (oneway?.GetType()?.ToString() ?? "<null>"));
}
}

View file

@ -19,6 +19,7 @@ namespace AspectedRouting.Language
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 IsNull = new IsNull();
public static readonly Function Default = new Default();
@ -94,7 +95,7 @@ namespace AspectedRouting.Language
// TODO FIX THIS so that it works
// An argument 'optimizes' it's types from 'string -> bool' to 'string -> string'
var eOpt = eSmallest.Optimize();
var eOpt = eSmallest.Optimize(out _);
if (eOpt == null || eOpt.Types.Count() == 0)
{
throw new Exception("Could not optimize " + eSmallest);
@ -106,6 +107,10 @@ namespace AspectedRouting.Language
public static IExpression SpecializeToSmallestType(this IExpression e)
{
if (e == null)
{
throw new Exception("Cannot specialize null to a smallest type");
}
if (e.Types.Count() == 1)
{
return e;
@ -160,7 +165,8 @@ namespace AspectedRouting.Language
var lastArg = args[args.Count - 1];
var firstArgs = args.GetRange(0, args.Count - 1);
return new Apply(Apply(function, firstArgs), lastArg);
var applied = Apply(function, firstArgs);
return new Apply(applied, lastArg);
}
}
}

View file

@ -14,9 +14,11 @@ namespace AspectedRouting.Language.Functions
public Constant(IEnumerable<Type> types, object o)
{
Types = types.ToList();
if (o is IEnumerable<IExpression> enumerable) {
if (o is IEnumerable<IExpression> enumerable)
{
o = enumerable.ToList();
if (enumerable.Any(x => x == null)) {
if (enumerable.Any(x => x == null))
{
throw new NullReferenceException("Some subexpression is null");
}
}
@ -26,10 +28,12 @@ namespace AspectedRouting.Language.Functions
public Constant(Type t, object o)
{
Types = new List<Type> {t};
if (o is IEnumerable<IExpression> enumerable) {
Types = new List<Type> { t };
if (o is IEnumerable<IExpression> enumerable)
{
o = enumerable.ToList();
if (enumerable.Any(x => x == null)) {
if (enumerable.Any(x => x == null))
{
throw new NullReferenceException("Some subexpression is null");
}
}
@ -42,7 +46,8 @@ namespace AspectedRouting.Language.Functions
var tps = exprs
.SpecializeToCommonTypes(out var specializedVersions)
.Select(t => new ListType(t));
if (specializedVersions.Any(x => x == null)) {
if (specializedVersions.Any(x => x == null))
{
throw new Exception("Specializing to common types failed for " +
string.Join(",", exprs.Select(e => e.ToString())));
}
@ -53,40 +58,50 @@ namespace AspectedRouting.Language.Functions
public Constant(IReadOnlyCollection<IExpression> exprs)
{
try {
try
{
Types = exprs
.SpecializeToCommonTypes(out var specializedVersions)
.Select(t => new ListType(t))
.ToList();
if (specializedVersions.Any(x => x == null)) {
throw new NullReferenceException("Some subexpression is null");
specializedVersions = specializedVersions.ToList();
if (specializedVersions.Any(x => x == null))
{
Console.Error.WriteLine(">> Some subexpressions of the list are null:");
foreach (var expr in exprs)
{
Console.Error.WriteLine(expr.Repr());
Console.Error.WriteLine("\n-------------------------\n");
}
throw new NullReferenceException("Some subexpression of specialized versions are null");
}
_o = specializedVersions.ToList();
}
catch (Exception e) {
catch (Exception e)
{
throw new Exception("While creating a list with members " +
string.Join(", ", exprs.Select(x => x.Optimize())) +
string.Join(", ", exprs.Select(x => x.Optimize(out var _))) +
$" {e.Message}", e);
}
}
public Constant(string s)
{
Types = new List<Type> {Typs.String};
Types = new List<Type> { Typs.String };
_o = s;
}
public Constant(double d)
{
if (d >= 0) {
Types = new[] {Typs.Double, Typs.PDouble};
if (d >= 0)
{
Types = new[] { Typs.Double, Typs.PDouble };
}
else {
Types = new[] {Typs.Double};
else
{
Types = new[] { Typs.Double };
}
_o = d;
@ -94,11 +109,13 @@ namespace AspectedRouting.Language.Functions
public Constant(int i)
{
if (i >= 0) {
Types = new[] {Typs.Double, Typs.Nat, Typs.Nat, Typs.PDouble};
if (i >= 0)
{
Types = new[] { Typs.Double, Typs.Nat, Typs.Nat, Typs.PDouble };
}
else {
Types = new[] {Typs.Double, Typs.Int};
else
{
Types = new[] { Typs.Double, Typs.Int };
}
_o = i;
@ -106,16 +123,17 @@ namespace AspectedRouting.Language.Functions
public Constant(Dictionary<string, string> tags)
{
Types = new[] {Typs.Tags};
Types = new[] { Typs.Tags };
_o = tags;
}
public IEnumerable<Type> Types { get; }
public IExpression PruneTypes(System.Func<Type, bool> allowedTypes)
public IExpression PruneTypes(Func<Type, bool> allowedTypes)
{
var passedTypes = Types.Where(allowedTypes);
if (!passedTypes.Any()) {
if (!passedTypes.Any())
{
return null;
}
@ -124,28 +142,32 @@ namespace AspectedRouting.Language.Functions
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) {
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
(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
}
return (double) _o;
return (double)_o;
case IntType _:
case NatType _:
return (int) _o;
return (int)_o;
}
return _o;
@ -155,31 +177,39 @@ namespace AspectedRouting.Language.Functions
{
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<IExpression> es) {
if (_o is IEnumerable<IExpression> es)
{
var innerTypes = new List<Type>();
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<IExpression>();
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;
}
@ -193,26 +223,38 @@ namespace AspectedRouting.Language.Functions
return new Constant(unified, newO);
}
public IExpression Optimize()
public IExpression Optimize(out bool somethingChanged)
{
if (_o is IEnumerable<IExpression> exprs) {
somethingChanged = false;
if (_o is IEnumerable<IExpression> exprs)
{
// 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) {
foreach (var expression in exprs)
{
var exprOpt = expression.Optimize(out var sc);
somethingChanged |= sc;
if (exprOpt == null || exprOpt.Types.Count() == 0)
{
throw new ArgumentException("Non-optimizable expression:" + expression);
}
optExprs.Add(exprOpt);
}
return new Constant(optExprs);
if (somethingChanged)
{
return new Constant(optExprs);
}
return this;
}
if (_o is IExpression expr) {
// This is a list
return new Constant(expr.Types, expr.Optimize());
if (_o is IExpression expr)
{
// This is a subexpression
somethingChanged = true;
return expr.Optimize(out var _);
}
return this;
@ -220,12 +262,15 @@ namespace AspectedRouting.Language.Functions
public void Visit(Func<IExpression, bool> f)
{
if (_o is IExpression e) {
if (_o is IExpression e)
{
e.Visit(f);
}
if (_o is IEnumerable<IExpression> es) {
foreach (var x in es) {
if (_o is IEnumerable<IExpression> es)
{
foreach (var x in es)
{
x.Visit(f);
}
}
@ -233,6 +278,28 @@ namespace AspectedRouting.Language.Functions
f(this);
}
public bool Equals(IExpression other)
{
if (other is Constant c)
{
return c._o.Equals(_o);
}
return false;
}
public string Repr()
{
if (_o is IEnumerable<IExpression> exprs)
{
return "new Constant(new []{" +
string.Join(",\n ", exprs.Select(e => e.Repr().Replace("\n","\n ")))
+ "})";
}
return "new Constant(" + (_o?.ToString() ?? null)+ ")";
}
public void EvaluateAll(Context c, HashSet<IExpression> addTo)
{
addTo.Add(this);
@ -247,26 +314,35 @@ namespace AspectedRouting.Language.Functions
{
return ObjectExtensions.Pretty(_o);
}
public object Get()
{
return _o;
}
}
public static class ObjectExtensions
{
public static string Pretty(this object o, Context context = null)
{
switch (o) {
switch (o)
{
case null: return "null";
case Dictionary<string, string> d:
var txt = "";
foreach (var (k, v) in d) {
foreach (var (k, v) in d)
{
txt += $"{k}={v};";
}
return $"{{{txt}}}";
case Dictionary<string, List<string>> 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 = "*";
}
@ -282,7 +358,7 @@ namespace AspectedRouting.Language.Functions
case object[] arr:
return arr.ToList().Pretty();
case double[] arr:
return arr.Select(d => (object) d).ToList().Pretty();
return arr.Select(d => (object)d).ToList().Pretty();
case string s:
return "\"" + s.Replace("\"", "\\\"") + "\"";
case IEnumerable<object> ls:

View file

@ -36,6 +36,7 @@ namespace AspectedRouting.Language.Functions
{
if (arguments.Count() <= 2)
{
}
var f0 = arguments[0];

View file

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Typ;
using Type = AspectedRouting.Language.Typ.Type;
namespace AspectedRouting.Language.Functions
{
@ -11,7 +13,7 @@ namespace AspectedRouting.Language.Functions
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)" +
"`(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." +

View file

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Typ;
using Type = AspectedRouting.Language.Typ.Type;
namespace AspectedRouting.Language.Functions
{
public class IsNull : Function
{
public override string Description { get; } = "Returns true if the given argument is null";
public override List<string> ArgNames { get; } = new List<string>{"a"};
public IsNull() : base("is_null", true,
new[]
{
new Curry(new Var("a"), Typs.Bool),
})
{
}
private IsNull(IEnumerable<Type> specializedTypes) : base("is_null", specializedTypes)
{
}
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
{
var unified = Types.SpecializeTo(allowedTypes);
if (unified == null)
{
return null;
}
return new IsNull(unified);
}
public override object Evaluate(Context c, params IExpression[] arguments)
{
var arg = (string) arguments[0].Evaluate(c);
if (arg == null)
{
return "yes";
}
return "no";
}
}
}

View file

@ -101,18 +101,18 @@ namespace AspectedRouting.Language.Functions
return resultFunction.Evaluate(c, otherARgs.ToArray());
}
public override IExpression Optimize()
public override IExpression Optimize(out bool somethingChanged)
{
var optimizedFunctions = new Dictionary<string, IExpression>();
somethingChanged = false;
foreach (var (k, e) in StringToResultFunctions)
{
var opt = e.Optimize();
var typeOptStr = string.Join(";", opt.Types);
var typeEStr = string.Join("; ", e.Types);
var opt = e.Optimize(out var sc);
somethingChanged |= sc;
if (!opt.Types.Any())
{
var typeOptStr = string.Join(";", opt.Types);
var typeEStr = string.Join("; ", e.Types);
throw new NullReferenceException($"Optimized version is null, has different or empty types: " +
$"\n{typeEStr}" +
$"\n{typeOptStr}");
@ -121,7 +121,13 @@ namespace AspectedRouting.Language.Functions
optimizedFunctions[k] = opt;
}
if (!somethingChanged)
{
return this;
}
return new Mapping(optimizedFunctions);
}
public static Mapping Construct(params (string key, IExpression e)[] exprs)

View file

@ -37,6 +37,11 @@ namespace AspectedRouting.Language.Functions
public IExpression Specialize(IEnumerable<Type> allowedTypes)
{
/* var filtered = allowedTypes.Where(at => !(at is Curry));
if (filtered.Count() == 0)
{
return null;
}*/
var unified = Types.SpecializeTo(allowedTypes);
if (unified == null)
{
@ -56,8 +61,9 @@ namespace AspectedRouting.Language.Functions
return new Parameter(passedTypes, this.ParamName);
}
public IExpression Optimize()
public IExpression Optimize(out bool somethingChanged)
{
somethingChanged = false;
return this;
}
@ -71,9 +77,26 @@ namespace AspectedRouting.Language.Functions
f(this);
}
public bool Equals(IExpression other)
{
if (other is Parameter p)
{
return p.ParamName.Equals( this.ParamName);
}
return false;
}
public string Repr()
{
return "new Parameter(\"" + this.ParamName + "\")";
}
public override string ToString()
{
return ParamName;
}
}
}

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
@ -31,20 +32,33 @@ namespace AspectedRouting.Language
/// Optimize a single expression, eventually recursively (e.g. a list can optimize all the contents)
/// </summary>
/// <returns></returns>
IExpression Optimize();
IExpression Optimize(out bool somethingChanged);
/// <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);
bool Equals(IExpression other);
/**
* Builds a string representation that can be used to paste into C# test programs
*/
string Repr();
}
public static class ExpressionExtensions
{
public static void PrintRepr(this IExpression e)
{
Console.WriteLine(e.Repr()+"\n\n-----------\n");
}
public static object Run(this IExpression e, Context c, Dictionary<string, string> tags)
{
try {
@ -111,7 +125,7 @@ namespace AspectedRouting.Language
foreach (var expr in exprs) {
if (specializedTypes == null) {
specializedTypes = expr.Types;
specializedTypes = expr.Types; // This is t
}
else {
var newlySpecialized = Typs.WidestCommonTypes(specializedTypes, expr.Types);

View file

@ -165,10 +165,18 @@ namespace AspectedRouting
continue;
}
var strSplit = str.Split("=");
var k = strSplit[0].Trim();
var v = strSplit[1].Trim();
tags[k] = v;
try
{
var strSplit = str.Split("=");
var k = strSplit[0].Trim();
var v = strSplit[1].Trim();
tags[k] = v;
}
catch (Exception e)
{
Console.Error.WriteLine("Could not parse tag: "+str);
}
}
try {

View file

@ -184,11 +184,11 @@ namespace AspectedRouting.Tests
}
if (actual.Priority >= 100 || actual.Priority <= -100)
{
{/*
Err($"priority is not within range of -100 and +100. This is needed due to a bug in Itinero2.0, see https://github.com/itinero/routing2/issues/30",
actual.Priority + " < 100 && -100 < "+actual.Priority,
actual.Priority);
success = false;
success = false;*/
}