character_set 1.8.0 → 1.9.0

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +10 -0
  4. data/README.md +2 -1
  5. data/Rakefile +12 -5
  6. data/ext/character_set/character_set.c +78 -67
  7. data/ext/character_set/java/src/main/java/org/jaynetics/characterset/CharacterSetService.java +15 -0
  8. data/ext/character_set/java/src/main/java/org/jaynetics/characterset/RubyCharacterSet.java +493 -0
  9. data/ext/character_set/java/src/main/java/org/jaynetics/characterset/StringOperations.java +138 -0
  10. data/lib/character_set/java.rb +6 -0
  11. data/lib/character_set/parser.rb +5 -3
  12. data/lib/character_set/predefined_sets/assigned.cps +95 -58
  13. data/lib/character_set/predefined_sets/emoji.cps +9 -11
  14. data/lib/character_set/predefined_sets.rb +1 -1
  15. data/lib/character_set/ruby_fallback/character_set_methods.rb +7 -10
  16. data/lib/character_set/ruby_fallback/set_methods.rb +215 -52
  17. data/lib/character_set/ruby_fallback.rb +4 -18
  18. data/lib/character_set/shared_methods.rb +39 -2
  19. data/lib/character_set/version.rb +1 -1
  20. data/lib/character_set/writer.rb +1 -1
  21. data/lib/character_set.rb +6 -1
  22. metadata +8 -38
  23. data/.gitattributes +0 -3
  24. data/.github/workflows/gouteur.yml +0 -20
  25. data/.github/workflows/lint.yml +0 -29
  26. data/.github/workflows/tests.yml +0 -28
  27. data/.gitignore +0 -33
  28. data/.gouteur.yml +0 -2
  29. data/.rspec +0 -3
  30. data/BENCHMARK.md +0 -90
  31. data/Gemfile +0 -21
  32. data/bin/console +0 -21
  33. data/bin/setup +0 -8
  34. data/character_set.gemspec +0 -24
  35. data/ext/character_set/unicode_casefold_table.h +0 -1438
  36. data/ext/character_set/unicode_casefold_table.h.tmpl +0 -11
  37. data/lib/character_set/ruby_fallback/vendored_set_classes.rb +0 -385
  38. data/tasks/benchmark.rake +0 -20
  39. data/tasks/benchmarks/count_in.rb +0 -13
  40. data/tasks/benchmarks/cover.rb +0 -25
  41. data/tasks/benchmarks/delete_in.rb +0 -29
  42. data/tasks/benchmarks/keep_in.rb +0 -29
  43. data/tasks/benchmarks/scan.rb +0 -13
  44. data/tasks/benchmarks/shared.rb +0 -28
  45. data/tasks/benchmarks/used_by.rb +0 -25
  46. data/tasks/benchmarks/z_add.rb +0 -12
  47. data/tasks/benchmarks/z_delete.rb +0 -12
  48. data/tasks/benchmarks/z_merge.rb +0 -15
  49. data/tasks/benchmarks/z_minmax.rb +0 -12
  50. data/tasks/sync_casefold_data.rake +0 -20
  51. data/tasks/sync_predefined_sets.rake +0 -9
  52. data/tasks/sync_ruby_spec.rake +0 -65
@@ -1,11 +0,0 @@
1
- // THIS FILE IS GENERATED BY $ rake sync_casefold_data - DO NOT EDIT
2
- // -*-C-*-
3
-
4
- typedef struct casefold_mapping {
5
- unsigned long from;
6
- unsigned long to;
7
- } casefold_mapping;
8
-
9
- #define CASEFOLD_COUNT 0
10
-
11
- static const casefold_mapping unicode_casefold_table[CASEFOLD_COUNT] = {};
@@ -1,385 +0,0 @@
1
- # set, vendored from https://github.com/ruby/set/blob/master/lib/set.rb,
2
- # with comments removed and linted.
3
- class CharacterSet::RubyFallback::Set
4
- Set = self
5
- include Enumerable
6
-
7
- def self.[](*ary)
8
- new(ary)
9
- end
10
-
11
- def initialize(enum = nil, &block)
12
- @hash = Hash.new(false)
13
-
14
- enum.nil? and return
15
-
16
- if block
17
- do_with_enum(enum) { |o| add(block[o]) }
18
- else
19
- merge(enum)
20
- end
21
- end
22
-
23
- def do_with_enum(enum, &block)
24
- if enum.respond_to?(:each_entry)
25
- enum.each_entry(&block) if block
26
- elsif enum.respond_to?(:each)
27
- enum.each(&block) if block
28
- else
29
- raise ArgumentError, "value must be enumerable"
30
- end
31
- end
32
- private :do_with_enum
33
-
34
- def initialize_dup(orig)
35
- super
36
- @hash = orig.instance_variable_get(:@hash).dup
37
- end
38
-
39
- if Kernel.instance_method(:initialize_clone).arity != 1
40
- def initialize_clone(orig, **options)
41
- super
42
- @hash = orig.instance_variable_get(:@hash).clone(**options)
43
- end
44
- else
45
- def initialize_clone(orig)
46
- super
47
- @hash = orig.instance_variable_get(:@hash).clone
48
- end
49
- end
50
-
51
- def freeze
52
- @hash.freeze
53
- super
54
- end
55
-
56
- def size
57
- @hash.size
58
- end
59
- alias length size
60
-
61
- def empty?
62
- @hash.empty?
63
- end
64
-
65
- def clear
66
- @hash.clear
67
- self
68
- end
69
-
70
- def to_a
71
- @hash.keys
72
- end
73
-
74
- def include?(o)
75
- @hash[o]
76
- end
77
- alias member? include?
78
-
79
- def superset?(set)
80
- case
81
- when set.instance_of?(self.class) && @hash.respond_to?(:>=)
82
- @hash >= set.instance_variable_get(:@hash)
83
- when set.is_a?(Set)
84
- size >= set.size && set.all? { |o| include?(o) }
85
- else
86
- raise ArgumentError, "value must be a set"
87
- end
88
- end
89
- alias >= superset?
90
-
91
- def proper_superset?(set)
92
- case
93
- when set.instance_of?(self.class) && @hash.respond_to?(:>)
94
- @hash > set.instance_variable_get(:@hash)
95
- when set.is_a?(Set)
96
- size > set.size && set.all? { |o| include?(o) }
97
- else
98
- raise ArgumentError, "value must be a set"
99
- end
100
- end
101
- alias > proper_superset?
102
-
103
- def subset?(set)
104
- case
105
- when set.instance_of?(self.class) && @hash.respond_to?(:<=)
106
- @hash <= set.instance_variable_get(:@hash)
107
- when set.is_a?(Set)
108
- size <= set.size && all? { |o| set.include?(o) }
109
- else
110
- raise ArgumentError, "value must be a set"
111
- end
112
- end
113
- alias <= subset?
114
-
115
- def proper_subset?(set)
116
- case
117
- when set.instance_of?(self.class) && @hash.respond_to?(:<)
118
- @hash < set.instance_variable_get(:@hash)
119
- when set.is_a?(Set)
120
- size < set.size && all? { |o| set.include?(o) }
121
- else
122
- raise ArgumentError, "value must be a set"
123
- end
124
- end
125
- alias < proper_subset?
126
-
127
- def <=>(set)
128
- return unless set.is_a?(Set)
129
-
130
- case size <=> set.size
131
- when -1 then -1 if proper_subset?(set)
132
- when +1 then +1 if proper_superset?(set)
133
- else 0 if self.==(set)
134
- end
135
- end
136
-
137
- def intersect?(set)
138
- case set
139
- when Set
140
- if size < set.size
141
- any? { |o| set.include?(o) }
142
- else
143
- set.any? { |o| include?(o) }
144
- end
145
- when Enumerable
146
- set.any? { |o| include?(o) }
147
- else
148
- raise ArgumentError, "value must be enumerable"
149
- end
150
- end
151
-
152
- def disjoint?(set)
153
- !intersect?(set)
154
- end
155
-
156
- def each(&block)
157
- block_given? or return enum_for(__method__) { size }
158
- @hash.each_key(&block)
159
- self
160
- end
161
-
162
- def add(o)
163
- @hash[o] = true
164
- self
165
- end
166
- alias << add
167
-
168
- def add?(o)
169
- add(o) unless include?(o)
170
- end
171
-
172
- def delete(o)
173
- @hash.delete(o)
174
- self
175
- end
176
-
177
- def delete?(o)
178
- delete(o) if include?(o)
179
- end
180
-
181
- def delete_if
182
- block_given? or return enum_for(__method__) { size }
183
- select { |o| yield o }.each { |o| @hash.delete(o) }
184
- self
185
- end
186
-
187
- def keep_if
188
- block_given? or return enum_for(__method__) { size }
189
- reject { |o| yield o }.each { |o| @hash.delete(o) }
190
- self
191
- end
192
-
193
- def reject!(&block)
194
- block_given? or return enum_for(__method__) { size }
195
- n = size
196
- delete_if(&block)
197
- self if size != n
198
- end
199
-
200
- def select!(&block)
201
- block_given? or return enum_for(__method__) { size }
202
- n = size
203
- keep_if(&block)
204
- self if size != n
205
- end
206
-
207
- alias filter! select!
208
-
209
- def merge(*enums, **_rest)
210
- enums.each do |enum|
211
- if enum.instance_of?(self.class)
212
- @hash.update(enum.instance_variable_get(:@hash))
213
- else
214
- do_with_enum(enum) { |o| add(o) }
215
- end
216
- end
217
-
218
- self
219
- end
220
-
221
- def subtract(enum)
222
- do_with_enum(enum) { |o| delete(o) }
223
- self
224
- end
225
-
226
- def |(enum)
227
- dup.merge(enum)
228
- end
229
- alias + |
230
- alias union |
231
-
232
- def -(enum)
233
- dup.subtract(enum)
234
- end
235
- alias difference -
236
-
237
- def &(enum)
238
- n = self.class.new
239
- if enum.is_a?(Set)
240
- if enum.size > size
241
- each { |o| n.add(o) if enum.include?(o) }
242
- else
243
- enum.each { |o| n.add(o) if include?(o) }
244
- end
245
- else
246
- do_with_enum(enum) { |o| n.add(o) if include?(o) }
247
- end
248
- n
249
- end
250
- alias intersection &
251
-
252
- def ^(enum)
253
- n = Set.new(enum)
254
- each { |o| n.add(o) unless n.delete?(o) }
255
- n
256
- end
257
-
258
- def ==(other)
259
- if self.equal?(other)
260
- true
261
- elsif other.instance_of?(self.class)
262
- @hash == other.instance_variable_get(:@hash)
263
- elsif other.is_a?(Set) && self.size == other.size
264
- other.all? { |o| @hash.include?(o) }
265
- else
266
- false
267
- end
268
- end
269
-
270
- def hash
271
- @hash.hash
272
- end
273
-
274
- def eql?(o)
275
- return false unless o.is_a?(Set)
276
- @hash.eql?(o.instance_variable_get(:@hash))
277
- end
278
-
279
- alias === include?
280
-
281
- def classify
282
- block_given? or return enum_for(__method__) { size }
283
-
284
- h = {}
285
-
286
- each { |i|
287
- (h[yield(i)] ||= self.class.new).add(i)
288
- }
289
-
290
- h
291
- end
292
-
293
- def divide(&func)
294
- func or return enum_for(__method__) { size }
295
-
296
- if func.arity == 2
297
- require 'tsort'
298
-
299
- class << dig = {}
300
- include TSort
301
-
302
- alias tsort_each_node each_key
303
- def tsort_each_child(node, &block)
304
- fetch(node).each(&block)
305
- end
306
- end
307
-
308
- each { |u|
309
- dig[u] = a = []
310
- each{ |v| func.call(u, v) and a << v }
311
- }
312
-
313
- set = Set.new()
314
- dig.each_strongly_connected_component { |css|
315
- set.add(self.class.new(css))
316
- }
317
- set
318
- else
319
- Set.new(classify(&func).values)
320
- end
321
- end
322
- end
323
-
324
- # sorted_set without rbtree dependency, vendored from
325
- # https://github.com/ruby/set/blob/72f08c4/lib/set.rb#L731-L800
326
- class CharacterSet::RubyFallback::SortedSet < CharacterSet::RubyFallback::Set
327
- def initialize(*args)
328
- @keys = nil
329
- super
330
- end
331
-
332
- def clear
333
- @keys = nil
334
- super
335
- end
336
-
337
- def add(o)
338
- @keys = nil
339
- super
340
- end
341
- alias << add
342
-
343
- def delete(o)
344
- @keys = nil
345
- @hash.delete(o)
346
- self
347
- end
348
-
349
- def delete_if
350
- block_given? or return enum_for(__method__) { size }
351
- n = @hash.size
352
- super
353
- @keys = nil if @hash.size != n
354
- self
355
- end
356
-
357
- def keep_if
358
- block_given? or return enum_for(__method__) { size }
359
- n = @hash.size
360
- super
361
- @keys = nil if @hash.size != n
362
- self
363
- end
364
-
365
- def merge(enum)
366
- @keys = nil
367
- super
368
- end
369
-
370
- def each(&block)
371
- block or return enum_for(__method__) { size }
372
- to_a.each(&block)
373
- self
374
- end
375
-
376
- def to_a
377
- (@keys = @hash.keys).sort! unless @keys
378
- @keys.dup
379
- end
380
-
381
- def freeze
382
- to_a
383
- super
384
- end
385
- end
data/tasks/benchmark.rake DELETED
@@ -1,20 +0,0 @@
1
- desc 'Run all IPS benchmarks'
2
- task :benchmark do
3
- Dir["#{__dir__}/benchmarks/*.rb"].sort.each { |file| load(file) }
4
- end
5
-
6
- namespace :benchmark do
7
- desc 'Run all IPS benchmarks and store the comparison results in BENCHMARK.md'
8
- task :write_to_file do
9
- Rake.application[:benchmark].invoke
10
-
11
- # extract comparison results from reports
12
- results = $benchmark_results
13
- .map { |caption, report| "```\n#{caption}\n\n#{report[/(?<=Comparison:).+/m].strip}\n```" }
14
- .join("\n")
15
- .gsub(/ \(±[^)]+\) |(?<=same-ish).*/, '') # remove some noise
16
-
17
- File.write "#{__dir__}/../BENCHMARK.md",
18
- "Results of `rake:benchmark` on #{RUBY_DESCRIPTION}\n\n#{results}\n"
19
- end
20
- end
@@ -1,13 +0,0 @@
1
- require_relative './shared'
2
-
3
- str = 'Lorem ipsum et dolorem'
4
- tr = '^A-Za-z'
5
- cs = CharacterSet.non_ascii_letter
6
-
7
- benchmark(
8
- caption: 'Counting non-letters',
9
- cases: {
10
- 'String#count' => -> { str.count(tr) },
11
- 'CharacterSet#count_in' => -> { cs.count_in(str) },
12
- }
13
- )
@@ -1,25 +0,0 @@
1
- require_relative './shared'
2
-
3
- str = 'Lorem ipsum et dolorem'
4
- rx = /\S/
5
- cs = CharacterSet.whitespace.inversion
6
-
7
- benchmark(
8
- caption: 'Detecting non-whitespace',
9
- cases: {
10
- 'Regexp#match?' => -> { rx.match?(str) },
11
- 'CharacterSet#cover?' => -> { cs.cover?(str) },
12
- }
13
- )
14
-
15
- str = 'Lorem ipsum et dolorem'
16
- rx = /[^a-z]/i
17
- cs = CharacterSet.new('A'..'Z') + CharacterSet.new('a'..'z')
18
-
19
- benchmark(
20
- caption: 'Detecting non-letters',
21
- cases: {
22
- 'Regexp#match?' => -> { rx.match?(str) },
23
- 'CharacterSet#cover?' => -> { cs.cover?(str) },
24
- }
25
- )
@@ -1,29 +0,0 @@
1
- require_relative './shared'
2
-
3
- str = 'Lorem ipsum et dolorem'
4
- rx = /\s/
5
- trt = "\t\n\v\f\r\s"
6
- cs = CharacterSet.whitespace
7
-
8
- benchmark(
9
- caption: 'Removing ASCII whitespace',
10
- cases: {
11
- 'String#gsub' => -> { str.gsub(rx, '') },
12
- 'String#tr' => -> { str.tr(trt, '') },
13
- 'CharacterSet#delete_in' => -> { cs.delete_in(str) },
14
- }
15
- )
16
-
17
- str = 'Lörem ipsüm ⛷ et dölörem'
18
- rx = /[\s\p{emoji}äüö]/
19
- trt = "\t\n\v\f\r\s😀-🙏äüö"
20
- cs = CharacterSet.whitespace + CharacterSet.emoji + CharacterSet['ä', 'ö', 'ü']
21
-
22
- benchmark(
23
- caption: 'Removing whitespace, emoji and umlauts',
24
- cases: {
25
- 'String#gsub' => -> { str.gsub(rx, '') },
26
- 'String#tr' => -> { str.tr(trt, '') },
27
- 'CharacterSet#delete_in' => -> { cs.delete_in(str) },
28
- }
29
- )
@@ -1,29 +0,0 @@
1
- require_relative './shared'
2
-
3
- str = 'Lorem ipsum et dolorem'
4
- rx = /\S/
5
- trt = "\u{0080}-\u{10FFFF}" # approximation
6
- cs = CharacterSet.whitespace
7
-
8
- benchmark(
9
- caption: 'Removing non-whitespace',
10
- cases: {
11
- 'String#gsub' => -> { str.gsub(rx, '') },
12
- 'String#tr' => -> { str.tr(trt, '') },
13
- 'CharacterSet#keep_in' => -> { cs.keep_in(str) },
14
- }
15
- )
16
-
17
- str = 'Lorem ipsum ⛷ et dolorem'
18
- rx = /\p{^emoji}/
19
- trt = "\u0000-\u{1F599}\u{1F650}-\u{10FFFF}"
20
- cs = CharacterSet.emoji
21
-
22
- benchmark(
23
- caption: 'Keeping only emoji',
24
- cases: {
25
- 'String#gsub' => -> { str.gsub(rx, '') },
26
- 'String#tr' => -> { str.tr(trt, '') },
27
- 'CharacterSet#keep_in' => -> { cs.keep_in(str) },
28
- }
29
- )
@@ -1,13 +0,0 @@
1
- require_relative './shared'
2
-
3
- str = 'Lorem ipsum ⛷ et dolorem'
4
- rx = /\p{emoji}/
5
- cs = CharacterSet.emoji
6
-
7
- benchmark(
8
- caption: 'Extracting emoji to an Array',
9
- cases: {
10
- 'String#scan' => -> { str.scan(rx) },
11
- 'CharacterSet#scan' => -> { cs.scan(str) },
12
- }
13
- )
@@ -1,28 +0,0 @@
1
- require 'benchmark/ips'
2
- require_relative '../../lib/character_set'
3
- if RUBY_VERSION.to_f >= 3.0 && !RUBY_PLATFORM[/java/i]
4
- require 'sorted_set'
5
- else
6
- require 'set'
7
- end
8
-
9
- def benchmark(caption: nil, cases: {})
10
- with_stdouts($stdout, string_io = StringIO.new) do
11
- puts caption
12
- Benchmark.ips do |x|
13
- cases.each { |label, callable| x.report(label, &callable) }
14
- x.compare!
15
- end
16
- end
17
- ($benchmark_results ||= {})[caption] = string_io.string
18
- end
19
-
20
- def with_stdouts(*ios)
21
- old_stdout = $stdout
22
- ios.define_singleton_method(:method_missing) { |*args| each { |io| io.send(*args) } }
23
- ios.define_singleton_method(:respond_to?) { |*args| IO.respond_to?(*args) }
24
- $stdout = ios
25
- yield
26
- ensure
27
- $stdout = old_stdout
28
- end
@@ -1,25 +0,0 @@
1
- require_relative './shared'
2
-
3
- str = 'Lorem ipsum et dolorem'
4
- rx = /\s/
5
- cs = CharacterSet.whitespace
6
-
7
- benchmark(
8
- caption: 'Detecting whitespace',
9
- cases: {
10
- 'Regexp#match?' => -> { rx.match?(str) },
11
- 'CharacterSet#used_by?' => -> { cs.used_by?(str) },
12
- }
13
- )
14
-
15
- str = 'Lorem ipsum et dolorem' * 20 + '⛷' + 'Lorem ipsum et dolorem' * 20
16
- rx = /\p{emoji}/
17
- cs = CharacterSet.emoji
18
-
19
- benchmark(
20
- caption: 'Detecting emoji in a large string',
21
- cases: {
22
- 'Regexp#match?' => -> { rx.match?(str) },
23
- 'CharacterSet#used_by?' => -> { cs.used_by?(str) },
24
- }
25
- )
@@ -1,12 +0,0 @@
1
- require_relative './shared'
2
-
3
- cs = CharacterSet[]
4
- ss = SortedSet[]
5
-
6
- benchmark(
7
- caption: 'Adding entries',
8
- cases: {
9
- 'CharacterSet#add' => -> { cs.add(rand(0x10FFFF)) },
10
- 'SortedSet#add' => -> { ss.add(rand(0x10FFFF)) },
11
- }
12
- )
@@ -1,12 +0,0 @@
1
- require_relative './shared'
2
-
3
- cs = CharacterSet.new(0..0x10FFFF)
4
- ss = SortedSet.new(0..0x10FFFF)
5
-
6
- benchmark(
7
- caption: 'Removing entries',
8
- cases: {
9
- 'CharacterSet#delete' => -> { cs.delete(rand(0x10FFFF)) },
10
- 'SortedSet#delete' => -> { ss.delete(rand(0x10FFFF)) },
11
- }
12
- )
@@ -1,15 +0,0 @@
1
- require_relative './shared'
2
-
3
- cs1 = CharacterSet.new(0...0x88000)
4
- cs2 = CharacterSet.new(0x88000..0x10FFFF)
5
-
6
- ss1 = SortedSet.new(0...0x88000)
7
- ss2 = SortedSet.new(0x88000..0x10FFFF)
8
-
9
- benchmark(
10
- caption: 'Merging entries',
11
- cases: {
12
- 'CharacterSet#merge' => -> { cs1.merge(cs2) },
13
- 'SortedSet#merge' => -> { ss1.merge(ss2) },
14
- }
15
- )
@@ -1,12 +0,0 @@
1
- require_relative './shared'
2
-
3
- cs = CharacterSet.new(0..0xFFFF)
4
- ss = SortedSet.new(0..0xFFFF)
5
-
6
- benchmark(
7
- caption: 'Getting the min and max',
8
- cases: {
9
- 'CharacterSet#minmax' => -> { cs.minmax },
10
- 'SortedSet#minmax' => -> { ss.minmax },
11
- }
12
- )
@@ -1,20 +0,0 @@
1
- desc 'Download unicode casefold data and write new C header file'
2
- task :sync_casefold_data do
3
- src_path = './CaseFolding.txt'
4
- dst_path = "#{__dir__}/../ext/character_set/unicode_casefold_table.h"
5
-
6
- `wget http://www.unicode.org/Public/UNIDATA/CaseFolding.txt`
7
-
8
- mapping = File.foreach(src_path).each_with_object({}) do |line, hash|
9
- from, type, to = line.split(/\s*;\s*/).first(3)
10
- # type 'C' stands for 'common', excludes mappings to multiple chars
11
- hash[from] = to if type == 'C'
12
- end.sort
13
-
14
- content = File.read(dst_path + '.tmpl')
15
- .sub(/(CASEFOLD_COUNT )0/, "\\1#{mapping.count}")
16
- .sub('{}', ['{', mapping.map { |a, b| "{0x#{a},0x#{b}}," }, '}'].join("\n"))
17
-
18
- File.write(dst_path, content)
19
- File.unlink(src_path)
20
- end
@@ -1,9 +0,0 @@
1
- desc 'Update codepoint data for predefined sets, based on Onigmo'
2
- task :sync_predefined_sets do
3
- %w[assigned emoji whitespace].each do |prop|
4
- require 'regexp_property_values'
5
- ranges = RegexpPropertyValues[prop].matched_ranges
6
- str = ranges.map { |r| "#{r.min.to_s(16)},#{r.max.to_s(16)}\n" }.join.upcase
7
- File.write("#{__dir__}/../lib/character_set/predefined_sets/#{prop}.cps", str, mode: 'w')
8
- end
9
- end