@@ -194,9 +194,8 @@ VALUE aho_replace_text(struct ahocorasick * restrict aho, const char* data,
194194 struct aho_trie_node * travasal_node = NULL ;
195195
196196 travasal_node = & (aho -> trie .root );
197- // Preallocate with input size; Ruby will grow if needed
198- VALUE main_result = rb_str_buf_new ((long )data_len );
199-
197+ // Defer allocation until first match; handle no-match fast path
198+ VALUE main_result = Qnil ;
200199 unsigned long long last_concat_pos = 0 ;
201200
202201 for (i = 0 ; i < data_len ; i ++ )
@@ -212,8 +211,11 @@ VALUE aho_replace_text(struct ahocorasick * restrict aho, const char* data,
212211 const int rlen = result -> len ;
213212 unsigned long long pos = i - rlen + 1 ;
214213
215- // concatenate from last_concat_pos
216- if (pos > last_concat_pos ) {
214+ // On first match, allocate result and copy prefix if any
215+ if (NIL_P (main_result )) {
216+ main_result = rb_str_buf_new ((long )data_len );
217+ if (pos > 0 ) rb_str_cat (main_result , & data [0 ], pos );
218+ } else if (pos > last_concat_pos ) {
217219 rb_str_cat (main_result , & data [last_concat_pos ], pos - last_concat_pos );
218220 }
219221
@@ -249,11 +251,15 @@ VALUE aho_replace_text(struct ahocorasick * restrict aho, const char* data,
249251 last_concat_pos = i + 1 ;
250252 }
251253
252- if (last_concat_pos < data_len ) {
253- rb_str_cat (main_result , & data [last_concat_pos ], (long )(data_len - last_concat_pos ));
254+ if (NIL_P (main_result )) {
255+ // No matches; return a copy of input (preserves previous API behavior of returning a new String)
256+ return rb_str_new (data , (long )data_len );
257+ } else {
258+ if (last_concat_pos < data_len ) {
259+ rb_str_cat (main_result , & data [last_concat_pos ], (long )(data_len - last_concat_pos ));
260+ }
261+ return main_result ;
254262 }
255-
256- return main_result ;
257263}
258264
259265inline void aho_register_match_callback (VALUE rb_result_container , struct ahocorasick * restrict aho ,
0 commit comments