Fix various bugs, improve docs, allow 'null' in JSON, specify behaviour of must_match better
This commit is contained in:
parent
8e3383baec
commit
e2cd6caa70
12 changed files with 172 additions and 102 deletions
26
AspectedRouting.Test/MappingTest.cs
Normal file
26
AspectedRouting.Test/MappingTest.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using AspectedRouting.Language;
|
||||||
|
using AspectedRouting.Language.Functions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Test
|
||||||
|
{
|
||||||
|
public class MappingTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public static void SimpleMapping_SimpleHighway_GivesResult()
|
||||||
|
{
|
||||||
|
var maxspeed = new Mapping(new[] {"residential", "living_street"},
|
||||||
|
new[] {
|
||||||
|
new Constant(30),
|
||||||
|
new Constant(20)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
var resMaxspeed= maxspeed.Evaluate(new Context(), new Constant("residential"));
|
||||||
|
Assert.Equal(30, resMaxspeed);
|
||||||
|
var livingStreetMaxspeed= maxspeed.Evaluate(new Context(), new Constant("living_street"));
|
||||||
|
Assert.Equal(20, livingStreetMaxspeed);
|
||||||
|
var undefinedSpeed = maxspeed.Evaluate(new Context(), new Constant("some_unknown_highway_type"));
|
||||||
|
Assert.Null(undefinedSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
AspectedRouting.Test/MustMatchTest.cs
Normal file
48
AspectedRouting.Test/MustMatchTest.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using AspectedRouting.Language;
|
||||||
|
using AspectedRouting.Language.Functions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AspectedRouting.Test
|
||||||
|
{
|
||||||
|
public class MustMatchTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void MustMatch_SimpleInput()
|
||||||
|
{
|
||||||
|
var mapValue = new Mapping(new[] {"residential", "living_street"},
|
||||||
|
new[] {
|
||||||
|
new Constant("yes"),
|
||||||
|
new Constant("no")
|
||||||
|
});
|
||||||
|
var mapTag = new Mapping(new[] {"highway"}, new[] {mapValue});
|
||||||
|
var mm = Funcs.MustMatch
|
||||||
|
.Apply(
|
||||||
|
new Constant(new[] {new Constant("highway")}),
|
||||||
|
Funcs.StringStringToTags.Apply(mapTag)
|
||||||
|
)
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
var residential = mm.Apply(new Constant(new Dictionary<string, string> {
|
||||||
|
{"highway", "residential"}
|
||||||
|
})).Evaluate(new Context());
|
||||||
|
Assert.Equal("yes", residential);
|
||||||
|
|
||||||
|
var living = mm.Apply(new Constant(new Dictionary<string, string> {
|
||||||
|
{"highway", "living_street"}
|
||||||
|
})).Evaluate(new Context());
|
||||||
|
Assert.Equal("no", living);
|
||||||
|
|
||||||
|
var unknown = mm.Apply(new Constant(new Dictionary<string, string> {
|
||||||
|
{"highway", "unknown_type"}
|
||||||
|
})).Evaluate(new Context());
|
||||||
|
Assert.Equal("yes", unknown);
|
||||||
|
|
||||||
|
var missing = mm.Apply(new Constant(new Dictionary<string, string> {
|
||||||
|
{"proposed:highway", "unknown_type"}
|
||||||
|
})).Evaluate(new Context());
|
||||||
|
Assert.Equal("no", missing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,10 +12,10 @@
|
||||||
</TestAncestor>
|
</TestAncestor>
|
||||||
</SessionState></s:String>
|
</SessionState></s:String>
|
||||||
|
|
||||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=a6a74f48_002D8456_002D43c7_002Dbbee_002Dd3da33a8a4be/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" Name="Integration_TestExamples" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=a6a74f48_002D8456_002D43c7_002Dbbee_002Dd3da33a8a4be/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="Integration_TestExamples" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||||
<Project Location="/home/pietervdvn/git/AspectedRouting/AspectedRouting.Test" Presentation="&lt;AspectedRouting.Test&gt;" />
|
<Project Location="/home/pietervdvn/git/AspectedRouting/AspectedRouting.Test" Presentation="&lt;AspectedRouting.Test&gt;" />
|
||||||
</SessionState></s:String>
|
</SessionState></s:String>
|
||||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=d2e3d58f_002Debff_002D4fb5_002D8d18_002Deafe85f4773d/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="SpecializeToCommonTypes_ValueAndFuncType_ShouldFail" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=d2e3d58f_002Debff_002D4fb5_002D8d18_002Deafe85f4773d/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" Name="SpecializeToCommonTypes_ValueAndFuncType_ShouldFail" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||||
<Project Location="/home/pietervdvn/git/AspectedRouting/AspectedRouting.Test" Presentation="&lt;AspectedRouting.Test&gt;" />
|
<Project Location="/home/pietervdvn/git/AspectedRouting/AspectedRouting.Test" Presentation="&lt;AspectedRouting.Test&gt;" />
|
||||||
</SessionState></s:String>
|
</SessionState></s:String>
|
||||||
</wpf:ResourceDictionary>
|
</wpf:ResourceDictionary>
|
|
@ -26,8 +26,7 @@ namespace AspectedRouting.IO.LuaSkeleton
|
||||||
, UnApply(
|
, UnApply(
|
||||||
IsFunc(Funcs.StringStringToTags),
|
IsFunc(Funcs.StringStringToTags),
|
||||||
Assign(collectedMapping))
|
Assign(collectedMapping))
|
||||||
).Invoke(bare))
|
).Invoke(bare)) {
|
||||||
{
|
|
||||||
AddDep(Funcs.FirstOf.Name);
|
AddDep(Funcs.FirstOf.Name);
|
||||||
return "first_match_of(tags, result, \n" +
|
return "first_match_of(tags, result, \n" +
|
||||||
" " + ToLua(order.First(), key) + "," +
|
" " + ToLua(order.First(), key) + "," +
|
||||||
|
@ -42,8 +41,7 @@ namespace AspectedRouting.IO.LuaSkeleton
|
||||||
, UnApply(
|
, UnApply(
|
||||||
IsFunc(Funcs.StringStringToTags),
|
IsFunc(Funcs.StringStringToTags),
|
||||||
Assign(collectedMapping))
|
Assign(collectedMapping))
|
||||||
).Invoke(bare))
|
).Invoke(bare)) {
|
||||||
{
|
|
||||||
AddDep(Funcs.MustMatch.Name);
|
AddDep(Funcs.MustMatch.Name);
|
||||||
return "must_match(tags, result, \n" +
|
return "must_match(tags, result, \n" +
|
||||||
" " + ToLua(order.First(), key) + "," +
|
" " + ToLua(order.First(), key) + "," +
|
||||||
|
@ -54,37 +52,32 @@ namespace AspectedRouting.IO.LuaSkeleton
|
||||||
if (UnApply(
|
if (UnApply(
|
||||||
IsFunc(Funcs.MemberOf),
|
IsFunc(Funcs.MemberOf),
|
||||||
Any
|
Any
|
||||||
).Invoke(bare))
|
).Invoke(bare)) {
|
||||||
{
|
|
||||||
AddDep("memberOf");
|
AddDep("memberOf");
|
||||||
return "memberOf(funcName, parameters, tags, result)";
|
return "memberOf(funcName, parameters, tags, result)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var collectedList = new List<IExpression>();
|
var collectedList = new List<IExpression>();
|
||||||
var func = new List<IExpression>();
|
var func = new List<IExpression>();
|
||||||
if (
|
if (
|
||||||
UnApply(
|
UnApply(
|
||||||
UnApply(IsFunc(Funcs.Dot), Assign(func)),
|
UnApply(IsFunc(Funcs.Dot), Assign(func)),
|
||||||
UnApply(IsFunc(Funcs.ListDot),
|
UnApply(IsFunc(Funcs.ListDot),
|
||||||
Assign(collectedList))).Invoke(bare))
|
Assign(collectedList))).Invoke(bare)) {
|
||||||
{
|
|
||||||
var exprs = (IEnumerable<IExpression>) ((Constant) collectedList.First()).Evaluate(_context);
|
var exprs = (IEnumerable<IExpression>) ((Constant) collectedList.First()).Evaluate(_context);
|
||||||
var luaExprs = new List<string>();
|
var luaExprs = new List<string>();
|
||||||
var funcName = func.First().ToString().TrimStart('$');
|
var funcName = func.First().ToString().TrimStart('$');
|
||||||
AddDep(funcName);
|
AddDep(funcName);
|
||||||
foreach (var expr in exprs)
|
foreach (var expr in exprs) {
|
||||||
{
|
|
||||||
var c = new List<IExpression>();
|
var c = new List<IExpression>();
|
||||||
if (UnApply(IsFunc(Funcs.Const), Assign(c)).Invoke(expr))
|
if (UnApply(IsFunc(Funcs.Const), Assign(c)).Invoke(expr)) {
|
||||||
{
|
|
||||||
luaExprs.Add(ToLua(c.First(), key));
|
luaExprs.Add(ToLua(c.First(), key));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expr.Types.First() is Curry curry
|
if (expr.Types.First() is Curry curry
|
||||||
&& curry.ArgType.Equals(Typs.Tags))
|
&& curry.ArgType.Equals(Typs.Tags)) {
|
||||||
{
|
|
||||||
var lua = ToLua(expr, key);
|
var lua = ToLua(expr, key);
|
||||||
luaExprs.Add(lua);
|
luaExprs.Add(lua);
|
||||||
}
|
}
|
||||||
|
@ -93,6 +86,7 @@ namespace AspectedRouting.IO.LuaSkeleton
|
||||||
return "\n " + funcName + "({\n " + string.Join(",\n ", luaExprs) +
|
return "\n " + funcName + "({\n " + string.Join(",\n ", luaExprs) +
|
||||||
"\n })";
|
"\n })";
|
||||||
}
|
}
|
||||||
|
|
||||||
collectedMapping.Clear();
|
collectedMapping.Clear();
|
||||||
var dottedFunction = new List<IExpression>();
|
var dottedFunction = new List<IExpression>();
|
||||||
dottedFunction.Clear();
|
dottedFunction.Clear();
|
||||||
|
@ -104,8 +98,7 @@ namespace AspectedRouting.IO.LuaSkeleton
|
||||||
UnApply(
|
UnApply(
|
||||||
IsFunc(Funcs.StringStringToTags),
|
IsFunc(Funcs.StringStringToTags),
|
||||||
Assign(collectedMapping))).Invoke(bare)
|
Assign(collectedMapping))).Invoke(bare)
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
var mapping = (Mapping) collectedMapping.First();
|
var mapping = (Mapping) collectedMapping.First();
|
||||||
var baseFunc = (Function) dottedFunction.First();
|
var baseFunc = (Function) dottedFunction.First();
|
||||||
AddDep(baseFunc.Name);
|
AddDep(baseFunc.Name);
|
||||||
|
@ -121,8 +114,7 @@ namespace AspectedRouting.IO.LuaSkeleton
|
||||||
// The expression might be a function which still expects a string (the value from the tag) as argument
|
// The expression might be a function which still expects a string (the value from the tag) as argument
|
||||||
if (!(bare is Mapping) &&
|
if (!(bare is Mapping) &&
|
||||||
bare.Types.First() is Curry curr &&
|
bare.Types.First() is Curry curr &&
|
||||||
curr.ArgType.Equals(Typs.String))
|
curr.ArgType.Equals(Typs.String)) {
|
||||||
{
|
|
||||||
var applied = new Apply(bare, new Constant(curr.ArgType, ("tags", "\"" + key + "\"")));
|
var applied = new Apply(bare, new Constant(curr.ArgType, ("tags", "\"" + key + "\"")));
|
||||||
return ToLua(applied.Optimize(), key);
|
return ToLua(applied.Optimize(), key);
|
||||||
}
|
}
|
||||||
|
@ -130,37 +122,31 @@ namespace AspectedRouting.IO.LuaSkeleton
|
||||||
|
|
||||||
// The expression might consist of multiple nested functions
|
// The expression might consist of multiple nested functions
|
||||||
var fArgs = bare.DeconstructApply();
|
var fArgs = bare.DeconstructApply();
|
||||||
if (fArgs != null)
|
if (fArgs != null) {
|
||||||
{
|
|
||||||
var (f, args) = fArgs.Value;
|
var (f, args) = fArgs.Value;
|
||||||
var baseFunc = (Function) f;
|
var baseFunc = (Function) f;
|
||||||
|
|
||||||
if (baseFunc.Name.Equals(Funcs.Id.Name))
|
if (baseFunc.Name.Equals(Funcs.Id.Name)) {
|
||||||
{
|
|
||||||
// This is an ugly hack
|
// This is an ugly hack
|
||||||
return ToLua(args.First());
|
return ToLua(args.First());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(baseFunc.Name.Equals(Funcs.Dot.Name))
|
|
||||||
{
|
|
||||||
|
|
||||||
if (args.Count == 1 || forceFirstArgInDot)
|
if (baseFunc.Name.Equals(Funcs.Dot.Name)) {
|
||||||
{
|
if (args.Count == 1 || forceFirstArgInDot) {
|
||||||
return ToLua(args[0]);
|
return ToLua(args[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var argsAsLua = args.Select(arg => ToLua(arg, key)).ToList();
|
var argsAsLua = args.Select(arg => ToLua(arg, key)).ToList();
|
||||||
var fName = argsAsLua[0];
|
var fName = argsAsLua[0];
|
||||||
var actualArgs =
|
var actualArgs =
|
||||||
string.Join(",",argsAsLua.GetRange(1, argsAsLua.Count - 1));
|
string.Join(",", argsAsLua.GetRange(1, argsAsLua.Count - 1));
|
||||||
return $"{fName}({actualArgs})";
|
return $"{fName}({actualArgs})";
|
||||||
}
|
}
|
||||||
|
|
||||||
AddDep(baseFunc.Name);
|
AddDep(baseFunc.Name);
|
||||||
|
|
||||||
var argExpressions = new List<string>();
|
var argExpressions = new List<string>();
|
||||||
foreach (var arg in args)
|
foreach (var arg in args) {
|
||||||
{
|
|
||||||
argExpressions.Add(ToLua(arg, key));
|
argExpressions.Add(ToLua(arg, key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,14 +155,12 @@ namespace AspectedRouting.IO.LuaSkeleton
|
||||||
|
|
||||||
|
|
||||||
var collected = new List<IExpression>();
|
var collected = new List<IExpression>();
|
||||||
switch (bare)
|
switch (bare) {
|
||||||
{
|
|
||||||
case LuaLiteral lua:
|
case LuaLiteral lua:
|
||||||
return lua.Lua;
|
return lua.Lua;
|
||||||
case FunctionCall fc:
|
case FunctionCall fc:
|
||||||
var called = _context.DefinedFunctions[fc.CalledFunctionName];
|
var called = _context.DefinedFunctions[fc.CalledFunctionName];
|
||||||
if (called.ProfileInternal)
|
if (called.ProfileInternal) {
|
||||||
{
|
|
||||||
return called.Name;
|
return called.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,15 +173,12 @@ namespace AspectedRouting.IO.LuaSkeleton
|
||||||
return MappingToLua(m).Indent();
|
return MappingToLua(m).Indent();
|
||||||
case Function f:
|
case Function f:
|
||||||
var fName = f.Name.TrimStart('$');
|
var fName = f.Name.TrimStart('$');
|
||||||
if (Funcs.Builtins.ContainsKey(fName))
|
if (Funcs.Builtins.ContainsKey(fName)) {
|
||||||
{
|
|
||||||
AddDep(f.Name);
|
AddDep(f.Name);
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
var definedFunc = _context.DefinedFunctions[fName];
|
var definedFunc = _context.DefinedFunctions[fName];
|
||||||
if (definedFunc.ProfileInternal)
|
if (definedFunc.ProfileInternal) {
|
||||||
{
|
|
||||||
return f.Name;
|
return f.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,13 +200,11 @@ namespace AspectedRouting.IO.LuaSkeleton
|
||||||
public string MappingToLua(Mapping m)
|
public string MappingToLua(Mapping m)
|
||||||
{
|
{
|
||||||
var isConstant = true;
|
var isConstant = true;
|
||||||
var contents = m.StringToResultFunctions.Select(kv =>
|
var contents = m.StringToResultFunctions.Select(kv => {
|
||||||
{
|
|
||||||
var (key, expr) = kv;
|
var (key, expr) = kv;
|
||||||
var left = "[\"" + key + "\"]";
|
var left = "[\"" + key + "\"]";
|
||||||
|
|
||||||
if (Regex.IsMatch(key, "^[a-zA-Z][_a-zA-Z-9]*$"))
|
if (Regex.IsMatch(key, "^[a-zA-Z][_a-zA-Z-9]*$")) {
|
||||||
{
|
|
||||||
left = key;
|
left = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,7 +212,8 @@ namespace AspectedRouting.IO.LuaSkeleton
|
||||||
if (luaExpr.Contains("tags")) {
|
if (luaExpr.Contains("tags")) {
|
||||||
isConstant = false;
|
isConstant = false;
|
||||||
}
|
}
|
||||||
return left + " = " + luaExpr ;
|
|
||||||
|
return left + " = " + luaExpr;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
var mapping =
|
var mapping =
|
||||||
|
@ -245,19 +225,17 @@ namespace AspectedRouting.IO.LuaSkeleton
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapping;
|
return mapping;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Neatly creates a value expression in lua, based on a constant
|
/// Neatly creates a value expression in lua, based on a constant
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="c"></param>
|
/// <param name="c"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private string ConstantToLua(Constant c)
|
private string ConstantToLua(Constant c)
|
||||||
{
|
{
|
||||||
var o = c.Evaluate(_context);
|
var o = c.Evaluate(_context);
|
||||||
switch (o)
|
switch (o) {
|
||||||
{
|
|
||||||
case LuaLiteral lua:
|
case LuaLiteral lua:
|
||||||
return lua.Lua;
|
return lua.Lua;
|
||||||
case IExpression e:
|
case IExpression e:
|
||||||
|
@ -268,15 +246,15 @@ namespace AspectedRouting.IO.LuaSkeleton
|
||||||
return "" + d;
|
return "" + d;
|
||||||
case string s:
|
case string s:
|
||||||
return '"' + s.Replace("\"", "\\\"") + '"';
|
return '"' + s.Replace("\"", "\\\"") + '"';
|
||||||
|
case null:
|
||||||
|
return "nil";
|
||||||
case ValueTuple<string, string> unpack:
|
case ValueTuple<string, string> unpack:
|
||||||
return unpack.Item1 + "[" + unpack.Item2 + "]";
|
return unpack.Item1 + "[" + unpack.Item2 + "]";
|
||||||
case IEnumerable<object> ls:
|
case IEnumerable<object> ls:
|
||||||
var t = ((ListType) c.Types.First()).InnerType;
|
var t = ((ListType) c.Types.First()).InnerType;
|
||||||
return "{" + string.Join(", ", ls.Select(obj =>
|
return "{" + string.Join(", ", ls.Select(obj => {
|
||||||
{
|
|
||||||
var objInConstant = new Constant(t, obj);
|
var objInConstant = new Constant(t, obj);
|
||||||
if (obj is Constant asConstant)
|
if (obj is Constant asConstant) {
|
||||||
{
|
|
||||||
objInConstant = asConstant;
|
objInConstant = asConstant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace AspectedRouting.IO.itinero1
|
||||||
{
|
{
|
||||||
public static class LuaStringExtensions
|
public static class LuaStringExtensions
|
||||||
{
|
{
|
||||||
|
|
||||||
public static string ToLuaTable(this Dictionary<string, string> tags)
|
public static string ToLuaTable(this Dictionary<string, string> tags)
|
||||||
{
|
{
|
||||||
var contents = tags.Select(kv =>
|
var contents = tags.Select(kv =>
|
||||||
|
|
|
@ -301,6 +301,9 @@ namespace AspectedRouting.IO.jsonParser
|
||||||
return new Constant(s);
|
return new Constant(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.ValueKind == JsonValueKind.Null) {
|
||||||
|
return new Constant(new Var("a"), null);
|
||||||
|
}
|
||||||
|
|
||||||
throw new Exception("Could not parse " + e);
|
throw new Exception("Could not parse " + e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
function if_then_else(condition, thn, els)
|
function if_then_else(condition, thn, els)
|
||||||
if (condition ~= nil and (condition == "yes" or condition == true or condition == "true") then
|
if (condition ~= nil and (condition == "yes" or condition == true or condition == "true")) then
|
||||||
return thn
|
return thn
|
||||||
else
|
else
|
||||||
return els -- if no third parameter is given, 'els' will be nil
|
return els -- if no third parameter is given, 'els' will be nil
|
||||||
|
|
|
@ -15,18 +15,26 @@ When applied on the tags {"a" : "X"}, this yields the table {"a":"yes", "b":"yes
|
||||||
MustMatch checks that every key in this last table yields yes - even if it is not in the original tags!
|
MustMatch checks that every key in this last table yields yes - even if it is not in the original tags!
|
||||||
|
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
- The tags of the feature
|
||||||
|
- The result table, where 'attributes_to_keep' might be set
|
||||||
|
- needed_keys which indicate which keys must be present in 'tags'
|
||||||
|
- table which is the table to match
|
||||||
|
|
||||||
]]
|
]]
|
||||||
function must_match(tags, result, needed_keys, table)
|
function must_match(tags, result, needed_keys, table)
|
||||||
for _, key in ipairs(needed_keys) do
|
for _, key in ipairs(needed_keys) do
|
||||||
local v = table[key] -- use the table here, as a tag that must _not_ match might be 'nil' in the tags
|
local v = tags[key]
|
||||||
if (v == nil) then
|
if (v == nil) then
|
||||||
|
-- a key is missing...
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
local mapping = table[key]
|
local mapping = table[key]
|
||||||
if (type(mapping) == "table") then
|
if (type(mapping) == "table") then
|
||||||
|
-- we have to map the value with a function:
|
||||||
local resultValue = mapping[v]
|
local resultValue = mapping[v]
|
||||||
if (resultValue == nil or
|
if (resultValue ~= nil or -- actually, having nil for a mapping is fine for this function!.
|
||||||
resultValue == false or
|
resultValue == false or
|
||||||
resultValue == "no" or
|
resultValue == "no" or
|
||||||
resultValue == "false") then
|
resultValue == "false") then
|
||||||
|
|
|
@ -7,6 +7,13 @@ using Type = AspectedRouting.Language.Typ.Type;
|
||||||
|
|
||||||
namespace AspectedRouting.Language.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The constructor takes a dictionary "key --> expression".
|
||||||
|
/// If a string is given as argument, the respective argument is returned. If the key is not found, 'null' is returned
|
||||||
|
///
|
||||||
|
/// If a table/dictionary/collection of tags is given, the key of the arguments is used and the expression is used as function on every respective value of the argument.
|
||||||
|
/// If the key is not found, null is returned for this expression instead.
|
||||||
|
/// </summary>
|
||||||
public class Mapping : Function
|
public class Mapping : Function
|
||||||
{
|
{
|
||||||
public readonly Dictionary<string, IExpression> StringToResultFunctions;
|
public readonly Dictionary<string, IExpression> StringToResultFunctions;
|
||||||
|
|
|
@ -2,47 +2,44 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AspectedRouting.Language.Expression;
|
using AspectedRouting.Language.Expression;
|
||||||
using AspectedRouting.Language.Typ;
|
using AspectedRouting.Language.Typ;
|
||||||
using Type = AspectedRouting.Language.Typ.Type;
|
|
||||||
|
|
||||||
namespace AspectedRouting.Language.Functions
|
namespace AspectedRouting.Language.Functions
|
||||||
{
|
{
|
||||||
public class MustMatch : Function
|
public class MustMatch : Function
|
||||||
{
|
{
|
||||||
public override string Description { get; } =
|
|
||||||
"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.";
|
|
||||||
|
|
||||||
public override List<string> ArgNames { get; } = new List<string>
|
|
||||||
{
|
|
||||||
"neededKeys (filled in by parser)",
|
|
||||||
"f"
|
|
||||||
};
|
|
||||||
|
|
||||||
public MustMatch() : base("mustMatch", true,
|
public MustMatch() : base("mustMatch", true,
|
||||||
new[]
|
new[] {
|
||||||
{
|
|
||||||
// [String] -> (Tags -> [string]) -> Tags -> bool
|
// [String] -> (Tags -> [string]) -> Tags -> bool
|
||||||
Curry.ConstructFrom(Typs.Bool, // Result type on top!
|
Curry.ConstructFrom(Typs.Bool, // Result type on top!
|
||||||
new ListType(Typs.String), // List of keys to check for
|
new ListType(Typs.String), // List of keys to check for
|
||||||
new Curry(Typs.Tags, new ListType(Typs.String)), // 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
|
Typs.Tags // The tags to apply this on
|
||||||
)
|
)
|
||||||
})
|
}) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private MustMatch(IEnumerable<Type> types) : base("mustMatch", types)
|
private MustMatch(IEnumerable<Type> types) : base("mustMatch", types) { }
|
||||||
{
|
|
||||||
}
|
public override string Description { get; } = Utils.Lines(
|
||||||
|
"Checks that every specified key is present and gives a non-false value.\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'. Note that 'null' is considered as true here too!",
|
||||||
|
"Note that this is a privileged builtin function, as the parser will automatically inject the keys used in the called function.\n",
|
||||||
|
"",
|
||||||
|
"Usage example",
|
||||||
|
"-------------",
|
||||||
|
"",
|
||||||
|
"`\'mustMatchKeys\': { \"highway\": { \"proposed\": \"no\", \"undefined\":null }}`",
|
||||||
|
"which will return 'yes' for {highway=residential}, {highway=living_street}, ..., but return 'no' for {highway=proposed}, but also for {some_other_key=xxx}",
|
||||||
|
"Also note that {highway=undefined} will return (somewhat surprisingly) 'yes' too - as null-values are considered as true here too!");
|
||||||
|
|
||||||
|
public override List<string> ArgNames { get; } = new List<string> {
|
||||||
|
"neededKeys (do not specify in source code -added automatically by the parser)",
|
||||||
|
"f"
|
||||||
|
};
|
||||||
|
|
||||||
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
public override IExpression Specialize(IEnumerable<Type> allowedTypes)
|
||||||
{
|
{
|
||||||
var unified = Types.SpecializeTo(allowedTypes);
|
var unified = Types.SpecializeTo(allowedTypes);
|
||||||
if (unified == null)
|
if (unified == null) {
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,36 +48,38 @@ namespace AspectedRouting.Language.Functions
|
||||||
|
|
||||||
public override object Evaluate(Context c, params IExpression[] arguments)
|
public override object Evaluate(Context c, params IExpression[] arguments)
|
||||||
{
|
{
|
||||||
var neededKeys = (IEnumerable<object>) arguments[0].Evaluate(c);
|
var neededKeys = (IEnumerable<object>) arguments[0].Evaluate(c);
|
||||||
var function = arguments[1];
|
var function = arguments[1];
|
||||||
|
|
||||||
var tags = (Dictionary<string, string>) arguments[2].Evaluate(c);
|
var tags = (Dictionary<string, string>) arguments[2].Evaluate(c);
|
||||||
|
|
||||||
foreach (var oo in neededKeys)
|
foreach (var oo in neededKeys) {
|
||||||
{
|
|
||||||
var o = oo;
|
var o = oo;
|
||||||
while (o is IExpression e)
|
while (o is IExpression e) {
|
||||||
{
|
|
||||||
o = e.Evaluate(c);
|
o = e.Evaluate(c);
|
||||||
}
|
}
|
||||||
if (!(o is string tagKey))
|
|
||||||
{
|
if (!(o is string tagKey)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tags.ContainsKey(tagKey)) return "no";
|
if (!tags.ContainsKey(tagKey)) {
|
||||||
|
// A required key is missing: return 'no'
|
||||||
|
return "no";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = (IEnumerable<object>) function.Evaluate(c, new Constant(tags));
|
var result = (IEnumerable<object>) function.Evaluate(c, new Constant(tags));
|
||||||
|
|
||||||
if (!result.Any(o =>
|
if (result.Any(o =>
|
||||||
o == null ||
|
o == null ||
|
||||||
(o is string s && (s.Equals("no") || s.Equals("false")))))
|
o is string s && (s.Equals("no") || s.Equals("false")))) {
|
||||||
{
|
// The mapped function is executed. If the mapped function gives 'no', null or 'false' for any value, "no" is returned
|
||||||
return "yes";
|
return "no";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "no";
|
return "yes";
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -43,7 +43,7 @@ namespace AspectedRouting.Tests
|
||||||
var tags = new Dictionary<string, string>();
|
var tags = new Dictionary<string, string>();
|
||||||
for (var i = 0; i < keys.Count; i++) {
|
for (var i = 0; i < keys.Count; i++) {
|
||||||
if (i < vals.Count && !string.IsNullOrEmpty(vals[i])) {
|
if (i < vals.Count && !string.IsNullOrEmpty(vals[i])) {
|
||||||
tags[keys[i]] = vals[i];
|
tags[keys[i]] = vals[i].Trim(new []{'"'}).Replace("\"","\\\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ namespace AspectedRouting.Tests
|
||||||
{
|
{
|
||||||
if (i < vals.Count && !string.IsNullOrEmpty(vals[i]))
|
if (i < vals.Count && !string.IsNullOrEmpty(vals[i]))
|
||||||
{
|
{
|
||||||
tags[keys[i]] = vals[i];
|
tags[keys[i]] = vals[i].Trim(new []{'\"'}).Replace("\"","\\\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue