Refactoring of lua printing, adds generation of itinero 2.0 profiles

This commit is contained in:
Pieter Vander Vennet 2020-06-10 01:02:17 +02:00
parent 8bf77cbc69
commit 5268d4ee81
25 changed files with 1191 additions and 580 deletions

View file

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

View file

@ -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<string>()
{
"function default_parameters()",
" local parameters = {}",
DeclareParametersFor(_profile.DefaultParameters),
" return parameters",
"end"
};
return string.Join("\n",impl);
}
/// <summary>
/// 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()`
///
/// </summary>
/// <param name="behaviour"></param>
/// <returns></returns>
public string DeclareParametersFor(Dictionary<string, IExpression> 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;
}
}
}

View file

@ -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<IExpression>();
var order = new List<IExpression>();
@ -63,6 +65,9 @@ namespace AspectedRouting.IO.itinero1
var func = new List<IExpression>();
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");
}

View file

@ -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<string> _alreadyAddedFunctions = new HashSet<string>();
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);
}
}
}

View file

@ -0,0 +1,78 @@
using System.Collections.Generic;
using System.IO;
using AspectedRouting.Language;
namespace AspectedRouting.IO.LuaSkeleton
{
/// <summary>
/// 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.
///
/// </summary>
public partial class LuaSkeleton
{
private readonly Context _context;
private readonly HashSet<string> _dependencies = new HashSet<string>();
private readonly List<string> _functionImplementations = new List<string>();
private readonly HashSet<string> _alreadyAddedFunctions = new HashSet<string>();
public LuaSkeleton(Context context)
{
_context = context;
}
internal void AddDep(string name)
{
_dependencies.Add(name);
}
public List<string> 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<string> GenerateDependencies()
{
var imps = new List<string>();
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;
}
}
}

View file

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

View file

@ -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<string> unitTestRunners)
{
_skeleton = skeleton;
unitTestRunners.ForEach(_skeleton.AddDep);
}
public string GenerateFullTestSuite(List<BehaviourTestSuite> profileTests, List<AspectTestSuite> 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<string>
{
"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<string, string> tags)
{
_skeleton.AddDep("debug_table");
var parameters = new Dictionary<string, string>();
var keysToCheck = new List<string>();
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);
}
/// <summary>
/// Generate a unit test call
/// </summary>
private string GenerateAspectUnitTestCall(string functionToApplyName, int index, string expected,
Dictionary<string, string> tags)
{
var parameters = new Dictionary<string, string>();
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;
}
}
}

View file

@ -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<string> _dependencies = new HashSet<string>();
private readonly HashSet<string> _neededKeys = new HashSet<string>();
private readonly List<string> _code = new List<string>();
private readonly List<string> _tests = new List<string>();
/// <summary>
/// A dictionary containing the implementation of basic functions
/// </summary>
/// <returns></returns>
private static IEnumerable<string> LoadFunctions(IEnumerable<string> names)
{
var imps = new List<string>();
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<string>();
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);
}
}
}

View file

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

View file

@ -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<string> extraKeys) GenerateMembershipPreprocessor()
{
// Extra keys are the names of introduced tag-keys, e.g. '_relation:bicycle_fastest:cycle_highway'
var extraKeys = new HashSet<string>();
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<string>
{
"",
"",
"-- 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);
}
}
}

View file

@ -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<AspectTestSuite> _aspectTestSuites;
private readonly List<BehaviourTestSuite> _profileTests;
private readonly LuaSkeleton.LuaSkeleton _skeleton;
private readonly LuaParameterPrinter _parameterPrinter;
public LuaPrinter1(ProfileMetaData profile, Context context,
List<AspectTestSuite> aspectTestSuites,
List<BehaviourTestSuite> 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<string>{"unitTest","unitTestProfile"}).GenerateFullTestSuite(_profileTests, _aspectTestSuites);
var keys = _profile.AllExpressions(_context).PossibleTags().Keys
.Concat(extraKeys)
.Select(key => "\"" + key + "\"")
.ToHashSet();
var header = new List<string>
{
$"-- 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<List<string>>
{
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<string> behaviourImplementations) GenerateProfileFunctions()
{
// Profiles gets the contents for the 'profiles' field, part of the profile spec
var profiles = new List<string>();
var behaviourImplementations = new List<string>();
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"
);
}
}
}

View file

@ -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<string>
{
"",
"",
"-- 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));
}
/// <summary>
/// Adds the necessary called functions and the profile main entry point
/// </summary>
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<string>();
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<string, IExpression> subParams, List<string> 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;
}
/// <summary>
/// `local parameters = default_parameters()` must still be invoked by caller!
/// </summary>
/// <param name="subParams"></param>
/// <returns></returns>
private string ParametersToLua(Dictionary<string, IExpression> subParams)
{
var impl = "";
foreach (var (paramName, value) in subParams)
{
if (paramName.Equals("description"))
{
continue;
}
impl += $" parameters.{paramName.TrimStart('#').FunctionName()} = {ToLua(value)}\n";
}
return impl;
}
}
}

View file

@ -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<string, string> tags)
{
AddDep("debug_table");
var parameters = new Dictionary<string, string>();
var keysToCheck = new List<string>();
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<string, string> tags)
{
var parameters = new Dictionary<string, string>();
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()})";
}
}
}

View file

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

View file

@ -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
{
/// <summary>
/// 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
///
/// </summary>
public partial class LuaPrinter2
{
private readonly ProfileMetaData _profile;
private readonly string _behaviourName;
private readonly Context _context;
private readonly List<AspectTestSuite> _aspectTests;
private readonly IEnumerable<BehaviourTestSuite> _behaviourTestSuite;
private readonly LuaSkeleton.LuaSkeleton _skeleton;
private readonly LuaParameterPrinter _parameterPrinter;
public LuaPrinter2(ProfileMetaData profile, string behaviourName,
Context context,
List<AspectTestSuite> aspectTests, IEnumerable<BehaviourTestSuite> 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<string>
{
$"name = \"{_profile.Name}.{_behaviourName}\"",
$"generationDate = \"{DateTime.Now:s}\"",
$"description = \"{_profile.Description}\""
};
var tests = new LuaTestPrinter(_skeleton, new List<string>() {"unitTestProfile2"}).GenerateFullTestSuite(
_behaviourTestSuite.ToList(), new List<AspectTestSuite>());
var all = new List<string>
{
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();
}
}
}

View file

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

View file

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

View file

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

View file

@ -207,6 +207,8 @@ namespace AspectedRouting.Language
{
// Read as: this function calls the value-function
var result = new Dictionary<string, List<string>>();
var calledFunctions = new Queue<string>();
void ScanExpression(IExpression e, string inFunction)
@ -252,6 +254,62 @@ namespace AspectedRouting.Language
return result;
}
public static (HashSet<string> parameterName, HashSet<string> calledFunctionNames) DirectlyAndInderectlyCalled(
this List<IExpression> exprs, Context ctx)
{
var parameters = new HashSet<string>();
var dependencies = new HashSet<string>();
var queue = new Queue<IExpression>();
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);
}
/// <summary>
/// Generates an overview of the dependencies of the expression, both which parameters it needs and what other functions (builtin or defined) it needs.
/// </summary>
/// <param name="expr"></param>
/// <returns></returns>
public static (HashSet<string> parameterName, HashSet<string> calledFunctionNames) DirectlyCalled(
this IExpression expr)
{
var parameters = new HashSet<string>();
var dependencies = new HashSet<string>();
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<string, HashSet<string>>();
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))

View file

@ -8,6 +8,7 @@ namespace AspectedRouting.Language
public class Context
{
public readonly Dictionary<string, IExpression> Parameters = new Dictionary<string, IExpression>();
public readonly Dictionary<string, AspectMetadata> DefinedFunctions = new Dictionary<string, AspectMetadata>();
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("$"))

View file

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

View file

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

View file

@ -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<string> jsonFileNames, List<string> 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<ProfileTestSuite> 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<ProfileTestSuite> profileTests)> ParseProfiles(
private static List<(ProfileMetaData profile, List<BehaviourTestSuite> profileTests)> ParseProfiles(
IEnumerable<string> jsonFiles, IReadOnlyCollection<string> testFiles, Context context)
{
var result = new List<(ProfileMetaData profile, List<ProfileTestSuite> profileTests)>();
var result = new List<(ProfileMetaData profile, List<BehaviourTestSuite> profileTests)>();
foreach (var jsonFile in jsonFiles)
{
try
@ -104,13 +74,13 @@ namespace AspectedRouting
profile.SanityCheckProfile(context);
var profileTests = new List<ProfileTestSuite>();
var profileTests = new List<BehaviourTestSuite>();
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,

View file

@ -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<string, string> 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<string, string> tags)> tests)
{
Profile = profile;
BehaviourName = profileName;
BehaviourName = behaviourName;
Tests = tests;
}

View file

@ -9,6 +9,16 @@ namespace AspectedRouting
return s.Replace("\n", "\n ");
}
public static List<T> InList<T>(this T t)
{
return new List<T> {t};
}
public static string Lined(this IEnumerable<string> lines)
{
return string.Join("\n", lines);
}
public static int Multiply(this IEnumerable<int> ints)
{
var factor = 1;