Standard methods missing in Ruby C API
For a few years now, I’ve been using my Ruby-enhanced MicroEMACS
to write editor extensions. I implemented this feature using
the Ruby C API,
which allows Ruby code to manipulate the edit buffer.
But when I tried to build the editor on Linux Mint 22 (Ubuntu 24.04),
I discovered that the Time.now class method is no longer available in editor extensions.
A similar issue occurred on Fedora 42: the Symbol#to_s instance method
doesn’t produce the expected result.
Time.now
The Time.now problem affected an extension I’d written that used Time.new in
several places, and now I was getting an error that Time didn’t
have a now method. This seemed weird, because outside of the editor,
Time.now still worked. After a long slog through Ruby documentation,
source code, and header files, I came across this commit
in the Ruby source code with this comment: “Moved Time.now to builtin”.
Somehow the Ruby interpreter, when run outside of the editor, is
able to patch up the Time class to use the built-in definition,
but I wasn’t able to figure out how to do that using only the Ruby C API.
However, I did find a way to do that in a mix of C and Ruby.
First, in the editor’s C code, define a Ruby-callable function that uses
rb_time_timespec_new to fetch the current local time:
static VALUE
my_now (VALUE self)
{
struct timespec ts;
VALUE now;
rb_timespec_now (&ts);
now = rb_time_timespec_new (&ts, INT_MAX);
return now;
}Then tell Ruby about this function and give it the name timenow:
rb_define_global_function("timenow", my_now, 0);Finally, in the Ruby helper script that the editor loads at startup,
add some code that dynamically creates the Time.now class method if it’s
not available:
class Time
unless Time.respond_to?(:now)
class << self
define_method(:now) { timenow }
end
end
endThis works on Linux Mint 21 (which does provide Time.now via the API) and
Linux Mint 22 (which does not provide Time.now via the API).
Symbol#to_s
I installed Fedora 42 on a spare laptop, and discovered a similar
problem: calling to_s on a symbol didn’t produce the
expected string, but instead seemed to do nothing, i.e. returned the symbol unchanged.
This caused the editor’s helper file pe.rb to fail when attempting to
call a MicroEMACS command. Also, the method Symbol#name,
which works outside the editor, was undefined, so I couldn’t use that.
The solution was very similar to the Time.now solution: implement
a helper function in C to fetch the string representation of a symbol,
and define a Symbol#to_s method to use that helper function.
First, in the editor’s C code, define a Ruby-callable function that uses
rb_sym_to_s to get the string name of a symbol:
static VALUE
my_sym2str (VALUE self, VALUE sym)
{
VALUE ret;
ret = rb_sym_to_s (sym);
return ret;
}Then tell Ruby about this function and give it the name sym2str:
rb_define_global_function("sym2str", my_sym2str, 1);Finally, in the Ruby helper script that the editor loads at startup,
add some code that dynamically creates the Symbol#to_s instance method if
Symbol#name is not available:
class Symbol
unless :name.respond_to?(:name)
define_method(:to_s) { sym2str(self) }
end
end