diff --git a/AspectedRouting.Test/AspectedRouting.Test.csproj b/AspectedRouting.Test/AspectedRouting.Test.csproj
new file mode 100644
index 0000000..8fa6c58
--- /dev/null
+++ b/AspectedRouting.Test/AspectedRouting.Test.csproj
@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>netcoreapp3.1</TargetFramework>
+
+        <IsPackable>false</IsPackable>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
+        <PackageReference Include="xunit" Version="2.4.0" />
+        <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
+        <PackageReference Include="coverlet.collector" Version="1.0.1" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="..\AspectedRouting\AspectedRouting.csproj" />
+    </ItemGroup>
+
+</Project>
\ No newline at end of file
diff --git a/AspectedRouting.Test/FunctionsTest.cs b/AspectedRouting.Test/FunctionsTest.cs
new file mode 100644
index 0000000..e519c19
--- /dev/null
+++ b/AspectedRouting.Test/FunctionsTest.cs
@@ -0,0 +1,63 @@
+using System.Collections.Generic;
+using AspectedRouting.IO.jsonParser;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Functions;
+using Xunit;
+
+namespace AspectedRouting.Test
+{
+    public class FunctionsTest
+    {
+        private IExpression MustMatchJson()
+        {
+            var json = "{" +
+                       "\"name\":\"test\"," +
+                       "\"description\":\"test\"," +
+                       "\"$mustMatch\":{\"a\":\"b\",\"x\":\"y\"}}";
+            return JsonParser.AspectFromJson(new Context(), json, "test.json");
+        }
+
+
+        [Fact]
+        public void TestAll_AllTags_Yes()
+        {
+            var tagsAx = new Dictionary<string, string>
+            {
+                {"a", "b"},
+                {"x", "y"}
+            };
+
+            var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize();
+            var result = expr.Evaluate(new Context());
+            Assert.Equal("yes", result);
+        }
+
+        [Fact]
+        public void TestAll_NoMatch_No()
+        {
+            var tagsAx = new Dictionary<string, string>
+            {
+                {"a", "b"},
+            };
+
+            var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize();
+            var result = expr.Evaluate(new Context());
+            Assert.Equal("no", result);
+        }
+
+        [Fact]
+        public void TestAll_NoMatchDifferent_No()
+        {
+            var tagsAx = new Dictionary<string, string>
+            {
+                {"a", "b"},
+                {"x", "someRandomValue"}
+            };
+
+            var expr = new Apply(MustMatchJson(), new Constant(tagsAx)).Optimize();
+            var result = expr.Evaluate(new Context());
+            Assert.Equal("no", result);
+        }
+    }
+}
\ No newline at end of file
diff --git a/AspectedRouting.Test/LuaPrinterTest.cs b/AspectedRouting.Test/LuaPrinterTest.cs
new file mode 100644
index 0000000..aec8d30
--- /dev/null
+++ b/AspectedRouting.Test/LuaPrinterTest.cs
@@ -0,0 +1,84 @@
+using System.Collections.Generic;
+using AspectedRouting.IO.itinero1;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Functions;
+using Xunit;
+
+namespace AspectedRouting.Test
+{
+    public class LuaPrinterTest
+    {
+        [Fact]
+        public void ToLua_SimpleMapping_Table()
+        {
+            var mapping = new Mapping(
+                new[] {"a", "b", "c"},
+                new[]
+                {
+                    new Constant(5),
+                    new Constant(6),
+                    new Constant(7),
+                }
+            );
+
+            var luaPrinter = new LuaPrinter(new Context());
+            var result = luaPrinter.MappingToLua(mapping);
+
+            Assert.Equal(
+                "{\n    a = 5,\n    b = 6,\n    c = 7\n}"
+                , result);
+        }
+
+        [Fact]
+        public void ToLua_NestedMapping_Table()
+        {
+            var mapping = new Mapping(
+                new[] {"a"},
+                new[]
+                {
+                    new Mapping(new[] {"b"},
+                        new[]
+                        {
+                            new Constant(42),
+                        }
+                    )
+                }
+            );
+            var luaPrinter = new LuaPrinter(new Context());
+            var result = luaPrinter.MappingToLua(mapping);
+            Assert.Equal("{\n    a = {\n        b = 42\n    }\n}", result);
+        }
+
+        [Fact]
+        public void Sanity_EveryBasicFunction_HasDescription()
+        {
+            var missing = new List<string>();
+            foreach (var (_, f) in Funcs.Builtins)
+            {
+                if (string.IsNullOrEmpty(f.Description))
+                {
+                    missing.Add(f.Name);
+                }
+            }
+
+            Assert.True(0 == missing.Count,
+                "These functions do not have a description: " + string.Join(", ", missing));
+        }
+        
+        [Fact]
+        public void Sanity_EveryBasicFunction_HasArgNames()
+        {
+            var missing = new List<string>();
+            foreach (var (_, f) in Funcs.Builtins)
+            {
+                if (f.ArgNames == null)
+                {
+                    missing.Add(f.Name);
+                }
+            }
+
+            Assert.True(0 == missing.Count,
+                "These functions do not have a description: " + string.Join(", ", missing));
+        }
+    }
+}
\ No newline at end of file
diff --git a/AspectedRouting.Test/TestAnalysis.cs b/AspectedRouting.Test/TestAnalysis.cs
new file mode 100644
index 0000000..2f51e0f
--- /dev/null
+++ b/AspectedRouting.Test/TestAnalysis.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using System.Linq;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Functions;
+using Xunit;
+
+namespace AspectedRouting.Test
+{
+    public class TestAnalysis
+    {
+        [Fact]
+        public void OnAll_SmallTagSet_AllCombinations()
+        {
+            var possibleTags = new Dictionary<string, List<string>>
+            {
+                {"a", new List<string> {"x", "y"}}
+            };
+
+            var all = possibleTags.OnAllCombinations(dict => ObjectExtensions.Pretty(dict), new List<string>()).ToList();
+            Assert.Equal(3, all.Count);
+            Assert.Contains("{}", all);
+            Assert.Contains("{a=x;}", all);
+            Assert.Contains("{a=y;}", all);
+        }
+
+        [Fact]
+        public void OnAll_TwoTagSet_AllCombinations()
+        {
+            var possibleTags = new Dictionary<string, List<string>>
+            {
+                {"a", new List<string> {"x", "y"}},
+                {"b", new List<string> {"u", "v"}}
+            };
+
+            var all = possibleTags.OnAllCombinations(dict => dict.Pretty(), new List<string>()).ToList();
+            Assert.Equal(9, all.Count);
+            Assert.Contains("{}", all);
+            Assert.Contains("{a=x;}", all);
+            Assert.Contains("{a=y;}", all);
+
+            Assert.Contains("{b=u;}", all);
+            Assert.Contains("{b=v;}", all);
+
+            Assert.Contains("{a=x;b=u;}", all);
+            Assert.Contains("{a=x;b=v;}", all);
+            Assert.Contains("{a=y;b=u;}", all);
+            Assert.Contains("{a=y;b=v;}", all);
+        }
+    }
+}
\ No newline at end of file
diff --git a/AspectedRouting.Test/TestInterpreter.cs b/AspectedRouting.Test/TestInterpreter.cs
new file mode 100644
index 0000000..f53b6de
--- /dev/null
+++ b/AspectedRouting.Test/TestInterpreter.cs
@@ -0,0 +1,182 @@
+using System.Collections.Generic;
+using System.Linq;
+using AspectedRouting.IO.jsonParser;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Functions;
+using AspectedRouting.Language.Typ;
+using Xunit;
+
+namespace AspectedRouting.Test
+{
+    public class TestInterpreter
+    {
+        [Fact]
+        public void MaxSpeedAspect_Evaluate_CorrectMaxSpeed()
+        {
+            var json =
+                "{\"name\": \"legal_maxspeed_be\",\"description\": \"Gives, for each type of highway, which the default legal maxspeed is in Belgium. This file is intended to be reused for in all vehicles, from pedestrian to car. In some cases, a legal maxspeed is not really defined (e.g. on footways). In that case, a socially acceptable speed should be taken (e.g.: a bicycle on a pedestrian path will go say around 12km/h)\",\"unit\": \"km/h\",\"$max\": {\"maxspeed\": \"$parse\",\"highway\": {\"residential\": 30},\"ferry\":5}}";
+
+            var aspect = JsonParser.AspectFromJson(null, json, null);
+            var tags = new Dictionary<string, string>
+            {
+                {"maxspeed", "42"},
+                {"highway", "residential"},
+                {"ferry", "yes"}
+            };
+
+            Assert.Equal("tags -> double", string.Join(", ", aspect.Types));
+            Assert.Equal(42d, new Apply(aspect, new Constant(tags)).Evaluate(null));
+        }
+
+        [Fact]
+        public void MaxSpeed_AnalyzeTags_AllTagsReturned()
+        {
+            var json =
+                "{\"name\": \"legal_maxspeed_be\",\"description\": \"Gives, for each type of highway, which the default legal maxspeed is in Belgium. This file is intended to be reused for in all vehicles, from pedestrian to car. In some cases, a legal maxspeed is not really defined (e.g. on footways). In that case, a socially acceptable speed should be taken (e.g.: a bicycle on a pedestrian path will go say around 12km/h)\",\"unit\": \"km/h\",\"$max\": {\"maxspeed\": \"$parse\",\"highway\": {\"residential\": 30},\"ferry\":5}}";
+
+            var aspect = JsonParser.AspectFromJson(null, json, null);
+
+            Assert.Equal(
+                new Dictionary<string, List<string>>
+                {
+                    {"maxspeed", new List<string>()},
+                    {"highway", new List<string> {"residential"}},
+                    {"ferry", new List<string>()}
+                },
+                aspect.PossibleTags());
+        }
+
+        [Fact]
+        public void EitherFunc_Const_Id_Specialized_Has_Correct_Type()
+
+        {
+            var b = new Var("b");
+            var c = new Var("c");
+
+            var eitherFunc = new Apply(Funcs.Const, Funcs.Id);
+            Assert.Single(eitherFunc.Types);
+            Assert.Equal(new Curry(b,
+                new Curry(c, c)), eitherFunc.Types.First());
+
+            var (_, (func, arg)) = eitherFunc.FunctionApplications.First();
+
+
+            // func  == const : (c -> c) -> b -> (c -> c)
+            var funcType = new Curry(new Curry(c, c),
+                new Curry(b, new Curry(c, c)));
+            Assert.Equal(funcType, func.Types.First());
+
+            // arg == id (but specialized): (c -> c)
+            var argType = new Curry(c, c);
+            Assert.Equal(argType, arg.Types.First());
+        }
+
+
+        [Fact]
+        public void EitherFunc_SpecializeToString_Const()
+        {
+            var a = new Constant("a");
+            var mconst = new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.Const);
+            var specialized = new Apply(mconst, a).Specialize(Typs.String);
+
+            Assert.Equal("((($const $id) $const) \"a\")", specialized.ToString());
+            Assert.Equal("string; $b -> string", string.Join("; ", new Apply(mconst, a).Types));
+            Assert.Equal("\"a\"", specialized.Evaluate(null).Pretty());
+
+            Assert.Equal("\"a\"", new Apply(new Apply(mconst, a), new Constant("42")).Specialize(Typs.String)
+                .Evaluate(null)
+                .Pretty());
+        }
+
+        [Fact]
+        public void Parse_Five_5()
+        {
+            var str = new Constant("5");
+            var parsed = new Apply(Funcs.Parse, str).Specialize(Typs.Double);
+            var o = parsed.Evaluate(null);
+            Assert.Equal(5d, o);
+        }
+
+        [Fact]
+        public void Concat_TwoString_AB()
+        {
+            var a = new Constant("a");
+            var b = new Constant("b");
+            Assert.Equal("string -> string -> string", Funcs.Concat.Types.First().ToString());
+            var ab = Funcs.Concat.Apply(a, b);
+            Assert.Equal("(($concat \"a\") \"b\")", ab.ToString());
+            Assert.Equal("ab", ab.Evaluate(null));
+        }
+
+        [Fact]
+        public void Id_Evaluate_ReturnsInput()
+        {
+            var a = new Constant("a");
+            var aId = Funcs.Id.Apply(a);
+            Assert.Equal("\"a\" : string", aId + " : " + string.Join(", ", aId.Types));
+            Assert.Equal("a", aId.Evaluate(null));
+        }
+
+        [Fact]
+        public void MaxTest()
+        {
+            var ls = new Constant(new ListType(Typs.Double),
+                new[] {1.1, 2.0, 3.0}.Select(d => (object) d));
+            Assert.Equal("[1.1, 2, 3] : list (double)",
+                ls.Evaluate(null).Pretty() + " : " + string.Join(", ", ls.Types));
+            var mx = Funcs.Max.Apply(ls);
+
+
+            Assert.Equal("($max [1.1, 2, 3]) : double", mx + " : " + string.Join(", ", mx.Types));
+            Assert.Equal(3d, mx.Evaluate(null));
+            var mxId = Funcs.Id.Apply(mx);
+            // identity function is not printed
+            Assert.Equal("($max [1.1, 2, 3]) : double", mxId + " : " + string.Join(", ", mxId.Types));
+            Assert.Equal(3d, mxId.Evaluate(null));
+        }
+
+        [Fact]
+        public void Fresh_SomeType_NewVarName()
+        {
+            Assert.Equal("$d", Var.Fresh(
+                Curry.ConstructFrom(new Var("a"),
+                    new Var("b"),
+                    new Var("b"),
+                    new Var("c"),
+                    new Var("aa"))).Name);
+        }
+
+        [Fact]
+        public void Mapping_Evaluate_CorrectMapping()
+        {
+            var mapping = Mapping.Construct(
+                ("a", new Constant(5.0)),
+                ("b", new Constant(-3))
+            );
+            Assert.Equal("5", mapping.Evaluate(null, new Constant("a")).Pretty());
+            Assert.Equal("-3", mapping.Evaluate(null, new Constant("b")).Pretty());
+        }
+
+        [Fact]
+        public void TestStringGeneration()
+        {
+            var v = Var.Fresh(new HashSet<string> {"$a", "$b"});
+            Assert.Equal("$c", v.Name);
+        }
+
+        [Fact]
+        public void TestDeconstruct()
+        {
+            var a = new Constant("a");
+
+            var app = new Apply(
+                new Apply(
+                    new Apply(Funcs.Id, Funcs.Id), Funcs.Id), a);
+            var (f, args ) = app.DeconstructApply().Value;
+            Assert.Equal(Funcs.Id.Name, ((Function) f).Name);
+            Assert.Equal(new List<IExpression> {Funcs.Id, Funcs.Id, a}.Select(e => e.ToString()),
+                args.Select(e => e.ToString()));
+        }
+    }
+}
\ No newline at end of file
diff --git a/AspectedRouting.sln b/AspectedRouting.sln
new file mode 100644
index 0000000..dc2b6c3
--- /dev/null
+++ b/AspectedRouting.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspectedRouting", "AspectedRouting\AspectedRouting.csproj", "{4A53D888-6872-4E5D-8022-338CC302473F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspectedRouting.Test", "AspectedRouting.Test\AspectedRouting.Test.csproj", "{A1309041-8AAE-42D7-A886-94C9FFC6A28C}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{4A53D888-6872-4E5D-8022-338CC302473F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{4A53D888-6872-4E5D-8022-338CC302473F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{4A53D888-6872-4E5D-8022-338CC302473F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{4A53D888-6872-4E5D-8022-338CC302473F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A1309041-8AAE-42D7-A886-94C9FFC6A28C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A1309041-8AAE-42D7-A886-94C9FFC6A28C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A1309041-8AAE-42D7-A886-94C9FFC6A28C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A1309041-8AAE-42D7-A886-94C9FFC6A28C}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+EndGlobal
diff --git a/AspectedRouting/Functions/MemberOf.cs b/AspectedRouting/Functions/MemberOf.cs
deleted file mode 100644
index 6316dc5..0000000
--- a/AspectedRouting/Functions/MemberOf.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using AspectedRouting.Typ;
-
-namespace AspectedRouting.Functions
-{
-    public class MemberOf : Function
-    {
-        public override string Description { get; } =
-            "This function uses memberships of relations to calculate values.\n" +
-            "\n" +
-            "Consider all the relations the scrutinized way is part of." +
-            "The enclosed function is executed for every single relation which is part of it, generating a list of results." +
-            "This list of results is in turn returned by 'memberOf'" +
-            "\n" +
-            "In itinero 1/lua, this is implemented by converting the matching relations and by adding the tags of the relations to the dictionary (or table) with the highway tags." +
-            "The prefix is '_relation:n:key=value', where 'n' is a value between 0 and the number of matching relations (implying that all of these numbers are scanned)." +
-            "The matching relations can be extracted by the compiler for the preprocessing.\n\n" +
-            "For testing, the relation can be emulated by using e.g. '_relation:0:key=value'";
-
-        public override List<string> ArgNames { get; } = new List<string>
-        {
-            "f","tags"
-        };
-
-        public MemberOf() : base(
-            "memberOf", true,
-            new[]
-            {
-                new Curry(
-                    new Curry(Typs.Tags, new Var("a")),
-                    new Curry(Typs.Tags, new ListType(new Var("a"))))
-            }
-        )
-        {
-        }
-
-        public MemberOf(IEnumerable<Type> types) : base("memberOf", types)
-        {
-        }
-
-        public override object Evaluate(Context c, params IExpression[] arguments)
-        {
-            var f = arguments[0];
-            var tags = (Dictionary<string, string>) arguments[1].Evaluate(c);
-
-            
-            var prefixes = new Dictionary<int, Dictionary<string, string>>();
-            foreach (var (k, v) in tags)
-            {
-                if (!k.StartsWith("_relation:")) continue;
-                var s = k.Split(":")[1];
-                if (int.TryParse(s, out var i))
-                {
-                    var key = k.Substring(("_relation:" + i + ":").Length);
-                    if (prefixes.TryGetValue(i, out var relationTags))
-                    {
-                        relationTags[key] = v;
-                    }
-                    else
-                    {
-                        prefixes[i] = new Dictionary<string, string>
-                        {
-                            {key, v}
-                        };
-                    }
-                }
-            }
-
-            
-            // At this point, we have all the tags of all the relations
-            // Time to run the function on all of them
-
-            var result = new List<object>();
-            foreach (var relationTags in prefixes.Values)
-            {
-                var o = f.Evaluate(c, new Constant(relationTags));
-                if (o != null)
-                {
-                    result.Add(o);
-                }
-            }
-            
-
-            return result;
-        }
-
-        public override IExpression Specialize(IEnumerable<Type> allowedTypes)
-        {
-            var unified = Types.SpecializeTo(allowedTypes);
-            if (unified == null)
-            {
-                return null;
-            }
-
-            return new MemberOf(unified);
-        }
-    }
-}
\ No newline at end of file
diff --git a/AspectedRouting/IO/JsonPrinter.cs b/AspectedRouting/IO/JsonPrinter.cs
deleted file mode 100644
index 79b81ce..0000000
--- a/AspectedRouting/IO/JsonPrinter.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using AspectedRouting.Functions;
-
-namespace AspectedRouting.IO
-{
-    public static class JsonPrinter
-    {
-        private static string Objs(params (string k, string v)[] stuff)
-        {
-            return "{\n" + string.Join("\n", stuff.Where(kv => kv.v != null).Select(kv => kv.k.Dq() + ": " + kv.v)).Indent() + "\n}";
-        }
-
-        private static string Arr(IEnumerable<string> strs)
-        {
-            return "[" + string.Join(", ", strs) + "]";
-        }
-
-        private static string Dq(this string s)
-        {
-            if (s == null)
-            {
-                return null;
-            }
-
-            return '"' + s + '"';
-        }
-
-        public static string Print(AspectMetadata meta)
-        {
-            return Objs(
-                ("name", meta.Name.Dq()),
-                ("description", meta.Description.Dq()),
-                ("unit", meta.Unit.Dq()),
-                ("author", meta.Author.Dq()),
-                ("file", meta.Filepath.Dq()),
-                ("type", Arr(meta.Types.Select(tp => tp.ToString().Dq()))),
-                ("value", meta.ExpressionImplementation.ToString())
-            );
-        }
-    }
-}
\ No newline at end of file
diff --git a/AspectedRouting/IO/LuaPrinter.cs b/AspectedRouting/IO/LuaPrinter.cs
deleted file mode 100644
index 6025afb..0000000
--- a/AspectedRouting/IO/LuaPrinter.cs
+++ /dev/null
@@ -1,535 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
-using AspectedRouting.Functions;
-using AspectedRouting.Typ;
-using static AspectedRouting.Deconstruct;
-
-namespace AspectedRouting.IO
-{
-    public class LuaPrinter
-    {
-        public static Dictionary<string, string> BasicFunctions = _basicFunctions();
-
-
-        private readonly HashSet<string> _deps = new HashSet<string>();
-        private readonly List<string> _code = new List<string>();
-        private readonly HashSet<string> _neededKeys = new HashSet<string>();
-
-        /// <summary>
-        /// A dictionary containing the implementation of basic functions
-        /// </summary>
-        /// <returns></returns>
-        private static Dictionary<string, string> _basicFunctions()
-        {
-            var imps = new Dictionary<string, string>();
-
-            var functionsToFetch = Funcs.BuiltinNames.ToList();
-            // These functions should be loaded from disk, but are not necessarily included
-            functionsToFetch.Add("table_to_list");
-            functionsToFetch.Add("debug_table");
-            functionsToFetch.Add("unitTest");
-            functionsToFetch.Add("unitTestProfile");
-            functionsToFetch.Add("double_compare");
-            
-            
-
-
-            foreach (var name in functionsToFetch)
-            {
-                var path = $"IO/lua/{name}.lua";
-                if (File.Exists(path))
-                {
-                    imps[name] = File.ReadAllText(path);
-                }
-            }
-
-            return imps;
-        }
-
-        public LuaPrinter()
-        {
-            _deps.Add("debug_table");
-        }
-
-
-        private string ToLua(IExpression bare, Context context, string key = "nil")
-        {
-            var collectedMapping = new List<IExpression>();
-            var order = new List<IExpression>();
-
-
-            if (UnApply(
-                UnApply(
-                    IsFunc(Funcs.FirstOf),
-                    Assign(order))
-                , UnApply(
-                    IsFunc(Funcs.StringStringToTags),
-                    Assign(collectedMapping))
-            ).Invoke(bare))
-            {
-                _deps.Add(Funcs.FirstOf.Name);
-                return "first_match_of(tags, result, \n" +
-                       "        " + ToLua(order.First(), context, key) + "," +
-                       ("\n" + MappingToLua((Mapping) collectedMapping.First(), context)).Indent().Indent() +
-                       ")";
-            }
-
-            if (UnApply(
-                UnApply(
-                    IsFunc(Funcs.MustMatch),
-                    Assign(order))
-                , UnApply(
-                    IsFunc(Funcs.StringStringToTags),
-                    Assign(collectedMapping))
-            ).Invoke(bare))
-            {
-                _deps.Add(Funcs.MustMatch.Name);
-                return "must_match(tags, result, \n" +
-                       "        " + ToLua(order.First(), context, key) + "," +
-                       ("\n" + MappingToLua((Mapping) collectedMapping.First(), context)).Indent().Indent() +
-                       ")";
-            }
-
-            var collectedList = new List<IExpression>();
-            var func = new List<IExpression>();
-
-
-            if (
-                UnApply(
-                    UnApply(IsFunc(Funcs.Dot), Assign(func)),
-                    UnApply(IsFunc(Funcs.ListDot),
-                        Assign(collectedList))).Invoke(bare))
-            {
-                var exprs = (IEnumerable<IExpression>) ((Constant) collectedList.First()).Evaluate(context);
-                var luaExprs = new List<string>();
-                var funcName = func.First().ToString().TrimStart('$');
-                _deps.Add(funcName);
-                foreach (var expr in exprs)
-                {
-                    var c = new List<IExpression>();
-                    if (UnApply(IsFunc(Funcs.Const), Assign(c)).Invoke(expr))
-                    {
-                        luaExprs.Add(ToLua(c.First(), context, key));
-                        continue;
-                    }
-
-                    if (expr.Types.First() is Curry curry
-                        && curry.ArgType.Equals(Typs.Tags))
-                    {
-                        var lua = ToLua(expr, context, key);
-                        luaExprs.Add(lua);
-                    }
-                }
-
-                return "\n        " + funcName + "({\n         " + string.Join(",\n         ", luaExprs) +
-                       "\n        })";
-            }
-
-
-            collectedMapping.Clear();
-            var dottedFunction = new List<IExpression>();
-
-
-            dottedFunction.Clear();
-
-            if (UnApply(
-                    UnApply(
-                        IsFunc(Funcs.Dot),
-                        Assign(dottedFunction)
-                    ),
-                    UnApply(
-                        IsFunc(Funcs.StringStringToTags),
-                        Assign(collectedMapping))).Invoke(bare)
-            )
-            {
-                var mapping = (Mapping) collectedMapping.First();
-                var baseFunc = (Function) dottedFunction.First();
-                _deps.Add(baseFunc.Name);
-                _deps.Add("table_to_list");
-
-                return baseFunc.Name +
-                       "(table_to_list(tags, result, " +
-                       ("\n" + MappingToLua(mapping, context)).Indent().Indent() +
-                       "))";
-            }
-
-            // The expression might be a function which still expects a string (the value from the tag) as argument
-            if (!(bare is Mapping) &&
-                bare.Types.First() is Curry curr &&
-                curr.ArgType.Equals(Typs.String))
-            {
-                var applied = new Apply(bare, new Constant(curr.ArgType, ("tags", "\"" + key + "\"")));
-                return ToLua(applied.Optimize(), context, key);
-            }
-
-
-            // The expression might consist of multiple nested functions
-            var fArgs = bare.DeconstructApply();
-            if (fArgs != null)
-            {
-                var (f, args) = fArgs.Value;
-                var baseFunc = (Function) f;
-                _deps.Add(baseFunc.Name);
-
-                return baseFunc.Name + "(" + string.Join(", ", args.Select(arg => ToLua(arg, context, key))) + ")";
-            }
-
-
-            var collected = new List<IExpression>();
-            switch (bare)
-            {
-                case FunctionCall fc:
-                    var called = context.DefinedFunctions[fc.CalledFunctionName];
-                    if (called.ProfileInternal)
-                    {
-                        return called.Name;
-                    }
-
-                    AddFunction(called, context);
-                    return $"{fc.CalledFunctionName.FunctionName()}(parameters, tags, result)";
-                case Constant c:
-                    return ConstantToLua(c, context);
-                case Mapping m:
-                    return MappingToLua(m, context).Indent();
-                case Function f:
-                    var fName = f.Name.TrimStart('$');
-
-                    if (Funcs.Builtins.ContainsKey(fName))
-                    {
-                        _deps.Add(f.Name);
-                    }
-                    else
-                    {
-                        var definedFunc = context.DefinedFunctions[fName];
-                        if (definedFunc.ProfileInternal)
-                        {
-                            return f.Name;
-                        }
-
-                        AddFunction(definedFunc, context);
-                    }
-
-                    return f.Name;
-                case Apply a when UnApply(IsFunc(Funcs.Const), Assign(collected)).Invoke(a):
-                    return ToLua(collected.First(), context, key);
-
-                case Parameter p:
-                    return $"parameters[\"{p.ParamName.FunctionName()}\"]";
-                default:
-                    throw new Exception("Could not convert " + bare + " to a lua expression");
-            }
-        }
-
-
-        public static string ConstantToLua(Constant c, Context context)
-        {
-            var o = c.Evaluate(context);
-            switch (o)
-            {
-                case IExpression e:
-                    return ConstantToLua(new Constant(e.Types.First(), e.Evaluate(null)), context);
-                case int i:
-                    return "" + i;
-                case double d:
-                    return "" + d;
-                case string s:
-                    return '"' + s.Replace("\"", "\\\"") + '"';
-                case ValueTuple<string, string> unpack:
-                    return unpack.Item1 + "[" + unpack.Item2 + "]";
-                case IEnumerable<object> ls:
-                    var t = (c.Types.First() as ListType).InnerType;
-                    return "{" + string.Join(", ", ls.Select(obj =>
-                    {
-                        var objInConstant = new Constant(t, obj);
-                        if (obj is Constant asConstant)
-                        {
-                            objInConstant = asConstant;
-                        }
-
-                        return ConstantToLua(objInConstant, context);
-                    })) + "}";
-                default:
-                    return o.ToString();
-            }
-        }
-
-        public string MappingToLua(Mapping m, Context context)
-        {
-            var contents = m.StringToResultFunctions.Select(kv =>
-                {
-                    var (key, expr) = kv;
-                    var left = "[\"" + key + "\"]";
-
-                    if (Regex.IsMatch(key, "^[a-zA-Z][_a-zA-Z-9]*$"))
-                    {
-                        left = key;
-                    }
-
-                    return left + " = " + ToLua(expr, context, key);
-                }
-            );
-            return
-                "{\n    " +
-                string.Join(",\n    ", contents) +
-                "\n}";
-        }
-
-
-        private HashSet<string> addFunctions = new HashSet<string>();
-
-        public void AddFunction(AspectMetadata meta, Context context)
-        {
-            if (addFunctions.Contains(meta.Name))
-            {
-                // already added
-                return;
-            }
-
-            addFunctions.Add(meta.Name);
-
-            var possibleTags = meta.PossibleTags();
-            var usedParams = meta.UsedParameters();
-            var numberOfCombinations = possibleTags.Values.Select(lst => 1 + lst.Count).Multiply();
-            var impl = string.Join("\n",
-                "--[[",
-                meta.Description,
-                "",
-                "Unit: " + meta.Unit,
-                "Created by " + meta.Author,
-                "Originally defined in " + meta.Filepath,
-                "Uses tags: " + string.Join(", ", possibleTags.Keys),
-                "Used parameters: " + string.Join(", ", usedParams),
-                "Number of combintations: " + numberOfCombinations,
-                "Returns values: ",
-                "]]",
-                "function " + meta.Name.FunctionName() + "(parameters, tags, result)",
-                "    return " + ToLua(meta.ExpressionImplementation, context),
-                "end"
-            );
-
-            _code.Add(impl);
-            foreach (var k in possibleTags.Keys)
-            {
-                _neededKeys.Add(k); // To generate a whitelist of OSM-keys that should be kept
-            }
-        }
-
-        private string CreateMembershipPreprocessor()
-        {
-
-            return "";
-
-
-        }
-
-        /// <summary>
-        /// Adds the necessary called functions and the profile main entry point
-        /// </summary>
-        public void CreateProfile(ProfileMetaData profile, Context context)
-        {
-            var defaultParameters = "\n";
-            foreach (var (name, (types, inFunction)) in profile.UsedParameters(context))
-            {
-                defaultParameters += $"{name}: {string.Join(", ", types)}\n" +
-                                     $"    Used in {inFunction}\n";
-            }
-
-
-            var impl = string.Join("\n",
-                "",
-                "",
-                $"name = \"{profile.Name}\"",
-                "normalize = false",
-                "vehicle_type = {" + string.Join(", ", profile.VehicleTyps.Select(s => "\"" + s + "\"")) + "}",
-                "meta_whitelist = {" + string.Join(", ", 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, context),
-                "    if (access == nil or access == \"no\") then",
-                "         return",
-                "    end",
-                "    local oneway = " + ToLua(profile.Oneway, context),
-                "    local speed = " + ToLua(profile.Speed, context),
-                "    local distance = 1 -- the weight per meter for distance travelled is, well, 1m/m",
-                "");
-
-            impl +=
-                "\n    local weight = \n        ";
-
-            var weightParts = new List<string>();
-            foreach (var (parameterName, expression) in profile.Weights)
-            {
-                var weightPart = ToLua(new Parameter(parameterName), context) + " * ";
-
-                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)))
-                {
-                    weightPart += "parse(" + ToLua(expression, context) + ")";
-                }
-                else
-                {
-                    weightPart += ToLua(expression, context);
-                }
-
-                weightParts.Add(weightPart);
-            }
-
-            impl += string.Join(" + \n        ", weightParts);
-
-            impl += string.Join("\n",
-                "",
-                "",
-                "",
-                "    -- put all the values into the result-table, as needed for itinero",
-                "    result.access = 1",
-                "    result.speed = speed",
-                "    result.factor = 1/weight",
-                "",
-                "    if (oneway == \"both\") then",
-                "        result.oneway = 0",
-                "    elseif (oneway == \"with\") then",
-                "        result.oneway = 1",
-                "    else",
-                "         result.oneway = 2",
-                "    end",
-                "",
-                "end",
-                "",
-                "",
-                "function default_parameters()",
-                "    return " + profile.DefaultParameters.ToLuaTable(),
-                "end",
-                "",
-                ""
-            );
-
-            impl += "\n\n" + CreateMembershipPreprocessor() + "\n\n";
-
-
-            var profiles = new List<string>();
-            foreach (var (name, subParams) in profile.Profiles)
-            {
-                var functionName = profile.Name + "_" + name;
-
-                subParams.TryGetValue("description", out var description);
-                profiles.Add(
-                    string.Join(",\n    ",
-                        $"    name = \"{name}\"",
-                        "    function_name = \"profile_" + functionName + "\"",
-                        "    metric = \"custom\""
-                    )
-                );
-
-                impl += string.Join("\n",
-                    "",
-                    "--[[",
-                    description,
-                    "]]",
-                    "function profile_" + functionName + "(tags, result)",
-                    "    local parameters = default_parameters()",
-                    ""
-                );
-
-                foreach (var (paramName, value) in subParams)
-                {
-                    impl += $"    parameters.{paramName.TrimStart('#').FunctionName()} = {value.Pretty()}\n";
-                }
-
-                impl += "    " + profile.Name + "(parameters, tags, result)\n";
-                impl += "end\n";
-            }
-
-            impl += "\n\n\n";
-            impl += "profiles = {\n    {\n" +
-                    string.Join("\n    },\n    {\n    ", profiles) + "\n    }\n}";
-
-            _code.Add(impl);
-        }
-
-        public string ToLua()
-        {
-            var deps = _deps.ToList();
-            deps.Add("unitTest");
-            deps.Add("unitTestProfile");
-            deps.Add("inv");
-            deps.Add("double_compare");
-            deps.Sort();
-            var code = deps.Select(d => BasicFunctions[d]).ToList();
-
-            var keys = _neededKeys.Select(key => "\"" + key + "\"");
-            code.Add("\n\nprofile_whitelist = {" + string.Join(", ", keys) + "}");
-
-            code.AddRange(_code);
-
-
-            return string.Join("\n\n\n", code);
-        }
-    }
-
-
-    public static class StringExtensions
-    {
-        public static string ToLuaTable(this Dictionary<string, string> tags)
-        {
-            var contents = tags.Select(kv =>
-            {
-                var (key, value) = kv;
-                var left = "[\"" + key + "\"]";
-
-                if (Regex.IsMatch(key, "^[a-zA-Z][_a-zA-Z-9]*$"))
-                {
-                    left = key;
-                }
-
-                return $"{left} =  \"{value}\"";
-            });
-            return "{" + string.Join(", ", contents) + "}";
-        }
-
-        public static string ToLuaTable(this Dictionary<string, object> tags)
-        {
-            var contents = tags.Select(kv =>
-            {
-                var (key, value) = kv;
-                var left = "[\"" + key + "\"]";
-
-                if (Regex.IsMatch(key, "^[a-zA-Z][_a-zA-Z-9]*$"))
-                {
-                    left = key;
-                }
-
-                return $"{left} =  {value.Pretty()}";
-            });
-            return "{" + string.Join(", ", contents) + "}";
-        }
-
-        public static string FunctionName(this string s)
-        {
-            return s.Replace(" ", "_").Replace(".", "_").Replace("-", "_");
-        }
-    }
-}
\ No newline at end of file
diff --git a/AspectedRouting/IO/MdPrinter.cs b/AspectedRouting/IO/MdPrinter.cs
index 8dd5ef9..d43225f 100644
--- a/AspectedRouting/IO/MdPrinter.cs
+++ b/AspectedRouting/IO/MdPrinter.cs
@@ -1,9 +1,9 @@
 using System;
-using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using AspectedRouting.Functions;
-using AspectedRouting.Typ;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
 namespace AspectedRouting.IO
 {
@@ -32,7 +32,7 @@ namespace AspectedRouting.IO
             get
             {
                 var txt = "## Builtin functions\n\n";
-                foreach (var biFunc in Functions.Funcs.BuiltinNames)
+                foreach (var biFunc in Funcs.BuiltinNames)
                 {
                     txt += "- " + biFunc + "\n";
                 }
@@ -68,7 +68,7 @@ namespace AspectedRouting.IO
             {
                 var args = f.ArgBreakdown();
                 var header = "Argument name |  ";
-                var line = "--------------| - ";
+                var line = "-------------- | - ";
                 for (int i = 0; i < f.Types.Count(); i++)
                 {
                     header += "|  ";
diff --git a/AspectedRouting/IO/ProfileTestSuite.cs b/AspectedRouting/IO/ProfileTestSuite.cs
deleted file mode 100644
index bcfa395..0000000
--- a/AspectedRouting/IO/ProfileTestSuite.cs
+++ /dev/null
@@ -1,135 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using AspectedRouting.Functions;
-
-namespace AspectedRouting.IO
-{
-    public struct Expected
-    {
-        public int Access, Oneway;
-        public double Speed, Weight;
-
-        public Expected(int access, int oneway, double speed, double weight)
-        {
-            Access = access;
-            Oneway = oneway;
-            Speed = speed;
-            Weight = weight;
-            if (Access == 0)
-            {
-            }
-        }
-    }
-
-    public class ProfileTestSuite
-    {
-        private readonly ProfileMetaData _profile;
-        private readonly string _profileName;
-        private readonly IEnumerable<(Expected, Dictionary<string, string> tags)> _tests;
-
-        public static ProfileTestSuite FromString(ProfileMetaData function, string profileName, string csvContents)
-        {
-            try
-            {
-                var all = csvContents.Split("\n").ToList();
-                var keys = all[0].Split(",").ToList();
-                keys = keys.GetRange(4, keys.Count - 4).Select(k => k.Trim()).ToList();
-
-                var tests = new List<(Expected, Dictionary<string, string>)>();
-
-                var line = 1;
-                foreach (var test in all.GetRange(1, all.Count - 1))
-                {
-                    line++;
-                    if (string.IsNullOrEmpty(test.Trim()))
-                    {
-                        continue;
-                    }
-
-                    try
-                    {
-                        var testData = test.Split(",").ToList();
-                        var expected = new Expected(
-                            int.Parse(testData[0]),
-                            int.Parse(testData[1]),
-                            double.Parse(testData[2]),
-                            double.Parse(testData[3])
-                        );
-                        var vals = testData.GetRange(4, testData.Count - 4);
-                        var tags = new Dictionary<string, string>();
-                        for (int i = 0; i < keys.Count; i++)
-                        {
-                            if (i < vals.Count && !string.IsNullOrEmpty(vals[i]))
-                            {
-                                tags[keys[i]] = vals[i];
-                            }
-                        }
-
-                        tests.Add((expected, tags));
-                    }
-                    catch (Exception e)
-                    {
-                        throw new Exception("On line " + line, e);
-                    }
-                }
-
-                return new ProfileTestSuite(function, profileName, tests);
-            }
-            catch (Exception e)
-            {
-                throw new Exception("In the profile test file for " + profileName, e);
-            }
-        }
-
-        public ProfileTestSuite(
-            ProfileMetaData profile,
-            string profileName,
-            IEnumerable<(Expected, Dictionary<string, string> tags)> tests)
-        {
-            _profile = profile;
-            _profileName = profileName;
-            _tests = tests;
-        }
-
-
-        public string ToLua()
-        {
-            var tests = string.Join("\n",
-                _tests.Select((test, i) => ToLua(i, test.Item1, test.tags)));
-            return tests + "\n";
-        }
-
-        private string ToLua(int index, Expected 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);
-            }
-            // function unit_test_profile(profile_function, profile_name, index, expected, tags)
-
-            return $"unit_test_profile(profile_bicycle_{_profileName.FunctionName()}, " +
-                   $"\"{_profileName}\", " +
-                   $"{index}, " +
-                   $"{{access = {expected.Access}, speed = {expected.Speed}, oneway = {expected.Oneway}, weight = {expected.Weight} }}, " +
-                   tags.ToLuaTable() +
-                   ")";
-
-        }
-
-        public void Run()
-        {
-        }
-    }
-}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero1/LuaPrinter.cs b/AspectedRouting/IO/itinero1/LuaPrinter.cs
new file mode 100644
index 0000000..11b2031
--- /dev/null
+++ b/AspectedRouting/IO/itinero1/LuaPrinter.cs
@@ -0,0 +1,92 @@
+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(List<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");
+
+            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 = {" + string.Join(", ", keys) + "}");
+
+            code.AddRange(_code);
+
+            code.Add("\n\n ------------------------------- TESTS -------------------------");
+
+            code.AddRange(_tests);
+
+            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",
+                "",
+                "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/LuaStringExtensions.cs b/AspectedRouting/IO/itinero1/LuaStringExtensions.cs
new file mode 100644
index 0000000..dc4e2b3
--- /dev/null
+++ b/AspectedRouting/IO/itinero1/LuaStringExtensions.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace AspectedRouting.IO.itinero1
+{
+    public static class LuaStringExtensions
+    {
+        public static string ToLuaTable(this Dictionary<string, string> tags)
+        {
+            var contents = tags.Select(kv =>
+            {
+                var (key, value) = kv;
+                var left = "[\"" + key + "\"]";
+
+                if (Regex.IsMatch(key, "^[a-zA-Z][_a-zA-Z-9]*$"))
+                {
+                    left = key;
+                }
+
+                return $"{left} = \"{value}\"";
+            });
+            return "{" + string.Join(", ", contents) + "}";
+        }
+
+        public static string FunctionName(this string s)
+        {
+            return s.Replace("$", "")
+                .Replace("#", "")
+                .Replace(" ", "_")
+                .Replace(".", "_")
+                .Replace("-", "_");
+        }
+    }
+}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero1/Luaprinter.Aspect.cs b/AspectedRouting/IO/itinero1/Luaprinter.Aspect.cs
new file mode 100644
index 0000000..31f72b7
--- /dev/null
+++ b/AspectedRouting/IO/itinero1/Luaprinter.Aspect.cs
@@ -0,0 +1,64 @@
+using System.Collections.Generic;
+using System.Linq;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+
+namespace AspectedRouting.IO.itinero1
+{
+    public partial class LuaPrinter
+    {
+        private readonly HashSet<string> _alreadyAddedFunctions = new HashSet<string>();
+
+        public void AddFunction(AspectMetadata meta)
+        {
+            if (_alreadyAddedFunctions.Contains(meta.Name))
+            {
+                // already added
+                return;
+            }
+
+            _alreadyAddedFunctions.Add(meta.Name);
+
+            var possibleTags = meta.PossibleTags() ?? new Dictionary<string, List<string>>();
+            var numberOfCombinations = 1;
+            numberOfCombinations = possibleTags.Values.Select(lst => 1 + lst.Count).Multiply();
+
+            var usedParams = meta.UsedParameters();
+
+            var funcNameDeclaration = "";
+
+            meta.Visit(e =>
+            {
+                if (e is Function f && f.Name.Equals(Funcs.MemberOf.Name))
+                {
+                    funcNameDeclaration = $"\n    local funcName = \"{meta.Name.FunctionName()}\"";
+                }
+
+                return true;
+            });
+
+            var impl = string.Join("\n",
+                "--[[",
+                meta.Description,
+                "",
+                "Unit: " + meta.Unit,
+                "Created by " + meta.Author,
+                "Originally defined in " + meta.Filepath,
+                "Uses tags: " + string.Join(", ", possibleTags.Keys),
+                "Used parameters: " + string.Join(", ", usedParams),
+                "Number of combintations: " + numberOfCombinations,
+                "Returns values: ",
+                "]]",
+                "function " + meta.Name.FunctionName() + "(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
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero1/Luaprinter.Expressions.cs b/AspectedRouting/IO/itinero1/Luaprinter.Expressions.cs
new file mode 100644
index 0000000..a60e051
--- /dev/null
+++ b/AspectedRouting/IO/itinero1/Luaprinter.Expressions.cs
@@ -0,0 +1,251 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Functions;
+using AspectedRouting.Language.Typ;
+using static AspectedRouting.Language.Deconstruct;
+
+namespace AspectedRouting.IO.itinero1
+{
+    public partial class LuaPrinter
+    {
+        private string ToLua(IExpression bare, string key = "nil")
+        {
+            var collectedMapping = new List<IExpression>();
+            var order = new List<IExpression>();
+
+
+            if (UnApply(
+                UnApply(
+                    IsFunc(Funcs.FirstOf),
+                    Assign(order))
+                , UnApply(
+                    IsFunc(Funcs.StringStringToTags),
+                    Assign(collectedMapping))
+            ).Invoke(bare))
+            {
+                AddDep(Funcs.FirstOf.Name);
+                return "first_match_of(tags, result, \n" +
+                       "        " + ToLua(order.First(), key) + "," +
+                       ("\n" + MappingToLua((Mapping) collectedMapping.First())).Indent().Indent() +
+                       ")";
+            }
+
+            if (UnApply(
+                UnApply(
+                    IsFunc(Funcs.MustMatch),
+                    Assign(order))
+                , UnApply(
+                    IsFunc(Funcs.StringStringToTags),
+                    Assign(collectedMapping))
+            ).Invoke(bare))
+            {
+                AddDep(Funcs.MustMatch.Name);
+                return "must_match(tags, result, \n" +
+                       "        " + ToLua(order.First(), key) + "," +
+                       ("\n" + MappingToLua((Mapping) collectedMapping.First())).Indent().Indent() +
+                       ")";
+            }
+
+            if (UnApply(
+                IsFunc(Funcs.MemberOf),
+                Any
+                ).Invoke(bare))
+            {
+                AddDep("memberOf");
+                return "member_of(funcName, parameters, tags, result)";
+            }
+
+            var collectedList = new List<IExpression>();
+            var func = new List<IExpression>();
+
+
+            if (
+                UnApply(
+                    UnApply(IsFunc(Funcs.Dot), Assign(func)),
+                    UnApply(IsFunc(Funcs.ListDot),
+                        Assign(collectedList))).Invoke(bare))
+            {
+                var exprs = (IEnumerable<IExpression>) ((Constant) collectedList.First()).Evaluate(_context);
+                var luaExprs = new List<string>();
+                var funcName = func.First().ToString().TrimStart('$');
+                AddDep(funcName);
+                foreach (var expr in exprs)
+                {
+                    var c = new List<IExpression>();
+                    if (UnApply(IsFunc(Funcs.Const), Assign(c)).Invoke(expr))
+                    {
+                        luaExprs.Add(ToLua(c.First(), key));
+                        continue;
+                    }
+
+                    if (expr.Types.First() is Curry curry
+                        && curry.ArgType.Equals(Typs.Tags))
+                    {
+                        var lua = ToLua(expr, key);
+                        luaExprs.Add(lua);
+                    }
+                }
+
+                return "\n        " + funcName + "({\n         " + string.Join(",\n         ", luaExprs) +
+                       "\n        })";
+            }
+
+
+            collectedMapping.Clear();
+            var dottedFunction = new List<IExpression>();
+
+
+            dottedFunction.Clear();
+
+            if (UnApply(
+                    UnApply(
+                        IsFunc(Funcs.Dot),
+                        Assign(dottedFunction)
+                    ),
+                    UnApply(
+                        IsFunc(Funcs.StringStringToTags),
+                        Assign(collectedMapping))).Invoke(bare)
+            )
+            {
+                var mapping = (Mapping) collectedMapping.First();
+                var baseFunc = (Function) dottedFunction.First();
+                AddDep(baseFunc.Name);
+                AddDep("table_to_list");
+
+                return baseFunc.Name +
+                       "(table_to_list(tags, result, " +
+                       ("\n" + MappingToLua(mapping)).Indent().Indent() +
+                       "))";
+            }
+
+            // The expression might be a function which still expects a string (the value from the tag) as argument
+            if (!(bare is Mapping) &&
+                bare.Types.First() is Curry curr &&
+                curr.ArgType.Equals(Typs.String))
+            {
+                var applied = new Apply(bare, new Constant(curr.ArgType, ("tags", "\"" + key + "\"")));
+                return ToLua(applied.Optimize(), key);
+            }
+
+
+            // The expression might consist of multiple nested functions
+            var fArgs = bare.DeconstructApply();
+            if (fArgs != null)
+            {
+                var (f, args) = fArgs.Value;
+                var baseFunc = (Function) f;
+                AddDep(baseFunc.Name);
+
+                return baseFunc.Name + "(" + string.Join(", ", args.Select(arg => ToLua(arg, key))) + ")";
+            }
+
+
+            var collected = new List<IExpression>();
+            switch (bare)
+            {
+                case FunctionCall fc:
+                    var called = _context.DefinedFunctions[fc.CalledFunctionName];
+                    if (called.ProfileInternal)
+                    {
+                        return called.Name;
+                    }
+
+                    AddFunction(called);
+                    return $"{fc.CalledFunctionName.FunctionName()}(parameters, tags, result)";
+                case Constant c:
+                    return ConstantToLua(c);
+                case Mapping m:
+                    return MappingToLua(m).Indent();
+                case Function f:
+                    var fName = f.Name.TrimStart('$');
+
+                    if (Funcs.Builtins.ContainsKey(fName))
+                    {
+                        AddDep(f.Name);
+                    }
+                    else
+                    {
+                        var definedFunc = _context.DefinedFunctions[fName];
+                        if (definedFunc.ProfileInternal)
+                        {
+                            return f.Name;
+                        }
+
+                        AddFunction(definedFunc);
+                    }
+
+                    return f.Name;
+                case Apply a when UnApply(IsFunc(Funcs.Const), Assign(collected))
+                    .Invoke(a):
+                    return ToLua(collected.First(), key);
+
+                case Parameter p:
+                    return $"parameters[\"{p.ParamName.FunctionName()}\"]";
+                default:
+                    throw new Exception("Could not convert " + bare + " to a lua expression");
+            }
+        }
+
+        public string MappingToLua(Mapping m)
+        {
+            var contents = m.StringToResultFunctions.Select(kv =>
+                {
+                    var (key, expr) = kv;
+                    var left = "[\"" + key + "\"]";
+
+                    if (Regex.IsMatch(key, "^[a-zA-Z][_a-zA-Z-9]*$"))
+                    {
+                        left = key;
+                    }
+
+                    return left + " = " + ToLua(expr, key);
+                }
+            );
+            return
+                "{\n    " +
+                string.Join(",\n    ", contents) +
+                "\n}";
+        }
+
+        /// <summary>
+        /// Neatly creates a value expression in lua, based on a constant
+        /// </summary>
+        /// <param name="c"></param>
+        /// <returns></returns>
+        private string ConstantToLua(Constant c)
+        {
+            var o = c.Evaluate(_context);
+            switch (o)
+            {
+                case IExpression e:
+                    return ConstantToLua(new Constant(e.Types.First(), e.Evaluate(null)));
+                case int i:
+                    return "" + i;
+                case double d:
+                    return "" + d;
+                case string s:
+                    return '"' + s.Replace("\"", "\\\"") + '"';
+                case ValueTuple<string, string> unpack:
+                    return unpack.Item1 + "[" + unpack.Item2 + "]";
+                case IEnumerable<object> ls:
+                    var t = (c.Types.First() as ListType).InnerType;
+                    return "{" + string.Join(", ", ls.Select(obj =>
+                    {
+                        var objInConstant = new Constant(t, obj);
+                        if (obj is Constant asConstant)
+                        {
+                            objInConstant = asConstant;
+                        }
+
+                        return ConstantToLua(objInConstant);
+                    })) + "}";
+                default:
+                    return o.ToString();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero1/Luaprinter.Profile.cs b/AspectedRouting/IO/itinero1/Luaprinter.Profile.cs
new file mode 100644
index 0000000..4b52e5b
--- /dev/null
+++ b/AspectedRouting/IO/itinero1/Luaprinter.Profile.cs
@@ -0,0 +1,256 @@
+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, _) in memberships)
+            {
+                foreach (var (behaviourName, parameters) in profile.Behaviours)
+                {
+                    var preProcName = "relation_preprocessing_for_" + calledInFunction.FunctionName();
+
+                    func.Add("");
+                    func.Add("");
+                    func.Add("    subresult.attributes_to_keep = {}");
+                    func.Add("    parameters = default_parameters()");
+                    func.Add(ParametersToLua(parameters));
+                    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!
+                    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 = {" + string.Join(", ", 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 = \n        ";
+
+            var weightParts = new List<string>();
+            foreach (var (parameterName, expression) in profile.Priority)
+            {
+                var priorityPart = ToLua(new Parameter(parameterName)) + " * ";
+
+                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);
+                }
+
+                weightParts.Add(priorityPart);
+            }
+
+            impl += string.Join(" + \n        ", weightParts);
+
+            impl += string.Join("\n",
+                "",
+                "",
+                "",
+                "    -- put all the values into the result-table, as needed for itinero",
+                "    result.access = 1",
+                "    result.speed = speed",
+                "    result.factor = priority",
+                "",
+                "    if (oneway == \"both\") then",
+                "        result.oneway = 0",
+                "    elseif (oneway == \"with\") then",
+                "        result.oneway = 1",
+                "    else",
+                "         result.oneway = 2",
+                "    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}\"",
+                    "    function_name = \"profile_" + functionName + "\"",
+                    "    metric = \"custom\""
+                )
+            );
+
+            AddDep("remove_relation_prefix");
+            var impl = string.Join("\n",
+                "",
+                "--[[",
+                description,
+                "]]",
+                "function profile_" + functionName + "(tags, result)",
+                $"    tags = remove_relation_prefix(tags, \"{name.FunctionName()}\")",
+                "    local parameters = default_parameters()",
+                ""
+            );
+
+            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;
+        }
+    }
+}
\ No newline at end of file
diff --git a/AspectedRouting/IO/itinero1/Luaprinter.TestSuites.cs b/AspectedRouting/IO/itinero1/Luaprinter.TestSuites.cs
new file mode 100644
index 0000000..98ac096
--- /dev/null
+++ b/AspectedRouting/IO/itinero1/Luaprinter.TestSuites.cs
@@ -0,0 +1,100 @@
+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, Expected 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(profile_bicycle_{testSuite.BehaviourName.FunctionName()}, " +
+                   $"\"{testSuite.BehaviourName}\", " +
+                   $"{index}, " +
+                   $"{{access = \"{D(expected.Access)}\", speed = {expected.Speed}, oneway = \"{D(expected.Oneway)}\", weight = {expected.Weight} }}, " +
+                   tags.ToLuaTable() +
+                   ")";
+        }
+
+        private string D(string s)
+        {
+            if (string.IsNullOrEmpty(s))
+            {
+                return "0";
+            }
+
+            return s;
+        }
+
+
+        public void AddTestSuite(FunctionTestSuite 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");
+            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/JsonParser.cs b/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs
similarity index 79%
rename from AspectedRouting/IO/JsonParser.cs
rename to AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs
index f4c0eee..5069706 100644
--- a/AspectedRouting/IO/JsonParser.cs
+++ b/AspectedRouting/IO/jsonParser/JsonParser.ParseAspectProfile.cs
@@ -1,512 +1,463 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text.Json;
-using AspectedRouting.Functions;
-using AspectedRouting.Typ;
-using static AspectedRouting.Functions.Funcs;
-using Type = AspectedRouting.Typ.Type;
-
-namespace AspectedRouting.IO
-{
-    public static class JsonParser
-    {
-        public static AspectMetadata AspectFromJson(Context c, string json, string fileName)
-        {
-            try
-            {
-                var doc = JsonDocument.Parse(json);
-                if (doc.RootElement.TryGetProperty("defaults", out _))
-                {
-                    // this is a profile
-                    return null;
-                }
-
-                return doc.RootElement.ParseAspect(fileName, c);
-            }
-            catch (Exception e)
-            {
-                throw new Exception("In the file " + fileName, e);
-            }
-        }
-
-        public static ProfileMetaData ProfileFromJson(Context c, string json, FileInfo f)
-        {
-            try
-            {
-                var doc = JsonDocument.Parse(json);
-                if (!doc.RootElement.TryGetProperty("defaults", out _))
-                {
-                    return null;
-                    // this is an aspect
-                }
-
-                return ParseProfile(doc.RootElement, c, f);
-            }
-            catch (Exception e)
-            {
-                throw new Exception("In the file " + f, e);
-            }
-        }
-
-
-        private static readonly IExpression _mconst =
-            new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.Const);
-
-        private static readonly IExpression _mappingWrapper =
-            new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), StringStringToTags);
-
-        private static IExpression ParseMapping(IEnumerable<JsonProperty> allArgs, Context context)
-        {
-            var keys = new List<string>();
-            var exprs = new List<IExpression>();
-
-            foreach (var prop in allArgs)
-            {
-                if (prop.Name.Equals("#"))
-                {
-                    continue;
-                }
-
-                keys.Add(prop.Name);
-                var argExpr = ParseExpression(prop.Value, context);
-                IExpression mappingWithOptArg;
-                if (argExpr.Types.Count() == 1 && argExpr.Types.First().Equals(Typs.String))
-                {
-                    mappingWithOptArg =
-                        Either(Funcs.Id, Funcs.Eq, argExpr);
-                }
-                else
-                {
-                    mappingWithOptArg = new Apply(_mconst, argExpr);
-                }
-
-                exprs.Add(mappingWithOptArg);
-            }
-
-            try
-            {
-                var simpleMapping = new Mapping(keys, exprs);
-                return new Apply(_mappingWrapper, simpleMapping);
-            }
-            catch (Exception e)
-            {
-                throw new Exception("While constructing a mapping with members " + string.Join(", ", exprs) +
-                                    ": " + e.Message, e);
-            }
-        }
-
-        private static IExpression ParseExpression(this JsonElement e, Context context)
-        {
-            if (e.ValueKind == JsonValueKind.Object)
-            {
-                // Parse an actual function
-                var funcCall = e.EnumerateObject().Where(v => v.Name.StartsWith("$")).ToList();
-                var allArgs = e.EnumerateObject().Where(v => !v.Name.StartsWith("$")).ToList();
-
-                if (funcCall.Count > 2)
-                {
-                    throw new ArgumentException("Multiple calls defined in object " + e);
-                }
-
-                if (funcCall.Count == 1)
-                {
-                    return ParseFunctionCall(context, funcCall, allArgs);
-                }
-
-                // Funccall has no elements: this is a mapping of strings or tags onto a value
-
-                return ParseMapping(allArgs, context);
-            }
-
-            if (e.ValueKind == JsonValueKind.Array)
-            {
-                var exprs = e.EnumerateArray().Select(json =>
-                    Either(Funcs.Id, Funcs.Const, json.ParseExpression(context)));
-                var list = new Constant(exprs);
-                return Either(Funcs.Id, Funcs.ListDot, list);
-            }
-
-            if (e.ValueKind == JsonValueKind.Number)
-            {
-                if (e.TryGetDouble(out var d))
-                {
-                    return new Constant(d);
-                }
-
-                if (e.TryGetInt32(out var i))
-                {
-                    return new Constant(i);
-                }
-            }
-
-            if (e.ValueKind == JsonValueKind.True)
-            {
-                return new Constant(Typs.Bool, "yes");
-            }
-
-            if (e.ValueKind == JsonValueKind.False)
-            {
-                return new Constant(Typs.Bool, "no");
-            }
-
-            if (e.ValueKind == JsonValueKind.String)
-            {
-                var s = e.GetString();
-                if (s.StartsWith("$"))
-                {
-                    var bi = BuiltinByName(s);
-
-                    if (bi != null)
-                    {
-                        return Either(Funcs.Dot, Funcs.Id, bi);
-                    }
-
-                    var definedFunc = context.GetFunction(s);
-                    return Either(Funcs.Dot, Funcs.Id, new FunctionCall(s, definedFunc.Types));
-                }
-
-                if (s.StartsWith("#"))
-                {
-                    // This is a parameter, the type of it is free
-                    return new Parameter(s);
-                }
-
-                return new Constant(s);
-            }
-
-
-            throw new Exception("Could not parse " + e);
-        }
-
-        private static IExpression ParseFunctionCall(Context context, IReadOnlyCollection<JsonProperty> funcCall,
-            IEnumerable<JsonProperty> allArgs)
-        {
-            var funcName = funcCall.First().Name;
-
-            var func = BuiltinByName(funcName);
-
-            // The list where all the arguments are collected
-            var args = new List<IExpression>();
-
-
-            // First argument of the function is the value of this property, e.g.
-            // { "$f": "xxx", "a2":"yyy", "a3":"zzz" }
-            var firstArgument = ParseExpression(funcCall.First().Value, context);
-
-
-            // Cheat for the very special case 'mustMatch'
-            if (func.Equals(Funcs.MustMatch))
-            {
-                // It gets an extra argument injected
-                var neededKeys = firstArgument.PossibleTags().Keys.ToList();
-                var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
-                args.Add(neededKeysArg);
-                args.Add(firstArgument);
-                return func.Apply(args);
-            }
-
-            args.Add(firstArgument);
-
-            var allExprs = allArgs
-                .Where(kv => !kv.NameEquals("#")) // Leave out comments
-                .ToDictionary(kv => kv.Name, kv => kv.Value.ParseExpression(context));
-
-
-            if (allExprs.Count > 1)
-            {
-                if (func.ArgNames == null || func.ArgNames.Count < 2)
-                    throw new ArgumentException("{funcName} does not specify argument names");
-
-                foreach (var argName in func.ArgNames)
-                {
-                    args.Add(allExprs[argName]);
-                }
-            }
-            else if (allExprs.Count == 1)
-            {
-                args.Add(allExprs.Single().Value);
-            }
-
-            return Either(Funcs.Id, Funcs.Dot, func).Apply(args);
-        }
-
-        private static IExpression GetTopLevelExpression(this JsonElement root, Context context)
-        {
-            IExpression mapping = null;
-            if (root.TryGetProperty("value", out var j))
-            {
-                // The expression is placed in the default 'value' location
-                mapping = j.ParseExpression(context);
-            }
-
-
-            // We search for the function call with '$'
-            foreach (var prop in root.EnumerateObject())
-            {
-                if (!prop.Name.StartsWith("$")) continue;
-
-
-                var f = (IExpression) BuiltinByName(prop.Name);
-                if (f == null)
-                {
-                    throw new KeyNotFoundException("The builtin function " + f + " was not found");
-                }
-
-                var fArg = prop.Value.ParseExpression(context);
-
-                if (fArg == null)
-                {
-                    throw new ArgumentException("Could not type expression " + prop);
-                }
-
-
-                if (mapping != null)
-                {
-                    // This is probably a firstOrderedVersion, a default, or some other function that should be applied
-                    return
-                        new Apply(
-                            Either(Funcs.Id, Funcs.Dot, new Apply(f, fArg)), mapping
-                        );
-                }
-
-                // Cheat for the very special case 'mustMatch'
-                if (f.Equals(Funcs.MustMatch))
-                {
-                    // It gets an extra argument injected
-                    var neededKeys = fArg.PossibleTags().Keys.ToList();
-                    var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
-                    f = f.Apply(new[] {neededKeysArg});
-                }
-
-                var appliedDot = new Apply(new Apply(Funcs.Dot, f), fArg);
-                var appliedDirect = new Apply(f, fArg);
-
-                if (!appliedDot.Types.Any())
-                {
-                    // Applied dot doesn't work out, so we return the other one
-                    return appliedDirect;
-                }
-
-                if (!appliedDirect.Types.Any())
-                {
-                    return appliedDot;
-                }
-
-                var eithered = new Apply(new Apply(Funcs.EitherFunc, appliedDot), appliedDirect);
-
-
-                // We apply the builtin function through a dot
-                return eithered;
-            }
-
-
-            throw new Exception(
-                "No top level reducer found. Did you forget the '$' in the reducing function? Did your forget 'value' to add the mapping?");
-        }
-
-        private static IExpression ParseProfileProperty(JsonElement e, Context c, string property)
-        {
-            try
-            {
-                var prop = e.GetProperty(property);
-                return ParseExpression(prop, c)
-                    .Specialize(new Curry(Typs.Tags, new Var("a")))
-                    .Optimize();
-            }
-            catch (Exception exc)
-            {
-                throw new Exception("While parsing the property " + property, exc);
-            }
-        }
-
-        private static Dictionary<string, object> ParseParameters(this JsonElement e)
-        {
-            var ps = new Dictionary<string, object>();
-            foreach (var obj in e.EnumerateObject())
-            {
-                var nm = obj.Name.TrimStart('#');
-                switch (obj.Value.ValueKind)
-                {
-                    case JsonValueKind.String:
-                        ps[nm] = obj.Value.ToString();
-                        break;
-                    case JsonValueKind.Number:
-                        ps[nm] = obj.Value.GetDouble();
-                        break;
-                    case JsonValueKind.True:
-                        ps[nm] = "yes";
-                        break;
-                    case JsonValueKind.False:
-                        ps[nm] = "no";
-                        break;
-                    default:
-                        throw new ArgumentOutOfRangeException();
-                }
-            }
-
-            return ps;
-        }
-
-        public static ProfileMetaData ParseProfile(this JsonElement e, Context context, FileInfo filepath)
-        {
-            var name = e.Get("name");
-            var author = e.TryGet("author");
-            if (filepath != null && !(name + ".json").ToLower().Equals(filepath.Name.ToLower()))
-            {
-                throw new ArgumentException($"Filename does not match the defined name: " +
-                                            $"filename is {filepath.Name}, declared name is {name}");
-            }
-
-            var vehicleTypes = e.GetProperty("vehicletypes").EnumerateArray().Select(
-                el => el.GetString()).ToList();
-            var metadata = e.GetProperty("metadata").EnumerateArray().Select(
-                el => el.GetString()).ToList();
-
-
-            var access = ParseProfileProperty(e, context, "access").Finalize();
-            var oneway = ParseProfileProperty(e, context, "oneway").Finalize();
-            var speed = ParseProfileProperty(e, context, "speed").Finalize();
-
-
-            IExpression TagsApplied(IExpression x)
-            {
-                return new Apply(x, new Constant(new Dictionary<string, string>()));
-            }
-
-            context.AddFunction("speed",
-                new AspectMetadata(TagsApplied(speed), "speed", "The speed of this profile", author, "", filepath.Name,
-                    true));
-            context.AddFunction("access",
-                new AspectMetadata(TagsApplied(access), "access", "The access of this profile", author, "",
-                    filepath.Name,
-                    true));
-            context.AddFunction("oneway",
-                new AspectMetadata(TagsApplied(oneway), "oneway", "The oneway of this profile", author, "",
-                    filepath.Name,
-                    true));
-            context.AddFunction("distance",
-                new AspectMetadata(new Constant(1), "distance", "The distance travelled of this profile", author, "",
-                    filepath.Name,
-                    true));
-
-
-            var weights = new Dictionary<string, IExpression>();
-            var weightProperty = e.GetProperty("weight");
-            foreach (var prop in weightProperty.EnumerateObject())
-            {
-                var parameter = prop.Name.TrimStart('#');
-                var factor = ParseExpression(prop.Value, context).Finalize();
-                weights[parameter] = factor;
-            }
-
-            var profiles = new Dictionary<string, Dictionary<string, object>>();
-
-            foreach (var profile in e.GetProperty("profiles").EnumerateObject())
-            {
-                profiles[profile.Name] = ParseParameters(profile.Value);
-            }
-
-            return new ProfileMetaData(
-                name,
-                e.Get("description"),
-                author,
-                filepath?.DirectoryName ?? "unknown",
-                vehicleTypes,
-                e.GetProperty("defaults").ParseParameters(),
-                profiles,
-                access,
-                oneway,
-                speed,
-                weights,
-                metadata
-            );
-        }
-
-        private static AspectMetadata ParseAspect(this JsonElement e, string filepath, Context context)
-        {
-            var expr = GetTopLevelExpression(e, context);
-
-
-            var targetTypes = new List<Type>();
-            foreach (var t in expr.Types)
-            {
-                var a = Var.Fresh(t);
-                var b = Var.Fresh(new Curry(a, t));
-
-                if (t.Unify(new Curry(Typs.Tags, a)) != null &&
-                    t.Unify(new Curry(Typs.Tags, new Curry(a, b))) == null
-                ) // Second should not  match
-                {
-                    // The target type is 'Tags -> a', where a is NOT a curry
-                    targetTypes.Add(t);
-                }
-            }
-
-            if (targetTypes.Count == 0)
-            {
-                throw new ArgumentException("The top level expression has types:\n" +
-                                            string.Join("\n    ", expr.Types) +
-                                            "\nwhich can not be specialized into a form suiting `tags -> a`\n" + expr);
-            }
-
-            var exprSpec = expr.Specialize(targetTypes);
-            if (expr == null)
-            {
-                throw new Exception("Could not specialize the expression " + expr + " to one of the target types " +
-                                    string.Join(", ", targetTypes));
-            }
-
-            expr = exprSpec.Finalize();
-
-            if (expr.Finalize() == null)
-            {
-                throw new NullReferenceException("The finalized form of expression `" + exprSpec + "` is null");
-            }
-
-            var name = e.Get("name");
-            if (expr.Types.Count() > 1)
-            {
-                throw new ArgumentException("The aspect " + name + " is ambigous, it matches multiple types: " +
-                                            string.Join(", ", expr.Types));
-            }
-
-            if (filepath != null && !(name + ".json").ToLower().Equals(filepath.ToLower()))
-            {
-                throw new ArgumentException($"Filename does not match the defined name: " +
-                                            $"filename is {filepath}, declared name is {name}");
-            }
-
-            return new AspectMetadata(
-                expr,
-                name,
-                e.Get("description"),
-                e.TryGet("author"),
-                e.TryGet("unit"),
-                filepath ?? "unknown"
-            );
-        }
-
-
-        private static string Get(this JsonElement json, string key)
-        {
-            if (json.TryGetProperty(key, out var p))
-            {
-                return p.GetString();
-            }
-
-            throw new ArgumentException($"The obligated property {key} is missing");
-        }
-
-        private static string TryGet(this JsonElement json, string key)
-        {
-            if (json.TryGetProperty(key, out var p))
-            {
-                return p.GetString();
-            }
-
-            return null;
-        }
-    }
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Functions;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
+
+namespace AspectedRouting.IO.jsonParser
+{
+    public static partial class JsonParser
+    {
+        public static AspectMetadata AspectFromJson(Context c, string json, string fileName)
+        {
+            try
+            {
+                var doc = JsonDocument.Parse(json);
+                if (doc.RootElement.TryGetProperty("defaults", out _))
+                {
+                    // this is a profile
+                    return null;
+                }
+
+                return doc.RootElement.ParseAspect(fileName, c);
+            }
+            catch (Exception e)
+            {
+                throw new Exception("In the file " + fileName, e);
+            }
+        }
+
+        public static ProfileMetaData ProfileFromJson(Context c, string json, FileInfo f)
+        {
+            try
+            {
+                var doc = JsonDocument.Parse(json);
+                if (!doc.RootElement.TryGetProperty("defaults", out _))
+                {
+                    return null;
+                    // this is an aspect
+                }
+
+                return ParseProfile(doc.RootElement, c, f);
+            }
+            catch (Exception e)
+            {
+                throw new Exception("In the file " + f, e);
+            }
+        }
+
+        private static ProfileMetaData ParseProfile(this JsonElement e, Context context, FileInfo filepath)
+        {
+            var name = e.Get("name");
+            var author = e.TryGet("author");
+            if (filepath != null && !(name + ".json").ToLower().Equals(filepath.Name.ToLower()))
+            {
+                throw new ArgumentException($"Filename does not match the defined name: " +
+                                            $"filename is {filepath.Name}, declared name is {name}");
+            }
+
+            var vehicleTypes = e.GetProperty("vehicletypes").EnumerateArray().Select(
+                el => el.GetString()).ToList();
+            var metadata = e.GetProperty("metadata").EnumerateArray().Select(
+                el => el.GetString()).ToList();
+
+
+            var access = ParseProfileProperty(e, context, "access").Finalize();
+            var oneway = ParseProfileProperty(e, context, "oneway").Finalize();
+            var speed = ParseProfileProperty(e, context, "speed").Finalize();
+
+
+            IExpression TagsApplied(IExpression x)
+            {
+                return new Apply(x, new Constant(new Dictionary<string, string>()));
+            }
+
+            context.AddFunction("speed",
+                new AspectMetadata(TagsApplied(speed), "speed", "The speed of this profile", author, "", filepath.Name,
+                    true));
+            context.AddFunction("access",
+                new AspectMetadata(TagsApplied(access), "access", "The access of this profile", author, "",
+                    filepath.Name,
+                    true));
+            context.AddFunction("oneway",
+                new AspectMetadata(TagsApplied(oneway), "oneway", "The oneway of this profile", author, "",
+                    filepath.Name,
+                    true));
+            context.AddFunction("distance",
+                new AspectMetadata(new Constant(1), "distance", "The distance travelled of this profile", author, "",
+                    filepath.Name,
+                    true));
+
+
+            var weights = new Dictionary<string, IExpression>();
+            var weightProperty = e.GetProperty("priority");
+            foreach (var prop in weightProperty.EnumerateObject())
+            {
+                var parameter = prop.Name.TrimStart('#');
+                var factor = ParseExpression(prop.Value, context).Finalize();
+                weights[parameter] = factor;
+            }
+
+            var profiles = new Dictionary<string, Dictionary<string, IExpression>>();
+
+            foreach (var profile in e.GetProperty("behaviours").EnumerateObject())
+            {
+                profiles[profile.Name] = ParseParameters(profile.Value);
+            }
+
+            return new ProfileMetaData(
+                name,
+                e.Get("description"),
+                author,
+                filepath?.DirectoryName ?? "unknown",
+                vehicleTypes,
+                e.GetProperty("defaults").ParseParameters(),
+                profiles,
+                access,
+                oneway,
+                speed,
+                weights,
+                metadata
+            );
+        }
+
+        private static readonly IExpression _mconst =
+            new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.Const);
+
+        private static readonly IExpression _mappingWrapper =
+            new Apply(new Apply(Funcs.EitherFunc, Funcs.Id), Funcs.StringStringToTags);
+
+        private static IExpression ParseMapping(IEnumerable<JsonProperty> allArgs, Context context)
+        {
+            var keys = new List<string>();
+            var exprs = new List<IExpression>();
+
+            foreach (var prop in allArgs)
+            {
+                if (prop.Name.Equals("#"))
+                {
+                    continue;
+                }
+
+                keys.Add(prop.Name);
+                var argExpr = ParseExpression(prop.Value, context);
+                IExpression mappingWithOptArg;
+                if (argExpr.Types.Count() == 1 && argExpr.Types.First().Equals(Typs.String))
+                {
+                    mappingWithOptArg =
+                        Funcs.Either(Funcs.Id, Funcs.Eq, argExpr);
+                }
+                else
+                {
+                    mappingWithOptArg = new Apply(_mconst, argExpr);
+                }
+
+                exprs.Add(mappingWithOptArg);
+            }
+
+            try
+            {
+                var simpleMapping = new Mapping(keys, exprs);
+                return new Apply(_mappingWrapper, simpleMapping);
+            }
+            catch (Exception e)
+            {
+                throw new Exception("While constructing a mapping with members " + string.Join(", ", exprs) +
+                                    ": " + e.Message, e);
+            }
+        }
+
+        private static IExpression ParseExpression(this JsonElement e, Context context)
+        {
+            if (e.ValueKind == JsonValueKind.Object)
+            {
+                // Parse an actual function
+                var funcCall = e.EnumerateObject().Where(v => v.Name.StartsWith("$")).ToList();
+                var allArgs = e.EnumerateObject().Where(v => !v.Name.StartsWith("$")).ToList();
+
+                if (funcCall.Count > 2)
+                {
+                    throw new ArgumentException("Multiple calls defined in object " + e);
+                }
+
+                if (funcCall.Count == 1)
+                {
+                    return ParseFunctionCall(context, funcCall, allArgs);
+                }
+
+                // Funccall has no elements: this is a mapping of strings or tags onto a value
+
+                return ParseMapping(allArgs, context);
+            }
+
+            if (e.ValueKind == JsonValueKind.Array)
+            {
+                var exprs = e.EnumerateArray().Select(json =>
+                    Funcs.Either(Funcs.Id, Funcs.Const, json.ParseExpression(context)));
+                var list = new Constant(exprs);
+                return Funcs.Either(Funcs.Id, Funcs.ListDot, list);
+            }
+
+            if (e.ValueKind == JsonValueKind.Number)
+            {
+                if (e.TryGetDouble(out var d))
+                {
+                    return new Constant(d);
+                }
+
+                if (e.TryGetInt32(out var i))
+                {
+                    return new Constant(i);
+                }
+            }
+
+            if (e.ValueKind == JsonValueKind.True)
+            {
+                return new Constant(Typs.Bool, "yes");
+            }
+
+            if (e.ValueKind == JsonValueKind.False)
+            {
+                return new Constant(Typs.Bool, "no");
+            }
+
+            if (e.ValueKind == JsonValueKind.String)
+            {
+                var s = e.GetString();
+                if (s.StartsWith("$"))
+                {
+                    var bi = Funcs.BuiltinByName(s);
+
+                    if (bi != null)
+                    {
+                        return Funcs.Either(Funcs.Dot, Funcs.Id, bi);
+                    }
+
+                    var definedFunc = context.GetFunction(s);
+                    return Funcs.Either(Funcs.Dot, Funcs.Id, new FunctionCall(s, definedFunc.Types));
+                }
+
+                if (s.StartsWith("#"))
+                {
+                    // This is a parameter, the type of it is free
+                    return new Parameter(s);
+                }
+
+                return new Constant(s);
+            }
+
+
+            throw new Exception("Could not parse " + e);
+        }
+
+        private static IExpression ParseFunctionCall(Context context, IReadOnlyCollection<JsonProperty> funcCall,
+            IEnumerable<JsonProperty> allArgs)
+        {
+            var funcName = funcCall.First().Name;
+
+            var func = Funcs.BuiltinByName(funcName);
+
+            if (func == null)
+            {
+                throw new ArgumentException($"The function with name {funcName} is not found");
+            }
+
+            // The list where all the arguments are collected
+            var args = new List<IExpression>();
+
+
+            // First argument of the function is the value of this property, e.g.
+            // { "$f": "xxx", "a2":"yyy", "a3":"zzz" }
+            var firstArgument = ParseExpression(funcCall.First().Value, context);
+
+
+            // Cheat for the very special case 'mustMatch'
+            if (func.Equals(Funcs.MustMatch))
+            {
+                // It gets an extra argument injected
+                var neededKeys = firstArgument.PossibleTags().Keys.ToList();
+                var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
+                args.Add(neededKeysArg);
+                args.Add(firstArgument);
+                return func.Apply(args);
+            }
+
+            args.Add(firstArgument);
+
+            var allExprs = allArgs
+                .Where(kv => !kv.NameEquals("#")) // Leave out comments
+                .ToDictionary(kv => kv.Name, kv => kv.Value.ParseExpression(context));
+
+
+            if (allExprs.Count > 1)
+            {
+                if (func.ArgNames == null || func.ArgNames.Count < 2)
+                    throw new ArgumentException("{funcName} does not specify argument names");
+
+                foreach (var argName in func.ArgNames)
+                {
+                    args.Add(allExprs[argName]);
+                }
+            }
+            else if (allExprs.Count == 1)
+            {
+                args.Add(allExprs.Single().Value);
+            }
+
+            return Funcs.Either(Funcs.Id, Funcs.Dot, func).Apply(args);
+        }
+
+
+        private static IExpression GetTopLevelExpression(this JsonElement root, Context context)
+        {
+            IExpression mapping = null;
+            if (root.TryGetProperty("value", out var j))
+            {
+                // The expression is placed in the default 'value' location
+                mapping = j.ParseExpression(context);
+            }
+
+
+            // We search for the function call with '$'
+            foreach (var prop in root.EnumerateObject())
+            {
+                if (!prop.Name.StartsWith("$")) continue;
+
+
+                var f = (IExpression) Funcs.BuiltinByName(prop.Name);
+                if (f == null)
+                {
+                    throw new KeyNotFoundException($"The builtin function {prop.Name} was not found");
+                }
+
+                var fArg = prop.Value.ParseExpression(context);
+
+                if (fArg == null)
+                {
+                    throw new ArgumentException("Could not type expression " + prop);
+                }
+
+
+                if (mapping != null)
+                {
+                    // This is probably a firstOrderedVersion, a default, or some other function that should be applied
+                    return
+                        new Apply(
+                            Funcs.Either(Funcs.Id, Funcs.Dot, new Apply(f, fArg)), mapping
+                        );
+                }
+
+                // Cheat for the very special case 'mustMatch'
+                if (f.Equals(Funcs.MustMatch))
+                {
+                    // It gets an extra argument injected
+                    var neededKeys = fArg.PossibleTags().Keys.ToList();
+                    var neededKeysArg = new Constant(new ListType(Typs.String), neededKeys);
+                    f = f.Apply(new[] {neededKeysArg});
+                }
+
+                var appliedDot = new Apply(new Apply(Funcs.Dot, f), fArg);
+                var appliedDirect = new Apply(f, fArg);
+
+                if (!appliedDot.Types.Any())
+                {
+                    // Applied dot doesn't work out, so we return the other one
+                    return appliedDirect;
+                }
+
+                if (!appliedDirect.Types.Any())
+                {
+                    return appliedDot;
+                }
+
+                var eithered = new Apply(new Apply(Funcs.EitherFunc, appliedDot), appliedDirect);
+
+
+                // We apply the builtin function through a dot
+                return eithered;
+            }
+
+
+            throw new Exception(
+                "No top level reducer found. Did you forget the '$' in the reducing function? Did your forget 'value' to add the mapping?");
+        }
+
+        private static AspectMetadata ParseAspect(this JsonElement e, string filepath, Context context)
+        {
+            var expr = GetTopLevelExpression(e, context);
+
+
+            var targetTypes = new List<Type>();
+            foreach (var t in expr.Types)
+            {
+                var a = Var.Fresh(t);
+                var b = Var.Fresh(new Curry(a, t));
+
+                if (t.Unify(new Curry(Typs.Tags, a)) != null &&
+                    t.Unify(new Curry(Typs.Tags, new Curry(a, b))) == null
+                ) // Second should not  match
+                {
+                    // The target type is 'Tags -> a', where a is NOT a curry
+                    targetTypes.Add(t);
+                }
+            }
+
+            if (targetTypes.Count == 0)
+            {
+                throw new ArgumentException("The top level expression has types:\n" +
+                                            string.Join("\n    ", expr.Types) +
+                                            "\nwhich can not be specialized into a form suiting `tags -> a`\n" + expr);
+            }
+
+            var exprSpec = expr.Specialize(targetTypes);
+            if (expr == null)
+            {
+                throw new Exception("Could not specialize the expression " + expr + " to one of the target types " +
+                                    string.Join(", ", targetTypes));
+            }
+
+            expr = exprSpec.Finalize();
+
+            if (expr.Finalize() == null)
+            {
+                throw new NullReferenceException("The finalized form of expression `" + exprSpec + "` is null");
+            }
+
+            var name = e.Get("name");
+            if (expr.Types.Count() > 1)
+            {
+                throw new ArgumentException("The aspect " + name + " is ambigous, it matches multiple types: " +
+                                            string.Join(", ", expr.Types));
+            }
+
+            if (filepath != null && !(name + ".json").ToLower().Equals(filepath.ToLower()))
+            {
+                throw new ArgumentException($"Filename does not match the defined name: " +
+                                            $"filename is {filepath}, declared name is {name}");
+            }
+           
+            var keys = (IEnumerable<string>) expr.PossibleTags()?.Keys ?? new List<string>();
+            foreach (var key in keys)
+            {
+                if (!key.Trim().Equals(key))
+                {
+                    Console.WriteLine($"Warning: a key can be trimmed: '{key}'");
+                }
+            }
+
+            return new AspectMetadata(
+                expr,
+                name,
+                e.Get("description"),
+                e.TryGet("author"),
+                e.TryGet("unit"),
+                filepath ?? "unknown"
+            );
+        }
+    }
 }
\ No newline at end of file
diff --git a/AspectedRouting/IO/jsonParser/JsonParser.cs b/AspectedRouting/IO/jsonParser/JsonParser.cs
new file mode 100644
index 0000000..2d03d11
--- /dev/null
+++ b/AspectedRouting/IO/jsonParser/JsonParser.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Functions;
+using AspectedRouting.Language.Typ;
+
+namespace AspectedRouting.IO.jsonParser
+{
+    public static partial class JsonParser
+    {
+        private static IExpression ParseProfileProperty(JsonElement e, Context c, string property)
+        {
+            try
+            {
+                var prop = e.GetProperty(property);
+                return ParseExpression(prop, c)
+                    .Specialize(new Curry(Typs.Tags, new Var("a")))
+                    .Optimize();
+            }
+            catch (Exception exc)
+            {
+                throw new Exception("While parsing the property " + property, exc);
+            }
+        }
+
+        private static Dictionary<string, IExpression> ParseParameters(this JsonElement e)
+        {
+            var ps = new Dictionary<string, IExpression>();
+            foreach (var obj in e.EnumerateObject())
+            {
+                var nm = obj.Name.TrimStart('#');
+                switch (obj.Value.ValueKind)
+                {
+                    case JsonValueKind.String:
+                        var v = obj.Value.ToString();
+                        if (v.Equals("yes") || v.Equals("no"))
+                        {
+                            ps[nm] = new Constant(Typs.Bool, v);
+                        }
+                        else
+                        {
+                            ps[nm] = new Constant(v);
+                        }
+
+                        break;
+                    case JsonValueKind.Number:
+                        ps[nm] = new Constant(obj.Value.GetDouble());
+                        break;
+                    case JsonValueKind.True:
+                        ps[nm] = new Constant(Typs.Bool, "yes");
+                        break;
+                    case JsonValueKind.False:
+                        ps[nm] = new Constant(Typs.Bool, "yes");
+                        break;
+                    case JsonValueKind.Array:
+                        var list = obj.Value.EnumerateArray().Select(e => e.ToString()).ToList();
+                        ps[nm] = new Constant(new ListType(Typs.String),list);
+                        break;
+                    default:
+                        throw new ArgumentException(
+                            "Parameters are not allowed to be complex expressions, they should be simple values. " +
+                            "Simplify the value for parameter " + obj.Name);
+                }
+            }
+
+            return ps;
+        }
+
+      
+
+
+        private static string Get(this JsonElement json, string key)
+        {
+            if (json.TryGetProperty(key, out var p))
+            {
+                return p.GetString();
+            }
+
+            throw new ArgumentException($"The obligated property {key} is missing");
+        }
+
+        private static string TryGet(this JsonElement json, string key)
+        {
+            if (json.TryGetProperty(key, out var p))
+            {
+                return p.GetString();
+            }
+
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/AspectedRouting/IO/lua/const.lua b/AspectedRouting/IO/lua/const.lua
index e69de29..0c9c7fc 100644
--- a/AspectedRouting/IO/lua/const.lua
+++ b/AspectedRouting/IO/lua/const.lua
@@ -0,0 +1,3 @@
+function const(a, b)
+    return a
+end
\ No newline at end of file
diff --git a/AspectedRouting/IO/lua/containedIn.lua b/AspectedRouting/IO/lua/containedIn.lua
new file mode 100644
index 0000000..d46a0c4
--- /dev/null
+++ b/AspectedRouting/IO/lua/containedIn.lua
@@ -0,0 +1,9 @@
+function containedIn(list, a)
+    for _, value in ipairs(list) do
+        if (value == a) then
+            return true
+        end
+    end
+
+    return false;
+end
\ No newline at end of file
diff --git a/AspectedRouting/IO/lua/double_compare.lua b/AspectedRouting/IO/lua/double_compare.lua
index ea6a4c8..348a75f 100644
--- a/AspectedRouting/IO/lua/double_compare.lua
+++ b/AspectedRouting/IO/lua/double_compare.lua
@@ -1,4 +1,8 @@
 function double_compare(a, b)
+    if (b == nil) then
+        return false
+    end
+    
     if (type(a) ~= "number") then
         a = parse(a)
     end
@@ -6,5 +10,9 @@ function double_compare(a, b)
     if(type(b) ~= "number") then
         b = parse(b)
     end
-    return math.abs(a - b) > 0.001
+    if (a == b) then
+        return true
+    end
+
+    return math.abs(a - b) < 0.0001
 end
\ No newline at end of file
diff --git a/AspectedRouting/IO/lua/memberOf.lua b/AspectedRouting/IO/lua/memberOf.lua
index 2f40f9c..0c9bbf0 100644
--- a/AspectedRouting/IO/lua/memberOf.lua
+++ b/AspectedRouting/IO/lua/memberOf.lua
@@ -1,3 +1,10 @@
-function member_of()
-    ???
+function member_of(calledIn, parameters, tags, result)
+    local k = "_relation:" .. calledIn
+    -- This tag is conventiently setup by all the preprocessors, which take the parameters into account
+    local doesMatch = tags[k]
+    if (doesMatch == "yes") then
+        result.attributes_to_keep[k] = "yes"
+        return true
+    end
+    return false
 end
\ No newline at end of file
diff --git a/AspectedRouting/IO/lua/mustMatch.lua b/AspectedRouting/IO/lua/mustMatch.lua
index 55313f3..dfc2886 100644
--- a/AspectedRouting/IO/lua/mustMatch.lua
+++ b/AspectedRouting/IO/lua/mustMatch.lua
@@ -1,5 +1,4 @@
 function must_match(tags, result, needed_keys, table)
-    local result_list = {}
     for _, key in ipairs(needed_keys) do
         local v = tags[key]
         if (v == nil) then
@@ -9,17 +8,30 @@ function must_match(tags, result, needed_keys, table)
         local mapping = table[key]
         if (type(mapping) == "table") then
             local resultValue = mapping[v]
-            if (v == nil or v == false) then
+            if (resultValue == nil or
+                    resultValue == false or
+                    resultValue == "no" or
+                    resultValue == "false") then
                 return false
             end
-            if (v == "no" or v == "false") then
+        elseif (type(mapping) == "string") then
+            local bool = mapping
+            if (bool == "yes" or bool == "1") then
+                return true
+            elseif (bool == "no" or bool == "0") then
                 return false
             end
-
-            result.attributes_to_keep[key] = v
+            error("MustMatch got a string value it can't handle: " .. bool)
         else
-            error("The mapping is not a table. This is not supported")
+            error("The mapping is not a table. This is not supported. We got " .. 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]
+            result.attributes_to_keep[key] = v
+        end
+
     return true;
 end
\ No newline at end of file
diff --git a/AspectedRouting/IO/lua/notEq.lua b/AspectedRouting/IO/lua/notEq.lua
index e27fe70..85d8d4a 100644
--- a/AspectedRouting/IO/lua/notEq.lua
+++ b/AspectedRouting/IO/lua/notEq.lua
@@ -1,4 +1,8 @@
 function notEq(a, b)
+    if (b == nil) then
+        b = "yes"
+    end
+    
     if (a ~= b) then
         return "yes"
     else
diff --git a/AspectedRouting/IO/lua/remove_relation_prefix.lua b/AspectedRouting/IO/lua/remove_relation_prefix.lua
new file mode 100644
index 0000000..fe62490
--- /dev/null
+++ b/AspectedRouting/IO/lua/remove_relation_prefix.lua
@@ -0,0 +1,20 @@
+function string.start(strt, s)
+    return string.sub(s, 1, string.len(strt)) == strt
+end
+
+
+-- every key starting with "_relation:<name>:XXX" is rewritten to "_relation:XXX"
+function remove_relation_prefix(tags, name)
+
+    local new_tags = {}
+    for k, v in pairs(tags) do
+        local prefix = "_relation:" .. name;
+        if (string.start(prefix, k)) then
+            local new_key = "_relation:" .. string.sub(k, string.len(prefix))
+            new_tags[new_key] = v
+        else
+            new_tags[k] = v
+        end
+    end
+    return new_tags
+end
\ No newline at end of file
diff --git a/AspectedRouting/IO/lua/unitTestProfile.lua b/AspectedRouting/IO/lua/unitTestProfile.lua
index b267f37..5c81355 100644
--- a/AspectedRouting/IO/lua/unitTestProfile.lua
+++ b/AspectedRouting/IO/lua/unitTestProfile.lua
@@ -3,36 +3,57 @@ failed_profile_tests = false
 expected should be a table containing 'access', 'speed' and 'weight'
 ]]
 function unit_test_profile(profile_function, profile_name, index, expected, tags)
-    result = {attributes_to_keep = {}}
+    local result = { attributes_to_keep = {} }
+    local profile_failed = false
     profile_function(tags, result)
 
-    if (result.access ~= expected.access) then
+    local accessCorrect = (result.access == 0 and expected.access == "no") 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 (result.access == 0) then
+    if (expected.access == "no") then
         -- we cannot access this road, the other results are irrelevant
+        if (profile_failed) then
+            print("The used tags for test " .. tostring(index) .. " are:")
+            debug_table(tags)
+        end
         return
     end
 
-    if (double_compare(result.speed, expected.speed)) then
+    if (not double_compare(result.speed, expected.speed)) then
         print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".speed: expected " .. expected.speed .. " but got " .. result.speed)
         failed_profile_tests = true
+        profile_failed = true
     end
 
-    if (double_compare(result.oneway, expected.oneway)) then
-        print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but got " .. result.oneway)
-        failed_profile_tests = true
+
+    local actualOneway = result.oneway;
+    if (result.oneway == 0) then
+        actualOneway = "both"
+    elseif (result.oneway == 1) then
+        actualOneway = "with"
+    elseif (result.oneway == 2) then
+        actualOneway = "against"
     end
 
-    if (double_compare(result.oneway, expected.oneway)) then
-        print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but got " .. result.oneway)
+    if (expected.oneway ~= actualOneway) then
+        print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but got " .. actualOneway)
         failed_profile_tests = true
+        profile_failed = true
     end
 
-    if (double_compare(inv(result.factor), 0.033333)) then
-        print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".factor: expected " .. expected.weight .. " but got " .. inv(result.factor))
+
+    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)
         failed_profile_tests = true
+        profile_failed = true
+    end
+
+    if (profile_failed == true) then
+        print("The used tags for test " .. tostring(index) .. " are:")
+        debug_table(tags)
     end
 end
\ No newline at end of file
diff --git a/AspectedRouting/IO/md/helpText.md b/AspectedRouting/IO/md/helpText.md
index dda917b..fa7dc48 100644
--- a/AspectedRouting/IO/md/helpText.md
+++ b/AspectedRouting/IO/md/helpText.md
@@ -17,6 +17,7 @@
 - parse
 - to_string
 - concat
+- containedIn
 - min
 - max
 - sum
@@ -40,7 +41,7 @@
 #### eq
 
 Argument name |  |  |  
---------------| - | - | - 
+-------------- | - | - | - 
 **a** | $a	 | $a	 | 
 **b** | $a	 | $a	 | 
 _return type_ | bool	 | string	 | 
@@ -66,14 +67,14 @@ end
 
 #### notEq
 
-Argument name |  |  |  
---------------| - | - | - 
-**a** | $a	 | $a	 | 
-**b** | $a	 | $a	 | 
-_return type_ | bool	 | string	 | 
+Argument name |  |  |  |  
+-------------- | - | - | - | - 
+**a** | $a	 | $a	 | bool	 | 
+**b** | $a	 | $a	 | _none_	 | 
+_return type_ | bool	 | string	 | bool	 | 
 
 
-Returns 'yes' if the two passed in values are _not_ the same
+OVerloaded function, either boolean not or returns 'yes' if the two passed in values are _not_ the same;
 
 
 
@@ -81,6 +82,10 @@ Lua implementation:
 
 ````lua
 function notEq(a, b)
+    if (b == nil) then
+        b = "yes"
+    end
+    
     if (a ~= b) then
         return "yes"
     else
@@ -92,14 +97,14 @@ end
 
 #### not
 
-Argument name |  |  |  
---------------| - | - | - 
-**a** | $a	 | $a	 | 
-**b** | $a	 | $a	 | 
-_return type_ | bool	 | string	 | 
+Argument name |  |  |  |  
+-------------- | - | - | - | - 
+**a** | $a	 | $a	 | bool	 | 
+**b** | $a	 | $a	 | _none_	 | 
+_return type_ | bool	 | string	 | bool	 | 
 
 
-Returns 'yes' if the two passed in values are _not_ the same
+OVerloaded function, either boolean not or returns 'yes' if the two passed in values are _not_ the same;
 
 
 
@@ -107,6 +112,10 @@ Lua implementation:
 
 ````lua
 function notEq(a, b)
+    if (b == nil) then
+        b = "yes"
+    end
+    
     if (a ~= b) then
         return "yes"
     else
@@ -119,7 +128,7 @@ end
 #### inv
 
 Argument name |  |  |  
---------------| - | - | - 
+-------------- | - | - | - 
 **d** | pdouble	 | double	 | 
 _return type_ | pdouble	 | double	 | 
 
@@ -140,7 +149,7 @@ end
 #### default
 
 Argument name |  |  
---------------| - | - 
+-------------- | - | - 
 **defaultValue** | $a	 | 
 **f** | $b -> $a	 | 
 _return type_ | $b -> $a	 | 
@@ -164,10 +173,10 @@ end
 
 #### parse
 
-Argument name |  |  
---------------| - | - 
-**s** | string	 | 
-_return type_ | double	 | 
+Argument name |  |  |  
+-------------- | - | - | - 
+**s** | string	 | string	 | 
+_return type_ | double	 | pdouble	 | 
 
 
 Parses a string into a numerical value
@@ -210,7 +219,7 @@ end
 #### to_string
 
 Argument name |  |  
---------------| - | - 
+-------------- | - | - 
 **obj** | $a	 | 
 _return type_ | string	 | 
 
@@ -231,7 +240,7 @@ end
 #### concat
 
 Argument name |  |  
---------------| - | - 
+-------------- | - | - 
 **a** | string	 | 
 **b** | string	 | 
 _return type_ | string	 | 
@@ -250,10 +259,38 @@ end
 ````
 
 
+#### containedIn
+
+Argument name |  |  
+-------------- | - | - 
+**list** | list ($a)	 | 
+**a** | $a	 | 
+_return type_ | bool	 | 
+
+
+Given a list of values, checks if the argument is contained in the list.
+
+
+
+Lua implementation:
+
+````lua
+function containedIn(list, a)
+    for _, value in ipairs(list) do
+        if (value == a) then
+            return true
+        end
+    end
+
+    return false;
+end
+````
+
+
 #### min
 
 Argument name |  |  |  |  |  |  
---------------| - | - | - | - | - | - 
+-------------- | - | - | - | - | - | - 
 **list** | list (nat)	 | list (int)	 | list (pdouble)	 | list (double)	 | list (bool)	 | 
 _return type_ | nat	 | int	 | pdouble	 | double	 | bool	 | 
 
@@ -283,7 +320,7 @@ end
 #### max
 
 Argument name |  |  |  |  |  |  
---------------| - | - | - | - | - | - 
+-------------- | - | - | - | - | - | - 
 **list** | list (nat)	 | list (int)	 | list (pdouble)	 | list (double)	 | list (bool)	 | 
 _return type_ | nat	 | int	 | pdouble	 | double	 | bool	 | 
 
@@ -313,7 +350,7 @@ end
 #### sum
 
 Argument name |  |  |  |  |  |  
---------------| - | - | - | - | - | - 
+-------------- | - | - | - | - | - | - 
 **list** | list (nat)	 | list (int)	 | list (pdouble)	 | list (double)	 | list (bool)	 | 
 _return type_ | nat	 | int	 | pdouble	 | double	 | int	 | 
 
@@ -341,7 +378,7 @@ end
 #### multiply
 
 Argument name |  |  |  |  |  |  
---------------| - | - | - | - | - | - 
+-------------- | - | - | - | - | - | - 
 **list** | list (nat)	 | list (int)	 | list (pdouble)	 | list (double)	 | list (bool)	 | 
 _return type_ | nat	 | int	 | pdouble	 | double	 | bool	 | 
 
@@ -366,7 +403,7 @@ end
 #### firstMatchOf
 
 Argument name |  |  
---------------| - | - 
+-------------- | - | - 
 **s** | list (string)	 | 
 _return type_ | (tags -> list ($a)) -> tags -> $a	 | 
 
@@ -404,7 +441,7 @@ end
 #### mustMatch
 
 Argument name |  |  
---------------| - | - 
+-------------- | - | - 
 **neededKeys (filled in by parser)** | list (string)	 | 
 **f** | tags -> list (bool)	 | 
 _return type_ | tags -> bool	 | 
@@ -420,7 +457,6 @@ Lua implementation:
 
 ````lua
 function must_match(tags, result, needed_keys, table)
-    local result_list = {}
     for _, key in ipairs(needed_keys) do
         local v = tags[key]
         if (v == nil) then
@@ -430,18 +466,31 @@ function must_match(tags, result, needed_keys, table)
         local mapping = table[key]
         if (type(mapping) == "table") then
             local resultValue = mapping[v]
-            if (v == nil or v == false) then
+            if (resultValue == nil or
+                    resultValue == false or
+                    resultValue == "no" or
+                    resultValue == "false") then
                 return false
             end
-            if (v == "no" or v == "false") then
+        elseif (type(mapping) == "string") then
+            local bool = mapping
+            if (bool == "yes" or bool == "1") then
+                return true
+            elseif (bool == "no" or bool == "0") then
                 return false
             end
-
-            result.attributes_to_keep[key] = v
+            error("MustMatch got a string value it can't handle: " .. bool)
         else
-            error("The mapping is not a table. This is not supported")
+            error("The mapping is not a table. This is not supported. We got " .. 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]
+            result.attributes_to_keep[key] = v
+        end
+
     return true;
 end
 ````
@@ -449,22 +498,41 @@ end
 
 #### memberOf
 
-- (tags -> $a) -> tags -> list ($a)
+Argument name |  |  
+-------------- | - | - 
+**f** | tags -> bool	 | 
+**tags** | tags	 | 
+_return type_ | bool	 | 
 
-This function uses memberships of relations to calculate values.
 
-Consider all the relations the scrutinized way is part of.The enclosed function is executed for every single relation which is part of it, generating a list of results.This list of results is in turn returned by 'memberOf'
-In itinero 1/lua, this is implemented by converting the matching relations and by adding the tags of the relations to the dictionary (or table) with the highway tags.The prefix is '_relation:n:key=value', where 'n' is a value between 0 and the number of matching relations (implying that all of these numbers are scanned).The matching relations can be extracted by the compiler for the preprocessing.
+This function returns true, if the way is member of a relation matching the specified function.
 
-For testing, the relation can be emulated by using e.g. '_relation:0:key=value'
+In order to use this for itinero 1.0, the membership _must_ be the top level expression.
+
+Conceptually, when the aspect is executed for a way, every relation will be used as argument in the subfunction `f`
+If this subfunction returns 'true', the entire aspect will return true.
+
+In the lua implementation for itinero 1.0, this is implemented slightly different: a flag `_relation:<aspect_name>="yes"` will be set if the aspect matches on every way for where this aspect matches.
+However, this plays poorly with parameters (e.g.: what if we want to cycle over a highway which is part of a certain cycling network with a certain `#network_name`?) Luckily, parameters can only be simple values. To work around this problem, an extra tag is introduced for _every single profile_:`_relation:<profile_name>:<aspect_name>=yes'. The subfunction is thus executed `countOr(relations) * countOf(profiles)` time, yielding `countOf(profiles)` tags. The profile function then picks the tags for himself and strips the `<profile_name>:` away from the key.
+
+
+
+In the test.csv, one can simply use `_relation:<aspect_name>=yes` to mimic relations in your tests
 
 
 
 Lua implementation:
 
 ````lua
-function member_of()
-    ???
+function member_of(calledIn, parameters, tags, result)
+    local k = "_relation:" .. calledIn
+    -- This tag is conventiently setup by all the preprocessors, which take the parameters into account
+    local doesMatch = tags[k]
+    if (doesMatch == "yes") then
+        result.attributes_to_keep[k] = "yes"
+        return true
+    end
+    return false
 end
 ````
 
@@ -472,7 +540,7 @@ end
 #### if_then_else
 
 Argument name |  |  |  
---------------| - | - | - 
+-------------- | - | - | - 
 **condition** | bool	 | bool	 | 
 **then** | $a	 | $a	 | 
 **else** | $a	 | _none_	 | 
@@ -499,7 +567,7 @@ end
 #### if
 
 Argument name |  |  |  
---------------| - | - | - 
+-------------- | - | - | - 
 **condition** | bool	 | bool	 | 
 **then** | $a	 | $a	 | 
 **else** | $a	 | _none_	 | 
@@ -526,7 +594,7 @@ end
 #### id
 
 Argument name |  |  
---------------| - | - 
+-------------- | - | - 
 **a** | $a	 | 
 _return type_ | $a	 | 
 
@@ -547,7 +615,7 @@ end
 #### const
 
 Argument name |  |  
---------------| - | - 
+-------------- | - | - 
 **a** | $a	 | 
 **b** | $b	 | 
 _return type_ | $a	 | 
@@ -560,14 +628,16 @@ Small utility function, which takes two arguments `a` and `b` and returns `a`. U
 Lua implementation:
 
 ````lua
-
+function const(a, b)
+    return a
+end
 ````
 
 
 #### constRight
 
 Argument name |  |  
---------------| - | - 
+-------------- | - | - 
 **a** | $a	 | 
 **b** | $b	 | 
 _return type_ | $b	 | 
@@ -587,11 +657,11 @@ Lua implementation:
 #### dot
 
 Argument name |  |  
---------------| - | - 
-**f** | $gType -> $arg	 | 
-**g** | $fType -> $gType	 | 
-**a** | $fType	 | 
-_return type_ | $arg	 | 
+-------------- | - | - 
+**f** | $b -> $c	 | 
+**g** | $a -> $b	 | 
+**a** | $a	 | 
+_return type_ | $c	 | 
 
 
 Higher order function: converts `f (g a)` into `(dot f g) a`. In other words, this fuses `f` and `g` in a new function, which allows the argument to be lifted out of the expression 
@@ -608,7 +678,7 @@ Lua implementation:
 #### listDot
 
 Argument name |  |  
---------------| - | - 
+-------------- | - | - 
 **list** | list ($a -> $b)	 | 
 **a** | $a	 | 
 _return type_ | list ($b)	 | 
@@ -628,10 +698,19 @@ Lua implementation:
 
 #### eitherFunc
 
-- ($a -> $b) -> ($c -> $d) -> $a -> $b
-- ($a -> $b) -> ($c -> $d) -> $c -> $d
+Argument name |  |  |  
+-------------- | - | - | - 
+**f** | $a -> $b	 | $a -> $b	 | 
+**g** | $c -> $d	 | $c -> $d	 | 
+**a** | $a	 | $c	 | 
+_return type_ | $b	 | $d	 | 
 
 
+EitherFunc is a small utility function, mostly used in the parser. It allows the compiler to choose a function, based on the types.
+
+Consider the mapping `{'someKey':'someValue'}`. Under normal circumstances, this acts as a pointwise-function, converting the string `someKey` into `someValue`, just like an ordinary dictionary would do. However, in the context of `mustMatch`, we would prefer this to act as a _check_, that the highway _has_ a key `someKey` which is `someValue`, thus acting as `{'someKey': {'$eq':'someValue'}}. Both behaviours are automatically supported in parsing, by parsing the string as `(eitherFunc id eq) 'someValue'`. The type system is then able to figure out which implementation is needed.
+
+Disclaimer: _you should never ever need this in your profiles_
 
 
 
@@ -644,9 +723,14 @@ Lua implementation:
 
 #### stringToTags
 
-- (string -> string -> $a) -> tags -> list ($a)
+Argument name |  |  
+-------------- | - | - 
+**f** | string -> string -> $a	 | 
+**tags** | tags	 | 
+_return type_ | list ($a)	 | 
 
 
+stringToTags converts a function `string -> string -> a` into a function `tags -> [a]`
 
 
 
diff --git a/AspectedRouting/Analysis.cs b/AspectedRouting/Language/Analysis.cs
similarity index 71%
rename from AspectedRouting/Analysis.cs
rename to AspectedRouting/Language/Analysis.cs
index c74bf64..6332633 100644
--- a/AspectedRouting/Analysis.cs
+++ b/AspectedRouting/Language/Analysis.cs
@@ -1,11 +1,12 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Functions;
-using AspectedRouting.Typ;
-using static AspectedRouting.Deconstruct;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Functions;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting
+namespace AspectedRouting.Language
 {
     public static class Analysis
     {
@@ -26,6 +27,7 @@ namespace AspectedRouting
             Console.WriteLine(e);
             var keys = e.PossibleTags().Keys.ToList();
 
+
             var results = possibleTags.OnAllCombinations(
                 tags =>
                 {
@@ -124,10 +126,10 @@ namespace AspectedRouting
             } while (SelectNext());
         }
 
-        public static Dictionary<string, (IEnumerable<Typ.Type> Types, string inFunction)> UsedParameters(
+        public static Dictionary<string, (List<Type> Types, string inFunction)> UsedParameters(
             this ProfileMetaData profile, Context context)
         {
-            var parameters = new Dictionary<string, (IEnumerable<Typ.Type> Types, string inFunction)>();
+            var parameters = new Dictionary<string, (List<Type> Types, string inFunction)>();
 
             void AddParams(IExpression e, string inFunction)
             {
@@ -149,7 +151,7 @@ namespace AspectedRouting
                     }
                     else
                     {
-                        parameters[param.ParamName] = (param.Types, inFunction);
+                        parameters[param.ParamName] = (param.Types.ToList(), inFunction);
                     }
                 }
             }
@@ -159,6 +161,12 @@ namespace AspectedRouting
             AddParams(profile.Oneway, profile.Name + ".oneway");
             AddParams(profile.Speed, profile.Name + ".speed");
 
+            foreach (var (key, expr) in profile.Priority)
+            {
+                AddParams(new Parameter(key), profile.Name + ".priority.lefthand");
+                AddParams(expr, profile.Name + ".priority");
+            }
+
             foreach (var (name, expr) in context.DefinedFunctions)
             {
                 AddParams(expr, name);
@@ -194,11 +202,11 @@ namespace AspectedRouting
             return text;
         }
 
-        public static void SanityCheckProfile(this ProfileMetaData pmd)
+        public static void SanityCheckProfile(this ProfileMetaData pmd, Context context)
         {
             var defaultParameters = pmd.DefaultParameters.Keys;
 
-            var usedParameters = pmd.UsedParameters(new Context()).Keys.Select(key => key.TrimStart('#'));
+            var usedParameters = pmd.UsedParameters(context).Keys.Select(key => key.TrimStart('#'));
 
             var diff = usedParameters.ToHashSet().Except(defaultParameters).ToList();
             if (diff.Any())
@@ -206,28 +214,48 @@ namespace AspectedRouting
                 throw new ArgumentException("No default value set for parameter " + string.Join(", ", diff));
             }
 
-            foreach (var (profileName, profileParams) in pmd.Profiles)
+            var unused = defaultParameters.Except(usedParameters);
+            if (unused.Any())
+            {
+                throw new ArgumentException("A default value is set for parameter, but it is unused: " +
+                                            string.Join(", ", unused));
+            }
+
+            foreach (var (behaviourName, behaviourParams) in pmd.Behaviours)
             {
                 var sum = 0.0;
-                foreach (var (paramName, _) in pmd.Weights)
+                var explanation = "";
+                foreach (var (paramName, _) in pmd.Priority)
                 {
-                    if (!profileParams.TryGetValue(paramName, out var weight))
+
+                    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)";
                         continue;
                     }
 
-                    if (!(weight is double d))
+                    var weightObj = weight.Evaluate(context);
+
+                    if (!(weightObj is double d))
                     {
-                        continue;
+                        throw new ArgumentException($"The parameter {paramName} is not a numeric value");
                     }
 
                     sum += Math.Abs(d);
+                    explanation += $"\n - {paramName} = {d}";
                 }
 
                 if (Math.Abs(sum) < 0.0001)
                 {
-                    throw new ArgumentException("Profile " + profileName +
-                                                ": the summed parameters to calculate the weight are zero or very low");
+                    throw new ArgumentException("Profile " + behaviourName +
+                                                ": the summed parameters to calculate the weight are zero or very low:" +
+                                                explanation);
                 }
             }
         }
@@ -238,9 +266,9 @@ namespace AspectedRouting
             {
                 var order = new List<IExpression>();
                 var mapping = new List<IExpression>();
-                if (UnApply(
-                    UnApply(IsFunc(Funcs.FirstOf), Assign(order)),
-                    Assign(mapping)
+                if (Deconstruct.UnApply(
+                    Deconstruct.UnApply(Deconstruct.IsFunc(Funcs.FirstOf), Deconstruct.Assign(order)),
+                    Deconstruct.Assign(mapping)
                 ).Invoke(expr))
                 {
                     var expectedKeys = ((IEnumerable<object>) order.First().Evaluate(null)).Select(o =>
@@ -339,5 +367,61 @@ namespace AspectedRouting
 
             return result;
         }
+
+        public static Dictionary<string, IExpression> MembershipMappingsFor(ProfileMetaData profile, Context context)
+        {
+            var calledFunctions = profile.Priority.Values.ToHashSet();
+            calledFunctions.Add(profile.Speed);
+            calledFunctions.Add(profile.Access);
+            calledFunctions.Add(profile.Oneway);
+
+
+            var calledFunctionQueue = new Queue<string>();
+            var alreadyAnalysedFunctions = new HashSet<string>();
+            var memberships = new Dictionary<string, IExpression>();
+
+            void HandleExpression(IExpression e, string calledIn)
+            {
+                e.Visit(f =>
+                {
+                    var mapping = new List<IExpression>();
+                    if (Deconstruct.UnApply(Deconstruct.IsFunc(Funcs.MemberOf),
+                        Deconstruct.Assign(mapping)
+                    ).Invoke(f))
+                    {
+                        memberships.Add(calledIn, mapping.First());
+                        return false;
+                    }
+
+                    if (f is FunctionCall fc)
+                    {
+                        calledFunctionQueue.Enqueue(fc.CalledFunctionName);
+                    }
+
+                    return true;
+                });
+            }
+
+            foreach (var e in calledFunctions)
+            {
+                HandleExpression(e, "profile_root");
+            }
+
+            while (calledFunctionQueue.TryDequeue(out var functionName))
+            {
+                if (alreadyAnalysedFunctions.Contains(functionName))
+                {
+                    continue;
+                }
+
+                alreadyAnalysedFunctions.Add(functionName);
+
+                var functionImplementation = context.GetFunction(functionName);
+                HandleExpression(functionImplementation, functionName);
+            }
+
+
+            return memberships;
+        }
     }
 }
\ No newline at end of file
diff --git a/AspectedRouting/Language/Context.cs b/AspectedRouting/Language/Context.cs
new file mode 100644
index 0000000..01c427a
--- /dev/null
+++ b/AspectedRouting/Language/Context.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Functions;
+
+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;
+
+        public Context()
+        {
+        }
+
+        protected Context(string aspectName, Dictionary<string, IExpression> parameters,
+            Dictionary<string, AspectMetadata> definedFunctions)
+        {
+            AspectName = aspectName;
+            Parameters = parameters;
+            DefinedFunctions = definedFunctions;
+        }
+
+        public Context(Context c) : this(c.AspectName, c.Parameters, c.DefinedFunctions)
+        {
+        }
+
+        public void AddParameter(string name, string value)
+        {
+            Parameters.Add(name, new Constant(value));
+        }
+
+        public void AddFunction(string name, AspectMetadata function)
+        {
+            if (Funcs.Builtins.ContainsKey(name))
+            {
+                throw new ArgumentException("Function " + name + " already exists, it is a builtin function");
+            }
+
+            if (DefinedFunctions.ContainsKey(name) && !function.ProfileInternal)
+            {
+                throw new ArgumentException("Function " + name + " already exists");
+            }
+
+            DefinedFunctions[name] = function;
+        }
+
+        public IExpression GetFunction(string name)
+        {
+            if (name.StartsWith("$"))
+            {
+                name = name.Substring(1);
+            }
+
+            if (Funcs.Builtins.ContainsKey(name))
+            {
+                return Funcs.Builtins[name];
+            }
+
+            if (DefinedFunctions.ContainsKey(name))
+            {
+                return DefinedFunctions[name];
+            }
+
+            throw new ArgumentException(
+                $"The function {name} is not a defined nor builtin function. Known functions are " +
+                string.Join(", ", DefinedFunctions.Keys));
+        }
+
+        public Context WithParameters(Dictionary<string, IExpression> parameters)
+        {
+            return new Context(AspectName, parameters, DefinedFunctions);
+        }
+
+        public Context WithAspectName(string name)
+        {
+            return new Context(name, Parameters, DefinedFunctions);
+        }
+    }
+}
\ No newline at end of file
diff --git a/AspectedRouting/Deconstruct.cs b/AspectedRouting/Language/Deconstruct.cs
similarity index 94%
rename from AspectedRouting/Deconstruct.cs
rename to AspectedRouting/Language/Deconstruct.cs
index 8247e74..6c89b11 100644
--- a/AspectedRouting/Deconstruct.cs
+++ b/AspectedRouting/Language/Deconstruct.cs
@@ -1,9 +1,9 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Functions;
+using AspectedRouting.Language.Expression;
 
-namespace AspectedRouting
+namespace AspectedRouting.Language
 {
     public static class Deconstruct
     {
@@ -99,9 +99,7 @@ namespace AspectedRouting
             };
         }
 
-        public static Func<IExpression, bool> Any()
-        {
-            return e => true;
-        }
+        public static readonly Func<IExpression, bool> Any = e => true;
+        
     }
 }
\ No newline at end of file
diff --git a/AspectedRouting/Functions/Apply.cs b/AspectedRouting/Language/Expression/Apply.cs
similarity index 86%
rename from AspectedRouting/Functions/Apply.cs
rename to AspectedRouting/Language/Expression/Apply.cs
index 623cfa5..e1ddb1c 100644
--- a/AspectedRouting/Functions/Apply.cs
+++ b/AspectedRouting/Language/Expression/Apply.cs
@@ -1,11 +1,11 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
-using static AspectedRouting.Deconstruct;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Functions;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Expression
 {
     public class Apply : IExpression
     {
@@ -88,7 +88,7 @@ namespace AspectedRouting.Functions
                                  $"is applied on an argument with types:" +
                                  $"{string.Join(", ", argument.Optimize().Types)}";
                 }
-                catch (Exception e)
+                catch (Exception)
                 {
                     _debugInfo = $"\n{f.TypeBreakdown()}\n" +
                                  $"{argument.TypeBreakdown()}";
@@ -99,9 +99,9 @@ namespace AspectedRouting.Functions
 
         public object Evaluate(Context c, params IExpression[] arguments)
         {
-            if (Types.Count() > 1)
+            if (!Types.Any())
             {
-                // We try to select the smallest type
+                throw new ArgumentException("Trying to invoke an invalid expression: " + this);
             }
 
             var type = Types.First();
@@ -166,20 +166,20 @@ namespace AspectedRouting.Functions
             // => (const dot _) id => dot id => id
             // or => (constRight _ id) id => id id => id 
             if (
-                UnApplyAny(
-                    UnApplyAny(
-                        UnApplyAny(
-                            IsFunc(Funcs.Const),
-                            IsFunc(Funcs.Dot)),
-                        Any()),
-                    IsFunc(Funcs.Id)
+                Deconstruct.UnApplyAny(
+                    Deconstruct.UnApplyAny(
+                        Deconstruct.UnApplyAny(
+                            Deconstruct.IsFunc(Funcs.Const),
+                            Deconstruct.IsFunc(Funcs.Dot)),
+                        Deconstruct.Any),
+                    Deconstruct.IsFunc(Funcs.Id)
                 ).Invoke(this)
-                && UnApplyAny(UnApplyAny(
-                        UnApplyAny(
-                            IsFunc(Funcs.ConstRight),
-                            Any()),
-                        IsFunc(Funcs.Id)),
-                    IsFunc(Funcs.Id)
+                && Deconstruct.UnApplyAny(Deconstruct.UnApplyAny(
+                        Deconstruct.UnApplyAny(
+                            Deconstruct.IsFunc(Funcs.ConstRight),
+                            Deconstruct.Any),
+                        Deconstruct.IsFunc(Funcs.Id)),
+                    Deconstruct.IsFunc(Funcs.Id)
                 ).Invoke(this))
             {
                 return Funcs.Id;
@@ -247,12 +247,12 @@ namespace AspectedRouting.Functions
 
                     // ((dot f0) f1)
                     // ((dot f0) f1) arg is the actual expression, but arg is already split of
-                    if (UnApply(
-                            UnApply(
-                                IsFunc(Funcs.Dot),
-                                Assign(f0)
+                    if (Deconstruct.UnApply(
+                            Deconstruct.UnApply(
+                                Deconstruct.IsFunc(Funcs.Dot),
+                                Deconstruct.Assign(f0)
                             ),
-                            Assign(f1)).Invoke(f)
+                            Deconstruct.Assign(f1)).Invoke(f)
                     )
                     {
                         // f0 (f1 arg)
diff --git a/AspectedRouting/Functions/Metadata.cs b/AspectedRouting/Language/Expression/AspectMetadata.cs
similarity index 84%
rename from AspectedRouting/Functions/Metadata.cs
rename to AspectedRouting/Language/Expression/AspectMetadata.cs
index 6f23849..a9bd917 100644
--- a/AspectedRouting/Functions/Metadata.cs
+++ b/AspectedRouting/Language/Expression/AspectMetadata.cs
@@ -1,8 +1,8 @@
 using System;
 using System.Collections.Generic;
-using Type = AspectedRouting.Typ.Type;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Expression
 {
     public class AspectMetadata : IExpression
     {
@@ -32,6 +32,12 @@ namespace AspectedRouting.Functions
 
         public object Evaluate(Context c, params IExpression[] arguments)
         {
+            if (ProfileInternal && arguments.Length > 0)
+            {
+                var tags = (Dictionary<string, string>) arguments[0].Evaluate(c);
+                
+            }
+            
             return ExpressionImplementation.Evaluate(c, arguments);
         }
 
diff --git a/AspectedRouting/Functions/Function.cs b/AspectedRouting/Language/Expression/Function.cs
similarity index 94%
rename from AspectedRouting/Functions/Function.cs
rename to AspectedRouting/Language/Expression/Function.cs
index edc0cdc..042d1a9 100644
--- a/AspectedRouting/Functions/Function.cs
+++ b/AspectedRouting/Language/Expression/Function.cs
@@ -1,10 +1,10 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Expression
 {
     public abstract class Function : IExpression
     {
diff --git a/AspectedRouting/Functions/FunctionCall.cs b/AspectedRouting/Language/Expression/FunctionCall.cs
similarity index 79%
rename from AspectedRouting/Functions/FunctionCall.cs
rename to AspectedRouting/Language/Expression/FunctionCall.cs
index 983b39f..845b57f 100644
--- a/AspectedRouting/Functions/FunctionCall.cs
+++ b/AspectedRouting/Language/Expression/FunctionCall.cs
@@ -1,9 +1,9 @@
 using System;
 using System.Collections.Generic;
-using AspectedRouting.Typ;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Expression
 {
     public class FunctionCall : IExpression
     {
@@ -34,7 +34,10 @@ namespace AspectedRouting.Functions
 
         public object Evaluate(Context c, params IExpression[] arguments)
         {
-            return c.GetFunction(_name).Evaluate(c, arguments);
+            
+            var func = c.GetFunction(_name);
+            c = c.WithAspectName(_name);
+            return func.Evaluate(c, arguments);
         }
 
         public IExpression Specialize(IEnumerable<Type> allowedTypes)
diff --git a/AspectedRouting/Functions/ProfileMetaData.cs b/AspectedRouting/Language/Expression/ProfileMetaData.cs
similarity index 62%
rename from AspectedRouting/Functions/ProfileMetaData.cs
rename to AspectedRouting/Language/Expression/ProfileMetaData.cs
index 6a04a2b..2309480 100644
--- a/AspectedRouting/Functions/ProfileMetaData.cs
+++ b/AspectedRouting/Language/Expression/ProfileMetaData.cs
@@ -1,7 +1,7 @@
 using System.Collections.Generic;
 using System.Linq;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Expression
 {
     public class ProfileMetaData
     {
@@ -12,19 +12,19 @@ namespace AspectedRouting.Functions
         public List<string> VehicleTyps { get; }
         public List<string> Metadata { get; }
 
-        public Dictionary<string, object> DefaultParameters { get; }
-        public Dictionary<string, Dictionary<string, object>> Profiles { get; }
+        public Dictionary<string, IExpression> DefaultParameters { get; }
+        public Dictionary<string, Dictionary<string, IExpression>> Behaviours { get; }
 
         public IExpression Access { get; }
         public IExpression Oneway { get; }
         public IExpression Speed { get; }
-        public Dictionary<string, IExpression> Weights { get; }
+        public Dictionary<string, IExpression> Priority { get; }
 
         public ProfileMetaData(string name, string description, string author, string filename,
-            List<string> vehicleTyps, Dictionary<string, object> defaultParameters,
-            Dictionary<string, Dictionary<string, object>> profiles,
+            List<string> vehicleTyps, Dictionary<string, IExpression> defaultParameters,
+            Dictionary<string, Dictionary<string, IExpression>> behaviours,
             IExpression access, IExpression oneway, IExpression speed,
-            Dictionary<string, IExpression> weights, List<string> metadata)
+            Dictionary<string, IExpression> priority, List<string> metadata)
         {
             Name = name;
             Description = description;
@@ -34,16 +34,16 @@ namespace AspectedRouting.Functions
             Access = access;
             Oneway = oneway;
             Speed = speed;
-            Weights = weights;
+            Priority = priority;
             Metadata = metadata;
             DefaultParameters = defaultParameters;
-            Profiles = profiles;
+            Behaviours = behaviours;
         }
 
         public override string ToString()
         {
             return $"Profile: {Name} {Filename}\naccess={Access}\noneway={Oneway}\nspeed={Speed}\n" +
-                   $"weights ={string.Join(" + ", Weights.Select(kv => "#" + kv.Key + " * " + kv.Value))} ";
+                   $"priorities = {string.Join(" + ", Priority.Select(kv => "#" + kv.Key + " * " + kv.Value))} ";
         }
     }
 }
\ No newline at end of file
diff --git a/AspectedRouting/Functions/Funcs.cs b/AspectedRouting/Language/Funcs.cs
similarity index 93%
rename from AspectedRouting/Functions/Funcs.cs
rename to AspectedRouting/Language/Funcs.cs
index 3f8ef67..e12e0ef 100644
--- a/AspectedRouting/Functions/Funcs.cs
+++ b/AspectedRouting/Language/Funcs.cs
@@ -1,11 +1,13 @@
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Functions;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
 // ReSharper disable UnusedMember.Global
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language
 {
     public static class Funcs
     {
@@ -23,12 +25,13 @@ namespace AspectedRouting.Functions
         public static readonly Function ToStringFunc = new ToString();
         public static readonly Function Concat = new Concat();
         
+        public static readonly Function ContainedIn = new ContainedIn();
         public static readonly Function Min = new Min();
         public static readonly Function Max = new Max();
         public static readonly Function Sum = new Sum();
         public static readonly Function Multiply = new Multiply();
-        
-        
+
+
         public static readonly Function FirstOf = new FirstMatchOf();
         public static readonly Function MustMatch = new MustMatch();
 
diff --git a/AspectedRouting/Functions/All.cs b/AspectedRouting/Language/Functions/All.cs
similarity index 89%
rename from AspectedRouting/Functions/All.cs
rename to AspectedRouting/Language/Functions/All.cs
index 82ce89a..add714c 100644
--- a/AspectedRouting/Functions/All.cs
+++ b/AspectedRouting/Language/Functions/All.cs
@@ -1,8 +1,9 @@
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class All : Function
     {
diff --git a/AspectedRouting/Functions/Concat.cs b/AspectedRouting/Language/Functions/Concat.cs
similarity index 89%
rename from AspectedRouting/Functions/Concat.cs
rename to AspectedRouting/Language/Functions/Concat.cs
index bf53aec..a9a6e92 100644
--- a/AspectedRouting/Functions/Concat.cs
+++ b/AspectedRouting/Language/Functions/Concat.cs
@@ -1,7 +1,8 @@
 using System.Collections.Generic;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Concat : Function
     {
diff --git a/AspectedRouting/Functions/Const.cs b/AspectedRouting/Language/Functions/Const.cs
similarity index 89%
rename from AspectedRouting/Functions/Const.cs
rename to AspectedRouting/Language/Functions/Const.cs
index f31414a..e7e267f 100644
--- a/AspectedRouting/Functions/Const.cs
+++ b/AspectedRouting/Language/Functions/Const.cs
@@ -1,8 +1,9 @@
 using System.Collections.Generic;
-using AspectedRouting.Typ;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Const : Function
     {
diff --git a/AspectedRouting/Functions/ConstRight.cs b/AspectedRouting/Language/Functions/ConstRight.cs
similarity index 88%
rename from AspectedRouting/Functions/ConstRight.cs
rename to AspectedRouting/Language/Functions/ConstRight.cs
index 3481cb1..bed89c3 100644
--- a/AspectedRouting/Functions/ConstRight.cs
+++ b/AspectedRouting/Language/Functions/ConstRight.cs
@@ -1,8 +1,9 @@
 using System.Collections.Generic;
-using AspectedRouting.Typ;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class ConstRight : Function
     {        public override string Description { get; } =
diff --git a/AspectedRouting/Functions/Constant.cs b/AspectedRouting/Language/Functions/Constant.cs
similarity index 96%
rename from AspectedRouting/Functions/Constant.cs
rename to AspectedRouting/Language/Functions/Constant.cs
index 7f18703..7dd2592 100644
--- a/AspectedRouting/Functions/Constant.cs
+++ b/AspectedRouting/Language/Functions/Constant.cs
@@ -1,10 +1,10 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Constant : IExpression
     {
@@ -40,7 +40,7 @@ namespace AspectedRouting.Functions
             }
             catch (Exception e)
             {
-                throw new Exception($"While creating a list with members {string.Join(", ", exprs)} {e.Message}", e);
+                throw new Exception($"While creating a list with members {string.Join(", ", exprs.Select(e => e.Optimize()))} {e.Message}", e);
             }
         }
 
diff --git a/AspectedRouting/Language/Functions/ContainedIn.cs b/AspectedRouting/Language/Functions/ContainedIn.cs
new file mode 100644
index 0000000..7dfc4a3
--- /dev/null
+++ b/AspectedRouting/Language/Functions/ContainedIn.cs
@@ -0,0 +1,64 @@
+using System.Collections.Generic;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
+
+namespace AspectedRouting.Language.Functions
+{
+    public class ContainedIn : Function
+    {
+        public override string Description { get; } =
+            "Given a list of values, checks if the argument is contained in the list.";
+
+        public override List<string> ArgNames { get; } = new List<string>{"list","a"};
+
+        public ContainedIn() : base("containedIn", true,
+            new[]
+            {
+                // [a] -> a -> bool
+                new Curry(
+                    new ListType(new Var("a")),
+                    new Curry(new Var("a"),
+                        Typs.Bool))
+            }
+        )
+        {
+        }
+
+        private ContainedIn(IEnumerable<Type> types) : base("containedIn", types)
+        {
+        }
+
+        public override object Evaluate(Context c, params IExpression[] arguments)
+        {
+            var list = (IEnumerable<IExpression>) arguments[0].Evaluate(c);
+            var arg = arguments[1];
+
+            var result = new List<object>();
+            foreach (var f in list)
+            {
+                var o = f.Evaluate(c);
+                while (o is IExpression e)
+                {
+                    o = e.Evaluate(c);
+                }
+                if (f.Equals(arg))
+                {
+                    return true;
+                }
+            }
+
+            return result;
+        }
+
+        public override IExpression Specialize(IEnumerable<Type> allowedTypes)
+        {
+            var unified = Types.SpecializeTo(allowedTypes);
+            if (unified == null)
+            {
+                return null;
+            }
+
+            return new ContainedIn(unified);
+        }
+    }
+}
\ No newline at end of file
diff --git a/AspectedRouting/Functions/Default.cs b/AspectedRouting/Language/Functions/Default.cs
similarity index 92%
rename from AspectedRouting/Functions/Default.cs
rename to AspectedRouting/Language/Functions/Default.cs
index b95b129..3f3f0c1 100644
--- a/AspectedRouting/Functions/Default.cs
+++ b/AspectedRouting/Language/Functions/Default.cs
@@ -1,8 +1,9 @@
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Default : Function
     {
diff --git a/AspectedRouting/Functions/Dot.cs b/AspectedRouting/Language/Functions/Dot.cs
similarity index 77%
rename from AspectedRouting/Functions/Dot.cs
rename to AspectedRouting/Language/Functions/Dot.cs
index 490ff3c..d966bda 100644
--- a/AspectedRouting/Functions/Dot.cs
+++ b/AspectedRouting/Language/Functions/Dot.cs
@@ -1,18 +1,19 @@
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Dot : Function
     {
         public override string Description { get; } = "Higher order function: converts `f (g a)` into `(dot f g) a`. In other words, this fuses `f` and `g` in a new function, which allows the argument to be lifted out of the expression ";
 
         public override List<string> ArgNames { get; } = new List<string>{"f","g","a"};
-        public static readonly Var A = new Var("fType");
-        public static readonly Var B = new Var("gType");
-        public static readonly Var C = new Var("arg");
+        public static readonly Var A = new Var("a");
+        public static readonly Var B = new Var("b");
+        public static readonly Var C = new Var("c");
 
         public Dot() : base("dot", true, new[]
         {
@@ -34,7 +35,7 @@ namespace AspectedRouting.Functions
         {
             var f0 = arguments[0];
             var f1 = arguments[1];
-            var resultType = (f1.Types.First() as Curry).ResultType;
+            var resultType = ((Curry) f1.Types.First()).ResultType;
             var a = arguments[2];
             return f0.Evaluate(c, new Constant(resultType, f1.Evaluate(c, a)));
         }
diff --git a/AspectedRouting/Functions/EitherFunc.cs b/AspectedRouting/Language/Functions/EitherFunc.cs
similarity index 61%
rename from AspectedRouting/Functions/EitherFunc.cs
rename to AspectedRouting/Language/Functions/EitherFunc.cs
index 4fd9871..27d6b97 100644
--- a/AspectedRouting/Functions/EitherFunc.cs
+++ b/AspectedRouting/Language/Functions/EitherFunc.cs
@@ -1,12 +1,24 @@
 using System;
 using System.Collections.Generic;
-using AspectedRouting.Typ;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class EitherFunc : Function
     {
+        public override string Description { get; } =
+            "EitherFunc is a small utility function, mostly used in the parser. It allows the compiler to choose a function, based on the types.\n\n" +
+            "" +
+            "Consider the mapping `{'someKey':'someValue'}`. Under normal circumstances, this acts as a pointwise-function, converting the string `someKey` into `someValue`, just like an ordinary dictionary would do. " +
+            "However, in the context of `mustMatch`, we would prefer this to act as a _check_, that the highway _has_ a key `someKey` which is `someValue`, thus acting as " +
+            "`{'someKey': {'$eq':'someValue'}}. " +
+            "Both behaviours are automatically supported in parsing, by parsing the string as `(eitherFunc id eq) 'someValue'`. " +
+            "The type system is then able to figure out which implementation is needed.\n\n" +
+            "Disclaimer: _you should never ever need this in your profiles_";
+
+        public override List<string> ArgNames { get; } = new List<string>{"f","g","a"};
         private static Var a = new Var("a");
         private static Var b = new Var("b");
         private static Var c = new Var("c");
diff --git a/AspectedRouting/Functions/Eq.cs b/AspectedRouting/Language/Functions/Eq.cs
similarity index 91%
rename from AspectedRouting/Functions/Eq.cs
rename to AspectedRouting/Language/Functions/Eq.cs
index 1aaf6f5..5095ea1 100644
--- a/AspectedRouting/Functions/Eq.cs
+++ b/AspectedRouting/Language/Functions/Eq.cs
@@ -1,7 +1,8 @@
 using System.Collections.Generic;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Eq : Function
     {  public override string Description { get; } = "Returns 'yes' if both values _are_ the same";
diff --git a/AspectedRouting/Functions/FirstMatchOf.cs b/AspectedRouting/Language/Functions/FirstMatchOf.cs
similarity index 93%
rename from AspectedRouting/Functions/FirstMatchOf.cs
rename to AspectedRouting/Language/Functions/FirstMatchOf.cs
index 27f7612..0fb306b 100644
--- a/AspectedRouting/Functions/FirstMatchOf.cs
+++ b/AspectedRouting/Language/Functions/FirstMatchOf.cs
@@ -1,9 +1,10 @@
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class FirstMatchOf : Function
     {
diff --git a/AspectedRouting/Functions/Id.cs b/AspectedRouting/Language/Functions/Id.cs
similarity index 89%
rename from AspectedRouting/Functions/Id.cs
rename to AspectedRouting/Language/Functions/Id.cs
index a9b9ea9..61ef45d 100644
--- a/AspectedRouting/Functions/Id.cs
+++ b/AspectedRouting/Language/Functions/Id.cs
@@ -1,8 +1,9 @@
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Id : Function
     {  public override string Description { get; } = "Returns the argument unchanged - the identity function. Seems useless at first sight, but useful in parsing";
diff --git a/AspectedRouting/Functions/If.cs b/AspectedRouting/Language/Functions/If.cs
similarity index 93%
rename from AspectedRouting/Functions/If.cs
rename to AspectedRouting/Language/Functions/If.cs
index 8811308..df894cb 100644
--- a/AspectedRouting/Functions/If.cs
+++ b/AspectedRouting/Language/Functions/If.cs
@@ -1,7 +1,8 @@
 using System.Collections.Generic;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class If : Function
     {
diff --git a/AspectedRouting/Functions/Inv.cs b/AspectedRouting/Language/Functions/Inv.cs
similarity index 88%
rename from AspectedRouting/Functions/Inv.cs
rename to AspectedRouting/Language/Functions/Inv.cs
index 8da69e4..b924009 100644
--- a/AspectedRouting/Functions/Inv.cs
+++ b/AspectedRouting/Language/Functions/Inv.cs
@@ -1,7 +1,8 @@
 using System.Collections.Generic;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Inv : Function
     {
diff --git a/AspectedRouting/Functions/ListDot.cs b/AspectedRouting/Language/Functions/ListDot.cs
similarity index 89%
rename from AspectedRouting/Functions/ListDot.cs
rename to AspectedRouting/Language/Functions/ListDot.cs
index a8b2445..2ae1ba2 100644
--- a/AspectedRouting/Functions/ListDot.cs
+++ b/AspectedRouting/Language/Functions/ListDot.cs
@@ -1,7 +1,8 @@
 using System.Collections.Generic;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class ListDot : Function
     {
diff --git a/AspectedRouting/Functions/Mapping.cs b/AspectedRouting/Language/Functions/Mapping.cs
similarity index 95%
rename from AspectedRouting/Functions/Mapping.cs
rename to AspectedRouting/Language/Functions/Mapping.cs
index 4c837d3..af11056 100644
--- a/AspectedRouting/Functions/Mapping.cs
+++ b/AspectedRouting/Language/Functions/Mapping.cs
@@ -1,10 +1,11 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Mapping : Function
     {
@@ -78,7 +79,6 @@ namespace AspectedRouting.Functions
             var otherARgs = arguments.ToList().GetRange(1, arguments.Length - 1);
             if (!StringToResultFunctions.TryGetValue(key, out var resultFunction))
             {
-                Console.WriteLine($"Warning: key {key} not found in mapping {this}");
                 return null;
             }
 
@@ -95,7 +95,7 @@ namespace AspectedRouting.Functions
 
                 var typeOptStr = string.Join(";", opt.Types);
                 var typeEStr = string.Join("; ", e.Types);
-                if (opt == null || !opt.Types.Any())
+                if (!opt.Types.Any())
                 {
                     throw new NullReferenceException($"Optimized version is null, has different or empty types: " +
                                                      $"\n{typeEStr}" +
diff --git a/AspectedRouting/Functions/Max.cs b/AspectedRouting/Language/Functions/Max.cs
similarity index 93%
rename from AspectedRouting/Functions/Max.cs
rename to AspectedRouting/Language/Functions/Max.cs
index ce2457b..fabc189 100644
--- a/AspectedRouting/Functions/Max.cs
+++ b/AspectedRouting/Language/Functions/Max.cs
@@ -1,8 +1,9 @@
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Max : Function
     {
diff --git a/AspectedRouting/Language/Functions/MemberOf.cs b/AspectedRouting/Language/Functions/MemberOf.cs
new file mode 100644
index 0000000..4f9df61
--- /dev/null
+++ b/AspectedRouting/Language/Functions/MemberOf.cs
@@ -0,0 +1,72 @@
+using System.Collections.Generic;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
+
+namespace AspectedRouting.Language.Functions
+{
+    public class MemberOf : Function
+    {
+        public override string Description { get; } =
+            "This function returns true, if the way is member of a relation matching the specified function.\n" +
+            "\n" +
+            "In order to use this for itinero 1.0, the membership _must_ be the top level expression.\n" +
+            "\n" +
+            "Conceptually, when the aspect is executed for a way, every relation will be used as argument in the subfunction `f`\n" +
+            "If this subfunction returns 'true', the entire aspect will return true.\n\n" +
+            "In the lua implementation for itinero 1.0, this is implemented slightly different:" +
+            " a flag `_relation:<aspect_name>=\"yes\"` will be set if the aspect matches on every way for where this aspect matches.\n" +
+            "However, this plays poorly with parameters (e.g.: what if we want to cycle over a highway which is part of a certain cycling network with a certain `#network_name`?) " +
+            "Luckily, parameters can only be simple values. To work around this problem, an extra tag is introduced for _every single profile_:" +
+            "`_relation:<profile_name>:<aspect_name>=yes'. The subfunction is thus executed `countOr(relations) * countOf(profiles)` time, yielding `countOf(profiles)` tags." +
+            " The profile function then picks the tags for himself and strips the `<profile_name>:` away from the key.\n\n" +
+            "\n\n" +
+            "In the test.csv, one can simply use `_relation:<aspect_name>=yes` to mimic relations in your tests";
+
+        public override List<string> ArgNames { get; } = new List<string>
+        {
+            "f","tags"
+        };
+
+        public MemberOf() : base(
+            "memberOf", true,
+            new[]
+            {
+                new Curry(
+                    new Curry(Typs.Tags, Typs.Bool),
+                    new Curry(Typs.Tags, Typs.Bool))
+            }
+        )
+        {
+        }
+
+        public MemberOf(IEnumerable<Type> types) : base("memberOf", types)
+        {
+        }
+
+        public override object Evaluate(Context c, params IExpression[] arguments)
+        {
+          //  var subFunction = arguments[0];
+            
+            var tags =(Dictionary<string, string>) arguments[1].Evaluate(c);
+            var name = c.AspectName.TrimStart('$');
+            
+            if(tags.TryGetValue("_relation:"+name, out var v))
+            {
+                return v;
+            }
+            return "no";
+        }
+
+        public override IExpression Specialize(IEnumerable<Type> allowedTypes)
+        {
+            var unified = Types.SpecializeTo(allowedTypes);
+            if (unified == null)
+            {
+                return null;
+            }
+
+            return new MemberOf(unified);
+        }
+    }
+}
\ No newline at end of file
diff --git a/AspectedRouting/Functions/Min.cs b/AspectedRouting/Language/Functions/Min.cs
similarity index 59%
rename from AspectedRouting/Functions/Min.cs
rename to AspectedRouting/Language/Functions/Min.cs
index dd33bb4..7c04f46 100644
--- a/AspectedRouting/Functions/Min.cs
+++ b/AspectedRouting/Language/Functions/Min.cs
@@ -1,13 +1,16 @@
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Min : Function
     {
-        public override string Description { get; } = "Out of a list of values, gets the smallest value. IN case of a list of bools, this acts as `and`";
-        public override List<string> ArgNames { get; } = new List<string>{"list"};
+        public override string Description { get; } =
+            "Out of a list of values, gets the smallest value. IN case of a list of bools, this acts as `and`";
+
+        public override List<string> ArgNames { get; } = new List<string> {"list"};
 
         public Min() : base("min", true,
             new[]
@@ -17,7 +20,6 @@ namespace AspectedRouting.Functions
                 new Curry(new ListType(Typs.PDouble), Typs.PDouble),
                 new Curry(new ListType(Typs.Double), Typs.Double),
                 new Curry(new ListType(Typs.Bool), Typs.Bool),
-                
             })
         {
         }
@@ -41,11 +43,12 @@ namespace AspectedRouting.Functions
 
         public override object Evaluate(Context c, params IExpression[] arguments)
         {
-            var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o!=null);
-            var expectedType = (Types.First() as Curry).ResultType;
+            var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o != null);
+            var expectedType = ((Curry) Types.First()).ResultType;
 
             switch (expectedType)
-            {  case BoolType _:
+            {
+                case BoolType _:
                     if (ls.Select(o => o.Equals("yes") || o.Equals("true")).All(b => b))
                     {
                         return "yes";
@@ -54,9 +57,27 @@ namespace AspectedRouting.Functions
                     return "no";
                 case DoubleType _:
                 case PDoubleType _:
-                    return ls.Select(o => (double) o).Min();
+
+
+                    return ls.Select(o =>
+                    {
+                        while (o is IExpression e)
+                        {
+                            o = e.Evaluate(c);
+                        }
+
+                        return (double) o;
+                    }).Min();
                 default:
-                    return ls.Select(o => (int) o).Min();
+                    return ls.Select(o =>
+                    {
+                        while (o is IExpression e)
+                        {
+                            o = e.Evaluate(c);
+                        }
+
+                        return (int) o;
+                    }).Min();
             }
         }
     }
diff --git a/AspectedRouting/Functions/Multiply.cs b/AspectedRouting/Language/Functions/Multiply.cs
similarity index 63%
rename from AspectedRouting/Functions/Multiply.cs
rename to AspectedRouting/Language/Functions/Multiply.cs
index ddbb727..f2c5af3 100644
--- a/AspectedRouting/Functions/Multiply.cs
+++ b/AspectedRouting/Language/Functions/Multiply.cs
@@ -1,16 +1,18 @@
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Multiply : Function
     {
-        
-        public override string Description { get; } = "Multiplies all the values in a given list. On a list of booleans, this acts as 'and' or 'all'";
+        public override string Description { get; } =
+            "Multiplies all the values in a given list. On a list of booleans, this acts as 'and' or 'all'";
+
         public override List<string> ArgNames { get; } = new List<string> {"list"};
 
-        
+
         public Multiply() : base("multiply", true,
             new[]
             {
@@ -42,19 +44,26 @@ namespace AspectedRouting.Functions
         public override object Evaluate(Context c, params IExpression[] arguments)
         {
             var ls = ((IEnumerable<object>) arguments[0].Evaluate(c)).Where(o => o != null);
-            var expectedType = (Types.First() as Curry).ResultType;
+            var expectedType = ((Curry) Types.First()).ResultType;
 
 
             switch (expectedType)
             {
                 case BoolType _:
-                    foreach (var o in ls)
+                    foreach (var oo in ls)
                     {
-                        if(!(o is string s))
+                        var o = oo;
+                        while (o is IExpression e)
+                        {
+                            o = e.Evaluate(c);
+                        }
+
+                        if (!(o is string s))
                         {
                             return "no";
                         }
-                        if(!(o.Equals("yes") || o.Equals("true")))
+
+                        if (!(s.Equals("yes") || s.Equals("true")))
                         {
                             return "no";
                         }
@@ -64,16 +73,28 @@ namespace AspectedRouting.Functions
                 case DoubleType _:
                 case PDoubleType _:
                     var mult = 1.0;
-                    foreach (var o in ls)
+                    foreach (var oo in ls)
                     {
+                        var o = oo;
+                        while (o is IExpression e)
+                        {
+                            o = e.Evaluate(c);
+                        }
+
                         mult *= (double) o;
                     }
 
                     return mult;
                 default:
                     var multI = 1;
-                    foreach (var o in ls)
+                    foreach (var oo in ls)
                     {
+                        var o = oo;
+                        while (o is IExpression e)
+                        {
+                            o = e.Evaluate(c);
+                        }
+
                         multI *= (int) o;
                     }
 
diff --git a/AspectedRouting/Functions/MustMatch.cs b/AspectedRouting/Language/Functions/MustMatch.cs
similarity index 93%
rename from AspectedRouting/Functions/MustMatch.cs
rename to AspectedRouting/Language/Functions/MustMatch.cs
index 2dcab50..3860a8b 100644
--- a/AspectedRouting/Functions/MustMatch.cs
+++ b/AspectedRouting/Language/Functions/MustMatch.cs
@@ -1,9 +1,10 @@
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class MustMatch : Function
     {
diff --git a/AspectedRouting/Functions/NotEq.cs b/AspectedRouting/Language/Functions/NotEq.cs
similarity index 69%
rename from AspectedRouting/Functions/NotEq.cs
rename to AspectedRouting/Language/Functions/NotEq.cs
index e1bfbff..cd5e9d4 100644
--- a/AspectedRouting/Functions/NotEq.cs
+++ b/AspectedRouting/Language/Functions/NotEq.cs
@@ -1,18 +1,20 @@
 using System.Collections.Generic;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class NotEq : Function
     {
-        public override string Description { get; } = "Returns 'yes' if the two passed in values are _not_ the same";
+        public override string Description { get; } = "OVerloaded function, either boolean not or returns 'yes' if the two passed in values are _not_ the same;";
         public override List<string> ArgNames { get; } = new List<string> {"a", "b"};
 
         public NotEq() : base("notEq", true,
             new[]
             {
                 Curry.ConstructFrom(Typs.Bool, new Var("a"), new Var("a")),
-                Curry.ConstructFrom(Typs.String, new Var("a"), new Var("a"))
+                Curry.ConstructFrom(Typs.String, new Var("a"), new Var("a")),
+                new Curry(Typs.Bool, Typs.Bool), 
             })
         {
             Funcs.AddBuiltin(this, "not");
@@ -35,6 +37,13 @@ namespace AspectedRouting.Functions
 
         public override object Evaluate(Context c, params IExpression[] arguments)
         {
+
+            if (arguments.Length == 1)
+            {
+                var booleanArg = arguments[0].Evaluate(c);
+                return booleanArg.Equals("no");
+            }
+            
             var arg0 = arguments[0].Evaluate(c);
             var arg1 = arguments[1].Evaluate(c);
             if ((!(arg0?.Equals(arg1) ?? false)))
diff --git a/AspectedRouting/Functions/Parameter.cs b/AspectedRouting/Language/Functions/Parameter.cs
similarity index 75%
rename from AspectedRouting/Functions/Parameter.cs
rename to AspectedRouting/Language/Functions/Parameter.cs
index 17b4b92..904a25b 100644
--- a/AspectedRouting/Functions/Parameter.cs
+++ b/AspectedRouting/Language/Functions/Parameter.cs
@@ -1,9 +1,9 @@
 using System;
 using System.Collections.Generic;
-using AspectedRouting.Typ;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Parameter : IExpression
     {
@@ -25,16 +25,8 @@ namespace AspectedRouting.Functions
 
         public object Evaluate(Context c, params IExpression[] args)
         {
-            return c?.Parameters?.GetValueOrDefault(ParamName, null);
-        }
-
-        public void EvaluateAll(Context c, HashSet<IExpression> addTo)
-        {
-            var v = Evaluate(c);
-            if (v != null)
-            {
-                addTo.Add(this);
-            }
+            var paramName = ParamName.TrimStart('#'); // context saves paramnames without '#'
+            return c?.Parameters?.GetValueOrDefault(paramName, null);
         }
 
         public IExpression Specialize(IEnumerable<Type> allowedTypes)
diff --git a/AspectedRouting/Functions/Parse.cs b/AspectedRouting/Language/Functions/Parse.cs
similarity index 87%
rename from AspectedRouting/Functions/Parse.cs
rename to AspectedRouting/Language/Functions/Parse.cs
index 10f39fb..07b2264 100644
--- a/AspectedRouting/Functions/Parse.cs
+++ b/AspectedRouting/Language/Functions/Parse.cs
@@ -1,8 +1,9 @@
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Parse : Function
     {
@@ -13,6 +14,7 @@ namespace AspectedRouting.Functions
             new[]
             {
                 new Curry(Typs.String, Typs.Double),
+                new Curry(Typs.String, Typs.PDouble),
             })
         {
         }
diff --git a/AspectedRouting/Functions/StringStringToTagsFunction.cs b/AspectedRouting/Language/Functions/StringStringToTagsFunction.cs
similarity index 73%
rename from AspectedRouting/Functions/StringStringToTagsFunction.cs
rename to AspectedRouting/Language/Functions/StringStringToTagsFunction.cs
index c412e81..fa71fe8 100644
--- a/AspectedRouting/Functions/StringStringToTagsFunction.cs
+++ b/AspectedRouting/Language/Functions/StringStringToTagsFunction.cs
@@ -1,13 +1,18 @@
 using System.Collections.Generic;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     /// <summary>
     /// Converts a function 'string -> string -> a' onto a function 'tags -> [a]'
     /// </summary>
     public class StringStringToTagsFunction : Function
     {
+        public override string Description { get; } =
+            "stringToTags converts a function `string -> string -> a` into a function `tags -> [a]`";
+        public override List<string> ArgNames { get; } = new List<string>{"f","tags"};
+
         private static Type baseFunction =
             Curry.ConstructFrom(new Var("a"), Typs.String, Typs.String);
 
@@ -15,7 +20,8 @@ namespace AspectedRouting.Functions
         public StringStringToTagsFunction() : base("stringToTags", true,
             new[]
             {
-                new Curry(baseFunction, new Curry(Typs.Tags, new ListType(new Var("a"))))
+                new Curry(baseFunction, 
+                    new Curry(Typs.Tags, new ListType(new Var("a"))))
             }
         )
         {
diff --git a/AspectedRouting/Functions/Sum.cs b/AspectedRouting/Language/Functions/Sum.cs
similarity index 94%
rename from AspectedRouting/Functions/Sum.cs
rename to AspectedRouting/Language/Functions/Sum.cs
index 048117a..9bc5c6c 100644
--- a/AspectedRouting/Functions/Sum.cs
+++ b/AspectedRouting/Language/Functions/Sum.cs
@@ -1,8 +1,9 @@
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class Sum : Function
     {
diff --git a/AspectedRouting/Functions/ToString.cs b/AspectedRouting/Language/Functions/ToString.cs
similarity index 88%
rename from AspectedRouting/Functions/ToString.cs
rename to AspectedRouting/Language/Functions/ToString.cs
index b7af8d9..0110cdb 100644
--- a/AspectedRouting/Functions/ToString.cs
+++ b/AspectedRouting/Language/Functions/ToString.cs
@@ -1,7 +1,8 @@
 using System.Collections.Generic;
-using AspectedRouting.Typ;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Typ;
 
-namespace AspectedRouting.Functions
+namespace AspectedRouting.Language.Functions
 {
     public class ToString : Function
     {
diff --git a/AspectedRouting/IExpression.cs b/AspectedRouting/Language/IExpression.cs
similarity index 66%
rename from AspectedRouting/IExpression.cs
rename to AspectedRouting/Language/IExpression.cs
index 197145f..0acfea2 100644
--- a/AspectedRouting/IExpression.cs
+++ b/AspectedRouting/Language/IExpression.cs
@@ -1,60 +1,13 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq;
-using AspectedRouting.Functions;
-using AspectedRouting.Typ;
-using Type = AspectedRouting.Typ.Type;
+using AspectedRouting.Language.Functions;
+using AspectedRouting.Language.Typ;
+using Type = AspectedRouting.Language.Typ.Type;
 
-namespace AspectedRouting
+namespace AspectedRouting.Language
 {
-    public class Context
-    {
-        public Dictionary<string, IExpression> Parameters = new Dictionary<string, IExpression>();
-        public Dictionary<string, AspectMetadata> DefinedFunctions = new Dictionary<string, AspectMetadata>();
-
-        public void AddParameter(string name, string value)
-        {
-            Parameters.Add(name, new Constant(value));
-        }
-
-        public void AddFunction(string name, AspectMetadata function)
-        {
-            if (Funcs.Builtins.ContainsKey(name))
-            {
-                throw new ArgumentException("Function " + name + " already exists, it is a builtin function");
-            }
-
-            if (DefinedFunctions.ContainsKey(name))
-            {
-                throw new ArgumentException("Function " + name + " already exists");
-            }
-
-            DefinedFunctions.Add(name, function);
-        }
-
-        public IExpression GetFunction(string name)
-        {
-            if (name.StartsWith("$"))
-            {
-                name = name.Substring(1);
-            }
-
-            if (Funcs.Builtins.ContainsKey(name))
-            {
-                return Funcs.Builtins[name];
-            }
-
-            if (DefinedFunctions.ContainsKey(name))
-            {
-                return DefinedFunctions[name];
-            }
-
-            throw new ArgumentException(
-                $"The function {name} is not a defined nor builtin function. Known functions are " +
-                string.Join(", ", DefinedFunctions.Keys));
-        }
-    }
-
     public interface IExpression
     {
         IEnumerable<Type> Types { get; }
@@ -79,6 +32,11 @@ namespace AspectedRouting
 
     public static class ExpressionExtensions
     {
+        public static object Run(this IExpression e, Context c, Dictionary<string, string> tags)
+        {
+            return e.Apply(new []{new Constant(tags)}).Evaluate(c);
+        }
+
         public static IExpression Specialize(this IExpression e, Type t)
         {
             if (t == null)
@@ -125,6 +83,7 @@ namespace AspectedRouting
         /// THen specializes every expression onto this common ground
         /// </summary>
         /// <returns>The common ground of types</returns>
+        [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
         public static IEnumerable<IExpression> SpecializeToCommonTypes(this IEnumerable<IExpression> exprs,
             out IEnumerable<Type> specializedTypes, out IEnumerable<IExpression> specializedExpressions)
         {
@@ -139,12 +98,13 @@ namespace AspectedRouting
                 }
 
                 var specialized = specializedTypes.SpecializeTo(f.Types.RenameVars(specializedTypes));
+                // ReSharper disable once JoinNullCheckWithUsage
                 if (specialized == null)
                 {
                     throw new ArgumentException("Could not unify\n   "
                                                 + "<previous items>: " + string.Join(", ", specializedTypes) +
                                                 "\nwith\n   "
-                                                + f + ": " + string.Join(", ", f.Types));
+                                                + f.Optimize() + ": " + string.Join(", ", f.Types));
                 }
 
                 specializedTypes = specialized;
diff --git a/AspectedRouting/Typ/BoolType.cs b/AspectedRouting/Language/Typ/BoolType.cs
similarity index 74%
rename from AspectedRouting/Typ/BoolType.cs
rename to AspectedRouting/Language/Typ/BoolType.cs
index ab5f861..dafc250 100644
--- a/AspectedRouting/Typ/BoolType.cs
+++ b/AspectedRouting/Language/Typ/BoolType.cs
@@ -1,4 +1,4 @@
-namespace AspectedRouting.Typ
+namespace AspectedRouting.Language.Typ
 {
     public class BoolType : Type
     {
diff --git a/AspectedRouting/Typ/Curry.cs b/AspectedRouting/Language/Typ/Curry.cs
similarity index 97%
rename from AspectedRouting/Typ/Curry.cs
rename to AspectedRouting/Language/Typ/Curry.cs
index 822dc51..8f2b945 100644
--- a/AspectedRouting/Typ/Curry.cs
+++ b/AspectedRouting/Language/Typ/Curry.cs
@@ -2,7 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 
-namespace AspectedRouting.Typ
+namespace AspectedRouting.Language.Typ
 {
     public class Curry : Type
     {
diff --git a/AspectedRouting/Typ/DoubleType.cs b/AspectedRouting/Language/Typ/DoubleType.cs
similarity index 75%
rename from AspectedRouting/Typ/DoubleType.cs
rename to AspectedRouting/Language/Typ/DoubleType.cs
index 9d45518..f122368 100644
--- a/AspectedRouting/Typ/DoubleType.cs
+++ b/AspectedRouting/Language/Typ/DoubleType.cs
@@ -1,4 +1,4 @@
-namespace AspectedRouting.Typ
+namespace AspectedRouting.Language.Typ
 {
     public class DoubleType : Type
     {
diff --git a/AspectedRouting/Typ/IntType.cs b/AspectedRouting/Language/Typ/IntType.cs
similarity index 73%
rename from AspectedRouting/Typ/IntType.cs
rename to AspectedRouting/Language/Typ/IntType.cs
index ba7e5ac..0e1747d 100644
--- a/AspectedRouting/Typ/IntType.cs
+++ b/AspectedRouting/Language/Typ/IntType.cs
@@ -1,4 +1,4 @@
-namespace AspectedRouting.Typ
+namespace AspectedRouting.Language.Typ
 {
     public class IntType : Type
     {
diff --git a/AspectedRouting/Typ/ListType.cs b/AspectedRouting/Language/Typ/ListType.cs
similarity index 83%
rename from AspectedRouting/Typ/ListType.cs
rename to AspectedRouting/Language/Typ/ListType.cs
index 5254fe8..15d8f5b 100644
--- a/AspectedRouting/Typ/ListType.cs
+++ b/AspectedRouting/Language/Typ/ListType.cs
@@ -1,4 +1,4 @@
-namespace AspectedRouting.Typ
+namespace AspectedRouting.Language.Typ
 {
     public class ListType : Type
     {
diff --git a/AspectedRouting/Typ/NatType.cs b/AspectedRouting/Language/Typ/NatType.cs
similarity index 73%
rename from AspectedRouting/Typ/NatType.cs
rename to AspectedRouting/Language/Typ/NatType.cs
index 323f9d9..14f23c3 100644
--- a/AspectedRouting/Typ/NatType.cs
+++ b/AspectedRouting/Language/Typ/NatType.cs
@@ -1,4 +1,4 @@
-namespace AspectedRouting.Typ
+namespace AspectedRouting.Language.Typ
 {
     public class NatType : Type
     {
diff --git a/AspectedRouting/Typ/PDoubleType.cs b/AspectedRouting/Language/Typ/PDoubleType.cs
similarity index 75%
rename from AspectedRouting/Typ/PDoubleType.cs
rename to AspectedRouting/Language/Typ/PDoubleType.cs
index eb7bb9d..4fba8df 100644
--- a/AspectedRouting/Typ/PDoubleType.cs
+++ b/AspectedRouting/Language/Typ/PDoubleType.cs
@@ -1,4 +1,4 @@
-namespace AspectedRouting.Typ
+namespace AspectedRouting.Language.Typ
 {
     public class PDoubleType : Type
     {
diff --git a/AspectedRouting/Typ/StringType.cs b/AspectedRouting/Language/Typ/StringType.cs
similarity index 75%
rename from AspectedRouting/Typ/StringType.cs
rename to AspectedRouting/Language/Typ/StringType.cs
index 83f4c55..91cf321 100644
--- a/AspectedRouting/Typ/StringType.cs
+++ b/AspectedRouting/Language/Typ/StringType.cs
@@ -1,4 +1,4 @@
-namespace AspectedRouting.Typ
+namespace AspectedRouting.Language.Typ
 {
     public class StringType : Type
     {
diff --git a/AspectedRouting/Typ/TagsType.cs b/AspectedRouting/Language/Typ/TagsType.cs
similarity index 70%
rename from AspectedRouting/Typ/TagsType.cs
rename to AspectedRouting/Language/Typ/TagsType.cs
index f923a04..65cf52f 100644
--- a/AspectedRouting/Typ/TagsType.cs
+++ b/AspectedRouting/Language/Typ/TagsType.cs
@@ -1,4 +1,4 @@
-namespace AspectedRouting.Typ
+namespace AspectedRouting.Language.Typ
 {
     public class TagsType : Type
     {
diff --git a/AspectedRouting/Typ/Type.cs b/AspectedRouting/Language/Typ/Type.cs
similarity index 94%
rename from AspectedRouting/Typ/Type.cs
rename to AspectedRouting/Language/Typ/Type.cs
index b8c0508..bde25a6 100644
--- a/AspectedRouting/Typ/Type.cs
+++ b/AspectedRouting/Language/Typ/Type.cs
@@ -1,4 +1,4 @@
-namespace AspectedRouting.Typ
+namespace AspectedRouting.Language.Typ
 {
     public abstract class Type
     {
diff --git a/AspectedRouting/Typ/Typs.cs b/AspectedRouting/Language/Typ/Typs.cs
similarity index 99%
rename from AspectedRouting/Typ/Typs.cs
rename to AspectedRouting/Language/Typ/Typs.cs
index de2261d..b9238d6 100644
--- a/AspectedRouting/Typ/Typs.cs
+++ b/AspectedRouting/Language/Typ/Typs.cs
@@ -1,7 +1,7 @@
 using System.Collections.Generic;
 using System.Linq;
 
-namespace AspectedRouting.Typ
+namespace AspectedRouting.Language.Typ
 {
     public static class Typs
     {
diff --git a/AspectedRouting/Typ/Var.cs b/AspectedRouting/Language/Typ/Var.cs
similarity index 98%
rename from AspectedRouting/Typ/Var.cs
rename to AspectedRouting/Language/Typ/Var.cs
index ad4667b..728e2ff 100644
--- a/AspectedRouting/Typ/Var.cs
+++ b/AspectedRouting/Language/Typ/Var.cs
@@ -1,7 +1,7 @@
 using System;
 using System.Collections.Generic;
 
-namespace AspectedRouting.Typ
+namespace AspectedRouting.Language.Typ
 {
     public class Var : Type
     {
diff --git a/AspectedRouting/Profiles/bicycle-manual.lua b/AspectedRouting/Profiles/bicycle-manual.lua
index 8802612..9c80a05 100644
--- a/AspectedRouting/Profiles/bicycle-manual.lua
+++ b/AspectedRouting/Profiles/bicycle-manual.lua
@@ -1,12 +1,7 @@
 -- The different profiles
 profiles = {
     
-    {
-        name = "comfort_safety_speed",
-        description = "A route which aims to be both safe and comfortable without sacrificing too much of speed",
-        function_name = "determine_weights_balanced",
-        metric = "custom"
-    },
+
 
     {
         name = "b2w",
@@ -72,151 +67,10 @@ profiles = {
 }
 
 
---[[
-What is a relative speedup/slowdown for each key?
-]]
-speed_bonuses = {
-    access = {
-        dismount = 0.2
-    },
-    highway = {
-    	path = 0.5, -- A small path through a forest is typically very slow to go through
-    	track = 0.7 -- an unmaintained track (in Belgium: tractor path) is slower as well
-    },
-    surface = {
-        paved = 0.99,
-        asphalt = 1, -- default speed is kept
-        concrete = 1,
-        metal = 1,
-        wood = 1,
-        ["concrete:lanes"] = 0.95,
-        ["concrete:plates"] = 1,
-        paving_stones = 1,
-        sett = 0.9, -- sett slows us down with 10%
-        unhewn_cobblestone = 0.75,
-        cobblestone = 0.8,
-        unpaved = 0.75,
-        compacted = 0.99,
-        fine_gravel = 0.99,
-        gravel = 0.9,
-        dirt = 0.6,
-        earth = 0.6,
-        grass = 0.6,
-        grass_paver = 0.9,
-        ground = 0.7,
-        sand = 0.5,
-        woodchips = 0.5,
-        snow = 0.5,
-        pebblestone = 0.5,
-        mud = 0.4
-    },
-    tracktype = {
-    	grade1 = 0.99,
-    	grade2 = 0.8,
-    	grade3 = 0.6,
-    	grade4 = 0.3,
-    	grade5 = 0.1
-    },
-    incline = {
-        up = 0.75,
-        down = 1.25,
-        [0] = 1,
-        ["0%"] = 1,
-        ["10%"] = 0.9,
-        ["-10%"] = 1.1,
-        ["20%"] = 0.8,
-        ["-20%"] = 1.2,
-        ["30%"] = 0.7,
-        ["-30%"] = 1.3,
-    }
-}
-
---[[
-Determine-speed determines the average speed for the given segment based on _physical properties_, 
-    such as surface, road incline, ...
-It is _not_ concerned with a legal (or socially accepted) speed - this is capped in the calling procedure
-]]
-function determine_speed(attributes, result)
-    local factor = calculate_factor(attributes, speed_bonuses, result)
-    result.speed = factor * attributes.settings.default_speed
-    return result.speed
-end
 
 
 
 
-
-
-
-
---[[
-How comfortable and nice is this road?
-Note: this is quite subjective as well!
-
-Takes a guess on how _comfortable_ this road is to travel.
-
-Here, aspects are:
-- road surface and smoothness
-- estimated greenery (abandoned=railway for the win)
-
-]]
-comfort_bonuses = {
-    highway = {
-        cycleway = 1.2,
-        primary = 0.3,
-        secondary = 0.4,
-        tertiary = 0.5,
-        unclassified = 0.8,
-        track = 0.95,
-        residential = 1.0,
-        living_street = 1.1,
-        footway = 0.95,
-        path = 0.5 
-    },
-    railway = {
-        abandoned = 2.0
-    },
-    cycleway = {
-        track = 1.2
-    },
-    access = {
-        designated = 1.2,
-        dismount = 0.5
-    },
-    cyclestreet = {
-        yes = 1.1
-    },
-
-
-    -- Quite, but not entirely the same as in speed
-    surface = {
-        paved = 0.99,
-        ["concrete:lanes"] = 0.8,
-        ["concrete:plates"] = 1,
-        sett = 0.9, -- sett slows us down with 10%
-        unhewn_cobblestone = 0.75,
-        cobblestone = 0.8,
-        unpaved = 0.75,
-        compacted = 1.1,
-        fine_gravel = 0.99,
-        gravel = 0.9,
-        dirt = 0.6,
-        earth = 0.6,
-        grass = 0.6,
-        grass_paver = 0.9,
-        ground = 0.7,
-        sand = 0.5,
-        woodchips = 0.5,
-        snow = 0.5,
-        pebblestone = 0.5,
-        mud = 0.4
-    },
-}
-
-function determine_comfort(attributes, result)
-    return calculate_factor(attributes, comfort_bonuses, result)
-end
-
 -- Returns 1 if no access restrictions, 0 if it is permissive
 function determine_permissive_score(attributes, result)
     if (attributes.access == "permissive") then
@@ -674,30 +528,6 @@ end
 function unit_tests()
 
  
-    -- Speed test are with default_speed = 100, in order to efficiently determine the factor
-    unit_test_speed({ highway = "residential" }, 100.0)
-    unit_test_speed({ highway = "residential", surface = "sett" }, 90.0)
-    unit_test_speed({ highway = "residential", incline = "up" }, 75.0)
-    unit_test_speed({ highway = "residential", incline = "up", surface = "mud" }, 30.0)
-    unit_test_speed({ highway = "residential", incline = "down" }, 125.0)
-    unit_test_speed({ highway = "residential", incline = "down", surface = "sett" }, 112.5)
-
-
-    unit_test_comfort({ highway = "residential" }, 1.0)
-    unit_test_comfort({ highway = "residential", cyclestreet = "yes" }, 1.1)
-    unit_test_comfort({ highway = "cycleway" }, 1.2)
-    unit_test_comfort({ highway = "cycleway", foot = "designated" }, 1.2)
-    unit_test_comfort({ highway = "path", bicycle = "designated", foot = "designated" }, 0.5)
-    unit_test_comfort({ highway = "path", bicycle = "designated" }, 0.5)
-    unit_test_comfort({ highway = "footway", foot = "designated" }, 0.95)
-    unit_test_comfort({ highway = "primary", cycleway = "no" }, 0.3)
-    unit_test_comfort({ highway = "primary", cycleway = "yes" }, 0.3)
-    unit_test_comfort({ highway = "primary", cycleway = "track" }, 0.36)
-
-    unit_test_comfort({ highway = "secondary", cycleway = "lane" }, 0.4)
-    unit_test_comfort({ ["cycleway:right"] = "lane", highway = "secondary", surface = "asphalt" }, 0.4)
-    unit_test_comfort({ highway = "residential", surface = "asphalt", cyclestreet = "yes" }, 1.1)
-
 
     unit_test_relation_tag_processor({ route = "bicycle", operator = "Stad Genk", color = "red", type = "route" },
         { StadGenk = "yes", cycle_network_colour = "red", cycle_network = "yes" });
diff --git a/AspectedRouting/Profiles/bicycle/bicycle.brussels.csv b/AspectedRouting/Profiles/bicycle/bicycle.brussels.csv
new file mode 100644
index 0000000..c18d585
--- /dev/null
+++ b/AspectedRouting/Profiles/bicycle/bicycle.brussels.csv
@@ -0,0 +1,4 @@
+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
diff --git a/AspectedRouting/Profiles/bicycle/bicycle.comfort.json b/AspectedRouting/Profiles/bicycle/bicycle.comfort.json
index f6e745f..c9790b6 100644
--- a/AspectedRouting/Profiles/bicycle/bicycle.comfort.json
+++ b/AspectedRouting/Profiles/bicycle/bicycle.comfort.json
@@ -1,12 +1,60 @@
 {
   "name": "bicycle.comfort",
-  "description": "Gives a comfort factor, purely based on physical aspects of the road",
+  "description": "Gives a comfort factor for a road, purely based on physical aspects of the road, which is a bit subjective; this takes a bit of scnery into account with a preference for `railway=abandoned` and `towpath=yes`",
   "unit": "[0, 2]",
   "$default": 1,
   "value": {
     "$multiply": {
+      "highway": {
+        "cycleway": 1.2,
+        "primary": 0.3,
+        "secondary": 0.4,
+        "tertiary": 0.5,
+        "unclassified": 0.8,
+        "track": 0.95,
+        "residential": 1.0,
+        "living_street": 1.1,
+        "footway": 0.95,
+        "path": 0.5
+      },
+      "railway": {
+        "abandoned": 2
+      },
+      "towpath": {
+        "yes": 2
+      },
+      "cycleway": {
+        "track": 1.2
+      },
       "cyclestreet": {
-        "yes": 1.5
+        "yes": 1.1
+      },
+      "access": {
+        "designated": 1.2,
+        "dismount": 0.5
+      },
+      "surface": {
+        "#": "The surface mapping heavily resembles the one in speed_factor, but it is not entirely the same",
+        "paved": 0.99,
+        "concrete:lanes": 0.8,
+        "concrete:plates": 1.0,
+        "sett": 0.9,
+        "unhewn_cobblestone": 0.75,
+        "cobblestone": 0.8,
+        "unpaved": 0.75,
+        "compacted": 1.1,
+        "fine_gravel": 0.99,
+        "gravel": 0.9,
+        "dirt": 0.6,
+        "earth": 0.6,
+        "grass": 0.6,
+        "grass_paver": 0.9,
+        "ground": 0.7,
+        "sand": 0.5,
+        "woodchips": 0.5,
+        "snow": 0.5,
+        "pebblestone": 0.5,
+        "mud": 0.4
       }
     }
   }
diff --git a/AspectedRouting/Profiles/bicycle/bicycle.comfort.test.csv b/AspectedRouting/Profiles/bicycle/bicycle.comfort.test.csv
new file mode 100644
index 0000000..1c10586
--- /dev/null
+++ b/AspectedRouting/Profiles/bicycle/bicycle.comfort.test.csv
@@ -0,0 +1,18 @@
+expected,highway,foot,bicycle,cyclestreet,cycleway,cycleway:right,surface,railway,towpath
+1,,,,,,,,,
+1,residential,,,,,,,,
+1.1,residential,,,yes,,,,,
+1.2,cycleway,,,,,,,,
+1.2,cycleway,designated,,,,,,,
+0.5,path,designated,designated,,,,,,
+0.5,path,,designated,,,,,,
+0.95,footway,designated,,,,,,,
+0.3,primary,,,,no,,,,
+0.3,primary,,,,yes,,,,
+0.36,primary,,,,track,,,,
+0.4,secondary,,,,lane,,,,
+0.4,secondary,,,,,lane,asphalt,,
+1.1,residential,,,yes,,,asphalt,,
+2,,,,,,,,abandoned,
+2,,,,,,,,,yes
+4,,,,,,,,abandoned,yes
diff --git a/AspectedRouting/Profiles/bicycle/bicycle.commute_networks.csv b/AspectedRouting/Profiles/bicycle/bicycle.commute_networks.csv
new file mode 100644
index 0000000..5cb810d
--- /dev/null
+++ b/AspectedRouting/Profiles/bicycle/bicycle.commute_networks.csv
@@ -0,0 +1,4 @@
+access,oneway,speed,priority,highway,_relation:bicycle.network_score
+designated,both,15,22,cycleway,yes
+no,,,,,
+designated,both,15,2,cycleway,
diff --git a/AspectedRouting/Profiles/bicycle/bicycle.fastest.csv b/AspectedRouting/Profiles/bicycle/bicycle.fastest.csv
index c8e8610..1cbff82 100644
--- a/AspectedRouting/Profiles/bicycle/bicycle.fastest.csv
+++ b/AspectedRouting/Profiles/bicycle/bicycle.fastest.csv
@@ -1,7 +1,7 @@
-access,oneway,speed,weight,highway,bicycle
-0,0,0,0,,
-1,0,30,0.03333,cycleway,
-0,0,0,0,primary,
-1,0,15,0.06666,pedestrian,
-1,0,15,0.0666,pedestrian,yes
-1,0,30,0.033333,residential,
\ No newline at end of file
+access,oneway,speed,priority,highway,bicycle
+no,both,0,0,,
+designated,both,15,15,cycleway,
+no,both,0,0,primary,
+dismount,both,2.25,2.25,pedestrian,
+yes,both,15,15,pedestrian,yes
+yes,both,15,15,residential,
diff --git a/AspectedRouting/Profiles/bicycle/bicycle.json b/AspectedRouting/Profiles/bicycle/bicycle.json
index 08547e1..10647f1 100644
--- a/AspectedRouting/Profiles/bicycle/bicycle.json
+++ b/AspectedRouting/Profiles/bicycle/bicycle.json
@@ -16,25 +16,27 @@
     "network"
   ],
   "defaults": {
-    "#max_speed": 30,
     "#defaultSpeed": 15,
-    "#speed": 0,
+    "#maxspeed": 30,
+    "#timeNeeded": 0,
     "#distance": 0,
     "#comfort": 0,
     "#safety": 0,
-    "#withDirection": "yes",
-    "#againstDirection": "yes",
-    "network": 0
+    "#network": 0,
+    "#networkOperator": []
   },
-  "profiles": {
+  "behaviours": {
+    
     "fastest": {
       "description": "The fastest route to your destination",
-      "#time_needed": 1
+      "#timeNeeded": 1
     },
     "shortest": {
       "description": "The shortest route, independent of of speed",
       "#distance": 1
     },
+    
+    
     "safety": {
       "description": "A defensive route shying away from big roads with lots of cars",
       "#safety": 1
@@ -48,10 +50,12 @@
       "#comfort": 1,
       "#safety": 1
     },
-    "node_networks": {
-      "description": "A route which prefers cycling over cycling node networks. Non-network parts prefer some comfort and safety. The on-network-part is considered flat (meaning that only distance on the node network counts)",
+
+    
+    "brussels": {
+      "description": "A route preferring the cycling network by operator 'Brussels Mobility'",
       "#network": 20,
-      "#network-node-network-only":true, 
+      "#networkOperator": ["Brussels Mobility"], 
       "#comfort": 1,
       "#safety": 1
     }
@@ -61,16 +65,20 @@
   "speed": {
     "$min": [
       "$legal_maxspeed_be",
-      "#defaultSpeed"
+      "#maxspeed",
+      {
+        "$multiply": [
+          "#defaultSpeed",
+          "$bicycle.speed_factor"
+        ]
+      }
     ]
   },
-  "weight": {
+  "priority": {
     "#comfort": "$bicycle.comfort",
     "#safety": "$bicycle.safety",
-    "#network": "$bicycle.network_score",
-    "#time_needed": {
-      "$inv": "$speed"
-    },
+    "#network": "$bicycle.network_by_operator",
+    "#timeNeeded":  "$speed",
     "#distance": "$distance"
   }
 }
\ No newline at end of file
diff --git a/AspectedRouting/Profiles/bicycle/bicycle.network_by_operator.json b/AspectedRouting/Profiles/bicycle/bicycle.network_by_operator.json
new file mode 100644
index 0000000..41e453f
--- /dev/null
+++ b/AspectedRouting/Profiles/bicycle/bicycle.network_by_operator.json
@@ -0,0 +1,13 @@
+{
+  "name": "bicycle.network_by_operator",
+  "description": "The 'bicycle.network_score' returns true if the way is part of a cycling network of a certain (group of) operators",
+  "$memberOf": {
+    "$mustMatch": {
+      "type": "route",
+      "route": "bicycle",
+      "state": {"$notEq": "proposed"},
+      "operator": {"$containedIn": "#networkOperator"}
+    }
+  }
+}
+
diff --git a/AspectedRouting/Profiles/bicycle/bicycle.network_score.json b/AspectedRouting/Profiles/bicycle/bicycle.network_score.json
index 985aab7..e2ef549 100644
--- a/AspectedRouting/Profiles/bicycle/bicycle.network_score.json
+++ b/AspectedRouting/Profiles/bicycle/bicycle.network_score.json
@@ -1,9 +1,12 @@
 {
   "name": "bicycle.network_score",
-  "description": "The 'bicycle.network_score' is a bit of a catch-all for bicycle networks and indicates wether or not the road is part of a matching cycling network.",
-  "$mustMatch": {
-    "type": "route",
-    "route": "bicycle"
+  "description": "The 'bicycle.network_score' returns true if the way is part of a cycling network",
+    "$memberOf": {
+      "$mustMatch": {
+        "type": "route",
+        "route": "bicycle",
+        "state": {"$notEq": "proposed"}
+      }
   }
 }
 
diff --git a/AspectedRouting/Profiles/bicycle/bicycle.speed_factor.json b/AspectedRouting/Profiles/bicycle/bicycle.speed_factor.json
new file mode 100644
index 0000000..653a492
--- /dev/null
+++ b/AspectedRouting/Profiles/bicycle/bicycle.speed_factor.json
@@ -0,0 +1,62 @@
+{
+  "name": "bicycle.speed_factor",
+  "description": "Calculates a speed factor for bicycles based on physical features, e.g. a sand surface will slow a cyclist down; going over pedestrian areas even more, ...",
+  "$multiply": {
+    "access": {
+      "#": "We have to go by foot. Default speed of 20km/h * 0.15 = 3km/h",
+      "dismount": 0.15
+    },
+    "highway": {
+      "#": "A small forest path is typically slow",
+      "path": 0.5,
+      "#": "an unmaintained track (in Belgium: tractor path) is slower as well",
+      "track": 0.7
+    },
+    "surface": {
+      "paved": 0.99,
+      "asphalt": 1,
+      "concrete": 1,
+      "metal": 1,
+      "wood": 1,
+      "concrete:lanes": 0.95,
+      "concrete:plates": 1,
+      "paving_stones": 1,
+      "sett": 0.9,
+      "unhewn_cobblestone": 0.75,
+      "cobblestone": 0.8,
+      "unpaved": 0.75,
+      "compacted": 0.99,
+      "fine_gravel": 0.99,
+      "gravel": 0.9,
+      "dirt": 0.6,
+      "earth": 0.6,
+      "grass": 0.6,
+      "grass_paver": 0.9,
+      "ground": 0.7,
+      "sand": 0.5,
+      "woodchips": 0.5,
+      "snow": 0.5,
+      "pebblestone": 0.5,
+      "mud": 0.4
+    },
+    "tracktype": {
+      "grade1": 0.99,
+      "grade2": 0.8,
+      "grade3": 0.6,
+      "grade4": 0.3,
+      "grade5": 0.1
+    },
+    "incline": {
+      "up": 0.75,
+      "down": 1.25,
+      "0": 1,
+      "0%": 1,
+      "10%": 0.9,
+      "-10%": 1.1,
+      "20%": 0.8,
+      "-20%": 1.2,
+      "30%": 0.7,
+      "-30%": 1.3
+    }
+  }
+}
\ No newline at end of file
diff --git a/AspectedRouting/Profiles/bicycle/bicycle.speed_factor.test.csv b/AspectedRouting/Profiles/bicycle/bicycle.speed_factor.test.csv
new file mode 100644
index 0000000..6566a5b
--- /dev/null
+++ b/AspectedRouting/Profiles/bicycle/bicycle.speed_factor.test.csv
@@ -0,0 +1,12 @@
+expected,incline,surface,highway,access
+1,,,,
+1,,,residential,
+0.75,up,,residential,
+1.25,down,,residential,
+0.3,up,mud,residential,
+1.125,down,sett,residential,
+0.675,up,sett,residential,
+0.9,,sett,residential,
+1,,asphalt,residential,
+0.15,,,residential,dismount
+0.0315,up,mud,track,dismount
diff --git a/AspectedRouting/Program.cs b/AspectedRouting/Program.cs
index ffba2f9..5332aa4 100644
--- a/AspectedRouting/Program.cs
+++ b/AspectedRouting/Program.cs
@@ -2,110 +2,132 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using AspectedRouting.Functions;
 using AspectedRouting.IO;
+using AspectedRouting.IO.itinero1;
+using AspectedRouting.IO.jsonParser;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Tests;
 
 namespace AspectedRouting
 {
-    class Program
+    static class Program
     {
+        public static List<(AspectMetadata aspect, FunctionTestSuite tests)> ParseAspects(
+            this List<string> jsonFileNames, Context context)
+        {
+            var aspects = new List<(AspectMetadata aspect, FunctionTestSuite tests)>();
+            foreach (var file in jsonFileNames)
+            {
+                var fi = new FileInfo(file);
+                var aspect = JsonParser.AspectFromJson(context, File.ReadAllText(file), fi.Name);
+                if (aspect != null)
+                {
+                    var testPath = fi.DirectoryName + "/" + aspect.Name + ".test.csv";
+                    FunctionTestSuite tests = null;
+                    if (File.Exists(testPath))
+                    {
+                        tests = FunctionTestSuite.FromString(aspect, File.ReadAllText(testPath));
+                    }
+
+                    aspects.Add((aspect, tests));
+                }
+            }
+
+            return aspects;
+        }
+
+        private static LuaPrinter GenerateLua(Context context,
+            List<(AspectMetadata aspect, FunctionTestSuite tests)> aspects,
+            ProfileMetaData profile, List<ProfileTestSuite> profileTests)
+        {
+            var luaPrinter = new LuaPrinter(context);
+            foreach (var (aspect, tests) in aspects)
+            {
+                luaPrinter.AddFunction(aspect);
+                if (tests != null)
+                {
+                    luaPrinter.AddTestSuite(tests);
+                }
+            }
+
+            luaPrinter.AddProfile(profile);
+
+
+            foreach (var testSuite in profileTests)
+            {
+                luaPrinter.AddTestSuite(testSuite);
+            }
+
+
+            return luaPrinter;
+        }
+
+        private static (ProfileMetaData profile, List<ProfileTestSuite> profileTests) ParseProfile(string profilePath,
+            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 testPath = profileFi.DirectoryName + "/" + profile.Name + "." + behaviourName + ".csv";
+                if (File.Exists(testPath))
+                {
+                    var test = ProfileTestSuite.FromString(context, profile, behaviourName, File.ReadAllText(testPath));
+                    profileTests.Add(test);
+                }
+                else
+                {
+                    Console.WriteLine($"[{behaviourName}] WARNING: no test found for behaviour");
+                }
+            }
+
+            return (profile, profileTests);
+        }
+
         public static void Main(string[] args)
         {
+            MdPrinter.GenerateHelpText("IO/md/helpText.md");
+
+
             var files = Directory.EnumerateFiles("Profiles", "*.json", SearchOption.AllDirectories)
                 .ToList();
 
 
             var context = new Context();
 
+            var aspects = ParseAspects(files, context);
 
-            MdPrinter.GenerateHelpText("IO/md/helpText.md");
-
-            var testSuites = new List<FunctionTestSuite>();
-            var aspects = new List<AspectMetadata>();
-            foreach (var file in files)
+            foreach (var (aspect, t) in aspects)
             {
-                var fi = new FileInfo(file);
-                var aspect = JsonParser.AspectFromJson(context, File.ReadAllText(file), fi.Name);
-                if (aspect != null)
+                if (t == null)
                 {
-                    aspects.Add(aspect);
-
-                    var testPath = fi.DirectoryName + "/" + aspect.Name + ".test.csv";
-                    if (File.Exists(testPath))
-                    {
-                        var tests = FunctionTestSuite.FromString(aspect, File.ReadAllText(testPath));
-                        testSuites.Add(tests);
-                    }
-                    else
-                    {
-                        Console.WriteLine($"[{aspect.Name}] No tests found: to directory" );
-                    }
+                    Console.WriteLine($"[{aspect.Name}] WARNING: no tests found: please add {aspect.Name}.test.csv");
+                }
+                else
+                {
+                    t.Run();
                 }
-            }
 
-            foreach (var aspect in aspects)
-            {
                 context.AddFunction(aspect.Name, aspect);
             }
 
 
             var profilePath = "Profiles/bicycle/bicycle.json";
-            var profile = JsonParser.ProfileFromJson(context, File.ReadAllText(profilePath), new FileInfo(profilePath));
+            var (profile, profileTests) = ParseProfile(profilePath, context);
 
-            var profileFi = new FileInfo(profilePath);
-            var profileTests = new List<ProfileTestSuite>();
-            foreach (var profileName in profile.Profiles.Keys)
+            foreach (var test in profileTests)
             {
-                var testPath = profileFi.DirectoryName + "/" + profile.Name + "." + profileName + ".csv";
-                if (File.Exists(testPath))
-                {
-                    var test = ProfileTestSuite.FromString(profile, profileName, File.ReadAllText(testPath));
-                    profileTests.Add(test);
-                }
-            }
-
-            profile.SanityCheckProfile();
-
-            var luaPrinter = new LuaPrinter();
-            luaPrinter.CreateProfile(profile, context);
-
-
-            var testCode = "\n\n\n\n\n\n\n\n--------------------------- Test code -------------------------\n\n\n";
-
-
-            foreach (var testSuite in testSuites)
-            {
-                testSuite.Run();
-                testCode += testSuite.ToLua() + "\n";
-            }
-
-            foreach (var testSuite in profileTests)
-            {
-                testCode += testSuite.ToLua() + "\n";
+                test.Run(context);
             }
 
 
-            // Compatibility code, itinero-transit doesn't know 'print'
-            testCode += 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",
-                "",
-                "if (not failed_tests and not failed_profile_tests) then",
-                "    print(\"Tests OK\")",
-                "end"
-            );
+            var luaPrinter = GenerateLua(context, aspects, profile, profileTests);
 
-            File.WriteAllText("output.lua", luaPrinter.ToLua() + testCode);
+            File.WriteAllText("output.lua", luaPrinter.ToLua());
         }
     }
 }
\ No newline at end of file
diff --git a/AspectedRouting/IO/FunctionTestSuite.cs b/AspectedRouting/Tests/FunctionTestSuite.cs
similarity index 59%
rename from AspectedRouting/IO/FunctionTestSuite.cs
rename to AspectedRouting/Tests/FunctionTestSuite.cs
index 47e9d53..ad6a30a 100644
--- a/AspectedRouting/IO/FunctionTestSuite.cs
+++ b/AspectedRouting/Tests/FunctionTestSuite.cs
@@ -1,14 +1,16 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using AspectedRouting.Functions;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Functions;
 
-namespace AspectedRouting.IO
+namespace AspectedRouting.Tests
 {
     public class FunctionTestSuite
     {
-        private readonly AspectMetadata _functionToApply;
-        private readonly IEnumerable<(string expected, Dictionary<string, string> tags)> _tests;
+        public readonly AspectMetadata FunctionToApply;
+        public readonly IEnumerable<(string expected, Dictionary<string, string> tags)> Tests;
 
         public static FunctionTestSuite FromString(AspectMetadata function, string csvContents)
         {
@@ -47,47 +49,23 @@ namespace AspectedRouting.IO
             AspectMetadata functionToApply,
             IEnumerable<(string expected, Dictionary<string, string> tags)> tests)
         {
-            _functionToApply = functionToApply;
-            _tests = tests;
-        }
 
-
-        public string ToLua()
-        {
-
-
-            var tests = string.Join("\n", _tests.Select((test, i) => ToLua(i, test.expected, test.tags)));
-            return "\n" + tests + "\n";
-        }
-
-        private string ToLua(int index, string expected, Dictionary<string, string> tags)
-        {
-            var parameters = new Dictionary<string, string>();
-
-
-            foreach (var (key, value) in tags)
+            if (functionToApply == null)
             {
-                if (key.StartsWith("#"))
-                {
-                    parameters[key.TrimStart('#')] = value;
-                }
+                throw new NullReferenceException("functionToApply is null");
             }
-
-            foreach (var (paramName, _) in parameters)
-            {
-                tags.Remove("#" + paramName);
-            }
-
-            var funcName = _functionToApply.Name.Replace(" ", "_").Replace(".", "_");
-            return
-                $"unit_test({funcName}, \"{_functionToApply.Name}\", {index}, \"{expected}\", {parameters.ToLuaTable()}, {tags.ToLuaTable()})";
+            FunctionToApply = functionToApply;
+            Tests = tests;
         }
 
+
+     
+
         public void Run()
         {
             var failed = false;
             var testCase = 0;
-            foreach (var test in _tests)
+            foreach (var test in Tests)
             {
                 testCase++;
                 var context = new Context();
@@ -99,14 +77,14 @@ namespace AspectedRouting.IO
                     }
                 }
 
-                var actual = _functionToApply.Evaluate(context, new Constant(test.tags));
+                var actual = FunctionToApply.Evaluate(context, new Constant(test.tags));
                 if (!actual.ToString().Equals(test.expected) &&
                     !(actual is double actualD && Math.Abs(double.Parse(test.expected) - actualD) < 0.0001)
                 )
                 {
                     failed = true;
                     Console.WriteLine(
-                        $"[{_functionToApply.Name}] Testcase {testCase} failed:\n   Expected: {test.expected}\n   actual: {actual}\n   tags: {test.tags.Pretty()}");
+                        $"[{FunctionToApply.Name}] Testcase {testCase} failed:\n   Expected: {test.expected}\n   actual: {actual}\n   tags: {test.tags.Pretty()}");
                 }
             }
 
@@ -115,7 +93,7 @@ namespace AspectedRouting.IO
                 throw new ArgumentException("Some test failed");
             }
 
-            Console.WriteLine($"[{_functionToApply.Name}] {testCase} tests successful");
+            Console.WriteLine($"[{FunctionToApply.Name}] {testCase} tests successful");
         }
     }
 }
\ No newline at end of file
diff --git a/AspectedRouting/Tests/ProfileTestSuite.cs b/AspectedRouting/Tests/ProfileTestSuite.cs
new file mode 100644
index 0000000..dffcc4c
--- /dev/null
+++ b/AspectedRouting/Tests/ProfileTestSuite.cs
@@ -0,0 +1,299 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using AspectedRouting.Language;
+using AspectedRouting.Language.Expression;
+using AspectedRouting.Language.Functions;
+using AspectedRouting.Language.Typ;
+
+namespace AspectedRouting.Tests
+{
+    public struct Expected
+    {
+        public readonly string Access;
+        public readonly string Oneway;
+        public readonly double Speed;
+        public readonly double Weight;
+
+        public Expected(string access, string oneway, double speed, double weight)
+        {
+            Access = access;
+            Oneway = oneway;
+            Speed = speed;
+            Weight = weight;
+        }
+    }
+
+    public class ProfileTestSuite
+    {
+        public readonly ProfileMetaData Profile;
+        public readonly string BehaviourName;
+        public readonly IEnumerable<(Expected, Dictionary<string, string> tags)> Tests;
+
+        public static ProfileTestSuite FromString(Context c, ProfileMetaData function, string profileName,
+            string csvContents)
+        {
+            try
+            {
+                var all = csvContents.Split("\n").ToList();
+                var keys = all[0].Split(",").ToList();
+                keys = keys.GetRange(4, keys.Count - 4).Select(k => k.Trim()).ToList();
+
+                foreach (var k in keys)
+                {
+                    if (k.StartsWith("_relations:"))
+                    {
+                        throw new ArgumentException(
+                            "To inject relation memberships, use '_relation:<aspect_name>', without S after relation");
+                    }
+
+                    if (k.StartsWith("_relation:"))
+                    {
+                        var aspectName = k.Substring("_relation:".Length);
+                        if (aspectName.Contains(":"))
+                        {
+                            throw new ArgumentException(
+                                "To inject relation memberships, use '_relation:<aspect_name>', don't add the behaviour name");
+                        }
+
+                        if (!c.DefinedFunctions.ContainsKey(aspectName))
+                        {
+                            throw new ArgumentException(
+                                $"'_relation:<aspect_name>' detected, but the aspect {aspectName} wasn't found. Try one of: " +
+                                string.Join(",", c.DefinedFunctions.Keys));
+                        }
+                    }
+                }
+
+
+                var tests = new List<(Expected, Dictionary<string, string>)>();
+
+                var line = 1;
+                foreach (var test in all.GetRange(1, all.Count - 1))
+                {
+                    line++;
+                    if (string.IsNullOrEmpty(test.Trim()))
+                    {
+                        continue;
+                    }
+
+                    try
+                    {
+                        var testData = test.Split(",").ToList();
+
+                        var speed = 0.0;
+                        if (!string.IsNullOrEmpty(testData[2]))
+                        {
+                            speed = double.Parse(testData[2]);
+                        }
+
+                        var weight = 0.0;
+                        if (!string.IsNullOrEmpty(testData[3]))
+                        {
+                            weight = double.Parse(testData[3]);
+                        }
+
+                        var expected = new Expected(
+                            testData[0],
+                            testData[1],
+                            speed,
+                            weight
+                        );
+                        var vals = testData.GetRange(4, testData.Count - 4);
+                        var tags = new Dictionary<string, string>();
+                        for (int i = 0; i < keys.Count; i++)
+                        {
+                            if (i < vals.Count && !string.IsNullOrEmpty(vals[i]))
+                            {
+                                tags[keys[i]] = vals[i];
+                            }
+                        }
+
+                        tests.Add((expected, tags));
+                    }
+                    catch (Exception e)
+                    {
+                        throw new Exception("On line " + line, e);
+                    }
+                }
+
+                return new ProfileTestSuite(function, profileName, tests);
+            }
+            catch (Exception e)
+            {
+                throw new Exception("In the profile test file for " + profileName, e);
+            }
+        }
+
+        public ProfileTestSuite(
+            ProfileMetaData profile,
+            string profileName,
+            IEnumerable<(Expected, Dictionary<string, string> tags)> tests)
+        {
+            Profile = profile;
+            BehaviourName = profileName;
+            Tests = tests;
+        }
+
+
+        private static bool Eq(Context c, string value, object result)
+        {
+            var v = Funcs.Eq.Apply(new Constant(value), new Constant(Typs.String, result));
+
+            var o = v.Evaluate(c);
+            return o is string s && s.Equals("yes");
+        }
+
+        public bool RunTest(Context c, int i, Expected expected, Dictionary<string, string> tags)
+        {
+            c = new Context(c);
+            tags = new Dictionary<string, string>(tags);
+            
+            void Err(string message, object exp, object act)
+            {
+                Console.WriteLine(
+                    $"[{Profile.Name}.{BehaviourName}]: Test {i} failed: {message}; expected {exp} but got {act}\n    {{{tags.Pretty()}}}");
+            }
+
+            var success = true;
+            var canAccess = Profile.Access.Run(c, tags);
+            tags["access"] = "" + canAccess;
+
+            if (!expected.Access.Equals(canAccess))
+            {
+                Err("access value incorrect", expected.Access, canAccess);
+                success = false;
+            }
+
+
+            if (expected.Access.Equals("no"))
+            {
+                return success;
+            }
+
+            var oneway = Profile.Oneway.Run(c, tags);
+            tags["oneway"] = "" + oneway;
+
+            if (!Eq(c, expected.Oneway, oneway))
+            {
+                Err("oneway value incorrect", expected.Oneway, oneway);
+                success = false;
+            }
+
+            var speed = (double) Profile.Speed.Run(c, tags);
+            tags["speed"] = "" + speed;
+
+            c.AddFunction("speed", new AspectMetadata(new Constant(Typs.Double, speed), 
+                "speed", "Actual speed of this function", "NA","NA","NA", true));
+            c.AddFunction("oneway", new AspectMetadata(new Constant(Typs.String, oneway), 
+                "oneway", "Actual direction of this function", "NA","NA","NA", true));
+            c.AddFunction("access", new AspectMetadata(new Constant(Typs.String, canAccess), 
+                "access", "Actual access of this function", "NA","NA","NA", true));
+
+            if (Math.Abs(speed - expected.Speed) > 0.0001)
+            {
+                Err("speed value incorrect", expected.Speed, speed);
+                success = false;
+            }
+
+
+            var priority = 0.0;
+            foreach (var (paramName, expression) in Profile.Priority)
+            {
+                var aspectInfluence = (double) c.Parameters[paramName].Evaluate(c);
+                // ReSharper disable once CompareOfFloatsByEqualityOperator
+                if (aspectInfluence == 0)
+                {
+                    continue;
+                }
+
+
+                var aspectWeightObj = new Apply(
+                    Funcs.EitherFunc.Apply(Funcs.Id, Funcs.Const, expression)
+                    , new Constant((tags))).Evaluate(c);
+
+                double aspectWeight;
+                switch (aspectWeightObj)
+                {
+                    case bool b:
+                        aspectWeight = b ? 1.0 : 0.0;
+                        break;
+                    case double d:
+                        aspectWeight = d;
+                        break;
+                    case int j:
+                        aspectWeight = j;
+                        break;
+                    case string s:
+                        if (s.Equals("yes"))
+                        {
+                            aspectWeight = 1.0;
+                            break;
+                        }
+                        else if (s.Equals("no"))
+                        {
+                            aspectWeight = 0.0;
+                            break;
+                        }
+
+                        throw new Exception($"Invalid value as result for {paramName}: got string {s}");
+                    default:
+                        throw new Exception($"Invalid value as result for {paramName}: got object {aspectWeightObj}");
+                }
+
+                priority += aspectInfluence * aspectWeight;
+            }
+
+            if (Math.Abs(priority - expected.Weight) > 0.0001)
+            {
+                Err("weight incorrect", expected.Weight, priority);
+                success = false;
+            }
+
+            return success;
+        }
+
+        public void Run(Context c)
+
+        {
+            var parameters = new Dictionary<string, IExpression>();
+
+            foreach (var (k, v) in Profile.DefaultParameters)
+            {
+                parameters[k] = v;
+            }
+
+            foreach (var (k, v) in Profile.Behaviours[BehaviourName])
+            {
+                parameters[k] = v;
+            }
+
+            c = c.WithParameters(parameters);
+
+            var allOk = true;
+            var i = 1;
+            foreach (var (expected, tags) in Tests)
+            {
+                try
+                {
+                    allOk &= RunTest(c, i, expected, tags);
+                }
+                catch (Exception e)
+                {
+                    throw new Exception("In a test for " + BehaviourName, e);
+                }
+
+                i++;
+            }
+
+            if (!allOk)
+            {
+                throw new ArgumentException("Some tests failed for " + BehaviourName);
+            }
+            else
+            {
+                Console.WriteLine($"[{BehaviourName}] {Tests.Count()} tests successful");
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/AspectedRouting/Utils.cs b/AspectedRouting/Utils.cs
index b297688..7d505d6 100644
--- a/AspectedRouting/Utils.cs
+++ b/AspectedRouting/Utils.cs
@@ -19,5 +19,7 @@ namespace AspectedRouting
 
             return factor;
         }
+
+
     }
 }
\ No newline at end of file
diff --git a/AspectedRouting/bicycle.onewayfull_analysis.csv b/AspectedRouting/bicycle.onewayfull_analysis.csv
deleted file mode 100644
index 46286bf..0000000
--- a/AspectedRouting/bicycle.onewayfull_analysis.csv
+++ /dev/null
@@ -1,1801 +0,0 @@
-result, oneway, oneway:bicycle, junction, cycleway, cycleway:left
-both, , , , , 
-with, yes, , , , 
-both, no, , , , 
-with, 1, , , , 
-against, -1, , , , 
-with, , yes, , , 
-with, yes, yes, , , 
-with, no, yes, , , 
-with, 1, yes, , , 
-with, -1, yes, , , 
-both, , no, , , 
-both, yes, no, , , 
-both, no, no, , , 
-both, 1, no, , , 
-both, -1, no, , , 
-with, , 1, , , 
-with, yes, 1, , , 
-with, no, 1, , , 
-with, 1, 1, , , 
-with, -1, 1, , , 
-against, , -1, , , 
-against, yes, -1, , , 
-against, no, -1, , , 
-against, 1, -1, , , 
-against, -1, -1, , , 
-with, , , roundabout, , 
-with, yes, , roundabout, , 
-with, no, , roundabout, , 
-with, 1, , roundabout, , 
-with, -1, , roundabout, , 
-with, , yes, roundabout, , 
-with, yes, yes, roundabout, , 
-with, no, yes, roundabout, , 
-with, 1, yes, roundabout, , 
-with, -1, yes, roundabout, , 
-both, , no, roundabout, , 
-both, yes, no, roundabout, , 
-both, no, no, roundabout, , 
-both, 1, no, roundabout, , 
-both, -1, no, roundabout, , 
-with, , 1, roundabout, , 
-with, yes, 1, roundabout, , 
-with, no, 1, roundabout, , 
-with, 1, 1, roundabout, , 
-with, -1, 1, roundabout, , 
-against, , -1, roundabout, , 
-against, yes, -1, roundabout, , 
-against, no, -1, roundabout, , 
-against, 1, -1, roundabout, , 
-against, -1, -1, roundabout, , 
-against, , , , right, 
-against, yes, , , right, 
-against, no, , , right, 
-against, 1, , , right, 
-against, -1, , , right, 
-with, , yes, , right, 
-with, yes, yes, , right, 
-with, no, yes, , right, 
-with, 1, yes, , right, 
-with, -1, yes, , right, 
-both, , no, , right, 
-both, yes, no, , right, 
-both, no, no, , right, 
-both, 1, no, , right, 
-both, -1, no, , right, 
-with, , 1, , right, 
-with, yes, 1, , right, 
-with, no, 1, , right, 
-with, 1, 1, , right, 
-with, -1, 1, , right, 
-against, , -1, , right, 
-against, yes, -1, , right, 
-against, no, -1, , right, 
-against, 1, -1, , right, 
-against, -1, -1, , right, 
-with, , , roundabout, right, 
-with, yes, , roundabout, right, 
-with, no, , roundabout, right, 
-with, 1, , roundabout, right, 
-with, -1, , roundabout, right, 
-with, , yes, roundabout, right, 
-with, yes, yes, roundabout, right, 
-with, no, yes, roundabout, right, 
-with, 1, yes, roundabout, right, 
-with, -1, yes, roundabout, right, 
-both, , no, roundabout, right, 
-both, yes, no, roundabout, right, 
-both, no, no, roundabout, right, 
-both, 1, no, roundabout, right, 
-both, -1, no, roundabout, right, 
-with, , 1, roundabout, right, 
-with, yes, 1, roundabout, right, 
-with, no, 1, roundabout, right, 
-with, 1, 1, roundabout, right, 
-with, -1, 1, roundabout, right, 
-against, , -1, roundabout, right, 
-against, yes, -1, roundabout, right, 
-against, no, -1, roundabout, right, 
-against, 1, -1, roundabout, right, 
-against, -1, -1, roundabout, right, 
-both, , , , opposite_lane, 
-both, yes, , , opposite_lane, 
-both, no, , , opposite_lane, 
-both, 1, , , opposite_lane, 
-both, -1, , , opposite_lane, 
-with, , yes, , opposite_lane, 
-with, yes, yes, , opposite_lane, 
-with, no, yes, , opposite_lane, 
-with, 1, yes, , opposite_lane, 
-with, -1, yes, , opposite_lane, 
-both, , no, , opposite_lane, 
-both, yes, no, , opposite_lane, 
-both, no, no, , opposite_lane, 
-both, 1, no, , opposite_lane, 
-both, -1, no, , opposite_lane, 
-with, , 1, , opposite_lane, 
-with, yes, 1, , opposite_lane, 
-with, no, 1, , opposite_lane, 
-with, 1, 1, , opposite_lane, 
-with, -1, 1, , opposite_lane, 
-against, , -1, , opposite_lane, 
-against, yes, -1, , opposite_lane, 
-against, no, -1, , opposite_lane, 
-against, 1, -1, , opposite_lane, 
-against, -1, -1, , opposite_lane, 
-with, , , roundabout, opposite_lane, 
-with, yes, , roundabout, opposite_lane, 
-with, no, , roundabout, opposite_lane, 
-with, 1, , roundabout, opposite_lane, 
-with, -1, , roundabout, opposite_lane, 
-with, , yes, roundabout, opposite_lane, 
-with, yes, yes, roundabout, opposite_lane, 
-with, no, yes, roundabout, opposite_lane, 
-with, 1, yes, roundabout, opposite_lane, 
-with, -1, yes, roundabout, opposite_lane, 
-both, , no, roundabout, opposite_lane, 
-both, yes, no, roundabout, opposite_lane, 
-both, no, no, roundabout, opposite_lane, 
-both, 1, no, roundabout, opposite_lane, 
-both, -1, no, roundabout, opposite_lane, 
-with, , 1, roundabout, opposite_lane, 
-with, yes, 1, roundabout, opposite_lane, 
-with, no, 1, roundabout, opposite_lane, 
-with, 1, 1, roundabout, opposite_lane, 
-with, -1, 1, roundabout, opposite_lane, 
-against, , -1, roundabout, opposite_lane, 
-against, yes, -1, roundabout, opposite_lane, 
-against, no, -1, roundabout, opposite_lane, 
-against, 1, -1, roundabout, opposite_lane, 
-against, -1, -1, roundabout, opposite_lane, 
-both, , , , opposite, 
-both, yes, , , opposite, 
-both, no, , , opposite, 
-both, 1, , , opposite, 
-both, -1, , , opposite, 
-with, , yes, , opposite, 
-with, yes, yes, , opposite, 
-with, no, yes, , opposite, 
-with, 1, yes, , opposite, 
-with, -1, yes, , opposite, 
-both, , no, , opposite, 
-both, yes, no, , opposite, 
-both, no, no, , opposite, 
-both, 1, no, , opposite, 
-both, -1, no, , opposite, 
-with, , 1, , opposite, 
-with, yes, 1, , opposite, 
-with, no, 1, , opposite, 
-with, 1, 1, , opposite, 
-with, -1, 1, , opposite, 
-against, , -1, , opposite, 
-against, yes, -1, , opposite, 
-against, no, -1, , opposite, 
-against, 1, -1, , opposite, 
-against, -1, -1, , opposite, 
-with, , , roundabout, opposite, 
-with, yes, , roundabout, opposite, 
-with, no, , roundabout, opposite, 
-with, 1, , roundabout, opposite, 
-with, -1, , roundabout, opposite, 
-with, , yes, roundabout, opposite, 
-with, yes, yes, roundabout, opposite, 
-with, no, yes, roundabout, opposite, 
-with, 1, yes, roundabout, opposite, 
-with, -1, yes, roundabout, opposite, 
-both, , no, roundabout, opposite, 
-both, yes, no, roundabout, opposite, 
-both, no, no, roundabout, opposite, 
-both, 1, no, roundabout, opposite, 
-both, -1, no, roundabout, opposite, 
-with, , 1, roundabout, opposite, 
-with, yes, 1, roundabout, opposite, 
-with, no, 1, roundabout, opposite, 
-with, 1, 1, roundabout, opposite, 
-with, -1, 1, roundabout, opposite, 
-against, , -1, roundabout, opposite, 
-against, yes, -1, roundabout, opposite, 
-against, no, -1, roundabout, opposite, 
-against, 1, -1, roundabout, opposite, 
-against, -1, -1, roundabout, opposite, 
-both, , , , opposite_share_busway, 
-both, yes, , , opposite_share_busway, 
-both, no, , , opposite_share_busway, 
-both, 1, , , opposite_share_busway, 
-both, -1, , , opposite_share_busway, 
-with, , yes, , opposite_share_busway, 
-with, yes, yes, , opposite_share_busway, 
-with, no, yes, , opposite_share_busway, 
-with, 1, yes, , opposite_share_busway, 
-with, -1, yes, , opposite_share_busway, 
-both, , no, , opposite_share_busway, 
-both, yes, no, , opposite_share_busway, 
-both, no, no, , opposite_share_busway, 
-both, 1, no, , opposite_share_busway, 
-both, -1, no, , opposite_share_busway, 
-with, , 1, , opposite_share_busway, 
-with, yes, 1, , opposite_share_busway, 
-with, no, 1, , opposite_share_busway, 
-with, 1, 1, , opposite_share_busway, 
-with, -1, 1, , opposite_share_busway, 
-against, , -1, , opposite_share_busway, 
-against, yes, -1, , opposite_share_busway, 
-against, no, -1, , opposite_share_busway, 
-against, 1, -1, , opposite_share_busway, 
-against, -1, -1, , opposite_share_busway, 
-with, , , roundabout, opposite_share_busway, 
-with, yes, , roundabout, opposite_share_busway, 
-with, no, , roundabout, opposite_share_busway, 
-with, 1, , roundabout, opposite_share_busway, 
-with, -1, , roundabout, opposite_share_busway, 
-with, , yes, roundabout, opposite_share_busway, 
-with, yes, yes, roundabout, opposite_share_busway, 
-with, no, yes, roundabout, opposite_share_busway, 
-with, 1, yes, roundabout, opposite_share_busway, 
-with, -1, yes, roundabout, opposite_share_busway, 
-both, , no, roundabout, opposite_share_busway, 
-both, yes, no, roundabout, opposite_share_busway, 
-both, no, no, roundabout, opposite_share_busway, 
-both, 1, no, roundabout, opposite_share_busway, 
-both, -1, no, roundabout, opposite_share_busway, 
-with, , 1, roundabout, opposite_share_busway, 
-with, yes, 1, roundabout, opposite_share_busway, 
-with, no, 1, roundabout, opposite_share_busway, 
-with, 1, 1, roundabout, opposite_share_busway, 
-with, -1, 1, roundabout, opposite_share_busway, 
-against, , -1, roundabout, opposite_share_busway, 
-against, yes, -1, roundabout, opposite_share_busway, 
-against, no, -1, roundabout, opposite_share_busway, 
-against, 1, -1, roundabout, opposite_share_busway, 
-against, -1, -1, roundabout, opposite_share_busway, 
-both, , , , opposite_track, 
-both, yes, , , opposite_track, 
-both, no, , , opposite_track, 
-both, 1, , , opposite_track, 
-both, -1, , , opposite_track, 
-with, , yes, , opposite_track, 
-with, yes, yes, , opposite_track, 
-with, no, yes, , opposite_track, 
-with, 1, yes, , opposite_track, 
-with, -1, yes, , opposite_track, 
-both, , no, , opposite_track, 
-both, yes, no, , opposite_track, 
-both, no, no, , opposite_track, 
-both, 1, no, , opposite_track, 
-both, -1, no, , opposite_track, 
-with, , 1, , opposite_track, 
-with, yes, 1, , opposite_track, 
-with, no, 1, , opposite_track, 
-with, 1, 1, , opposite_track, 
-with, -1, 1, , opposite_track, 
-against, , -1, , opposite_track, 
-against, yes, -1, , opposite_track, 
-against, no, -1, , opposite_track, 
-against, 1, -1, , opposite_track, 
-against, -1, -1, , opposite_track, 
-with, , , roundabout, opposite_track, 
-with, yes, , roundabout, opposite_track, 
-with, no, , roundabout, opposite_track, 
-with, 1, , roundabout, opposite_track, 
-with, -1, , roundabout, opposite_track, 
-with, , yes, roundabout, opposite_track, 
-with, yes, yes, roundabout, opposite_track, 
-with, no, yes, roundabout, opposite_track, 
-with, 1, yes, roundabout, opposite_track, 
-with, -1, yes, roundabout, opposite_track, 
-both, , no, roundabout, opposite_track, 
-both, yes, no, roundabout, opposite_track, 
-both, no, no, roundabout, opposite_track, 
-both, 1, no, roundabout, opposite_track, 
-both, -1, no, roundabout, opposite_track, 
-with, , 1, roundabout, opposite_track, 
-with, yes, 1, roundabout, opposite_track, 
-with, no, 1, roundabout, opposite_track, 
-with, 1, 1, roundabout, opposite_track, 
-with, -1, 1, roundabout, opposite_track, 
-against, , -1, roundabout, opposite_track, 
-against, yes, -1, roundabout, opposite_track, 
-against, no, -1, roundabout, opposite_track, 
-against, 1, -1, roundabout, opposite_track, 
-against, -1, -1, roundabout, opposite_track, 
-with, , , , , no
-with, yes, , , , no
-with, no, , , , no
-with, 1, , , , no
-with, -1, , , , no
-with, , yes, , , no
-with, yes, yes, , , no
-with, no, yes, , , no
-with, 1, yes, , , no
-with, -1, yes, , , no
-both, , no, , , no
-both, yes, no, , , no
-both, no, no, , , no
-both, 1, no, , , no
-both, -1, no, , , no
-with, , 1, , , no
-with, yes, 1, , , no
-with, no, 1, , , no
-with, 1, 1, , , no
-with, -1, 1, , , no
-against, , -1, , , no
-against, yes, -1, , , no
-against, no, -1, , , no
-against, 1, -1, , , no
-against, -1, -1, , , no
-with, , , roundabout, , no
-with, yes, , roundabout, , no
-with, no, , roundabout, , no
-with, 1, , roundabout, , no
-with, -1, , roundabout, , no
-with, , yes, roundabout, , no
-with, yes, yes, roundabout, , no
-with, no, yes, roundabout, , no
-with, 1, yes, roundabout, , no
-with, -1, yes, roundabout, , no
-both, , no, roundabout, , no
-both, yes, no, roundabout, , no
-both, no, no, roundabout, , no
-both, 1, no, roundabout, , no
-both, -1, no, roundabout, , no
-with, , 1, roundabout, , no
-with, yes, 1, roundabout, , no
-with, no, 1, roundabout, , no
-with, 1, 1, roundabout, , no
-with, -1, 1, roundabout, , no
-against, , -1, roundabout, , no
-against, yes, -1, roundabout, , no
-against, no, -1, roundabout, , no
-against, 1, -1, roundabout, , no
-against, -1, -1, roundabout, , no
-against, , , , right, no
-against, yes, , , right, no
-against, no, , , right, no
-against, 1, , , right, no
-against, -1, , , right, no
-with, , yes, , right, no
-with, yes, yes, , right, no
-with, no, yes, , right, no
-with, 1, yes, , right, no
-with, -1, yes, , right, no
-both, , no, , right, no
-both, yes, no, , right, no
-both, no, no, , right, no
-both, 1, no, , right, no
-both, -1, no, , right, no
-with, , 1, , right, no
-with, yes, 1, , right, no
-with, no, 1, , right, no
-with, 1, 1, , right, no
-with, -1, 1, , right, no
-against, , -1, , right, no
-against, yes, -1, , right, no
-against, no, -1, , right, no
-against, 1, -1, , right, no
-against, -1, -1, , right, no
-with, , , roundabout, right, no
-with, yes, , roundabout, right, no
-with, no, , roundabout, right, no
-with, 1, , roundabout, right, no
-with, -1, , roundabout, right, no
-with, , yes, roundabout, right, no
-with, yes, yes, roundabout, right, no
-with, no, yes, roundabout, right, no
-with, 1, yes, roundabout, right, no
-with, -1, yes, roundabout, right, no
-both, , no, roundabout, right, no
-both, yes, no, roundabout, right, no
-both, no, no, roundabout, right, no
-both, 1, no, roundabout, right, no
-both, -1, no, roundabout, right, no
-with, , 1, roundabout, right, no
-with, yes, 1, roundabout, right, no
-with, no, 1, roundabout, right, no
-with, 1, 1, roundabout, right, no
-with, -1, 1, roundabout, right, no
-against, , -1, roundabout, right, no
-against, yes, -1, roundabout, right, no
-against, no, -1, roundabout, right, no
-against, 1, -1, roundabout, right, no
-against, -1, -1, roundabout, right, no
-both, , , , opposite_lane, no
-both, yes, , , opposite_lane, no
-both, no, , , opposite_lane, no
-both, 1, , , opposite_lane, no
-both, -1, , , opposite_lane, no
-with, , yes, , opposite_lane, no
-with, yes, yes, , opposite_lane, no
-with, no, yes, , opposite_lane, no
-with, 1, yes, , opposite_lane, no
-with, -1, yes, , opposite_lane, no
-both, , no, , opposite_lane, no
-both, yes, no, , opposite_lane, no
-both, no, no, , opposite_lane, no
-both, 1, no, , opposite_lane, no
-both, -1, no, , opposite_lane, no
-with, , 1, , opposite_lane, no
-with, yes, 1, , opposite_lane, no
-with, no, 1, , opposite_lane, no
-with, 1, 1, , opposite_lane, no
-with, -1, 1, , opposite_lane, no
-against, , -1, , opposite_lane, no
-against, yes, -1, , opposite_lane, no
-against, no, -1, , opposite_lane, no
-against, 1, -1, , opposite_lane, no
-against, -1, -1, , opposite_lane, no
-with, , , roundabout, opposite_lane, no
-with, yes, , roundabout, opposite_lane, no
-with, no, , roundabout, opposite_lane, no
-with, 1, , roundabout, opposite_lane, no
-with, -1, , roundabout, opposite_lane, no
-with, , yes, roundabout, opposite_lane, no
-with, yes, yes, roundabout, opposite_lane, no
-with, no, yes, roundabout, opposite_lane, no
-with, 1, yes, roundabout, opposite_lane, no
-with, -1, yes, roundabout, opposite_lane, no
-both, , no, roundabout, opposite_lane, no
-both, yes, no, roundabout, opposite_lane, no
-both, no, no, roundabout, opposite_lane, no
-both, 1, no, roundabout, opposite_lane, no
-both, -1, no, roundabout, opposite_lane, no
-with, , 1, roundabout, opposite_lane, no
-with, yes, 1, roundabout, opposite_lane, no
-with, no, 1, roundabout, opposite_lane, no
-with, 1, 1, roundabout, opposite_lane, no
-with, -1, 1, roundabout, opposite_lane, no
-against, , -1, roundabout, opposite_lane, no
-against, yes, -1, roundabout, opposite_lane, no
-against, no, -1, roundabout, opposite_lane, no
-against, 1, -1, roundabout, opposite_lane, no
-against, -1, -1, roundabout, opposite_lane, no
-both, , , , opposite, no
-both, yes, , , opposite, no
-both, no, , , opposite, no
-both, 1, , , opposite, no
-both, -1, , , opposite, no
-with, , yes, , opposite, no
-with, yes, yes, , opposite, no
-with, no, yes, , opposite, no
-with, 1, yes, , opposite, no
-with, -1, yes, , opposite, no
-both, , no, , opposite, no
-both, yes, no, , opposite, no
-both, no, no, , opposite, no
-both, 1, no, , opposite, no
-both, -1, no, , opposite, no
-with, , 1, , opposite, no
-with, yes, 1, , opposite, no
-with, no, 1, , opposite, no
-with, 1, 1, , opposite, no
-with, -1, 1, , opposite, no
-against, , -1, , opposite, no
-against, yes, -1, , opposite, no
-against, no, -1, , opposite, no
-against, 1, -1, , opposite, no
-against, -1, -1, , opposite, no
-with, , , roundabout, opposite, no
-with, yes, , roundabout, opposite, no
-with, no, , roundabout, opposite, no
-with, 1, , roundabout, opposite, no
-with, -1, , roundabout, opposite, no
-with, , yes, roundabout, opposite, no
-with, yes, yes, roundabout, opposite, no
-with, no, yes, roundabout, opposite, no
-with, 1, yes, roundabout, opposite, no
-with, -1, yes, roundabout, opposite, no
-both, , no, roundabout, opposite, no
-both, yes, no, roundabout, opposite, no
-both, no, no, roundabout, opposite, no
-both, 1, no, roundabout, opposite, no
-both, -1, no, roundabout, opposite, no
-with, , 1, roundabout, opposite, no
-with, yes, 1, roundabout, opposite, no
-with, no, 1, roundabout, opposite, no
-with, 1, 1, roundabout, opposite, no
-with, -1, 1, roundabout, opposite, no
-against, , -1, roundabout, opposite, no
-against, yes, -1, roundabout, opposite, no
-against, no, -1, roundabout, opposite, no
-against, 1, -1, roundabout, opposite, no
-against, -1, -1, roundabout, opposite, no
-both, , , , opposite_share_busway, no
-both, yes, , , opposite_share_busway, no
-both, no, , , opposite_share_busway, no
-both, 1, , , opposite_share_busway, no
-both, -1, , , opposite_share_busway, no
-with, , yes, , opposite_share_busway, no
-with, yes, yes, , opposite_share_busway, no
-with, no, yes, , opposite_share_busway, no
-with, 1, yes, , opposite_share_busway, no
-with, -1, yes, , opposite_share_busway, no
-both, , no, , opposite_share_busway, no
-both, yes, no, , opposite_share_busway, no
-both, no, no, , opposite_share_busway, no
-both, 1, no, , opposite_share_busway, no
-both, -1, no, , opposite_share_busway, no
-with, , 1, , opposite_share_busway, no
-with, yes, 1, , opposite_share_busway, no
-with, no, 1, , opposite_share_busway, no
-with, 1, 1, , opposite_share_busway, no
-with, -1, 1, , opposite_share_busway, no
-against, , -1, , opposite_share_busway, no
-against, yes, -1, , opposite_share_busway, no
-against, no, -1, , opposite_share_busway, no
-against, 1, -1, , opposite_share_busway, no
-against, -1, -1, , opposite_share_busway, no
-with, , , roundabout, opposite_share_busway, no
-with, yes, , roundabout, opposite_share_busway, no
-with, no, , roundabout, opposite_share_busway, no
-with, 1, , roundabout, opposite_share_busway, no
-with, -1, , roundabout, opposite_share_busway, no
-with, , yes, roundabout, opposite_share_busway, no
-with, yes, yes, roundabout, opposite_share_busway, no
-with, no, yes, roundabout, opposite_share_busway, no
-with, 1, yes, roundabout, opposite_share_busway, no
-with, -1, yes, roundabout, opposite_share_busway, no
-both, , no, roundabout, opposite_share_busway, no
-both, yes, no, roundabout, opposite_share_busway, no
-both, no, no, roundabout, opposite_share_busway, no
-both, 1, no, roundabout, opposite_share_busway, no
-both, -1, no, roundabout, opposite_share_busway, no
-with, , 1, roundabout, opposite_share_busway, no
-with, yes, 1, roundabout, opposite_share_busway, no
-with, no, 1, roundabout, opposite_share_busway, no
-with, 1, 1, roundabout, opposite_share_busway, no
-with, -1, 1, roundabout, opposite_share_busway, no
-against, , -1, roundabout, opposite_share_busway, no
-against, yes, -1, roundabout, opposite_share_busway, no
-against, no, -1, roundabout, opposite_share_busway, no
-against, 1, -1, roundabout, opposite_share_busway, no
-against, -1, -1, roundabout, opposite_share_busway, no
-both, , , , opposite_track, no
-both, yes, , , opposite_track, no
-both, no, , , opposite_track, no
-both, 1, , , opposite_track, no
-both, -1, , , opposite_track, no
-with, , yes, , opposite_track, no
-with, yes, yes, , opposite_track, no
-with, no, yes, , opposite_track, no
-with, 1, yes, , opposite_track, no
-with, -1, yes, , opposite_track, no
-both, , no, , opposite_track, no
-both, yes, no, , opposite_track, no
-both, no, no, , opposite_track, no
-both, 1, no, , opposite_track, no
-both, -1, no, , opposite_track, no
-with, , 1, , opposite_track, no
-with, yes, 1, , opposite_track, no
-with, no, 1, , opposite_track, no
-with, 1, 1, , opposite_track, no
-with, -1, 1, , opposite_track, no
-against, , -1, , opposite_track, no
-against, yes, -1, , opposite_track, no
-against, no, -1, , opposite_track, no
-against, 1, -1, , opposite_track, no
-against, -1, -1, , opposite_track, no
-with, , , roundabout, opposite_track, no
-with, yes, , roundabout, opposite_track, no
-with, no, , roundabout, opposite_track, no
-with, 1, , roundabout, opposite_track, no
-with, -1, , roundabout, opposite_track, no
-with, , yes, roundabout, opposite_track, no
-with, yes, yes, roundabout, opposite_track, no
-with, no, yes, roundabout, opposite_track, no
-with, 1, yes, roundabout, opposite_track, no
-with, -1, yes, roundabout, opposite_track, no
-both, , no, roundabout, opposite_track, no
-both, yes, no, roundabout, opposite_track, no
-both, no, no, roundabout, opposite_track, no
-both, 1, no, roundabout, opposite_track, no
-both, -1, no, roundabout, opposite_track, no
-with, , 1, roundabout, opposite_track, no
-with, yes, 1, roundabout, opposite_track, no
-with, no, 1, roundabout, opposite_track, no
-with, 1, 1, roundabout, opposite_track, no
-with, -1, 1, roundabout, opposite_track, no
-against, , -1, roundabout, opposite_track, no
-against, yes, -1, roundabout, opposite_track, no
-against, no, -1, roundabout, opposite_track, no
-against, 1, -1, roundabout, opposite_track, no
-against, -1, -1, roundabout, opposite_track, no
-both, , , , , yes
-both, yes, , , , yes
-both, no, , , , yes
-both, 1, , , , yes
-both, -1, , , , yes
-with, , yes, , , yes
-with, yes, yes, , , yes
-with, no, yes, , , yes
-with, 1, yes, , , yes
-with, -1, yes, , , yes
-both, , no, , , yes
-both, yes, no, , , yes
-both, no, no, , , yes
-both, 1, no, , , yes
-both, -1, no, , , yes
-with, , 1, , , yes
-with, yes, 1, , , yes
-with, no, 1, , , yes
-with, 1, 1, , , yes
-with, -1, 1, , , yes
-against, , -1, , , yes
-against, yes, -1, , , yes
-against, no, -1, , , yes
-against, 1, -1, , , yes
-against, -1, -1, , , yes
-with, , , roundabout, , yes
-with, yes, , roundabout, , yes
-with, no, , roundabout, , yes
-with, 1, , roundabout, , yes
-with, -1, , roundabout, , yes
-with, , yes, roundabout, , yes
-with, yes, yes, roundabout, , yes
-with, no, yes, roundabout, , yes
-with, 1, yes, roundabout, , yes
-with, -1, yes, roundabout, , yes
-both, , no, roundabout, , yes
-both, yes, no, roundabout, , yes
-both, no, no, roundabout, , yes
-both, 1, no, roundabout, , yes
-both, -1, no, roundabout, , yes
-with, , 1, roundabout, , yes
-with, yes, 1, roundabout, , yes
-with, no, 1, roundabout, , yes
-with, 1, 1, roundabout, , yes
-with, -1, 1, roundabout, , yes
-against, , -1, roundabout, , yes
-against, yes, -1, roundabout, , yes
-against, no, -1, roundabout, , yes
-against, 1, -1, roundabout, , yes
-against, -1, -1, roundabout, , yes
-against, , , , right, yes
-against, yes, , , right, yes
-against, no, , , right, yes
-against, 1, , , right, yes
-against, -1, , , right, yes
-with, , yes, , right, yes
-with, yes, yes, , right, yes
-with, no, yes, , right, yes
-with, 1, yes, , right, yes
-with, -1, yes, , right, yes
-both, , no, , right, yes
-both, yes, no, , right, yes
-both, no, no, , right, yes
-both, 1, no, , right, yes
-both, -1, no, , right, yes
-with, , 1, , right, yes
-with, yes, 1, , right, yes
-with, no, 1, , right, yes
-with, 1, 1, , right, yes
-with, -1, 1, , right, yes
-against, , -1, , right, yes
-against, yes, -1, , right, yes
-against, no, -1, , right, yes
-against, 1, -1, , right, yes
-against, -1, -1, , right, yes
-with, , , roundabout, right, yes
-with, yes, , roundabout, right, yes
-with, no, , roundabout, right, yes
-with, 1, , roundabout, right, yes
-with, -1, , roundabout, right, yes
-with, , yes, roundabout, right, yes
-with, yes, yes, roundabout, right, yes
-with, no, yes, roundabout, right, yes
-with, 1, yes, roundabout, right, yes
-with, -1, yes, roundabout, right, yes
-both, , no, roundabout, right, yes
-both, yes, no, roundabout, right, yes
-both, no, no, roundabout, right, yes
-both, 1, no, roundabout, right, yes
-both, -1, no, roundabout, right, yes
-with, , 1, roundabout, right, yes
-with, yes, 1, roundabout, right, yes
-with, no, 1, roundabout, right, yes
-with, 1, 1, roundabout, right, yes
-with, -1, 1, roundabout, right, yes
-against, , -1, roundabout, right, yes
-against, yes, -1, roundabout, right, yes
-against, no, -1, roundabout, right, yes
-against, 1, -1, roundabout, right, yes
-against, -1, -1, roundabout, right, yes
-both, , , , opposite_lane, yes
-both, yes, , , opposite_lane, yes
-both, no, , , opposite_lane, yes
-both, 1, , , opposite_lane, yes
-both, -1, , , opposite_lane, yes
-with, , yes, , opposite_lane, yes
-with, yes, yes, , opposite_lane, yes
-with, no, yes, , opposite_lane, yes
-with, 1, yes, , opposite_lane, yes
-with, -1, yes, , opposite_lane, yes
-both, , no, , opposite_lane, yes
-both, yes, no, , opposite_lane, yes
-both, no, no, , opposite_lane, yes
-both, 1, no, , opposite_lane, yes
-both, -1, no, , opposite_lane, yes
-with, , 1, , opposite_lane, yes
-with, yes, 1, , opposite_lane, yes
-with, no, 1, , opposite_lane, yes
-with, 1, 1, , opposite_lane, yes
-with, -1, 1, , opposite_lane, yes
-against, , -1, , opposite_lane, yes
-against, yes, -1, , opposite_lane, yes
-against, no, -1, , opposite_lane, yes
-against, 1, -1, , opposite_lane, yes
-against, -1, -1, , opposite_lane, yes
-with, , , roundabout, opposite_lane, yes
-with, yes, , roundabout, opposite_lane, yes
-with, no, , roundabout, opposite_lane, yes
-with, 1, , roundabout, opposite_lane, yes
-with, -1, , roundabout, opposite_lane, yes
-with, , yes, roundabout, opposite_lane, yes
-with, yes, yes, roundabout, opposite_lane, yes
-with, no, yes, roundabout, opposite_lane, yes
-with, 1, yes, roundabout, opposite_lane, yes
-with, -1, yes, roundabout, opposite_lane, yes
-both, , no, roundabout, opposite_lane, yes
-both, yes, no, roundabout, opposite_lane, yes
-both, no, no, roundabout, opposite_lane, yes
-both, 1, no, roundabout, opposite_lane, yes
-both, -1, no, roundabout, opposite_lane, yes
-with, , 1, roundabout, opposite_lane, yes
-with, yes, 1, roundabout, opposite_lane, yes
-with, no, 1, roundabout, opposite_lane, yes
-with, 1, 1, roundabout, opposite_lane, yes
-with, -1, 1, roundabout, opposite_lane, yes
-against, , -1, roundabout, opposite_lane, yes
-against, yes, -1, roundabout, opposite_lane, yes
-against, no, -1, roundabout, opposite_lane, yes
-against, 1, -1, roundabout, opposite_lane, yes
-against, -1, -1, roundabout, opposite_lane, yes
-both, , , , opposite, yes
-both, yes, , , opposite, yes
-both, no, , , opposite, yes
-both, 1, , , opposite, yes
-both, -1, , , opposite, yes
-with, , yes, , opposite, yes
-with, yes, yes, , opposite, yes
-with, no, yes, , opposite, yes
-with, 1, yes, , opposite, yes
-with, -1, yes, , opposite, yes
-both, , no, , opposite, yes
-both, yes, no, , opposite, yes
-both, no, no, , opposite, yes
-both, 1, no, , opposite, yes
-both, -1, no, , opposite, yes
-with, , 1, , opposite, yes
-with, yes, 1, , opposite, yes
-with, no, 1, , opposite, yes
-with, 1, 1, , opposite, yes
-with, -1, 1, , opposite, yes
-against, , -1, , opposite, yes
-against, yes, -1, , opposite, yes
-against, no, -1, , opposite, yes
-against, 1, -1, , opposite, yes
-against, -1, -1, , opposite, yes
-with, , , roundabout, opposite, yes
-with, yes, , roundabout, opposite, yes
-with, no, , roundabout, opposite, yes
-with, 1, , roundabout, opposite, yes
-with, -1, , roundabout, opposite, yes
-with, , yes, roundabout, opposite, yes
-with, yes, yes, roundabout, opposite, yes
-with, no, yes, roundabout, opposite, yes
-with, 1, yes, roundabout, opposite, yes
-with, -1, yes, roundabout, opposite, yes
-both, , no, roundabout, opposite, yes
-both, yes, no, roundabout, opposite, yes
-both, no, no, roundabout, opposite, yes
-both, 1, no, roundabout, opposite, yes
-both, -1, no, roundabout, opposite, yes
-with, , 1, roundabout, opposite, yes
-with, yes, 1, roundabout, opposite, yes
-with, no, 1, roundabout, opposite, yes
-with, 1, 1, roundabout, opposite, yes
-with, -1, 1, roundabout, opposite, yes
-against, , -1, roundabout, opposite, yes
-against, yes, -1, roundabout, opposite, yes
-against, no, -1, roundabout, opposite, yes
-against, 1, -1, roundabout, opposite, yes
-against, -1, -1, roundabout, opposite, yes
-both, , , , opposite_share_busway, yes
-both, yes, , , opposite_share_busway, yes
-both, no, , , opposite_share_busway, yes
-both, 1, , , opposite_share_busway, yes
-both, -1, , , opposite_share_busway, yes
-with, , yes, , opposite_share_busway, yes
-with, yes, yes, , opposite_share_busway, yes
-with, no, yes, , opposite_share_busway, yes
-with, 1, yes, , opposite_share_busway, yes
-with, -1, yes, , opposite_share_busway, yes
-both, , no, , opposite_share_busway, yes
-both, yes, no, , opposite_share_busway, yes
-both, no, no, , opposite_share_busway, yes
-both, 1, no, , opposite_share_busway, yes
-both, -1, no, , opposite_share_busway, yes
-with, , 1, , opposite_share_busway, yes
-with, yes, 1, , opposite_share_busway, yes
-with, no, 1, , opposite_share_busway, yes
-with, 1, 1, , opposite_share_busway, yes
-with, -1, 1, , opposite_share_busway, yes
-against, , -1, , opposite_share_busway, yes
-against, yes, -1, , opposite_share_busway, yes
-against, no, -1, , opposite_share_busway, yes
-against, 1, -1, , opposite_share_busway, yes
-against, -1, -1, , opposite_share_busway, yes
-with, , , roundabout, opposite_share_busway, yes
-with, yes, , roundabout, opposite_share_busway, yes
-with, no, , roundabout, opposite_share_busway, yes
-with, 1, , roundabout, opposite_share_busway, yes
-with, -1, , roundabout, opposite_share_busway, yes
-with, , yes, roundabout, opposite_share_busway, yes
-with, yes, yes, roundabout, opposite_share_busway, yes
-with, no, yes, roundabout, opposite_share_busway, yes
-with, 1, yes, roundabout, opposite_share_busway, yes
-with, -1, yes, roundabout, opposite_share_busway, yes
-both, , no, roundabout, opposite_share_busway, yes
-both, yes, no, roundabout, opposite_share_busway, yes
-both, no, no, roundabout, opposite_share_busway, yes
-both, 1, no, roundabout, opposite_share_busway, yes
-both, -1, no, roundabout, opposite_share_busway, yes
-with, , 1, roundabout, opposite_share_busway, yes
-with, yes, 1, roundabout, opposite_share_busway, yes
-with, no, 1, roundabout, opposite_share_busway, yes
-with, 1, 1, roundabout, opposite_share_busway, yes
-with, -1, 1, roundabout, opposite_share_busway, yes
-against, , -1, roundabout, opposite_share_busway, yes
-against, yes, -1, roundabout, opposite_share_busway, yes
-against, no, -1, roundabout, opposite_share_busway, yes
-against, 1, -1, roundabout, opposite_share_busway, yes
-against, -1, -1, roundabout, opposite_share_busway, yes
-both, , , , opposite_track, yes
-both, yes, , , opposite_track, yes
-both, no, , , opposite_track, yes
-both, 1, , , opposite_track, yes
-both, -1, , , opposite_track, yes
-with, , yes, , opposite_track, yes
-with, yes, yes, , opposite_track, yes
-with, no, yes, , opposite_track, yes
-with, 1, yes, , opposite_track, yes
-with, -1, yes, , opposite_track, yes
-both, , no, , opposite_track, yes
-both, yes, no, , opposite_track, yes
-both, no, no, , opposite_track, yes
-both, 1, no, , opposite_track, yes
-both, -1, no, , opposite_track, yes
-with, , 1, , opposite_track, yes
-with, yes, 1, , opposite_track, yes
-with, no, 1, , opposite_track, yes
-with, 1, 1, , opposite_track, yes
-with, -1, 1, , opposite_track, yes
-against, , -1, , opposite_track, yes
-against, yes, -1, , opposite_track, yes
-against, no, -1, , opposite_track, yes
-against, 1, -1, , opposite_track, yes
-against, -1, -1, , opposite_track, yes
-with, , , roundabout, opposite_track, yes
-with, yes, , roundabout, opposite_track, yes
-with, no, , roundabout, opposite_track, yes
-with, 1, , roundabout, opposite_track, yes
-with, -1, , roundabout, opposite_track, yes
-with, , yes, roundabout, opposite_track, yes
-with, yes, yes, roundabout, opposite_track, yes
-with, no, yes, roundabout, opposite_track, yes
-with, 1, yes, roundabout, opposite_track, yes
-with, -1, yes, roundabout, opposite_track, yes
-both, , no, roundabout, opposite_track, yes
-both, yes, no, roundabout, opposite_track, yes
-both, no, no, roundabout, opposite_track, yes
-both, 1, no, roundabout, opposite_track, yes
-both, -1, no, roundabout, opposite_track, yes
-with, , 1, roundabout, opposite_track, yes
-with, yes, 1, roundabout, opposite_track, yes
-with, no, 1, roundabout, opposite_track, yes
-with, 1, 1, roundabout, opposite_track, yes
-with, -1, 1, roundabout, opposite_track, yes
-against, , -1, roundabout, opposite_track, yes
-against, yes, -1, roundabout, opposite_track, yes
-against, no, -1, roundabout, opposite_track, yes
-against, 1, -1, roundabout, opposite_track, yes
-against, -1, -1, roundabout, opposite_track, yes
-both, , , , , lane
-both, yes, , , , lane
-both, no, , , , lane
-both, 1, , , , lane
-both, -1, , , , lane
-with, , yes, , , lane
-with, yes, yes, , , lane
-with, no, yes, , , lane
-with, 1, yes, , , lane
-with, -1, yes, , , lane
-both, , no, , , lane
-both, yes, no, , , lane
-both, no, no, , , lane
-both, 1, no, , , lane
-both, -1, no, , , lane
-with, , 1, , , lane
-with, yes, 1, , , lane
-with, no, 1, , , lane
-with, 1, 1, , , lane
-with, -1, 1, , , lane
-against, , -1, , , lane
-against, yes, -1, , , lane
-against, no, -1, , , lane
-against, 1, -1, , , lane
-against, -1, -1, , , lane
-with, , , roundabout, , lane
-with, yes, , roundabout, , lane
-with, no, , roundabout, , lane
-with, 1, , roundabout, , lane
-with, -1, , roundabout, , lane
-with, , yes, roundabout, , lane
-with, yes, yes, roundabout, , lane
-with, no, yes, roundabout, , lane
-with, 1, yes, roundabout, , lane
-with, -1, yes, roundabout, , lane
-both, , no, roundabout, , lane
-both, yes, no, roundabout, , lane
-both, no, no, roundabout, , lane
-both, 1, no, roundabout, , lane
-both, -1, no, roundabout, , lane
-with, , 1, roundabout, , lane
-with, yes, 1, roundabout, , lane
-with, no, 1, roundabout, , lane
-with, 1, 1, roundabout, , lane
-with, -1, 1, roundabout, , lane
-against, , -1, roundabout, , lane
-against, yes, -1, roundabout, , lane
-against, no, -1, roundabout, , lane
-against, 1, -1, roundabout, , lane
-against, -1, -1, roundabout, , lane
-against, , , , right, lane
-against, yes, , , right, lane
-against, no, , , right, lane
-against, 1, , , right, lane
-against, -1, , , right, lane
-with, , yes, , right, lane
-with, yes, yes, , right, lane
-with, no, yes, , right, lane
-with, 1, yes, , right, lane
-with, -1, yes, , right, lane
-both, , no, , right, lane
-both, yes, no, , right, lane
-both, no, no, , right, lane
-both, 1, no, , right, lane
-both, -1, no, , right, lane
-with, , 1, , right, lane
-with, yes, 1, , right, lane
-with, no, 1, , right, lane
-with, 1, 1, , right, lane
-with, -1, 1, , right, lane
-against, , -1, , right, lane
-against, yes, -1, , right, lane
-against, no, -1, , right, lane
-against, 1, -1, , right, lane
-against, -1, -1, , right, lane
-with, , , roundabout, right, lane
-with, yes, , roundabout, right, lane
-with, no, , roundabout, right, lane
-with, 1, , roundabout, right, lane
-with, -1, , roundabout, right, lane
-with, , yes, roundabout, right, lane
-with, yes, yes, roundabout, right, lane
-with, no, yes, roundabout, right, lane
-with, 1, yes, roundabout, right, lane
-with, -1, yes, roundabout, right, lane
-both, , no, roundabout, right, lane
-both, yes, no, roundabout, right, lane
-both, no, no, roundabout, right, lane
-both, 1, no, roundabout, right, lane
-both, -1, no, roundabout, right, lane
-with, , 1, roundabout, right, lane
-with, yes, 1, roundabout, right, lane
-with, no, 1, roundabout, right, lane
-with, 1, 1, roundabout, right, lane
-with, -1, 1, roundabout, right, lane
-against, , -1, roundabout, right, lane
-against, yes, -1, roundabout, right, lane
-against, no, -1, roundabout, right, lane
-against, 1, -1, roundabout, right, lane
-against, -1, -1, roundabout, right, lane
-both, , , , opposite_lane, lane
-both, yes, , , opposite_lane, lane
-both, no, , , opposite_lane, lane
-both, 1, , , opposite_lane, lane
-both, -1, , , opposite_lane, lane
-with, , yes, , opposite_lane, lane
-with, yes, yes, , opposite_lane, lane
-with, no, yes, , opposite_lane, lane
-with, 1, yes, , opposite_lane, lane
-with, -1, yes, , opposite_lane, lane
-both, , no, , opposite_lane, lane
-both, yes, no, , opposite_lane, lane
-both, no, no, , opposite_lane, lane
-both, 1, no, , opposite_lane, lane
-both, -1, no, , opposite_lane, lane
-with, , 1, , opposite_lane, lane
-with, yes, 1, , opposite_lane, lane
-with, no, 1, , opposite_lane, lane
-with, 1, 1, , opposite_lane, lane
-with, -1, 1, , opposite_lane, lane
-against, , -1, , opposite_lane, lane
-against, yes, -1, , opposite_lane, lane
-against, no, -1, , opposite_lane, lane
-against, 1, -1, , opposite_lane, lane
-against, -1, -1, , opposite_lane, lane
-with, , , roundabout, opposite_lane, lane
-with, yes, , roundabout, opposite_lane, lane
-with, no, , roundabout, opposite_lane, lane
-with, 1, , roundabout, opposite_lane, lane
-with, -1, , roundabout, opposite_lane, lane
-with, , yes, roundabout, opposite_lane, lane
-with, yes, yes, roundabout, opposite_lane, lane
-with, no, yes, roundabout, opposite_lane, lane
-with, 1, yes, roundabout, opposite_lane, lane
-with, -1, yes, roundabout, opposite_lane, lane
-both, , no, roundabout, opposite_lane, lane
-both, yes, no, roundabout, opposite_lane, lane
-both, no, no, roundabout, opposite_lane, lane
-both, 1, no, roundabout, opposite_lane, lane
-both, -1, no, roundabout, opposite_lane, lane
-with, , 1, roundabout, opposite_lane, lane
-with, yes, 1, roundabout, opposite_lane, lane
-with, no, 1, roundabout, opposite_lane, lane
-with, 1, 1, roundabout, opposite_lane, lane
-with, -1, 1, roundabout, opposite_lane, lane
-against, , -1, roundabout, opposite_lane, lane
-against, yes, -1, roundabout, opposite_lane, lane
-against, no, -1, roundabout, opposite_lane, lane
-against, 1, -1, roundabout, opposite_lane, lane
-against, -1, -1, roundabout, opposite_lane, lane
-both, , , , opposite, lane
-both, yes, , , opposite, lane
-both, no, , , opposite, lane
-both, 1, , , opposite, lane
-both, -1, , , opposite, lane
-with, , yes, , opposite, lane
-with, yes, yes, , opposite, lane
-with, no, yes, , opposite, lane
-with, 1, yes, , opposite, lane
-with, -1, yes, , opposite, lane
-both, , no, , opposite, lane
-both, yes, no, , opposite, lane
-both, no, no, , opposite, lane
-both, 1, no, , opposite, lane
-both, -1, no, , opposite, lane
-with, , 1, , opposite, lane
-with, yes, 1, , opposite, lane
-with, no, 1, , opposite, lane
-with, 1, 1, , opposite, lane
-with, -1, 1, , opposite, lane
-against, , -1, , opposite, lane
-against, yes, -1, , opposite, lane
-against, no, -1, , opposite, lane
-against, 1, -1, , opposite, lane
-against, -1, -1, , opposite, lane
-with, , , roundabout, opposite, lane
-with, yes, , roundabout, opposite, lane
-with, no, , roundabout, opposite, lane
-with, 1, , roundabout, opposite, lane
-with, -1, , roundabout, opposite, lane
-with, , yes, roundabout, opposite, lane
-with, yes, yes, roundabout, opposite, lane
-with, no, yes, roundabout, opposite, lane
-with, 1, yes, roundabout, opposite, lane
-with, -1, yes, roundabout, opposite, lane
-both, , no, roundabout, opposite, lane
-both, yes, no, roundabout, opposite, lane
-both, no, no, roundabout, opposite, lane
-both, 1, no, roundabout, opposite, lane
-both, -1, no, roundabout, opposite, lane
-with, , 1, roundabout, opposite, lane
-with, yes, 1, roundabout, opposite, lane
-with, no, 1, roundabout, opposite, lane
-with, 1, 1, roundabout, opposite, lane
-with, -1, 1, roundabout, opposite, lane
-against, , -1, roundabout, opposite, lane
-against, yes, -1, roundabout, opposite, lane
-against, no, -1, roundabout, opposite, lane
-against, 1, -1, roundabout, opposite, lane
-against, -1, -1, roundabout, opposite, lane
-both, , , , opposite_share_busway, lane
-both, yes, , , opposite_share_busway, lane
-both, no, , , opposite_share_busway, lane
-both, 1, , , opposite_share_busway, lane
-both, -1, , , opposite_share_busway, lane
-with, , yes, , opposite_share_busway, lane
-with, yes, yes, , opposite_share_busway, lane
-with, no, yes, , opposite_share_busway, lane
-with, 1, yes, , opposite_share_busway, lane
-with, -1, yes, , opposite_share_busway, lane
-both, , no, , opposite_share_busway, lane
-both, yes, no, , opposite_share_busway, lane
-both, no, no, , opposite_share_busway, lane
-both, 1, no, , opposite_share_busway, lane
-both, -1, no, , opposite_share_busway, lane
-with, , 1, , opposite_share_busway, lane
-with, yes, 1, , opposite_share_busway, lane
-with, no, 1, , opposite_share_busway, lane
-with, 1, 1, , opposite_share_busway, lane
-with, -1, 1, , opposite_share_busway, lane
-against, , -1, , opposite_share_busway, lane
-against, yes, -1, , opposite_share_busway, lane
-against, no, -1, , opposite_share_busway, lane
-against, 1, -1, , opposite_share_busway, lane
-against, -1, -1, , opposite_share_busway, lane
-with, , , roundabout, opposite_share_busway, lane
-with, yes, , roundabout, opposite_share_busway, lane
-with, no, , roundabout, opposite_share_busway, lane
-with, 1, , roundabout, opposite_share_busway, lane
-with, -1, , roundabout, opposite_share_busway, lane
-with, , yes, roundabout, opposite_share_busway, lane
-with, yes, yes, roundabout, opposite_share_busway, lane
-with, no, yes, roundabout, opposite_share_busway, lane
-with, 1, yes, roundabout, opposite_share_busway, lane
-with, -1, yes, roundabout, opposite_share_busway, lane
-both, , no, roundabout, opposite_share_busway, lane
-both, yes, no, roundabout, opposite_share_busway, lane
-both, no, no, roundabout, opposite_share_busway, lane
-both, 1, no, roundabout, opposite_share_busway, lane
-both, -1, no, roundabout, opposite_share_busway, lane
-with, , 1, roundabout, opposite_share_busway, lane
-with, yes, 1, roundabout, opposite_share_busway, lane
-with, no, 1, roundabout, opposite_share_busway, lane
-with, 1, 1, roundabout, opposite_share_busway, lane
-with, -1, 1, roundabout, opposite_share_busway, lane
-against, , -1, roundabout, opposite_share_busway, lane
-against, yes, -1, roundabout, opposite_share_busway, lane
-against, no, -1, roundabout, opposite_share_busway, lane
-against, 1, -1, roundabout, opposite_share_busway, lane
-against, -1, -1, roundabout, opposite_share_busway, lane
-both, , , , opposite_track, lane
-both, yes, , , opposite_track, lane
-both, no, , , opposite_track, lane
-both, 1, , , opposite_track, lane
-both, -1, , , opposite_track, lane
-with, , yes, , opposite_track, lane
-with, yes, yes, , opposite_track, lane
-with, no, yes, , opposite_track, lane
-with, 1, yes, , opposite_track, lane
-with, -1, yes, , opposite_track, lane
-both, , no, , opposite_track, lane
-both, yes, no, , opposite_track, lane
-both, no, no, , opposite_track, lane
-both, 1, no, , opposite_track, lane
-both, -1, no, , opposite_track, lane
-with, , 1, , opposite_track, lane
-with, yes, 1, , opposite_track, lane
-with, no, 1, , opposite_track, lane
-with, 1, 1, , opposite_track, lane
-with, -1, 1, , opposite_track, lane
-against, , -1, , opposite_track, lane
-against, yes, -1, , opposite_track, lane
-against, no, -1, , opposite_track, lane
-against, 1, -1, , opposite_track, lane
-against, -1, -1, , opposite_track, lane
-with, , , roundabout, opposite_track, lane
-with, yes, , roundabout, opposite_track, lane
-with, no, , roundabout, opposite_track, lane
-with, 1, , roundabout, opposite_track, lane
-with, -1, , roundabout, opposite_track, lane
-with, , yes, roundabout, opposite_track, lane
-with, yes, yes, roundabout, opposite_track, lane
-with, no, yes, roundabout, opposite_track, lane
-with, 1, yes, roundabout, opposite_track, lane
-with, -1, yes, roundabout, opposite_track, lane
-both, , no, roundabout, opposite_track, lane
-both, yes, no, roundabout, opposite_track, lane
-both, no, no, roundabout, opposite_track, lane
-both, 1, no, roundabout, opposite_track, lane
-both, -1, no, roundabout, opposite_track, lane
-with, , 1, roundabout, opposite_track, lane
-with, yes, 1, roundabout, opposite_track, lane
-with, no, 1, roundabout, opposite_track, lane
-with, 1, 1, roundabout, opposite_track, lane
-with, -1, 1, roundabout, opposite_track, lane
-against, , -1, roundabout, opposite_track, lane
-against, yes, -1, roundabout, opposite_track, lane
-against, no, -1, roundabout, opposite_track, lane
-against, 1, -1, roundabout, opposite_track, lane
-against, -1, -1, roundabout, opposite_track, lane
-both, , , , , shared_lane
-both, yes, , , , shared_lane
-both, no, , , , shared_lane
-both, 1, , , , shared_lane
-both, -1, , , , shared_lane
-with, , yes, , , shared_lane
-with, yes, yes, , , shared_lane
-with, no, yes, , , shared_lane
-with, 1, yes, , , shared_lane
-with, -1, yes, , , shared_lane
-both, , no, , , shared_lane
-both, yes, no, , , shared_lane
-both, no, no, , , shared_lane
-both, 1, no, , , shared_lane
-both, -1, no, , , shared_lane
-with, , 1, , , shared_lane
-with, yes, 1, , , shared_lane
-with, no, 1, , , shared_lane
-with, 1, 1, , , shared_lane
-with, -1, 1, , , shared_lane
-against, , -1, , , shared_lane
-against, yes, -1, , , shared_lane
-against, no, -1, , , shared_lane
-against, 1, -1, , , shared_lane
-against, -1, -1, , , shared_lane
-with, , , roundabout, , shared_lane
-with, yes, , roundabout, , shared_lane
-with, no, , roundabout, , shared_lane
-with, 1, , roundabout, , shared_lane
-with, -1, , roundabout, , shared_lane
-with, , yes, roundabout, , shared_lane
-with, yes, yes, roundabout, , shared_lane
-with, no, yes, roundabout, , shared_lane
-with, 1, yes, roundabout, , shared_lane
-with, -1, yes, roundabout, , shared_lane
-both, , no, roundabout, , shared_lane
-both, yes, no, roundabout, , shared_lane
-both, no, no, roundabout, , shared_lane
-both, 1, no, roundabout, , shared_lane
-both, -1, no, roundabout, , shared_lane
-with, , 1, roundabout, , shared_lane
-with, yes, 1, roundabout, , shared_lane
-with, no, 1, roundabout, , shared_lane
-with, 1, 1, roundabout, , shared_lane
-with, -1, 1, roundabout, , shared_lane
-against, , -1, roundabout, , shared_lane
-against, yes, -1, roundabout, , shared_lane
-against, no, -1, roundabout, , shared_lane
-against, 1, -1, roundabout, , shared_lane
-against, -1, -1, roundabout, , shared_lane
-against, , , , right, shared_lane
-against, yes, , , right, shared_lane
-against, no, , , right, shared_lane
-against, 1, , , right, shared_lane
-against, -1, , , right, shared_lane
-with, , yes, , right, shared_lane
-with, yes, yes, , right, shared_lane
-with, no, yes, , right, shared_lane
-with, 1, yes, , right, shared_lane
-with, -1, yes, , right, shared_lane
-both, , no, , right, shared_lane
-both, yes, no, , right, shared_lane
-both, no, no, , right, shared_lane
-both, 1, no, , right, shared_lane
-both, -1, no, , right, shared_lane
-with, , 1, , right, shared_lane
-with, yes, 1, , right, shared_lane
-with, no, 1, , right, shared_lane
-with, 1, 1, , right, shared_lane
-with, -1, 1, , right, shared_lane
-against, , -1, , right, shared_lane
-against, yes, -1, , right, shared_lane
-against, no, -1, , right, shared_lane
-against, 1, -1, , right, shared_lane
-against, -1, -1, , right, shared_lane
-with, , , roundabout, right, shared_lane
-with, yes, , roundabout, right, shared_lane
-with, no, , roundabout, right, shared_lane
-with, 1, , roundabout, right, shared_lane
-with, -1, , roundabout, right, shared_lane
-with, , yes, roundabout, right, shared_lane
-with, yes, yes, roundabout, right, shared_lane
-with, no, yes, roundabout, right, shared_lane
-with, 1, yes, roundabout, right, shared_lane
-with, -1, yes, roundabout, right, shared_lane
-both, , no, roundabout, right, shared_lane
-both, yes, no, roundabout, right, shared_lane
-both, no, no, roundabout, right, shared_lane
-both, 1, no, roundabout, right, shared_lane
-both, -1, no, roundabout, right, shared_lane
-with, , 1, roundabout, right, shared_lane
-with, yes, 1, roundabout, right, shared_lane
-with, no, 1, roundabout, right, shared_lane
-with, 1, 1, roundabout, right, shared_lane
-with, -1, 1, roundabout, right, shared_lane
-against, , -1, roundabout, right, shared_lane
-against, yes, -1, roundabout, right, shared_lane
-against, no, -1, roundabout, right, shared_lane
-against, 1, -1, roundabout, right, shared_lane
-against, -1, -1, roundabout, right, shared_lane
-both, , , , opposite_lane, shared_lane
-both, yes, , , opposite_lane, shared_lane
-both, no, , , opposite_lane, shared_lane
-both, 1, , , opposite_lane, shared_lane
-both, -1, , , opposite_lane, shared_lane
-with, , yes, , opposite_lane, shared_lane
-with, yes, yes, , opposite_lane, shared_lane
-with, no, yes, , opposite_lane, shared_lane
-with, 1, yes, , opposite_lane, shared_lane
-with, -1, yes, , opposite_lane, shared_lane
-both, , no, , opposite_lane, shared_lane
-both, yes, no, , opposite_lane, shared_lane
-both, no, no, , opposite_lane, shared_lane
-both, 1, no, , opposite_lane, shared_lane
-both, -1, no, , opposite_lane, shared_lane
-with, , 1, , opposite_lane, shared_lane
-with, yes, 1, , opposite_lane, shared_lane
-with, no, 1, , opposite_lane, shared_lane
-with, 1, 1, , opposite_lane, shared_lane
-with, -1, 1, , opposite_lane, shared_lane
-against, , -1, , opposite_lane, shared_lane
-against, yes, -1, , opposite_lane, shared_lane
-against, no, -1, , opposite_lane, shared_lane
-against, 1, -1, , opposite_lane, shared_lane
-against, -1, -1, , opposite_lane, shared_lane
-with, , , roundabout, opposite_lane, shared_lane
-with, yes, , roundabout, opposite_lane, shared_lane
-with, no, , roundabout, opposite_lane, shared_lane
-with, 1, , roundabout, opposite_lane, shared_lane
-with, -1, , roundabout, opposite_lane, shared_lane
-with, , yes, roundabout, opposite_lane, shared_lane
-with, yes, yes, roundabout, opposite_lane, shared_lane
-with, no, yes, roundabout, opposite_lane, shared_lane
-with, 1, yes, roundabout, opposite_lane, shared_lane
-with, -1, yes, roundabout, opposite_lane, shared_lane
-both, , no, roundabout, opposite_lane, shared_lane
-both, yes, no, roundabout, opposite_lane, shared_lane
-both, no, no, roundabout, opposite_lane, shared_lane
-both, 1, no, roundabout, opposite_lane, shared_lane
-both, -1, no, roundabout, opposite_lane, shared_lane
-with, , 1, roundabout, opposite_lane, shared_lane
-with, yes, 1, roundabout, opposite_lane, shared_lane
-with, no, 1, roundabout, opposite_lane, shared_lane
-with, 1, 1, roundabout, opposite_lane, shared_lane
-with, -1, 1, roundabout, opposite_lane, shared_lane
-against, , -1, roundabout, opposite_lane, shared_lane
-against, yes, -1, roundabout, opposite_lane, shared_lane
-against, no, -1, roundabout, opposite_lane, shared_lane
-against, 1, -1, roundabout, opposite_lane, shared_lane
-against, -1, -1, roundabout, opposite_lane, shared_lane
-both, , , , opposite, shared_lane
-both, yes, , , opposite, shared_lane
-both, no, , , opposite, shared_lane
-both, 1, , , opposite, shared_lane
-both, -1, , , opposite, shared_lane
-with, , yes, , opposite, shared_lane
-with, yes, yes, , opposite, shared_lane
-with, no, yes, , opposite, shared_lane
-with, 1, yes, , opposite, shared_lane
-with, -1, yes, , opposite, shared_lane
-both, , no, , opposite, shared_lane
-both, yes, no, , opposite, shared_lane
-both, no, no, , opposite, shared_lane
-both, 1, no, , opposite, shared_lane
-both, -1, no, , opposite, shared_lane
-with, , 1, , opposite, shared_lane
-with, yes, 1, , opposite, shared_lane
-with, no, 1, , opposite, shared_lane
-with, 1, 1, , opposite, shared_lane
-with, -1, 1, , opposite, shared_lane
-against, , -1, , opposite, shared_lane
-against, yes, -1, , opposite, shared_lane
-against, no, -1, , opposite, shared_lane
-against, 1, -1, , opposite, shared_lane
-against, -1, -1, , opposite, shared_lane
-with, , , roundabout, opposite, shared_lane
-with, yes, , roundabout, opposite, shared_lane
-with, no, , roundabout, opposite, shared_lane
-with, 1, , roundabout, opposite, shared_lane
-with, -1, , roundabout, opposite, shared_lane
-with, , yes, roundabout, opposite, shared_lane
-with, yes, yes, roundabout, opposite, shared_lane
-with, no, yes, roundabout, opposite, shared_lane
-with, 1, yes, roundabout, opposite, shared_lane
-with, -1, yes, roundabout, opposite, shared_lane
-both, , no, roundabout, opposite, shared_lane
-both, yes, no, roundabout, opposite, shared_lane
-both, no, no, roundabout, opposite, shared_lane
-both, 1, no, roundabout, opposite, shared_lane
-both, -1, no, roundabout, opposite, shared_lane
-with, , 1, roundabout, opposite, shared_lane
-with, yes, 1, roundabout, opposite, shared_lane
-with, no, 1, roundabout, opposite, shared_lane
-with, 1, 1, roundabout, opposite, shared_lane
-with, -1, 1, roundabout, opposite, shared_lane
-against, , -1, roundabout, opposite, shared_lane
-against, yes, -1, roundabout, opposite, shared_lane
-against, no, -1, roundabout, opposite, shared_lane
-against, 1, -1, roundabout, opposite, shared_lane
-against, -1, -1, roundabout, opposite, shared_lane
-both, , , , opposite_share_busway, shared_lane
-both, yes, , , opposite_share_busway, shared_lane
-both, no, , , opposite_share_busway, shared_lane
-both, 1, , , opposite_share_busway, shared_lane
-both, -1, , , opposite_share_busway, shared_lane
-with, , yes, , opposite_share_busway, shared_lane
-with, yes, yes, , opposite_share_busway, shared_lane
-with, no, yes, , opposite_share_busway, shared_lane
-with, 1, yes, , opposite_share_busway, shared_lane
-with, -1, yes, , opposite_share_busway, shared_lane
-both, , no, , opposite_share_busway, shared_lane
-both, yes, no, , opposite_share_busway, shared_lane
-both, no, no, , opposite_share_busway, shared_lane
-both, 1, no, , opposite_share_busway, shared_lane
-both, -1, no, , opposite_share_busway, shared_lane
-with, , 1, , opposite_share_busway, shared_lane
-with, yes, 1, , opposite_share_busway, shared_lane
-with, no, 1, , opposite_share_busway, shared_lane
-with, 1, 1, , opposite_share_busway, shared_lane
-with, -1, 1, , opposite_share_busway, shared_lane
-against, , -1, , opposite_share_busway, shared_lane
-against, yes, -1, , opposite_share_busway, shared_lane
-against, no, -1, , opposite_share_busway, shared_lane
-against, 1, -1, , opposite_share_busway, shared_lane
-against, -1, -1, , opposite_share_busway, shared_lane
-with, , , roundabout, opposite_share_busway, shared_lane
-with, yes, , roundabout, opposite_share_busway, shared_lane
-with, no, , roundabout, opposite_share_busway, shared_lane
-with, 1, , roundabout, opposite_share_busway, shared_lane
-with, -1, , roundabout, opposite_share_busway, shared_lane
-with, , yes, roundabout, opposite_share_busway, shared_lane
-with, yes, yes, roundabout, opposite_share_busway, shared_lane
-with, no, yes, roundabout, opposite_share_busway, shared_lane
-with, 1, yes, roundabout, opposite_share_busway, shared_lane
-with, -1, yes, roundabout, opposite_share_busway, shared_lane
-both, , no, roundabout, opposite_share_busway, shared_lane
-both, yes, no, roundabout, opposite_share_busway, shared_lane
-both, no, no, roundabout, opposite_share_busway, shared_lane
-both, 1, no, roundabout, opposite_share_busway, shared_lane
-both, -1, no, roundabout, opposite_share_busway, shared_lane
-with, , 1, roundabout, opposite_share_busway, shared_lane
-with, yes, 1, roundabout, opposite_share_busway, shared_lane
-with, no, 1, roundabout, opposite_share_busway, shared_lane
-with, 1, 1, roundabout, opposite_share_busway, shared_lane
-with, -1, 1, roundabout, opposite_share_busway, shared_lane
-against, , -1, roundabout, opposite_share_busway, shared_lane
-against, yes, -1, roundabout, opposite_share_busway, shared_lane
-against, no, -1, roundabout, opposite_share_busway, shared_lane
-against, 1, -1, roundabout, opposite_share_busway, shared_lane
-against, -1, -1, roundabout, opposite_share_busway, shared_lane
-both, , , , opposite_track, shared_lane
-both, yes, , , opposite_track, shared_lane
-both, no, , , opposite_track, shared_lane
-both, 1, , , opposite_track, shared_lane
-both, -1, , , opposite_track, shared_lane
-with, , yes, , opposite_track, shared_lane
-with, yes, yes, , opposite_track, shared_lane
-with, no, yes, , opposite_track, shared_lane
-with, 1, yes, , opposite_track, shared_lane
-with, -1, yes, , opposite_track, shared_lane
-both, , no, , opposite_track, shared_lane
-both, yes, no, , opposite_track, shared_lane
-both, no, no, , opposite_track, shared_lane
-both, 1, no, , opposite_track, shared_lane
-both, -1, no, , opposite_track, shared_lane
-with, , 1, , opposite_track, shared_lane
-with, yes, 1, , opposite_track, shared_lane
-with, no, 1, , opposite_track, shared_lane
-with, 1, 1, , opposite_track, shared_lane
-with, -1, 1, , opposite_track, shared_lane
-against, , -1, , opposite_track, shared_lane
-against, yes, -1, , opposite_track, shared_lane
-against, no, -1, , opposite_track, shared_lane
-against, 1, -1, , opposite_track, shared_lane
-against, -1, -1, , opposite_track, shared_lane
-with, , , roundabout, opposite_track, shared_lane
-with, yes, , roundabout, opposite_track, shared_lane
-with, no, , roundabout, opposite_track, shared_lane
-with, 1, , roundabout, opposite_track, shared_lane
-with, -1, , roundabout, opposite_track, shared_lane
-with, , yes, roundabout, opposite_track, shared_lane
-with, yes, yes, roundabout, opposite_track, shared_lane
-with, no, yes, roundabout, opposite_track, shared_lane
-with, 1, yes, roundabout, opposite_track, shared_lane
-with, -1, yes, roundabout, opposite_track, shared_lane
-both, , no, roundabout, opposite_track, shared_lane
-both, yes, no, roundabout, opposite_track, shared_lane
-both, no, no, roundabout, opposite_track, shared_lane
-both, 1, no, roundabout, opposite_track, shared_lane
-both, -1, no, roundabout, opposite_track, shared_lane
-with, , 1, roundabout, opposite_track, shared_lane
-with, yes, 1, roundabout, opposite_track, shared_lane
-with, no, 1, roundabout, opposite_track, shared_lane
-with, 1, 1, roundabout, opposite_track, shared_lane
-with, -1, 1, roundabout, opposite_track, shared_lane
-against, , -1, roundabout, opposite_track, shared_lane
-against, yes, -1, roundabout, opposite_track, shared_lane
-against, no, -1, roundabout, opposite_track, shared_lane
-against, 1, -1, roundabout, opposite_track, shared_lane
-against, -1, -1, roundabout, opposite_track, shared_lane
-both, , , , , share_busway
-both, yes, , , , share_busway
-both, no, , , , share_busway
-both, 1, , , , share_busway
-both, -1, , , , share_busway
-with, , yes, , , share_busway
-with, yes, yes, , , share_busway
-with, no, yes, , , share_busway
-with, 1, yes, , , share_busway
-with, -1, yes, , , share_busway
-both, , no, , , share_busway
-both, yes, no, , , share_busway
-both, no, no, , , share_busway
-both, 1, no, , , share_busway
-both, -1, no, , , share_busway
-with, , 1, , , share_busway
-with, yes, 1, , , share_busway
-with, no, 1, , , share_busway
-with, 1, 1, , , share_busway
-with, -1, 1, , , share_busway
-against, , -1, , , share_busway
-against, yes, -1, , , share_busway
-against, no, -1, , , share_busway
-against, 1, -1, , , share_busway
-against, -1, -1, , , share_busway
-with, , , roundabout, , share_busway
-with, yes, , roundabout, , share_busway
-with, no, , roundabout, , share_busway
-with, 1, , roundabout, , share_busway
-with, -1, , roundabout, , share_busway
-with, , yes, roundabout, , share_busway
-with, yes, yes, roundabout, , share_busway
-with, no, yes, roundabout, , share_busway
-with, 1, yes, roundabout, , share_busway
-with, -1, yes, roundabout, , share_busway
-both, , no, roundabout, , share_busway
-both, yes, no, roundabout, , share_busway
-both, no, no, roundabout, , share_busway
-both, 1, no, roundabout, , share_busway
-both, -1, no, roundabout, , share_busway
-with, , 1, roundabout, , share_busway
-with, yes, 1, roundabout, , share_busway
-with, no, 1, roundabout, , share_busway
-with, 1, 1, roundabout, , share_busway
-with, -1, 1, roundabout, , share_busway
-against, , -1, roundabout, , share_busway
-against, yes, -1, roundabout, , share_busway
-against, no, -1, roundabout, , share_busway
-against, 1, -1, roundabout, , share_busway
-against, -1, -1, roundabout, , share_busway
-against, , , , right, share_busway
-against, yes, , , right, share_busway
-against, no, , , right, share_busway
-against, 1, , , right, share_busway
-against, -1, , , right, share_busway
-with, , yes, , right, share_busway
-with, yes, yes, , right, share_busway
-with, no, yes, , right, share_busway
-with, 1, yes, , right, share_busway
-with, -1, yes, , right, share_busway
-both, , no, , right, share_busway
-both, yes, no, , right, share_busway
-both, no, no, , right, share_busway
-both, 1, no, , right, share_busway
-both, -1, no, , right, share_busway
-with, , 1, , right, share_busway
-with, yes, 1, , right, share_busway
-with, no, 1, , right, share_busway
-with, 1, 1, , right, share_busway
-with, -1, 1, , right, share_busway
-against, , -1, , right, share_busway
-against, yes, -1, , right, share_busway
-against, no, -1, , right, share_busway
-against, 1, -1, , right, share_busway
-against, -1, -1, , right, share_busway
-with, , , roundabout, right, share_busway
-with, yes, , roundabout, right, share_busway
-with, no, , roundabout, right, share_busway
-with, 1, , roundabout, right, share_busway
-with, -1, , roundabout, right, share_busway
-with, , yes, roundabout, right, share_busway
-with, yes, yes, roundabout, right, share_busway
-with, no, yes, roundabout, right, share_busway
-with, 1, yes, roundabout, right, share_busway
-with, -1, yes, roundabout, right, share_busway
-both, , no, roundabout, right, share_busway
-both, yes, no, roundabout, right, share_busway
-both, no, no, roundabout, right, share_busway
-both, 1, no, roundabout, right, share_busway
-both, -1, no, roundabout, right, share_busway
-with, , 1, roundabout, right, share_busway
-with, yes, 1, roundabout, right, share_busway
-with, no, 1, roundabout, right, share_busway
-with, 1, 1, roundabout, right, share_busway
-with, -1, 1, roundabout, right, share_busway
-against, , -1, roundabout, right, share_busway
-against, yes, -1, roundabout, right, share_busway
-against, no, -1, roundabout, right, share_busway
-against, 1, -1, roundabout, right, share_busway
-against, -1, -1, roundabout, right, share_busway
-both, , , , opposite_lane, share_busway
-both, yes, , , opposite_lane, share_busway
-both, no, , , opposite_lane, share_busway
-both, 1, , , opposite_lane, share_busway
-both, -1, , , opposite_lane, share_busway
-with, , yes, , opposite_lane, share_busway
-with, yes, yes, , opposite_lane, share_busway
-with, no, yes, , opposite_lane, share_busway
-with, 1, yes, , opposite_lane, share_busway
-with, -1, yes, , opposite_lane, share_busway
-both, , no, , opposite_lane, share_busway
-both, yes, no, , opposite_lane, share_busway
-both, no, no, , opposite_lane, share_busway
-both, 1, no, , opposite_lane, share_busway
-both, -1, no, , opposite_lane, share_busway
-with, , 1, , opposite_lane, share_busway
-with, yes, 1, , opposite_lane, share_busway
-with, no, 1, , opposite_lane, share_busway
-with, 1, 1, , opposite_lane, share_busway
-with, -1, 1, , opposite_lane, share_busway
-against, , -1, , opposite_lane, share_busway
-against, yes, -1, , opposite_lane, share_busway
-against, no, -1, , opposite_lane, share_busway
-against, 1, -1, , opposite_lane, share_busway
-against, -1, -1, , opposite_lane, share_busway
-with, , , roundabout, opposite_lane, share_busway
-with, yes, , roundabout, opposite_lane, share_busway
-with, no, , roundabout, opposite_lane, share_busway
-with, 1, , roundabout, opposite_lane, share_busway
-with, -1, , roundabout, opposite_lane, share_busway
-with, , yes, roundabout, opposite_lane, share_busway
-with, yes, yes, roundabout, opposite_lane, share_busway
-with, no, yes, roundabout, opposite_lane, share_busway
-with, 1, yes, roundabout, opposite_lane, share_busway
-with, -1, yes, roundabout, opposite_lane, share_busway
-both, , no, roundabout, opposite_lane, share_busway
-both, yes, no, roundabout, opposite_lane, share_busway
-both, no, no, roundabout, opposite_lane, share_busway
-both, 1, no, roundabout, opposite_lane, share_busway
-both, -1, no, roundabout, opposite_lane, share_busway
-with, , 1, roundabout, opposite_lane, share_busway
-with, yes, 1, roundabout, opposite_lane, share_busway
-with, no, 1, roundabout, opposite_lane, share_busway
-with, 1, 1, roundabout, opposite_lane, share_busway
-with, -1, 1, roundabout, opposite_lane, share_busway
-against, , -1, roundabout, opposite_lane, share_busway
-against, yes, -1, roundabout, opposite_lane, share_busway
-against, no, -1, roundabout, opposite_lane, share_busway
-against, 1, -1, roundabout, opposite_lane, share_busway
-against, -1, -1, roundabout, opposite_lane, share_busway
-both, , , , opposite, share_busway
-both, yes, , , opposite, share_busway
-both, no, , , opposite, share_busway
-both, 1, , , opposite, share_busway
-both, -1, , , opposite, share_busway
-with, , yes, , opposite, share_busway
-with, yes, yes, , opposite, share_busway
-with, no, yes, , opposite, share_busway
-with, 1, yes, , opposite, share_busway
-with, -1, yes, , opposite, share_busway
-both, , no, , opposite, share_busway
-both, yes, no, , opposite, share_busway
-both, no, no, , opposite, share_busway
-both, 1, no, , opposite, share_busway
-both, -1, no, , opposite, share_busway
-with, , 1, , opposite, share_busway
-with, yes, 1, , opposite, share_busway
-with, no, 1, , opposite, share_busway
-with, 1, 1, , opposite, share_busway
-with, -1, 1, , opposite, share_busway
-against, , -1, , opposite, share_busway
-against, yes, -1, , opposite, share_busway
-against, no, -1, , opposite, share_busway
-against, 1, -1, , opposite, share_busway
-against, -1, -1, , opposite, share_busway
-with, , , roundabout, opposite, share_busway
-with, yes, , roundabout, opposite, share_busway
-with, no, , roundabout, opposite, share_busway
-with, 1, , roundabout, opposite, share_busway
-with, -1, , roundabout, opposite, share_busway
-with, , yes, roundabout, opposite, share_busway
-with, yes, yes, roundabout, opposite, share_busway
-with, no, yes, roundabout, opposite, share_busway
-with, 1, yes, roundabout, opposite, share_busway
-with, -1, yes, roundabout, opposite, share_busway
-both, , no, roundabout, opposite, share_busway
-both, yes, no, roundabout, opposite, share_busway
-both, no, no, roundabout, opposite, share_busway
-both, 1, no, roundabout, opposite, share_busway
-both, -1, no, roundabout, opposite, share_busway
-with, , 1, roundabout, opposite, share_busway
-with, yes, 1, roundabout, opposite, share_busway
-with, no, 1, roundabout, opposite, share_busway
-with, 1, 1, roundabout, opposite, share_busway
-with, -1, 1, roundabout, opposite, share_busway
-against, , -1, roundabout, opposite, share_busway
-against, yes, -1, roundabout, opposite, share_busway
-against, no, -1, roundabout, opposite, share_busway
-against, 1, -1, roundabout, opposite, share_busway
-against, -1, -1, roundabout, opposite, share_busway
-both, , , , opposite_share_busway, share_busway
-both, yes, , , opposite_share_busway, share_busway
-both, no, , , opposite_share_busway, share_busway
-both, 1, , , opposite_share_busway, share_busway
-both, -1, , , opposite_share_busway, share_busway
-with, , yes, , opposite_share_busway, share_busway
-with, yes, yes, , opposite_share_busway, share_busway
-with, no, yes, , opposite_share_busway, share_busway
-with, 1, yes, , opposite_share_busway, share_busway
-with, -1, yes, , opposite_share_busway, share_busway
-both, , no, , opposite_share_busway, share_busway
-both, yes, no, , opposite_share_busway, share_busway
-both, no, no, , opposite_share_busway, share_busway
-both, 1, no, , opposite_share_busway, share_busway
-both, -1, no, , opposite_share_busway, share_busway
-with, , 1, , opposite_share_busway, share_busway
-with, yes, 1, , opposite_share_busway, share_busway
-with, no, 1, , opposite_share_busway, share_busway
-with, 1, 1, , opposite_share_busway, share_busway
-with, -1, 1, , opposite_share_busway, share_busway
-against, , -1, , opposite_share_busway, share_busway
-against, yes, -1, , opposite_share_busway, share_busway
-against, no, -1, , opposite_share_busway, share_busway
-against, 1, -1, , opposite_share_busway, share_busway
-against, -1, -1, , opposite_share_busway, share_busway
-with, , , roundabout, opposite_share_busway, share_busway
-with, yes, , roundabout, opposite_share_busway, share_busway
-with, no, , roundabout, opposite_share_busway, share_busway
-with, 1, , roundabout, opposite_share_busway, share_busway
-with, -1, , roundabout, opposite_share_busway, share_busway
-with, , yes, roundabout, opposite_share_busway, share_busway
-with, yes, yes, roundabout, opposite_share_busway, share_busway
-with, no, yes, roundabout, opposite_share_busway, share_busway
-with, 1, yes, roundabout, opposite_share_busway, share_busway
-with, -1, yes, roundabout, opposite_share_busway, share_busway
-both, , no, roundabout, opposite_share_busway, share_busway
-both, yes, no, roundabout, opposite_share_busway, share_busway
-both, no, no, roundabout, opposite_share_busway, share_busway
-both, 1, no, roundabout, opposite_share_busway, share_busway
-both, -1, no, roundabout, opposite_share_busway, share_busway
-with, , 1, roundabout, opposite_share_busway, share_busway
-with, yes, 1, roundabout, opposite_share_busway, share_busway
-with, no, 1, roundabout, opposite_share_busway, share_busway
-with, 1, 1, roundabout, opposite_share_busway, share_busway
-with, -1, 1, roundabout, opposite_share_busway, share_busway
-against, , -1, roundabout, opposite_share_busway, share_busway
-against, yes, -1, roundabout, opposite_share_busway, share_busway
-against, no, -1, roundabout, opposite_share_busway, share_busway
-against, 1, -1, roundabout, opposite_share_busway, share_busway
-against, -1, -1, roundabout, opposite_share_busway, share_busway
-both, , , , opposite_track, share_busway
-both, yes, , , opposite_track, share_busway
-both, no, , , opposite_track, share_busway
-both, 1, , , opposite_track, share_busway
-both, -1, , , opposite_track, share_busway
-with, , yes, , opposite_track, share_busway
-with, yes, yes, , opposite_track, share_busway
-with, no, yes, , opposite_track, share_busway
-with, 1, yes, , opposite_track, share_busway
-with, -1, yes, , opposite_track, share_busway
-both, , no, , opposite_track, share_busway
-both, yes, no, , opposite_track, share_busway
-both, no, no, , opposite_track, share_busway
-both, 1, no, , opposite_track, share_busway
-both, -1, no, , opposite_track, share_busway
-with, , 1, , opposite_track, share_busway
-with, yes, 1, , opposite_track, share_busway
-with, no, 1, , opposite_track, share_busway
-with, 1, 1, , opposite_track, share_busway
-with, -1, 1, , opposite_track, share_busway
-against, , -1, , opposite_track, share_busway
-against, yes, -1, , opposite_track, share_busway
-against, no, -1, , opposite_track, share_busway
-against, 1, -1, , opposite_track, share_busway
-against, -1, -1, , opposite_track, share_busway
-with, , , roundabout, opposite_track, share_busway
-with, yes, , roundabout, opposite_track, share_busway
-with, no, , roundabout, opposite_track, share_busway
-with, 1, , roundabout, opposite_track, share_busway
-with, -1, , roundabout, opposite_track, share_busway
-with, , yes, roundabout, opposite_track, share_busway
-with, yes, yes, roundabout, opposite_track, share_busway
-with, no, yes, roundabout, opposite_track, share_busway
-with, 1, yes, roundabout, opposite_track, share_busway
-with, -1, yes, roundabout, opposite_track, share_busway
-both, , no, roundabout, opposite_track, share_busway
-both, yes, no, roundabout, opposite_track, share_busway
-both, no, no, roundabout, opposite_track, share_busway
-both, 1, no, roundabout, opposite_track, share_busway
-both, -1, no, roundabout, opposite_track, share_busway
-with, , 1, roundabout, opposite_track, share_busway
-with, yes, 1, roundabout, opposite_track, share_busway
-with, no, 1, roundabout, opposite_track, share_busway
-with, 1, 1, roundabout, opposite_track, share_busway
-with, -1, 1, roundabout, opposite_track, share_busway
-against, , -1, roundabout, opposite_track, share_busway
-against, yes, -1, roundabout, opposite_track, share_busway
-against, no, -1, roundabout, opposite_track, share_busway
-against, 1, -1, roundabout, opposite_track, share_busway
-against, -1, -1, roundabout, opposite_track, share_busway
diff --git a/AspectedRouting/output.lua b/AspectedRouting/output.lua
index 06a2a83..fdc513b 100644
--- a/AspectedRouting/output.lua
+++ b/AspectedRouting/output.lua
@@ -1,16 +1,20 @@
-function debug_table(table, prefix)
-    if (prefix == nil) then
-        prefix = ""
-    end
-    for k, v in pairs(table) do
+-- Itinero 1.0-profile, generated on 2020-05-02T02:24:17
 
-        if (type(v) == "table") then
-            debug_table(v, "   ")
-        else
-            print(prefix .. tostring(k) .. " = " .. tostring(v))
-        end
+
+
+
+----------------------------- UTILS ---------------------------
+
+
+function member_of(calledIn, parameters, tags, result)
+    local k = "_relation:" .. calledIn
+    -- This tag is conventiently setup by all the preprocessors, which take the parameters into account
+    local doesMatch = tags[k]
+    if (doesMatch == "yes") then
+        result.attributes_to_keep[k] = "yes"
+        return true
     end
-    print("")
+    return false
 end
 
 
@@ -22,30 +26,47 @@ function default(defaultValue, realValue)
 end
 
 
-
-
-
-function double_compare(a, b)
-    if (type(a) ~= "number") then
-        a = parse(a)
+function multiply(list)
+    local factor = 1
+    for _, value in ipairs(list) do
+        factor = factor * value
     end
-
-    if(type(b) ~= "number") then
-        b = parse(b)
-    end
-    return math.abs(a - b) > 0.001
+    return factor;
 end
 
 
-function eq(a, b)
-    if (a == b) then
-        return "yes"
-    else
-        return "no"
+function table_to_list(tags, result, factor_table)
+    local list = {}
+    for key, mapping in pairs(factor_table) do
+        local v = tags[key]
+        if (v ~= nil) then
+            if (type(mapping) == "table") then
+                local f = mapping[v]
+                if (f ~= nil) then
+                    table.insert(list, f);
+                    result.attributes_to_keep[key] = v
+                end
+            else
+                table.insert(list, mapping);
+                result.attributes_to_keep[key] = v
+            end
+        end
     end
+
+    return list;
 end
 
 
+failed_tests = false
+function unit_test(f, fname, index, expected, parameters, tags)
+    local result = {attributes_to_keep = {}}
+    local actual = f(parameters, tags, result)
+    if (tostring(actual) ~= expected) then
+        print("[" .. fname .. "] " .. index .. " failed: expected " .. expected .. " but got " .. tostring(actual))
+        failed_tests = true
+    end
+end
+
 
 function first_match_of(tags, result, order_of_keys, table)
     for _, key in ipairs(order_of_keys) do
@@ -69,67 +90,11 @@ function first_match_of(tags, result, order_of_keys, table)
 end
 
 
-function inv(n)
-    return 1/n
-end
-
-
-function inv(n)
-    return 1/n
-end
-
-
-function min(list)
-    local min
-    for _, value in ipairs(list) do
-        if (min == nil) then
-            min = value
-        elseif (min > value) then
-            min = value
-        end
-    end
-
-    return min;
-end
-
-
-function multiply(list)
-    local factor = 1
-    for _, value in ipairs(list) do
-        factor = factor * value
-    end
-    return factor;
-end
-
-
-function must_match(tags, result, needed_keys, table)
-    local result_list = {}
-    for _, key in ipairs(needed_keys) do
-        local v = tags[key]
-        if (v == nil) then
-            return false
-        end
-
-        local mapping = table[key]
-        if (type(mapping) == "table") then
-            local resultValue = mapping[v]
-            if (v == nil or v == false) then
-                return false
-            end
-            if (v == "no" or v == "false") then
-                return false
-            end
-
-            result.attributes_to_keep[key] = v
-        else
-            error("The mapping is not a table. This is not supported")
-        end
-    end
-    return true;
-end
-
-
 function notEq(a, b)
+    if (b == nil) then
+        b = "yes"
+    end
+    
     if (a ~= b) then
         return "yes"
     else
@@ -167,280 +132,245 @@ function parse(string)
 end
 
 
-function table_to_list(tags, result, factor_table)
-    local list = {}
-    for key, mapping in pairs(factor_table) do
+function must_match(tags, result, needed_keys, table)
+    for _, key in ipairs(needed_keys) do
         local v = tags[key]
-        if (v ~= nil) then
-            if (type(mapping) == "table") then
-                local f = mapping[v]
-                if (f ~= nil) then
-                    table.insert(list, f);
-                    result.attributes_to_keep[key] = v
-                end
-            else
-                table.insert(list, mapping);
-                result.attributes_to_keep[key] = v
+        if (v == nil) then
+            return false
+        end
+
+        local mapping = table[key]
+        if (type(mapping) == "table") then
+            local resultValue = mapping[v]
+            if (resultValue == nil or
+                    resultValue == false or
+                    resultValue == "no" or
+                    resultValue == "false") then
+                return false
             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
+                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)
         end
     end
 
-    return list;
+        -- 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]
+            result.attributes_to_keep[key] = v
+        end
+
+    return true;
 end
 
 
-failed_tests = false
-function unit_test(f, fname, index, expected, parameters, tags)
-    local result = {attributes_to_keep = {}}
-    local actual = f(parameters, tags, result)
-    if (tostring(actual) ~= expected) then
-        print("[" .. fname .. "] " .. index .. " failed: expected " .. expected .. " but got " .. tostring(actual))
-        failed_tests = true
+function eq(a, b)
+    if (a == b) then
+        return "yes"
+    else
+        return "no"
     end
 end
 
 
+
+function containedIn(list, a)
+    for _, value in ipairs(list) do
+        if (value == a) then
+            return true
+        end
+    end
+
+    return false;
+end
+
+
+function min(list)
+    local min
+    for _, value in ipairs(list) do
+        if (min == nil) then
+            min = value
+        elseif (min > value) then
+            min = value
+        end
+    end
+
+    return min;
+end
+
+
+function string.start(strt, s)
+    return string.sub(s, 1, string.len(strt)) == strt
+end
+
+
+-- every key starting with "_relation:<name>:XXX" is rewritten to "_relation:XXX"
+function remove_relation_prefix(tags, name)
+
+    local new_tags = {}
+    for k, v in pairs(tags) do
+        local prefix = "_relation:" .. name;
+        if (string.start(prefix, k)) then
+            local new_key = "_relation:" .. string.sub(k, string.len(prefix))
+            new_tags[new_key] = v
+        else
+            new_tags[k] = v
+        end
+    end
+    return new_tags
+end
+
+
+function debug_table(table, prefix)
+    if (prefix == nil) then
+        prefix = ""
+    end
+    for k, v in pairs(table) do
+
+        if (type(v) == "table") then
+            debug_table(v, "   ")
+        else
+            print(prefix .. tostring(k) .. " = " .. tostring(v))
+        end
+    end
+    print("")
+end
+
+
 failed_profile_tests = false
 --[[
 expected should be a table containing 'access', 'speed' and 'weight'
 ]]
 function unit_test_profile(profile_function, profile_name, index, expected, tags)
-    result = {attributes_to_keep = {}}
+    local result = { attributes_to_keep = {} }
+    local profile_failed = false
     profile_function(tags, result)
 
-    if (result.access ~= expected.access) then
+    local accessCorrect = (result.access == 0 and expected.access == "no") 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 (result.access == 0) then
+    if (expected.access == "no") then
         -- we cannot access this road, the other results are irrelevant
+        if (profile_failed) then
+            print("The used tags for test " .. tostring(index) .. " are:")
+            debug_table(tags)
+        end
         return
     end
 
-    if (double_compare(result.speed, expected.speed)) then
+    if (not double_compare(result.speed, expected.speed)) then
         print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".speed: expected " .. expected.speed .. " but got " .. result.speed)
         failed_profile_tests = true
+        profile_failed = true
     end
 
-    if (double_compare(result.oneway, expected.oneway)) then
-        print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but got " .. result.oneway)
+
+    local actualOneway = result.oneway;
+    if (result.oneway == 0) then
+        actualOneway = "both"
+    elseif (result.oneway == 1) then
+        actualOneway = "with"
+    elseif (result.oneway == 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)
         failed_profile_tests = true
+        profile_failed = true
     end
 
-    if (double_compare(result.oneway, expected.oneway)) then
-        print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".oneway: expected " .. expected.oneway .. " but got " .. result.oneway)
+
+    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)
         failed_profile_tests = true
+        profile_failed = true
     end
 
-    if (double_compare(inv(result.factor), 0.033333)) then
-        print("Test " .. tostring(index) .. " failed for " .. profile_name .. ".factor: expected " .. expected.weight .. " but got " .. inv(result.factor))
-        failed_profile_tests = true
+    if (profile_failed == true) then
+        print("The used tags for test " .. tostring(index) .. " are:")
+        debug_table(tags)
     end
 end
 
 
+function inv(n)
+    return 1/n
+end
 
 
-profile_whitelist = {"access", "highway", "bicycle", "cycleway:right", "cycleway:left", "cycleway", "anyways:bicycle", "anyways:access", "anyways:construction", "oneway", "oneway:bicycle", "junction", "maxspeed", "designation", "ferry", "cyclestreet", "motor", "foot", "towpath", "type", "route"}
+function double_compare(a, b)
+    if (b == nil) then
+        return false
+    end
+    
+    if (type(a) ~= "number") then
+        a = parse(a)
+    end
+
+    if(type(b) ~= "number") then
+        b = parse(b)
+    end
+    if (a == b) then
+        return true
+    end
+
+    return math.abs(a - b) < 0.0001
+end
+
+
+
+
+----------------------------- PROFILE ---------------------------
+
+
+
+
+profile_whitelist = {"type", "route", "state", "operator", "access", "motor", "foot", "bicycle", "cyclestreet", "towpath", "designation", "highway", "cycleway", "cycleway:left", "cycleway:right", "railway", "surface", "oneway", "oneway:bicycle", "junction", "anyways:bicycle", "anyways:access", "anyways:construction", "tracktype", "incline", "maxspeed", "ferry", "_relation:bicycle_network_by_operator"}
 
 
 --[[
-Gives, for each type of highway, whether or not a normal bicycle can enter legally.
-Note that legal access is a bit 'grey' in the case of roads marked private and permissive, in which case these values are returned 
+The 'bicycle.network_score' returns true if the way is part of a cycling network of a certain (group of) operators
 
-Unit: 'designated': Access is allowed and even specifically for bicycles
-'yes': bicycles are allowed here
-'permissive': bicycles are allowed here, but this might be a private road or service where usage is allowed, but uncommon
-'dismount': cycling here is not allowed, but walking with the bicycle is
-'destination': cycling is allowed here, but only if truly necessary to reach the destination
-'private': this is a private road, only go here if the destination is here
-'no': do not cycle here
+Unit: 
 Created by 
-Originally defined in bicycle.legal_access.json
-Uses tags: access, highway, bicycle, cycleway:right, cycleway:left, cycleway, anyways:bicycle, anyways:access, anyways:construction
-Used parameters: 
-Number of combintations: 48
+Originally defined in bicycle.network_by_operator.json
+Uses tags: type, route, state, operator
+Used parameters: #networkOperator
+Number of combintations: 5
 Returns values: 
 ]]
-function bicycle_legal_access(parameters, tags, result)
-    return default("no", first_match_of(tags, result, 
-        {"anyways:bicycle", "anyways:access", "anyways:construction", "bicycle", "access", "cycleway:right", "cycleway:left", "cycleway", "highway"},
-        {
-            access = {
-                no = "no",
-                customers = "no",
-                private = "no",
-                permissive = "permissive",
-                destination = "destination",
-                delivery = "destination",
-                service = "destination"
-            },
-            highway = {
-                cycleway = "designated",
-                residential = "yes",
-                living_street = "yes",
-                service = "permissive",
-                services = "permissive",
-                track = "yes",
-                crossing = "dismount",
-                footway = "dismount",
-                pedestrian = "dismount",
-                corridor = "dismount",
-                path = "permissive",
-                primary = "no",
-                primary_link = "no",
-                secondary = "yes",
-                secondary_link = "yes",
-                tertiary = "yes",
-                tertiary_link = "yes",
-                unclassified = "yes",
-                road = "yes"
-            },
-            bicycle = {
-                yes = "yes",
-                no = "no",
-                use_sidepath = "no",
-                designated = "designated",
-                permissive = "permissive",
-                private = "private",
-                official = "designated",
-                dismount = "dismount"
-            },
-            ["cycleway:right"] = notEq("no", tags["cycleway:right"]),
-            ["cycleway:left"] = notEq("no", tags["cycleway:left"]),
-            cycleway = notEq("no", tags["cycleway"]),
-            ["anyways:bicycle"] = tags["anyways:bicycle"],
-            ["anyways:access"] = {
-                no = "no",
-                destination = "destination",
-                yes = "yes"
-            },
-            ["anyways:construction"] = {
-                yes = "no"
-            }
-        }))
+function bicycle_network_by_operator(parameters, tags, result)
+    local funcName = "bicycle_network_by_operator"
+    return member_of(funcName, parameters, tags, result)
 end
 
 
 --[[
-Determines wether or not a bicycle can go in both ways in this street, and if it is oneway, in what direction
+The 'bicycle.network_score' returns true if the way is part of a cycling network
 
-Unit: both: direction is allowed in both direction
-with: this is a oneway street with direction allowed with the grain of the way
-against: oneway street with direction against the way
+Unit: 
 Created by 
-Originally defined in bicycle.oneway.json
-Uses tags: oneway, oneway:bicycle, junction, cycleway, cycleway:left
+Originally defined in bicycle.network_score.json
+Uses tags: type, route, state
 Used parameters: 
-Number of combintations: 28
+Number of combintations: 4
 Returns values: 
 ]]
-function bicycle_oneway(parameters, tags, result)
-    return default("both", first_match_of(tags, result, 
-        {"oneway:bicycle", "junction", "cycleway", "cycleway:left", "oneway"},
-        {
-            oneway = {
-                yes = "with",
-                no = "both",
-                ["1"] = "with",
-                ["-1"] = "against"
-            },
-            ["oneway:bicycle"] = {
-                yes = "with",
-                no = "both",
-                ["1"] = "with",
-                ["-1"] = "against"
-            },
-            junction = {
-                roundabout = "with"
-            },
-            cycleway = {
-                right = "against",
-                opposite_lane = "both",
-                track = "both",
-                lane = "both",
-                opposite = "both",
-                opposite_share_busway = "both",
-                opposite_track = "both"
-            },
-            ["cycleway:left"] = {
-                no = "with",
-                yes = "both",
-                lane = "both",
-                track = "both",
-                shared_lane = "both",
-                share_busway = "both"
-            }
-        }))
-end
-
-
---[[
-Gives, for each type of highway, which the default legal maxspeed is in Belgium. This file is intended to be reused for in all vehicles, from pedestrian to car. In some cases, a legal maxspeed is not really defined (e.g. on footways). In that case, a socially acceptable speed should be taken (e.g.: a bicycle on a pedestrian path will go say around 12km/h)
-
-Unit: km/h
-Created by 
-Originally defined in legal_maxspeed_be.json
-Uses tags: maxspeed, highway, designation, ferry
-Used parameters: 
-Number of combintations: 27
-Returns values: 
-]]
-function legal_maxspeed_be(parameters, tags, result)
-    return default(30, first_match_of(tags, result, 
-        {"maxspeed", "designation", "highway", "ferry"},
-        {
-            maxspeed = parse(tags["maxspeed"]),
-            highway = {
-                cycleway = 30,
-                footway = 20,
-                crossing = 20,
-                pedestrian = 15,
-                path = 15,
-                corridor = 5,
-                residential = 30,
-                living_street = 20,
-                service = 30,
-                services = 30,
-                track = 50,
-                unclassified = 50,
-                road = 50,
-                motorway = 120,
-                motorway_link = 120,
-                primary = 90,
-                primary_link = 90,
-                secondary = 50,
-                secondary_link = 50,
-                tertiary = 50,
-                tertiary_link = 50
-            },
-            designation = {
-                towpath = 30
-            },
-            ferry = 5
-        }))
-end
-
-
---[[
-Gives a comfort factor, purely based on physical aspects of the road
-
-Unit: [0, 2]
-Created by 
-Originally defined in bicycle.comfort.json
-Uses tags: cyclestreet
-Used parameters: 
-Number of combintations: 3
-Returns values: 
-]]
-function bicycle_comfort(parameters, tags, result)
-    return default(1, multiply(table_to_list(tags, result, 
-        {
-            cyclestreet = {
-                yes = 1.5
-            }
-        })))
+function bicycle_network_score(parameters, tags, result)
+    local funcName = "bicycle_network_score"
+    return member_of(funcName, parameters, tags, result)
 end
 
 
@@ -523,28 +453,422 @@ end
 
 
 --[[
-The 'bicycle.network_score' is a bit of a catch-all for bicycle networks and indicates wether or not the road is part of a matching cycling network.
+Gives a comfort factor for a road, purely based on physical aspects of the road, which is a bit subjective; this takes a bit of scnery into account with a preference for `railway=abandoned` and `towpath=yes`
+
+Unit: [0, 2]
+Created by 
+Originally defined in bicycle.comfort.json
+Uses tags: highway, railway, towpath, cycleway, cyclestreet, access, surface
+Used parameters: 
+Number of combintations: 44
+Returns values: 
+]]
+function bicycle_comfort(parameters, tags, result)
+    return default(1, multiply(table_to_list(tags, result, 
+        {
+            highway = {
+                cycleway = 1.2,
+                primary = 0.3,
+                secondary = 0.4,
+                tertiary = 0.5,
+                unclassified = 0.8,
+                track = 0.95,
+                residential = 1,
+                living_street = 1.1,
+                footway = 0.95,
+                path = 0.5
+            },
+            railway = {
+                abandoned = 2
+            },
+            towpath = {
+                yes = 2
+            },
+            cycleway = {
+                track = 1.2
+            },
+            cyclestreet = {
+                yes = 1.1
+            },
+            access = {
+                designated = 1.2,
+                dismount = 0.5
+            },
+            surface = {
+                paved = 0.99,
+                ["concrete:lanes"] = 0.8,
+                ["concrete:plates"] = 1,
+                sett = 0.9,
+                unhewn_cobblestone = 0.75,
+                cobblestone = 0.8,
+                unpaved = 0.75,
+                compacted = 1.1,
+                fine_gravel = 0.99,
+                gravel = 0.9,
+                dirt = 0.6,
+                earth = 0.6,
+                grass = 0.6,
+                grass_paver = 0.9,
+                ground = 0.7,
+                sand = 0.5,
+                woodchips = 0.5,
+                snow = 0.5,
+                pebblestone = 0.5,
+                mud = 0.4
+            }
+        })))
+end
+
+
+--[[
+Determines wether or not a bicycle can go in both ways in this street, and if it is oneway, in what direction
+
+Unit: both: direction is allowed in both direction
+with: this is a oneway street with direction allowed with the grain of the way
+against: oneway street with direction against the way
+Created by 
+Originally defined in bicycle.oneway.json
+Uses tags: oneway, oneway:bicycle, junction, cycleway, cycleway:left
+Used parameters: 
+Number of combintations: 28
+Returns values: 
+]]
+function bicycle_oneway(parameters, tags, result)
+    return default("both", first_match_of(tags, result, 
+        {"oneway:bicycle", "junction", "cycleway", "cycleway:left", "oneway"},
+        {
+            oneway = {
+                yes = "with",
+                no = "both",
+                ["1"] = "with",
+                ["-1"] = "against"
+            },
+            ["oneway:bicycle"] = {
+                yes = "with",
+                no = "both",
+                ["1"] = "with",
+                ["-1"] = "against"
+            },
+            junction = {
+                roundabout = "with"
+            },
+            cycleway = {
+                right = "against",
+                opposite_lane = "both",
+                track = "both",
+                lane = "both",
+                opposite = "both",
+                opposite_share_busway = "both",
+                opposite_track = "both"
+            },
+            ["cycleway:left"] = {
+                no = "with",
+                yes = "both",
+                lane = "both",
+                track = "both",
+                shared_lane = "both",
+                share_busway = "both"
+            }
+        }))
+end
+
+
+--[[
+Gives, for each type of highway, whether or not a normal bicycle can enter legally.
+Note that legal access is a bit 'grey' in the case of roads marked private and permissive, in which case these values are returned 
+
+Unit: 'designated': Access is allowed and even specifically for bicycles
+'yes': bicycles are allowed here
+'permissive': bicycles are allowed here, but this might be a private road or service where usage is allowed, but uncommon
+'dismount': cycling here is not allowed, but walking with the bicycle is
+'destination': cycling is allowed here, but only if truly necessary to reach the destination
+'private': this is a private road, only go here if the destination is here
+'no': do not cycle here
+Created by 
+Originally defined in bicycle.legal_access.json
+Uses tags: access, highway, bicycle, cycleway:right, cycleway:left, cycleway, anyways:bicycle, anyways:access, anyways:construction
+Used parameters: 
+Number of combintations: 48
+Returns values: 
+]]
+function bicycle_legal_access(parameters, tags, result)
+    return default("no", first_match_of(tags, result, 
+        {"anyways:bicycle", "anyways:access", "anyways:construction", "bicycle", "access", "cycleway:right", "cycleway:left", "cycleway", "highway"},
+        {
+            access = {
+                no = "no",
+                customers = "no",
+                private = "no",
+                permissive = "permissive",
+                destination = "destination",
+                delivery = "destination",
+                service = "destination"
+            },
+            highway = {
+                cycleway = "designated",
+                residential = "yes",
+                living_street = "yes",
+                service = "permissive",
+                services = "permissive",
+                track = "yes",
+                crossing = "dismount",
+                footway = "dismount",
+                pedestrian = "dismount",
+                corridor = "dismount",
+                path = "permissive",
+                primary = "no",
+                primary_link = "no",
+                secondary = "yes",
+                secondary_link = "yes",
+                tertiary = "yes",
+                tertiary_link = "yes",
+                unclassified = "yes",
+                road = "yes"
+            },
+            bicycle = {
+                yes = "yes",
+                no = "no",
+                use_sidepath = "no",
+                designated = "designated",
+                permissive = "permissive",
+                private = "private",
+                official = "designated",
+                dismount = "dismount"
+            },
+            ["cycleway:right"] = notEq("no", tags["cycleway:right"]),
+            ["cycleway:left"] = notEq("no", tags["cycleway:left"]),
+            cycleway = notEq("no", tags["cycleway"]),
+            ["anyways:bicycle"] = tags["anyways:bicycle"],
+            ["anyways:access"] = {
+                no = "no",
+                destination = "destination",
+                yes = "yes"
+            },
+            ["anyways:construction"] = {
+                yes = "no"
+            }
+        }))
+end
+
+
+--[[
+Calculates a speed factor for bicycles based on physical features, e.g. a sand surface will slow a cyclist down; going over pedestrian areas even more, ...
 
 Unit: 
 Created by 
-Originally defined in bicycle.network_score.json
-Uses tags: type, route
+Originally defined in bicycle.speed_factor.json
+Uses tags: access, highway, surface, tracktype, incline
 Used parameters: 
-Number of combintations: 3
+Number of combintations: 49
 Returns values: 
 ]]
-function bicycle_network_score(parameters, tags, result)
+function bicycle_speed_factor(parameters, tags, result)
+    return multiply(table_to_list(tags, result, 
+        {
+            access = {
+                dismount = 0.15
+            },
+            highway = {
+                path = 0.5,
+                track = 0.7
+            },
+            surface = {
+                paved = 0.99,
+                asphalt = 1,
+                concrete = 1,
+                metal = 1,
+                wood = 1,
+                ["concrete:lanes"] = 0.95,
+                ["concrete:plates"] = 1,
+                paving_stones = 1,
+                sett = 0.9,
+                unhewn_cobblestone = 0.75,
+                cobblestone = 0.8,
+                unpaved = 0.75,
+                compacted = 0.99,
+                fine_gravel = 0.99,
+                gravel = 0.9,
+                dirt = 0.6,
+                earth = 0.6,
+                grass = 0.6,
+                grass_paver = 0.9,
+                ground = 0.7,
+                sand = 0.5,
+                woodchips = 0.5,
+                snow = 0.5,
+                pebblestone = 0.5,
+                mud = 0.4
+            },
+            tracktype = {
+                ["grade1"] = 0.99,
+                ["grade2"] = 0.8,
+                ["grade3"] = 0.6,
+                ["grade4"] = 0.3,
+                ["grade5"] = 0.1
+            },
+            incline = {
+                up = 0.75,
+                down = 1.25,
+                ["0"] = 1,
+                ["0%"] = 1,
+                ["10%"] = 0.9,
+                ["-10%"] = 1.1,
+                ["20%"] = 0.8,
+                ["-20%"] = 1.2,
+                ["30%"] = 0.7,
+                ["-30%"] = 1.3
+            }
+        }))
+end
+
+
+--[[
+Gives, for each type of highway, which the default legal maxspeed is in Belgium. This file is intended to be reused for in all vehicles, from pedestrian to car. In some cases, a legal maxspeed is not really defined (e.g. on footways). In that case, a socially acceptable speed should be taken (e.g.: a bicycle on a pedestrian path will go say around 12km/h)
+
+Unit: km/h
+Created by 
+Originally defined in legal_maxspeed_be.json
+Uses tags: maxspeed, highway, designation, ferry
+Used parameters: 
+Number of combintations: 27
+Returns values: 
+]]
+function legal_maxspeed_be(parameters, tags, result)
+    return default(30, first_match_of(tags, result, 
+        {"maxspeed", "designation", "highway", "ferry"},
+        {
+            maxspeed = parse(tags["maxspeed"]),
+            highway = {
+                cycleway = 30,
+                footway = 20,
+                crossing = 20,
+                pedestrian = 15,
+                path = 15,
+                corridor = 5,
+                residential = 30,
+                living_street = 20,
+                service = 30,
+                services = 30,
+                track = 50,
+                unclassified = 50,
+                road = 50,
+                motorway = 120,
+                motorway_link = 120,
+                primary = 90,
+                primary_link = 90,
+                secondary = 50,
+                secondary_link = 50,
+                tertiary = 50,
+                tertiary_link = 50
+            },
+            designation = {
+                towpath = 30
+            },
+            ferry = 5
+        }))
+end
+
+
+--[[
+Function preprocessing needed for aspect bicycle.network_by_operator, called by the relation preprocessor
+
+Unit: 
+Created by Generator
+Originally defined in NA
+Uses tags: type, route, state, operator
+Used parameters: #networkOperator
+Number of combintations: 5
+Returns values: 
+]]
+function relation_preprocessing_for_bicycle_network_by_operator(parameters, tags, result)
     return must_match(tags, result, 
-        {"type", "route"},
+        {"type", "route", "state", "operator"},
         {
             type = eq("route", tags["type"]),
-            route = eq("bicycle", tags["route"])
+            route = eq("bicycle", tags["route"]),
+            state = notEq("proposed", tags["state"]),
+            operator = containedIn(parameters["networkOperator"], tags["operator"])
         })
 end
 
 
 
 
+-- 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 = {}
+
+
+    subresult.attributes_to_keep = {}
+    parameters = default_parameters()
+    parameters.timeNeeded = 1
+
+    matched = relation_preprocessing_for_bicycle_network_by_operator(parameters, relation_tags, subresult)
+    if (matched) then
+        result.attributes_to_keep["_relation:fastest:bicycle_network_by_operator"] = "yes"
+    end
+
+
+    subresult.attributes_to_keep = {}
+    parameters = default_parameters()
+    parameters.distance = 1
+
+    matched = relation_preprocessing_for_bicycle_network_by_operator(parameters, relation_tags, subresult)
+    if (matched) then
+        result.attributes_to_keep["_relation:shortest:bicycle_network_by_operator"] = "yes"
+    end
+
+
+    subresult.attributes_to_keep = {}
+    parameters = default_parameters()
+    parameters.safety = 1
+
+    matched = relation_preprocessing_for_bicycle_network_by_operator(parameters, relation_tags, subresult)
+    if (matched) then
+        result.attributes_to_keep["_relation:safety:bicycle_network_by_operator"] = "yes"
+    end
+
+
+    subresult.attributes_to_keep = {}
+    parameters = default_parameters()
+    parameters.comfort = 1
+
+    matched = relation_preprocessing_for_bicycle_network_by_operator(parameters, relation_tags, subresult)
+    if (matched) then
+        result.attributes_to_keep["_relation:comfort:bicycle_network_by_operator"] = "yes"
+    end
+
+
+    subresult.attributes_to_keep = {}
+    parameters = default_parameters()
+    parameters.comfort = 1
+    parameters.safety = 1
+
+    matched = relation_preprocessing_for_bicycle_network_by_operator(parameters, relation_tags, subresult)
+    if (matched) then
+        result.attributes_to_keep["_relation:comfort_safety:bicycle_network_by_operator"] = "yes"
+    end
+
+
+    subresult.attributes_to_keep = {}
+    parameters = default_parameters()
+    parameters.network = 20
+    parameters.networkOperator = {"Brussels Mobility"}
+    parameters.comfort = 1
+    parameters.safety = 1
+
+    matched = relation_preprocessing_for_bicycle_network_by_operator(parameters, relation_tags, subresult)
+    if (matched) then
+        result.attributes_to_keep["_relation:brussels:bicycle_network_by_operator"] = "yes"
+    end
+end
+
+
+
+
 name = "bicycle"
 normalize = false
 vehicle_type = {"vehicle", "bicycle"}
@@ -560,8 +884,22 @@ Comfort is calculated as well, based on the parameters which are padded in
 Created by 
 Originally defined in /home/pietervdvn/werk/AspectedRouting/AspectedRouting/Profiles/bicycle
 Used parameters: 
-    #defaultSpeed: double
+    #maxspeed: pdouble
         Used in bicycle.speed
+    #defaultSpeed: pdouble
+        Used in bicycle.speed
+    comfort: $parameter
+        Used in bicycle.priority.lefthand
+    safety: $parameter
+        Used in bicycle.priority.lefthand
+    network: $parameter
+        Used in bicycle.priority.lefthand
+    timeNeeded: $parameter
+        Used in bicycle.priority.lefthand
+    distance: $parameter
+        Used in bicycle.priority.lefthand
+    #networkOperator: list (string)
+        Used in bicycle.network_by_operator
     
 ]]
 function bicycle(parameters, tags, result)
@@ -578,26 +916,34 @@ function bicycle(parameters, tags, result)
     if (access == nil or access == "no") then
          return
     end
+    tags.access = access
     local oneway = bicycle_oneway(parameters, tags, result)
+    tags.oneway = oneway
     local speed = 
         min({
          legal_maxspeed_be(parameters, tags, result),
-         parameters["#defaultSpeed"]
+         parameters["maxspeed"],
+         
+        multiply({
+         parameters["defaultSpeed"],
+         bicycle_speed_factor(parameters, tags, result)
         })
+        })
+    tags.speed = speed
     local distance = 1 -- the weight per meter for distance travelled is, well, 1m/m
 
-    local weight = 
-        parameters["comfort"] * dot(bicycle_comfort(parameters, tags, result), bicycle_comfort(parameters, tags, result)) + 
-        parameters["safety"] * dot(bicycle_safety(parameters, tags, result), bicycle_safety(parameters, tags, result)) + 
-        parameters["network"] * dot(bicycle_network_score(parameters, tags, result), bicycle_network_score(parameters, tags, result)) + 
-        parameters["time_needed"] * inv(speed) + 
+    local priority = 
+        parameters["comfort"] * bicycle_comfort(parameters, tags, result) + 
+        parameters["safety"] * bicycle_safety(parameters, tags, result) + 
+        parameters["network"] * parse(bicycle_network_by_operator(parameters, tags, result)) + 
+        parameters["timeNeeded"] * speed + 
         parameters["distance"] * distance
 
 
     -- put all the values into the result-table, as needed for itinero
     result.access = 1
     result.speed = speed
-    result.factor = 1/weight
+    result.factor = priority
 
     if (oneway == "both") then
         result.oneway = 0
@@ -611,69 +957,79 @@ end
 
 
 function default_parameters()
-    return {max_speed =  30, defaultSpeed =  15, speed =  0, distance =  0, comfort =  0, safety =  0, withDirection =  "yes", againstDirection =  "yes", network =  0}
+    local parameters = {}
+    parameters.defaultSpeed = 15
+    parameters.maxspeed = 30
+    parameters.timeNeeded = 0
+    parameters.distance = 0
+    parameters.comfort = 0
+    parameters.safety = 0
+    parameters.network = 0
+    parameters.networkOperator = {}
+
+    return parameters
 end
 
 
 --[[
-The fastest route to your destination
+"The fastest route to your destination"
 ]]
 function profile_bicycle_fastest(tags, result)
+    tags = remove_relation_prefix(tags, "fastest")
     local parameters = default_parameters()
-    parameters.description = "The fastest route to your destination"
-    parameters.time_needed = 1
+    parameters.timeNeeded = 1
     bicycle(parameters, tags, result)
 end
 
 --[[
-The shortest route, independent of of speed
+"The shortest route, independent of of speed"
 ]]
 function profile_bicycle_shortest(tags, result)
+    tags = remove_relation_prefix(tags, "shortest")
     local parameters = default_parameters()
-    parameters.description = "The shortest route, independent of of speed"
     parameters.distance = 1
     bicycle(parameters, tags, result)
 end
 
 --[[
-A defensive route shying away from big roads with lots of cars
+"A defensive route shying away from big roads with lots of cars"
 ]]
 function profile_bicycle_safety(tags, result)
+    tags = remove_relation_prefix(tags, "safety")
     local parameters = default_parameters()
-    parameters.description = "A defensive route shying away from big roads with lots of cars"
     parameters.safety = 1
     bicycle(parameters, tags, result)
 end
 
 --[[
-A comfortable route preferring well-paved roads, smaller roads and a bit of scenery at the cost of speed
+"A comfortable route preferring well-paved roads, smaller roads and a bit of scenery at the cost of speed"
 ]]
 function profile_bicycle_comfort(tags, result)
+    tags = remove_relation_prefix(tags, "comfort")
     local parameters = default_parameters()
-    parameters.description = "A comfortable route preferring well-paved roads, smaller roads and a bit of scenery at the cost of speed"
     parameters.comfort = 1
     bicycle(parameters, tags, result)
 end
 
 --[[
-A route which aims to be both safe and comfortable at the cost of speed
+"A route which aims to be both safe and comfortable at the cost of speed"
 ]]
 function profile_bicycle_comfort_safety(tags, result)
+    tags = remove_relation_prefix(tags, "comfort_safety")
     local parameters = default_parameters()
-    parameters.description = "A route which aims to be both safe and comfortable at the cost of speed"
     parameters.comfort = 1
     parameters.safety = 1
     bicycle(parameters, tags, result)
 end
 
 --[[
-A route which prefers cycling over cycling node networks. Non-network parts prefer some comfort and safety. The on-network-part is considered flat (meaning that only distance on the node network counts)
+"A route preferring the cycling network by operator 'Brussels Mobility'"
 ]]
-function profile_bicycle_node_networks(tags, result)
+function profile_bicycle_brussels(tags, result)
+    tags = remove_relation_prefix(tags, "brussels")
     local parameters = default_parameters()
-    parameters.description = "A route which prefers cycling over cycling node networks. Non-network parts prefer some comfort and safety. The on-network-part is considered flat (meaning that only distance on the node network counts)"
     parameters.network = 20
-    parameters.network_node_network_only = "yes"
+    parameters.networkOperator = {"Brussels Mobility"}
     parameters.comfort = 1
     parameters.safety = 1
     bicycle(parameters, tags, result)
@@ -708,8 +1064,8 @@ profiles = {
         metric = "custom"
     },
     {
-        name = "node_networks",
-        function_name = "profile_bicycle_node_networks",
+        name = "brussels",
+        function_name = "profile_bicycle_brussels",
         metric = "custom"
     }
 }
@@ -717,73 +1073,108 @@ profiles = {
 
 
 
+ ------------------------------- TESTS -------------------------
 
 
-
---------------------------- Test code -------------------------
+unit_test(bicycle_safety, "bicycle.safety", 0, "0.15", {}, {highway = "primary", cycleway = "no"})
+unit_test(bicycle_safety, "bicycle.safety", 1, "0.285", {}, {highway = "primary", cycleway = "yes"})
+unit_test(bicycle_safety, "bicycle.safety", 2, "0.45", {}, {highway = "primary", cycleway = "track"})
+unit_test(bicycle_safety, "bicycle.safety", 3, "0.4", {}, {highway = "secondary", cycleway = "lane"})
+unit_test(bicycle_safety, "bicycle.safety", 4, "0.9", {}, {highway = "residential"})
+unit_test(bicycle_safety, "bicycle.safety", 5, "1", {}, {highway = "cycleway"})
+unit_test(bicycle_safety, "bicycle.safety", 6, "1.35", {}, {highway = "residential", cyclestreet = "yes"})
+unit_test(bicycle_safety, "bicycle.safety", 7, "0.95", {}, {highway = "cycleway", foot = "designated"})
+unit_test(bicycle_safety, "bicycle.safety", 8, "0.95", {}, {highway = "footway", foot = "designated"})
+unit_test(bicycle_safety, "bicycle.safety", 9, "1.425", {}, {highway = "path", foot = "designated", bicycle = "designated"})
+unit_test(bicycle_safety, "bicycle.safety", 10, "1.5", {}, {highway = "path", bicycle = "designated"})
+unit_test(bicycle_safety, "bicycle.safety", 11, "0.4", {}, {highway = "secondary", ["cycleway:right"] = "lane"})
 
 
-
-unit_test(bicycle_safety, "bicycle.safety", 0, "0.15", {}, {highway =  "primary", cycleway =  "no"})
-unit_test(bicycle_safety, "bicycle.safety", 1, "0.285", {}, {highway =  "primary", cycleway =  "yes"})
-unit_test(bicycle_safety, "bicycle.safety", 2, "0.45", {}, {highway =  "primary", cycleway =  "track"})
-unit_test(bicycle_safety, "bicycle.safety", 3, "0.4", {}, {highway =  "secondary", cycleway =  "lane"})
-unit_test(bicycle_safety, "bicycle.safety", 4, "0.9", {}, {highway =  "residential"})
-unit_test(bicycle_safety, "bicycle.safety", 5, "1", {}, {highway =  "cycleway"})
-unit_test(bicycle_safety, "bicycle.safety", 6, "1.35", {}, {highway =  "residential", cyclestreet =  "yes"})
-unit_test(bicycle_safety, "bicycle.safety", 7, "0.95", {}, {highway =  "cycleway", foot =  "designated"})
-unit_test(bicycle_safety, "bicycle.safety", 8, "0.95", {}, {highway =  "footway", foot =  "designated"})
-unit_test(bicycle_safety, "bicycle.safety", 9, "1.425", {}, {highway =  "path", foot =  "designated", bicycle =  "designated"})
-unit_test(bicycle_safety, "bicycle.safety", 10, "1.5", {}, {highway =  "path", bicycle =  "designated"})
-unit_test(bicycle_safety, "bicycle.safety", 11, "0.4", {}, {highway =  "secondary", ["cycleway:right"] =  "lane"})
+unit_test(bicycle_comfort, "bicycle.comfort", 0, "1", {}, {})
+unit_test(bicycle_comfort, "bicycle.comfort", 1, "1", {}, {highway = "residential"})
+unit_test(bicycle_comfort, "bicycle.comfort", 2, "1.1", {}, {highway = "residential", cyclestreet = "yes"})
+unit_test(bicycle_comfort, "bicycle.comfort", 3, "1.2", {}, {highway = "cycleway"})
+unit_test(bicycle_comfort, "bicycle.comfort", 4, "1.2", {}, {highway = "cycleway", foot = "designated"})
+unit_test(bicycle_comfort, "bicycle.comfort", 5, "0.5", {}, {highway = "path", foot = "designated", bicycle = "designated"})
+unit_test(bicycle_comfort, "bicycle.comfort", 6, "0.5", {}, {highway = "path", bicycle = "designated"})
+unit_test(bicycle_comfort, "bicycle.comfort", 7, "0.95", {}, {highway = "footway", foot = "designated"})
+unit_test(bicycle_comfort, "bicycle.comfort", 8, "0.3", {}, {highway = "primary", cycleway = "no"})
+unit_test(bicycle_comfort, "bicycle.comfort", 9, "0.3", {}, {highway = "primary", cycleway = "yes"})
+unit_test(bicycle_comfort, "bicycle.comfort", 10, "0.36", {}, {highway = "primary", cycleway = "track"})
+unit_test(bicycle_comfort, "bicycle.comfort", 11, "0.4", {}, {highway = "secondary", cycleway = "lane"})
+unit_test(bicycle_comfort, "bicycle.comfort", 12, "0.4", {}, {highway = "secondary", ["cycleway:right"] = "lane", surface = "asphalt"})
+unit_test(bicycle_comfort, "bicycle.comfort", 13, "1.1", {}, {highway = "residential", cyclestreet = "yes", surface = "asphalt"})
+unit_test(bicycle_comfort, "bicycle.comfort", 14, "2", {}, {railway = "abandoned"})
+unit_test(bicycle_comfort, "bicycle.comfort", 15, "2", {}, {towpath = "yes"})
+unit_test(bicycle_comfort, "bicycle.comfort", 16, "4", {}, {railway = "abandoned", towpath = "yes"})
 
 
 unit_test(bicycle_oneway, "bicycle.oneway", 0, "both", {}, {})
-unit_test(bicycle_oneway, "bicycle.oneway", 1, "both", {}, {oneway =  "no"})
-unit_test(bicycle_oneway, "bicycle.oneway", 2, "with", {}, {oneway =  "no", ["oneway:bicycle"] =  "yes"})
-unit_test(bicycle_oneway, "bicycle.oneway", 3, "with", {}, {junction =  "roundabout"})
-unit_test(bicycle_oneway, "bicycle.oneway", 4, "both", {}, {oneway =  "yes", cycleway =  "opposite"})
-unit_test(bicycle_oneway, "bicycle.oneway", 5, "against", {}, {oneway =  "yes", ["oneway:bicycle"] =  "-1"})
-unit_test(bicycle_oneway, "bicycle.oneway", 6, "with", {}, {highway =  "residential", oneway =  "yes"})
-unit_test(bicycle_oneway, "bicycle.oneway", 7, "both", {}, {highway =  "residential", oneway =  "no"})
-unit_test(bicycle_oneway, "bicycle.oneway", 8, "both", {}, {highway =  "residential", oneway =  "yes", ["oneway:bicycle"] =  "no"})
-unit_test(bicycle_oneway, "bicycle.oneway", 9, "with", {}, {highway =  "residential", junction =  "roundabout"})
-unit_test(bicycle_oneway, "bicycle.oneway", 10, "both", {}, {highway =  "residential", ["oneway:bicycle"] =  "no", junction =  "roundabout"})
-unit_test(bicycle_oneway, "bicycle.oneway", 11, "against", {}, {highway =  "residential", ["oneway:bicycle"] =  "-1"})
-unit_test(bicycle_oneway, "bicycle.oneway", 12, "both", {}, {highway =  "residential", oneway =  "invalidKey", ["oneway:bicycle"] =  "no"})
-unit_test(bicycle_oneway, "bicycle.oneway", 13, "with", {}, {highway =  "secondary", oneway =  "yes", ["cycleway:right"] =  "track"})
-unit_test(bicycle_oneway, "bicycle.oneway", 14, "both", {}, {highway =  "secondary", oneway =  "yes", ["cycleway:left"] =  "track"})
-unit_test(bicycle_oneway, "bicycle.oneway", 15, "both", {}, {highway =  "secondary", oneway =  "yes", cycleway =  "track"})
-unit_test(bicycle_oneway, "bicycle.oneway", 16, "with", {}, {oneway =  "yes", ["cycleway:left"] =  "no"})
-unit_test(bicycle_oneway, "bicycle.oneway", 17, "both", {}, {highway =  "residential", oneway =  "yes", ["cycleway:left"] =  "lane"})
+unit_test(bicycle_oneway, "bicycle.oneway", 1, "both", {}, {oneway = "no"})
+unit_test(bicycle_oneway, "bicycle.oneway", 2, "with", {}, {oneway = "no", ["oneway:bicycle"] = "yes"})
+unit_test(bicycle_oneway, "bicycle.oneway", 3, "with", {}, {junction = "roundabout"})
+unit_test(bicycle_oneway, "bicycle.oneway", 4, "both", {}, {oneway = "yes", cycleway = "opposite"})
+unit_test(bicycle_oneway, "bicycle.oneway", 5, "against", {}, {oneway = "yes", ["oneway:bicycle"] = "-1"})
+unit_test(bicycle_oneway, "bicycle.oneway", 6, "with", {}, {highway = "residential", oneway = "yes"})
+unit_test(bicycle_oneway, "bicycle.oneway", 7, "both", {}, {highway = "residential", oneway = "no"})
+unit_test(bicycle_oneway, "bicycle.oneway", 8, "both", {}, {highway = "residential", oneway = "yes", ["oneway:bicycle"] = "no"})
+unit_test(bicycle_oneway, "bicycle.oneway", 9, "with", {}, {highway = "residential", junction = "roundabout"})
+unit_test(bicycle_oneway, "bicycle.oneway", 10, "both", {}, {highway = "residential", ["oneway:bicycle"] = "no", junction = "roundabout"})
+unit_test(bicycle_oneway, "bicycle.oneway", 11, "against", {}, {highway = "residential", ["oneway:bicycle"] = "-1"})
+unit_test(bicycle_oneway, "bicycle.oneway", 12, "both", {}, {highway = "residential", oneway = "invalidKey", ["oneway:bicycle"] = "no"})
+unit_test(bicycle_oneway, "bicycle.oneway", 13, "with", {}, {highway = "secondary", oneway = "yes", ["cycleway:right"] = "track"})
+unit_test(bicycle_oneway, "bicycle.oneway", 14, "both", {}, {highway = "secondary", oneway = "yes", ["cycleway:left"] = "track"})
+unit_test(bicycle_oneway, "bicycle.oneway", 15, "both", {}, {highway = "secondary", oneway = "yes", cycleway = "track"})
+unit_test(bicycle_oneway, "bicycle.oneway", 16, "with", {}, {oneway = "yes", ["cycleway:left"] = "no"})
+unit_test(bicycle_oneway, "bicycle.oneway", 17, "both", {}, {highway = "residential", oneway = "yes", ["cycleway:left"] = "lane"})
 
 
 unit_test(bicycle_legal_access, "bicycle.legal_access", 0, "no", {}, {})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 1, "no", {}, {access =  "no"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 2, "yes", {}, {bicycle =  "yes", access =  "no"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 3, "permissive", {}, {highway =  "path"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 4, "yes", {}, {highway =  "pedestrian", bicycle =  "yes"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 5, "dismount", {}, {highway =  "pedestrian"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 6, "designated", {}, {highway =  "cycleway"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 7, "destination", {}, {highway =  "residential", access =  "destination"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 8, "no", {}, {highway =  "residential", access =  "private"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 9, "designated", {}, {highway =  "residential", bicycle =  "designated"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 10, "designated", {}, {highway =  "motorway", bicycle =  "designated"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 11, "no", {}, {highway =  "residential", bicycle =  "use_sidepath"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 12, "yes", {}, {highway =  "residential", access =  "no", ["anyways:access"] =  "yes"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 13, "no", {}, {highway =  "primary"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 14, "yes", {}, {highway =  "primary", ["cycleway:right"] =  "yes"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 15, "yes", {}, {highway =  "primary", ["cycleway:right"] =  "yes"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 16, "yes", {}, {highway =  "secondary", ["cycleway:right"] =  "track"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 17, "destination", {}, {highway =  "service", access =  "destination"})
-unit_test(bicycle_legal_access, "bicycle.legal_access", 18, "no", {}, {highway =  "residential", bicycle =  "use_sidepath"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 1, "no", {}, {access = "no"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 2, "yes", {}, {bicycle = "yes", access = "no"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 3, "permissive", {}, {highway = "path"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 4, "yes", {}, {highway = "pedestrian", bicycle = "yes"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 5, "dismount", {}, {highway = "pedestrian"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 6, "designated", {}, {highway = "cycleway"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 7, "destination", {}, {highway = "residential", access = "destination"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 8, "no", {}, {highway = "residential", access = "private"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 9, "designated", {}, {highway = "residential", bicycle = "designated"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 10, "designated", {}, {highway = "motorway", bicycle = "designated"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 11, "no", {}, {highway = "residential", bicycle = "use_sidepath"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 12, "yes", {}, {highway = "residential", access = "no", ["anyways:access"] = "yes"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 13, "no", {}, {highway = "primary"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 14, "yes", {}, {highway = "primary", ["cycleway:right"] = "yes"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 15, "yes", {}, {highway = "primary", ["cycleway:right"] = "yes"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 16, "yes", {}, {highway = "secondary", ["cycleway:right"] = "track"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 17, "destination", {}, {highway = "service", access = "destination"})
+unit_test(bicycle_legal_access, "bicycle.legal_access", 18, "no", {}, {highway = "residential", bicycle = "use_sidepath"})
+
+
+unit_test(bicycle_speed_factor, "bicycle.speed_factor", 0, "1", {}, {})
+unit_test(bicycle_speed_factor, "bicycle.speed_factor", 1, "1", {}, {highway = "residential"})
+unit_test(bicycle_speed_factor, "bicycle.speed_factor", 2, "0.75", {}, {incline = "up", highway = "residential"})
+unit_test(bicycle_speed_factor, "bicycle.speed_factor", 3, "1.25", {}, {incline = "down", highway = "residential"})
+unit_test(bicycle_speed_factor, "bicycle.speed_factor", 4, "0.3", {}, {incline = "up", surface = "mud", highway = "residential"})
+unit_test(bicycle_speed_factor, "bicycle.speed_factor", 5, "1.125", {}, {incline = "down", surface = "sett", highway = "residential"})
+unit_test(bicycle_speed_factor, "bicycle.speed_factor", 6, "0.675", {}, {incline = "up", surface = "sett", highway = "residential"})
+unit_test(bicycle_speed_factor, "bicycle.speed_factor", 7, "0.9", {}, {surface = "sett", highway = "residential"})
+unit_test(bicycle_speed_factor, "bicycle.speed_factor", 8, "1", {}, {surface = "asphalt", highway = "residential"})
+unit_test(bicycle_speed_factor, "bicycle.speed_factor", 9, "0.15", {}, {highway = "residential", access = "dismount"})
+unit_test(bicycle_speed_factor, "bicycle.speed_factor", 10, "0.0315", {}, {incline = "up", surface = "mud", highway = "track", access = "dismount"})
+
+
+unit_test_profile(profile_bicycle_fastest, "fastest", 0, {access = "no", speed = 0, oneway = "both", weight = 0 }, {})
+unit_test_profile(profile_bicycle_fastest, "fastest", 1, {access = "designated", speed = 15, oneway = "both", weight = 15 }, {highway = "cycleway"})
+unit_test_profile(profile_bicycle_fastest, "fastest", 2, {access = "no", speed = 0, oneway = "both", weight = 0 }, {highway = "primary"})
+unit_test_profile(profile_bicycle_fastest, "fastest", 3, {access = "dismount", speed = 2.25, oneway = "both", weight = 2.25 }, {highway = "pedestrian"})
+unit_test_profile(profile_bicycle_fastest, "fastest", 4, {access = "yes", speed = 15, oneway = "both", weight = 15 }, {highway = "pedestrian", bicycle = "yes"})
+unit_test_profile(profile_bicycle_fastest, "fastest", 5, {access = "yes", speed = 15, oneway = "both", weight = 15 }, {highway = "residential"})
+
+
+unit_test_profile(profile_bicycle_brussels, "brussels", 0, {access = "no", speed = 0, oneway = "both", weight = 0 }, {})
+unit_test_profile(profile_bicycle_brussels, "brussels", 1, {access = "yes", speed = 15, oneway = "both", weight = 1.9 }, {highway = "residential"})
+unit_test_profile(profile_bicycle_brussels, "brussels", 2, {access = "yes", speed = 15, oneway = "both", weight = 21.9 }, {highway = "residential", ["_relation:bicycle_network_by_operator"] = "yes"})
 
-unit_test_profile(profile_bicycle_fastest, "fastest", 0, {access = 0, speed = 0, oneway = 0, weight = 0 }, {})
-unit_test_profile(profile_bicycle_fastest, "fastest", 1, {access = 1, speed = 30, oneway = 0, weight = 0.03333 }, {highway =  "cycleway"})
-unit_test_profile(profile_bicycle_fastest, "fastest", 2, {access = 0, speed = 0, oneway = 0, weight = 0 }, {highway =  "primary"})
-unit_test_profile(profile_bicycle_fastest, "fastest", 3, {access = 1, speed = 15, oneway = 0, weight = 0.06666 }, {highway =  "pedestrian"})
-unit_test_profile(profile_bicycle_fastest, "fastest", 4, {access = 1, speed = 15, oneway = 0, weight = 0.0666 }, {highway =  "pedestrian", bicycle =  "yes"})
-unit_test_profile(profile_bicycle_fastest, "fastest", 5, {access = 1, speed = 30, oneway = 0, weight = 0.033333 }, {highway =  "residential"})
 
 
 if (itinero == nil) then