mruby-encoding: add Poorman's Encoding gem

The difference from CRuby's Encoding:

- Encoding is a module, instead of a class
- each Encoding (e.g. `Encoding::UTF_8) is a string instead of Encoding object
- only supports  `UTF-8` and `ASCII-8BIT` (and its alias `BINARY`)

Using this gem automatically turn on `MRB_UTF8_STRING` support.
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-01-16 08:43:02 +09:00
parent e45ea53773
commit 74bdae9e4c
4 changed files with 198 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
MRuby::Gem::Specification.new('mruby-encoding') do |spec|
spec.license = 'MIT'
spec.author = 'mruby developers'
spec.summary = "Poorman's Encoding for mruby"
spec.build.defines << "HAVE_MRUBY_ENCODING_GEM"
spec.build.defines << "MRB_UTF8_STRING"
spec.add_test_dependency 'mruby-string-ext'
end
+133
View File
@@ -0,0 +1,133 @@
#include <mruby.h>
#include <mruby/string.h>
#include <mruby/variable.h>
#include <mruby/internal.h>
#include <mruby/presym.h>
#define ENC_ASCII_8BIT "ASCII-8BIT"
#define ENC_BINARY "BINARY"
#define ENC_UTF8 "UTF-8"
#define ENC_COMP_P(enc, enc_lit) \
casecmp_p(RSTRING_PTR(enc), RSTRING_LEN(enc), enc_lit, sizeof(enc_lit"")-1)
static mrb_bool
casecmp_p(const char *s1, mrb_int len1, const char *s2, mrb_int len2)
{
if (len1 != len2) return FALSE;
const char *e1 = s1 + len1;
const char *e2 = s2 + len2;
while (s1 < e1 && s2 < e2) {
if (*s1 != *s2 && TOUPPER(*s1) != TOUPPER(*s2)) return FALSE;
s1++;
s2++;
}
return TRUE;
}
/*
* call-seq:
* string.valid_encoding? -> true or false
*
* Returns true for a string which is encoded correctly.
*
*/
static mrb_value
str_valid_enc_p(mrb_state *mrb, mrb_value str)
{
#define utf8_islead(c) ((unsigned char)((c)&0xc0) != 0x80)
struct RString *s = mrb_str_ptr(str);
if (RSTR_SINGLE_BYTE_P(s)) return mrb_true_value();
if (RSTR_BINARY_P(s)) return mrb_true_value();
mrb_int byte_len = RSTR_LEN(s);
mrb_int utf8_len = 0;
const char *p = RSTR_PTR(s);
const char *e = p + byte_len;
while (p < e) {
mrb_int len = mrb_utf8len(p, e);
if (len == 1 && (*p & 0x80)) return mrb_false_value();
p += len;
utf8_len++;
}
if (byte_len == utf8_len) RSTR_SET_SINGLE_BYTE_FLAG(s);
return mrb_true_value();
}
static mrb_value
str_b(mrb_state *mrb, mrb_value self)
{
mrb_value str = mrb_str_dup(mrb, self);
mrb_str_ptr(str)->flags |= MRB_STR_BINARY;
return str;
}
static mrb_value
get_encoding(mrb_state *mrb, mrb_sym enc)
{
struct RClass *e = mrb_module_get_id(mrb, MRB_SYM(Encoding));
return mrb_const_get(mrb, mrb_obj_value(e), enc);
}
static mrb_value
str_encoding(mrb_state *mrb, mrb_value self)
{
struct RString *s = mrb_str_ptr(self);
if (RSTR_BINARY_P(s)) {
return get_encoding(mrb, MRB_SYM(BINARY));
}
return get_encoding(mrb, MRB_SYM(UTF_8));
}
static mrb_value
str_force_encoding(mrb_state *mrb, mrb_value self)
{
mrb_value enc;
mrb_get_args(mrb, "S", &enc);
struct RString *s = mrb_str_ptr(self);
if (ENC_COMP_P(enc, ENC_ASCII_8BIT) ||
ENC_COMP_P(enc, ENC_BINARY)) {
s->flags |= MRB_STR_BINARY;
}
else if (ENC_COMP_P(enc, ENC_UTF8)) {
s->flags &= ~MRB_STR_BINARY;
}
else {
mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown encoding name - %v", enc);
}
return self;
}
void
mrb_mruby_encoding_gem_init(mrb_state* mrb)
{
struct RClass *s = mrb->string_class;
mrb_define_method_id(mrb, s, MRB_SYM(b), str_b, MRB_ARGS_NONE());
mrb_define_method_id(mrb, s, MRB_SYM_Q(valid_encoding), str_valid_enc_p, MRB_ARGS_NONE());
mrb_define_method_id(mrb, s, MRB_SYM(encoding), str_encoding, MRB_ARGS_NONE());
mrb_define_method_id(mrb, s, MRB_SYM(force_encoding), str_force_encoding, MRB_ARGS_REQ(1));
/* Poorman's Encoding
*
* Encoding - module instead of class
* encodings - supports only UTF-8 and ASCII-8BIT (and its alias BINARY)
* each Encoding - encoding name string instead of Encoding object
*
*/
struct RClass *e = mrb_define_module_id(mrb, MRB_SYM(Encoding));
mrb_value b = mrb_str_new_lit_frozen(mrb, ENC_ASCII_8BIT);
mrb_define_const_id(mrb, e, MRB_SYM(ASCII_8BIT), b);
mrb_define_const_id(mrb, e, MRB_SYM(BINARY), b);
mrb_define_const_id(mrb, e, MRB_SYM(UTF_8), mrb_str_new_lit_frozen(mrb, ENC_UTF8));
}
void
mrb_mruby_encoding_gem_final(mrb_state* mrb)
{
}
+27
View File
@@ -0,0 +1,27 @@
assert('Integer#chr') do
assert_equal("A", 65.chr)
assert_equal("B", 0x42.chr)
assert_equal("\xab", 171.chr)
assert_raise(RangeError) { -1.chr }
assert_raise(RangeError) { 256.chr }
assert_equal("A", 65.chr("ASCII-8BIT"))
assert_equal("B", 0x42.chr("BINARY"))
assert_equal("\xab", 171.chr("ascii-8bit"))
assert_raise(RangeError) { -1.chr("binary") }
assert_raise(RangeError) { 256.chr("Ascii-8bit") }
assert_raise(ArgumentError) { 65.chr("ASCII") }
assert_raise(ArgumentError) { 65.chr("ASCII-8BIT", 2) }
assert_raise(TypeError) { 65.chr(:BINARY) }
if __ENCODING__ == "ASCII-8BIT"
assert_raise(ArgumentError) { 65.chr("UTF-8") }
else
assert_equal("A", 65.chr("UTF-8"))
assert_equal("B", 0x42.chr("UTF-8"))
assert_equal("«", 171.chr("utf-8"))
assert_equal("", 12354.chr("Utf-8"))
assert_raise(RangeError) { -1.chr("utf-8") }
assert_raise(RangeError) { 0x110000.chr.chr("UTF-8") }
end
end
+30
View File
@@ -0,0 +1,30 @@
##
# String(Ext) Test
UTF8STRING = __ENCODING__ == "UTF-8"
assert('String#valid_encoding?') do
assert_true "hello".valid_encoding?
if UTF8STRING
assert_true "".valid_encoding?
assert_false "\xfe".valid_encoding?
assert_false "\xfe".valid_encoding?
assert_true "\xfe".b.valid_encoding?
else
assert_true "\xfe".valid_encoding?
end
end
assert('String#encoding') do
if UTF8STRING
a = ""
assert_equal Encoding::UTF_8, a.encoding
assert_equal Encoding::BINARY, a.b.encoding
assert_equal a, a.force_encoding(Encoding::BINARY)
assert_equal a, a.force_encoding(Encoding::BINARY)
assert_equal Encoding::BINARY, a.encoding
else
a = "hello"
assert_equal Encoding::BINARY, a.encoding
end
end