Problem
Suppose you have a binding B with a deep nested structure and you want to find out whether B/x/y/z exists. You could make an expression with a bunch of existence tests combined with the logical and operator:
1 B!x && B/x!y && B/x/y!z
But that can get repetitive if you do it many times. Of course we can solve this with a little function.
List Path Implementation
The obvious way to express a path is as a list of text strings. It's pretty easy to write a recusive function which will test for the existence of paths specified this way:
1 /**nocache**/
2 path_exists(b, path) {
3 return if _length(path) == 0 then TRUE else {
4 n = _head(path);
5 value if (_is_binding(b) && b!$n)
6 then path_exists(b/$n, _tail(path))
7 else FALSE;
8 };
9 };
Here's an example using that:
1 b = [a/b/c/d/e/f = 1];
2 test = <
3 path_exists(b, <"a", "b", "c", "d", "e", "f">),
4 path_exists(b, <"a", "b", "c", "d", "e", "x">),
5 path_exists(b, <"a", "b", "c", "d", "e">),
6 path_exists(b, <"a", "b", "c", "d", "x">),
7 path_exists(b, <"a", "b", "c", "d">),
8 path_exists(b, <"a", "b", "c", "x">),
9 path_exists(b, <"a", "b", "c">),
10 path_exists(b, <"a", "b", "x">),
11 path_exists(b, <"a", "b">),
12 path_exists(b, <"a", "x">),
13 path_exists(b, <"a">),
14 path_exists(b, <"x">),
15 path_exists(b, <>),
16 >;
Binding Path Implementation
Though lists obviously work, it's shorter and perhaps more natural to write a path like this:
1 [x/y/z=1]
Than like this:
1 <"x","y","z">
So probably the user should be able to specify the path to test for as a binding. Of course if we do that, people might want to test for the existence of multiple paths in one call, which wouldn't be too hard.
1 /**nocache**/
2 paths_exist(b, path) {
3 return if _is_binding(path) then {
4 result = TRUE;
5 foreach [n = v] in path do
6 {
7 result = (result &&
8 (if _is_binding(b) && b!$n
9 then paths_exist(b/$n, v)
10 else FALSE));
11 };
12 value result;
13 } else TRUE;
14 };
Here's an example using this version:
1 b = [a/b/c/d = 1, e/f/g/h = 2];
2 test = <
3 paths_exist(b, [a/b/c/d = 1, e/f/g/h = 1]),
4 paths_exist(b, [a/b/c/x = 1, e/f/g/h = 1]),
5 paths_exist(b, [a/b/c/d = 1, e/f/g/x = 1]),
6 paths_exist(b, [a/b/c = 1, e/f/g = 1]),
7 paths_exist(b, [a/b/x = 1, e/f/g = 1]),
8 paths_exist(b, [a/b/c = 1, e/f/x = 1]),
9 paths_exist(b, [a/b = 1, e/f = 1]),
10 paths_exist(b, [a/x = 1, e/f = 1]),
11 paths_exist(b, [a/b = 1, e/x = 1]),
12 >;
Combined Implementation
As long as we're written implementations that use both lists and bindings, we might as well combine them:
1 /**nocache**/
2 paths_exist(b, path) {
3 return if _is_binding(path) then {
4 result = TRUE;
5 foreach [n = v] in path do
6 {
7 result = (result &&
8 (if _is_binding(b) && b!$n
9 then paths_exist(b/$n, v)
10 else FALSE));
11 };
12 value result;
13 } else if _is_list(path) then {
14 value if _length(path) == 0 then TRUE else {
15 n = _head(path);
16 value if (_is_binding(b) && b!$n)
17 then paths_exist(b/$n, _tail(path))
18 else FALSE;
19 };
20 } else TRUE;
21 };
Here's a test showing both kinds:
1 b = [a/b/c/d = 1, e/f/g/h = 2];
2 test = <
3 "binding paths:"
4 paths_exist(b, [a/b/c/d = 1, e/f/g/h = 1]),
5 paths_exist(b, [a/b/c/x = 1, e/f/g/h = 1]),
6 paths_exist(b, [a/b/c/d = 1, e/f/g/x = 1]),
7 paths_exist(b, [a/b/c = 1, e/f/g = 1]),
8 paths_exist(b, [a/b/x = 1, e/f/g = 1]),
9 paths_exist(b, [a/b/c = 1, e/f/x = 1]),
10 paths_exist(b, [a/b = 1, e/f = 1]),
11 paths_exist(b, [a/x = 1, e/f = 1]),
12 paths_exist(b, [a/b = 1, e/x = 1]),
13 "list paths:",
14 paths_exist(b, <"a", "b", "c", "d">),
15 paths_exist(b, <"a", "b", "c", "x">),
16 paths_exist(b, <"a", "b", "c">),
17 paths_exist(b, <"a", "b", "x">),
18 paths_exist(b, <"a", "b">),
19 paths_exist(b, <"a", "x">),
20 paths_exist(b, <"a">),
21 paths_exist(b, <"x">),
22 paths_exist(b, <>),
23 >;