diff --git a/AspectedRouting/AspectedRouting.csproj b/AspectedRouting/AspectedRouting.csproj
index 700b7ec..5f918b6 100644
--- a/AspectedRouting/AspectedRouting.csproj
+++ b/AspectedRouting/AspectedRouting.csproj
@@ -12,6 +12,9 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/AspectedRouting/IO/LuaSkeleton/LuaParameterPrinter.Parameters.cs b/AspectedRouting/IO/LuaSkeleton/LuaParameterPrinter.Parameters.cs
new file mode 100644
index 0000000..cc2db70
--- /dev/null
+++ b/AspectedRouting/IO/LuaSkeleton/LuaParameterPrinter.Parameters.cs
@@ -0,0 +1,65 @@
+using System.Collections.Generic;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+
+namespace AspectedRouting.IO.itinero1
+{
+ public class LuaParameterPrinter
+ {
+ private ProfileMetaData _profile;
+ private LuaSkeleton.LuaSkeleton _skeleton;
+
+ public LuaParameterPrinter(ProfileMetaData profile, LuaSkeleton.LuaSkeleton skeleton)
+ {
+ _profile = profile;
+ _skeleton = skeleton;
+ }
+
+
+ public string GenerateDefaultParameters()
+ {
+ var impl = new List()
+ {
+ "function default_parameters()",
+ " local parameters = {}",
+ DeclareParametersFor(_profile.DefaultParameters),
+ " return parameters",
+ "end"
+ };
+
+ return string.Join("\n",impl);
+ }
+
+ ///
+ /// Generates a piece of code of the following format:
+ ///
+ /// parameters["x"] = a;
+ /// parameters["y"] = b:
+ /// ...
+ ///
+ /// Where x=a and y=b are defined in the profile
+ ///
+ /// Dependencies are added.
+ ///
+ /// Note that the caller should still add `local paramaters = default_parameters()`
+ ///
+ ///
+ ///
+ ///
+ public string DeclareParametersFor(Dictionary subParams)
+ {
+ var impl = "";
+ foreach (var (paramName, value) in subParams)
+ {
+ if (paramName.Equals("description"))
+ {
+ continue;
+ }
+
+ impl += $" parameters.{paramName.TrimStart('#').AsLuaIdentifier()} = {_skeleton.ToLua(value)}\n";
+ }
+
+ return impl;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero1/Luaprinter.Expressions.cs b/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Expressions.cs
similarity index 95%
rename from AspectedRouting/IO/itinero1/Luaprinter.Expressions.cs
rename to AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Expressions.cs
index 05befa5..aab59d6 100644
--- a/AspectedRouting/IO/itinero1/Luaprinter.Expressions.cs
+++ b/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Expressions.cs
@@ -2,18 +2,20 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
+using AspectedRouting.IO.itinero1;
using AspectedRouting.Language;
using AspectedRouting.Language.Expression;
using AspectedRouting.Language.Functions;
using AspectedRouting.Language.Typ;
using static AspectedRouting.Language.Deconstruct;
-namespace AspectedRouting.IO.itinero1
+namespace AspectedRouting.IO.LuaSkeleton
{
- public partial class LuaPrinter
+ public partial class LuaSkeleton
{
- private string ToLua(IExpression bare, string key = "nil")
+ internal string ToLua(IExpression bare, string key = "nil")
{
+
var collectedMapping = new List();
var order = new List();
@@ -63,6 +65,9 @@ namespace AspectedRouting.IO.itinero1
var func = new List();
+
+
+
if (
UnApply(
UnApply(IsFunc(Funcs.Dot), Assign(func)),
@@ -155,7 +160,7 @@ namespace AspectedRouting.IO.itinero1
}
AddFunction(called);
- return $"{fc.CalledFunctionName.FunctionName()}(parameters, tags, result)";
+ return $"{fc.CalledFunctionName.AsLuaIdentifier()}(parameters, tags, result)";
case Constant c:
return ConstantToLua(c);
case Mapping m:
@@ -184,7 +189,7 @@ namespace AspectedRouting.IO.itinero1
return ToLua(collected.First(), key);
case Parameter p:
- return $"parameters[\"{p.ParamName.FunctionName()}\"]";
+ return $"parameters[\"{p.ParamName.AsLuaIdentifier()}\"]";
default:
throw new Exception("Could not convert " + bare + " to a lua expression");
}
diff --git a/AspectedRouting/IO/itinero1/Luaprinter.Aspect.cs b/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Function.cs
similarity index 76%
rename from AspectedRouting/IO/itinero1/Luaprinter.Aspect.cs
rename to AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Function.cs
index f9bd0e1..3e616db 100644
--- a/AspectedRouting/IO/itinero1/Luaprinter.Aspect.cs
+++ b/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.Function.cs
@@ -1,13 +1,13 @@
using System.Collections.Generic;
using System.Linq;
+using AspectedRouting.IO.itinero1;
using AspectedRouting.Language;
using AspectedRouting.Language.Expression;
-namespace AspectedRouting.IO.itinero1
+namespace AspectedRouting.IO.LuaSkeleton
{
- public partial class LuaPrinter
+ public partial class LuaSkeleton
{
- private readonly HashSet _alreadyAddedFunctions = new HashSet();
public void AddFunction(AspectMetadata meta)
{
@@ -31,7 +31,7 @@ namespace AspectedRouting.IO.itinero1
{
if (e is Function f && f.Name.Equals(Funcs.MemberOf.Name))
{
- funcNameDeclaration = $"\n local funcName = \"{meta.Name.FunctionName()}\"";
+ funcNameDeclaration = $"\n local funcName = \"{meta.Name.AsLuaIdentifier()}\"";
}
return true;
@@ -49,16 +49,12 @@ namespace AspectedRouting.IO.itinero1
"Number of combintations: " + numberOfCombinations,
"Returns values: ",
"]]",
- "function " + meta.Name.FunctionName() + "(parameters, tags, result)" + funcNameDeclaration,
+ "function " + meta.Name.AsLuaIdentifier() + "(parameters, tags, result)" + funcNameDeclaration,
" return " + ToLua(meta.ExpressionImplementation),
"end"
);
- _code.Add(impl);
- foreach (var k in possibleTags.Keys)
- {
- _neededKeys.Add(k); // To generate a whitelist of OSM-keys that should be kept
- }
+ _functionImplementations.Add(impl);
}
}
}
\ No newline at end of file
diff --git a/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.cs b/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.cs
new file mode 100644
index 0000000..2a5707e
--- /dev/null
+++ b/AspectedRouting/IO/LuaSkeleton/LuaSkeleton.cs
@@ -0,0 +1,78 @@
+using System.Collections.Generic;
+using System.IO;
+using AspectedRouting.Language;
+
+namespace AspectedRouting.IO.LuaSkeleton
+{
+
+ ///
+ /// The 'LuaSkeleton' is a class which is used in Lua generation of profiles.
+ ///
+ /// The lua skeleton basically keeps track of dependencies, and added functions.
+ /// Once done, all these can be retrieved as code.
+ ///
+ /// E.g. if an expression is turned into lua with 'ToExpression', then the dependencies will be automatically added.
+ ///
+ ///
+ public partial class LuaSkeleton
+ {
+ private readonly Context _context;
+
+ private readonly HashSet _dependencies = new HashSet();
+ private readonly List _functionImplementations = new List();
+ private readonly HashSet _alreadyAddedFunctions = new HashSet();
+
+ public LuaSkeleton(Context context)
+ {
+ _context = context;
+ }
+
+ internal void AddDep(string name)
+ {
+ _dependencies.Add(name);
+ }
+
+ public List GenerateFunctions()
+ {
+ return _functionImplementations;
+ }
+
+ public void AddDependenciesFor(IExpression e)
+ {
+ var (_, functionNames) = e.InList().DirectlyAndInderectlyCalled(_context);
+ foreach (var functionName in functionNames)
+ {
+
+ if (_context.DefinedFunctions.TryGetValue(functionName, out var aspectemeta))
+ {
+ AddFunction(aspectemeta);
+ }
+ else
+ {
+ AddDep(functionName);
+ }
+ }
+ }
+
+ public List GenerateDependencies()
+ {
+ var imps = new List();
+
+ foreach (var name in _dependencies)
+ {
+ var path = $"IO/lua/{name}.lua";
+ if (File.Exists(path))
+ {
+ imps.Add(File.ReadAllText(path));
+ }
+ else
+ {
+ throw new FileNotFoundException(path);
+ }
+ }
+
+ return imps;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero1/LuaStringExtensions.cs b/AspectedRouting/IO/LuaSkeleton/LuaStringExtensions.cs
similarity index 93%
rename from AspectedRouting/IO/itinero1/LuaStringExtensions.cs
rename to AspectedRouting/IO/LuaSkeleton/LuaStringExtensions.cs
index dc4e2b3..5f28130 100644
--- a/AspectedRouting/IO/itinero1/LuaStringExtensions.cs
+++ b/AspectedRouting/IO/LuaSkeleton/LuaStringExtensions.cs
@@ -23,7 +23,7 @@ namespace AspectedRouting.IO.itinero1
return "{" + string.Join(", ", contents) + "}";
}
- public static string FunctionName(this string s)
+ public static string AsLuaIdentifier(this string s)
{
return s.Replace("$", "")
.Replace("#", "")
diff --git a/AspectedRouting/IO/LuaSkeleton/LuaTestPrinter.cs b/AspectedRouting/IO/LuaSkeleton/LuaTestPrinter.cs
new file mode 100644
index 0000000..76f1f1d
--- /dev/null
+++ b/AspectedRouting/IO/LuaSkeleton/LuaTestPrinter.cs
@@ -0,0 +1,151 @@
+using System.Collections.Generic;
+using System.Linq;
+using AspectedRouting.Tests;
+
+namespace AspectedRouting.IO.itinero1
+{
+ public class LuaTestPrinter
+ {
+ private LuaSkeleton.LuaSkeleton _skeleton;
+
+ public LuaTestPrinter(LuaSkeleton.LuaSkeleton skeleton, List unitTestRunners)
+ {
+ _skeleton = skeleton;
+ unitTestRunners.ForEach(_skeleton.AddDep);
+ }
+
+
+ public string GenerateFullTestSuite(List profileTests, List aspectTests)
+ {
+
+ _skeleton.AddDep("inv");
+ _skeleton.AddDep("double_compare");
+
+
+ var aspectTestSuite =
+ string.Join("\n\n", aspectTests
+ .Where(x => x != null)
+ .Select(
+ GenerateAspectTestSuite
+ ));
+
+ var profileTestSuite =
+ string.Join("\n\n", profileTests
+ .Where(x => x != null)
+ .Select(
+ GenerateProfileTestSuite
+ ));
+
+ return string.Join("\n\n\n", new List
+ {
+ "function test_all()",
+ " " + aspectTestSuite.Indent(),
+ " -- Behaviour tests --",
+ " " + profileTestSuite.Indent(),
+ "end"
+ });
+ }
+
+
+ private string GenerateProfileTestSuite(BehaviourTestSuite testSuite)
+ {
+ return string.Join("\n",
+ testSuite.Tests.Select((test, i) => GenerateProfileUnitTestCall(testSuite, i, test.Item1, test.tags))
+ .ToList());
+ }
+
+ private string GenerateProfileUnitTestCall(BehaviourTestSuite testSuite, int index, ProfileResult expected,
+ Dictionary tags)
+ {
+ _skeleton.AddDep("debug_table");
+ var parameters = new Dictionary();
+
+
+ var keysToCheck = new List();
+ foreach (var (key, value) in tags)
+ {
+ if (key.StartsWith("#"))
+ {
+ parameters[key.TrimStart('#')] = value;
+ }
+
+ if (key.StartsWith("_relation:"))
+ {
+ keysToCheck.Add(key);
+ }
+ }
+
+
+ foreach (var key in keysToCheck)
+ {
+ var newKey = key.Replace(".", "_");
+ tags[newKey] = tags[key];
+ tags.Remove(key);
+ }
+
+ foreach (var (paramName, _) in parameters)
+ {
+ tags.Remove("#" + paramName);
+ }
+
+ // Generates something like:
+ // function unit_test_profile(profile_function, profile_name, index, expected, tags)
+ return
+ $"unit_test_profile(behaviour_{testSuite.Profile.Name.AsLuaIdentifier()}_{testSuite.BehaviourName.AsLuaIdentifier()}, " +
+ $"\"{testSuite.BehaviourName}\", " +
+ $"{index}, " +
+ $"{{access = \"{D(expected.Access)}\", speed = {expected.Speed}, oneway = \"{D(expected.Oneway)}\", priority = {expected.Priority} }}, " +
+ tags.ToLuaTable() +
+ ")";
+ }
+
+
+ private string GenerateAspectTestSuite(AspectTestSuite testSuite)
+ {
+ var fName = testSuite.FunctionToApply.Name;
+ var tests =
+ testSuite.Tests
+ .Select((test, i) => GenerateAspectUnitTestCall(fName, i, test.expected, test.tags))
+ .ToList();
+ return string.Join("\n", tests);
+ }
+
+ ///
+ /// Generate a unit test call
+ ///
+ private string GenerateAspectUnitTestCall(string functionToApplyName, int index, string expected,
+ Dictionary tags)
+ {
+ var parameters = new Dictionary();
+
+ foreach (var (key, value) in tags)
+ {
+ if (key.StartsWith("#"))
+ {
+ parameters[key.TrimStart('#')] = value;
+ }
+ }
+
+ foreach (var (paramName, _) in parameters)
+ {
+ tags.Remove("#" + paramName);
+ }
+
+ _skeleton.AddDep("unitTest");
+ _skeleton.AddDep("debug_table");
+ return
+ $"unit_test({functionToApplyName.AsLuaIdentifier()}, \"{functionToApplyName}\", {index}, \"{expected}\", {parameters.ToLuaTable()}, {tags.ToLuaTable()})";
+ }
+
+
+ private string D(string s)
+ {
+ if (string.IsNullOrEmpty(s))
+ {
+ return "0";
+ }
+
+ return s;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero1/LuaPrinter.cs b/AspectedRouting/IO/itinero1/LuaPrinter.cs
deleted file mode 100644
index 13f3dca..0000000
--- a/AspectedRouting/IO/itinero1/LuaPrinter.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-
-namespace AspectedRouting.IO.itinero1
-{
- public partial class LuaPrinter
- {
- private readonly HashSet _dependencies = new HashSet();
- private readonly HashSet _neededKeys = new HashSet();
-
- private readonly List _code = new List();
- private readonly List _tests = new List();
-
- ///
- /// A dictionary containing the implementation of basic functions
- ///
- ///
- private static IEnumerable LoadFunctions(IEnumerable names)
- {
- var imps = new List();
-
- foreach (var name in names)
- {
- var path = $"IO/lua/{name}.lua";
- if (File.Exists(path))
- {
- imps.Add(File.ReadAllText(path));
- }
- else
- {
- throw new FileNotFoundException(path);
- }
- }
-
- return imps;
- }
-
- private void AddDep(string name)
- {
- _dependencies.Add(name);
- }
-
-
- public string ToLua()
- {
- var deps = _dependencies.ToList();
- deps.Add("unitTestProfile");
- deps.Add("inv");
- deps.Add("double_compare");
- deps.Add("spoken_instructions");
-
- var code = new List();
-
- code.Add($"-- Itinero 1.0-profile, generated on {DateTime.Now:s}");
- code.Add("\n\n----------------------------- UTILS ---------------------------");
- code.AddRange(LoadFunctions(deps).ToList());
-
- code.Add("\n\n----------------------------- PROFILE ---------------------------");
- var keys = _neededKeys.Select(key => "\"" + key + "\"");
- code.Add("\n\nprofile_whitelist = {\n " + string.Join("\n , ", keys) + "}");
-
- code.AddRange(_code);
-
- code.Add("\n\n ------------------------------- TESTS -------------------------");
-
- code.Add("function test_all()");
- code.Add(string.Join("\n",_tests).Indent());
- code.Add("end");
-
- var compatibility = string.Join("\n",
-
-
- "",
- "if (itinero == nil) then",
- " itinero = {}",
- " itinero.log = print",
- "",
- " -- Itinero is not defined -> we are running from a lua interpreter -> the tests are intended",
- " runTests = true",
- "",
- "",
- "else",
- " print = itinero.log",
- "end",
- "",
- "test_all()",
- "if (not failed_tests and not failed_profile_tests) then",
- " print(\"Tests OK\")",
- "end"
- );
- code.Add(compatibility);
-
- return string.Join("\n\n\n", code);
- }
- }
-}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero1/LuaPrinter1.MainFunction.cs b/AspectedRouting/IO/itinero1/LuaPrinter1.MainFunction.cs
new file mode 100644
index 0000000..ce10936
--- /dev/null
+++ b/AspectedRouting/IO/itinero1/LuaPrinter1.MainFunction.cs
@@ -0,0 +1,130 @@
+using System.Collections.Generic;
+using System.Linq;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Functions;
+using AspectedRouting.Language.Typ;
+
+namespace AspectedRouting.IO.itinero1
+{
+ public partial class LuaPrinter1
+ {
+
+
+ private string GenerateMainProfileFunction()
+ {
+ var impl = string.Join("\n",
+ "",
+ "",
+ "--[[",
+ _profile.Name,
+ "This is the main function called to calculate the access, oneway and speed.",
+ "Comfort is calculated as well, based on the parameters which are padded in",
+ "",
+ "Created by " + _profile.Author,
+ "Originally defined in " + _profile.Filename,
+ "]]",
+ "function " + _profile.Name + "(parameters, tags, result)",
+ "",
+ " -- initialize the result table on the default values",
+ " result.access = 0",
+ " result.speed = 0",
+ " result.factor = 1",
+ " result.direction = 0",
+ " result.canstop = true",
+ " result.attributes_to_keep = {}",
+ "",
+ " local access = " + _skeleton.ToLua(_profile.Access),
+ " if (access == nil or access == \"no\") then",
+ " return",
+ " end",
+ " tags.access = access",
+ " local oneway = " + _skeleton.ToLua(_profile.Oneway),
+ " tags.oneway = oneway",
+ " local speed = " + _skeleton.ToLua(_profile.Speed),
+ " tags.speed = speed",
+ " local distance = 1 -- the weight per meter for distance travelled is, well, 1m/m",
+ "");
+
+ impl +=
+ "\n local priority = 0\n ";
+
+ foreach (var (parameterName, expression) in _profile.Priority)
+ {
+ var paramInLua = _skeleton.ToLua(new Parameter(parameterName));
+
+
+ var exprInLua = _skeleton.ToLua(expression.Optimize());
+ var subs = new Curry(Typs.Tags, new Var(("a"))).UnificationTable(expression.Types.First());
+ if (subs != null && subs.TryGetValue("$a", out var resultType) &&
+ (resultType.Equals(Typs.Bool) || resultType.Equals(Typs.String)))
+ {
+ _skeleton. AddDep("parse");
+ exprInLua = "parse(" + exprInLua + ")";
+ }
+
+ impl += "\n " + string.Join("\n ",
+ $"if({paramInLua} ~= 0) then",
+ $" priority = priority + {paramInLua} * {exprInLua}",
+ "end"
+ );
+ }
+
+
+ impl += string.Join("\n",
+ "",
+ "",
+ " if (priority <= 0) then",
+ " result.access = 0",
+ " return",
+ " end",
+ "",
+ " result.access = 1",
+ " result.speed = speed",
+ " result.factor = 1 / priority",
+ "",
+ " if (oneway == \"both\") then",
+ " result.direction = 0",
+ " elseif (oneway == \"with\") then",
+ " result.direction = 1",
+ " elseif (oneway == \"against\") then",
+ " result.direction = 2",
+ " else",
+ " error(\"Unexpected value for oneway: \"..oneway)",
+ " end",
+ "",
+ "end"
+ );
+
+ return impl;
+ }
+
+
+ private (string functionName, string implementation) GenerateBehaviourFunction(
+ string behaviourName,
+ Dictionary behaviourParameters)
+ {
+ var referenceName = _profile.Name + "_" + behaviourName;
+ var functionName = referenceName.AsLuaIdentifier();
+ behaviourParameters.TryGetValue("description", out var description);
+
+ _skeleton.AddDep("remove_relation_prefix");
+ var impl = string.Join("\n",
+ "",
+ "--[[",
+ description,
+ "]]",
+ "function behaviour_" + functionName + "(tags, result)",
+ $" tags = remove_relation_prefix(tags, \"{behaviourName.AsLuaIdentifier()}\")",
+ " local parameters = default_parameters()",
+ " parameters.name = \"" + referenceName + "\"",
+ ""
+ );
+
+ impl += _parameterPrinter.DeclareParametersFor(behaviourParameters);
+
+ impl += " " + _profile.Name + "(parameters, tags, result)\n";
+ impl += "end\n";
+ return (functionName, impl);
+ }
+ }
+}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero1/LuaPrinter1.RelationPreprocessor.cs b/AspectedRouting/IO/itinero1/LuaPrinter1.RelationPreprocessor.cs
new file mode 100644
index 0000000..5fd6465
--- /dev/null
+++ b/AspectedRouting/IO/itinero1/LuaPrinter1.RelationPreprocessor.cs
@@ -0,0 +1,108 @@
+using System.Collections.Generic;
+using System.Linq;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+
+namespace AspectedRouting.IO.itinero1
+{
+ public partial class LuaPrinter1
+ {
+
+ private (string implementation, HashSet extraKeys) GenerateMembershipPreprocessor()
+ {
+ // Extra keys are the names of introduced tag-keys, e.g. '_relation:bicycle_fastest:cycle_highway'
+ var extraKeys = new HashSet();
+ var memberships = Analysis.MembershipMappingsFor(_profile, _context);
+
+ foreach (var (calledInFunction, membership) in memberships)
+ {
+ var funcMetaData = new AspectMetadata(
+ membership,
+ "relation_preprocessing_for_" + calledInFunction.AsLuaIdentifier(),
+ "Function preprocessing needed for aspect " + calledInFunction +
+ ", called by the relation preprocessor",
+ "Generator", "", "NA"
+ );
+
+
+ _skeleton.AddFunction(funcMetaData);
+ }
+
+
+ var func = new List
+ {
+ "",
+ "",
+ "-- Processes the relation. All tags which are added to result.attributes_to_keep will be copied to 'attributes' of each individual way",
+ "function relation_tag_processor(relation_tags, result)",
+ " local parameters = {}",
+ " local subresult = {}",
+ " local matched = false",
+ " result.attributes_to_keep = {}",
+ " ",
+ " -- Legacy to add colours to the bike networks",
+ " legacy_relation_preprocessor(relation_tags, result)"
+ };
+ _skeleton.AddDep("legacy");
+
+ foreach (var (calledInFunction, expr) in memberships)
+ {
+ func.Add($"\n\n -- {calledInFunction} ---");
+
+ var usedParameters = expr.UsedParameters().Select(param => param.ParamName.TrimStart('#')).ToHashSet();
+
+ // First, we calculate the value for the default parameters
+ var preProcName = "relation_preprocessing_for_" + calledInFunction.AsLuaIdentifier();
+ func.Add("");
+ func.Add("");
+ func.Add(" subresult.attributes_to_keep = {}");
+ func.Add(" parameters = default_parameters()");
+ func.Add($" matched = {preProcName}(parameters, relation_tags, subresult)");
+ func.Add(" if (matched) then");
+ var tagKey = "_relation:" + calledInFunction.AsLuaIdentifier();
+ extraKeys.Add(tagKey);
+ func.Add(
+ " -- " + tagKey +
+ " is the default value, which will be overwritten in 'remove_relation_prefix' for behaviours having a different parameter settign");
+ func.Add($" result.attributes_to_keep[\"{tagKey}\"] = \"yes\"");
+ func.Add(" end");
+
+
+ if (usedParameters.Count() == 0)
+ {
+ // Every behaviour uses the default parameters for this one
+ func.Add(" -- No parameter dependence for aspect " + calledInFunction);
+ continue;
+ }
+
+ foreach (var (behaviourName, parameters) in _profile.Behaviours)
+ {
+ if (usedParameters.Except(parameters.Keys.ToHashSet()).Any())
+ {
+ // The parameters where the membership depends on, are not used here
+ // This is thus the same as the default. We don't have to calculate it
+ continue;
+ }
+
+ func.Add("");
+ func.Add("");
+ func.Add(" subresult.attributes_to_keep = {}");
+ 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(" if (matched) then");
+ tagKey = "_relation:" + behaviourName.AsLuaIdentifier() + ":" + calledInFunction.AsLuaIdentifier();
+ extraKeys.Add(tagKey);
+ func.Add($" result.attributes_to_keep[\"{tagKey}\"] = \"yes\"");
+ func.Add(" end");
+ }
+ }
+
+ func.Add("end");
+
+ return (string.Join("\n", func), extraKeys);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero1/LuaPrinter1.cs b/AspectedRouting/IO/itinero1/LuaPrinter1.cs
new file mode 100644
index 0000000..55e4271
--- /dev/null
+++ b/AspectedRouting/IO/itinero1/LuaPrinter1.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Tests;
+
+namespace AspectedRouting.IO.itinero1
+{
+ public partial class LuaPrinter1
+ {
+ private readonly ProfileMetaData _profile;
+ private readonly Context _context;
+ private readonly List _aspectTestSuites;
+ private readonly List _profileTests;
+ private readonly LuaSkeleton.LuaSkeleton _skeleton;
+
+
+ private readonly LuaParameterPrinter _parameterPrinter;
+
+
+ public LuaPrinter1(ProfileMetaData profile, Context context,
+ List aspectTestSuites,
+ List profileTests)
+ {
+ _profile = profile;
+ _context = context;
+ _aspectTestSuites = aspectTestSuites;
+ _profileTests = profileTests;
+ _skeleton = new LuaSkeleton.LuaSkeleton(context);
+ _parameterPrinter = new LuaParameterPrinter(profile, _skeleton);
+ }
+
+ public string ToLua()
+ {
+ _skeleton.AddDep("spoken_instructions");
+
+ var (membershipFunction, extraKeys) = GenerateMembershipPreprocessor();
+ var (profileOverview, behaviourFunctions) = GenerateProfileFunctions();
+ var mainFunction = GenerateMainProfileFunction();
+ var tests = new LuaTestPrinter(_skeleton, new List{"unitTest","unitTestProfile"}).GenerateFullTestSuite(_profileTests, _aspectTestSuites);
+
+
+ var keys = _profile.AllExpressions(_context).PossibleTags().Keys
+ .Concat(extraKeys)
+ .Select(key => "\"" + key + "\"")
+ .ToHashSet();
+
+ var header = new List
+ {
+ $"-- Itinero 1.0-profile, generated by AspectedRouting on {DateTime.Now:s}",
+ $"name = \"{_profile.Name}\"",
+ "normalize = false",
+ "vehicle_type = {" + string.Join(", ", _profile.VehicleTyps.Select(s => "\"" + s + "\"")) + "}",
+ // meta_whitelist is defined in the profile file, these are tags that are included in the generated route, but are not relevant for determining weights
+ "meta_whitelist = {\n \"cycle_network_colour\"," // cycle network colour is sneaked in here for legacy reasons
+ + string.Join("\n , ", _profile.Metadata.Select(s => "\"" + s + "\""))
+ + " }",
+ "profile_whitelist = {\n " + string.Join("\n , ", keys) + "\n }",
+ "",
+ "",
+ "",
+ profileOverview,
+ "",
+ _parameterPrinter.GenerateDefaultParameters()
+ };
+
+
+ // Add the aspect functions to the skeleton
+ var usedFunctions = _profile.CalledFunctionsRecursive(_context).Values.SelectMany(v => v).ToHashSet();
+ foreach (var functionName in usedFunctions)
+ {
+ _skeleton.AddFunction(_context.GetAspect(functionName));
+ }
+
+
+ // The dependencies should be generated after all the other functions are generated, to make sure all are added
+ var dependencies = _skeleton.GenerateDependencies();
+ var functions = _skeleton.GenerateFunctions();
+
+ var allCode = new List>
+ {
+ header,
+ behaviourFunctions,
+ mainFunction.InList(),
+ membershipFunction.InList(),
+ "---------------------- ASPECTS ----------------------".InList(),
+ functions,
+ "---------------------- UTILS ------------------------".InList(),
+ dependencies,
+ "----------------------- TESTS ------------------------".InList(),
+ tests.InList(),
+ GenerateLegacyTail().InList()
+ };
+
+
+ return string.Join("\n\n\n", allCode.Select(code => string.Join("\n", code)));
+ }
+
+
+ private (string profilesOverview, List behaviourImplementations) GenerateProfileFunctions()
+ {
+ // Profiles gets the contents for the 'profiles' field, part of the profile spec
+ var profiles = new List();
+ var behaviourImplementations = new List();
+ foreach (var (behaviourName, behaviourParameters) in _profile.Behaviours)
+ {
+ var (functionName, implementation ) = GenerateBehaviourFunction(behaviourName, behaviourParameters);
+ behaviourImplementations.Add(implementation);
+ profiles.Add(
+ string.Join(",\n ",
+ $" name = \"{behaviourName.AsLuaIdentifier()}\"",
+ " function_name = \"behaviour_" + functionName + "\"",
+ " metric = \"custom\""
+ )
+ );
+ }
+
+ var profilesOverview = "profiles = {\n {\n" +
+ string.Join("\n },\n {\n ", profiles) + "\n }\n}";
+ return (profilesOverview, behaviourImplementations);
+ }
+
+
+ private string GenerateLegacyTail()
+ {
+ return string.Join("\n",
+ "",
+ "if (itinero == nil) then",
+ " itinero = {}",
+ " itinero.log = print",
+ "",
+ " -- Itinero is not defined -> we are running from a lua interpreter -> the tests are intended",
+ " runTests = true",
+ "",
+ "",
+ "else",
+ " print = itinero.log",
+ "end",
+ "",
+ "test_all()",
+ "if (not failed_tests and not failed_profile_tests) then",
+ " print(\"Tests OK\")",
+ "end"
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero1/Luaprinter.Profile.cs b/AspectedRouting/IO/itinero1/Luaprinter.Profile.cs
deleted file mode 100644
index 35c4ee9..0000000
--- a/AspectedRouting/IO/itinero1/Luaprinter.Profile.cs
+++ /dev/null
@@ -1,297 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using AspectedRouting.Language;
-using AspectedRouting.Language.Expression;
-using AspectedRouting.Language.Functions;
-using AspectedRouting.Language.Typ;
-
-namespace AspectedRouting.IO.itinero1
-{
- public partial class LuaPrinter
- {
- private readonly Context _context;
-
- public LuaPrinter(Context context)
- {
- _context = context;
- }
-
- private void CreateMembershipPreprocessor(ProfileMetaData profile)
- {
- var memberships = Analysis.MembershipMappingsFor(profile, _context);
-
- foreach (var (calledInFunction, membership) in memberships)
- {
- var funcMetaData = new AspectMetadata(
- membership,
- "relation_preprocessing_for_" + calledInFunction.FunctionName(),
- "Function preprocessing needed for aspect " + calledInFunction +
- ", called by the relation preprocessor",
- "Generator", "", "NA"
- );
-
-
- AddFunction(funcMetaData);
- }
-
-
- var func = new List
- {
- "",
- "",
- "-- Processes the relation. All tags which are added to result.attributes_to_keep will be copied to 'attributes' of each individual way",
- "function relation_tag_processor(relation_tags, result)",
- " local parameters = {}",
- " local subresult = {}",
- " local matched = false",
- " result.attributes_to_keep = {}"
- };
-
- foreach (var (calledInFunction, expr) in memberships)
- {
- func.Add($"\n\n -- {calledInFunction} ---");
-
- var usedParameters = expr.UsedParameters().Select(param => param.ParamName.TrimStart('#')).ToHashSet();
-
- // First, we calculate the value for the default parameters
- var preProcName = "relation_preprocessing_for_" + calledInFunction.FunctionName();
- func.Add("");
- func.Add("");
- func.Add(" subresult.attributes_to_keep = {}");
- func.Add(" parameters = default_parameters()");
- func.Add($" matched = {preProcName}(parameters, relation_tags, subresult)");
- func.Add(" if (matched) then");
- var tagKey = "_relation:" + calledInFunction.FunctionName();
- _neededKeys.Add(tagKey);
- func.Add(
- " -- " + tagKey +
- " is the default value, which will be overwritten in 'remove_relation_prefix' for behaviours having a different parameter settign");
- func.Add($" result.attributes_to_keep[\"{tagKey}\"] = \"yes\"");
- func.Add(" end");
-
-
- if (usedParameters.Count() == 0)
- {
- // Every behaviour uses the default parameters for this one
- func.Add(" -- No parameter dependence for aspect " + calledInFunction);
- continue;
- }
-
- foreach (var (behaviourName, parameters) in profile.Behaviours)
- {
- if (usedParameters.Except(parameters.Keys.ToHashSet()).Any())
- {
- // The parameters where the membership depends on, are not used here
- // This is thus the same as the default. We don't have to calculate it
- continue;
- }
-
- func.Add("");
- func.Add("");
- func.Add(" subresult.attributes_to_keep = {}");
- func.Add(" parameters = default_parameters()");
- func.Add(ParametersToLua(parameters.Where(kv => usedParameters.Contains(kv.Key))
- .ToDictionary(kv => kv.Key, kv => kv.Value)));
- func.Add($" matched = {preProcName}(parameters, relation_tags, subresult)");
- func.Add(" if (matched) then");
- tagKey = "_relation:" + behaviourName.FunctionName() + ":" + calledInFunction.FunctionName();
- _neededKeys.Add(tagKey);
- func.Add($" result.attributes_to_keep[\"{tagKey}\"] = \"yes\"");
- func.Add(" end");
- }
- }
-
- func.Add("end");
-
-
- _code.Add(string.Join("\n", func));
- }
-
-
- ///
- /// Adds the necessary called functions and the profile main entry point
- ///
- public void AddProfile(ProfileMetaData profile)
- {
- var defaultParameters = "\n";
- foreach (var (name, (types, inFunction)) in profile.UsedParameters(_context))
- {
- defaultParameters += $"{name}: {string.Join(", ", types)}\n" +
- $" Used in {inFunction}\n";
- }
-
- CreateMembershipPreprocessor(profile);
-
-
- var impl = string.Join("\n",
- "",
- "",
- $"name = \"{profile.Name}\"",
- "normalize = false",
- "vehicle_type = {" + string.Join(", ", profile.VehicleTyps.Select(s => "\"" + s + "\"")) + "}",
- "meta_whitelist = {\n " + string.Join("\n , ", profile.Metadata.Select(s => "\"" + s + "\"")) +
- "}",
- "",
- "",
- "",
- "--[[",
- profile.Name,
- "This is the main function called to calculate the access, oneway and speed.",
- "Comfort is calculated as well, based on the parameters which are padded in",
- "",
- "Created by " + profile.Author,
- "Originally defined in " + profile.Filename,
- "Used parameters: " + defaultParameters.Indent(),
- "]]",
- "function " + profile.Name + "(parameters, tags, result)",
- "",
- " -- initialize the result table on the default values",
- " result.access = 0",
- " result.speed = 0",
- " result.factor = 1",
- " result.direction = 0",
- " result.canstop = true",
- " result.attributes_to_keep = {}",
- "",
- " local access = " + ToLua(profile.Access),
- " if (access == nil or access == \"no\") then",
- " return",
- " end",
- " tags.access = access",
- " local oneway = " + ToLua(profile.Oneway),
- " tags.oneway = oneway",
- " local speed = " + ToLua(profile.Speed),
- " tags.speed = speed",
- " local distance = 1 -- the weight per meter for distance travelled is, well, 1m/m",
- "");
-
- impl +=
- "\n local priority = 0\n ";
-
- foreach (var (parameterName, expression) in profile.Priority)
- {
- var paramInLua = ToLua(new Parameter(parameterName));
-
-
- var exprInLua = ToLua(expression);
- var subs = new Curry(Typs.Tags, new Var(("a"))).UnificationTable(expression.Types.First());
- if (subs != null && subs.TryGetValue("$a", out var resultType) &&
- (resultType.Equals(Typs.Bool) || resultType.Equals(Typs.String)))
- {
- AddDep("parse");
- exprInLua = "parse(" + exprInLua + ")";
- }
-
- impl += "\n " + string.Join("\n ",
- $"if({paramInLua} ~= 0) then",
- $" priority = priority + {paramInLua} * {exprInLua}",
- "end"
- );
- }
-
-
- impl += string.Join("\n",
- "",
- "",
- " if (priority <= 0) then",
- " result.access = 0",
- " return",
- " end",
- "",
- " result.access = 1",
- " result.speed = speed",
- " result.factor = 1 / priority",
- "",
- " if (oneway == \"both\") then",
- " result.direction = 0",
- " elseif (oneway == \"with\") then",
- " result.direction = 1",
- " elseif (oneway == \"against\") then",
- " result.direction = 2",
- " else",
- " error(\"Unexpected value for oneway: \"..oneway)",
- " end",
- "",
- "end",
- "",
- "",
- "function default_parameters()",
- " local parameters = {}",
- ParametersToLua(profile.DefaultParameters),
- " return parameters",
- "end",
- "",
- ""
- );
-
-
- var profiles = new List();
- foreach (var (name, subParams) in profile.Behaviours)
- {
- impl += BehaviourFunction(profile, name, subParams, profiles);
- }
-
- impl += "\n\n\n";
- impl += "profiles = {\n {\n" +
- string.Join("\n },\n {\n ", profiles) + "\n }\n}";
-
- _code.Add(impl);
- }
-
- private string BehaviourFunction(ProfileMetaData profile,
- string name,
- Dictionary subParams, List profiles)
- {
- var functionName = profile.Name + "_" + name;
-
- subParams.TryGetValue("description", out var description);
- profiles.Add(
- string.Join(",\n ",
- $" name = \"{name.FunctionName()}\"",
- " function_name = \"behaviour_" + functionName.FunctionName() + "\"",
- " metric = \"custom\""
- )
- );
-
- AddDep("remove_relation_prefix");
- var impl = string.Join("\n",
- "",
- "--[[",
- description,
- "]]",
- "function behaviour_" + functionName.FunctionName() + "(tags, result)",
- $" tags = remove_relation_prefix(tags, \"{name.FunctionName()}\")",
- " local parameters = default_parameters()",
- " parameters.name = \"" + functionName + "\"",
- ""
- );
-
- impl += ParametersToLua(subParams);
-
- impl += " " + profile.Name + "(parameters, tags, result)\n";
- impl += "end\n";
- return impl;
- }
-
- ///
- /// `local parameters = default_parameters()` must still be invoked by caller!
- ///
- ///
- ///
- private string ParametersToLua(Dictionary subParams)
- {
- var impl = "";
- foreach (var (paramName, value) in subParams)
- {
- if (paramName.Equals("description"))
- {
- continue;
- }
-
- impl += $" parameters.{paramName.TrimStart('#').FunctionName()} = {ToLua(value)}\n";
- }
-
- return impl;
- }
- }
-}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero1/Luaprinter.TestSuites.cs b/AspectedRouting/IO/itinero1/Luaprinter.TestSuites.cs
deleted file mode 100644
index 71d7522..0000000
--- a/AspectedRouting/IO/itinero1/Luaprinter.TestSuites.cs
+++ /dev/null
@@ -1,101 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using AspectedRouting.Tests;
-
-namespace AspectedRouting.IO.itinero1
-{
- public partial class LuaPrinter
- {
- public void AddTestSuite(ProfileTestSuite testSuite)
- {
- var tests = string.Join("\n",
- testSuite.Tests.Select((test, i) => ToLua(testSuite, i, test.Item1, test.tags)));
- _tests.Add(tests);
- }
-
- private string ToLua(ProfileTestSuite testSuite, int index, ProfileResult expected, Dictionary tags)
- {
- AddDep("debug_table");
- var parameters = new Dictionary();
-
-
- var keysToCheck = new List();
- foreach (var (key, value) in tags)
- {
- if (key.StartsWith("#"))
- {
- parameters[key.TrimStart('#')] = value;
- }
- if(key.StartsWith("_relation:"))
- {
- keysToCheck.Add(key);
- }
- }
-
-
- foreach (var key in keysToCheck)
- {
- var newKey = key.Replace(".", "_");
- tags[newKey] = tags[key];
- tags.Remove(key);
- }
-
- foreach (var (paramName, _) in parameters)
- {
- tags.Remove("#" + paramName);
- }
- // function unit_test_profile(profile_function, profile_name, index, expected, tags)
-
- return $"unit_test_profile(behaviour_{testSuite.Profile.Name.FunctionName()}_{testSuite.BehaviourName.FunctionName()}, " +
- $"\"{testSuite.BehaviourName}\", " +
- $"{index}, " +
- $"{{access = \"{D(expected.Access)}\", speed = {expected.Speed}, oneway = \"{D(expected.Oneway)}\", weight = {expected.Priority} }}, " +
- tags.ToLuaTable() +
- ")";
- }
-
- private string D(string s)
- {
- if (string.IsNullOrEmpty(s))
- {
- return "0";
- }
-
- return s;
- }
-
-
- public void AddTestSuite(AspectTestSuite testSuite)
- {
- var fName = testSuite.FunctionToApply.Name;
- var tests = string.Join("\n",
- testSuite.Tests.Select((test, i) => ToLua(fName, i, test.expected, test.tags)));
- _tests.Add(tests);
- }
-
- private string ToLua(string functionToApplyName, int index, string expected, Dictionary tags)
- {
- var parameters = new Dictionary();
-
-
- foreach (var (key, value) in tags)
- {
- if (key.StartsWith("#"))
- {
- parameters[key.TrimStart('#')] = value;
- }
- }
-
- foreach (var (paramName, _) in parameters)
- {
- tags.Remove("#" + paramName);
- }
-
- AddDep("unitTest");
- AddDep("debug_table");
- var funcName = functionToApplyName.Replace(" ", "_").Replace(".", "_");
- return
- $"unit_test({funcName}, \"{functionToApplyName}\", {index}, \"{expected}\", {parameters.ToLuaTable()}, {tags.ToLuaTable()})";
- }
- }
-}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero2/LuaPrinter2.MainFunction.cs b/AspectedRouting/IO/itinero2/LuaPrinter2.MainFunction.cs
new file mode 100644
index 0000000..92202d4
--- /dev/null
+++ b/AspectedRouting/IO/itinero2/LuaPrinter2.MainFunction.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Typ;
+
+namespace AspectedRouting.IO.itinero2
+{
+ public partial class LuaPrinter2
+ {
+ private string GenerateFactorFunction()
+ {
+ var parameters = new Dictionary();
+ foreach (var (name, value) in _profile.DefaultParameters)
+ {
+ parameters[name] = value;
+ }
+
+ foreach (var (name, value) in _profile.Behaviours[_behaviourName])
+ {
+ parameters[name] = value;
+ }
+
+
+ var aspects = new List();
+
+ foreach (var (paramName, expr) in _profile.Priority)
+ {
+ var paramExpr = parameters[paramName].Evaluate(_context);
+ if (!(paramExpr is double weight))
+ {
+ continue;
+ }
+
+ if (weight == 0)
+ {
+ continue;
+ }
+
+ var expression = _profile.Priority[paramName];
+ var exprInLua = _skeleton.ToLua(expression);
+ var subs = new Curry(Typs.Tags, new Var(("a"))).UnificationTable(expression.Types.First());
+ if (subs != null && subs.TryGetValue("$a", out var resultType) &&
+ (resultType.Equals(Typs.Bool) || resultType.Equals(Typs.String)))
+ {
+ _skeleton.AddDep("parse");
+ exprInLua = "parse(" + exprInLua + ")";
+ }
+
+ aspects.Add(weight + " * " + exprInLua);
+ }
+
+ Console.WriteLine(aspects.Lined());
+ var code = new List()
+ {
+ "--[[",
+ "Generates the factor according to the priorities and the parameters for this behaviour",
+ "Note: 'result' is not actually used",
+ "]]",
+ "function calculate_priority(parameters, tags, result, access, oneway, speed)",
+ " local distance = 1",
+ " local priority = \n " + string.Join(" +\n ", aspects),
+ " return priority",
+ "end"
+ };
+ return code.Lined();
+ }
+
+ private string GenerateMainFunction()
+ {
+ var parameters = _profile.Behaviours[_behaviourName];
+
+ _skeleton.AddDependenciesFor(_profile.Access);
+ _skeleton.AddDependenciesFor(_profile.Oneway);
+ _skeleton.AddDependenciesFor(_profile.Speed);
+
+ _skeleton.AddDep("eq");
+ var code = new List
+ {
+ "--[[",
+ _profile.Behaviours[_behaviourName]["description"].Evaluate(_context).ToString(),
+ "]]",
+ "function factor(tags, result)",
+ " ",
+ " -- initialize the result table on the default values",
+ " result.forward_speed = 0",
+ " result.backward_speed = 0",
+ " result.forward = 0",
+ " result.backward = 0",
+ " result.canstop = true",
+ " result.attributes_to_keep = {} -- not actually used anymore, but the code generation still uses this",
+
+ "",
+ "",
+ " local parameters = default_parameters()",
+ _parameterPrinter.DeclareParametersFor(parameters),
+ "",
+ " local oneway = " + _skeleton.ToLua(_profile.Oneway),
+ " tags.oneway = oneway",
+ " -- forward calculation",
+ " tags[\"_direction\"] = \"with\"",
+ " local access_forward = " + _skeleton.ToLua(_profile.Access),
+ " if(oneway == \"against\") then",
+ " access_forward = \"no\"",
+ " end",
+
+ " if(access_forward ~= nil and access_forward ~= \"no\") then",
+ " tags.access = access_forward",
+ " result.forward_speed = " + _skeleton.ToLua(_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",
+ " result.forward_speed = 0",
+ " else",
+ " result.forward = 1 / priority",
+ " end",
+ " end",
+ "",
+ " -- backward calculation",
+ " tags[\"_direction\"] = \"against\"",
+ " local access_backward = " + _skeleton.ToLua(_profile.Access),
+ "",
+ " if(oneway == \"with\") then",
+ " access_backward = \"no\"",
+ " end",
+ "",
+ " if(access_backward ~= nil and access_backward ~= \"no\") then",
+ " tags.access = access_backward" +
+ " result.backward_speed = " + _skeleton.ToLua(_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",
+ " result.backward_speed = 0",
+ " else",
+ " result.backward = 1 / priority",
+ " end",
+ " end",
+
+ "end"
+ };
+
+ return code.Lined();
+ }
+ }
+}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero2/LuaPrinter2.cs b/AspectedRouting/IO/itinero2/LuaPrinter2.cs
new file mode 100644
index 0000000..a7b2cc9
--- /dev/null
+++ b/AspectedRouting/IO/itinero2/LuaPrinter2.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using AspectedRouting.IO.itinero1;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Tests;
+
+namespace AspectedRouting.IO.itinero2
+{
+ ///
+ /// Lua printer for itinero2-lua format
+ ///
+ /// The itinero 2.0 lua profile is a whole lot simpler then the 1.0 format,
+ /// as a single profile there only describes a single behaviour of a vehicle:
+ ///
+ /// It has:
+ /// - name: string, e.g. 'bicycle.fastest'
+ /// - factor(attributes, result): void, where 'attributes' are all the tags of the way,
+ /// and result must contain (after calling):
+ /// - 'forward_speed', a double describing the forward speed (in km/h)
+ /// - 'backward_speed', the speed when travelling in the opposite direction (0 if not possible)
+ /// - 'forward', a double describing the forwardfactor
+ /// - 'backward', the backward factor
+ /// - 'canstop', a boolean indicating if stopping along the road is possible
+ ///
+ ///
+ public partial class LuaPrinter2
+ {
+ private readonly ProfileMetaData _profile;
+ private readonly string _behaviourName;
+ private readonly Context _context;
+ private readonly List _aspectTests;
+ private readonly IEnumerable _behaviourTestSuite;
+
+ private readonly LuaSkeleton.LuaSkeleton _skeleton;
+ private readonly LuaParameterPrinter _parameterPrinter;
+
+
+ public LuaPrinter2(ProfileMetaData profile, string behaviourName,
+ Context context,
+ List aspectTests, IEnumerable behaviourTestSuite)
+ {
+ _skeleton = new LuaSkeleton.LuaSkeleton(context);
+ _profile = profile;
+ _behaviourName = behaviourName;
+ _context = context;
+ _aspectTests = aspectTests;
+ _behaviourTestSuite = behaviourTestSuite;
+ _parameterPrinter = new LuaParameterPrinter(_profile, _skeleton);
+ }
+
+ public string ToLua()
+ {
+ var header =
+ new List
+ {
+ $"name = \"{_profile.Name}.{_behaviourName}\"",
+ $"generationDate = \"{DateTime.Now:s}\"",
+ $"description = \"{_profile.Description}\""
+ };
+
+ var tests = new LuaTestPrinter(_skeleton, new List() {"unitTestProfile2"}).GenerateFullTestSuite(
+ _behaviourTestSuite.ToList(), new List());
+ var all = new List
+ {
+ header.Lined(),
+ "",
+ GenerateMainFunction(),
+ "",
+ GenerateFactorFunction(),
+ "",
+ _parameterPrinter.GenerateDefaultParameters(),
+ "",
+ "",
+ string.Join("\n\n", _skeleton.GenerateFunctions()),
+ "",
+ string.Join("\n\n", _skeleton.GenerateDependencies()), // Should be AFTER generating the main function!
+ "",
+ tests,
+ "",
+
+ "if (itinero == nil) then",
+ " itinero = {}",
+ " itinero.log = print",
+ "",
+ " -- Itinero is not defined -> we are running from a lua interpreter -> the tests are intended",
+ " runTests = true",
+ "",
+ "",
+ "else",
+ " print = itinero.log",
+ "end",
+ "",
+ "test_all()",
+ "if (not failed_tests and not failed_profile_tests and print ~= nil) then",
+ " print(\"Tests OK\")",
+ "end"
+ };
+
+ return all.Lined();
+ }
+ }
+}
\ No newline at end of file
diff --git a/AspectedRouting/IO/lua/legacy.lua b/AspectedRouting/IO/lua/legacy.lua
new file mode 100644
index 0000000..13265aa
--- /dev/null
+++ b/AspectedRouting/IO/lua/legacy.lua
@@ -0,0 +1,17 @@
+--[[
+ Legacy function to add cycle_colour
+]]
+
+function legacy_relation_preprocessor(attributes, result)
+ if (attributes.route == "bicycle") then
+ -- This is a cycling network, the colour is copied
+ if (attributes.colour ~= nil) then
+ result.attributes_to_keep.cycle_network_colour = attributes.colour
+ end
+
+ if (attributes.color ~= nil) then
+ -- for the americans!
+ result.attributes_to_keep.cycle_network_colour = attributes.color
+ end
+ end
+end
diff --git a/AspectedRouting/IO/lua/unitTestProfile.lua b/AspectedRouting/IO/lua/unitTestProfile.lua
index bbf84a6..7d7e39b 100644
--- a/AspectedRouting/IO/lua/unitTestProfile.lua
+++ b/AspectedRouting/IO/lua/unitTestProfile.lua
@@ -1,20 +1,20 @@
failed_profile_tests = false
--[[
-expected should be a table containing 'access', 'speed' and 'weight'
+expected should be a table containing 'access', 'speed' and 'priority'
]]
function unit_test_profile(profile_function, profile_name, index, expected, tags)
local result = { attributes_to_keep = {} }
local profile_failed = false
profile_function(tags, result)
- local accessCorrect = (result.access == 0 and (expected.access == "no" or expected.weight <= 0)) or result.access == 1
+ local accessCorrect = (result.access == 0 and (expected.access == "no" or expected.priority <= 0)) or result.access == 1
if (not accessCorrect) then
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".access: expected " .. expected.access .. " but got " .. result.access)
profile_failed = true
failed_profile_tests = true
end
- if (expected.access == "no" or expected.weight <= 0) then
+ if (expected.access == "no" or expected.priority <= 0) then
-- we cannot access this road, the other results are irrelevant
if (profile_failed) then
print("The used tags for test " .. tostring(index) .. " are:")
@@ -46,8 +46,8 @@ function unit_test_profile(profile_function, profile_name, index, expected, tags
end
- if (not double_compare(result.factor, 1/expected.weight)) then
- print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".factor: expected " .. expected.weight .. " but got " .. 1/result.factor)
+ if (not double_compare(result.factor, 1/expected.priority)) then
+ print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".factor: expected " .. expected.priority .. " but got " .. 1/result.factor)
failed_profile_tests = true
profile_failed = true
end
diff --git a/AspectedRouting/IO/lua/unitTestProfile2.lua b/AspectedRouting/IO/lua/unitTestProfile2.lua
new file mode 100644
index 0000000..3593f04
--- /dev/null
+++ b/AspectedRouting/IO/lua/unitTestProfile2.lua
@@ -0,0 +1,67 @@
+failed_profile_tests = false
+--[[
+Unit test of a behaviour function for an itinero 2.0 profile
+]]
+
+function unit_test_profile(profile_function, profile_name, index, expected, tags)
+ -- Note: we don't actually use 'profile_function'
+
+ local result = {}
+ local profile_failed = false
+ factor(tags, result)
+
+
+ local forward_access = result.forward_speed > 0 and result.forward > 0;
+ local backward_access = result.backward_speed > 0 and result.backward > 0;
+
+ if (not forward_access and not backward_access) then
+
+ if (expected.access == "no" or expected.speed <= 0 or expected.priority <= 0) then
+ -- All is fine, we can't access this thing anyway
+ return
+ end
+
+ profile_failed = true
+ print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".access: expected " .. expected.access .. " but forward and backward are 0 (for either speed or factor)")
+ end
+
+ if (expected.oneway == "with") then
+ if (backward_access) then
+ -- we can go against the direction, not good
+ print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is possible")
+ profile_failed = true;
+ end
+ if (not forward_access) then
+ print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is not possible")
+ end
+ end
+
+ if (expected.oneway == "against") then
+ if (forward_access) then
+ -- we can go against the direction, not good
+ print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going with the direction is possible")
+ end
+ if (not backward_access) then
+ print("Test " .. tostring(index) .. " warning for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but going against the direction is not possible")
+ end
+ end
+
+ if (result.forward_speed ~= expected.speed and result.backward_speed ~= expected.speed) then
+ print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".speed: expected " .. expected.speed .. " but got " .. result.forward_speed .. " forward and " .. result.backward_speed .. " backward")
+ profile_failed = true;
+ end
+
+
+ if (result.forward ~= expected.priority and result.backward ~= expected.priority) then
+ print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".priority: expected " .. expected.priority .. " but got " .. result.forward .. " forward and " .. result.backward .. " backward")
+ profile_failed = true;
+ end
+
+ if(profile_failed) then
+ failed_profile_tests = true;
+ debug_table(tags, "tags: ")
+ debug_table(expected, "expected: ")
+ debug_table(result, "result: ")
+ end
+
+end
\ No newline at end of file
diff --git a/AspectedRouting/Language/Analysis.cs b/AspectedRouting/Language/Analysis.cs
index 73c9781..88030a3 100644
--- a/AspectedRouting/Language/Analysis.cs
+++ b/AspectedRouting/Language/Analysis.cs
@@ -207,6 +207,8 @@ namespace AspectedRouting.Language
{
// Read as: this function calls the value-function
var result = new Dictionary>();
+
+
var calledFunctions = new Queue();
void ScanExpression(IExpression e, string inFunction)
@@ -252,6 +254,62 @@ namespace AspectedRouting.Language
return result;
}
+ public static (HashSet parameterName, HashSet calledFunctionNames) DirectlyAndInderectlyCalled(
+ this List exprs, Context ctx)
+ {
+ var parameters = new HashSet();
+ var dependencies = new HashSet();
+
+ var queue = new Queue();
+ exprs.ForEach(queue.Enqueue);
+
+ while (queue.TryDequeue(out var next))
+ {
+ var (p, deps) = next.DirectlyCalled();
+ parameters.UnionWith(p);
+ var toCheck = deps.Except(dependencies);
+ dependencies.UnionWith(deps);
+
+ foreach (var fName in toCheck)
+ {
+ queue.Enqueue(ctx.GetFunction(fName));
+ }
+ }
+
+ return (parameters, dependencies);
+ }
+
+
+ ///
+ /// Generates an overview of the dependencies of the expression, both which parameters it needs and what other functions (builtin or defined) it needs.
+ ///
+ ///
+ ///
+ public static (HashSet parameterName, HashSet calledFunctionNames) DirectlyCalled(
+ this IExpression expr)
+ {
+ var parameters = new HashSet();
+ var dependencies = new HashSet();
+
+ expr.Visit(e =>
+ {
+ if (e is FunctionCall fc)
+ {
+ dependencies.Add(fc.CalledFunctionName);
+ }
+
+ if (e is Parameter p)
+ {
+ parameters.Add(p.ParamName);
+ }
+
+ return true;
+ });
+
+
+ return (parameters, dependencies);
+ }
+
public static string TypeBreakdown(this IExpression e)
{
var text = "";
@@ -404,12 +462,12 @@ namespace AspectedRouting.Language
var usedTags = new Dictionary>();
foreach (var expr in exprs)
{
-
var possible = expr.PossibleTags();
if (possible == null)
{
continue;
}
+
foreach (var (key, values) in possible)
{
if (!usedTags.TryGetValue(key, out var collection))
diff --git a/AspectedRouting/Language/Context.cs b/AspectedRouting/Language/Context.cs
index 01c427a..3563785 100644
--- a/AspectedRouting/Language/Context.cs
+++ b/AspectedRouting/Language/Context.cs
@@ -8,6 +8,7 @@ namespace AspectedRouting.Language
public class Context
{
public readonly Dictionary Parameters = new Dictionary();
+
public readonly Dictionary DefinedFunctions = new Dictionary();
public readonly string AspectName;
@@ -48,6 +49,23 @@ namespace AspectedRouting.Language
DefinedFunctions[name] = function;
}
+ public AspectMetadata GetAspect(string name)
+ {
+ if (name.StartsWith("$"))
+ {
+ name = name.Substring(1);
+ }
+
+ if (DefinedFunctions.ContainsKey(name))
+ {
+ return DefinedFunctions[name];
+ }
+
+ throw new ArgumentException(
+ $"The aspect {name} is not a defined function. Known functions are " +
+ string.Join(", ", DefinedFunctions.Keys));
+ }
+
public IExpression GetFunction(string name)
{
if (name.StartsWith("$"))
diff --git a/AspectedRouting/Language/Expression/Apply.cs b/AspectedRouting/Language/Expression/Apply.cs
index e1ddb1c..ada9cec 100644
--- a/AspectedRouting/Language/Expression/Apply.cs
+++ b/AspectedRouting/Language/Expression/Apply.cs
@@ -185,6 +185,8 @@ namespace AspectedRouting.Language.Expression
return Funcs.Id;
}
+
+
if (Types.Count() > 1)
{
@@ -198,6 +200,18 @@ namespace AspectedRouting.Language.Expression
return new Apply(_debugInfo, optimized);
}
+
+ {
+ var arg = new List();
+ if (
+ Deconstruct.UnApplyAny(
+ Deconstruct.IsFunc(Funcs.Id),
+ Deconstruct.Assign(arg)
+ ).Invoke(this))
+ {
+ return arg.First();
+ }
+ }
{
var (f, a) = FunctionApplications.Values.First();
@@ -274,6 +288,7 @@ namespace AspectedRouting.Language.Expression
{
return;
}
+
foreach (var (_, (f, a)) in FunctionApplications)
{
f.Visit(visitor);
diff --git a/AspectedRouting/Language/Expression/ProfileMetaData.cs b/AspectedRouting/Language/Expression/ProfileMetaData.cs
index 5fc74b4..4e4f26d 100644
--- a/AspectedRouting/Language/Expression/ProfileMetaData.cs
+++ b/AspectedRouting/Language/Expression/ProfileMetaData.cs
@@ -35,9 +35,9 @@ namespace AspectedRouting.Language.Expression
Author = author;
Filename = filename;
VehicleTyps = vehicleTyps;
- Access = access;
- Oneway = oneway;
- Speed = speed;
+ Access = access.Optimize();
+ Oneway = oneway.Optimize();
+ Speed = speed.Optimize();
Priority = priority;
Metadata = metadata;
DefaultParameters = defaultParameters;
diff --git a/AspectedRouting/Program.cs b/AspectedRouting/Program.cs
index 2c726ef..6d132a4 100644
--- a/AspectedRouting/Program.cs
+++ b/AspectedRouting/Program.cs
@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using AspectedRouting.IO;
using AspectedRouting.IO.itinero1;
+using AspectedRouting.IO.itinero2;
using AspectedRouting.IO.jsonParser;
using AspectedRouting.Language;
using AspectedRouting.Language.Expression;
@@ -13,7 +14,7 @@ namespace AspectedRouting
{
static class Program
{
- public static IEnumerable<(AspectMetadata aspect, AspectTestSuite tests)> ParseAspects(
+ public static List<(AspectMetadata aspect, AspectTestSuite tests)> ParseAspects(
this IEnumerable jsonFileNames, List testFileNames, Context context)
{
var aspects = new List<(AspectMetadata aspect, AspectTestSuite tests)>();
@@ -55,42 +56,11 @@ namespace AspectedRouting
return null;
}
- private static LuaPrinter GenerateLua(Context context,
- IEnumerable<(AspectMetadata aspect, AspectTestSuite tests)> aspects,
- ProfileMetaData profile, List profileTests)
- {
- var luaPrinter = new LuaPrinter(context);
- var usedFunctions = profile.CalledFunctionsRecursive(context).Values.SelectMany(v => v).ToHashSet();
-
- foreach (var (aspect, tests) in aspects)
- {
- if (!usedFunctions.Contains(aspect.Name))
- {
- continue;
- }
-
- luaPrinter.AddFunction(aspect);
- if (tests != null)
- {
- luaPrinter.AddTestSuite(tests);
- }
- }
-
- luaPrinter.AddProfile(profile);
- foreach (var testSuite in profileTests)
- {
- luaPrinter.AddTestSuite(testSuite);
- }
-
-
- return luaPrinter;
- }
-
- private static IEnumerable<(ProfileMetaData profile, List profileTests)> ParseProfiles(
+ private static List<(ProfileMetaData profile, List profileTests)> ParseProfiles(
IEnumerable jsonFiles, IReadOnlyCollection testFiles, Context context)
{
- var result = new List<(ProfileMetaData profile, List profileTests)>();
+ var result = new List<(ProfileMetaData profile, List profileTests)>();
foreach (var jsonFile in jsonFiles)
{
try
@@ -104,13 +74,13 @@ namespace AspectedRouting
profile.SanityCheckProfile(context);
- var profileTests = new List();
+ var profileTests = new List();
foreach (var behaviourName in profile.Behaviours.Keys)
{
var path = testFiles.FindTest($"{profile.Name}.{behaviourName}.behaviour_test.csv");
if (path != null && File.Exists(path))
{
- var test = ProfileTestSuite.FromString(context, profile, behaviourName,
+ var test = BehaviourTestSuite.FromString(context, profile, behaviourName,
File.ReadAllText(path));
profileTests.Add(test);
}
@@ -201,6 +171,23 @@ namespace AspectedRouting
Console.WriteLine($"Error in the file {file}:\n {msg}");
}
+ private static void PrintUsedTags(ProfileMetaData profile, Context context)
+ {
+ Console.WriteLine("\n\n\n---------- " + profile.Name + " --------------");
+ foreach (var (key, values) in profile.AllExpressions(context).PossibleTags())
+ {
+ var vs = "*";
+ if (values.Any())
+ {
+ vs = string.Join(", ", values);
+ }
+
+ Console.WriteLine(key + ": " + vs);
+ }
+
+ Console.WriteLine("\n\n\n------------------------");
+ }
+
public static void Main(string[] args)
{
if (args.Length < 2)
@@ -255,24 +242,26 @@ namespace AspectedRouting
}
- Console.WriteLine("\n\n\n---------- " + profile.Name + " --------------");
- foreach (var (key, values) in profile.AllExpressions(context).PossibleTags())
+ PrintUsedTags(profile, context);
+
+ var aspectTests = aspects.Select(a => a.tests).ToList();
+ var luaProfile = new LuaPrinter1(profile, context,
+ aspectTests,
+ profileTests
+ ).ToLua();
+ File.WriteAllText(outputDir + "/" + profile.Name + ".lua", luaProfile);
+
+ foreach (var (behaviourName, behaviourParameters) in profile.Behaviours)
{
- var vs = "*";
- if (values.Any())
- {
- vs = string.Join(", ", values);
- }
-
- Console.WriteLine(key + ": " + vs);
+ var lua2behaviour = new LuaPrinter2(
+ profile,
+ behaviourName,
+ context,
+ aspectTests,
+ profileTests.Where(testsSuite => testsSuite.BehaviourName == behaviourName)
+ ).ToLua();
+ File.WriteAllText($"{outputDir}/itinero2/{profile.Name}.{behaviourName}.lua", lua2behaviour);
}
-
- Console.WriteLine("\n\n\n------------------------");
-
- var luaPrinter = GenerateLua(context, aspects, profile, profileTests);
-
-
- File.WriteAllText(outputDir + "/" + profile.Name + ".lua", luaPrinter.ToLua());
}
Repl(context,
diff --git a/AspectedRouting/Tests/ProfileTestSuite.cs b/AspectedRouting/Tests/ProfileTestSuite.cs
index e524cb6..9da3385 100644
--- a/AspectedRouting/Tests/ProfileTestSuite.cs
+++ b/AspectedRouting/Tests/ProfileTestSuite.cs
@@ -8,13 +8,13 @@ using AspectedRouting.Language.Typ;
namespace AspectedRouting.Tests
{
- public class ProfileTestSuite
+ public class BehaviourTestSuite
{
public readonly ProfileMetaData Profile;
public readonly string BehaviourName;
public readonly IEnumerable<(ProfileResult, Dictionary tags)> Tests;
- public static ProfileTestSuite FromString(Context c, ProfileMetaData function, string profileName,
+ public static BehaviourTestSuite FromString(Context c, ProfileMetaData function, string behaviourName,
string csvContents)
{
try
@@ -109,21 +109,21 @@ namespace AspectedRouting.Tests
}
}
- return new ProfileTestSuite(function, profileName, tests);
+ return new BehaviourTestSuite(function, behaviourName, tests);
}
catch (Exception e)
{
- throw new Exception("In the profile test file for " + profileName, e);
+ throw new Exception("In the profile test file for " + behaviourName, e);
}
}
- public ProfileTestSuite(
+ public BehaviourTestSuite(
ProfileMetaData profile,
- string profileName,
+ string behaviourName,
IEnumerable<(ProfileResult, Dictionary tags)> tests)
{
Profile = profile;
- BehaviourName = profileName;
+ BehaviourName = behaviourName;
Tests = tests;
}
diff --git a/AspectedRouting/Utils.cs b/AspectedRouting/Utils.cs
index 7d505d6..80bf2d4 100644
--- a/AspectedRouting/Utils.cs
+++ b/AspectedRouting/Utils.cs
@@ -9,6 +9,16 @@ namespace AspectedRouting
return s.Replace("\n", "\n ");
}
+ public static List InList(this T t)
+ {
+ return new List {t};
+ }
+
+ public static string Lined(this IEnumerable lines)
+ {
+ return string.Join("\n", lines);
+ }
+
public static int Multiply(this IEnumerable ints)
{
var factor = 1;