~ubuntu-branches/debian/sid/adabrowse/sid

« back to all changes in this revision

Viewing changes to util-strings.adb

  • Committer: Bazaar Package Importer
  • Author(s): Ludovic Brenta
  • Date: 2004-02-14 13:22:40 UTC
  • Revision ID: james.westby@ubuntu.com-20040214132240-cqumhiq1677pkvzo
Tags: upstream-4.0.2
ImportĀ upstreamĀ versionĀ 4.0.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
-------------------------------------------------------------------------------
 
2
--
 
3
--  <STRONG>Copyright &copy; 2001, 2002 by Thomas Wolf.</STRONG>
 
4
--  <BLOCKQUOTE>
 
5
--    This piece of software is free software; you can redistribute it and/or
 
6
--    modify it under the terms of the  GNU General Public License as published
 
7
--    by the Free Software  Foundation; either version 2, or (at your option)
 
8
--    any later version. This software is distributed in the hope that it will
 
9
--    be useful, but <EM>without any warranty</EM>; without even the implied
 
10
--    warranty of <EM>merchantability or fitness for a particular purpose.</EM>
 
11
--    See the GNU General Public License for  more details. You should have
 
12
--    received a copy of the GNU General Public License with this distribution,
 
13
--    see file "<A HREF="GPL.txt">GPL.txt</A>". If not, write to the Free
 
14
--    Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307,
 
15
--    USA.
 
16
--  </BLOCKQUOTE>
 
17
--  <BLOCKQUOTE>
 
18
--    As a special exception from the GPL, if other files instantiate generics
 
19
--    from this unit, or you link this unit with other files to produce an
 
20
--    executable, this unit does not by itself cause the resulting executable
 
21
--    to be covered by the GPL. This exception does not however invalidate any
 
22
--    other reasons why the executable file might be covered by the GPL.
 
23
--  </BLOCKQUOTE>
 
24
--
 
25
--  <AUTHOR>
 
26
--    Thomas Wolf  (TW) <E_MAIL>
 
27
--  </AUTHOR>
 
28
--
 
29
--  <PURPOSE>
 
30
--    Various string utilities not provided in the standard library. Some
 
31
--    of these also are repeated here, so that one can get all one needs
 
32
--    with a single "@with@".
 
33
--  </PURPOSE>
 
34
--
 
35
--  <NOT_TASK_SAFE>
 
36
--
 
37
--  <NO_STORAGE>
 
38
--
 
39
--  <HISTORY>
 
40
--    01-MAR-2002   TW  Initial version.
 
41
--    14-MAR-2002   TW  Added 'Count'.
 
42
--    18-MAR-2002   TW  Added 'Letters'.
 
43
--    02-MAY-2002   TW  Added 'Identifier'.
 
44
--    02-AUG-2002   TW  Added 'Replace'; corrected bug in 'To_Mixed'.
 
45
--    06-AUG-2002   TW  Replaced the body of 'Replace' with a non-recursive
 
46
--                      algorithm. The recursive one got slow on large strings
 
47
--                      with many replacements.
 
48
--    07-AUG-2002   TW  Added 'First_Index', 'Last_Index', and 'Count' with
 
49
--                      a string pattern.
 
50
--    12-OCT-2002   TW  Added 'Next_Blank'.
 
51
--    18-JUN-2003   TW  Added 'Equal'.
 
52
--    07-JUL-2003   TW  Added 'Cardinality'.
 
53
--  </HISTORY>
 
54
-------------------------------------------------------------------------------
 
55
 
 
56
pragma License (Modified_GPL);
 
57
 
 
58
with Ada.Exceptions;
 
59
 
 
60
package body Util.Strings is
 
61
 
 
62
   ----------------------------------------------------------------------------
 
63
 
 
64
   package ASF renames Ada.Strings.Fixed;
 
65
 
 
66
   ----------------------------------------------------------------------------
 
67
 
 
68
   function To_Mixed (S : in String) return String
 
69
   is
 
70
      Result : String (S'Range);
 
71
      Prev   : Character := '_';
 
72
   begin
 
73
      for I in Result'Range loop
 
74
         if Prev = '_' or else Prev = '.' or else
 
75
            Ada.Strings.Maps.Is_In (Prev, Blanks)
 
76
         then
 
77
            Result (I) := To_Upper (S (I));
 
78
         else
 
79
            Result (I) := To_Lower (S (I));
 
80
         end if;
 
81
         Prev := S (I);
 
82
      end loop;
 
83
      return Result;
 
84
   end To_Mixed;
 
85
 
 
86
   ----------------------------------------------------------------------------
 
87
 
 
88
   function Cardinality
 
89
     (Set : in Ada.Strings.Maps.Character_Set)
 
90
     return Natural
 
91
   is
 
92
      N : Natural := 0;
 
93
   begin
 
94
      for Ch in Character loop
 
95
         if Ada.Strings.Maps.Is_In (Ch, Set) then N := N + 1; end if;
 
96
      end loop;
 
97
      return N;
 
98
   end Cardinality;
 
99
 
 
100
   --  Returns the number of characters in @Set@.
 
101
 
 
102
   function Equal
 
103
     (Left, Right : in String)
 
104
     return Boolean
 
105
   is
 
106
   begin
 
107
      return To_Lower (Left) = To_Lower (Right);
 
108
   end Equal;
 
109
 
 
110
   function First_Index
 
111
     (Src : in String;
 
112
      Ch  : in Character)
 
113
     return Natural
 
114
   is
 
115
   begin
 
116
      for I in Src'Range loop
 
117
         if Src (I) = Ch then return I; end if;
 
118
      end loop;
 
119
      return 0;
 
120
   end First_Index;
 
121
 
 
122
   function Last_Index
 
123
     (Src : in String;
 
124
      Ch  : in Character)
 
125
     return Natural
 
126
   is
 
127
   begin
 
128
      for I in reverse Src'Range loop
 
129
         if Src (I) = Ch then return I; end if;
 
130
      end loop;
 
131
      return 0;
 
132
   end Last_Index;
 
133
 
 
134
   function First_Index
 
135
     (Source  : in String;
 
136
      Pattern : in String)
 
137
     return Natural
 
138
   is
 
139
      Length : constant Natural := Pattern'Length;
 
140
   begin
 
141
      if Length = 0 then raise Ada.Strings.Pattern_Error; end if;
 
142
      for I in Source'First .. Source'Last - Length + 1 loop
 
143
         if Source (I .. I + Length - 1) = Pattern then
 
144
            return I;
 
145
         end if;
 
146
      end loop;
 
147
      return 0;
 
148
   end First_Index;
 
149
 
 
150
   function Last_Index
 
151
     (Source   : in String;
 
152
      Pattern  : in String)
 
153
     return Natural
 
154
   is
 
155
      Length : constant Natural := Pattern'Length;
 
156
   begin
 
157
      if Length = 0 then raise Ada.Strings.Pattern_Error; end if;
 
158
      for I in reverse Source'First .. Source'Last - Length + 1 loop
 
159
         if Source (I .. I + Length - 1) = Pattern then
 
160
            return I;
 
161
         end if;
 
162
      end loop;
 
163
      return 0;
 
164
   end Last_Index;
 
165
 
 
166
   function Index
 
167
     (Src : in String;
 
168
      Ch  : in Character;
 
169
      Dir : in Ada.Strings.Direction := Forward)
 
170
     return Natural
 
171
   is
 
172
      use type Ada.Strings.Direction;
 
173
   begin
 
174
      if Dir = Forward then
 
175
         return First_Index (Src, Ch);
 
176
      else
 
177
         return Last_Index (Src, Ch);
 
178
      end if;
 
179
   end Index;
 
180
 
 
181
   function Index
 
182
     (Source  : in String;
 
183
      Pattern : in String;
 
184
      Dir     : in Ada.Strings.Direction := Forward)
 
185
     return Natural
 
186
   is
 
187
      use type Ada.Strings.Direction;
 
188
   begin
 
189
      if Dir = Forward then
 
190
         return First_Index (Source, Pattern);
 
191
      else
 
192
         return Last_Index (Source, Pattern);
 
193
      end if;
 
194
   end Index;
 
195
 
 
196
   ----------------------------------------------------------------------------
 
197
 
 
198
   function Count
 
199
     (Src : in String;
 
200
      Ch  : in Character)
 
201
     return Natural
 
202
   is
 
203
      N : Natural := 0;
 
204
   begin
 
205
      for I in Src'Range loop
 
206
         if Src (I) = Ch then N := N + 1; end if;
 
207
      end loop;
 
208
      return N;
 
209
   end Count;
 
210
 
 
211
   function Count
 
212
     (Source  : in String;
 
213
      Pattern : in String)
 
214
     return Natural
 
215
   is
 
216
      Length : constant Natural := Pattern'Length;
 
217
   begin
 
218
      if Length = 0 then raise Ada.Strings.Pattern_Error; end if;
 
219
      if Length > Source'Length then return 0; end if;
 
220
      declare
 
221
         Stop : constant Natural := Source'Last - Length + 1;
 
222
         I    :          Natural := Source'First;
 
223
         N    :          Natural := 0;
 
224
      begin
 
225
         while I <= Stop loop
 
226
            if Source (I .. I + Length - 1) = Pattern then
 
227
               N := N + 1; I := I + Length;
 
228
            else
 
229
               I := I + 1;
 
230
            end if;
 
231
         end loop;
 
232
         return N;
 
233
      end;
 
234
   end Count;
 
235
 
 
236
   ----------------------------------------------------------------------------
 
237
 
 
238
   function Is_Blank
 
239
     (Ch : in Character)
 
240
     return Boolean
 
241
   is
 
242
   begin
 
243
      return Ada.Strings.Maps.Is_In (Ch, Blanks);
 
244
   end Is_Blank;
 
245
 
 
246
   ----------------------------------------------------------------------------
 
247
 
 
248
   function Is_In
 
249
     (Set : in Ada.Strings.Maps.Character_Set;
 
250
      Ch  : in Character)
 
251
     return Boolean
 
252
   is
 
253
   begin
 
254
      return Ada.Strings.Maps.Is_In (Ch, Set);
 
255
   end Is_In;
 
256
 
 
257
   ----------------------------------------------------------------------------
 
258
 
 
259
   function Trim
 
260
     (S    : in String;
 
261
      Side : in Ada.Strings.Trim_End := Both)
 
262
     return String
 
263
   is
 
264
   begin
 
265
      case Side is
 
266
         when Both =>
 
267
            return ASF.Trim (S, Blanks, Blanks);
 
268
         when Left =>
 
269
            return ASF.Trim (S, Blanks, Null_Set);
 
270
         when Right =>
 
271
            return ASF.Trim (S, Null_Set, Blanks);
 
272
      end case;
 
273
   end Trim;
 
274
 
 
275
   ----------------------------------------------------------------------------
 
276
 
 
277
   procedure Get_String
 
278
     (S        : in     String;
 
279
      From, To :    out Natural;
 
280
      Delim    : in     Character := '"';
 
281
      Escape   : in     Character := No_Escape)
 
282
   is
 
283
   begin
 
284
      From := S'First; To := 0;
 
285
      while From <= S'Last and then S (From) /= Delim loop
 
286
         From := From + 1;
 
287
      end loop;
 
288
      if From = S'Last then
 
289
         return;
 
290
      elsif From > S'Last then
 
291
         From := 0; return;
 
292
      end if;
 
293
      To := Skip_String (S (From .. S'Last), Delim, Escape);
 
294
   end Get_String;
 
295
 
 
296
   function In_String
 
297
     (S      : in String;
 
298
      Delim  : in Character := '"';
 
299
      Escape : in Character := No_Escape)
 
300
     return Boolean
 
301
   is
 
302
      I, From, To : Natural;
 
303
   begin
 
304
      I := S'First;
 
305
      loop
 
306
         Get_String (S (I .. S'Last), From, To, Delim, Escape);
 
307
         exit when From = 0 or else To = 0;
 
308
         I := To + 1;
 
309
      end loop;
 
310
      return From /= 0 and then To = 0;
 
311
   end In_String;
 
312
 
 
313
   function Skip_String
 
314
     (S      : in String;
 
315
      Delim  : in Character := '"';
 
316
      Escape : in Character := No_Escape)
 
317
     return Natural
 
318
   is
 
319
      From : Natural;
 
320
      To   : Natural := 0;
 
321
   begin
 
322
      if S'Last < S'First then return 0; end if;
 
323
      From := S'First + 1;
 
324
      if Escape = No_Escape then
 
325
         To := First_Index (S (From .. S'Last), Delim);
 
326
      else
 
327
         To := From;
 
328
         if Escape /= Delim then
 
329
            declare
 
330
               Escaped : Boolean := False;
 
331
            begin
 
332
               --  Be careful: a double occurrence of the escape is a literal
 
333
               --  escape!
 
334
               while To <= S'Last loop
 
335
                  if S (To) = Escape then
 
336
                     Escaped := not Escaped;
 
337
                  elsif S (To) = Delim and then not Escaped then
 
338
                     return To;
 
339
                  else
 
340
                     Escaped := False;
 
341
                  end if;
 
342
                  To := To + 1;
 
343
               end loop;
 
344
            end;
 
345
         else
 
346
            --  Delimiters are escaped by doubling!
 
347
            while To < S'Last loop
 
348
               if S (To) = Delim then
 
349
                  if S (To + 1) /= Delim then
 
350
                     return To;
 
351
                  end if;
 
352
                  To := To + 2;
 
353
               else
 
354
                  To := To + 1;
 
355
               end if;
 
356
            end loop;
 
357
            if To <= S'Last and then S (To) = Delim then return To; end if;
 
358
         end if;
 
359
         To := 0;
 
360
      end if;
 
361
      return To;
 
362
   end Skip_String;
 
363
 
 
364
   function Quote
 
365
     (S      : in String;
 
366
      Delim  : in Character;
 
367
      Escape : in Character)
 
368
     return String
 
369
   is
 
370
   begin
 
371
      if Escape = No_Escape then
 
372
         return S;
 
373
      else
 
374
         declare
 
375
            Result : String (1 .. S'Length * 2);
 
376
            J      : Natural := 1;
 
377
         begin
 
378
            if Escape = Delim then
 
379
               for I in S'Range loop
 
380
                  if S (I) = Delim then
 
381
                     Result (J) := Escape; J := J + 1;
 
382
                  end if;
 
383
                  Result (J) := S (I); J := J + 1;
 
384
               end loop;
 
385
            else
 
386
               for I in S'Range loop
 
387
                  if S (I) = Delim or else S (I) = Escape then
 
388
                     Result (J) := Escape; J := J + 1;
 
389
                  end if;
 
390
                  Result (J) := S (I); J := J + 1;
 
391
               end loop;
 
392
            end if;
 
393
            return Result (1 .. J - 1);
 
394
         end;
 
395
      end if;
 
396
   end Quote;
 
397
 
 
398
   function Unquote
 
399
     (S      : in String;
 
400
      Delim  : in Character;
 
401
      Escape : in Character)
 
402
     return String
 
403
   is
 
404
      Result : String (1 .. S'Length);
 
405
      I      : Natural := S'First;
 
406
      J      : Natural := Result'First;
 
407
   begin
 
408
      if Escape = Delim then
 
409
         while I <= S'Last loop
 
410
            if S (I) = Delim and then
 
411
               I < S'Last and then S (I + 1) = Delim
 
412
            then
 
413
               I := I + 1;
 
414
            end if;
 
415
            Result (J) := S (I);
 
416
            J := J + 1; I := I + 1;
 
417
         end loop;
 
418
      else
 
419
         while I <= S'Last loop
 
420
            if S (I) = Escape and then
 
421
               I < S'Last and then
 
422
               (S (I + 1) = Escape or else S (I + 1) = Delim)
 
423
            then
 
424
               I := I + 1;
 
425
            end if;
 
426
            Result (J) := S (I);
 
427
            J := J + 1; I := I + 1;
 
428
         end loop;
 
429
      end if;
 
430
      return Result (Result'First .. J - 1);
 
431
   end Unquote;
 
432
 
 
433
   function Unquote_All
 
434
     (S      : in String;
 
435
      Quotes : in Ada.Strings.Maps.Character_Set;
 
436
      Escape : in Character := No_Escape)
 
437
     return String
 
438
   is
 
439
      I, J : Natural;
 
440
      Ch   : Character;
 
441
   begin
 
442
      I := ASF.Index (S, Quotes);
 
443
      if I = 0 then return S; end if; --  No string found
 
444
      if Escape = No_Escape then
 
445
         Ch := S (I);
 
446
      else
 
447
         Ch := Escape;
 
448
      end if;
 
449
      J := Skip_String (S (I .. S'Last), S (I), Ch);
 
450
      if J = 0 then
 
451
         --  Unterminated string!
 
452
         return S;
 
453
      end if;
 
454
      return S (S'First .. I) &
 
455
             Unquote (S (I + 1 .. J - 1), S (I), Ch) & S (I) &
 
456
             Unquote_All (S (J + 1 .. S'Last), Quotes, Escape);
 
457
   end Unquote_All;
 
458
 
 
459
   ----------------------------------------------------------------------------
 
460
 
 
461
   function Is_Prefix
 
462
     (Source : in String;
 
463
      Prefix : in String)
 
464
     return Boolean
 
465
   is
 
466
   begin
 
467
      return
 
468
        Source (Source'First ..
 
469
                Natural'Min (Source'Last,
 
470
                             Source'First + Prefix'Length - 1)) =
 
471
        Prefix;
 
472
   end Is_Prefix;
 
473
 
 
474
   function Is_Suffix
 
475
     (Source : in String;
 
476
      Suffix : in String)
 
477
     return Boolean
 
478
   is
 
479
   begin
 
480
      if Suffix'Length > Source'Length then return False; end if;
 
481
      return
 
482
        Source (Source'Last - Suffix'Length + 1 .. Source'Last) = Suffix;
 
483
   end Is_Suffix;
 
484
 
 
485
   ----------------------------------------------------------------------------
 
486
 
 
487
   ID_Chars : constant Ada.Strings.Maps.Character_Set :=
 
488
     Ada.Strings.Maps."or" (Letters, Ada.Strings.Maps.To_Set ("0123456789_"));
 
489
 
 
490
   function Identifier
 
491
     (S : in String)
 
492
     return Natural
 
493
   is
 
494
   begin
 
495
      if S'Last < S'First or else not Is_In (Letters, S (S'First)) then
 
496
         return 0;
 
497
      end if;
 
498
      declare
 
499
         I : Natural := S'First + 1;
 
500
      begin
 
501
         while I <= S'Last and then Is_In (ID_Chars, S (I)) loop
 
502
            I := I + 1;
 
503
         end loop;
 
504
         return I - 1;
 
505
      end;
 
506
   end Identifier;
 
507
 
 
508
   ----------------------------------------------------------------------------
 
509
 
 
510
   function Next_Non_Blank
 
511
     (S : in String)
 
512
     return Natural
 
513
   is
 
514
   begin
 
515
      for I in S'Range loop
 
516
         if not Is_Blank (S (I)) then return I; end if;
 
517
      end loop;
 
518
      return 0;
 
519
   end Next_Non_Blank;
 
520
 
 
521
   ----------------------------------------------------------------------------
 
522
 
 
523
   function Next_Blank
 
524
     (S : in String)
 
525
     return Natural
 
526
   is
 
527
   begin
 
528
      for I in S'Range loop
 
529
         if Is_Blank (S (I)) then return I; end if;
 
530
      end loop;
 
531
      return 0;
 
532
   end Next_Blank;
 
533
 
 
534
   ----------------------------------------------------------------------------
 
535
 
 
536
   function Replace
 
537
     (Source : in String;
 
538
      What   : in String;
 
539
      By     : in String)
 
540
     return String
 
541
   is
 
542
      --  Speed optimized for both small and large strings, with few and many
 
543
      --  replacements.
 
544
      N : constant Natural := Count (Source, What);
 
545
   begin
 
546
      if N = 0 then return Source; end if;
 
547
      declare
 
548
         By_Length   : constant Natural := By'Length;
 
549
         What_Length : constant Natural := What'Length;
 
550
         Result      : String (1 ..
 
551
                               Source'Length - N * (What_Length - By_Length));
 
552
         --  Now if only the compiler was smart enough to allocate 'Result'
 
553
         --  directly on the secondary stack instead of using 'alloca'...
 
554
         --  Note that 'Result' has exactly the length it needs!
 
555
         I, J, K     : Natural;
 
556
      begin
 
557
         J := 1;
 
558
         I := Source'First;
 
559
         while I <= Source'Last loop
 
560
            K := First_Index (Source (I .. Source'Last), What);
 
561
            if K = 0 then
 
562
               Result (J .. Result'Last) := Source (I .. Source'Last);
 
563
               --  J := Result'Last + 1;
 
564
               I := Source'Last + 1;
 
565
            else
 
566
               Result (J .. J + (K - I) - 1) := Source (I .. K - 1);
 
567
               J := J + (K - I);
 
568
               Result (J .. J + By_Length - 1) := By;
 
569
               J := J + By_Length;
 
570
               I := K + What_Length;
 
571
            end if;
 
572
         end loop;
 
573
         return Result;
 
574
      end;
 
575
   end Replace;
 
576
 
 
577
   ----------------------------------------------------------------------------
 
578
   --  Wildcard string matching
 
579
 
 
580
   package ASM renames Ada.Strings.Maps;
 
581
 
 
582
   procedure Raise_Unterminated_Set
 
583
     (Pattern : in String)
 
584
   is
 
585
   begin
 
586
      Ada.Exceptions.Raise_Exception
 
587
        (Illegal_Pattern'Identity,
 
588
         "Unterminated character set: '" & Pattern & "'");
 
589
   end Raise_Unterminated_Set;
 
590
 
 
591
   procedure Raise_Backslash_At_End
 
592
   is
 
593
   begin
 
594
      Ada.Exceptions.Raise_Exception
 
595
        (Illegal_Pattern'Identity,
 
596
         "'\' at end of pattern (must be followed by a character)");
 
597
   end Raise_Backslash_At_End;
 
598
 
 
599
   --  generic...
 
600
   function Wildcard_Match
 
601
     (Pattern      : in String;
 
602
      Text         : in String)
 
603
     return Boolean
 
604
   is
 
605
 
 
606
      procedure Parse_Set
 
607
        (Pattern      : in     String;
 
608
         Set          :    out ASM.Character_Set;
 
609
         Stop         :    out Natural;
 
610
         Inverted     :    out Boolean)
 
611
      is
 
612
         use type ASM.Character_Set;
 
613
 
 
614
         Lower : Character;
 
615
         Start : Natural;
 
616
      begin
 
617
         Set := Null_Set;
 
618
         if Pattern'Last <= Pattern'First + 1 then
 
619
            --  Even if it is "[]", it is unterminated!
 
620
            Raise_Unterminated_Set (Pattern);
 
621
         end if;
 
622
         Start := Pattern'First + 1;
 
623
         Inverted :=
 
624
           Set_Inverter /= No_Set_Inverter and then
 
625
           Pattern (Start) = Set_Inverter;
 
626
         if Inverted then Start := Start + 1; end if;
 
627
         --  Go looking for the end of the set.
 
628
         if Start <= Pattern'Last and then Pattern (Start) = ']' then
 
629
            --  This ']' is to be part of the set!
 
630
            Stop := Start + 1;
 
631
         else
 
632
            Stop := Start;
 
633
         end if;
 
634
         Stop := First_Index (Pattern (Stop .. Pattern'Last), ']');
 
635
         if Stop <= Start then
 
636
            Raise_Unterminated_Set (Pattern);
 
637
         end if;
 
638
         --  The set is defined by 'Pattern (Start .. Stop-1)'
 
639
         while Start < Stop loop
 
640
            Lower := Pattern (Start);
 
641
            Start := Start + 1;
 
642
            if Pattern (Start) = '-' and then (Start + 1 < Stop) then
 
643
               --  We have a range.
 
644
               Start := Start + 1;
 
645
               if Pattern (Start) < Lower then
 
646
                  Ada.Exceptions.Raise_Exception
 
647
                    (Illegal_Pattern'Identity,
 
648
                     "Upper bound of range in character set is smaller " &
 
649
                     "than lower bound: '" &
 
650
                     Lower & '-' & Pattern (Start) & "'");
 
651
               end if;
 
652
               Set :=
 
653
                 Set or
 
654
                 ASM.To_Set (ASM.Character_Range'(Lower, Pattern (Start)));
 
655
               Start := Start + 1;
 
656
            else
 
657
               Set :=
 
658
                 Set or ASM.To_Set (ASM.Character_Range'(Lower, Lower));
 
659
            end if;
 
660
         end loop;
 
661
      end Parse_Set;
 
662
 
 
663
      Match_Impossible : exception;
 
664
 
 
665
      function Internal_Match
 
666
        (Pattern      : in String;
 
667
         Text         : in String)
 
668
        return Boolean
 
669
      is
 
670
         Pattern_I : Natural := Pattern'First;
 
671
         Text_I    : Natural := Text'First;
 
672
         Switch    : Character;
 
673
      begin
 
674
         while Text_I <= Text'Last loop
 
675
            if (Pattern_I > Pattern'Last) then
 
676
               --  The text is not yet exhausted, hence it can't match!
 
677
               return False;
 
678
            end if;
 
679
            Switch := Pattern (Pattern_I);
 
680
            if Has_Escape and then Switch = '\' then
 
681
               --  Literal match with next pattern character
 
682
               if Pattern_I = Pattern'Last then
 
683
                  Raise_Backslash_At_End;
 
684
               end if;
 
685
               Pattern_I := Pattern_I + 1;
 
686
               if Pattern (Pattern_I) /= Text (Text_I) then
 
687
                  return False;
 
688
               end if;
 
689
               Pattern_I := Pattern_I + 1;
 
690
               Text_I    := Text_I + 1;
 
691
            elsif Has_Char_Set and then Switch = '[' then
 
692
               --  Character set
 
693
               declare
 
694
                  Set      : ASM.Character_Set;
 
695
                  Inverted : Boolean;
 
696
               begin
 
697
                  Parse_Set (Pattern (Pattern_I .. Pattern'Last),
 
698
                             Set, Pattern_I, Inverted);
 
699
                  if Inverted = ASM.Is_In (Text (Text_I), Set) then
 
700
                     return False;
 
701
                  end if;
 
702
               end;
 
703
               Pattern_I := Pattern_I + 1;
 
704
               Text_I    := Text_I + 1;
 
705
            elsif Switch = Any_One then
 
706
               if Zero_Or_One then
 
707
                  --  Null matches are OK!
 
708
                  declare
 
709
                     Result : Boolean;
 
710
                  begin
 
711
                     --  First try the null match, i.e. advance the
 
712
                     --  pattern, but not the text.
 
713
                     Result :=
 
714
                       Internal_Match
 
715
                         (Pattern (Pattern_I + 1 .. Pattern'Last),
 
716
                          Text (Text_I .. Text'Last));
 
717
                     if Result then return Result; end if;
 
718
                     --  No match: try the any-one match: advance both.
 
719
                  end; --  block
 
720
               end if;
 
721
               --  Match any character.
 
722
               Pattern_I := Pattern_I + 1;
 
723
               Text_I    := Text_I + 1;
 
724
            elsif Switch = Zero_Or_More then
 
725
               --  First, collate sequences of '*'s (and maybe '?'s).
 
726
               while Pattern_I < Pattern'Last and then
 
727
                     (Pattern (Pattern_I + 1) = Zero_Or_More or
 
728
                      (Zero_Or_One and then
 
729
                       Pattern (Pattern_I + 1) = Any_One))
 
730
               loop
 
731
                  Pattern_I := Pattern_I + 1;
 
732
               end loop;
 
733
               --  '*' at the end of the pattern matches anything:
 
734
               if Pattern_I >= Pattern'Last then return True; end if;
 
735
               --  Find next possible match, if any.
 
736
               declare
 
737
                  Next : Natural;
 
738
                  P_I  : constant Natural := Pattern_I + 1;
 
739
               begin
 
740
                  if Pattern (P_I) = Any_One then
 
741
                     --  Can match any one character: don't skip.
 
742
                     Next := Text_I;
 
743
                  elsif Has_Char_Set and then Pattern (P_I) = '[' then
 
744
                     --  Skip to the next character matching the set
 
745
                     declare
 
746
                        Set      : ASM.Character_Set;
 
747
                        Dummy    : Natural;
 
748
                        Inverted : Boolean;
 
749
                     begin
 
750
                        Parse_Set (Pattern (P_I .. Pattern'Last),
 
751
                                   Set, Dummy, Inverted);
 
752
                        if Inverted then
 
753
                           Next := ASF.Index (Text (Text_I .. Text'Last),
 
754
                                              Set, Ada.Strings.Outside);
 
755
                        else
 
756
                           Next := ASF.Index (Text (Text_I .. Text'Last),
 
757
                                              Set, Ada.Strings.Inside);
 
758
                        end if;
 
759
                     end; --  block
 
760
                  else
 
761
                     --  Skip ahead to the next matching character
 
762
                     declare
 
763
                        Ch : Character := Pattern (P_I);
 
764
                     begin
 
765
                        if Has_Escape and then Ch = '\' then
 
766
                           if P_I = Pattern'Last then
 
767
                              Raise_Backslash_At_End;
 
768
                           end if;
 
769
                           Ch := Pattern (P_I + 1);
 
770
                        end if;
 
771
                        Next :=
 
772
                          First_Index (Text (Text_I .. Text'Last), Ch);
 
773
                     end;
 
774
                  end if;
 
775
                  if Next = 0 then raise Match_Impossible; end if;
 
776
                  --  No match was possible, so abort the whole thing.
 
777
                  --  (This can be done safely because any other match
 
778
                  --  for any previous '*' could only require an even
 
779
                  --  later match of the character following the current
 
780
                  --  '*'-sequence - but we know that this is impossible.
 
781
                  --  Therefore, any other matching for previous '*'s is
 
782
                  --  bound to fail and hence we may give up. Without
 
783
                  --  this, the algorithm would have quadratic behaviour
 
784
                  --  in some failure cases, e.g. the "-adobe" negative
 
785
                  --  case in the test program.)
 
786
                  declare
 
787
                     Result : Boolean;
 
788
                  begin
 
789
                     Result :=
 
790
                       Internal_Match (Pattern (P_I .. Pattern'Last),
 
791
                                       Text (Next .. Text'Last));
 
792
                     --  This recursion is limited by the number of '*'
 
793
                     --  sequences in the pattern.
 
794
                     if Result then return Result; end if;
 
795
                  end;
 
796
                  --  This star couldn't match this minimal text
 
797
                  --  sequence; try to extend it. Note: 'Pattern_I' has
 
798
                  --  not been incremented here.
 
799
                  Text_I := Next + 1;
 
800
               end; --  block
 
801
            else
 
802
               --  Literal match between the pattern and the text
 
803
               if Pattern (Pattern_I) /= Text (Text_I) then
 
804
                  return False;
 
805
               end if;
 
806
               Pattern_I := Pattern_I + 1;
 
807
               Text_I    := Text_I + 1;
 
808
            end if;
 
809
         end loop;
 
810
         --  Skip remaining '*'s (and maybe '?'s) at the end of the pattern.
 
811
         while Pattern_I <= Pattern'Last and then
 
812
               (Pattern (Pattern_I) = Zero_Or_More or
 
813
                (Zero_Or_One and then Pattern (Pattern_I) = Any_One))
 
814
         loop
 
815
            Pattern_I := Pattern_I + 1;
 
816
         end loop;
 
817
         return Pattern_I > Pattern'Last;
 
818
      end Internal_Match;
 
819
 
 
820
   begin --  Match_G
 
821
      return Internal_Match (Pattern, Text);
 
822
   exception
 
823
      when Match_Impossible =>
 
824
         return False;
 
825
   end Wildcard_Match;
 
826
 
 
827
   function Instance is new Wildcard_Match;
 
828
 
 
829
   function Match
 
830
     (Pattern : in String;
 
831
      Text    : in String)
 
832
     return Boolean
 
833
     renames Instance;
 
834
 
 
835
end Util.Strings;