1 /* regex.c: regular expression interface routines for the ed line editor. */
2 /* GNU ed - The GNU line editor.
3 Copyright (C) 1993, 1994 Andrew Moore, Talke Studio
4 Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012
5 Free Software Foundation, Inc.
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include <sys/types.h>
32 static regex_t * global_pat = 0;
33 static bool patlock = false; /* if set, pattern not freed by get_compiled_pattern */
35 static char * stbuf = 0; /* substitution template buffer */
36 static int stbufsz = 0; /* substitution template buffer size */
37 static int stlen = 0; /* substitution template length */
39 static char * rbuf = 0; /* replace_matching_text buffer */
40 static int rbufsz = 0; /* replace_matching_text buffer size */
43 bool prev_pattern( void ) { return global_pat != 0; }
46 /* translate characters in a string */
47 static void translit_text( char * p, int len, const char from, const char to )
51 if( *p == from ) *p = to;
57 /* overwrite newlines with ASCII NULs */
58 static void newline_to_nul( char * const s, const int len )
59 { translit_text( s, len, '\n', '\0' ); }
61 /* overwrite ASCII NULs with newlines */
62 static void nul_to_newline( char * const s, const int len )
63 { translit_text( s, len, '\0', '\n' ); }
66 /* expand a POSIX character class */
67 static const char * parse_char_class( const char * p )
73 for( ; *p != ']' && *p != '\n'; ++p )
74 if( *p == '[' && ( ( d = p[1] ) == '.' || d == ':' || d == '=' ) )
75 for( ++p, c = *++p; *p != ']' || c != d; ++p )
76 if( ( c = *p ) == '\n' )
78 return ( ( *p == ']' ) ? p : 0 );
82 /* copy a pattern string from the command buffer; return pointer to the copy */
83 static char * extract_pattern( const char ** const ibufpp, const char delimiter )
85 static char * buf = 0;
87 const char * nd = *ibufpp;
90 while( *nd != delimiter && *nd != '\n' )
94 nd = parse_char_class( ++nd );
95 if( !nd ) { set_error_msg( "Unbalanced brackets ([])" ); return 0; }
97 else if( *nd == '\\' && *++nd == '\n' )
98 { set_error_msg( "Trailing backslash (\\)" ); return 0; }
102 if( !resize_buffer( &buf, &bufsz, len + 1 ) ) return 0;
103 memcpy( buf, *ibufpp, len );
106 if( isbinary() ) nul_to_newline( buf, len );
111 /* return pointer to compiled pattern from command buffer */
112 static regex_t * get_compiled_pattern( const char ** const ibufpp )
114 static regex_t * exp = 0;
116 const char delimiter = **ibufpp;
119 if( delimiter == ' ' )
120 { set_error_msg( "Invalid pattern delimiter" ); return 0; }
121 if( delimiter == '\n' || *++*ibufpp == '\n' || **ibufpp == delimiter )
123 if( !exp ) set_error_msg( "No previous pattern" );
126 exps = extract_pattern( ibufpp, delimiter );
127 if( !exps ) return 0;
128 /* buffer alloc'd && not reserved */
129 if( exp && !patlock ) regfree( exp );
132 exp = (regex_t *) malloc( sizeof (regex_t) );
135 show_strerror( 0, errno );
136 set_error_msg( "Memory exhausted" );
141 n = regcomp( exp, exps, 0 );
145 regerror( n, exp, buf, sizeof buf );
146 set_error_msg( buf );
154 /* add line matching a pattern to the global-active list */
155 bool build_active_list( const char ** const ibufpp, const int first_addr,
156 const int second_addr, const bool match )
161 const char delimiter = **ibufpp;
163 if( delimiter == ' ' || delimiter == '\n' )
164 { set_error_msg( "Invalid pattern delimiter" ); return false; }
165 pat = get_compiled_pattern( ibufpp );
166 if( !pat ) return false;
167 if( **ibufpp == delimiter ) ++*ibufpp;
169 lp = search_line_node( first_addr );
170 for( addr = first_addr; addr <= second_addr; ++addr, lp = lp->q_forw )
172 char * const s = get_sbuf_line( lp );
173 if( !s ) return false;
174 if( isbinary() ) nul_to_newline( s, lp->len );
175 if( !regexec( pat, s, 0, 0, 0 ) == match && !set_active_node( lp ) )
182 /* return pointer to copy of substitution template in the command buffer */
183 static char * extract_subst_template( const char ** const ibufpp,
184 const bool isglobal )
188 const char delimiter = **ibufpp;
191 if( **ibufpp == '%' && (*ibufpp)[1] == delimiter )
194 if( !stbuf ) set_error_msg( "No previous substitution" );
197 while( **ibufpp != delimiter )
199 if( !resize_buffer( &stbuf, &stbufsz, i + 2 ) ) return 0;
200 c = stbuf[i++] = *(*ibufpp)++;
201 if( c == '\n' && **ibufpp == 0 ) { --i, --*ibufpp; break; }
202 if( c == '\\' && ( stbuf[i++] = *(*ibufpp)++ ) == '\n' && !isglobal )
204 while( ( *ibufpp = get_tty_line( &n ) ) &&
205 ( n == 0 || ( n > 0 && (*ibufpp)[n-1] != '\n' ) ) )
207 if( !*ibufpp ) return 0;
210 if( !resize_buffer( &stbuf, &stbufsz, i + 1 ) ) return 0;
211 stbuf[stlen = i] = 0;
216 /* extract substitution tail from the command buffer */
217 bool extract_subst_tail( const char ** const ibufpp, int * const gflagsp,
218 int * const snump, const bool isglobal )
220 const char delimiter = **ibufpp;
222 *gflagsp = *snump = 0;
223 if( delimiter == '\n' ) { stlen = 0; *gflagsp = GPR; return true; }
224 if( !extract_subst_template( ibufpp, isglobal ) ) return false;
225 if( **ibufpp == '\n' ) { *gflagsp = GPR; return true; }
226 if( **ibufpp == delimiter ) ++*ibufpp;
227 if( **ibufpp >= '1' && **ibufpp <= '9' )
228 return parse_int( snump, *ibufpp, ibufpp );
229 if( **ibufpp == 'g' ) { ++*ibufpp; *gflagsp = GSG; }
234 /* return the address of the next line matching a pattern in a given
235 direction. wrap around begin/end of editor buffer if necessary */
236 int next_matching_node_addr( const char ** const ibufpp, const bool forward )
238 const regex_t * const pat = get_compiled_pattern( ibufpp );
239 int addr = current_addr();
241 if( !pat ) return -1;
243 addr = ( forward ? inc_addr( addr ) : dec_addr( addr ) );
246 const line_t * const lp = search_line_node( addr );
247 char * const s = get_sbuf_line( lp );
249 if( isbinary() ) nul_to_newline( s, lp->len );
250 if( !regexec( pat, s, 0, 0, 0 ) ) return addr;
253 while( addr != current_addr() );
254 set_error_msg( "No match" );
259 bool new_compiled_pattern( const char ** const ibufpp )
263 disable_interrupts();
264 tpat = get_compiled_pattern( ibufpp );
265 if( tpat && tpat != global_pat )
267 if( global_pat ) { regfree( global_pat ); free( global_pat ); }
269 patlock = true; /* reserve pattern */
272 return ( tpat ? true : false );
276 /* modify text according to a substitution template; return offset to
277 end of modified text */
278 static int apply_subst_template( const char * const boln,
279 const regmatch_t * const rm, int offset,
282 const char * sub = stbuf;
284 for( ; sub - stbuf < stlen; ++sub )
289 int j = rm[0].rm_so; int k = rm[0].rm_eo;
290 if( !resize_buffer( &rbuf, &rbufsz, offset + k - j ) ) return -1;
291 while( j < k ) rbuf[offset++] = boln[j++];
293 else if( *sub == '\\' && *++sub >= '1' && *sub <= '9' &&
294 ( n = *sub - '0' ) <= re_nsub )
296 int j = rm[n].rm_so; int k = rm[n].rm_eo;
297 if( !resize_buffer( &rbuf, &rbufsz, offset + k - j ) ) return -1;
298 while( j < k ) rbuf[offset++] = boln[j++];
302 if( !resize_buffer( &rbuf, &rbufsz, offset + 1 ) ) return -1;
303 rbuf[offset++] = *sub;
306 if( !resize_buffer( &rbuf, &rbufsz, offset + 1 ) ) return -1;
312 /* replace text matched by a pattern according to a substitution
313 template; return size of the modified text */
314 static int replace_matching_text( const line_t * const lp, const int gflags,
317 enum { se_max = 30 }; /* max subexpressions in a regular expression */
318 regmatch_t rm[se_max];
319 char * txt = get_sbuf_line( lp );
321 int i = 0, offset = 0;
322 bool changed = false;
324 if( !txt ) return -1;
325 if( isbinary() ) nul_to_newline( txt, lp->len );
327 if( !regexec( global_pat, txt, se_max, rm, 0 ) )
331 if( !snum || snum == ++matchno )
333 changed = true; i = rm[0].rm_so;
334 if( !resize_buffer( &rbuf, &rbufsz, offset + i ) ) return -1;
335 if( isbinary() ) newline_to_nul( txt, rm[0].rm_eo );
336 memcpy( rbuf + offset, txt, i ); offset += i;
337 offset = apply_subst_template( txt, rm, offset, global_pat->re_nsub );
338 if( offset < 0 ) return -1;
343 if( !resize_buffer( &rbuf, &rbufsz, offset + i ) ) return -1;
344 if( isbinary() ) newline_to_nul( txt, i );
345 memcpy( rbuf + offset, txt, i ); offset += i;
349 while( *txt && ( !changed || ( ( gflags & GSG ) && rm[0].rm_eo ) ) &&
350 !regexec( global_pat, txt, se_max, rm, REG_NOTBOL ) );
352 if( !resize_buffer( &rbuf, &rbufsz, offset + i + 2 ) ) return -1;
353 if( i > 0 && !rm[0].rm_eo && ( gflags & GSG ) )
354 { set_error_msg( "Infinite substitution loop" ); return -1; }
355 if( isbinary() ) newline_to_nul( txt, i );
356 memcpy( rbuf + offset, txt, i );
357 memcpy( rbuf + offset + i, "\n", 2 );
359 return ( changed ? offset + i + 1 : 0 );
363 /* for each line in a range, change text matching a pattern according to
364 a substitution template; return false if error */
365 bool search_and_replace( const int first_addr, const int second_addr,
366 const int gflags, const int snum, const bool isglobal )
369 bool match_found = false;
371 set_current_addr( first_addr - 1 );
372 for( lc = 0; lc <= second_addr - first_addr; ++lc )
374 const line_t * const lp = search_line_node( inc_current_addr() );
375 const int size = replace_matching_text( lp, gflags, snum );
376 if( size < 0 ) return false;
379 const char * txt = rbuf;
380 const char * const eot = rbuf + size;
382 disable_interrupts();
383 if( !delete_lines( current_addr(), current_addr(), isglobal ) )
384 { enable_interrupts(); return false; }
386 txt = put_sbuf_line( txt, size, current_addr() );
387 if( !txt ) { enable_interrupts(); return false; }
388 if( up ) up->tail = search_line_node( current_addr() );
391 up = push_undo_atom( UADD, current_addr(), current_addr() );
392 if( !up ) { enable_interrupts(); return false; }
400 if( !match_found && !( gflags & GLB ) )
401 { set_error_msg( "No match" ); return false; }