metaractor-sycamore 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.editorconfig +24 -0
- data/.envrc +3 -0
- data/.github/workflows/specs.yml +13 -0
- data/.gitignore +16 -0
- data/.rspec +6 -0
- data/.ruby-version +1 -0
- data/.yardopts +13 -0
- data/AUTHORS +2 -0
- data/CHANGELOG.md +88 -0
- data/CONTRIBUTING.md +5 -0
- data/CREDITS +0 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +22 -0
- data/README.md +571 -0
- data/Rakefile +36 -0
- data/VERSION +1 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/devenv.lock +210 -0
- data/devenv.nix +25 -0
- data/devenv.yaml +8 -0
- data/lib/outstand_sycamore.rb +1 -0
- data/lib/sycamore/absence.rb +179 -0
- data/lib/sycamore/exceptions.rb +16 -0
- data/lib/sycamore/extension/nothing.rb +4 -0
- data/lib/sycamore/extension/path.rb +7 -0
- data/lib/sycamore/extension/tree.rb +4 -0
- data/lib/sycamore/extension.rb +2 -0
- data/lib/sycamore/nothing.rb +157 -0
- data/lib/sycamore/path.rb +266 -0
- data/lib/sycamore/path_root.rb +43 -0
- data/lib/sycamore/stree.rb +4 -0
- data/lib/sycamore/tree.rb +1469 -0
- data/lib/sycamore/version.rb +28 -0
- data/lib/sycamore.rb +13 -0
- data/support/doctest_helper.rb +2 -0
- data/support/travis.sh +6 -0
- data/sycamore.gemspec +29 -0
- metadata +152 -0
data/devenv.lock
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
{
|
2
|
+
"nodes": {
|
3
|
+
"devenv": {
|
4
|
+
"locked": {
|
5
|
+
"dir": "src/modules",
|
6
|
+
"lastModified": 1686054274,
|
7
|
+
"narHash": "sha256-93aebyN7EMmeFFXisFIvp28UEbrozu79vd3pKPjvNR0=",
|
8
|
+
"owner": "cachix",
|
9
|
+
"repo": "devenv",
|
10
|
+
"rev": "c51a56bac8853c019241fe8d821c0a0d82422835",
|
11
|
+
"type": "github"
|
12
|
+
},
|
13
|
+
"original": {
|
14
|
+
"dir": "src/modules",
|
15
|
+
"owner": "cachix",
|
16
|
+
"repo": "devenv",
|
17
|
+
"type": "github"
|
18
|
+
}
|
19
|
+
},
|
20
|
+
"flake-compat": {
|
21
|
+
"flake": false,
|
22
|
+
"locked": {
|
23
|
+
"lastModified": 1673956053,
|
24
|
+
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
25
|
+
"owner": "edolstra",
|
26
|
+
"repo": "flake-compat",
|
27
|
+
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
28
|
+
"type": "github"
|
29
|
+
},
|
30
|
+
"original": {
|
31
|
+
"owner": "edolstra",
|
32
|
+
"repo": "flake-compat",
|
33
|
+
"type": "github"
|
34
|
+
}
|
35
|
+
},
|
36
|
+
"flake-compat_2": {
|
37
|
+
"flake": false,
|
38
|
+
"locked": {
|
39
|
+
"lastModified": 1673956053,
|
40
|
+
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
41
|
+
"owner": "edolstra",
|
42
|
+
"repo": "flake-compat",
|
43
|
+
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
44
|
+
"type": "github"
|
45
|
+
},
|
46
|
+
"original": {
|
47
|
+
"owner": "edolstra",
|
48
|
+
"repo": "flake-compat",
|
49
|
+
"type": "github"
|
50
|
+
}
|
51
|
+
},
|
52
|
+
"flake-utils": {
|
53
|
+
"locked": {
|
54
|
+
"lastModified": 1667395993,
|
55
|
+
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
56
|
+
"owner": "numtide",
|
57
|
+
"repo": "flake-utils",
|
58
|
+
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
59
|
+
"type": "github"
|
60
|
+
},
|
61
|
+
"original": {
|
62
|
+
"owner": "numtide",
|
63
|
+
"repo": "flake-utils",
|
64
|
+
"type": "github"
|
65
|
+
}
|
66
|
+
},
|
67
|
+
"flake-utils_2": {
|
68
|
+
"inputs": {
|
69
|
+
"systems": "systems"
|
70
|
+
},
|
71
|
+
"locked": {
|
72
|
+
"lastModified": 1685518550,
|
73
|
+
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
74
|
+
"owner": "numtide",
|
75
|
+
"repo": "flake-utils",
|
76
|
+
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
77
|
+
"type": "github"
|
78
|
+
},
|
79
|
+
"original": {
|
80
|
+
"owner": "numtide",
|
81
|
+
"repo": "flake-utils",
|
82
|
+
"type": "github"
|
83
|
+
}
|
84
|
+
},
|
85
|
+
"gitignore": {
|
86
|
+
"inputs": {
|
87
|
+
"nixpkgs": [
|
88
|
+
"pre-commit-hooks",
|
89
|
+
"nixpkgs"
|
90
|
+
]
|
91
|
+
},
|
92
|
+
"locked": {
|
93
|
+
"lastModified": 1660459072,
|
94
|
+
"narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=",
|
95
|
+
"owner": "hercules-ci",
|
96
|
+
"repo": "gitignore.nix",
|
97
|
+
"rev": "a20de23b925fd8264fd7fad6454652e142fd7f73",
|
98
|
+
"type": "github"
|
99
|
+
},
|
100
|
+
"original": {
|
101
|
+
"owner": "hercules-ci",
|
102
|
+
"repo": "gitignore.nix",
|
103
|
+
"type": "github"
|
104
|
+
}
|
105
|
+
},
|
106
|
+
"nixpkgs": {
|
107
|
+
"locked": {
|
108
|
+
"lastModified": 1685938391,
|
109
|
+
"narHash": "sha256-96Jw6TbWDLSopt5jqCW8w1Fc1cjQyZlhfBnJ3OZGpME=",
|
110
|
+
"owner": "NixOS",
|
111
|
+
"repo": "nixpkgs",
|
112
|
+
"rev": "31cd1b4afbaf0b1e81272ee9c31d1ab606503aed",
|
113
|
+
"type": "github"
|
114
|
+
},
|
115
|
+
"original": {
|
116
|
+
"owner": "NixOS",
|
117
|
+
"ref": "nixpkgs-unstable",
|
118
|
+
"repo": "nixpkgs",
|
119
|
+
"type": "github"
|
120
|
+
}
|
121
|
+
},
|
122
|
+
"nixpkgs-ruby": {
|
123
|
+
"inputs": {
|
124
|
+
"flake-compat": "flake-compat",
|
125
|
+
"flake-utils": "flake-utils",
|
126
|
+
"nixpkgs": [
|
127
|
+
"nixpkgs"
|
128
|
+
]
|
129
|
+
},
|
130
|
+
"locked": {
|
131
|
+
"lastModified": 1685159223,
|
132
|
+
"narHash": "sha256-rwHYapZEvxDJ8dw3hMGY/AE9c6qls9CifAUvObnx7e0=",
|
133
|
+
"owner": "bobvanderlinden",
|
134
|
+
"repo": "nixpkgs-ruby",
|
135
|
+
"rev": "dfaf58567eda18bad5340a9432e601d96c497943",
|
136
|
+
"type": "github"
|
137
|
+
},
|
138
|
+
"original": {
|
139
|
+
"owner": "bobvanderlinden",
|
140
|
+
"repo": "nixpkgs-ruby",
|
141
|
+
"type": "github"
|
142
|
+
}
|
143
|
+
},
|
144
|
+
"nixpkgs-stable": {
|
145
|
+
"locked": {
|
146
|
+
"lastModified": 1685801374,
|
147
|
+
"narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=",
|
148
|
+
"owner": "NixOS",
|
149
|
+
"repo": "nixpkgs",
|
150
|
+
"rev": "c37ca420157f4abc31e26f436c1145f8951ff373",
|
151
|
+
"type": "github"
|
152
|
+
},
|
153
|
+
"original": {
|
154
|
+
"owner": "NixOS",
|
155
|
+
"ref": "nixos-23.05",
|
156
|
+
"repo": "nixpkgs",
|
157
|
+
"type": "github"
|
158
|
+
}
|
159
|
+
},
|
160
|
+
"pre-commit-hooks": {
|
161
|
+
"inputs": {
|
162
|
+
"flake-compat": "flake-compat_2",
|
163
|
+
"flake-utils": "flake-utils_2",
|
164
|
+
"gitignore": "gitignore",
|
165
|
+
"nixpkgs": [
|
166
|
+
"nixpkgs"
|
167
|
+
],
|
168
|
+
"nixpkgs-stable": "nixpkgs-stable"
|
169
|
+
},
|
170
|
+
"locked": {
|
171
|
+
"lastModified": 1686050334,
|
172
|
+
"narHash": "sha256-R0mczWjDzBpIvM3XXhO908X5e2CQqjyh/gFbwZk/7/Q=",
|
173
|
+
"owner": "cachix",
|
174
|
+
"repo": "pre-commit-hooks.nix",
|
175
|
+
"rev": "6881eb2ae5d8a3516e34714e7a90d9d95914c4dc",
|
176
|
+
"type": "github"
|
177
|
+
},
|
178
|
+
"original": {
|
179
|
+
"owner": "cachix",
|
180
|
+
"repo": "pre-commit-hooks.nix",
|
181
|
+
"type": "github"
|
182
|
+
}
|
183
|
+
},
|
184
|
+
"root": {
|
185
|
+
"inputs": {
|
186
|
+
"devenv": "devenv",
|
187
|
+
"nixpkgs": "nixpkgs",
|
188
|
+
"nixpkgs-ruby": "nixpkgs-ruby",
|
189
|
+
"pre-commit-hooks": "pre-commit-hooks"
|
190
|
+
}
|
191
|
+
},
|
192
|
+
"systems": {
|
193
|
+
"locked": {
|
194
|
+
"lastModified": 1681028828,
|
195
|
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
196
|
+
"owner": "nix-systems",
|
197
|
+
"repo": "default",
|
198
|
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
199
|
+
"type": "github"
|
200
|
+
},
|
201
|
+
"original": {
|
202
|
+
"owner": "nix-systems",
|
203
|
+
"repo": "default",
|
204
|
+
"type": "github"
|
205
|
+
}
|
206
|
+
}
|
207
|
+
},
|
208
|
+
"root": "root",
|
209
|
+
"version": 7
|
210
|
+
}
|
data/devenv.nix
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
{ pkgs, ... }:
|
2
|
+
|
3
|
+
{
|
4
|
+
# https://devenv.sh/packages/
|
5
|
+
packages = with pkgs; [
|
6
|
+
git
|
7
|
+
];
|
8
|
+
|
9
|
+
# https://devenv.sh/languages/
|
10
|
+
languages.ruby.enable = true;
|
11
|
+
# Uses bobvanderlinden/nixpkgs-ruby to supply any version of ruby
|
12
|
+
languages.ruby.versionFile = ./.ruby-version;
|
13
|
+
|
14
|
+
enterShell = ''
|
15
|
+
export BUNDLE_BIN="$DEVENV_ROOT/.devenv/bin"
|
16
|
+
export PATH="$DEVENV_PROFILE/bin:$DEVENV_ROOT/bin:$BUNDLE_BIN:$PATH"
|
17
|
+
export BOOTSNAP_CACHE_DIR="$DEVENV_ROOT/.devenv/state"
|
18
|
+
'';
|
19
|
+
|
20
|
+
# The unix socket path can't be "too long".
|
21
|
+
# Make sure it's short for when we need it.
|
22
|
+
env.RUBY_DEBUG_SOCK_DIR = "/tmp/";
|
23
|
+
|
24
|
+
# See full reference at https://devenv.sh/reference/options/
|
25
|
+
}
|
data/devenv.yaml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'sycamore'
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Sycamore
|
4
|
+
|
5
|
+
##
|
6
|
+
# An Absence object represents the absence of a specific child {Sycamore::Tree}.
|
7
|
+
#
|
8
|
+
# +Absence+ instances get created when accessing non-existent children of a
|
9
|
+
# Tree with {Tree#child_of} or {Tree#child_at}.
|
10
|
+
# It is not intended to be instantiated by the user.
|
11
|
+
#
|
12
|
+
# An +Absence+ object can be used like a normal {Sycamore::Tree}.
|
13
|
+
# {Tree::QUERY_METHODS Query} and {Tree::DESTRUCTIVE_COMMAND_METHODS pure destructive command method}
|
14
|
+
# calls get delegated to {Sycamore::Nothing}, i.e. will behave like an empty Tree.
|
15
|
+
# On every other {Tree::COMMAND_METHODS command} calls, the +Absence+ object
|
16
|
+
# gets resolved, which means the missing tree will be created, added to the
|
17
|
+
# parent tree and the method call gets delegated to the now existing tree.
|
18
|
+
# After the +Absence+ object is resolved all subsequent method calls are
|
19
|
+
# delegated to the created tree.
|
20
|
+
# The type of tree eventually created is determined by the {Tree#new_child}
|
21
|
+
# implementation of the parent tree and the parent node.
|
22
|
+
#
|
23
|
+
class Absence < Delegator
|
24
|
+
|
25
|
+
##
|
26
|
+
# @api private
|
27
|
+
#
|
28
|
+
def initialize(parent_tree, parent_node)
|
29
|
+
@parent_tree, @parent_node = parent_tree, parent_node
|
30
|
+
end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
alias at new
|
34
|
+
end
|
35
|
+
|
36
|
+
########################################################################
|
37
|
+
# presence creation
|
38
|
+
########################################################################
|
39
|
+
|
40
|
+
##
|
41
|
+
# The tree object to which all method calls are delegated.
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
#
|
45
|
+
def presence
|
46
|
+
@tree or Nothing
|
47
|
+
end
|
48
|
+
|
49
|
+
alias __getobj__ presence
|
50
|
+
|
51
|
+
##
|
52
|
+
# @api private
|
53
|
+
#
|
54
|
+
def create
|
55
|
+
@parent_tree = @parent_tree.add_node_with_empty_child(@parent_node)
|
56
|
+
@tree = @parent_tree[@parent_node]
|
57
|
+
end
|
58
|
+
|
59
|
+
########################################################################
|
60
|
+
# Absence and Nothing predicates
|
61
|
+
########################################################################
|
62
|
+
|
63
|
+
##
|
64
|
+
# (see Tree#absent?)
|
65
|
+
#
|
66
|
+
def absent?
|
67
|
+
@tree.nil?
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# (see Tree#nothing?)
|
72
|
+
#
|
73
|
+
def nothing?
|
74
|
+
false
|
75
|
+
end
|
76
|
+
|
77
|
+
########################################################################
|
78
|
+
# Element access
|
79
|
+
########################################################################
|
80
|
+
|
81
|
+
#####################
|
82
|
+
# query methods #
|
83
|
+
#####################
|
84
|
+
|
85
|
+
def child_of(node)
|
86
|
+
if absent?
|
87
|
+
raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
|
88
|
+
|
89
|
+
Absence.at(self, node)
|
90
|
+
else
|
91
|
+
presence.child_of(node)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def child_at(*path)
|
96
|
+
if absent?
|
97
|
+
# TODO: This is duplication of Tree#child_at! How can we remove it, without introducing a module for this single method or inherit from Tree?
|
98
|
+
case path.length
|
99
|
+
when 0 then raise ArgumentError, 'wrong number of arguments (given 0, expected 1+)'
|
100
|
+
when 1 then child_of(*path)
|
101
|
+
else child_of(path[0]).child_at(*path[1..-1])
|
102
|
+
end
|
103
|
+
else
|
104
|
+
presence.child_at(*path)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
alias [] child_at
|
109
|
+
alias dig child_at
|
110
|
+
|
111
|
+
##
|
112
|
+
# A developer-friendly string representation of the absent tree.
|
113
|
+
#
|
114
|
+
# @return [String]
|
115
|
+
#
|
116
|
+
def inspect
|
117
|
+
"#{absent? ? 'absent' : 'present'} child of node #{@parent_node.inspect} in #{@parent_tree.inspect}"
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# Duplicates the resolved tree or raises an error, when unresolved.
|
122
|
+
#
|
123
|
+
# @return [Tree]
|
124
|
+
#
|
125
|
+
# @raise [TypeError] when this {Absence} is not resolved yet
|
126
|
+
#
|
127
|
+
def dup
|
128
|
+
presence.dup
|
129
|
+
end
|
130
|
+
|
131
|
+
##
|
132
|
+
# Clones the resolved tree or raises an error, when unresolved.
|
133
|
+
#
|
134
|
+
# @return [Tree]
|
135
|
+
#
|
136
|
+
# @raise [TypeError] when this {Absence} is not resolved yet
|
137
|
+
#
|
138
|
+
def clone
|
139
|
+
presence.clone
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# Checks if the absent tree is frozen.
|
144
|
+
#
|
145
|
+
# @return [Boolean]
|
146
|
+
#
|
147
|
+
def frozen?
|
148
|
+
if absent?
|
149
|
+
false
|
150
|
+
else
|
151
|
+
presence.frozen?
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
#####################
|
156
|
+
# command methods #
|
157
|
+
#####################
|
158
|
+
|
159
|
+
# TODO: YARD should be informed about this method definitions.
|
160
|
+
Tree.command_methods.each do |command_method|
|
161
|
+
if Tree.pure_destructive_command_methods.include?(command_method)
|
162
|
+
define_method command_method do |*args|
|
163
|
+
if absent?
|
164
|
+
self
|
165
|
+
else
|
166
|
+
presence.send(command_method, *args) # TODO: How can we hand over a possible block? With eval etc.?
|
167
|
+
end
|
168
|
+
end
|
169
|
+
else
|
170
|
+
# TODO: This method should be atomic.
|
171
|
+
define_method command_method do |*args|
|
172
|
+
create if absent?
|
173
|
+
presence.send(command_method, *args) # TODO: How can we hand over a possible block? With eval etc.?
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Sycamore
|
2
|
+
# raised when a value is not a valid node
|
3
|
+
class InvalidNode < ArgumentError ; end
|
4
|
+
|
5
|
+
# raised when trying to call a additive command method of the {Nothing} tree
|
6
|
+
class NothingMutation < StandardError ; end
|
7
|
+
|
8
|
+
# raised when calling {Tree#node} or {Tree#node!} on a Tree with multiple nodes
|
9
|
+
class NonUniqueNodeSet < StandardError ; end
|
10
|
+
|
11
|
+
# raised when calling {Tree#node!} on a Tree without nodes
|
12
|
+
class EmptyNodeSet < StandardError ; end
|
13
|
+
|
14
|
+
# raised when trying to fetch the child of a leaf
|
15
|
+
class ChildError < KeyError ; end
|
16
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Sycamore
|
4
|
+
|
5
|
+
##
|
6
|
+
# The Nothing Tree singleton class.
|
7
|
+
#
|
8
|
+
# The Nothing Tree is an empty Sycamore Tree, and means "there are no nodes".
|
9
|
+
#
|
10
|
+
# It is immutable:
|
11
|
+
# - {Tree::QUERY_METHODS Query method} calls will behave like a normal, empty Tree.
|
12
|
+
# - {Tree::DESTRUCTIVE_COMMAND_METHODS Pure destructive command} calls, will be ignored, i.e. being no-ops.
|
13
|
+
# - But all other {Tree::COMMAND_METHODS command} calls, will raise a {NothingMutation}.
|
14
|
+
#
|
15
|
+
# It is the only Tree object that will return +true+ on a {#nothing?} call.
|
16
|
+
# But like {Absence}, it will return +true+ on {#absent?} and +false+ on {#existent?}.
|
17
|
+
#
|
18
|
+
class NothingTree < Tree
|
19
|
+
include Singleton
|
20
|
+
|
21
|
+
########################################################################
|
22
|
+
# Absence and Nothing predicates
|
23
|
+
########################################################################
|
24
|
+
|
25
|
+
##
|
26
|
+
# (see Tree#nothing?)
|
27
|
+
#
|
28
|
+
def nothing?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# (see Tree#absent?)
|
34
|
+
#
|
35
|
+
def absent?
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
########################################################################
|
40
|
+
# CQS element access
|
41
|
+
########################################################################
|
42
|
+
|
43
|
+
# TODO: YARD should be informed about this method definitions.
|
44
|
+
command_methods.each do |command_method|
|
45
|
+
define_method command_method do |*args|
|
46
|
+
raise NothingMutation, 'attempt to change the Nothing tree'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# TODO: YARD should be informed about this method definitions.
|
51
|
+
pure_destructive_command_methods.each do |command_method|
|
52
|
+
define_method(command_method) { |*args| self }
|
53
|
+
end
|
54
|
+
|
55
|
+
def child_of(node)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_native_object(sleaf_child_as: nil, **args)
|
60
|
+
sleaf_child_as
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# A string representation of the Nothing tree.
|
65
|
+
#
|
66
|
+
# @return [String]
|
67
|
+
#
|
68
|
+
def to_s
|
69
|
+
'Tree[Nothing]'
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# A developer-friendly string representation of the Nothing tree.
|
74
|
+
#
|
75
|
+
# @return [String]
|
76
|
+
#
|
77
|
+
def inspect
|
78
|
+
'#<Sycamore::Nothing>'
|
79
|
+
end
|
80
|
+
|
81
|
+
def freeze
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
85
|
+
########################################################################
|
86
|
+
# Equality
|
87
|
+
########################################################################
|
88
|
+
|
89
|
+
##
|
90
|
+
# Checks if the given object is an empty tree.
|
91
|
+
#
|
92
|
+
# @return [Boolean]
|
93
|
+
#
|
94
|
+
def ==(other)
|
95
|
+
(other.is_a?(Tree) or other.is_a?(Absence)) and other.empty?
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
########################################################################
|
100
|
+
# Falsiness
|
101
|
+
#
|
102
|
+
# Sadly, in Ruby we can't do that match to reach this goal.
|
103
|
+
#
|
104
|
+
# see http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/
|
105
|
+
########################################################################
|
106
|
+
|
107
|
+
##
|
108
|
+
# Try to emulate a falsey value, by negating to +true+.
|
109
|
+
#
|
110
|
+
# @return [Boolean] +true+
|
111
|
+
#
|
112
|
+
# @see http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/
|
113
|
+
#
|
114
|
+
def !
|
115
|
+
true
|
116
|
+
end
|
117
|
+
|
118
|
+
# def nil?
|
119
|
+
# true
|
120
|
+
# end
|
121
|
+
|
122
|
+
|
123
|
+
########################################################################
|
124
|
+
# Some helpers
|
125
|
+
#
|
126
|
+
# Ideally these would be implemented with Refinements, but since they
|
127
|
+
# aren't available anywhere (I'm looking at you, JRuby), we have to be
|
128
|
+
# content with this.
|
129
|
+
#
|
130
|
+
########################################################################
|
131
|
+
|
132
|
+
def like?(object)
|
133
|
+
object.nil? or object.equal? self
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# @api private
|
138
|
+
class NestedStringPresentation
|
139
|
+
include Singleton
|
140
|
+
|
141
|
+
def inspect
|
142
|
+
'n/a'
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# @api private
|
148
|
+
NestedString = NestedStringPresentation.instance.freeze
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
############################################################################
|
153
|
+
# The Nothing Tree Singleton object
|
154
|
+
#
|
155
|
+
Nothing = NothingTree.instance.freeze
|
156
|
+
|
157
|
+
end
|