rucy 0.4.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ca0dce824c22dfee0f0bcc9e8a593d29a55d793d2e71cda65b5aa0438a1f42a
4
- data.tar.gz: 5600a73ce3d2c837f2fec6d3052ece836ce2f11d8035c5192c4193ffced10208
3
+ metadata.gz: bad5f79df70e83f98dcc1571f7a43a25855b1bde8387063728ac2b9b4beeacde
4
+ data.tar.gz: 233f871a156d7391412515bb7f40c0f813489cf9c920a0a7d594bcd0a7ee0e79
5
5
  SHA512:
6
- metadata.gz: 38794541c07f9ea75e4616a572c225427eca8ff86ea70bcd19e53114040bd270cf441e0d984fa876461de189ffb0baaa25f7e17e9a66530cadeef919c991acdd
7
- data.tar.gz: d41b2ec54cbb56fbd6ba86294bba9e20a63ccf3bc94078662489d4e42137a8f5f8ceda6688477a809583e14e9fe17db181e07f65be8a31e85b876ea32689cc73
6
+ metadata.gz: 378c52e7c4e79fac2e80f0b7a0c3d60fd9ee908dd4338343e64ba82cd1b7127faf98eede21d355b0067f01325b2e38028eee012c3c41ca3d55cc09171db71a70
7
+ data.tar.gz: a204c450f186c05448ae3ffc1c15415d28fa4458ea6378d1b1f00fc93d50642ae80d202348ef27ab4dccdb40da48dfed2798abcc6c238d7e63bc67f750e673ec
@@ -49,7 +49,7 @@ class RubyBase : public ClassWrapper<T>
49
49
 
50
50
  #define THIS(type) to<type*>(self)
51
51
 
52
- #define CHECK(type) RUCY_CHECK_OBJ(type, self)
52
+ #define CHECK(type) RUCY_CHECK_PTR(type, self)
53
53
 
54
54
  #define CALL(obj, fun) RUCY_CALL_SUPER(obj, fun)
55
55
 
@@ -97,6 +97,38 @@ VALUE base_new_raw(VALUE self)
97
97
  return value(new Base);
98
98
  }
99
99
 
100
+ static Base* base_raw_ref = NULL;
101
+
102
+ static
103
+ VALUE base_set_raw_ref(VALUE self, VALUE obj)
104
+ {
105
+ if (base_raw_ref)
106
+ invalid_state_error(__FILE__, __LINE__);
107
+
108
+ // holds the c++ object without holding the ruby one, like a native handle
109
+ // that retains before the wrapper is set up
110
+ base_raw_ref = to<Base*>(obj);
111
+ base_raw_ref->Xot::RefCountable<>::retain();
112
+ return obj;
113
+ }
114
+
115
+ static
116
+ VALUE base_clear_raw_ref(VALUE self)
117
+ {
118
+ if (base_raw_ref)
119
+ base_raw_ref->Xot::RefCountable<>::release();
120
+ base_raw_ref = NULL;
121
+ }
122
+
123
+ static
124
+ VALUE base_call_raw_ref_name_overridable(VALUE self)
125
+ {
126
+ if (!base_raw_ref)
127
+ invalid_state_error(__FILE__, __LINE__);
128
+
129
+ return value(base_raw_ref->name_overridable());
130
+ }
131
+
100
132
 
101
133
  /*
102
134
  alloc function.
@@ -168,6 +200,9 @@ Init_class ()
168
200
  rb_define_method(cBase, "call_name", RUBY_METHOD_FUNC(call_name), 0);
169
201
  rb_define_method(cBase, "call_name_overridable", RUBY_METHOD_FUNC(call_name_overridable), 0);
170
202
  rb_define_singleton_method(cBase, "new_raw", RUBY_METHOD_FUNC(base_new_raw), 0);
203
+ rb_define_singleton_method(cBase, "set_raw_ref", RUBY_METHOD_FUNC(base_set_raw_ref), 1);
204
+ rb_define_singleton_method(cBase, "clear_raw_ref", RUBY_METHOD_FUNC(base_clear_raw_ref), 0);
205
+ rb_define_singleton_method(cBase, "call_raw_ref_name_overridable", RUBY_METHOD_FUNC(base_call_raw_ref_name_overridable), 0);
171
206
 
172
207
  cSub = rb_define_class_under(mTester, "Sub", cBase);
173
208
  rb_define_alloc_func(cSub, sub_alloc);
@@ -1,6 +1,15 @@
1
1
  #include "defs.h"
2
2
 
3
3
 
4
+ #ifdef OSX
5
+ #include <objc/runtime.h>
6
+ #include <objc/message.h>
7
+ #include <objc/objc-exception.h>
8
+ #include <CoreFoundation/CoreFoundation.h>
9
+ #undef nil// hides Rucy::nil()
10
+ #endif
11
+
12
+
4
13
  using namespace Rucy;
5
14
 
6
15
 
@@ -81,6 +90,25 @@ VALUE throw_cstring(VALUE self)
81
90
  throw "cstring";
82
91
  }
83
92
 
93
+ #ifdef OSX
94
+ /*
95
+ throw NSException. this file is c++, so go through the objc runtime.
96
+ */
97
+ static
98
+ VALUE throw_objc_exception(VALUE self)
99
+ {
100
+ typedef id (*Fun) (id, SEL, id, id, id);
101
+
102
+ CFStringRef name = CFSTR("TesterException");
103
+ CFStringRef reason = CFSTR("throw_objc_exception");
104
+ id exception = ((Fun) objc_msgSend)(
105
+ (id) objc_getClass("NSException"),
106
+ sel_registerName("exceptionWithName:reason:userInfo:"),
107
+ (id) name, (id) reason, NULL);
108
+ objc_exception_throw(exception);
109
+ }
110
+ #endif
111
+
84
112
 
85
113
  void
86
114
  Init_exception ()
@@ -96,4 +124,7 @@ Init_exception ()
96
124
  rb_define_method(mTester, "throw_custom_exception", RUBY_METHOD_FUNC(throw_custom_exception), 0);
97
125
  rb_define_method(mTester, "throw_std_string", RUBY_METHOD_FUNC(throw_std_string), 0);
98
126
  rb_define_method(mTester, "throw_cstring", RUBY_METHOD_FUNC(throw_cstring), 0);
127
+ #ifdef OSX
128
+ rb_define_method(mTester, "throw_objc_exception", RUBY_METHOD_FUNC(throw_objc_exception), 0);
129
+ #endif
99
130
  }
@@ -23,9 +23,9 @@ log (const char* str)
23
23
  */
24
24
  VALUE all_logs(VALUE self)
25
25
  {
26
- std::vector<Value> a;
27
- for (size_t i = 0; i < logs.size(); ++i) a.push_back(logs[i].c_str());
28
- return array(&a[0], a.size());
26
+ Value a = array(NULL, logs.size());
27
+ for (size_t i = 0; i < logs.size(); ++i) a.push(logs[i].c_str());
28
+ return a;
29
29
  }
30
30
 
31
31
  /*
@@ -5,9 +5,9 @@ using namespace Rucy;
5
5
 
6
6
 
7
7
  static
8
- VALUE value_to_char(VALUE self, VALUE num)
8
+ VALUE value_to_schar(VALUE self, VALUE num)
9
9
  {
10
- return value(to<char>(num));
10
+ return value(to<signed char>(num));
11
11
  }
12
12
 
13
13
  static
@@ -97,6 +97,19 @@ VALUE array_value(VALUE self)
97
97
  }
98
98
  }
99
99
 
100
+ static
101
+ VALUE value_is_frozen(VALUE self, VALUE obj)
102
+ {
103
+ return value(obj.is_frozen());
104
+ }
105
+
106
+ static
107
+ VALUE value_check_frozen(VALUE self, VALUE obj)
108
+ {
109
+ obj.check_frozen();
110
+ return obj;
111
+ }
112
+
100
113
 
101
114
  void
102
115
  Init_value ()
@@ -104,7 +117,7 @@ Init_value ()
104
117
  Module mRucy = rb_define_module("Rucy");
105
118
  Module mTester = rb_define_module_under(mRucy, "Tester");
106
119
 
107
- rb_define_method(mTester, "value_to_char", RUBY_METHOD_FUNC(value_to_char), 1);
120
+ rb_define_method(mTester, "value_to_schar", RUBY_METHOD_FUNC(value_to_schar), 1);
108
121
  rb_define_method(mTester, "value_to_uchar", RUBY_METHOD_FUNC(value_to_uchar), 1);
109
122
  rb_define_method(mTester, "value_to_short", RUBY_METHOD_FUNC(value_to_short), 1);
110
123
  rb_define_method(mTester, "value_to_ushort", RUBY_METHOD_FUNC(value_to_ushort), 1);
@@ -119,4 +132,7 @@ Init_value ()
119
132
 
120
133
  rb_define_method(mTester, "nil_value", RUBY_METHOD_FUNC(nil_value), -1);
121
134
  rb_define_method(mTester, "array_value", RUBY_METHOD_FUNC(array_value), -1);
135
+
136
+ rb_define_method(mTester, "value_is_frozen", RUBY_METHOD_FUNC(value_is_frozen), 1);
137
+ rb_define_method(mTester, "value_check_frozen", RUBY_METHOD_FUNC(value_check_frozen), 1);
122
138
  }
@@ -9,52 +9,5 @@ permissions:
9
9
 
10
10
  jobs:
11
11
  release:
12
- runs-on: macos-latest
13
-
14
- steps:
15
- - name: ruby 3.2
16
- uses: ruby/setup-ruby@v1
17
- with:
18
- ruby-version: 3.2
19
-
20
- - name: checkout
21
- uses: actions/checkout@v4
22
-
23
- - name: setup gems
24
- run: bundle install
25
-
26
- - name: setup dependencies
27
- run: "ruby -I.github/workflows -rutils -e 'setup_dependencies'"
28
-
29
- - name: packages
30
- run: bundle exec rake packages
31
-
32
- - name: setup zig
33
- if: github.event.repository.name == 'reflex-terminal'
34
- uses: mlugg/setup-zig@v2
35
- with:
36
- version: 0.16.0
37
-
38
- - name: test
39
- run: bundle exec rake test
40
-
41
- - name: create gem
42
- id: gem
43
- run: |
44
- bundle exec rake gem
45
- echo path=$(ruby -e 'print Dir.glob("*.gem").first') >> $GITHUB_OUTPUT
46
-
47
- - name: create github release
48
- env:
49
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
50
- run: ruby -I.github/workflows -rutils -e 'release(*ARGV)' ./${{ steps.gem.outputs.path }}
51
-
52
- - name: upload to rubygems
53
- env:
54
- GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_AUTH_TOKEN }}
55
- run: |
56
- mkdir -p $HOME/.gem
57
- touch $HOME/.gem/credentials
58
- chmod 0600 $HOME/.gem/credentials
59
- printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
60
- bundle exec rake upload
12
+ uses: xord/xot/.github/workflows/release-gem.yml@master
13
+ secrets: inherit
@@ -1,35 +1,12 @@
1
1
  name: Tag
2
2
 
3
3
  on:
4
- push:
4
+ workflow_run:
5
+ workflows: [Test]
6
+ types: [completed]
5
7
  branches: [master]
6
8
 
7
9
  jobs:
8
10
  tag:
9
- runs-on: ubuntu-latest
10
-
11
- steps:
12
- - name: ruby 3.2
13
- uses: ruby/setup-ruby@v1
14
- with:
15
- ruby-version: 3.2
16
-
17
- - name: checkout
18
- uses: actions/checkout@v4
19
- with:
20
- fetch-depth: 0
21
- token: ${{ secrets.PAT }}
22
-
23
- - name: setup dependencies
24
- run: "ruby -I.github/workflows -rutils -e 'setup_dependencies only: :xot'"
25
-
26
- - name: setup user name and email
27
- run: |
28
- git config --global user.email "xordog@gmail.com"
29
- git config --global user.name "xord"
30
-
31
- - name: tag versions
32
- run: "ruby -I.github/workflows -rutils -e 'tag_versions'"
33
-
34
- - name: push tags
35
- run: git push origin --tags
11
+ uses: xord/xot/.github/workflows/tag.yml@master
12
+ secrets: inherit
@@ -7,40 +7,4 @@ on:
7
7
 
8
8
  jobs:
9
9
  test:
10
- runs-on: macos-latest
11
-
12
- steps:
13
- - name: ruby 3.2
14
- uses: ruby/setup-ruby@v1
15
- with:
16
- ruby-version: 3.2
17
-
18
- - name: checkout
19
- uses: actions/checkout@v4
20
-
21
- - name: setup gems
22
- run: bundle install
23
-
24
- - name: setup dependencies
25
- run: "ruby -I.github/workflows -rutils -e 'setup_dependencies'"
26
-
27
- - name: packages
28
- run: bundle exec rake verbose packages
29
-
30
- - name: setup zig
31
- if: github.event.repository.name == 'reflex-terminal'
32
- uses: mlugg/setup-zig@v2
33
- with:
34
- version: 0.16.0
35
-
36
- - name: vendor
37
- run: bundle exec rake vendor
38
-
39
- - name: lib
40
- run: bundle exec rake verbose lib
41
-
42
- - name: ext
43
- run: bundle exec rake verbose ext
44
-
45
- - name: test
46
- run: bundle exec rake verbose test
10
+ uses: xord/xot/.github/workflows/test.yml@master
data/ChangeLog.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # rucy ChangeLog
2
2
 
3
3
 
4
+ ## [v0.5.0] - 2026-11-04
5
+
6
+ - [BREAKING] Rename RUCY_CHECK_OBJ to RUCY_CHECK_PTR and RUCY_CHECK_OBJECT to RUCY_CHECK_OBJ
7
+ - [BREAKING] Take signed char instead of char in value_to
8
+ - [BREAKING] Remove the hint_memory_usage callback
9
+ - Tag the strings handed over from C++ as UTF-8, not as ASCII-8BIT
10
+ - Add Value::freeze, Value::is_frozen and Value::check_frozen
11
+ - Keep a Ruby exception alive while it is thrown and caught in C++
12
+ - Put the name and reason of an Objective-C exception into NativeError
13
+
14
+ - Fix a crash when a C++ object outlives its Ruby object
15
+ - Fix a crash when the GC runs while an array is made from C++ values
16
+
17
+
4
18
  ## [v0.4.0] - 2026-08-04
5
19
 
6
20
  - Add Rucy::yield and route extension block yields through it
data/Gemfile.lock CHANGED
@@ -1,21 +1,26 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
- power_assert (3.0.1)
5
- rake (13.3.1)
6
- test-unit (3.7.7)
4
+ power_assert (3.1.0)
5
+ rake (13.4.2)
6
+ test-unit (3.7.8)
7
7
  power_assert
8
- yard (0.9.39)
8
+ yard (0.9.45)
9
9
 
10
10
  PLATFORMS
11
- arm64-darwin-21
12
- arm64-darwin-23
13
- arm64-darwin-24
11
+ ruby
14
12
 
15
13
  DEPENDENCIES
16
14
  rake
17
15
  test-unit
18
16
  yard
19
17
 
18
+ CHECKSUMS
19
+ bundler (4.0.20) sha256=7978a8ac648767f5e635bc522445b79e80a52b907a39a36c2d8085ed6bc762ae
20
+ power_assert (3.1.0) sha256=fefe98ed5d3140628138f3f9f9194d6ece01f1320e69700d3c928adca7518251
21
+ rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
22
+ test-unit (3.7.8) sha256=689d1ca53f4d46f678b4e5d68d8e4294f87fc5e8b9238cc4de7c5727e8d65943
23
+ yard (0.9.45) sha256=52e211493f7cb8a3ebf7e104a25a1e73937a3103092545d34cb88fafebb3dc51
24
+
20
25
  BUNDLED WITH
21
- 2.4.19
26
+ 4.0.20
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.5.0
data/ext/rucy/class.cpp CHANGED
@@ -49,7 +49,7 @@ class RubyBase : public ClassWrapper<T>
49
49
 
50
50
  #define THIS(type) to<type*>(self)
51
51
 
52
- #define CHECK(type) RUCY_CHECK_OBJ(type, self)
52
+ #define CHECK(type) RUCY_CHECK_PTR(type, self)
53
53
 
54
54
  #define CALL(obj, fun) RUCY_CALL_SUPER(obj, fun)
55
55
 
@@ -103,6 +103,41 @@ RUCY_DEF0(base_new_raw)
103
103
  }
104
104
  RUCY_END
105
105
 
106
+ static Base* base_raw_ref = NULL;
107
+
108
+ static
109
+ RUCY_DEF1(base_set_raw_ref, obj)
110
+ {
111
+ if (base_raw_ref)
112
+ invalid_state_error(__FILE__, __LINE__);
113
+
114
+ // holds the c++ object without holding the ruby one, like a native handle
115
+ // that retains before the wrapper is set up
116
+ base_raw_ref = to<Base*>(obj);
117
+ base_raw_ref->Xot::RefCountable<>::retain();
118
+ return obj;
119
+ }
120
+ RUCY_END
121
+
122
+ static
123
+ RUCY_DEF0(base_clear_raw_ref)
124
+ {
125
+ if (base_raw_ref)
126
+ base_raw_ref->Xot::RefCountable<>::release();
127
+ base_raw_ref = NULL;
128
+ }
129
+ RUCY_END
130
+
131
+ static
132
+ RUCY_DEF0(base_call_raw_ref_name_overridable)
133
+ {
134
+ if (!base_raw_ref)
135
+ invalid_state_error(__FILE__, __LINE__);
136
+
137
+ return value(base_raw_ref->name_overridable());
138
+ }
139
+ RUCY_END
140
+
106
141
 
107
142
  /*
108
143
  alloc function.
@@ -181,6 +216,10 @@ Init_class ()
181
216
  cBase.define_method("call_name", call_name);
182
217
  cBase.define_method("call_name_overridable", call_name_overridable);
183
218
  cBase.define_singleton_method("new_raw", base_new_raw);
219
+ cBase.define_singleton_method("set_raw_ref", base_set_raw_ref);
220
+ cBase.define_singleton_method("clear_raw_ref", base_clear_raw_ref);
221
+ cBase.define_singleton_method(
222
+ "call_raw_ref_name_overridable", base_call_raw_ref_name_overridable);
184
223
 
185
224
  cSub = mTester.define_class("Sub", cBase);
186
225
  cSub.define_alloc_func(sub_alloc);
@@ -1,6 +1,15 @@
1
1
  #include "defs.h"
2
2
 
3
3
 
4
+ #ifdef OSX
5
+ #include <objc/runtime.h>
6
+ #include <objc/message.h>
7
+ #include <objc/objc-exception.h>
8
+ #include <CoreFoundation/CoreFoundation.h>
9
+ #undef nil// hides Rucy::nil()
10
+ #endif
11
+
12
+
4
13
  using namespace Rucy;
5
14
 
6
15
 
@@ -89,6 +98,26 @@ RUCY_DEF0(throw_cstring)
89
98
  }
90
99
  RUCY_END
91
100
 
101
+ #ifdef OSX
102
+ /*
103
+ throw NSException. this file is c++, so go through the objc runtime.
104
+ */
105
+ static
106
+ RUCY_DEF0(throw_objc_exception)
107
+ {
108
+ typedef id (*Fun) (id, SEL, id, id, id);
109
+
110
+ CFStringRef name = CFSTR("TesterException");
111
+ CFStringRef reason = CFSTR("throw_objc_exception");
112
+ id exception = ((Fun) objc_msgSend)(
113
+ (id) objc_getClass("NSException"),
114
+ sel_registerName("exceptionWithName:reason:userInfo:"),
115
+ (id) name, (id) reason, NULL);
116
+ objc_exception_throw(exception);
117
+ }
118
+ RUCY_END
119
+ #endif
120
+
92
121
 
93
122
  void
94
123
  Init_exception ()
@@ -104,4 +133,7 @@ Init_exception ()
104
133
  mTester.define_method("throw_custom_exception", throw_custom_exception);
105
134
  mTester.define_method("throw_std_string", throw_std_string);
106
135
  mTester.define_method("throw_cstring", throw_cstring);
136
+ #ifdef OSX
137
+ mTester.define_method("throw_objc_exception", throw_objc_exception);
138
+ #endif
107
139
  }
data/ext/rucy/extconf.rb CHANGED
@@ -10,7 +10,8 @@ require 'rucy/extension'
10
10
 
11
11
  Xot::ExtConf.new Xot, Rucy do
12
12
  setup do
13
- headers << 'ruby.h'
13
+ headers << 'ruby.h'
14
+ frameworks << 'Foundation' if osx?
14
15
  end
15
16
 
16
17
  create_makefile 'rucy/tester'
data/ext/rucy/tester.cpp CHANGED
@@ -23,9 +23,9 @@ log (const char* str)
23
23
  */
24
24
  RUCY_DEF0(all_logs)
25
25
  {
26
- std::vector<Value> a;
27
- for (size_t i = 0; i < logs.size(); ++i) a.push_back(logs[i].c_str());
28
- return array(&a[0], a.size());
26
+ Value a = array(NULL, logs.size());
27
+ for (size_t i = 0; i < logs.size(); ++i) a.push(logs[i].c_str());
28
+ return a;
29
29
  }
30
30
  RUCY_END
31
31
 
data/ext/rucy/value.cpp CHANGED
@@ -5,9 +5,9 @@ using namespace Rucy;
5
5
 
6
6
 
7
7
  static
8
- RUCY_DEF1(value_to_char, num)
8
+ RUCY_DEF1(value_to_schar, num)
9
9
  {
10
- return value(to<char>(num));
10
+ return value(to<signed char>(num));
11
11
  }
12
12
  RUCY_END
13
13
 
@@ -110,6 +110,21 @@ RUCY_DEFN(array_value)
110
110
  }
111
111
  RUCY_END
112
112
 
113
+ static
114
+ RUCY_DEF1(value_is_frozen, obj)
115
+ {
116
+ return value(obj.is_frozen());
117
+ }
118
+ RUCY_END
119
+
120
+ static
121
+ RUCY_DEF1(value_check_frozen, obj)
122
+ {
123
+ obj.check_frozen();
124
+ return obj;
125
+ }
126
+ RUCY_END
127
+
113
128
 
114
129
  void
115
130
  Init_value ()
@@ -117,7 +132,7 @@ Init_value ()
117
132
  Module mRucy = define_module("Rucy");
118
133
  Module mTester = mRucy.define_module("Tester");
119
134
 
120
- mTester.define_method("value_to_char", value_to_char);
135
+ mTester.define_method("value_to_schar", value_to_schar);
121
136
  mTester.define_method("value_to_uchar", value_to_uchar);
122
137
  mTester.define_method("value_to_short", value_to_short);
123
138
  mTester.define_method("value_to_ushort", value_to_ushort);
@@ -126,10 +141,13 @@ Init_value ()
126
141
  mTester.define_method("value_to_long", value_to_long);
127
142
  mTester.define_method("value_to_ulong", value_to_ulong);
128
143
 
129
- mTester.define_method("true_to_value", true_to_value);
144
+ mTester.define_method( "true_to_value", true_to_value);
130
145
  mTester.define_method("false_to_value", false_to_value);
131
- mTester.define_method("null_to_value", NULL_to_value);
146
+ mTester.define_method( "null_to_value", NULL_to_value);
132
147
 
133
- mTester.define_method("nil_value", nil_value);
148
+ mTester.define_method( "nil_value", nil_value);
134
149
  mTester.define_method("array_value", array_value);
150
+
151
+ mTester.define_method("value_is_frozen", value_is_frozen);
152
+ mTester.define_method("value_check_frozen", value_check_frozen);
135
153
  }
@@ -5,6 +5,7 @@
5
5
 
6
6
 
7
7
  #include <stdexcept>
8
+ #include <exception>
8
9
  #include <xot/exception.h>
9
10
  #include <rucy/value.h>
10
11
 
@@ -30,7 +31,7 @@ namespace Rucy
30
31
 
31
32
  private:
32
33
 
33
- /*Global*/Value val;
34
+ GlobalValue val;
34
35
 
35
36
  };// RubyException
36
37
 
@@ -98,6 +99,9 @@ namespace Rucy
98
99
  const char* file, int line, const char* format = NULL, ...);
99
100
 
100
101
 
102
+ Xot::String get_unknown_exception_message (std::exception_ptr exception);
103
+
104
+
101
105
  }// Rucy
102
106
 
103
107
 
@@ -256,7 +256,8 @@
256
256
  catch (...) \
257
257
  { \
258
258
  RUCY_GOTO_RAISE(rb_exc_new2( \
259
- Rucy::native_error_class(), "Unknown C++ exception was thrown.")); \
259
+ Rucy::native_error_class(), \
260
+ Rucy::get_unknown_exception_message(std::current_exception()).c_str())); \
260
261
  }
261
262
 
262
263
  #define RUCY_GOTO_RAISE(exception) \
@@ -290,7 +291,7 @@
290
291
  }
291
292
 
292
293
 
293
- #define RUCY_CHECK_OBJ(native_class, obj) \
294
+ #define RUCY_CHECK_PTR(native_class, obj) \
294
295
  do \
295
296
  { \
296
297
  native_class* p = \
@@ -299,7 +300,7 @@
299
300
  } \
300
301
  while(0)
301
302
 
302
- #define RUCY_CHECK_OBJECT(native_class, obj) \
303
+ #define RUCY_CHECK_OBJ(native_class, obj) \
303
304
  do \
304
305
  { \
305
306
  native_class* p = \
@@ -338,7 +339,7 @@ namespace Rucy
338
339
 
339
340
  public:
340
341
 
341
- GlobalValue value;
342
+ mutable GlobalValue value;
342
343
 
343
344
  ClassWrapper ()
344
345
  : value(nil(), true), overridable(true)
@@ -353,7 +354,10 @@ namespace Rucy
353
354
 
354
355
  void release (intptr_t by_ruby) const override
355
356
  {
356
- if (!by_ruby) value.enable_gc();
357
+ if (by_ruby)
358
+ value = nil();
359
+ else
360
+ value.enable_gc();
357
361
  RucyWrapped::release();
358
362
  }
359
363
 
@@ -496,7 +500,7 @@ namespace Rucy
496
500
  static
497
501
  RUCY_DEF1(wrapper_equal, other)
498
502
  {
499
- RUCY_CHECK_OBJ(T, self);
503
+ RUCY_CHECK_PTR(T, self);
500
504
  if (!other.is_a(get_ruby_class<T>()))
501
505
  return value(false);
502
506
  return value(get_type_ptr<T>(self) == get_type_ptr<T>(other));
@@ -507,7 +511,7 @@ namespace Rucy
507
511
  static
508
512
  RUCY_DEF0(wrapper_hash)
509
513
  {
510
- RUCY_CHECK_OBJ(T, self);
514
+ RUCY_CHECK_PTR(T, self);
511
515
  return value((unsigned long long) get_type_ptr<T>(self));
512
516
  }
513
517
  RUCY_DEF_END
@@ -4,7 +4,8 @@
4
4
  #define __RUCY_VALUE_H__
5
5
 
6
6
 
7
- #include <vector>
7
+ #include <iterator>
8
+ #include <type_traits>
8
9
  #include <rucy/ruby.h>
9
10
  #include <rucy/symbol.h>
10
11
 
@@ -111,6 +112,12 @@ namespace Rucy
111
112
 
112
113
  bool respond_to (Symbol name) const;
113
114
 
115
+ Value freeze ();
116
+
117
+ bool is_frozen () const;
118
+
119
+ void check_frozen () const;
120
+
114
121
  Value inspect () const;
115
122
 
116
123
  // String
@@ -199,6 +206,8 @@ namespace Rucy
199
206
 
200
207
  GlobalValue (const This& obj, bool gc = false);
201
208
 
209
+ GlobalValue& operator = (const RubyValue& v);
210
+
202
211
  GlobalValue& operator = (const Value& v);
203
212
 
204
213
  GlobalValue& operator = (const This& obj);
@@ -224,6 +233,8 @@ namespace Rucy
224
233
 
225
234
  Value value ( char n);
226
235
 
236
+ Value value ( signed char n);
237
+
227
238
  Value value (unsigned char n);
228
239
 
229
240
  Value value ( short n);
@@ -264,8 +275,13 @@ namespace Rucy
264
275
  std::input_iterator_tag
265
276
  >::value>::type* = nullptr)
266
277
  {
267
- std::vector<Value> v(begin, end);
268
- return array(&v[0], v.size());
278
+ size_t size = 0;
279
+ if constexpr (std::sized_sentinel_for<T, T>)
280
+ size = end - begin;
281
+
282
+ Value ary = array(NULL, size);
283
+ for (T it = begin; it != end; ++it) ary.push(*it);
284
+ return ary;
269
285
  }
270
286
 
271
287
 
@@ -293,6 +309,10 @@ namespace Rucy
293
309
  }
294
310
 
295
311
 
312
+ // the range of char changes with the platform, so use signed or unsigned char
313
+ template <> char value_to<char> (Value obj, bool convert) = delete;
314
+
315
+
296
316
  }// Rucy
297
317
 
298
318
 
data/rucy.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
 
12
12
  ext = Rucy::Extension
13
13
  name = ext.name true
14
- rdocs = glob.call *%w[README .doc/ext/**/*.cpp]
14
+ rdocs = glob.call(*%w[README .doc/ext/**/*.cpp])
15
15
 
16
16
  s.name = name
17
17
  s.version = ext.version
@@ -25,7 +25,7 @@ Gem::Specification.new do |s|
25
25
  s.platform = Gem::Platform::RUBY
26
26
  s.required_ruby_version = '>= 3.0.0'
27
27
 
28
- s.add_dependency 'xot', '~> 0.4.0'
28
+ s.add_dependency 'xot', '~> 0.5.0'
29
29
 
30
30
  s.files = `git ls-files`.split $/
31
31
  s.executables = s.files.grep(%r{^bin/}) {|f| File.basename f}
data/src/exception.cpp CHANGED
@@ -144,4 +144,18 @@ namespace Rucy
144
144
  }
145
145
 
146
146
 
147
+ Xot::String
148
+ get_unknown_exception_message (std::exception_ptr exception)
149
+ {
150
+ #if defined(OSX) || defined(IOS)
151
+ Xot::String get_native_unknown_exception_message (std::exception_ptr);
152
+ Xot::String message = get_native_unknown_exception_message(exception);
153
+ if (!message.empty())
154
+ return message;
155
+ #endif
156
+
157
+ return "Unknown C++ exception was thrown.";
158
+ }
159
+
160
+
147
161
  }// Rucy
@@ -0,0 +1,2 @@
1
+ // -*- objc -*-
2
+ #include "../osx/exception.mm"
@@ -0,0 +1,31 @@
1
+ // -*- objc -*-
2
+ #include "rucy/exception.h"
3
+
4
+
5
+ #import <Foundation/Foundation.h>
6
+
7
+
8
+ namespace Rucy
9
+ {
10
+
11
+
12
+ Xot::String
13
+ get_native_unknown_exception_message (std::exception_ptr exception)
14
+ {
15
+ try
16
+ {
17
+ std::rethrow_exception(exception);
18
+ }
19
+ catch (NSException* e)
20
+ {
21
+ return Xot::stringf(
22
+ "%s: %s", e.name.UTF8String, e.reason ? e.reason.UTF8String : "");
23
+ }
24
+ catch (...)
25
+ {
26
+ return "";
27
+ }
28
+ }
29
+
30
+
31
+ }// Rucy
data/src/rucy.cpp CHANGED
@@ -2,7 +2,6 @@
2
2
  #include "rucy/rucy.h"
3
3
 
4
4
 
5
- #include <xot/util.h>
6
5
  #include "rucy/exception.h"
7
6
 
8
7
 
@@ -10,18 +9,6 @@ namespace Rucy
10
9
  {
11
10
 
12
11
 
13
- static void
14
- hint_memory_usage (ssize_t size)
15
- {
16
- rb_gc_adjust_memory_usage(size);
17
-
18
- // rb_gc_adjust_memory_usage() uses MEMOP_TYPE_REALLOC internally,
19
- // which increases malloc_increase but does not check the GC trigger.
20
- // A tiny ruby_xmalloc() goes through MEMOP_TYPE_MALLOC and triggers
21
- // GC when malloc_increase exceeds malloc_limit.
22
- if (size > 0) ruby_xfree(ruby_xmalloc(1));
23
- }
24
-
25
12
  void
26
13
  init ()
27
14
  {
@@ -29,8 +16,6 @@ namespace Rucy
29
16
  if (done) return;
30
17
  done = true;
31
18
 
32
- Xot::set_hint_memory_usage_fun(hint_memory_usage);
33
-
34
19
  rucy_module();
35
20
  native_error_class();
36
21
  invalid_state_error_class();
data/src/value.cpp.erb CHANGED
@@ -58,15 +58,13 @@ namespace Rucy
58
58
  static VALUE
59
59
  str_new (const char* s, rb_encoding* encoding)
60
60
  {
61
- rb_encoding* e = encoding ? encoding : rb_default_internal_encoding();
62
- return e ? rb_enc_str_new_cstr(s, e) : rb_str_new_cstr(s);
61
+ return rb_enc_str_new_cstr(s, encoding ? encoding : rb_utf8_encoding());
63
62
  }
64
63
 
65
64
  static VALUE
66
65
  str_new (const char* s, size_t len, rb_encoding* encoding)
67
66
  {
68
- rb_encoding* e = encoding ? encoding : rb_default_internal_encoding();
69
- return e ? rb_enc_str_new(s, len, e) : rb_str_new(s, len);
67
+ return rb_enc_str_new(s, len, encoding ? encoding : rb_utf8_encoding());
70
68
  }
71
69
 
72
70
  Value::Value (const char* s, rb_encoding* encoding)
@@ -314,6 +312,24 @@ namespace Rucy
314
312
  return rb_respond_to(val, name.symbol());
315
313
  }
316
314
 
315
+ Value
316
+ Value::freeze ()
317
+ {
318
+ return rb_obj_freeze(val);
319
+ }
320
+
321
+ bool
322
+ Value::is_frozen () const
323
+ {
324
+ return RB_OBJ_FROZEN(val);
325
+ }
326
+
327
+ void
328
+ Value::check_frozen () const
329
+ {
330
+ rb_check_frozen(val);
331
+ }
332
+
317
333
  Value
318
334
  Value::inspect () const
319
335
  {
@@ -492,6 +508,13 @@ namespace Rucy
492
508
  init(gc_);
493
509
  }
494
510
 
511
+ GlobalValue&
512
+ GlobalValue::operator = (const RubyValue& v)
513
+ {
514
+ Super::operator=(v);
515
+ return *this;
516
+ }
517
+
495
518
  GlobalValue&
496
519
  GlobalValue::operator = (const Value& v)
497
520
  {
@@ -552,6 +575,12 @@ namespace Rucy
552
575
  return (int) n;
553
576
  }
554
577
 
578
+ Value
579
+ value (signed char n)
580
+ {
581
+ return (int) n;
582
+ }
583
+
555
584
  Value
556
585
  value (unsigned char n)
557
586
  {
@@ -652,18 +681,18 @@ namespace Rucy
652
681
  return (bool) obj;
653
682
  }
654
683
 
655
- template <> char
656
- value_to<char> (Value obj, bool convert)
684
+ template <> signed char
685
+ value_to<signed char> (Value obj, bool convert)
657
686
  {
658
687
  if (convert) obj = obj.to_i();
659
688
  int n = NUM2INT(obj.value());
660
689
  if (
661
- n < std::numeric_limits<char>::min() ||
662
- n > std::numeric_limits<char>::max())
690
+ n < std::numeric_limits<signed char>::min() ||
691
+ n > std::numeric_limits<signed char>::max())
663
692
  {
664
- range_error(__FILE__, __LINE__, "cannot cast '%d' to char", n);
693
+ range_error(__FILE__, __LINE__, "cannot cast '%d' to signed char", n);
665
694
  }
666
- return (char) n;
695
+ return (signed char) n;
667
696
  }
668
697
 
669
698
  template <> unsigned char
data/test/test_class.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'weakref'
1
2
  require_relative 'helper'
2
3
 
3
4
 
@@ -130,4 +131,16 @@ class TestClass < Test::Unit::TestCase
130
131
  assert_equal '~SimpleObj(2)', last_log(/2/)
131
132
  end
132
133
 
134
+ def test_freed_ruby_object_is_not_called()
135
+ # made on another thread, whose stack is gone with no stale value left
136
+ ref = Thread.new {WeakRef.new Base.set_raw_ref(Temp.new)}.value
137
+ 3.times {GC.start; break unless ref.weakref_alive?}
138
+ # the gc scans the stack conservatively, so a stale value can keep it alive
139
+ omit 'the ruby object was not freed by the gc' if ref.weakref_alive?
140
+
141
+ assert_equal "Sub::name_overridable", Base.call_raw_ref_name_overridable
142
+ ensure
143
+ Base.clear_raw_ref
144
+ end
145
+
133
146
  end# TestClass
@@ -33,4 +33,10 @@ class TestException < Test::Unit::TestCase
33
33
  assert_raise(Rucy::NativeError) {throw_cstring}
34
34
  end
35
35
 
36
+ def test_throw_objc_exception()
37
+ return unless osx?
38
+ e = assert_raise(Rucy::NativeError) {throw_objc_exception}
39
+ assert_match(/TesterException: throw_objc_exception/, e.message)
40
+ end
41
+
36
42
  end# TestException
data/test/test_value.rb CHANGED
@@ -5,12 +5,12 @@ class TestFunction < Test::Unit::TestCase
5
5
 
6
6
  include Rucy::Tester
7
7
 
8
- def test_value_to_char()
9
- assert_equal 0, value_to_char( 0)
10
- assert_equal 127, value_to_char( 127)
11
- assert_raise(RangeError) {value_to_char 128}
12
- assert_equal(-128, value_to_char(-128))
13
- assert_raise(RangeError) {value_to_char(-129)}
8
+ def test_value_to_schar()
9
+ assert_equal 0, value_to_schar( 0)
10
+ assert_equal 127, value_to_schar( 127)
11
+ assert_raise(RangeError) {value_to_schar 128}
12
+ assert_equal(-128, value_to_schar(-128))
13
+ assert_raise(RangeError) {value_to_schar(-129)}
14
14
  end
15
15
 
16
16
  def test_value_to_uchar()
@@ -61,4 +61,13 @@ class TestFunction < Test::Unit::TestCase
61
61
  assert_raise(ArgumentError) {array_value 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
62
62
  end
63
63
 
64
+ def test_frozen()
65
+ assert_false value_is_frozen(+'')
66
+ assert_true value_is_frozen( ''.freeze)
67
+
68
+ o = Object.new
69
+ assert_equal o, value_check_frozen(o)
70
+ assert_raise(FrozenError) {value_check_frozen o.freeze}
71
+ end
72
+
64
73
  end# TestFunction
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rucy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - xordog
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-04 00:00:00.000000000 Z
11
+ date: 2026-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: xot
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.4.0
19
+ version: 0.5.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.4.0
26
+ version: 0.5.0
27
27
  description: This library helps you to develop Ruby Extension by C++.
28
28
  email: xordog@gmail.com
29
29
  executables:
@@ -48,7 +48,6 @@ files:
48
48
  - ".github/workflows/release-gem.yml"
49
49
  - ".github/workflows/tag.yml"
50
50
  - ".github/workflows/test.yml"
51
- - ".github/workflows/utils.rb"
52
51
  - CLAUDE.md
53
52
  - CONTRIBUTING.md
54
53
  - ChangeLog.md
@@ -89,7 +88,9 @@ files:
89
88
  - src/exception.cpp
90
89
  - src/extension.cpp
91
90
  - src/function.cpp.erb
91
+ - src/ios/exception.mm
92
92
  - src/module.cpp.erb
93
+ - src/osx/exception.mm
93
94
  - src/rucy.cpp
94
95
  - src/symbol.cpp
95
96
  - src/value.cpp.erb
@@ -119,7 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
120
  - !ruby/object:Gem::Version
120
121
  version: '0'
121
122
  requirements: []
122
- rubygems_version: 3.4.19
123
+ rubygems_version: 3.5.22
123
124
  signing_key:
124
125
  specification_version: 4
125
126
  summary: A Ruby C++ Extension Helper Library.
@@ -1,127 +0,0 @@
1
- require 'shellwords'
2
-
3
- ALL_REPO = 'xord/all'
4
- ALL_DIR = '../all'
5
- ALL_FETCH_DEPTH = 100
6
-
7
- RENAMES = {reflex: 'reflexion'}
8
-
9
- def sh(cmd)
10
- puts cmd
11
- system cmd
12
- end
13
-
14
- def setup_dependencies(only: nil)
15
- gemspec_path = `git ls-files`.lines(chomp: true).find {|l| l =~ /\.gemspec$/}
16
- return unless gemspec_path
17
-
18
- gemspec = File.read gemspec_path
19
- name = File.basename gemspec_path, '.gemspec'
20
-
21
- exts = File.readlines('Rakefile')
22
- .map {|l| l[%r|^\s*require\W+([\w\-\_]+)/extension\W+$|, 1]}
23
- .compact
24
- .reject {|ext| ext == name}
25
- exts = exts & [only].flatten.map(&:to_s) if only
26
- return if exts.empty?
27
-
28
- unless setup_dependencies_via_monorepo(exts)
29
- setup_dependencies_via_each_repo_by_version(gemspec, exts)
30
- end
31
-
32
- exts.each {|ext| sh %( cd ../#{ext} && rake ext )}
33
- end
34
-
35
- def setup_dependencies_via_monorepo(exts)
36
- return false unless checkout_monorepo
37
- exts.each {|ext| sh %( ln -snf all/#{ext} ../#{ext} )}
38
- true
39
- end
40
-
41
- def checkout_monorepo()
42
- uuid = `git log -1 --format=%B`[/^\[\[([0-9a-fA-F-]+)\]\]$/, 1]
43
- return false unless uuid
44
-
45
- commit = setup_monorepo uuid
46
- return false unless commit
47
-
48
- Dir.chdir(ALL_DIR) {sh %( git checkout -q #{commit} )}
49
- true
50
- end
51
-
52
- def setup_monorepo(uuid)
53
- unless File.directory? ALL_DIR
54
- url = "https://github.com/#{ALL_REPO}.git"
55
- sh %( git clone --no-tags --depth #{ALL_FETCH_DEPTH} #{url} #{ALL_DIR} )
56
- end
57
- loop do
58
- commit = find_monorepo_commit uuid
59
- return commit if commit
60
-
61
- deepened = Dir.chdir ALL_DIR do
62
- before = `git rev-list --count HEAD`.to_i
63
- sh %( git fetch --deepen #{ALL_FETCH_DEPTH} )
64
- `git rev-list --count HEAD`.to_i > before
65
- end
66
- return nil unless deepened
67
- end
68
- end
69
-
70
- def find_monorepo_commit(uuid)
71
- Dir.chdir ALL_DIR do
72
- out = `git log origin/HEAD -F --grep="[[#{uuid}]]" --format=%H -1`.strip
73
- out.empty? ? nil : out
74
- end
75
- end
76
-
77
- def setup_dependencies_via_each_repo_by_version(gemspec, exts)
78
- exts.each do |ext|
79
- gem = RENAMES[ext.to_sym].then {|s| s || ext}
80
- ver = gemspec[/add_dependency.*['"]#{gem}['"].*['"]\s*~>\s*([\d\.]+)\s*['"]/, 1]
81
- opts = '-c advice.detachedHead=false --depth 1'
82
- clone = "git clone #{opts} https://github.com/xord/#{ext}.git ../#{ext}"
83
-
84
- # 'rake subtree:push' pushes all subrepos, so cloning by new tag
85
- # often fails before tagging each new tag
86
- sh %( #{clone} --branch v#{ver} || #{clone} )
87
- end
88
- end
89
-
90
- def tag_versions()
91
- changes = changelogs
92
- tags = `git tag`.lines chomp: true
93
- vers = `git log --oneline ./VERSION`
94
- .lines(chomp: true)
95
- .map {|line| line.split.first[/^\w+$/]}
96
- .map {|sha| [`git cat-file -p #{sha}:./VERSION 2>/dev/null`[/[\d\.]+/], sha]}
97
- .select {|ver, sha| ver && sha}
98
- .reverse
99
- .to_h
100
-
101
- vers.to_a.reverse.each do |ver, sha|
102
- tag = "v#{ver}"
103
- break if tags.include?(tag)
104
- sh %( git tag -a -m \"#{changes[tag]&.gsub '"', '\\"'}\" #{tag} #{sha} )
105
- end
106
- end
107
-
108
- def release(*paths)
109
- tag = ENV['GITHUB_REF']&.sub(%r|^refs/tags/|, '') || raise('GITHUB_REF tag not set')
110
- notes = (changelogs[tag] || '').shellescape
111
- paths = paths.flatten.join ' '
112
-
113
- sh(%( gh release create #{tag} #{paths} --notes #{notes} )) ||
114
- sh(%( gh release upload #{tag} #{paths} --clobber )) ||
115
- raise('failed to upload to releases')
116
- end
117
-
118
- def changelogs()
119
- File.read('ChangeLog.md')
120
- .split(/^\s*##\s*\[\s*(v[\d\.]+)\s*\].*$/)
121
- .slice(1..)
122
- .each_slice(2)
123
- .to_h
124
- .transform_values(&:strip!)
125
- rescue Errno::ENOENT
126
- raise 'failed to get changelogs'
127
- end