sub_object 1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ba4fda603b0841fbf226306a1858819a85a733f8c6863c86bcc7091aa3987dbc
4
+ data.tar.gz: b61a445e22d45c5a35f224e867f04b74ee6a03300fe8cbc705e4771f9cba3d79
5
+ SHA512:
6
+ metadata.gz: 0000c9fe5c0d2910ca8b70d63cdabd0e7cf20461c68df7209d75530475eea2ae5d2ccca9b21cfdc20ee48efabca069331d89d92a519e66533d4349adf23939c7
7
+ data.tar.gz: 701376479f084de002d3bf88adcbb9806ba2fa34a5ff989f3d5d50fffc153fce99e6b82c40a3afa6a56672c35138f23eaaa991026a2e74caedde76ec9b588608
data/.gitignore ADDED
@@ -0,0 +1,51 @@
1
+ # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2
+ #
3
+ # If you find yourself ignoring temporary files generated by your text editor
4
+ # or operating system, you probably want to add a global ignore instead:
5
+ # git config --global core.excludesfile '~/.gitignore_global'
6
+
7
+ # Ignore bundler config.
8
+ /.bundle
9
+ /vendor/bundle
10
+
11
+ # Ignore all logfiles and tempfiles.
12
+ /log/*
13
+ /tmp/*
14
+ !/log/.keep
15
+ !/tmp/.keep
16
+
17
+ .rbenv-version
18
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
19
+ .rvmrc
20
+
21
+ /node_modules
22
+ /yarn-error.log
23
+
24
+ .byebug_history
25
+
26
+ *.[oa]
27
+ *.so
28
+ *~
29
+ *.nogem
30
+ *nogem.*
31
+ *.bak
32
+ *.BAK
33
+ *.backup
34
+ *.org
35
+ *.orig
36
+ *.elc
37
+ *.pyc
38
+ \#*\#
39
+
40
+ # Elastic Beanstalk Files
41
+ .elasticbeanstalk/*
42
+ !.elasticbeanstalk/*.cfg.yml
43
+ !.elasticbeanstalk/*.global.yml
44
+
45
+ # yard
46
+ *.yardoc
47
+
48
+ # Ruby Gem doc
49
+ *.gem
50
+ doc/*
51
+
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012-2018 Scott Chacon and others
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Makefile ADDED
@@ -0,0 +1,23 @@
1
+ ALL =
2
+
3
+ objs =
4
+
5
+ .SUFFIXES: .so .o .c .f
6
+
7
+ #.o.so:
8
+ # ${LD} ${LFLAGS} -o $@ $< ${LINK_LIB}
9
+
10
+ all: ${ALL}
11
+
12
+
13
+ .PHONY: clean test doc
14
+ clean:
15
+ $(RM) bin/*~
16
+
17
+ ## You may need RUBYLIB=`pwd`/lib:$RUBYLIB
18
+ test:
19
+ rake test
20
+
21
+ doc:
22
+ yard doc; [[ -x ".github" && ( "README.en.rdoc" -nt ".github/README.md" ) ]] && ( ruby -r rdoc -e 'puts RDoc::Markup::ToMarkdown.new.convert ARGF.read' < README.en.rdoc > .github/README.md ; echo ".github/README.md is updated." ) || exit 0
23
+
data/README.en.rdoc ADDED
@@ -0,0 +1,287 @@
1
+
2
+ = SubObject - Parent class for memory-efficient sub-Something
3
+
4
+ == Summary
5
+
6
+ This class {SubObject} is the parent class for
7
+ {SubString}[http://rubygems.org/gems/sub_string] and alike.
8
+ This class expresses Ruby sub-Object (like Sub-String),
9
+ which are obtained with the +self[i, j]+ method,
10
+ but taking up negligible memory space, as its instance internally holds
11
+ the (likely positional, though arbitrary) information +(i, j)+ only. This class provides the base
12
+ interface so the instance behaves exactly like the original
13
+ class (String for SubString, for example) as duck-typing, except
14
+ destructive modification, which is prohibited.
15
+
16
+ If the original object (which an instance of this class refers to) is ever destructively modified in a way it changes
17
+ its hash value, warning will be issued whenever this instance is accessed.
18
+
19
+ The entire package of SubObject is found in the
20
+ {Ruby Gems page}[http://rubygems.org/gems/sub_object]
21
+ and in
22
+ {Github}[https://github.com/masasakano/sub_object]
23
+
24
+ === Important note to the developers
25
+
26
+ To inherit this class, make sure to include the lines equivalent to
27
+ the following 2 lines, replacing the method name +:to_str+ (which is for
28
+ SubString class) to suit your child class.
29
+
30
+ TO_SOURCE_METHOD = :to_str
31
+ alias_method TO_SOURCE_METHOD, :to_source
32
+
33
+ == Cencept
34
+
35
+ This class takes three parameters in the initialization: *source*, *pos*
36
+ (position), and *size*. *source* is the original object, and
37
+ basically the constructed instance of this class holds these three
38
+ pieces of information only (plus a hash value, strictly speaking).
39
+ Then, whenever it is referred to, it reconstructs
40
+
41
+ source[pos, size]
42
+
43
+ and works exactly like +source[pos, size]+ does
44
+ ({duck-typing}[https://en.wikipedia.org/wiki/Duck_typing]),
45
+ whereas it uses negligible internal memory space on its own. Note the
46
+ information it needs for the +source+ is basically +source.object_id+
47
+ or equivalent, which is nothing in terms of memory use.
48
+ In other words, this class does
49
+ not create the copy of sub-Object from the original source object, as,
50
+ for example, +String#[i,j]+ does.
51
+
52
+ As an example, the child class {SubString}[http://rubygems.org/gems/sub_string] (provided as a different Gem)
53
+ works as:
54
+
55
+ src = "abcdef"
56
+ ss = SubString.new(src, -4, 3) # => Similar to "abcdef"[-4,3]
57
+ print ss # => "cde" (STDOUT)
58
+ ss+'3p' # => "cde3p"
59
+ ss.upcase # => "CDE"
60
+ ss.sub(/^./, 'Q') # => "Qde"
61
+ ss.is_a?(String) # => true
62
+ "xy_"+ss # => "xy_cde"
63
+ "cde" == ss # => true
64
+
65
+ Internally the instance holds the source object. Therefore, as long
66
+ as the instance is alive, the source object is never garbage-collected (GC).
67
+
68
+ If the source object has been destructively altered, such as with the
69
+ destructive method +clear+, the corresponding object of this class
70
+ is likely not to make sense any more. This class can detect such destructive
71
+ modification (using the method +Object#hash+) and issues a warning
72
+ whenever it is accessed after the source object has been destructively
73
+ altered, unless the appropriate global settings (see the next section)
74
+ are set to suppress it.
75
+
76
+ Similarly, because this class supplies an object with the filter
77
+ appropriate for the class when it receives any message (i.e., method),
78
+ it does not make sense to apply a destructive change on the instance of this class.
79
+ Therefore, whenever a destructive method is applied, this class tries
80
+ to raise NoMethodError exception. The routine to identify the
81
+ destructive method relies thoroughly on the method name.
82
+ The methods ending with "!" are regarded as destructive.
83
+ Other standard distructive method names are defined in the constant {SubObject::DESTRUCTIVE_METHODS}.
84
+ Each child class may add entries or modify it.
85
+
86
+ Note that if a (likely user-defined) desturctive method passes the check,
87
+ the result is most likely to be different from intended. It certainly
88
+ never alters this instance destructively (unless +[]+ method of the
89
+ source object returns self, which is against the Ruby convention), and
90
+ the returned value may be not like the expected value.
91
+
92
+ === Potential use
93
+
94
+ You can make a subclass of this class, corresponding to any class that
95
+ has the method of +[i, j]+. For example, *SubArray* class is
96
+ perfectly possible; indeed it is defined as {SubObject::SubArray}.
97
+ To be fair, I am afraid *SubArray* class does not have much practical
98
+ value (apart from the demonstration of the concept!), because the only
99
+ advantage of this class is to use the minimum memory, whereas the
100
+ original sub-array as in +Array#[i,j]+ does not take much memory in
101
+ the first place, given Array is by definition just a container.
102
+
103
+ By stark contrast, each sub-String in Ruby default takes up memory
104
+ according to the length of the sub-String. Consider an example:
105
+
106
+ src = "Some very extremely lengthy string.... (snipped)".
107
+ sub = src[1..-1]
108
+
109
+ The variable +sub+ uses up about the same memory of +src+.
110
+ If a great number of +sub+ is created and held alive, the total memory
111
+ used by the process can become quicly multifold, even by orders of magnitude.
112
+
113
+ This is where this class comes in handy. For example, a parsing
114
+ program applied to a huge text document with a complex grammar may
115
+ hold a number of such String variables. By using this class instead
116
+ of String, it can save some valuable memory.
117
+
118
+ That is precisely why
119
+ {SubString}[http://rubygems.org/gems/sub_string],
120
+ which is a child class of this class and for String, is registered as an official Gem.
121
+ (In practice, this class is a generalised version of *SubString*,
122
+ which Ruby allows as a flexible programming language!)
123
+
124
+ == Description
125
+
126
+ === Initialization
127
+
128
+ Initialize as follows:
129
+
130
+ SubObject.new( source, index1, index2 )
131
+
132
+ Usually, +index1+ is the starting index and +index2+ is the size,
133
+ though how they should be recognized depends on the definition of the method
134
+ +[i,j]+ of +source+.
135
+
136
+ === Constant
137
+
138
+ {SubObject::DESTRUCTIVE_METHODS}:: This public constant is the array holding the list of the names (as String) of the methods that should be recognized as destructive (other than those ending with "!").
139
+ {SubObject::TO_SOURCE_METHOD}:: This public constant specifies the method that projects to (returns) the original-like instance; e.g., :to_str for String.
140
+
141
+ The class variable +TO_SOURCE_METHOD+ is meant to be set by each
142
+ child class (and child classes only). For example, if it is the subclass for String, it should
143
+ be +:to_str+. Specifically, the registered method must respond to
144
+ +source[i,j]+. Nott that in this class (parent class), it is **left unset**, but the (private) method of the
145
+ same name +to_original_method+ returns +:itself+ instead.
146
+
147
+ *WARNING*: Do not set this class variable in this class, as it could result in unexpected behaviours if
148
+ a child class and the parent class are used simultaneously.
149
+
150
+
151
+ === Class-global settings and class methods
152
+
153
+ When this class is accessed after any alteration of the original
154
+ source object has been detected, it may issue warning (as the insntace does not make sense any more).
155
+ The warning is suppressed when the Ruby global variable +$VERBOSE+ is
156
+ nil (it is false in Ruby default). Or, if the following setting is
157
+ made (internaly, it sets/reads a class instance variable) and set non-nil,
158
+ its value precedes +$VERBOSE+:
159
+
160
+ SubObject.verbose # => getter
161
+ SubObject.verbose=true # => setter
162
+
163
+ where SubObject should be replaced with the name of your child class that inherits {SubObject}.
164
+ If this value is true or false, such a warning is issued or suppresed,
165
+ respectively, regardless of the value of the global variable +$VERBOSE+.
166
+
167
+
168
+ === Instance methods
169
+
170
+ The following is the instance methods of {SubObject} unique to this class.
171
+
172
+ +#source()+:: Returns the first argument given in initialization. The returned value is dup-ped and **frozen**.
173
+ +#pos()+:: Returns the second argument given in initialization (usually meaning the starting index).
174
+ +#subsize()+:: Returns the third argument given in initialization (usually meaning the size of the sub-"source"). Usually this is equivalent to the method +#size+; this method is introduced in case the class of the +source+ somehow does not have the +#size+ method.
175
+ +#pos_size()+:: Returns the two-component array of +[pos, subsize]+
176
+ +#to_source()+:: Returns the instance as close as the original +source+ (the class of it, etc).
177
+
178
+ In addition, {#inspect}[SubObject#inspect] is redefined.
179
+
180
+ Any public methods but destructive ones that are defined for the +source+ can
181
+ be applied to the instance of this class.
182
+
183
+ == Algorithm
184
+
185
+ A child class of this class should redefine the constant
186
+ +TO_SOURCE_METHOD+ in the child class, as well as setting alias to the
187
+ appropriate method like,
188
+
189
+ TO_SOURCE_METHOD = :to_str
190
+ alias_method TO_SOURCE_METHOD, :to_source
191
+
192
+ (which is an example for SubString class)
193
+ so the method would project to (return) the corresponding instance as close as the original +source+ (the class of it, etc).
194
+ For example, it should be +:to_str+ for {SubString}[http://rubygems.org/gems/sub_string] and
195
+ +:to_ary+ for {SubObject::SubArray}. Providing they are appropriately set, even
196
+ the equal operator works *regardless of the directions* (as long as the
197
+ methods in the other classes are designed, considering duck-typing
198
+ appropriately - which should be fine for the built-in classes).
199
+
200
+ Internally, almost any method that the instance receives, except for those specific to this class, is processed in
201
+ the following order:
202
+
203
+ 1. {#method_missing}[SubObject#method_missing] (almost any methods except those defined in Object should be processed here)
204
+ 1. +super+ if destuctive (usually NoMethodError, because Object class does not have "destructive" methods, though you may argue +taint+ etc is "destructive")
205
+ 2. Else, +send+ to {#to_source}[SubObject#to_source]
206
+ 2. {#to_source}[SubObject#to_source]
207
+ 1. check whether the original {#source}[SubObject#source] has been altered and issues a warning if the conditions are met.
208
+ 2. +#source+[+#pos+, +#subsize+]
209
+ 3. +send+ to +TO_SOURCE_METHOD+ (+:to_str+ etc; +:itself+ in default)
210
+ 3. Back to the method received
211
+ 1. +send+ the method with the arguments and block to it.
212
+
213
+ Note {#respond_to_missing?}[SubObject#respond_to_missing?] is redefined so those instance methods are
214
+ recognized appropriately by +#respond_to?+ and Ruby built-in
215
+ +#default?+ sentence.
216
+
217
+ (See {this post}[https://stackoverflow.com/questions/44107544/respond-to-versus-defined/58673436#58673436]
218
+ for how Ruby handles in +#respond_to?+ etc.)
219
+
220
+ == Example
221
+
222
+ An example of +SubObject+ with a test class +MyC+, which
223
+
224
+ 1. takes the given single argument in initialization, and keeps it as its instance variable (+@x+),
225
+ 2. returns an own instance having the sum of +@x+, +a+ and +b+ for the method +[a,b]+
226
+ 3. has the method +plus1+, which returns its instance variable plus 1,
227
+ 4. has the method +dest!+
228
+
229
+ class MyC
230
+ def initialize(a); @x = a; end
231
+ def [](a, b); MyC.new(a+b); end
232
+ def plus1; @x+1; end
233
+ def to_s; @x.to_s; end
234
+ def dest!; end
235
+ end
236
+
237
+ # How MyC behaves.
238
+ myo = MyC.new(8)
239
+ myo.is_a?(MyC) # => true
240
+ myo.to_s # => "1"
241
+ myo.plus1 # => 2 # (= 8+1) (self is unmodified)
242
+
243
+ # How "Sub"-MyC behaves.
244
+ obj = SubObject.new(myo, 2, 3)
245
+ obj.respond_to?(:plus1) # => true
246
+ obj.to_s # => "6" # (= 1+(2+3))
247
+ obj.plus1 # => 7 # (= 6+1)
248
+ obj.dest! # => raise NoMethodError (destructive method)
249
+
250
+ (SubObject === obj) # => true
251
+ obj.instance_of?(SubObject) # => true
252
+ obj.is_a?( SubObject) # => false
253
+ (MyC === obj) # => false
254
+ obj.instance_of?(MyC) # => false
255
+ obj.is_a?( MyC) # => true
256
+
257
+ == Install
258
+
259
+ This script requires {Ruby}[http://www.ruby-lang.org] Version 2.0
260
+ or above.
261
+
262
+ You can install it from the usual Ruby gem command.
263
+ Or, alternatively, download it and put the library file in one of your Ruby library search paths.
264
+
265
+ == Developer's note
266
+
267
+ The master of this README file is found in
268
+ {RubyGems/sub_object}[https://rubygems.org/gems/sub_object]
269
+
270
+ === Tests
271
+
272
+ Ruby codes under the directory <tt>test/</tt> are the test scripts.
273
+ You can run them from the top directory as <tt>ruby test/test_****.rb</tt>
274
+ or simply run <tt>make test</tt>.
275
+
276
+
277
+ == Known bugs and Todo items
278
+
279
+ * This class ignores any optional (keyword) parameters for the methods. It is due to the fact Ruby {BasicObject#method_missing}[https://ruby-doc.org/core-2.6.5/BasicObject.html#method-i-method_missing] does not take them into account as of Ruby-2.6.5. It may change in future versions of Ruby.
280
+
281
+
282
+ == Copyright
283
+
284
+ Author:: Masa Sakano < info a_t wisebabel dot com >
285
+ Versions:: The versions of this package follow Semantic Versioning (2.0.0) http://semver.org/
286
+ License:: MIT
287
+
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
9
+
data/lib/sub_object.rb ADDED
@@ -0,0 +1,222 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Parent class for SubString and SubObject::SubArray or similar.
4
+ #
5
+ # == Summary
6
+ #
7
+ # This class SubObject is the parent class for
8
+ # {SubString}[http://rubygems.org/gems/sub_string] and alike.
9
+ # This class expresses Ruby sub-Object (like Sub-String),
10
+ # which are obtained with the +self[i, j]+ method,
11
+ # but taking up negligible memory space, as its instance internally holds
12
+ # the (likely positional, though arbitrary) information +(i, j)+ only. It provides the basic
13
+ # interface so the instance behaves exactly like the original
14
+ # class (String for SubString, for example) as duck-typing, except
15
+ # destructive modification, which is prohibited.
16
+ #
17
+ # If the original object is destructively modified in a way it changes
18
+ # its hash value, warning is issued whenever this instance is accessed.
19
+ #
20
+ # == Important note to the developers
21
+ #
22
+ # To inherit this class, make sure to include the lines equivalent to
23
+ # the following 2 lines, replacing the method name +:to_str+ (which is for
24
+ # SubString class) to suit your child class.
25
+ #
26
+ # TO_SOURCE_METHOD = :to_str
27
+ # alias_method TO_SOURCE_METHOD, :to_source
28
+ #
29
+ # The full reference is found in the top page
30
+ # {SubObject}[http://rubygems.org/gems/sub_object]
31
+ # and in {Github}[https://github.com/masasakano/sub_object]
32
+ #
33
+ # @author Masa Sakano (Wise Babel Ltd)
34
+ #
35
+ class SubObject
36
+ # The verbosity flag specific to this class, which has a priority
37
+ # over the global $VERBOSE if set non-nil. If this is true or false,
38
+ # the warning is always issued or suppressed, respectively, when
39
+ # an instance is accessed after the source object is destructively modified.
40
+ # NOTE this class instance variable is specific to this class and
41
+ # is NOT shared with or inherited to its children!
42
+ @verbosity = nil
43
+
44
+ # Symbol of the method that projects to (returns) the original-like instance;
45
+ # e.g., :to_str for String. The value should be overwritten in the child class of {SubObject}.
46
+ TO_SOURCE_METHOD = :itself
47
+
48
+ # Symbol of the method to get the original-like instance; e.g., :to_str for String.
49
+ # The value should be set in the child class, along with the crucial alias, e.g.,
50
+ #
51
+ # TO_SOURCE_METHOD = :to_str
52
+ # alias_method TO_SOURCE_METHOD, :to_source
53
+ #
54
+ # @return [Symbol] Method name like :to_str
55
+ def to_source_method
56
+ self.class::TO_SOURCE_METHOD
57
+ end
58
+ private :to_source_method
59
+
60
+ # Getter of the class instance variable @verbosity
61
+ def self.verbose
62
+ (defined? @verbosity) ? @verbosity : nil
63
+ end
64
+
65
+ # Setter of the class instance variable @@verbosity
66
+ def self.verbose=(obj)
67
+ @verbosity=obj
68
+ end
69
+
70
+ # Warning/Error messages.
71
+ ERR_MSGS = {
72
+ no_method_error: 'source does not accept #[] method',
73
+ argument_error: 'source does not accept #[i, j]-type method',
74
+ type_error: 'wrong type for (pos, size)=(%s, %s)', # Specify (pos,size) as the argument.
75
+ }
76
+ private_constant :ERR_MSGS # since Ruby 1.9.3
77
+
78
+ # List of the names (String) of destructive methods (other than those ending with "!").
79
+ DESTRUCTIVE_METHODS = %w( []= << clear concat force_encoding insert prepend replace )
80
+
81
+ # Starting (character) position
82
+ attr_reader :pos
83
+
84
+ # Size of SubObject. Identical with #size
85
+ # attr_reader :isize
86
+
87
+ # Returns a new instance of SubObject equivalent to source[ pos, size ]
88
+ #
89
+ # @param source [String]
90
+ # @param pos [Integer]
91
+ # @param size [Integer]
92
+ def initialize(source, pos, size)
93
+ @source, @pos, @isize = source, pos, size
94
+
95
+ # Sanity check
96
+ begin
97
+ _ = @source[@pos, @isize]
98
+ rescue NoMethodError
99
+ raise TypeError, ERR_MSGS[:no_method_error]
100
+ rescue ArgumentError
101
+ raise TypeError, ERR_MSGS[:argument_error]
102
+ rescue TypeError
103
+ raise TypeError, ERR_MSGS[:type_error]%[pos.inspect, size.inspect]
104
+ end
105
+
106
+ if !source.respond_to? self.class::TO_SOURCE_METHOD
107
+ raise TypeError, "Wrong source class #{source.class.name} for this class #{self.class.name}"
108
+ end
109
+
110
+ # Hash value retained to check its potential destructive change
111
+ @hash = @source.hash
112
+ end
113
+
114
+ # @return [Aray]
115
+ def pos_size
116
+ [@pos, @isize]
117
+ end
118
+
119
+ # @return usually the size (Integer)
120
+ def subsize
121
+ @isize
122
+ end
123
+
124
+ # Frozen String returned.
125
+ def source
126
+ warn_hash
127
+ src = @source.dup
128
+ src.freeze
129
+ src
130
+ end
131
+
132
+ # Returns the original representation of the instance as in the source
133
+ #
134
+ # Each child class should set the constant TO_SOURCE_METHOD
135
+ # appropriately and should alias this method to the method registered
136
+ # to TO_SOURCE_METHOD . For example, for SubString,
137
+ #
138
+ # TO_SOURCE_METHOD = :to_str
139
+ # alias_method TO_SOURCE_METHOD, :to_source
140
+ #
141
+ # Warning: DO NOT OVERWRITE THIS METHOD.
142
+ #
143
+ # @return [Object]
144
+ def to_source
145
+ warn_hash
146
+ @source[@pos, @isize].send(to_source_method)
147
+ end
148
+
149
+ # Method aliases to back up the Object default originals
150
+ %i(== === is_a? :kind_of? =~ <=> to_s).each do |ec|
151
+ begin
152
+ alias_method :to_s_before_sub_object, ec if !method_defined? :to_s_before_sub_object
153
+ rescue NameError
154
+ warn "The method {#ec.inspect} is disabled in Object(!!), hence for #{name}" if !$VERBOSE.nil?
155
+ end
156
+ end
157
+
158
+ # Redefining some methods in Object
159
+ #
160
+ # Statements repeated, because Module#define_method uses instance_eval
161
+ # at run-time, which is inefficient.
162
+
163
+ # Redefining a method in Object to evaluate via {#to_source}
164
+ def ==( *rest); to_source.send(__method__, *rest); end
165
+ # Redefining a method in Object to evaluate via {#to_source}
166
+ def ===( *rest); to_source.send(__method__, *rest); end
167
+ # Redefining a method in Object to evaluate via {#to_source}
168
+ def is_a?( *rest); to_source.send(__method__, *rest); end
169
+ # Redefining a method in Object to evaluate via {#to_source}
170
+ def kind_of?(*rest); to_source.send(__method__, *rest); end
171
+ # Redefining a method in Object to evaluate via {#to_source}
172
+ def =~( *rest); to_source.send(__method__, *rest); end
173
+ # Redefining a method in Object to evaluate via {#to_source}
174
+ def <=>( *rest); to_source.send(__method__, *rest); end
175
+ # Redefining a method in Object to evaluate via {#to_source}
176
+ def to_s( *rest); to_source.send(__method__, *rest); end
177
+
178
+ alias_method :inspect_before_sub_object, :inspect
179
+
180
+ # @return [String]
181
+ def inspect
182
+ warn_hash
183
+ sprintf('%s[%d,%d]%s', self.class.name.split(/::/)[-1], @pos, @isize, @source[@pos, @isize].inspect)
184
+ end
185
+
186
+ # method_missing for any but destructive methods
187
+ #
188
+ # @return [Object]
189
+ # @see #respond_to_missing?
190
+ def method_missing(method_name, *rest, &block)
191
+ destructive_method?(method_name) ? super : to_source.send(method_name, *rest, &block)
192
+ end
193
+
194
+ # Obligatory redefinition, following redefined {#method_missing}
195
+ def respond_to_missing?(method_name, *rest)
196
+ destructive_method?(method_name) ? super : to_source.send(:respond_to?, method_name, *rest)
197
+ end
198
+
199
+ ##################
200
+ private
201
+ ##################
202
+
203
+ def destructive_method?(method_name)
204
+ met = method_name.to_s
205
+ (/!$/ =~ met) || DESTRUCTIVE_METHODS.include?(met)
206
+ end
207
+ private :destructive_method?
208
+
209
+ def warn_hash
210
+ klass = self.class
211
+ verbosity_loc = (klass.instance_variable_defined?(:@verbosity) ? klass.instance_variable_get(:@verbosity) : nil)
212
+ if ((verbosity_loc.nil? && !$VERBOSE.nil?) || verbosity_loc) && @hash != @source.hash
213
+ str = @source[@pos, @isize].inspect
214
+ # Suppresses the length only for warning (not the standard inspect), which would be printed repeatedly.
215
+ str = str[0,60]+'..."' if str.size > 64
216
+ # warn() would not run if $VERBOSE.nil? (in this case it should run if @verbosity is true).
217
+ $stderr.printf("WARNING: source string has destructively changed: %s[%d,%d]%s\n", self.class.name, @pos, @isize, str)
218
+ end
219
+ end
220
+ private :warn_hash
221
+ end
222
+
@@ -0,0 +1,26 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'sub_object' # Gem: {SubObject}[http://rubygems.org/gems/sub_object]
4
+
5
+ class SubObject
6
+
7
+ # Child class of SubObject for Array: SubObject::SubArray
8
+ #
9
+ # See for detail the full reference in the top page
10
+ # {SubObject}[http://rubygems.org/gems/sub_object]
11
+ # and in {Github}[https://github.com/masasakano/sub_object]
12
+ #
13
+ # @author Masa Sakano (Wise Babel Ltd)
14
+ #
15
+ class SubArray < SubObject
16
+ # Symbol of the method that projects to (returns) the original-like instance;
17
+ # e.g., :to_str for String. The value should be overwritten in the child class of {SubObject}.
18
+ TO_SOURCE_METHOD = :to_ary
19
+ alias_method TO_SOURCE_METHOD, :to_source
20
+
21
+ ar = %w( append delete delete_at delete_if fill keep_if push pop shift unshift )
22
+ # @note Add to the list of destructive method: append delete delete_at delete_if fill keep_if push pop shift unshift
23
+ DESTRUCTIVE_METHODS.concat ar
24
+ end
25
+ end
26
+
@@ -0,0 +1,52 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'rake'
4
+ require 'date'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{sub_object}.sub(/.*/){|c| (c == File.basename(Dir.pwd)) ? c : raise("ERROR: s.name=(#{c}) in gemspec seems wrong!")}
8
+ s.version = "1.0"
9
+ # s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ # s.bindir = 'bin'
11
+ # %w(sub_object).each do |f|
12
+ # path = s.bindir+'/'+f
13
+ # File.executable?(path) ? s.executables << f : raise("ERROR: Executable (#{path}) is not executable!")
14
+ # end
15
+ s.authors = ["Masa Sakano"]
16
+ s.date = %q{2019-11-05}.sub(/.*/){|c| (Date.parse(c) == Date.today) ? c : raise("ERROR: s.date=(#{c}) is not today!")}
17
+ s.summary = %q{Parent class for memory-efficient sub-Something}
18
+ s.description = %q{Class SubObject that is the parent class for Ruby SubString and SubArray classes and alike, providing the base interface. This and child classes use negligible memory space, as their instance holds the positional information only. It behaves exactly like the source object (duck-typing), except destructive modification is prohibited. If the original source object is destructively altered, the corresponding instance can detect it and issue warning.}
19
+ # s.email = %q{abc@example.com}
20
+ s.extra_rdoc_files = [
21
+ #"LICENSE.txt",
22
+ "README.en.rdoc",
23
+ ]
24
+ s.license = 'MIT'
25
+ s.files = FileList['.gitignore','lib/**/*.rb','[A-Z]*','test/**/*.rb', '*.gemspec'].to_a.delete_if{ |f|
26
+ ret = false
27
+ arignore = IO.readlines('.gitignore')
28
+ arignore.map{|i| i.chomp}.each do |suffix|
29
+ if File.fnmatch(suffix, File.basename(f))
30
+ ret = true
31
+ break
32
+ end
33
+ end
34
+ ret
35
+ }
36
+ s.files.reject! { |fn| File.symlink? fn }
37
+
38
+ # s.add_runtime_dependency 'rails'
39
+ # s.add_development_dependency "bourne", [">= 0"]
40
+ s.homepage = %q{https://www.wisebabel.com}
41
+ s.rdoc_options = ["--charset=UTF-8"]
42
+
43
+ # s.require_paths = ["lib"] # Default "lib"
44
+ s.required_ruby_version = '>= 2.0' # respond_to_missing?
45
+ s.test_files = Dir['test/**/*.rb']
46
+ s.test_files.reject! { |fn| File.symlink? fn }
47
+ # s.requirements << 'libmagick, v6.0' # Simply, info to users.
48
+ # s.rubygems_version = %q{1.3.5} # This is always set automatically!!
49
+
50
+ s.metadata["yard.run"] = "yri" # use "yard" to build full HTML docs.
51
+ end
52
+
@@ -0,0 +1,211 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # @author Masa Sakano (Wise Babel Ltd)
4
+
5
+ #require 'open3'
6
+ require 'sub_object'
7
+ require 'sub_object/sub_array'
8
+
9
+ $stdout.sync=true
10
+ $stderr.sync=true
11
+ # print '$LOAD_PATH=';p $LOAD_PATH
12
+
13
+ #################################################
14
+ # Unit Test
15
+ #################################################
16
+
17
+ gem "minitest"
18
+ # require 'minitest/unit'
19
+ require 'minitest/autorun'
20
+
21
+ class TestUnitSubObject < MiniTest::Test
22
+ T = true
23
+ F = false
24
+ SCFNAME = File.basename(__FILE__)
25
+ EXE = "%s/../bin/%s" % [File.dirname(__FILE__), File.basename(__FILE__).sub(/^test_(.+)\.rb/, '\1')]
26
+
27
+ class MyA
28
+ end
29
+
30
+ class MyB
31
+ def [](a)
32
+ end
33
+ end
34
+
35
+ class MyC
36
+ attr_reader :x
37
+ def initialize(a)
38
+ @x = a
39
+ end
40
+
41
+ def [](a, b)
42
+ raise TypeError if a.respond_to? :to_hash
43
+ MyC.new(@x+a+b)
44
+ end
45
+
46
+ def plus1
47
+ @x+1
48
+ end
49
+ def to_s
50
+ @x.to_s
51
+ end
52
+
53
+ def dest!
54
+ end
55
+
56
+ # Without this you could not compare (once it is frozen)
57
+ def ==(other)
58
+ @x == other.x
59
+ end
60
+
61
+ # Test of hash values
62
+ def hash
63
+ @x
64
+ end
65
+ def modify_self!
66
+ @x = @x * 9
67
+ end
68
+ def reverse_self!
69
+ @x = @x / 9
70
+ end
71
+ end
72
+
73
+ def setup
74
+ end
75
+
76
+ def teardown
77
+ end
78
+
79
+ def test_sub_object01
80
+ err = assert_raises(TypeError){ SubObject.new MyA.new, 2, 3 }
81
+ assert_match( /\] method/, err.message )
82
+ err = assert_raises(TypeError){ SubObject.new MyB.new, 2, 3 }
83
+ assert_match( /type method/, err.message )
84
+ err = assert_raises(TypeError){ SubObject.new MyC.new(8), 2, Hash.new[3] }
85
+ assert_match( /type for/, err.message )
86
+
87
+ myo = MyC.new(1)
88
+ assert myo.is_a?(MyC)
89
+ assert_equal "1", myo.to_s
90
+ assert_equal 2, myo.plus1
91
+ assert_nil myo.dest!
92
+ assert_equal 1, myo.hash
93
+
94
+ obj = SubObject.new myo, 2, 3
95
+ assert_equal myo, obj.source
96
+ refute_equal myo.object_id, obj.source.object_id # b/c the latter is frozen
97
+ assert_equal myo.object_id, obj.instance_variable_get(:@source).object_id
98
+ assert_equal 2, obj.pos
99
+ assert_equal 3, obj.subsize
100
+ assert_equal [2, 3], obj.pos_size
101
+ assert_equal MyC.new(myo.x+2+3), obj.to_source
102
+ assert_equal "SubObject", obj.class.name
103
+ assert_equal "6", obj.to_s
104
+ assert myo.respond_to?(:to_s)
105
+ assert obj.respond_to?(:to_s)
106
+ assert myo.respond_to?(:plus1)
107
+ assert obj.respond_to?(:plus1)
108
+ refute myo.respond_to?(:naiyo)
109
+ refute obj.respond_to?(:naiyo)
110
+ assert_equal 7, obj.plus1
111
+ assert (SubObject === obj)
112
+ assert_instance_of(SubObject, obj)
113
+ refute obj.kind_of?(SubObject)
114
+ refute obj.is_a?( SubObject)
115
+ refute (MyC === obj)
116
+ refute_instance_of(MyC, obj)
117
+ assert obj.kind_of?(MyC)
118
+ assert obj.is_a?( MyC)
119
+ err = assert_raises(NoMethodError){ obj.to_str }
120
+ err = assert_raises(NoMethodError){ obj.dest! }
121
+
122
+ assert_equal 1, myo.hash
123
+ assert_equal 1, obj.instance_variable_get(:@hash)
124
+ myo.modify_self!
125
+ assert_equal 1, obj.instance_variable_get(:@hash)
126
+ assert_equal 9, myo.hash
127
+ assert_equal myo.object_id, obj.instance_variable_get(:@source).object_id
128
+ assert_equal myo.hash, obj.instance_variable_get(:@source).hash
129
+ assert_equal 9, obj.instance_variable_get(:@source).hash
130
+ assert_output('', /destructively/){ obj.source }
131
+ assert_output('', /destructively/){ obj.plus1 }
132
+ assert_output('', /destructively/){ obj.to_s }
133
+ ## The following is true and it is as expected,
134
+ # However, you do not care what it returns once the source
135
+ # has been destructively modified... Hence taken out of this test.
136
+ # assert_equal "14", obj.to_s
137
+
138
+ mutex = Mutex.new
139
+ exclu = Thread.new {
140
+ mutex.synchronize {
141
+ org_verbose = $VERBOSE
142
+ assert_output('', ''){ _ = SubObject.verbose }
143
+ begin
144
+ $VERBOSE = true
145
+ assert_nil SubObject.verbose
146
+ SubObject.verbose=nil;
147
+ assert_nil SubObject.verbose
148
+ assert_output('', /destructively/){ obj.source }
149
+ $VERBOSE = false
150
+ assert_output('', /destructively/){ obj.source }
151
+ $VERBOSE = nil
152
+ assert_output('', ''){ obj.source }
153
+
154
+ SubObject.verbose = true
155
+ assert_equal true, SubObject.verbose
156
+ assert_equal true, SubObject.instance_variable_get(:@verbosity)
157
+ assert_output('', /destructively/){ obj.source }
158
+ SubObject.verbose = false
159
+ assert_equal false, SubObject.verbose
160
+ assert_output('', ''){ obj.source }
161
+ $VERBOSE = true
162
+ assert_output('', ''){ obj.source }
163
+ SubObject.verbose=nil;
164
+ assert_output('', /destructively/){ obj.source }
165
+
166
+ # Original String recovered, hence its hash value.
167
+ myo.reverse_self!
168
+ assert_output('', ''){ obj.source }
169
+ ensure
170
+ $VERBOSE = org_verbose
171
+ SubObject.verbose=nil;
172
+ end
173
+ }
174
+ }
175
+ exclu.join
176
+ end
177
+
178
+ def test_sub_array01
179
+ assert_raises(TypeError){ SubObject::SubArray.new "bad", -3, 2 }
180
+
181
+ ary = [2,4,6,8,10]
182
+ obj = SubObject::SubArray.new ary, -3, 2
183
+ assert_equal( -3, obj.pos)
184
+ assert_equal 2, obj.subsize
185
+ assert_equal [-3, 2], obj.pos_size
186
+ assert_equal ary[-3, 2], obj.to_source
187
+ assert_equal :to_ary, SubObject::SubArray::TO_SOURCE_METHOD
188
+ assert_equal :itself, SubObject::TO_SOURCE_METHOD
189
+ assert_raises(TypeError){ SubObject::SubArray.new ary, -3, :a }
190
+ assert (SubObject === obj)
191
+ assert (SubObject::SubArray === obj)
192
+ assert obj.instance_of?(SubObject::SubArray)
193
+ refute obj.instance_of?(SubObject)
194
+ refute obj.kind_of?(SubObject)
195
+ refute obj.is_a?( SubObject)
196
+ refute obj.kind_of?(SubObject::SubArray)
197
+ refute obj.is_a?( SubObject::SubArray)
198
+ assert_equal [6,8], obj.to_ary
199
+ assert_equal obj, [6,8]
200
+ assert_equal [6,8], obj
201
+ assert_equal [6,8,9], obj+[9]
202
+ assert obj.respond_to?(:to_ary)
203
+ assert obj.respond_to?(:map)
204
+ refute obj.respond_to?(:map!)
205
+ refute obj.respond_to?(:naiyo)
206
+ assert_raises(NoMethodError){ obj.push 5 }
207
+ assert_raises(NoMethodError){ obj.keep_if{} }
208
+ end
209
+
210
+ end # class TestUnitSubObject < MiniTest::Test
211
+
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sub_object
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - Masa Sakano
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-11-05 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Class SubObject that is the parent class for Ruby SubString and SubArray
14
+ classes and alike, providing the base interface. This and child classes use negligible
15
+ memory space, as their instance holds the positional information only. It behaves
16
+ exactly like the source object (duck-typing), except destructive modification is
17
+ prohibited. If the original source object is destructively altered, the corresponding
18
+ instance can detect it and issue warning.
19
+ email:
20
+ executables: []
21
+ extensions: []
22
+ extra_rdoc_files:
23
+ - README.en.rdoc
24
+ files:
25
+ - ".gitignore"
26
+ - LICENSE.txt
27
+ - Makefile
28
+ - README.en.rdoc
29
+ - Rakefile
30
+ - lib/sub_object.rb
31
+ - lib/sub_object/sub_array.rb
32
+ - sub_object.gemspec
33
+ - test/test_sub_object.rb
34
+ homepage: https://www.wisebabel.com
35
+ licenses:
36
+ - MIT
37
+ metadata:
38
+ yard.run: yri
39
+ post_install_message:
40
+ rdoc_options:
41
+ - "--charset=UTF-8"
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '2.0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 3.0.3
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: Parent class for memory-efficient sub-Something
59
+ test_files:
60
+ - test/test_sub_object.rb