From 54c8427df209d215adb7de7dd55369cba4a0f8d3 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Thu, 21 May 2026 08:23:15 +0900 Subject: [PATCH] mruby-regexp: handle `\b` inside character class as backspace Inside `[...]`, `\b` denotes U+0008 (backspace) -- the same as MRI/Onigmo and PCRE. parse_escape() was missing the case, so the backslash was dropped and the bare letter `b` was inserted into the class. `[\b]` therefore matched every `b` instead of backspace. Add `case 'b': return '\b';` to parse_escape(). The function is only reached from the character-class body and range endpoints; the top-level dispatcher emits RE_WBOUND for `\b` before falling through, so the word-boundary semantics outside `[...]` are unchanged. Reported by Sam Ruby in matz/spinel#632; same engine bug affects both spinel and mruby. Co-authored-by: Claude --- mrbgems/mruby-regexp/src/re_compile.c | 2 ++ mrbgems/mruby-regexp/test/regexp.rb | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/mrbgems/mruby-regexp/src/re_compile.c b/mrbgems/mruby-regexp/src/re_compile.c index 9ffcab203..6ea625913 100644 --- a/mrbgems/mruby-regexp/src/re_compile.c +++ b/mrbgems/mruby-regexp/src/re_compile.c @@ -214,6 +214,8 @@ parse_escape(re_compiler *c) case 'v': return '\v'; case 'a': return '\a'; case 'e': return 0x1b; + case 'b': return '\b'; /* backspace; only reachable inside [...] since the + top-level dispatcher emits RE_WBOUND for `\b` */ default: return ch; /* literal: \., \\, \/, \(, etc. */ } } diff --git a/mrbgems/mruby-regexp/test/regexp.rb b/mrbgems/mruby-regexp/test/regexp.rb index 8f1499c27..e6e0a0bb6 100644 --- a/mrbgems/mruby-regexp/test/regexp.rb +++ b/mrbgems/mruby-regexp/test/regexp.rb @@ -47,6 +47,14 @@ assert("Regexp - character class") do assert_equal "abc", md[0] end +assert("Regexp - \\b inside character class is backspace") do + # Outside [...], \b is the word boundary assertion; inside [...] + # it must mean U+0008 (backspace), matching MRI/Onigmo. + assert_equal "Ruby", "Ruby".gsub(/[\b]/, "X") + assert_equal "aXc", "a\bc".gsub(/[\b]/, "X") + assert_equal ["\b", "\t", "\n"], "ABC\b\t\n".scan(/[\b-\n]/) +end + assert("Regexp - dot") do re = Regexp.new("a.c") assert_true re.match?("abc")