BigDecimal.mode()class method. In doing so, I learned quite a bit about JRuby's implementation strategy, as well as the internals of the C source code for MRI (Matz's Ruby Implementation) Ruby.
BigDecimal.mode()is a funky little method in the
BigDecimalmodule, which is not part of the core API but part of the standard library that ships with Ruby. It's kind of multi-variate -- what it does, exactly, depends on how it's called.
The first parameter to
BigDecimal.mode()is required, and it must be a Fixnum representing either the constant
BigDecimal::ROUNDING_MODEor the exception mode to be set (more on that later). If it's
BigDecimal::ROUNDING_MODEand there is no second argument, then
mode()just returns the current rounding mode. If a second argument is present, it must also be a Fixnum, and it must equate to one of the seven rounding modes Ruby recognizes (e.g.,
BigDecimal::ROUND_FLOOR, etc.). In this case,
mode()sets the rounding mode (for all
BigDecimals, remember, since this is a class method) to the value of the second argument.
If the first argument is a Fixnum that is not equal to
BigDecimal::ROUNDING_MODE, then it is expected to have one of its bits set to correspond to one of the known exception modes (e.g.,
BigDecimal::EXCEPTION_INFINITY). Again, if there is no second argument,
mode()simply reports the current exception mode(s) (each bit in the returned value corresponds to a single exception mode set). If there is a second argument, it must be one of 'true' or 'false'. If 'true',
mode()sets the mode passed in the first argument. If 'false',
mode()unsets (i.e., turns off) the mode passed in the first argument.
Not So Fast...When I picked up this task,
mode()was just a default stub that printed a message to the console and returned
nil. Not a lot to go on there. So I turned to the MRI source code to figure out just what it was supposed to do.
Introducing: One of the first things MRI does (in a lot of methods, as it turns out) is to call the function
rb_scan_args()), which is implemented in the file
class.cwith the following signature:
int rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...)It takes the number of arguments passed, a pointer to a structure containing the values of those arguments, a format string of some sort, and...some other stuff. The number and values of the arguments are self-explanatory, but the format string and the trailing "other stuff" are decidedly not, so let's take a look at them.
The format string consists, minimally, of two digits. The first digit is the number of required arguments, the second is the number of optional arguments.
rb_scan_argsparses the format string to find these numbers, then it walks the list of argument values and stuffs each value into its corresponding reference (which is what the "other stuff" in the signature actually is: a group of references to store the values of the arguments in).
BigDecimal.mode()makes this call to
if(rb_scan_args(argc,argv,"11",&which,&val)==1) val = Qnil;In English:
- get one required argument and store its value in the variable
- get the optional second argument if it exists and put its value in
rb_scan_argsreturned 1 (i.e., only one argument was provided), then set the value of the optional argument to its default of
Meanwhile, Back in JRuby...This has gone on a bit long, so I'll just close by saying that JRuby does not have an equivalent for
rb_scan_args(), or at least not one that is called on a per-method basis. The runtime is responsible for bundling arguments and calling the appropriate Java method based on the number of arguments actually present. This causes a bit of a problem right now for class methods that take optional arguments (as
BigDecimal.mode()does), but that's a subject for another post.