character_set 1.7.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/CHANGELOG.md +19 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +4 -3
  6. data/Rakefile +12 -5
  7. data/ext/character_set/character_set.c +91 -67
  8. data/ext/character_set/java/src/main/java/org/jaynetics/characterset/CharacterSetService.java +15 -0
  9. data/ext/character_set/java/src/main/java/org/jaynetics/characterset/RubyCharacterSet.java +493 -0
  10. data/ext/character_set/java/src/main/java/org/jaynetics/characterset/StringOperations.java +138 -0
  11. data/lib/character_set/core_ext/regexp_ext.rb +8 -0
  12. data/lib/character_set/expression_converter.rb +37 -54
  13. data/lib/character_set/java.rb +6 -0
  14. data/lib/character_set/parser.rb +5 -3
  15. data/lib/character_set/predefined_sets/assigned.cps +95 -58
  16. data/lib/character_set/predefined_sets/emoji.cps +9 -11
  17. data/lib/character_set/predefined_sets.rb +1 -1
  18. data/lib/character_set/ruby_fallback/character_set_methods.rb +7 -14
  19. data/lib/character_set/ruby_fallback/set_methods.rb +215 -53
  20. data/lib/character_set/ruby_fallback.rb +4 -2
  21. data/lib/character_set/set_method_adapters.rb +1 -1
  22. data/lib/character_set/shared_methods.rb +43 -2
  23. data/lib/character_set/version.rb +1 -1
  24. data/lib/character_set/writer.rb +1 -1
  25. data/lib/character_set.rb +6 -1
  26. metadata +8 -38
  27. data/.gitattributes +0 -3
  28. data/.github/workflows/gouteur.yml +0 -20
  29. data/.github/workflows/lint.yml +0 -29
  30. data/.github/workflows/tests.yml +0 -26
  31. data/.gitignore +0 -33
  32. data/.gouteur.yml +0 -2
  33. data/.rspec +0 -3
  34. data/BENCHMARK.md +0 -90
  35. data/Gemfile +0 -20
  36. data/bin/console +0 -21
  37. data/bin/setup +0 -8
  38. data/character_set.gemspec +0 -24
  39. data/ext/character_set/unicode_casefold_table.h +0 -1438
  40. data/ext/character_set/unicode_casefold_table.h.tmpl +0 -11
  41. data/lib/character_set/ruby_fallback/vendored_set_classes.rb +0 -492
  42. data/tasks/benchmark.rake +0 -20
  43. data/tasks/benchmarks/count_in.rb +0 -13
  44. data/tasks/benchmarks/cover.rb +0 -25
  45. data/tasks/benchmarks/delete_in.rb +0 -29
  46. data/tasks/benchmarks/keep_in.rb +0 -29
  47. data/tasks/benchmarks/scan.rb +0 -13
  48. data/tasks/benchmarks/shared.rb +0 -28
  49. data/tasks/benchmarks/used_by.rb +0 -25
  50. data/tasks/benchmarks/z_add.rb +0 -12
  51. data/tasks/benchmarks/z_delete.rb +0 -12
  52. data/tasks/benchmarks/z_merge.rb +0 -15
  53. data/tasks/benchmarks/z_minmax.rb +0 -12
  54. data/tasks/sync_casefold_data.rake +0 -20
  55. data/tasks/sync_predefined_sets.rake +0 -9
  56. data/tasks/sync_ruby_spec.rake +0 -65
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 778cea0208adb290e09f454e3f88c021531decede99604cf2b67f48c9ab3bcd8
4
- data.tar.gz: 43672f0afce2846bec846791e7445c9614605194967e061b1d9f0be305298be4
3
+ metadata.gz: da6d7ab7c6a15344346659ce9681d1fc1b34de097964d78f424d8de524c5a268
4
+ data.tar.gz: 438b26ea6414b47536f7b10ad6b257d5b8f07c1d7609e222d2bb67f9de895443
5
5
  SHA512:
6
- metadata.gz: 635f9fb21c973b03b9a0556f2b6cf2c608753acb73616aa8681a5bada3418f955ec887164a03fd2add4edae8a60292eb6e4b681d11a8cf33d1083499afe83815
7
- data.tar.gz: 6a80f4f7f3f6c2357d84dc71bb5086f273471a8ae5af4e09abc502dc9893da90962d49527b40564de664af0a910f2e99720062dd41ba53427882ecb36e1a40a0
6
+ metadata.gz: '094d9fd765cba5ac6625af56fe76b6c0ef1c0c5987456a213dc82bef01dde5ceed4379149b681eed6d3f7156b9fa56c1b90bc3e24cacf570a24dc057797a8876'
7
+ data.tar.gz: 60cac5a36346957f73bee186003d1154fb7281823128d0b16104300025dbe07704724bd87cd166de8f0259e3a513bb9a525356b591078377fa21dd3cddffe369
data/.rubocop.yml CHANGED
@@ -8,10 +8,13 @@ AllCops:
8
8
  RubyInterpreters:
9
9
  - ruby
10
10
  - rake
11
- TargetRubyVersion: 2.5 # really 2.1, but 2.5 is lowest supported by rubocop
11
+ TargetRubyVersion: 3.0
12
12
 
13
13
  Lint/AmbiguousOperatorPrecedence:
14
14
  Enabled: false
15
15
 
16
16
  Lint/AmbiguousRegexpLiteral:
17
17
  Enabled: false
18
+
19
+ Metrics:
20
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -6,6 +6,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.9.0] - 2026-10-28
10
+
11
+ The minimum ruby version is now 3.0 (3.1 in jruby).
12
+
13
+ ### Added
14
+
15
+ - fast Java backend based on `BitSet` operations (#4)
16
+ - updated predefined "assigned" and "emoji" sets
17
+ - complete casefold mapping (e.g. `k` => `K`, `K`)
18
+
19
+ ## [1.8.0] - 2024-01-07
20
+
21
+ ### Added
22
+
23
+ - support for `#<=>` and `#join`, which were added to `set` in the meantime
24
+ - support for getting the (overall) character set of a Regexp with multiple expressions
25
+ - support for global and local case-insensitivity in Regexp inputs
26
+ - `Regexp#{covered_by_character_set?,uses_character_set?}` methods (if core ext is used)
27
+
9
28
  ## [1.7.0] - 2023-05-12
10
29
 
11
30
  ### Added
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018 Janosch Müller
3
+ Copyright (c) 2018-2023 Janosch Müller
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/character_set.svg)](http://badge.fury.io/rb/character_set)
4
4
  [![Build Status](https://github.com/jaynetics/character_set/workflows/tests/badge.svg)](https://github.com/jaynetics/character_set/actions)
5
5
  [![Build Status](https://github.com/jaynetics/character_set/workflows/gouteur/badge.svg)](https://github.com/jaynetics/character_set/actions)
6
- [![codecov](https://codecov.io/gh/jaynetics/character_set/branch/master/graph/badge.svg)](https://codecov.io/gh/jaynetics/character_set)
6
+ [![Coverage](https://codecov.io/gh/jaynetics/character_set/branch/main/graph/badge.svg?token=oY7gcWNbIN)](https://codecov.io/gh/jaynetics/character_set)
7
7
 
8
- This is a C-extended Ruby gem to work with sets of Unicode codepoints.
8
+ This is a Ruby gem with C and Java extensions that allows fast operations on sets of Unicode codepoints.
9
9
 
10
10
  It can [read](#parseinitialize) and [write](#write) sets of codepoints in various formats and it implements the stdlib `Set` interface for them.
11
11
 
@@ -43,7 +43,7 @@ CharacterSet.parse('[a-c]')
43
43
  CharacterSet.parse('\U00000061-\U00000063')
44
44
  ```
45
45
 
46
- If the gems [`regexp_parser`](https://github.com/ammar/regexp_parser) and [`regexp_property_values`](https://github.com/jaynetics/regexp_property_values) are installed, `Regexp` and unicode property names can also be read. Regexp intersections, negations, and set nesting are covered, but the `i`-flag is ignored; call `#case_insensitive` on the result if needed.
46
+ If the gems [`regexp_parser`](https://github.com/ammar/regexp_parser) and [`regexp_property_values`](https://github.com/jaynetics/regexp_property_values) are installed, `Regexp` instances and unicode property names can also be read.
47
47
 
48
48
  ```ruby
49
49
  CharacterSet.of(/./) # => #<CharacterSet (size: 1112064)>
@@ -144,6 +144,7 @@ CharacterSet['a'].inversion(include_surrogates: true)
144
144
 
145
145
  ```ruby
146
146
  CharacterSet['1', 'A'].case_insensitive # => CharacterSet['1', 'A', 'a']
147
+ CharacterSet['s'].case_insensitive # => CharacterSet["S", "s", "ſ"]
147
148
  ```
148
149
 
149
150
  ### Write
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: false
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rspec/core/rake_task'
3
5
  require 'rubygems/package_task'
@@ -11,7 +13,7 @@ task default: :spec
11
13
 
12
14
  namespace :spec do
13
15
  task :quick do
14
- ENV['SKIP_MEMSAFETY_SPECS'] = '1'
16
+ ENV['QUICK_SPEC_RUN'] = '1'
15
17
  Rake::Task[:spec].invoke
16
18
  end
17
19
  end
@@ -21,11 +23,11 @@ Rake::ExtensionTask.new('character_set') do |ext|
21
23
  end
22
24
 
23
25
  namespace :java do
24
- java_gemspec = eval File.read('./character_set.gemspec')
26
+ java_gemspec = Gem::Specification.load('./character_set.gemspec')
25
27
  java_gemspec.platform = 'java'
26
28
  java_gemspec.extensions = []
27
-
28
- java_gemspec.add_runtime_dependency 'range_compressor', '~> 1.0'
29
+ java_gemspec.required_ruby_version = '>= 3.1'
30
+ java_gemspec.files |= ['lib/character_set/java.rb', 'lib/character_set/character_set.jar']
29
31
 
30
32
  Gem::PackageTask.new(java_gemspec) do |pkg|
31
33
  pkg.need_zip = true
@@ -34,9 +36,14 @@ namespace :java do
34
36
  end
35
37
  end
36
38
 
39
+ Rake::Task['java:gem'].enhance(['java:compile'])
40
+
37
41
  task package: 'java:gem'
38
42
 
39
- unless RUBY_PLATFORM =~ /java/
43
+ if RUBY_ENGINE == 'jruby'
44
+ task(:benchmark).enhance(['java:compile'])
45
+ task(:spec).enhance(['java:compile', 'java:verify_backend'])
46
+ else
40
47
  # recompile before benchmarking or running specs
41
48
  task(:benchmark).enhance([:compile])
42
49
  task(:spec).enhance([:compile])
@@ -1,6 +1,5 @@
1
1
  #include "ruby.h"
2
2
  #include "ruby/encoding.h"
3
- #include "unicode_casefold_table.h"
4
3
 
5
4
  #define UNICODE_PLANE_SIZE 0x10000
6
5
  #define UNICODE_PLANE_COUNT 17
@@ -305,11 +304,7 @@ cs_method_max(VALUE self)
305
304
  static VALUE
306
305
  cs_method_minmax(VALUE self)
307
306
  {
308
- VALUE arr;
309
- arr = rb_ary_new2(2);
310
- rb_ary_push(arr, cs_method_min(self));
311
- rb_ary_push(arr, cs_method_max(self));
312
- return arr;
307
+ return rb_ary_new_from_args(2, cs_method_min(self), cs_method_max(self));
313
308
  }
314
309
 
315
310
  #define RETURN_COMBINED_CS(cs_a, cs_b, comp_op) \
@@ -462,18 +457,33 @@ cs_cps_eql(VALUE cs_a, VALUE cs_b)
462
457
  return Qtrue;
463
458
  }
464
459
 
460
+ static inline VALUE
461
+ cs_is_pure_cs(VALUE obj)
462
+ {
463
+ VALUE klass;
464
+ ID pure_id;
465
+ klass = rb_const_get(rb_cObject, rb_intern("CharacterSet"));
466
+ pure_id = rb_intern("Pure");
467
+ return rb_const_defined_at(klass, pure_id) &&
468
+ RTEST(rb_obj_is_kind_of(obj, rb_const_get_at(klass, pure_id)));
469
+ }
470
+
465
471
  static VALUE
466
472
  cs_method_eql_p(VALUE self, VALUE other)
467
473
  {
468
- if (!cs_check_type(other))
474
+ if (cs_check_type(other))
469
475
  {
470
- return Qfalse;
476
+ if (self == other) // same object_id
477
+ {
478
+ return Qtrue;
479
+ }
480
+ return cs_cps_eql(self, other);
471
481
  }
472
- if (self == other) // same object_id
482
+ if (cs_is_pure_cs(other))
473
483
  {
474
- return Qtrue;
484
+ return rb_funcall(other, rb_intern("=="), 1, self);
475
485
  }
476
- return cs_cps_eql(self, other);
486
+ return Qfalse;
477
487
  }
478
488
 
479
489
  static inline VALUE
@@ -568,7 +578,9 @@ cs_merge_rb_array(VALUE self, VALUE rb_array)
568
578
  static VALUE
569
579
  cs_method_merge(VALUE self, VALUE other)
570
580
  {
581
+
571
582
  rb_check_frozen(self);
583
+
572
584
  if (cs_check_type(other))
573
585
  {
574
586
  return cs_merge_cs(self, other);
@@ -577,12 +589,22 @@ cs_method_merge(VALUE self, VALUE other)
577
589
  {
578
590
  return cs_merge_rb_array(self, other);
579
591
  }
580
- return cs_merge_rb_range(self, other);
592
+ if (rb_obj_is_kind_of(other, rb_cRange))
593
+ {
594
+ return cs_merge_rb_range(self, other);
595
+ }
596
+ if (cs_is_pure_cs(other))
597
+ {
598
+ return cs_merge_rb_array(self, rb_funcall(other, rb_intern("to_a"), 0, 0));
599
+ }
600
+
601
+ rb_raise(rb_eArgError, "pass a CharacterSet, Array, or Range");
581
602
  }
582
603
 
583
604
  static VALUE
584
605
  cs_method_initialize_copy(VALUE self, VALUE orig)
585
606
  {
607
+ rb_obj_init_copy(self, orig);
586
608
  cs_merge_cs(self, orig);
587
609
  return self;
588
610
  }
@@ -675,6 +697,18 @@ cs_method_proper_superset_p(VALUE self, VALUE other)
675
697
  return (is_superset && is_proper) ? Qtrue : Qfalse;
676
698
  }
677
699
 
700
+ static VALUE
701
+ cs_method_spaceship_operator(VALUE self, VALUE other)
702
+ {
703
+ if (cs_method_eql_p(self, other))
704
+ return INT2FIX(0);
705
+ if (cs_method_proper_subset_p(self, other))
706
+ return INT2FIX(-1);
707
+ if (cs_method_proper_superset_p(self, other))
708
+ return INT2FIX(1);
709
+ return Qnil;
710
+ }
711
+
678
712
  // *******************************
679
713
  // `CharacterSet`-specific methods
680
714
  // *******************************
@@ -729,7 +763,7 @@ cs_method_sample(int argc, VALUE *argv, VALUE self)
729
763
  VALUE array, to_a_args[1] = {Qtrue};
730
764
  rb_check_arity(argc, 0, 1);
731
765
  array = cs_method_to_a(1, to_a_args, self);
732
- return rb_funcall(array, rb_intern("sample"), argc, argc ? argv[0] : 0);
766
+ return rb_funcallv(array, rb_intern("sample"), argc, argv);
733
767
  }
734
768
 
735
769
  static inline VALUE
@@ -821,7 +855,6 @@ cs_method_ext_section_ratio(VALUE self, VALUE from, VALUE upto)
821
855
 
822
856
  #define MAX_CP 0x10FFFF
823
857
  #define MAX_ASCII_CP 0x7F
824
- #define MAX_BMP_CP 0xFFFF
825
858
  #define MIN_ASTRAL_CP 0x10000
826
859
 
827
860
  static inline VALUE
@@ -829,7 +862,7 @@ cs_has_cp_in_plane(cs_ar *cps, cs_cp len, unsigned int plane)
829
862
  {
830
863
  cs_cp plane_beg, plane_end;
831
864
  plane_beg = plane * UNICODE_PLANE_SIZE;
832
- plane_end = (plane + 1) * MAX_BMP_CP;
865
+ plane_end = (plane + 1) * UNICODE_PLANE_SIZE - 1;
833
866
  return cs_has_cp_in_section(cps, len, plane_beg, plane_end);
834
867
  }
835
868
 
@@ -871,7 +904,7 @@ cs_method_plane(VALUE self, VALUE plane_num)
871
904
  cs_cp plane, plane_beg, plane_end;
872
905
  plane = cs_valid_plane_num(plane_num);
873
906
  plane_beg = plane * UNICODE_PLANE_SIZE;
874
- plane_end = (plane + 1) * MAX_BMP_CP;
907
+ plane_end = (plane + 1) * UNICODE_PLANE_SIZE - 1;
875
908
  return cs_from_section(self, plane_beg, plane_end);
876
909
  }
877
910
 
@@ -924,44 +957,41 @@ add_str_cp_to_arr(unsigned int str_cp, cs_ar *cp_arr, cs_cp len, struct cs_data
924
957
  return 1;
925
958
  }
926
959
 
960
+ struct cs_case_fold_data
961
+ {
962
+ cs_ar *cps;
963
+ cs_cp len;
964
+ struct cs_data *data;
965
+ };
966
+
967
+ static int
968
+ cs_apply_case_fold(OnigCodePoint from, OnigCodePoint *to, int to_len, void *arg)
969
+ {
970
+ struct cs_case_fold_data *fold = arg;
971
+
972
+ if (to_len == 1 && tst_cp(fold->cps, fold->len, from))
973
+ {
974
+ set_cp(fold->data, to[0]);
975
+ }
976
+
977
+ return 0;
978
+ }
979
+
927
980
  static VALUE
928
981
  cs_method_case_insensitive(VALUE self)
929
982
  {
930
- cs_cp i, len;
931
- cs_ar *cps;
932
983
  VALUE new_cs;
933
- struct cs_data *new_data;
984
+ struct cs_case_fold_data fold;
934
985
 
935
- cps = cs_fetch_cps(self, &len);
936
- new_cs = cs_alloc(RBASIC(self)->klass, &new_data);
986
+ fold.cps = cs_fetch_cps(self, &fold.len);
987
+ new_cs = cs_alloc(RBASIC(self)->klass, &fold.data);
937
988
  cs_merge_cs(new_cs, self);
938
989
 
939
- for (i = 0; i < CASEFOLD_COUNT; i++)
940
- {
941
- casefold_mapping m = unicode_casefold_table[i];
942
-
943
- if (tst_cp(cps, len, m.from))
944
- {
945
- set_cp(new_data, m.to);
946
- }
947
- else if (tst_cp(cps, len, m.to))
948
- {
949
- set_cp(new_data, m.from);
950
- }
951
- }
990
+ // Flags 0 exclude multi-character folds; the API supplies both directions.
991
+ ONIGENC_APPLY_ALL_CASE_FOLD(rb_utf8_encoding(), 0, cs_apply_case_fold, &fold);
992
+ RB_GC_GUARD(self);
952
993
 
953
994
  return new_cs;
954
-
955
- // OnigCaseFoldType flags;
956
- // rb_encoding *enc;
957
- //
958
- // enc = rb_utf8_encoding();
959
- //
960
- // ONIGENC_CASE_UPCASE | ONIGENC_CASE_DOWNCASE (not public on ruby < 2.4)
961
- // flags = (1<<13) | (1<<14);
962
- //
963
- // // case_map args: flags, pp, end, to, to_end, enc
964
- // enc->case_map(flags, (const OnigUChar**)&cp, ?, ?, ?, enc);
965
995
  }
966
996
 
967
997
  static inline VALUE
@@ -1017,23 +1047,13 @@ each_mb_cp(VALUE str, str_cp_handler func, cs_ar *cp_arr, cs_cp len, struct cs_d
1017
1047
  return Qtrue;
1018
1048
  }
1019
1049
 
1020
- // single_byte_optimizable - copied from string.c
1050
+ // Keep byte iteration for ASCII text and binary input.
1021
1051
  static inline int
1022
1052
  single_byte_optimizable(VALUE str)
1023
1053
  {
1024
- rb_encoding *enc;
1025
- if (ENC_CODERANGE(str) == ENC_CODERANGE_7BIT)
1026
- {
1027
- return 1;
1028
- }
1029
-
1030
- enc = rb_enc_get(str);
1031
- if (rb_enc_mbmaxlen(enc) == 1)
1032
- {
1033
- return 1;
1034
- }
1035
-
1036
- return 0;
1054
+ rb_encoding *enc = rb_enc_get(str);
1055
+ return enc == rb_ascii8bit_encoding() ||
1056
+ (rb_enc_asciicompat(enc) && ENC_CODERANGE(str) == ENC_CODERANGE_7BIT);
1037
1057
  }
1038
1058
 
1039
1059
  static inline VALUE
@@ -1150,7 +1170,8 @@ cs_apply_to_str(VALUE set, VALUE str, int delete, int bang)
1150
1170
  {
1151
1171
  cs_ar *cps;
1152
1172
  cs_cp cs_len;
1153
- VALUE orig_str_len;
1173
+ VALUE original = str;
1174
+ long working_str_len;
1154
1175
 
1155
1176
  rb_encoding *orig_enc, *utf8;
1156
1177
  char *s, *send, *t;
@@ -1158,9 +1179,12 @@ cs_apply_to_str(VALUE set, VALUE str, int delete, int bang)
1158
1179
 
1159
1180
  raise_arg_err_unless_string(str);
1160
1181
 
1161
- orig_str_len = RSTRING_LEN(str);
1182
+ if (bang)
1183
+ rb_check_frozen(original);
1184
+
1185
+ working_str_len = RSTRING_LEN(str);
1162
1186
 
1163
- if (orig_str_len == 0)
1187
+ if (working_str_len == 0)
1164
1188
  {
1165
1189
  return bang ? Qnil : str;
1166
1190
  }
@@ -1221,17 +1245,17 @@ cs_apply_to_str(VALUE set, VALUE str, int delete, int bang)
1221
1245
  rb_str_set_len(str, t - RSTRING_PTR(str));
1222
1246
  ENC_CODERANGE_SET(str, cr);
1223
1247
 
1224
- if (bang && (RSTRING_LEN(str) == (long)orig_str_len)) // string unchanged
1248
+ if (bang && (RSTRING_LEN(str) == working_str_len)) // string unchanged
1225
1249
  {
1226
1250
  return Qnil;
1227
1251
  }
1228
1252
 
1229
1253
  if (!orig_was_utf8)
1230
1254
  {
1231
- return rb_str_encode(str, rb_enc_from_encoding(orig_enc), 0, Qnil);
1255
+ str = rb_str_encode(str, rb_enc_from_encoding(orig_enc), 0, Qnil);
1232
1256
  }
1233
1257
 
1234
- return str;
1258
+ return bang ? rb_str_replace(original, str) : str;
1235
1259
  }
1236
1260
 
1237
1261
  static VALUE
@@ -1313,8 +1337,7 @@ void Init_character_set()
1313
1337
  rb_define_method(cs, "eql?", cs_method_eql_p, 1);
1314
1338
  rb_define_method(cs, "==", cs_method_eql_p, 1);
1315
1339
  rb_define_method(cs, "merge", cs_method_merge, 1);
1316
- rb_define_method(cs, "initialize_clone", cs_method_initialize_copy, 1);
1317
- rb_define_method(cs, "initialize_dup", cs_method_initialize_copy, 1);
1340
+ rb_define_method(cs, "initialize_copy", cs_method_initialize_copy, 1);
1318
1341
  rb_define_method(cs, "subtract", cs_method_subtract, 1);
1319
1342
  rb_define_method(cs, "subset?", cs_method_subset_p, 1);
1320
1343
  rb_define_method(cs, "<=", cs_method_subset_p, 1);
@@ -1324,6 +1347,7 @@ void Init_character_set()
1324
1347
  rb_define_method(cs, ">=", cs_method_superset_p, 1);
1325
1348
  rb_define_method(cs, "proper_superset?", cs_method_proper_superset_p, 1);
1326
1349
  rb_define_method(cs, ">", cs_method_proper_superset_p, 1);
1350
+ rb_define_method(cs, "<=>", cs_method_spaceship_operator, 1);
1327
1351
 
1328
1352
  // `CharacterSet`-specific methods
1329
1353
 
@@ -0,0 +1,15 @@
1
+ package org.jaynetics.characterset;
2
+
3
+ import org.jruby.Ruby;
4
+ import org.jruby.RubyClass;
5
+ import org.jruby.runtime.load.BasicLibraryService;
6
+
7
+ public final class CharacterSetService implements BasicLibraryService {
8
+ @Override
9
+ public boolean basicLoad(Ruby runtime) {
10
+ RubyClass klass = runtime.getClass("CharacterSet");
11
+ klass.setAllocator(RubyCharacterSet::new);
12
+ klass.defineAnnotatedMethods(RubyCharacterSet.class);
13
+ return true;
14
+ }
15
+ }