@@ -1689,6 +1689,38 @@ name_to_backref_number(struct re_registers *regs, VALUE regexp, const char* name
16891689 rb_long2int (name_end - name ), name );
16901690}
16911691
1692+ /*
1693+ * Resolve capture group index from Integer, Symbol, or String.
1694+ * Returns the resolved register index, or -1 if unmatched/out of range.
1695+ * For Symbol/String specifiers, raises IndexError if the named group
1696+ * does not exist.
1697+ */
1698+ static long
1699+ resolve_capture_index (struct strscanner * p , VALUE specifier )
1700+ {
1701+ const char * name ;
1702+ long i ;
1703+ if (! MATCHED_P (p )) return -1 ;
1704+ switch (TYPE (specifier )) {
1705+ case T_SYMBOL :
1706+ specifier = rb_sym2str (specifier );
1707+ /* fall through */
1708+ case T_STRING :
1709+ RSTRING_GETMEM (specifier , name , i );
1710+ i = name_to_backref_number (& (p -> regs ), p -> regex , name , name + i ,
1711+ rb_enc_get (specifier ));
1712+ break ;
1713+ default :
1714+ i = NUM2LONG (specifier );
1715+ }
1716+ if (i < 0 )
1717+ i += p -> regs .num_regs ;
1718+ if (i < 0 ) return -1 ;
1719+ if (i >= p -> regs .num_regs ) return -1 ;
1720+ if (p -> regs .beg [i ] == -1 ) return -1 ;
1721+ return i ;
1722+ }
1723+
16921724/*
16931725 *
16941726 * :markup: markdown
@@ -1763,36 +1795,93 @@ name_to_backref_number(struct re_registers *regs, VALUE regexp, const char* name
17631795static VALUE
17641796strscan_aref (VALUE self , VALUE idx )
17651797{
1766- const char * name ;
17671798 struct strscanner * p ;
17681799 long i ;
17691800
17701801 GET_SCANNER (self , p );
1771- if (! MATCHED_P (p )) return Qnil ;
1772-
1773- switch (TYPE (idx )) {
1774- case T_SYMBOL :
1775- idx = rb_sym2str (idx );
1776- /* fall through */
1777- case T_STRING :
1778- RSTRING_GETMEM (idx , name , i );
1779- i = name_to_backref_number (& (p -> regs ), p -> regex , name , name + i , rb_enc_get (idx ));
1780- break ;
1781- default :
1782- i = NUM2LONG (idx );
1783- }
1784-
1785- if (i < 0 )
1786- i += p -> regs .num_regs ;
1787- if (i < 0 ) return Qnil ;
1788- if (i >= p -> regs .num_regs ) return Qnil ;
1789- if (p -> regs .beg [i ] == -1 ) return Qnil ;
1802+ i = resolve_capture_index (p , idx );
1803+ if (i < 0 ) return Qnil ;
17901804
17911805 return extract_range (p ,
17921806 adjust_register_position (p , p -> regs .beg [i ]),
17931807 adjust_register_position (p , p -> regs .end [i ]));
17941808}
17951809
1810+ /*
1811+ * :markup: markdown
1812+ *
1813+ * call-seq:
1814+ * integer_at(specifier, base=10) -> integer or nil
1815+ *
1816+ * Returns the captured substring at the given `specifier` as an Integer,
1817+ * following the behavior of `String#to_i(base)`.
1818+ *
1819+ * `specifier` can be an Integer (positive, negative, or zero), a Symbol,
1820+ * or a String for named capture groups.
1821+ *
1822+ * Returns `nil` if:
1823+ * - No match has been performed or the last match failed
1824+ * - The `specifier` is an Integer and is out of range
1825+ * - The group at `specifier` did not participate in the match
1826+ *
1827+ * Raises IndexError if `specifier` is a Symbol or String that does not
1828+ * correspond to a named capture group, consistent with
1829+ * `StringScanner#[]`.
1830+ *
1831+ * This is semantically equivalent to `self[specifier]&.to_i(base)`
1832+ * but avoids the allocation of a temporary String when possible.
1833+ *
1834+ * ```rb
1835+ * scanner = StringScanner.new("2024-06-15")
1836+ * scanner.scan(/(\d{4})-(\d{2})-(\d{2})/)
1837+ * scanner.integer_at(1) # => 2024
1838+ * scanner.integer_at(1, 16) # => 8228
1839+ * ```
1840+ */
1841+ static VALUE
1842+ strscan_integer_at (int argc , VALUE * argv , VALUE self )
1843+ {
1844+ struct strscanner * p ;
1845+ long i ;
1846+ long beg , end , len ;
1847+ const char * ptr ;
1848+ VALUE rb_specifier ;
1849+ VALUE rb_base ;
1850+ int base = 10 ;
1851+
1852+ GET_SCANNER (self , p );
1853+ rb_scan_args (argc , argv , "11" , & rb_specifier , & rb_base );
1854+ if (argc > 1 )
1855+ base = NUM2INT (rb_base );
1856+ i = resolve_capture_index (p , rb_specifier );
1857+ if (i < 0 )
1858+ return Qnil ;
1859+
1860+ beg = adjust_register_position (p , p -> regs .beg [i ]);
1861+ end = adjust_register_position (p , p -> regs .end [i ]);
1862+ len = end - beg ;
1863+ ptr = S_PBEG (p ) + beg ;
1864+ #ifdef HAVE_RB_INT_PARSE_CSTR
1865+ {
1866+ /*
1867+ * Ruby 2.5 or later export the rb_int_parse_cstr() symbol but
1868+ * prototype definition isn't provided. Ruby 4.1 or later
1869+ * provide prototype definition.
1870+ */
1871+ # ifndef RB_INT_PARSE_DEFAULT
1872+ VALUE rb_int_parse_cstr (const char * str , ssize_t len , char * * endp ,
1873+ size_t * ndigits , int base , int flags );
1874+ # define RB_INT_PARSE_DEFAULT 0x07
1875+ # endif
1876+ char * endp ;
1877+ return rb_int_parse_cstr (ptr , len , & endp , NULL , base ,
1878+ RB_INT_PARSE_DEFAULT );
1879+ }
1880+ #else
1881+ return rb_str_to_inum (rb_str_new (ptr , len ), base , 0 );
1882+ #endif
1883+ }
1884+
17961885/*
17971886 * :markup: markdown
17981887 * :include: strscan/link_refs.txt
@@ -2353,6 +2442,7 @@ Init_strscan(void)
23532442 rb_define_method (StringScanner , "matched" , strscan_matched , 0 );
23542443 rb_define_method (StringScanner , "matched_size" , strscan_matched_size , 0 );
23552444 rb_define_method (StringScanner , "[]" , strscan_aref , 1 );
2445+ rb_define_method (StringScanner , "integer_at" , strscan_integer_at , -1 );
23562446 rb_define_method (StringScanner , "pre_match" , strscan_pre_match , 0 );
23572447 rb_define_method (StringScanner , "post_match" , strscan_post_match , 0 );
23582448 rb_define_method (StringScanner , "size" , strscan_size , 0 );
0 commit comments