This commit is contained in:
Pieter Vander Vennet 2020-05-04 17:41:48 +02:00
parent 3fd19dce5c
commit 287dde6cee
20 changed files with 402 additions and 1349 deletions

View file

@ -58,15 +58,19 @@ namespace AspectedRouting.IO.itinero1
code.Add("\n\n----------------------------- PROFILE ---------------------------");
var keys = _neededKeys.Select(key => "\"" + key + "\"");
code.Add("\n\nprofile_whitelist = {" + string.Join(", ", keys) + "}");
code.Add("\n\nprofile_whitelist = {\n " + string.Join("\n , ", keys) + "}");
code.AddRange(_code);
code.Add("\n\n ------------------------------- TESTS -------------------------");
code.AddRange(_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 = {}",
@ -80,6 +84,7 @@ namespace AspectedRouting.IO.itinero1
" print = itinero.log",
"end",
"",
"test_all()",
"if (not failed_tests and not failed_profile_tests) then",
" print(\"Tests OK\")",
"end"

View file

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

View file

@ -15,7 +15,7 @@ namespace AspectedRouting.IO.itinero1
{
_context = context;
}
private void CreateMembershipPreprocessor(ProfileMetaData profile)
{
var memberships = Analysis.MembershipMappingsFor(profile, _context);
@ -47,34 +47,69 @@ namespace AspectedRouting.IO.itinero1
" result.attributes_to_keep = {}"
};
foreach (var (calledInFunction, _) in memberships)
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)
{
var preProcName = "relation_preprocessing_for_" + calledInFunction.FunctionName();
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));
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");
var tagKey = "_relation:" + behaviourName.FunctionName() + ":" + calledInFunction.FunctionName();
_neededKeys.Add("_relation:"+calledInFunction.FunctionName()); // Slightly different then tagkey!
tagKey = "_relation:" + behaviourName.FunctionName() + ":" + calledInFunction.FunctionName();
_neededKeys.Add("_relation:" + calledInFunction.FunctionName()); // Slightly different then 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>
@ -96,7 +131,8 @@ namespace AspectedRouting.IO.itinero1
$"name = \"{profile.Name}\"",
"normalize = false",
"vehicle_type = {" + string.Join(", ", profile.VehicleTyps.Select(s => "\"" + s + "\"")) + "}",
"meta_whitelist = {" + string.Join(", ", profile.Metadata.Select(s => "\"" + s + "\"")) + "}",
"meta_whitelist = {\n " + string.Join("\n , ", profile.Metadata.Select(s => "\"" + s + "\"")) +
"}",
"",
"",
"",
@ -132,28 +168,31 @@ namespace AspectedRouting.IO.itinero1
"");
impl +=
"\n local priority = \n ";
"\n local priority = 0\n ";
var weightParts = new List<string>();
foreach (var (parameterName, expression) in profile.Priority)
{
var priorityPart = ToLua(new Parameter(parameterName)) + " * ";
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)))
{
priorityPart += "parse(" + ToLua(expression) + ")";
}
else
{
priorityPart += ToLua(expression);
AddDep("parse");
exprInLua = "parse(" + exprInLua + ")";
}
weightParts.Add(priorityPart);
impl += "\n "+string.Join("\n ",
$"if({paramInLua} ~= 0) then",
$" priority = priority + {paramInLua} * {exprInLua}",
"end"
);
}
impl += string.Join(" + \n ", weightParts);
impl += string.Join("\n",
"",
@ -162,14 +201,16 @@ namespace AspectedRouting.IO.itinero1
" -- put all the values into the result-table, as needed for itinero",
" result.access = 1",
" result.speed = speed",
" result.factor = priority",
" result.factor = 1 / priority",
"",
" if (oneway == \"both\") then",
" result.oneway = 0",
" result.direction = 0",
" elseif (oneway == \"with\") then",
" result.oneway = 1",
" result.direction = 1",
" elseif (oneway == \"against\") then",
" result.direction = 2",
" else",
" result.oneway = 2",
" error(\"Unexpected value for oneway: \"..oneway)",
" end",
"",
"end",
@ -239,7 +280,6 @@ namespace AspectedRouting.IO.itinero1
/// <returns></returns>
private string ParametersToLua(Dictionary<string, IExpression> subParams)
{
var impl = "";
foreach (var (paramName, value) in subParams)
{
@ -247,6 +287,7 @@ namespace AspectedRouting.IO.itinero1
{
continue;
}
impl += $" parameters.{paramName.TrimStart('#').FunctionName()} = {ToLua(value)}\n";
}

View file

@ -92,6 +92,7 @@ namespace AspectedRouting.IO.itinero1
}
AddDep("unitTest");
AddDep("debug_table");
var funcName = functionToApplyName.Replace(" ", "_").Replace(".", "_");
return
$"unit_test({funcName}, \"{functionToApplyName}\", {index}, \"{expected}\", {parameters.ToLuaTable()}, {tags.ToLuaTable()})";

View file

@ -53,6 +53,12 @@ namespace AspectedRouting.IO.jsonParser
private static ProfileMetaData ParseProfile(this JsonElement e, Context context, FileInfo filepath)
{
if (!e.TryGetProperty("priority",out _))
{
// not a profile
return null;
}
var name = e.Get("name");
var author = e.TryGet("author");
if (filepath != null && !(name + ".json").ToLower().Equals(filepath.Name.ToLower()))

View file

@ -1,6 +1,24 @@
--[[
must_match checks that a collection of tags matches a specification.
The function is not trivial and contains a few subtilities.
Consider the following source:
{"$mustMatch":{ "a":"X", "b":{"not":"Y"}}}
This is desugared into
{"$mustMatch":{ "a":{"$eq":"X"}, "b":{"not":"Y"}}}
When applied on the tags {"a" : "X"}, this yields the table {"a":"yes", "b":"yes} (as `notEq` "Y" "nil") yields "yes"..
MustMatch checks that every key in this last table yields yes - even if it is not in the original tags!
]]
function must_match(tags, result, needed_keys, table)
for _, key in ipairs(needed_keys) do
local v = tags[key]
local v = table[key] -- use the table here, as a tag that must _not_ match might be 'nil' in the tags
if (v == nil) then
return false
end
@ -16,22 +34,29 @@ function must_match(tags, result, needed_keys, table)
end
elseif (type(mapping) == "string") then
local bool = mapping
if (bool == "yes" or bool == "1") then
return true
elseif (bool == "no" or bool == "0") then
if (bool == "no" or bool == "0") then
return false
end
if (bool ~= "yes" and bool ~= "1") then
error("MustMatch got a string value it can't handle: " .. bool)
end
elseif (type(mapping) == "boolean") then
if(not mapping) then
return false
end
error("MustMatch got a string value it can't handle: " .. bool)
else
error("The mapping is not a table. This is not supported. We got " .. mapping)
error("The mapping is not a table. This is not supported. We got " .. tostring(mapping) .. " (" .. type(mapping)..")")
end
end
-- Now that we know for sure that every key matches, we add them all
for _, key in ipairs(needed_keys) do
local v = tags[key]
-- Now that we know for sure that every key matches, we add them all
for _, key in ipairs(needed_keys) do
local v = tags[key] -- this is the only place where we use the original tags
if (v ~= nil) then
result.attributes_to_keep[key] = v
end
end
return true;
return true
end

View file

@ -30,24 +30,24 @@ function unit_test_profile(profile_function, profile_name, index, expected, tags
end
local actualOneway = result.oneway;
if (result.oneway == 0) then
local actualOneway = result.direction;
if (result.direction == 0) then
actualOneway = "both"
elseif (result.oneway == 1) then
elseif (result.direction == 1) then
actualOneway = "with"
elseif (result.oneway == 2) then
elseif (result.direction == 2) then
actualOneway = "against"
end
if (expected.oneway ~= actualOneway) then
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but got " .. actualOneway)
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.direction .. " but got " .. actualOneway)
failed_profile_tests = true
profile_failed = true
end
if (not double_compare(result.factor, expected.weight)) then
print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".factor: expected " .. expected.weight .. " but got " .. result.factor)
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)
failed_profile_tests = true
profile_failed = true
end

View file

@ -508,9 +508,27 @@ Note that this is a privileged builtin function, as the parser will automaticall
Lua implementation:
````lua
--[[
must_match checks that a collection of tags matches a specification.
The function is not trivial and contains a few subtilities.
Consider the following source:
{"$mustMatch":{ "a":"X", "b":{"not":"Y"}}}
This is desugared into
{"$mustMatch":{ "a":{"$eq":"X"}, "b":{"not":"Y"}}}
When applied on the tags {"a" : "X"}, this yields the table {"a":"yes", "b":"yes} (as `notEq` "Y" "nil") yields "yes"..
MustMatch checks that every key in this last table yields yes - even if it is not in the original tags!
]]
function must_match(tags, result, needed_keys, table)
for _, key in ipairs(needed_keys) do
local v = tags[key]
local v = table[key] -- use the table here, as a tag that must _not_ match might be 'nil' in the tags
if (v == nil) then
return false
end
@ -526,24 +544,31 @@ function must_match(tags, result, needed_keys, table)
end
elseif (type(mapping) == "string") then
local bool = mapping
if (bool == "yes" or bool == "1") then
return true
elseif (bool == "no" or bool == "0") then
if (bool == "no" or bool == "0") then
return false
end
if (bool ~= "yes" and bool ~= "1") then
error("MustMatch got a string value it can't handle: " .. bool)
end
elseif (type(mapping) == "boolean") then
if(not mapping) then
return false
end
error("MustMatch got a string value it can't handle: " .. bool)
else
error("The mapping is not a table. This is not supported. We got " .. mapping)
error("The mapping is not a table. This is not supported. We got " .. tostring(mapping) .. " (" .. type(mapping)..")")
end
end
-- Now that we know for sure that every key matches, we add them all
for _, key in ipairs(needed_keys) do
local v = tags[key]
-- Now that we know for sure that every key matches, we add them all
for _, key in ipairs(needed_keys) do
local v = tags[key] -- this is the only place where we use the original tags
if (v ~= nil) then
result.attributes_to_keep[key] = v
end
end
return true;
return true
end
````

View file

@ -131,6 +131,7 @@ namespace AspectedRouting.Language
{
var parameters = new Dictionary<string, (List<Type> Types, string inFunction)>();
void AddParams(IExpression e, string inFunction)
{
var parms = e.UsedParameters();
@ -167,11 +168,15 @@ namespace AspectedRouting.Language
AddParams(expr, profile.Name + ".priority");
}
foreach (var (name, expr) in context.DefinedFunctions)
var calledFunctions = profile.CalledFunctionsRecursive(context).Values
.SelectMany(ls => ls).ToHashSet();
foreach (var calledFunction in calledFunctions)
{
AddParams(expr, name);
var func = context.GetFunction(calledFunction);
AddParams(func, calledFunction);
}
return parameters;
}
@ -191,6 +196,54 @@ namespace AspectedRouting.Language
return result;
}
public static Dictionary<string, List<string>> CalledFunctionsRecursive(this ProfileMetaData profile,
Context c)
{
// 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)
{
result.Add(inFunction, new List<string>());
e.Visit(x =>
{
if (x is FunctionCall fc)
{
result[inFunction].Add(fc.CalledFunctionName);
if (!result.ContainsKey(fc.CalledFunctionName))
{
calledFunctions.Enqueue(fc.CalledFunctionName);
}
}
return true;
});
}
ScanExpression(profile.Access, profile.Name + ".access");
ScanExpression(profile.Oneway, profile.Name + ".oneway");
ScanExpression(profile.Speed, profile.Name + ".speed");
foreach (var (key, expr) in profile.Priority)
{
ScanExpression(new Parameter(key), $"{profile.Name}.priority.{key}.lefthand");
ScanExpression(expr, $"{profile.Name}.priority.{key}");
}
while (calledFunctions.TryDequeue(out var calledFunction))
{
var func = c.GetFunction(calledFunction);
ScanExpression(func, calledFunction);
}
return result;
}
public static string TypeBreakdown(this IExpression e)
{
var text = "";
@ -206,12 +259,28 @@ namespace AspectedRouting.Language
{
var defaultParameters = pmd.DefaultParameters.Keys;
var usedParameters = pmd.UsedParameters(context).Keys.Select(key => key.TrimStart('#'));
var usedMetadata = pmd.UsedParameters(context);
string MetaList(IEnumerable<string> paramNames)
{
var metaInfo = "";
foreach (var paramName in paramNames)
{
var _ = usedMetadata.TryGetValue(paramName, out var inFunction) ||
usedMetadata.TryGetValue('#' + paramName, out inFunction);
metaInfo += $"\n - {paramName} (used in {inFunction.inFunction})";
}
return metaInfo;
}
var usedParameters = usedMetadata.Keys.Select(key => key.TrimStart('#'));
var diff = usedParameters.ToHashSet().Except(defaultParameters).ToList();
if (diff.Any())
{
throw new ArgumentException("No default value set for parameter " + string.Join(", ", diff));
throw new ArgumentException("No default value set for parameter: " + MetaList(diff));
}
var unused = defaultParameters.Except(usedParameters);
@ -221,19 +290,21 @@ namespace AspectedRouting.Language
string.Join(", ", unused));
}
var paramsUsedInBehaviour = new HashSet<string>();
foreach (var (behaviourName, behaviourParams) in pmd.Behaviours)
{
var sum = 0.0;
var explanation = "";
foreach (var (paramName, _) in pmd.Priority)
{
paramsUsedInBehaviour.Add(paramName);
if (!pmd.DefaultParameters.ContainsKey(paramName))
{
throw new ArgumentException(
$"The behaviour {behaviourName} uses a parameter for which no default is set: {paramName}");
}
if (!behaviourParams.TryGetValue(paramName, out var weight))
{
explanation += $"\n - {paramName} = default (not set)";
@ -244,7 +315,8 @@ namespace AspectedRouting.Language
if (!(weightObj is double d))
{
throw new ArgumentException($"The parameter {paramName} is not a numeric value");
throw new ArgumentException(
$"The parameter {paramName} is not a numeric value in profile {behaviourName}");
}
sum += Math.Abs(d);
@ -258,6 +330,14 @@ namespace AspectedRouting.Language
explanation);
}
}
var defaultOnly = defaultParameters.Except(paramsUsedInBehaviour).ToList();
if (defaultOnly.Any())
{
Console.WriteLine(
$"[{pmd.Name}] WARNING: Some parameters only have a default value: {string.Join(", ", defaultOnly)}");
}
}
public static void SanityCheck(this IExpression e)

View file

@ -21,18 +21,7 @@ profiles = {
function_name = "determine_weights_networks_node_network",
metric = "custom"
},
{
name = "genk",
description = "[Custom] A route following the Genk cycle network",
function_name = "determine_weights_genk",
metric = "custom"
},
{
name = "brussels",
description = "[Custom] A route following the Brussels cycle network",
function_name = "determine_weights_brussels",
metric = "custom"
},
{
name = "cycle_highway",
description = "A functional route, preferring cycle_highways or the Brussels Mobility network. If none are availale, will favour speed",
@ -303,49 +292,6 @@ function determine_weights_networks_node_network(attributes, result)
determine_weights(attributes, result)
end
function determine_weights_genk(attributes, result)
-- we add a 'settings' element to attributes, they can be used by other profiles
if (attributes.settings == nil) then
attributes.settings = {}
attributes.settings.default_speed = 15
attributes.settings.min_speed = 3
attributes.settings.max_speed = 30
attributes.settings.safety_weight = 1
attributes.settings.time_weight = 0
attributes.settings.comfort_weight = 0
attributes.settings.network_weight = 3
attributes.settings.clear_restrictions_preference = 1
attributes.cycle_network = "yes"
attributes.settings.cycle_network_operator = "Stad Genk"
end
determine_weights(attributes, result)
end
function determine_weights_brussels(attributes, result)
-- we add a 'settings' element to attributes, they can be used by other profiles
if (attributes.settings == nil) then
attributes.settings = {}
attributes.settings.default_speed = 15
attributes.settings.min_speed = 3
attributes.settings.max_speed = 30
attributes.settings.safety_weight = 1
attributes.settings.time_weight = 0
attributes.settings.comfort_weight = 1
attributes.settings.network_weight = 5
attributes.settings.clear_restrictions_preference = 1;
attributes.settings.cycle_network_operator = "Brussels Mobility"
end
determine_weights(attributes, result)
end
function determine_weights_anyways_network(attributes, result)

View file

@ -1,4 +1,5 @@
access,oneway,speed,priority,highway,_relation:bicycle.network_by_operator
no,both,0,0,,
yes,both,15,1.9,residential,
yes,both,15,21.9,residential,yes
yes,both,15,6.9,residential,yes
dismount,both,2.25,0.0195,footway,

1 access oneway speed priority highway _relation:bicycle.network_by_operator
2 no both 0 0
3 yes both 15 1.9 residential
4 yes both 15 21.9 6.9 residential yes
5 dismount both 2.25 0.0195 footway

View file

@ -31,7 +31,7 @@
},
"access": {
"designated": 1.2,
"dismount": 0.5
"dismount": 0.01
},
"surface": {
"#": "The surface mapping heavily resembles the one in speed_factor, but it is not entirely the same",

View file

@ -0,0 +1,6 @@
access,oneway,speed,priority,highway,_relation:bicycle.network_by_operator,_relation:bicycle.network_is_cyclehighway
no,,,,,,
yes,both,15,1.9,residential,,
yes,both,15,6.9,residential,yes,
yes,both,15,6.9,residential,,yes
yes,both,15,11.9,residential,yes,yes
1 access oneway speed priority highway _relation:bicycle.network_by_operator _relation:bicycle.network_is_cyclehighway
2 no
3 yes both 15 1.9 residential
4 yes both 15 6.9 residential yes
5 yes both 15 6.9 residential yes
6 yes both 15 11.9 residential yes yes

View file

@ -22,8 +22,9 @@
"#distance": 0,
"#comfort": 0,
"#safety": 0,
"#network": 0,
"#networkOperator": []
"#operatorNetworkScore": 0,
"#networkOperator": [],
"#cycleHighwayNetworkScore": 0
},
"behaviours": {
@ -54,10 +55,31 @@
"brussels": {
"description": "A route preferring the cycling network by operator 'Brussels Mobility'",
"#network": 20,
"#operatorNetworkScore": 5,
"#networkOperator": ["Brussels Mobility"],
"#comfort": 1,
"#safety": 1
},
"genk": {
"description": "A route preferring the cycling network by operator 'Stad Genk'",
"#operatorNetworkScore": 3,
"#networkOperator": ["Stad Genk"],
"#comfort": 0,
"#safety": 1
},
"cycle_highway": {
"description": "A route preferring the 'cycle-highways' ",
"#cycleHighwayNetworkScore": 5,
"#comfort": 1,
"#safety": 1
},
"commute": {
"description": "A route preferring the 'cycle-highways' or the cycling network by operator 'Brussels Mobility'",
"#operatorNetworkScore": 5,
"#networkOperator": ["Brussels Mobility"],
"#cycleHighwayNetworkScore": 5,
"#comfort": 1,
"#safety": 1
}
},
"access": "$bicycle.legal_access",
@ -77,7 +99,8 @@
"priority": {
"#comfort": "$bicycle.comfort",
"#safety": "$bicycle.safety",
"#network": "$bicycle.network_by_operator",
"#operatorNetworkScore": "$bicycle.network_by_operator",
"#cycleHighwayNetworkScore": "$bicycle.network_is_cyclehighway",
"#timeNeeded": "$speed",
"#distance": "$distance"
}

View file

@ -5,7 +5,7 @@
"$mustMatch": {
"type": "route",
"route": "bicycle",
"state": {"$notEq": "proposed"},
"state": {"$not": "proposed"},
"operator": {"$containedIn": "#networkOperator"}
}
}

View file

@ -0,0 +1,13 @@
{
"name": "bicycle.network_is_cyclehighway",
"description": "Returns true if the highway is part of a [cycle highway](https://wiki.openstreetmap.org/wiki/Tag:cycle_network%3Dcycle_highway)",
"$memberOf": {
"$mustMatch": {
"type": "route",
"route": "bicycle",
"state": {"$not": "proposed"},
"cycle_network": "cycle_highway"
}
}
}

View file

@ -6,9 +6,11 @@
"value": {
"$multiply": {
"access": {
"no": 1.1
"#": "access=no implies no access for cars too!",
"no": 1.1,
"dismount": 0.01
},
"motor": {
"motor_vehicle": {
"no": 1.5
},
"foot": {

View file

@ -0,0 +1,37 @@
{
"name": "small",
"description": "A minimal example, to start designing a profile or to debug",
"vehicletypes": [
"vehicle",
"bicycle"
],
"metadata": [
"name"
],
"defaults": {
"#defaultSpeed": 15,
"#distance": 0,
"#cycleHighwayNetworkScore": 0
},
"behaviours": {
"shortest": {
"description": "The shortest route, independent of of speed",
"#distance": 1
},
"fastest": {
"description": "The shortest route, independent of of speed",
"#distance": 1
},
"commute": {
"description": "The shortest route, independent of of speed",
"#cycleHighwayNetworkScore": 3
}
},
"access": "$bicycle.legal_access",
"oneway": "$bicycle.oneway",
"speed": "#defaultSpeed",
"priority": {
"#distance": "$distance",
"#cycleHighwayNetworkScore": "$bicycle.network_is_cyclehighway"
}
}

View file

@ -42,8 +42,16 @@ namespace AspectedRouting
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)
{
@ -52,8 +60,6 @@ namespace AspectedRouting
}
luaPrinter.AddProfile(profile);
foreach (var testSuite in profileTests)
{
luaPrinter.AddTestSuite(testSuite);
@ -63,29 +69,54 @@ namespace AspectedRouting
return luaPrinter;
}
private static (ProfileMetaData profile, List<ProfileTestSuite> profileTests) ParseProfile(string profilePath,
Context context)
private static List<(ProfileMetaData profile, List<ProfileTestSuite> profileTests)> ParseProfiles(
IEnumerable<string> jsonFiles, Context context)
{
var profile = JsonParser.ProfileFromJson(context, File.ReadAllText(profilePath), new FileInfo(profilePath));
profile.SanityCheckProfile(context);
var profileFi = new FileInfo(profilePath);
var profileTests = new List<ProfileTestSuite>();
foreach (var behaviourName in profile.Behaviours.Keys)
var result = new List<(ProfileMetaData profile, List<ProfileTestSuite> profileTests)>();
foreach (var jsonFile in jsonFiles)
{
var testPath = profileFi.DirectoryName + "/" + profile.Name + "." + behaviourName + ".csv";
if (File.Exists(testPath))
try
{
var test = ProfileTestSuite.FromString(context, profile, behaviourName, File.ReadAllText(testPath));
profileTests.Add(test);
var profile =
JsonParser.ProfileFromJson(context, File.ReadAllText(jsonFile), new FileInfo(jsonFile));
if (profile == null)
{
continue;
}
profile.SanityCheckProfile(context);
var profileFi = new FileInfo(jsonFile);
var profileTests = new List<ProfileTestSuite>();
foreach (var behaviourName in profile.Behaviours.Keys)
{
var path = profileFi.DirectoryName + "/" + profile.Name + "." + behaviourName + ".csv";
if (File.Exists(path))
{
var test = ProfileTestSuite.FromString(context, profile, behaviourName,
File.ReadAllText(path));
profileTests.Add(test);
}
else
{
Console.WriteLine($"[{profile.Name}] WARNING: no test found for behaviour {behaviourName}");
}
}
result.Add((profile, profileTests));
}
else
catch (Exception e)
{
Console.WriteLine($"[{behaviourName}] WARNING: no test found for behaviour");
PrintError(jsonFile, e);
}
}
return (profile, profileTests);
return result;
}
private static void PrintError(string file, Exception exception)
{
Console.WriteLine($"Error in the file {file}:\n {exception.Message}");
}
public static void Main(string[] args)
@ -101,6 +132,15 @@ namespace AspectedRouting
var aspects = ParseAspects(files, context);
foreach (var (aspect, _) in aspects)
{
context.AddFunction(aspect.Name, aspect);
}
var profiles = ParseProfiles(files, context);
// With everything parsed and typechecked, time for tests
foreach (var (aspect, t) in aspects)
{
if (t == null)
@ -111,23 +151,18 @@ namespace AspectedRouting
{
t.Run();
}
context.AddFunction(aspect.Name, aspect);
}
var profilePath = "Profiles/bicycle/bicycle.json";
var (profile, profileTests) = ParseProfile(profilePath, context);
foreach (var test in profileTests)
foreach (var (profile, profileTests) in profiles)
{
test.Run(context);
foreach (var test in profileTests)
{
test.Run(context);
}
var luaPrinter = GenerateLua(context, aspects, profile, profileTests);
File.WriteAllText(profile.Name + ".lua", luaPrinter.ToLua());
}
var luaPrinter = GenerateLua(context, aspects, profile, profileTests);
File.WriteAllText("output.lua", luaPrinter.ToLua());
}
}
}

File diff suppressed because it is too large Load diff