~ubuntu-branches/ubuntu/hoary/malaga/hoary

« back to all changes in this revision

Viewing changes to patterns.c

  • Committer: Bazaar Package Importer
  • Author(s): Thomas Bushnell, BSG
  • Date: 2004-08-20 12:58:50 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20040820125850-rx9s8bn0ep8jgist
Tags: 6.13-4
This should have been urgency=high, because it is an important and
long-delayed accomodation to new upstream with a bajillion bug fixes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright (C) 1995 Bjoern Beutel. */
 
2
 
 
3
/* Description. =============================================================*/
 
4
 
 
5
/* This module contains function to compile and execute pattern matching 
 
6
 * strings (regular expressions). */
 
7
 
 
8
/* Includes. ================================================================*/
 
9
 
 
10
#include <ctype.h>
 
11
#include <string.h>
 
12
#include <stdio.h>
 
13
#include <stdlib.h>
 
14
#include <setjmp.h>
 
15
#include "basic.h"
 
16
#include "patterns.h"
 
17
 
 
18
/* Constants. ===============================================================*/
 
19
 
 
20
#define SPECIAL_CHARS ".[]-^()*?+|\\" /* Characters with a special meaning. */
 
21
 
 
22
/* These are the instructions for matching a pattern.
 
23
 *
 
24
 * A pattern is a 0-terminated sequence of CHARs, defined as follows:
 
25
 * - P[] is the coded pattern string.
 
26
 * - PI is the current index into P[].
 
27
 * - PIS[] is the stack of indexes into P[], used for backtracking.
 
28
 * - T[] is the text to be examined.
 
29
 * - TI is the current index into T[].
 
30
 * - TIS[] is the stack of indexes into T[], used for backtracking.
 
31
 * - SI is the stack index, used for PIS[] and TIS[].
 
32
 * - VB[I] and VE[I] store beginning and end, resp., for variable no. I. */
 
33
enum 
 
34
 
35
  /* code 0 is EOS */
 
36
  PAT_JUMP = 1, /* PI += (byte_t) P[PI]; */
 
37
  PAT_JUMP_NOW, /* SI++; PIS[SI] = PI+1; TIS[SI] = TI; PI += (byte_t) P[PI]; */
 
38
  PAT_JUMP_LATER, /* SP++; PIS[SI] = PI + (byte_t) P[PI]; TIS[SI] = I; PI++; */
 
39
  PAT_MATCH_ANY, /* if T[TI] != EOS then TI++; else fail; */
 
40
  PAT_MATCH_CLASS, /* if (T[TI] in {P[ PI + 1 ],..,P[ PI + P[PI] ]})
 
41
                    * { TI++; PI += P[PI]+1; } else fail; */ 
 
42
  PAT_MATCH_NOT_CLASS, /* if (T[TI] in {P[ PI + 1 ],..,P[ PI + P[PI] ]})
 
43
                        * fail; else { TI++; PI += P[PI]+1; } */
 
44
  PAT_START_VAR_0, /* VB[0] = I; */
 
45
  PAT_START_VAR_1, /* VB[1] = I; */
 
46
  PAT_START_VAR_2, /* VB[2] = I; */
 
47
  PAT_START_VAR_3, /* VB[3] = I; */
 
48
  PAT_START_VAR_4, /* VB[4] = I; */
 
49
  PAT_END_VAR_0, /* VE[0] = I; */
 
50
  PAT_END_VAR_1, /* VE[1] = I; */
 
51
  PAT_END_VAR_2, /* VE[2] = I; */
 
52
  PAT_END_VAR_3, /* VE[3] = I; */
 
53
  PAT_END_VAR_4 /* VE[4] = I; */
 
54
};
 
55
 
 
56
/* Types. ===================================================================*/
 
57
 
 
58
typedef struct {string_t string, pattern;} pattern_state_t;
 
59
 
 
60
/* Global variables. ========================================================*/
 
61
 
 
62
string_t pattern_var[ PATTERN_VAR_MAX ]; /* Pattern variables. */
 
63
 
 
64
/* Variables. ===============================================================*/
 
65
 
 
66
static pattern_state_t *stack; /* Stack used for backtracking. */
 
67
static int_t stack_size;
 
68
 
 
69
/* Forwards. ================================================================*/
 
70
 
 
71
static void compile_pattern_local( text_t *text, 
 
72
                                   string_t *string_p, 
 
73
                                   bool_t *may_be_empty );
 
74
 
 
75
/* Functions. ===============================================================*/
 
76
 
 
77
static bool_t 
 
78
is_pattern_char( string_t s )
 
79
{
 
80
  return ((*s == '\\' && s[1] != EOS && strchr( SPECIAL_CHARS, s[1] ) != NULL)
 
81
          || (IS_PRINT( *s ) && strchr( SPECIAL_CHARS, *s ) == NULL));
 
82
}
 
83
 
 
84
/*---------------------------------------------------------------------------*/
 
85
 
 
86
static char_t 
 
87
pattern_char( string_t *string_p )
 
88
/* See if *STRING_P points to a valid char or to an escape sequence.
 
89
 * Return the character code. Show an error if not valid. */
 
90
{
 
91
  string_t s;
 
92
  char_t c;
 
93
 
 
94
  s = *string_p;
 
95
  if (s[0] == '\\' && s[1] != EOS && strchr( SPECIAL_CHARS, s[1] ) != NULL) 
 
96
  { 
 
97
    c = TO_LOWER( s[1] );
 
98
    s += 2;
 
99
  }
 
100
  else if (IS_PRINT( s[0] ) && strchr( SPECIAL_CHARS, s[0] ) == NULL)
 
101
  { 
 
102
    c = TO_LOWER( s[0] );
 
103
    s++;
 
104
  }
 
105
  else if (s[0] == EOS) 
 
106
    complain( "Unexpected end of pattern." );
 
107
  else 
 
108
    complain( "Invalid char \"%c\" in pattern.", s[0] );
 
109
  *string_p = s;
 
110
  return c;
 
111
}
 
112
 
 
113
/*---------------------------------------------------------------------------*/
 
114
 
 
115
static char_t 
 
116
offset( int_t offs )
 
117
/* Return OFFS as a char. */
 
118
{
 
119
  /* See if OFFS can be stored in a char. */
 
120
  if ((byte_t) offs != offs || offs == 0) 
 
121
    complain( "Pattern too complex." );
 
122
 
 
123
  return (byte_t) offs;
 
124
}
 
125
 
 
126
/*---------------------------------------------------------------------------*/
 
127
 
 
128
static void 
 
129
compile_char_class( text_t *text, string_t *string_p )
 
130
/* Compile a character class at *STRING_P.
 
131
 * Save the resulting pattern in TEXT. */
 
132
{
 
133
  string_t s;
 
134
  int_t ca, ce, c; /* First char, last char and current char for ranges. */
 
135
  int_t patch_index;
 
136
 
 
137
  s = *string_p;
 
138
  s++;
 
139
  if (*s == '^') 
 
140
  { 
 
141
    s++;
 
142
    add_char_to_text( text, PAT_MATCH_NOT_CLASS );
 
143
  } 
 
144
  else 
 
145
    add_char_to_text( text, PAT_MATCH_CLASS );
 
146
  patch_index = text->string_size;
 
147
 
 
148
  /* Read chars and ranges. */
 
149
  do 
 
150
  { 
 
151
    if (*s == EOS) 
 
152
      complain( "Missing \"]\" in pattern." );
 
153
    ca = ORD( pattern_char( &s ) );
 
154
    if (*s == '-') 
 
155
    { 
 
156
      s++;
 
157
      ce = ORD( pattern_char( &s ) );
 
158
      if (ca > ce) 
 
159
        complain( "Invalid range \"%c-%c\" in pattern.", ca, ce );
 
160
    } 
 
161
    else 
 
162
      ce = ca;
 
163
    for (c = ca; c <= ce; c++) 
 
164
      add_char_to_text( text, (char_t) c );
 
165
  } while (*s != ']');
 
166
  *string_p = ++s;
 
167
  insert_char_in_text( text, offset( text->string_size - patch_index ), 
 
168
                       patch_index ); 
 
169
}
 
170
 
 
171
/*---------------------------------------------------------------------------*/
 
172
 
 
173
static bool_t
 
174
check_greedy( string_t *string_p )
 
175
{
 
176
  if (**string_p == '?')
 
177
  {
 
178
    (*string_p)++;
 
179
    return FALSE;
 
180
  }
 
181
  else 
 
182
    return TRUE;
 
183
}
 
184
 
 
185
/*---------------------------------------------------------------------------*/
 
186
 
 
187
static void 
 
188
compile_atom( text_t *text, string_t *string_p, bool_t *may_be_empty )
 
189
/* Compile an atom at *STRING_P. 
 
190
 * Save the resulting pattern in TEXT. 
 
191
 * MAY_BE_EMPTY becomes TRUE iff the atom may match an empty string. */
 
192
{
 
193
  string_t s;
 
194
  int_t start, length;
 
195
  bool_t may_be_empty_local, greedy;
 
196
 
 
197
  s = *string_p;
 
198
  *may_be_empty = TRUE;
 
199
  while (TRUE) 
 
200
  { 
 
201
    may_be_empty_local = FALSE;
 
202
    start = text->string_size;
 
203
    if (*s == '[') 
 
204
      compile_char_class( text, &s );
 
205
    else if (*s == '.') 
 
206
    { 
 
207
      s++;
 
208
      add_char_to_text( text, PAT_MATCH_ANY );
 
209
    } 
 
210
    else if (*s == '(') 
 
211
    { 
 
212
      s++;
 
213
      compile_pattern_local( text, &s, &may_be_empty_local );
 
214
      if (*s++ != ')') 
 
215
        complain( "Missing \")\" in pattern." );
 
216
    } 
 
217
    else if (is_pattern_char( s )) 
 
218
      add_char_to_text( text, pattern_char( &s ) );
 
219
    else 
 
220
      break;
 
221
    length = text->string_size - start;
 
222
 
 
223
    /* There may be a postfix operator. */
 
224
    if (*s == '?') 
 
225
    { 
 
226
      s++;
 
227
      greedy = check_greedy( &s );
 
228
      if (greedy) 
 
229
        insert_char_in_text( text, PAT_JUMP_LATER, start );
 
230
      else 
 
231
        insert_char_in_text( text, PAT_JUMP_NOW, start );
 
232
      insert_char_in_text( text, offset( 2 + length ), start + 1 );
 
233
    }
 
234
    else if (*s == '*') 
 
235
    { 
 
236
      s++;
 
237
      if (may_be_empty_local) 
 
238
        complain( "Pattern argument for \"*\" may match empty string." );
 
239
      greedy = check_greedy( &s );
 
240
      insert_char_in_text( text, PAT_JUMP, start );
 
241
      insert_char_in_text( text, offset( 2 + length ), start + 1 );
 
242
      if (greedy) 
 
243
        add_char_to_text( text, PAT_JUMP_NOW );
 
244
      else 
 
245
        add_char_to_text( text, PAT_JUMP_LATER );
 
246
      add_char_to_text( text, offset( -length ) );
 
247
    } 
 
248
    else if (*s == '+') 
 
249
    { 
 
250
      s++;
 
251
      if (may_be_empty_local) 
 
252
        complain( "Pattern argument for \"+\" may match empty string.");
 
253
      greedy = check_greedy( &s );
 
254
      if (greedy) 
 
255
        add_char_to_text( text, PAT_JUMP_NOW );
 
256
      else 
 
257
        add_char_to_text( text, PAT_JUMP_LATER );
 
258
      add_char_to_text( text, offset( -length ) );
 
259
      *may_be_empty = FALSE;
 
260
    } 
 
261
    else 
 
262
      *may_be_empty &= may_be_empty_local;
 
263
  }
 
264
  *string_p = s;
 
265
}
 
266
 
 
267
/*---------------------------------------------------------------------------*/
 
268
 
 
269
static void 
 
270
compile_pattern_local (text_t *text, string_t *string_p, bool_t *may_be_empty)
 
271
/* Convert STRING_P to a pattern to be used as input to "match_pattern".
 
272
 * If PATTERN_VAR_NO != -1, mark the pattern so the string matching this
 
273
 * pattern will be stored in PATTERN_VAR[ PATTERN_VAR_NO ].
 
274
 * The result pattern must be freed after usage. */
 
275
{
 
276
  string_t s;
 
277
  int_t start, length;
 
278
  bool_t may_be_empty_local;
 
279
 
 
280
  s = *string_p;
 
281
  start = text->string_size;
 
282
  compile_atom( text, &s, may_be_empty );
 
283
  length = text->string_size - start;
 
284
  while (*s == '|') 
 
285
  { 
 
286
    s++;
 
287
 
 
288
    /* Add jump from start of last alternative to start of this alternative. */
 
289
    insert_char_in_text( text, PAT_JUMP_LATER, start++ );
 
290
    insert_char_in_text( text, offset( length + 4 ), start++ );
 
291
 
 
292
    /* Compile this alternative. */
 
293
    start = text->string_size;
 
294
    compile_atom( text, &s, &may_be_empty_local );
 
295
    length = text->string_size - start;
 
296
    *may_be_empty |= may_be_empty_local;
 
297
 
 
298
    /* Add jump from end of last alternative to end of this alternative. */
 
299
    insert_char_in_text( text, PAT_JUMP, start++ );
 
300
    if (*s == '|') 
 
301
      insert_char_in_text( text, offset( length + 4 ), start++ ); 
 
302
    else
 
303
      insert_char_in_text( text, offset( length + 2 ), start++ );
 
304
  }
 
305
  *string_p = s;
 
306
}
 
307
 
 
308
/*---------------------------------------------------------------------------*/
 
309
 
 
310
string_t 
 
311
compile_pattern( string_t string, int_t pattern_var_no )
 
312
/* Convert STRING to a pattern to be used as input to "match_pattern".
 
313
 * If PATTERN_VAR_NO != -1, mark the pattern so the string matching this
 
314
 * pattern will be stored in PATTERN_VAR[ PATTERN_VAR_NO ].
 
315
 * The result pattern must be freed after usage. */
 
316
{
 
317
  text_t *text;
 
318
  bool_t may_be_empty;
 
319
 
 
320
  text = new_text();
 
321
  if (pattern_var_no != -1) 
 
322
    add_char_to_text( text, PAT_START_VAR_0 + pattern_var_no );
 
323
  compile_pattern_local( text, &string, &may_be_empty );
 
324
  if (*string != EOS) 
 
325
    complain( "Illegal char \"%c\" in pattern.", *string );
 
326
  if (pattern_var_no != -1) 
 
327
    add_char_to_text( text, PAT_END_VAR_0 + pattern_var_no );
 
328
  return text_to_string( &text );
 
329
}
 
330
 
 
331
/*---------------------------------------------------------------------------*/
 
332
 
 
333
bool_t 
 
334
match_pattern( string_t string, string_t pattern )
 
335
/* Test whether STRING matches PATTERN (a string of chars compiled with
 
336
 * "compile_pattern") and set substrings in PATTERN_VAR.
 
337
 * The substrings can be freed after usage. */
 
338
{
 
339
  struct {string_t start; string_t end;} var[ PATTERN_VAR_MAX ];
 
340
  int_t sp, i;
 
341
  bool_t found_mismatch;
 
342
  char_t code;
 
343
  string_t index;      
 
344
 
 
345
  sp = 0;
 
346
  found_mismatch = FALSE;
 
347
 
 
348
  /* Clear all variables. */
 
349
  for (i = 0; i < PATTERN_VAR_MAX; i++) 
 
350
    var[i].start = var[i].end = NULL;
 
351
 
 
352
  while (! found_mismatch) 
 
353
  { 
 
354
    code = *pattern++;
 
355
    switch (code) 
 
356
    {
 
357
    case EOS:
 
358
      if (*string == EOS) 
 
359
      { 
 
360
        for (i = 0; i < PATTERN_VAR_MAX; i++) 
 
361
        { 
 
362
          if (var[i].start != NULL && var[i].end != NULL) 
 
363
          { 
 
364
            free_mem( &pattern_var[i] );
 
365
            pattern_var[i] = new_string( var[i].start, var[i].end );
 
366
          }
 
367
        }
 
368
        return TRUE;
 
369
      } 
 
370
      else 
 
371
        found_mismatch = TRUE;
 
372
      break;
 
373
    case PAT_JUMP:
 
374
      pattern += (byte_t) *pattern - 1;
 
375
      break;
 
376
    case PAT_JUMP_NOW:
 
377
      if (sp == stack_size)
 
378
      {
 
379
        stack_size = renew_vector( &stack, sizeof( pattern_state_t ), 
 
380
                                   stack_size + 100 );
 
381
      }
 
382
      stack[ sp ].string = string;
 
383
      stack[ sp ].pattern = pattern + 1;
 
384
      sp++;
 
385
      pattern += (byte_t) *pattern - 1;
 
386
      break;
 
387
    case PAT_JUMP_LATER:
 
388
      if (sp == stack_size)
 
389
      {
 
390
        stack_size = renew_vector( &stack, sizeof( pattern_state_t ),
 
391
                                   stack_size + 100 );
 
392
      }
 
393
      stack[ sp ].string = string;
 
394
      stack[ sp ].pattern = pattern + (byte_t) *pattern - 1;
 
395
      sp++;
 
396
      pattern++;
 
397
      break;
 
398
    case PAT_MATCH_ANY:
 
399
      if (*string++ == EOS) 
 
400
        found_mismatch = TRUE;
 
401
      break;
 
402
    case PAT_MATCH_CLASS:
 
403
      if (*string == EOS) 
 
404
        found_mismatch = TRUE;
 
405
      else 
 
406
      { 
 
407
        index = pattern + 1;
 
408
        pattern += (byte_t) *pattern + 1;
 
409
        while (index < pattern && TO_LOWER( *string ) != *index) 
 
410
          index++;
 
411
        string++;
 
412
        if (index >= pattern) 
 
413
          found_mismatch = TRUE;
 
414
      }
 
415
      break;
 
416
    case PAT_MATCH_NOT_CLASS:
 
417
      if (*string == EOS) 
 
418
        found_mismatch = TRUE;
 
419
      else 
 
420
      { 
 
421
        index = pattern + 1;
 
422
        pattern += (byte_t) *pattern + 1;
 
423
        while (index < pattern && TO_LOWER( *string ) != *index) 
 
424
          index++;
 
425
        string++;
 
426
        if (index < pattern) 
 
427
          found_mismatch = TRUE;
 
428
      }
 
429
      break;
 
430
    case PAT_START_VAR_0:
 
431
    case PAT_START_VAR_1:
 
432
    case PAT_START_VAR_2:
 
433
    case PAT_START_VAR_3:
 
434
    case PAT_START_VAR_4:
 
435
      var[ code - PAT_START_VAR_0 ].start = string;
 
436
      break;
 
437
    case PAT_END_VAR_0:
 
438
    case PAT_END_VAR_1:
 
439
    case PAT_END_VAR_2:
 
440
    case PAT_END_VAR_3:
 
441
    case PAT_END_VAR_4:
 
442
      var[ code - PAT_END_VAR_0 ].end = string;
 
443
      break;
 
444
    default:
 
445
      if (code != TO_LOWER( *string )) 
 
446
        found_mismatch = TRUE;
 
447
      string++;
 
448
      break;
 
449
    }
 
450
 
 
451
    /* If this path was not successful and there is another path, try it. */
 
452
    if (found_mismatch && sp > 0) 
 
453
    { 
 
454
      sp--;
 
455
      string = stack[ sp ].string;
 
456
      pattern = stack[ sp ].pattern;
 
457
      found_mismatch = FALSE;
 
458
    }
 
459
  }
 
460
  return FALSE;
 
461
}
 
462
 
 
463
/*---------------------------------------------------------------------------*/
 
464
 
 
465
void 
 
466
terminate_patterns( void )
 
467
/* Terminate this module. */
 
468
{
 
469
  int_t i;
 
470
 
 
471
  for (i = 0; i < PATTERN_VAR_MAX; i++) 
 
472
    free_mem( &pattern_var[i] );
 
473
  free_mem( &stack );
 
474
  stack_size = 0;
 
475
}
 
476
 
 
477
/* End of file. =============================================================*/