1------------------------------------------------------------------------------
2--                                                                          --
3--                         GNAT RUN-TIME COMPONENTS                         --
4--                                                                          --
5--              A D A . S T R I N G S . W I D E _ S E A R C H               --
6--                                                                          --
7--                                 B o d y                                  --
8--                                                                          --
9--          Copyright (C) 1992-2015, Free Software Foundation, Inc.         --
10--                                                                          --
11-- GNAT is free software;  you can  redistribute it  and/or modify it under --
12-- terms of the  GNU General Public License as published  by the Free Soft- --
13-- ware  Foundation;  either version 3,  or (at your option) any later ver- --
14-- sion.  GNAT is distributed in the hope that it will be useful, but WITH- --
15-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
16-- or FITNESS FOR A PARTICULAR PURPOSE.                                     --
17--                                                                          --
18-- As a special exception under Section 7 of GPL version 3, you are granted --
19-- additional permissions described in the GCC Runtime Library Exception,   --
20-- version 3.1, as published by the Free Software Foundation.               --
21--                                                                          --
22-- You should have received a copy of the GNU General Public License and    --
23-- a copy of the GCC Runtime Library Exception along with this program;     --
24-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
25-- <http://www.gnu.org/licenses/>.                                          --
26--                                                                          --
27-- GNAT was originally developed  by the GNAT team at  New York University. --
28-- Extensive contributions were provided by Ada Core Technologies Inc.      --
29--                                                                          --
30------------------------------------------------------------------------------
31
32with Ada.Strings.Wide_Maps; use Ada.Strings.Wide_Maps;
33with System;                use System;
34
35package body Ada.Strings.Wide_Search is
36
37   -----------------------
38   -- Local Subprograms --
39   -----------------------
40
41   function Belongs
42     (Element : Wide_Character;
43      Set     : Wide_Maps.Wide_Character_Set;
44      Test    : Membership) return Boolean;
45   pragma Inline (Belongs);
46   --  Determines if the given element is in (Test = Inside) or not in
47   --  (Test = Outside) the given character set.
48
49   -------------
50   -- Belongs --
51   -------------
52
53   function Belongs
54     (Element : Wide_Character;
55      Set     : Wide_Maps.Wide_Character_Set;
56      Test    : Membership) return Boolean
57   is
58   begin
59      if Test = Inside then
60         return Is_In (Element, Set);
61      else
62         return not Is_In (Element, Set);
63      end if;
64   end Belongs;
65
66   -----------
67   -- Count --
68   -----------
69
70   function Count
71     (Source  : Wide_String;
72      Pattern : Wide_String;
73      Mapping : Wide_Maps.Wide_Character_Mapping := Wide_Maps.Identity)
74      return Natural
75   is
76      PL1 : constant Integer := Pattern'Length - 1;
77      Num : Natural;
78      Ind : Natural;
79      Cur : Natural;
80
81   begin
82      if Pattern = "" then
83         raise Pattern_Error;
84      end if;
85
86      Num := 0;
87      Ind := Source'First;
88
89      --  Unmapped case
90
91      if Mapping'Address = Wide_Maps.Identity'Address then
92         while Ind <= Source'Last - PL1 loop
93            if Pattern = Source (Ind .. Ind + PL1) then
94               Num := Num + 1;
95               Ind := Ind + Pattern'Length;
96            else
97               Ind := Ind + 1;
98            end if;
99         end loop;
100
101      --  Mapped case
102
103      else
104         while Ind <= Source'Last - PL1 loop
105            Cur := Ind;
106            for K in Pattern'Range loop
107               if Pattern (K) /= Value (Mapping, Source (Cur)) then
108                  Ind := Ind + 1;
109                  goto Cont;
110               else
111                  Cur := Cur + 1;
112               end if;
113            end loop;
114
115            Num := Num + 1;
116            Ind := Ind + Pattern'Length;
117
118         <<Cont>>
119            null;
120         end loop;
121      end if;
122
123      --  Return result
124
125      return Num;
126   end Count;
127
128   function Count
129     (Source  : Wide_String;
130      Pattern : Wide_String;
131      Mapping : Wide_Maps.Wide_Character_Mapping_Function) return Natural
132   is
133      PL1 : constant Integer := Pattern'Length - 1;
134      Num : Natural;
135      Ind : Natural;
136      Cur : Natural;
137
138   begin
139      if Pattern = "" then
140         raise Pattern_Error;
141      end if;
142
143      --  Check for null pointer in case checks are off
144
145      if Mapping = null then
146         raise Constraint_Error;
147      end if;
148
149      Num := 0;
150      Ind := Source'First;
151      while Ind <= Source'Last - PL1 loop
152         Cur := Ind;
153         for K in Pattern'Range loop
154            if Pattern (K) /= Mapping (Source (Cur)) then
155               Ind := Ind + 1;
156               goto Cont;
157            else
158               Cur := Cur + 1;
159            end if;
160         end loop;
161
162         Num := Num + 1;
163         Ind := Ind + Pattern'Length;
164
165      <<Cont>>
166         null;
167      end loop;
168
169      return Num;
170   end Count;
171
172   function Count
173     (Source : Wide_String;
174      Set    : Wide_Maps.Wide_Character_Set) return Natural
175   is
176      N : Natural := 0;
177
178   begin
179      for J in Source'Range loop
180         if Is_In (Source (J), Set) then
181            N := N + 1;
182         end if;
183      end loop;
184
185      return N;
186   end Count;
187
188   ----------------
189   -- Find_Token --
190   ----------------
191
192   procedure Find_Token
193     (Source : Wide_String;
194      Set    : Wide_Maps.Wide_Character_Set;
195      From   : Positive;
196      Test   : Membership;
197      First  : out Positive;
198      Last   : out Natural)
199   is
200   begin
201      for J in From .. Source'Last loop
202         if Belongs (Source (J), Set, Test) then
203            First := J;
204
205            for K in J + 1 .. Source'Last loop
206               if not Belongs (Source (K), Set, Test) then
207                  Last := K - 1;
208                  return;
209               end if;
210            end loop;
211
212            --  Here if J indexes first char of token, and all chars after J
213            --  are in the token.
214
215            Last := Source'Last;
216            return;
217         end if;
218      end loop;
219
220      --  Here if no token found
221
222      First := From;
223      Last  := 0;
224   end Find_Token;
225
226   procedure Find_Token
227     (Source : Wide_String;
228      Set    : Wide_Maps.Wide_Character_Set;
229      Test   : Membership;
230      First  : out Positive;
231      Last   : out Natural)
232   is
233   begin
234      for J in Source'Range loop
235         if Belongs (Source (J), Set, Test) then
236            First := J;
237
238            for K in J + 1 .. Source'Last loop
239               if not Belongs (Source (K), Set, Test) then
240                  Last := K - 1;
241                  return;
242               end if;
243            end loop;
244
245            --  Here if J indexes first char of token, and all chars after J
246            --  are in the token.
247
248            Last := Source'Last;
249            return;
250         end if;
251      end loop;
252
253      --  Here if no token found
254
255      --  RM 2005 A.4.3 (68/1) specifies that an exception must be raised if
256      --  Source'First is not positive and is assigned to First. Formulation
257      --  is slightly different in RM 2012, but the intent seems similar, so
258      --  we check explicitly for that condition.
259
260      if Source'First not in Positive then
261         raise Constraint_Error;
262
263      else
264         First := Source'First;
265         Last  := 0;
266      end if;
267   end Find_Token;
268
269   -----------
270   -- Index --
271   -----------
272
273   function Index
274     (Source  : Wide_String;
275      Pattern : Wide_String;
276      Going   : Direction := Forward;
277      Mapping : Wide_Maps.Wide_Character_Mapping := Wide_Maps.Identity)
278      return Natural
279   is
280      PL1 : constant Integer := Pattern'Length - 1;
281      Cur : Natural;
282
283      Ind : Integer;
284      --  Index for start of match check. This can be negative if the pattern
285      --  length is greater than the string length, which is why this variable
286      --  is Integer instead of Natural. In this case, the search loops do not
287      --  execute at all, so this Ind value is never used.
288
289   begin
290      if Pattern = "" then
291         raise Pattern_Error;
292      end if;
293
294      --  Forwards case
295
296      if Going = Forward then
297         Ind := Source'First;
298
299         --  Unmapped forward case
300
301         if Mapping'Address = Wide_Maps.Identity'Address then
302            for J in 1 .. Source'Length - PL1 loop
303               if Pattern = Source (Ind .. Ind + PL1) then
304                  return Ind;
305               else
306                  Ind := Ind + 1;
307               end if;
308            end loop;
309
310         --  Mapped forward case
311
312         else
313            for J in 1 .. Source'Length - PL1 loop
314               Cur := Ind;
315
316               for K in Pattern'Range loop
317                  if Pattern (K) /= Value (Mapping, Source (Cur)) then
318                     goto Cont1;
319                  else
320                     Cur := Cur + 1;
321                  end if;
322               end loop;
323
324               return Ind;
325
326            <<Cont1>>
327               Ind := Ind + 1;
328            end loop;
329         end if;
330
331      --  Backwards case
332
333      else
334         --  Unmapped backward case
335
336         Ind := Source'Last - PL1;
337
338         if Mapping'Address = Wide_Maps.Identity'Address then
339            for J in reverse 1 .. Source'Length - PL1 loop
340               if Pattern = Source (Ind .. Ind + PL1) then
341                  return Ind;
342               else
343                  Ind := Ind - 1;
344               end if;
345            end loop;
346
347         --  Mapped backward case
348
349         else
350            for J in reverse 1 .. Source'Length - PL1 loop
351               Cur := Ind;
352
353               for K in Pattern'Range loop
354                  if Pattern (K) /= Value (Mapping, Source (Cur)) then
355                     goto Cont2;
356                  else
357                     Cur := Cur + 1;
358                  end if;
359               end loop;
360
361               return Ind;
362
363            <<Cont2>>
364               Ind := Ind - 1;
365            end loop;
366         end if;
367      end if;
368
369      --  Fall through if no match found. Note that the loops are skipped
370      --  completely in the case of the pattern being longer than the source.
371
372      return 0;
373   end Index;
374
375   function Index
376     (Source  : Wide_String;
377      Pattern : Wide_String;
378      Going   : Direction := Forward;
379      Mapping : Wide_Maps.Wide_Character_Mapping_Function) return Natural
380   is
381      PL1 : constant Integer := Pattern'Length - 1;
382      Ind : Natural;
383      Cur : Natural;
384
385   begin
386      if Pattern = "" then
387         raise Pattern_Error;
388      end if;
389
390      --  Check for null pointer in case checks are off
391
392      if Mapping = null then
393         raise Constraint_Error;
394      end if;
395
396      --  If Pattern longer than Source it can't be found
397
398      if Pattern'Length > Source'Length then
399         return 0;
400      end if;
401
402      --  Forwards case
403
404      if Going = Forward then
405         Ind := Source'First;
406         for J in 1 .. Source'Length - PL1 loop
407            Cur := Ind;
408
409            for K in Pattern'Range loop
410               if Pattern (K) /= Mapping.all (Source (Cur)) then
411                  goto Cont1;
412               else
413                  Cur := Cur + 1;
414               end if;
415            end loop;
416
417            return Ind;
418
419         <<Cont1>>
420            Ind := Ind + 1;
421         end loop;
422
423      --  Backwards case
424
425      else
426         Ind := Source'Last - PL1;
427         for J in reverse 1 .. Source'Length - PL1 loop
428            Cur := Ind;
429
430            for K in Pattern'Range loop
431               if Pattern (K) /= Mapping.all (Source (Cur)) then
432                  goto Cont2;
433               else
434                  Cur := Cur + 1;
435               end if;
436            end loop;
437
438            return Ind;
439
440         <<Cont2>>
441            Ind := Ind - 1;
442         end loop;
443      end if;
444
445      --  Fall through if no match found. Note that the loops are skipped
446      --  completely in the case of the pattern being longer than the source.
447
448      return 0;
449   end Index;
450
451   function Index
452     (Source : Wide_String;
453      Set    : Wide_Maps.Wide_Character_Set;
454      Test   : Membership := Inside;
455      Going  : Direction  := Forward) return Natural
456   is
457   begin
458      --  Forwards case
459
460      if Going = Forward then
461         for J in Source'Range loop
462            if Belongs (Source (J), Set, Test) then
463               return J;
464            end if;
465         end loop;
466
467      --  Backwards case
468
469      else
470         for J in reverse Source'Range loop
471            if Belongs (Source (J), Set, Test) then
472               return J;
473            end if;
474         end loop;
475      end if;
476
477      --  Fall through if no match
478
479      return 0;
480   end Index;
481
482   function Index
483     (Source  : Wide_String;
484      Pattern : Wide_String;
485      From    : Positive;
486      Going   : Direction := Forward;
487      Mapping : Wide_Maps.Wide_Character_Mapping := Wide_Maps.Identity)
488      return Natural
489   is
490   begin
491      if Going = Forward then
492         if From < Source'First then
493            raise Index_Error;
494         end if;
495
496         return
497           Index (Source (From .. Source'Last), Pattern, Forward, Mapping);
498
499      else
500         if From > Source'Last then
501            raise Index_Error;
502         end if;
503
504         return
505           Index (Source (Source'First .. From), Pattern, Backward, Mapping);
506      end if;
507   end Index;
508
509   function Index
510     (Source  : Wide_String;
511      Pattern : Wide_String;
512      From    : Positive;
513      Going   : Direction := Forward;
514      Mapping : Wide_Maps.Wide_Character_Mapping_Function) return Natural
515   is
516   begin
517      if Going = Forward then
518         if From < Source'First then
519            raise Index_Error;
520         end if;
521
522         return Index
523           (Source (From .. Source'Last), Pattern, Forward, Mapping);
524
525      else
526         if From > Source'Last then
527            raise Index_Error;
528         end if;
529
530         return Index
531           (Source (Source'First .. From), Pattern, Backward, Mapping);
532      end if;
533   end Index;
534
535   function Index
536     (Source  : Wide_String;
537      Set     : Wide_Maps.Wide_Character_Set;
538      From    : Positive;
539      Test    : Membership := Inside;
540      Going   : Direction := Forward) return Natural
541   is
542   begin
543      if Going = Forward then
544         if From < Source'First then
545            raise Index_Error;
546         end if;
547
548         return
549           Index (Source (From .. Source'Last), Set, Test, Forward);
550
551      else
552         if From > Source'Last then
553            raise Index_Error;
554         end if;
555
556         return
557           Index (Source (Source'First .. From), Set, Test, Backward);
558      end if;
559   end Index;
560
561   ---------------------
562   -- Index_Non_Blank --
563   ---------------------
564
565   function Index_Non_Blank
566     (Source : Wide_String;
567      Going  : Direction := Forward) return Natural
568   is
569   begin
570      if Going = Forward then
571         for J in Source'Range loop
572            if Source (J) /= Wide_Space then
573               return J;
574            end if;
575         end loop;
576
577      else -- Going = Backward
578         for J in reverse Source'Range loop
579            if Source (J) /= Wide_Space then
580               return J;
581            end if;
582         end loop;
583      end if;
584
585      --  Fall through if no match
586
587      return 0;
588   end Index_Non_Blank;
589
590   function Index_Non_Blank
591     (Source : Wide_String;
592      From   : Positive;
593      Going  : Direction := Forward) return Natural
594   is
595   begin
596      if Going = Forward then
597         if From < Source'First then
598            raise Index_Error;
599         end if;
600
601         return
602           Index_Non_Blank (Source (From .. Source'Last), Forward);
603
604      else
605         if From > Source'Last then
606            raise Index_Error;
607         end if;
608
609         return
610           Index_Non_Blank (Source (Source'First .. From), Backward);
611      end if;
612   end Index_Non_Blank;
613
614end Ada.Strings.Wide_Search;
615