/*
 *  call-seq:
 *     str.sub!(pattern, replacement)          => str or nil
 *     str.sub!(pattern) {|match| block }      => str or nil
 *  
 *  Performs the substitutions of <code>String#sub</code> in place,
 *  returning <i>str</i>, or <code>nil</code> if no substitutions were
 *  performed.
 */

static VALUE
rb_str_sub_bang(argc, argv, str)
    int argc;
    VALUE *argv;
    VALUE str;
{
    VALUE pat, repl, match;
    struct re_registers *regs;
    int iter = 0;
    int tainted = 0;
    long plen;

    if (argc == 1 && rb_block_given_p()) {
        iter = 1;
    }
    else if (argc == 2) {
        repl = argv[1];
        StringValue(repl);
        if (OBJ_TAINTED(repl)) tainted = 1;
    }
    else {
        rb_raise(rb_eArgError, "wrong number of arguments (%d for 2)", argc);
    }

    pat = get_pat(argv[0], 1);
    if (rb_reg_search(pat, str, 0, 0) >= 0) {
        rb_str_modify(str);
        match = rb_backref_get();
        regs = RMATCH(match)->regs;

        if (iter) {
            char *p = RSTRING(str)->ptr; long len = RSTRING(str)->len;

            rb_match_busy(match);
            repl = rb_obj_as_string(rb_yield(rb_reg_nth_match(0, match)));
            str_mod_check(str, p, len);
            str_frozen_check(str);
            rb_backref_set(match);
        }
        else {
            repl = rb_reg_regsub(repl, str, regs);
        }
        if (OBJ_TAINTED(repl)) tainted = 1;
        plen = END(0) - BEG(0);
        if (RSTRING(repl)->len > plen) {
            RESIZE_CAPA(str, RSTRING(str)->len + RSTRING(repl)->len - plen);
        }
        if (RSTRING(repl)->len != plen) {
            memmove(RSTRING(str)->ptr + BEG(0) + RSTRING(repl)->len,
                    RSTRING(str)->ptr + BEG(0) + plen,
                    RSTRING(str)->len - BEG(0) - plen);
        }
        memcpy(RSTRING(str)->ptr + BEG(0),
               RSTRING(repl)->ptr, RSTRING(repl)->len);
        RSTRING(str)->len += RSTRING(repl)->len - plen;
        RSTRING(str)->ptr[RSTRING(str)->len] = '\0';
        if (tainted) OBJ_TAINT(str);

        return str;
    }
    return Qnil;
}