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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ebb6792f685df02534f1ef04a92d7f0c5fdcb482e5aaa4856d7a39726e17f007
4
- data.tar.gz: c6630aab9b6506c46a970ba83c257cd753f8f76760b6ce8d2639f51efba83eeb
3
+ metadata.gz: da6d7ab7c6a15344346659ce9681d1fc1b34de097964d78f424d8de524c5a268
4
+ data.tar.gz: 438b26ea6414b47536f7b10ad6b257d5b8f07c1d7609e222d2bb67f9de895443
5
5
  SHA512:
6
- metadata.gz: 4c773a0546d05939d0b295e50355c6efe870a1ed74901d63c24097ff598d4a43bcd00ce2d03fb492a48fd9c03968a79ee78b789d92836843d6621dca3e8f313c
7
- data.tar.gz: 560d3c3aa3f7e4daac3b6d2c89fb9dd6840777fa4d5896fb33564023ef745d81a7e4d0e51fe0ba42f6cd4504bc0b088657cd4ef1ab15d213aa1bb096ba404542
6
+ metadata.gz: '094d9fd765cba5ac6625af56fe76b6c0ef1c0c5987456a213dc82bef01dde5ceed4379149b681eed6d3f7156b9fa56c1b90bc3e24cacf570a24dc057797a8876'
7
+ data.tar.gz: 60cac5a36346957f73bee186003d1154fb7281823128d0b16104300025dbe07704724bd87cd166de8f0259e3a513bb9a525356b591078377fa21dd3cddffe369
data/.rubocop.yml CHANGED
@@ -8,7 +8,7 @@ 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
data/CHANGELOG.md CHANGED
@@ -6,6 +6,16 @@ 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
+
9
19
  ## [1.8.0] - 2024-01-07
10
20
 
11
21
  ### Added
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Build Status](https://github.com/jaynetics/character_set/workflows/gouteur/badge.svg)](https://github.com/jaynetics/character_set/actions)
6
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
 
@@ -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
  }
@@ -741,7 +763,7 @@ cs_method_sample(int argc, VALUE *argv, VALUE self)
741
763
  VALUE array, to_a_args[1] = {Qtrue};
742
764
  rb_check_arity(argc, 0, 1);
743
765
  array = cs_method_to_a(1, to_a_args, self);
744
- return rb_funcall(array, rb_intern("sample"), argc, argc ? argv[0] : 0);
766
+ return rb_funcallv(array, rb_intern("sample"), argc, argv);
745
767
  }
746
768
 
747
769
  static inline VALUE
@@ -833,7 +855,6 @@ cs_method_ext_section_ratio(VALUE self, VALUE from, VALUE upto)
833
855
 
834
856
  #define MAX_CP 0x10FFFF
835
857
  #define MAX_ASCII_CP 0x7F
836
- #define MAX_BMP_CP 0xFFFF
837
858
  #define MIN_ASTRAL_CP 0x10000
838
859
 
839
860
  static inline VALUE
@@ -841,7 +862,7 @@ cs_has_cp_in_plane(cs_ar *cps, cs_cp len, unsigned int plane)
841
862
  {
842
863
  cs_cp plane_beg, plane_end;
843
864
  plane_beg = plane * UNICODE_PLANE_SIZE;
844
- plane_end = (plane + 1) * MAX_BMP_CP;
865
+ plane_end = (plane + 1) * UNICODE_PLANE_SIZE - 1;
845
866
  return cs_has_cp_in_section(cps, len, plane_beg, plane_end);
846
867
  }
847
868
 
@@ -883,7 +904,7 @@ cs_method_plane(VALUE self, VALUE plane_num)
883
904
  cs_cp plane, plane_beg, plane_end;
884
905
  plane = cs_valid_plane_num(plane_num);
885
906
  plane_beg = plane * UNICODE_PLANE_SIZE;
886
- plane_end = (plane + 1) * MAX_BMP_CP;
907
+ plane_end = (plane + 1) * UNICODE_PLANE_SIZE - 1;
887
908
  return cs_from_section(self, plane_beg, plane_end);
888
909
  }
889
910
 
@@ -936,44 +957,41 @@ add_str_cp_to_arr(unsigned int str_cp, cs_ar *cp_arr, cs_cp len, struct cs_data
936
957
  return 1;
937
958
  }
938
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
+
939
980
  static VALUE
940
981
  cs_method_case_insensitive(VALUE self)
941
982
  {
942
- cs_cp i, len;
943
- cs_ar *cps;
944
983
  VALUE new_cs;
945
- struct cs_data *new_data;
984
+ struct cs_case_fold_data fold;
946
985
 
947
- cps = cs_fetch_cps(self, &len);
948
- 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);
949
988
  cs_merge_cs(new_cs, self);
950
989
 
951
- for (i = 0; i < CASEFOLD_COUNT; i++)
952
- {
953
- casefold_mapping m = unicode_casefold_table[i];
954
-
955
- if (tst_cp(cps, len, m.from))
956
- {
957
- set_cp(new_data, m.to);
958
- }
959
- else if (tst_cp(cps, len, m.to))
960
- {
961
- set_cp(new_data, m.from);
962
- }
963
- }
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);
964
993
 
965
994
  return new_cs;
966
-
967
- // OnigCaseFoldType flags;
968
- // rb_encoding *enc;
969
- //
970
- // enc = rb_utf8_encoding();
971
- //
972
- // ONIGENC_CASE_UPCASE | ONIGENC_CASE_DOWNCASE (not public on ruby < 2.4)
973
- // flags = (1<<13) | (1<<14);
974
- //
975
- // // case_map args: flags, pp, end, to, to_end, enc
976
- // enc->case_map(flags, (const OnigUChar**)&cp, ?, ?, ?, enc);
977
995
  }
978
996
 
979
997
  static inline VALUE
@@ -1029,23 +1047,13 @@ each_mb_cp(VALUE str, str_cp_handler func, cs_ar *cp_arr, cs_cp len, struct cs_d
1029
1047
  return Qtrue;
1030
1048
  }
1031
1049
 
1032
- // single_byte_optimizable - copied from string.c
1050
+ // Keep byte iteration for ASCII text and binary input.
1033
1051
  static inline int
1034
1052
  single_byte_optimizable(VALUE str)
1035
1053
  {
1036
- rb_encoding *enc;
1037
- if (ENC_CODERANGE(str) == ENC_CODERANGE_7BIT)
1038
- {
1039
- return 1;
1040
- }
1041
-
1042
- enc = rb_enc_get(str);
1043
- if (rb_enc_mbmaxlen(enc) == 1)
1044
- {
1045
- return 1;
1046
- }
1047
-
1048
- 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);
1049
1057
  }
1050
1058
 
1051
1059
  static inline VALUE
@@ -1162,7 +1170,8 @@ cs_apply_to_str(VALUE set, VALUE str, int delete, int bang)
1162
1170
  {
1163
1171
  cs_ar *cps;
1164
1172
  cs_cp cs_len;
1165
- VALUE orig_str_len;
1173
+ VALUE original = str;
1174
+ long working_str_len;
1166
1175
 
1167
1176
  rb_encoding *orig_enc, *utf8;
1168
1177
  char *s, *send, *t;
@@ -1170,9 +1179,12 @@ cs_apply_to_str(VALUE set, VALUE str, int delete, int bang)
1170
1179
 
1171
1180
  raise_arg_err_unless_string(str);
1172
1181
 
1173
- orig_str_len = RSTRING_LEN(str);
1182
+ if (bang)
1183
+ rb_check_frozen(original);
1184
+
1185
+ working_str_len = RSTRING_LEN(str);
1174
1186
 
1175
- if (orig_str_len == 0)
1187
+ if (working_str_len == 0)
1176
1188
  {
1177
1189
  return bang ? Qnil : str;
1178
1190
  }
@@ -1233,17 +1245,17 @@ cs_apply_to_str(VALUE set, VALUE str, int delete, int bang)
1233
1245
  rb_str_set_len(str, t - RSTRING_PTR(str));
1234
1246
  ENC_CODERANGE_SET(str, cr);
1235
1247
 
1236
- if (bang && (RSTRING_LEN(str) == (long)orig_str_len)) // string unchanged
1248
+ if (bang && (RSTRING_LEN(str) == working_str_len)) // string unchanged
1237
1249
  {
1238
1250
  return Qnil;
1239
1251
  }
1240
1252
 
1241
1253
  if (!orig_was_utf8)
1242
1254
  {
1243
- 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);
1244
1256
  }
1245
1257
 
1246
- return str;
1258
+ return bang ? rb_str_replace(original, str) : str;
1247
1259
  }
1248
1260
 
1249
1261
  static VALUE
@@ -1325,8 +1337,7 @@ void Init_character_set()
1325
1337
  rb_define_method(cs, "eql?", cs_method_eql_p, 1);
1326
1338
  rb_define_method(cs, "==", cs_method_eql_p, 1);
1327
1339
  rb_define_method(cs, "merge", cs_method_merge, 1);
1328
- rb_define_method(cs, "initialize_clone", cs_method_initialize_copy, 1);
1329
- rb_define_method(cs, "initialize_dup", cs_method_initialize_copy, 1);
1340
+ rb_define_method(cs, "initialize_copy", cs_method_initialize_copy, 1);
1330
1341
  rb_define_method(cs, "subtract", cs_method_subtract, 1);
1331
1342
  rb_define_method(cs, "subset?", cs_method_subset_p, 1);
1332
1343
  rb_define_method(cs, "<=", cs_method_subset_p, 1);
@@ -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
+ }