Imported Upstream version 1.12
[platform/upstream/ed.git] / regex.c
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-2015 Antonio Diaz Diaz.
5
6     This program is free software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <stddef.h>
21 #include <errno.h>
22 #include <sys/types.h>
23 #include <regex.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "ed.h"
29
30
31 static regex_t * global_pat = 0;
32 static bool patlock = false;    /* if set, pattern not freed by get_compiled_pattern */
33
34 static char * stbuf = 0;        /* substitution template buffer */
35 static int stbufsz = 0;         /* substitution template buffer size */
36 static int stlen = 0;           /* substitution template length */
37
38 static char * rbuf = 0;         /* replace_matching_text buffer */
39 static int rbufsz = 0;          /* replace_matching_text buffer size */
40
41
42 bool prev_pattern( void ) { return global_pat != 0; }
43
44
45 /* translate characters in a string */
46 static void translit_text( char * p, int len, const char from, const char to )
47   {
48   while( --len >= 0 )
49     {
50     if( *p == from ) *p = to;
51     ++p;
52     }
53   }
54
55
56 /* overwrite newlines with ASCII NULs */
57 static void newline_to_nul( char * const s, const int len )
58   { translit_text( s, len, '\n', '\0' ); }
59
60 /* overwrite ASCII NULs with newlines */
61 static void nul_to_newline( char * const s, const int len )
62   { translit_text( s, len, '\0', '\n' ); }
63
64
65 /* expand a POSIX character class */
66 static const char * parse_char_class( const char * p )
67   {
68   char c, d;
69
70   if( *p == '^' ) ++p;
71   if( *p == ']' ) ++p;
72   for( ; *p != ']' && *p != '\n'; ++p )
73     if( *p == '[' && ( ( d = p[1] ) == '.' || d == ':' || d == '=' ) )
74       for( ++p, c = *++p; *p != ']' || c != d; ++p )
75         if( ( c = *p ) == '\n' )
76           return 0;
77   return ( ( *p == ']' ) ? p : 0 );
78   }
79
80
81 /* copy a pattern string from the command buffer; return pointer to the copy */
82 static char * extract_pattern( const char ** const ibufpp, const char delimiter )
83   {
84   static char * buf = 0;
85   static int bufsz = 0;
86   const char * nd = *ibufpp;
87   int len;
88
89   while( *nd != delimiter && *nd != '\n' )
90     {
91     if( *nd == '[' )
92       {
93       nd = parse_char_class( ++nd );
94       if( !nd ) { set_error_msg( "Unbalanced brackets ([])" ); return 0; }
95       }
96     else if( *nd == '\\' && *++nd == '\n' )
97       { set_error_msg( "Trailing backslash (\\)" ); return 0; }
98     ++nd;
99     }
100   len = nd - *ibufpp;
101   if( !resize_buffer( &buf, &bufsz, len + 1 ) ) return 0;
102   memcpy( buf, *ibufpp, len );
103   buf[len] = 0;
104   *ibufpp = nd;
105   if( isbinary() ) nul_to_newline( buf, len );
106   return buf;
107   }
108
109
110 /* return pointer to compiled pattern from command buffer */
111 static regex_t * get_compiled_pattern( const char ** const ibufpp )
112   {
113   static regex_t * exp = 0;
114   const char * exps;
115   const char delimiter = **ibufpp;
116   int n;
117
118   if( delimiter == ' ' )
119     { set_error_msg( "Invalid pattern delimiter" ); return 0; }
120   if( delimiter == '\n' || *++*ibufpp == '\n' || **ibufpp == delimiter )
121     {
122     if( !exp ) set_error_msg( "No previous pattern" );
123     return exp;
124     }
125   exps = extract_pattern( ibufpp, delimiter );
126   if( !exps ) return 0;
127   /* buffer alloc'd && not reserved */
128   if( exp && !patlock ) regfree( exp );
129   else
130     {
131     exp = (regex_t *) malloc( sizeof (regex_t) );
132     if( !exp )
133       {
134       show_strerror( 0, errno );
135       set_error_msg( "Memory exhausted" );
136       return 0;
137       }
138     }
139   patlock = false;
140   n = regcomp( exp, exps, 0 );
141   if( n )
142     {
143     char buf[80];
144     regerror( n, exp, buf, sizeof buf );
145     set_error_msg( buf );
146     free( exp );
147     exp = 0;
148     }
149   return exp;
150   }
151
152
153 /* add line matching a pattern to the global-active list */
154 bool build_active_list( const char ** const ibufpp, const int first_addr,
155                         const int second_addr, const bool match )
156   {
157   const regex_t * pat;
158   const line_t * lp;
159   int addr;
160   const char delimiter = **ibufpp;
161
162   if( delimiter == ' ' || delimiter == '\n' )
163     { set_error_msg( "Invalid pattern delimiter" ); return false; }
164   pat = get_compiled_pattern( ibufpp );
165   if( !pat ) return false;
166   if( **ibufpp == delimiter ) ++*ibufpp;
167   clear_active_list();
168   lp = search_line_node( first_addr );
169   for( addr = first_addr; addr <= second_addr; ++addr, lp = lp->q_forw )
170     {
171     char * const s = get_sbuf_line( lp );
172     if( !s ) return false;
173     if( isbinary() ) nul_to_newline( s, lp->len );
174     if( !regexec( pat, s, 0, 0, 0 ) == match && !set_active_node( lp ) )
175       return false;
176     }
177   return true;
178   }
179
180
181 /* return pointer to copy of substitution template in the command buffer */
182 static char * extract_subst_template( const char ** const ibufpp,
183                                       const bool isglobal )
184   {
185   int i = 0, n = 0;
186   char c;
187   const char delimiter = **ibufpp;
188
189   ++*ibufpp;
190   if( **ibufpp == '%' && (*ibufpp)[1] == delimiter )
191     {
192     ++*ibufpp;
193     if( !stbuf ) set_error_msg( "No previous substitution" );
194     return stbuf;
195     }
196   while( **ibufpp != delimiter )
197     {
198     if( !resize_buffer( &stbuf, &stbufsz, i + 2 ) ) return 0;
199     c = stbuf[i++] = *(*ibufpp)++;
200     if( c == '\n' && **ibufpp == 0 ) { --i, --*ibufpp; break; }
201     if( c == '\\' && ( stbuf[i++] = *(*ibufpp)++ ) == '\n' && !isglobal )
202       {
203       while( ( *ibufpp = get_tty_line( &n ) ) &&
204              ( n == 0 || ( n > 0 && (*ibufpp)[n-1] != '\n' ) ) )
205         clearerr( stdin );
206       if( !*ibufpp ) return 0;
207       }
208     }
209   if( !resize_buffer( &stbuf, &stbufsz, i + 1 ) ) return 0;
210   stbuf[stlen = i] = 0;
211   return stbuf;
212   }
213
214
215 /* extract substitution tail from the command buffer */
216 bool extract_subst_tail( const char ** const ibufpp, int * const gflagsp,
217                          int * const snump, const bool isglobal )
218   {
219   const char delimiter = **ibufpp;
220
221   *gflagsp = *snump = 0;
222   if( delimiter == '\n' ) { stlen = 0; *gflagsp = GPR; return true; }
223   if( !extract_subst_template( ibufpp, isglobal ) ) return false;
224   if( **ibufpp == '\n' ) { *gflagsp = GPR; return true; }
225   if( **ibufpp == delimiter ) ++*ibufpp;
226   if( **ibufpp >= '1' && **ibufpp <= '9' )
227     return parse_int( snump, *ibufpp, ibufpp );
228   if( **ibufpp == 'g' ) { ++*ibufpp; *gflagsp = GSG; }
229   return true;
230   }
231
232
233 /* return the address of the next line matching a pattern in a given
234    direction. wrap around begin/end of editor buffer if necessary */
235 int next_matching_node_addr( const char ** const ibufpp, const bool forward )
236   {
237   const regex_t * const pat = get_compiled_pattern( ibufpp );
238   int addr = current_addr();
239
240   if( !pat ) return -1;
241   do {
242     addr = ( forward ? inc_addr( addr ) : dec_addr( addr ) );
243     if( addr )
244       {
245       const line_t * const lp = search_line_node( addr );
246       char * const s = get_sbuf_line( lp );
247       if( !s ) return -1;
248       if( isbinary() ) nul_to_newline( s, lp->len );
249       if( !regexec( pat, s, 0, 0, 0 ) ) return addr;
250       }
251     }
252   while( addr != current_addr() );
253   set_error_msg( "No match" );
254   return -1;
255   }
256
257
258 bool new_compiled_pattern( const char ** const ibufpp )
259   {
260   regex_t * tpat;
261
262   disable_interrupts();
263   tpat = get_compiled_pattern( ibufpp );
264   if( tpat && tpat != global_pat )
265     {
266     if( global_pat ) { regfree( global_pat ); free( global_pat ); }
267     global_pat = tpat;
268     patlock = true;             /* reserve pattern */
269     }
270   enable_interrupts();
271   return ( tpat ? true : false );
272   }
273
274
275 /* modify text according to a substitution template; return offset to
276    end of modified text */
277 static int apply_subst_template( const char * const boln,
278                                  const regmatch_t * const rm, int offset,
279                                  const int re_nsub )
280   {
281   const char * sub = stbuf;
282
283   for( ; sub - stbuf < stlen; ++sub )
284     {
285     int n;
286     if( *sub == '&' )
287       {
288       int j = rm[0].rm_so; int k = rm[0].rm_eo;
289       if( !resize_buffer( &rbuf, &rbufsz, offset + k - j ) ) return -1;
290       while( j < k ) rbuf[offset++] = boln[j++];
291       }
292     else if( *sub == '\\' && *++sub >= '1' && *sub <= '9' &&
293              ( n = *sub - '0' ) <= re_nsub )
294       {
295       int j = rm[n].rm_so; int k = rm[n].rm_eo;
296       if( !resize_buffer( &rbuf, &rbufsz, offset + k - j ) ) return -1;
297       while( j < k ) rbuf[offset++] = boln[j++];
298       }
299     else
300       {
301       if( !resize_buffer( &rbuf, &rbufsz, offset + 1 ) ) return -1;
302       rbuf[offset++] = *sub;
303       }
304     }
305   if( !resize_buffer( &rbuf, &rbufsz, offset + 1 ) ) return -1;
306   rbuf[offset] = 0;
307   return offset;
308   }
309
310
311 /* replace text matched by a pattern according to a substitution
312    template; return size of the modified text */
313 static int replace_matching_text( const line_t * const lp, const int gflags,
314                                   const int snum )
315   {
316   enum { se_max = 30 }; /* max subexpressions in a regular expression */
317   regmatch_t rm[se_max];
318   char * txt = get_sbuf_line( lp );
319   const char * eot;
320   int i = 0, offset = 0;
321   bool changed = false;
322
323   if( !txt ) return -1;
324   if( isbinary() ) nul_to_newline( txt, lp->len );
325   eot = txt + lp->len;
326   if( !regexec( global_pat, txt, se_max, rm, 0 ) )
327     {
328     int matchno = 0;
329     do {
330       if( !snum || snum == ++matchno )
331         {
332         changed = true; i = rm[0].rm_so;
333         if( !resize_buffer( &rbuf, &rbufsz, offset + i ) ) return -1;
334         if( isbinary() ) newline_to_nul( txt, rm[0].rm_eo );
335         memcpy( rbuf + offset, txt, i ); offset += i;
336         offset = apply_subst_template( txt, rm, offset, global_pat->re_nsub );
337         if( offset < 0 ) return -1;
338         }
339       else
340         {
341         i = rm[0].rm_eo;
342         if( !resize_buffer( &rbuf, &rbufsz, offset + i ) ) return -1;
343         if( isbinary() ) newline_to_nul( txt, i );
344         memcpy( rbuf + offset, txt, i ); offset += i;
345         }
346       txt += rm[0].rm_eo;
347       }
348     while( *txt && ( !changed || ( ( gflags & GSG ) && rm[0].rm_eo ) ) &&
349            !regexec( global_pat, txt, se_max, rm, REG_NOTBOL ) );
350     i = eot - txt;
351     if( !resize_buffer( &rbuf, &rbufsz, offset + i + 2 ) ) return -1;
352     if( i > 0 && !rm[0].rm_eo && ( gflags & GSG ) )
353       { set_error_msg( "Infinite substitution loop" ); return -1; }
354     if( isbinary() ) newline_to_nul( txt, i );
355     memcpy( rbuf + offset, txt, i );
356     memcpy( rbuf + offset + i, "\n", 2 );
357     }
358   return ( changed ? offset + i + 1 : 0 );
359   }
360
361
362 /* for each line in a range, change text matching a pattern according to
363    a substitution template; return false if error */
364 bool search_and_replace( const int first_addr, const int second_addr,
365                          const int gflags, const int snum, const bool isglobal )
366   {
367   int lc;
368   bool match_found = false;
369
370   set_current_addr( first_addr - 1 );
371   for( lc = 0; lc <= second_addr - first_addr; ++lc )
372     {
373     const line_t * const lp = search_line_node( inc_current_addr() );
374     const int size = replace_matching_text( lp, gflags, snum );
375     if( size < 0 ) return false;
376     if( size )
377       {
378       const char * txt = rbuf;
379       const char * const eot = rbuf + size;
380       undo_t * up = 0;
381       disable_interrupts();
382       if( !delete_lines( current_addr(), current_addr(), isglobal ) )
383         { enable_interrupts(); return false; }
384       do {
385         txt = put_sbuf_line( txt, size, current_addr() );
386         if( !txt ) { enable_interrupts(); return false; }
387         if( up ) up->tail = search_line_node( current_addr() );
388         else
389           {
390           up = push_undo_atom( UADD, current_addr(), current_addr() );
391           if( !up ) { enable_interrupts(); return false; }
392           }
393         }
394       while( txt != eot );
395       enable_interrupts();
396       match_found = true;
397       }
398     }
399   if( !match_found && !( gflags & GLB ) )
400     { set_error_msg( "No match" ); return false; }
401   return true;
402   }