1 ------------------------------------------------------------------------------
3 -- GNAT RUN-TIME COMPONENTS --
5 -- A D A . S T R I N G S . S E A R C H --
9 -- Copyright (C) 1992-2024, Free Software Foundation, Inc. --
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. --
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. --
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/>. --
27 -- GNAT was originally developed by the GNAT team at New York University. --
28 -- Extensive contributions were provided by Ada Core Technologies Inc. --
30 ------------------------------------------------------------------------------
32 -- Note: This code is derived from the ADAR.CSH public domain Ada 83
33 -- versions of the Appendix C string handling packages (code extracted
34 -- from Ada.Strings.Fixed). A significant change is that we optimize the
35 -- case of identity mappings for Count and Index, and also Index_Non_Blank
36 -- is specialized (rather than using the general Index routine).
38 -- Ghost code, loop invariants and assertions in this unit are meant for
39 -- analysis only, not for run-time checking, as it would be too costly
40 -- otherwise. This is enforced by setting the assertion policy to Ignore.
42 pragma Assertion_Policy
(Ghost
=> Ignore
,
43 Loop_Invariant
=> Ignore
,
46 with Ada
.Strings
.Maps
; use Ada
.Strings
.Maps
;
47 with System
; use System
;
49 package body Ada
.Strings
.Search
with SPARK_Mode
is
51 -----------------------
52 -- Local Subprograms --
53 -----------------------
57 Set
: Maps
.Character_Set
;
58 Test
: Membership
) return Boolean;
59 pragma Inline
(Belongs
);
60 -- Determines if the given element is in (Test = Inside) or not in
61 -- (Test = Outside) the given character set.
69 Set
: Maps
.Character_Set
;
70 Test
: Membership
) return Boolean
72 (if Test
= Inside
then
74 else not (Is_In
(Element
, Set
)));
83 Mapping
: Maps
.Character_Mapping
:= Maps
.Identity
) return Natural
85 PL1
: constant Integer := Pattern
'Length - 1;
94 -- Isolating the null string case to ensure Source'First, Source'Last in
102 Ind
:= Source
'First - 1;
106 if Is_Identity
(Mapping
) then
107 while Ind
< Source
'Last - PL1
loop
109 if Pattern
= Source
(Ind
.. Ind
+ PL1
) then
114 pragma Loop_Invariant
(Num
<= Ind
- (Source
'First - 1));
115 pragma Loop_Invariant
(Ind
>= Source
'First);
116 pragma Loop_Variant
(Increases
=> Ind
);
122 while Ind
< Source
'Last - PL1
loop
124 for K
in Pattern
'Range loop
125 if Pattern
(K
) /= Value
(Mapping
,
126 Source
(Ind
+ (K
- Pattern
'First)))
128 pragma Assert
(not (Match
(Source
, Pattern
, Mapping
, Ind
)));
132 pragma Loop_Invariant
133 (for all J
in Pattern
'First .. K
=>
134 Pattern
(J
) = Value
(Mapping
,
135 Source
(Ind
+ (J
- Pattern
'First))));
138 pragma Assert
(Match
(Source
, Pattern
, Mapping
, Ind
));
144 pragma Loop_Invariant
(Num
<= Ind
- (Source
'First - 1));
145 pragma Loop_Invariant
(Ind
>= Source
'First);
146 pragma Loop_Variant
(Increases
=> Ind
);
158 Mapping
: Maps
.Character_Mapping_Function
) return Natural
160 PL1
: constant Integer := Pattern
'Length - 1;
169 -- Isolating the null string case to ensure Source'First, Source'Last in
176 -- Check for null pointer in case checks are off
178 if Mapping
= null then
179 raise Constraint_Error
;
183 Ind
:= Source
'First - 1;
184 while Ind
< Source
'Last - PL1
loop
186 for K
in Pattern
'Range loop
187 if Pattern
(K
) /= Mapping
(Source
(Ind
+ (K
- Pattern
'First))) then
188 pragma Annotate
(GNATprove
, False_Positive
,
189 "call via access-to-subprogram",
190 "function Mapping must always terminate");
191 pragma Assert
(not (Match
(Source
, Pattern
, Mapping
, Ind
)));
195 pragma Loop_Invariant
196 (for all J
in Pattern
'First .. K
=>
197 Pattern
(J
) = Mapping
(Source
(Ind
+ (J
- Pattern
'First))));
198 pragma Annotate
(GNATprove
, False_Positive
,
199 "call via access-to-subprogram",
200 "function Mapping must always terminate");
203 pragma Assert
(Match
(Source
, Pattern
, Mapping
, Ind
));
209 pragma Loop_Invariant
(Num
<= Ind
- (Source
'First - 1));
210 pragma Loop_Invariant
(Ind
>= Source
'First);
211 pragma Loop_Variant
(Increases
=> Ind
);
219 Set
: Maps
.Character_Set
) return Natural
224 for J
in Source
'Range loop
225 pragma Loop_Invariant
(N
<= J
- Source
'First);
226 if Is_In
(Source
(J
), Set
) then
240 Set
: Maps
.Character_Set
;
243 First
: out Positive;
247 -- AI05-031: Raise Index error if Source non-empty and From not in range
249 if Source
'Length /= 0 and then From
not in Source
'Range then
253 -- If Source is the empty string, From may still be out of its
254 -- range. The following ensures that in all cases there is no
255 -- possible erroneous access to a non-existing character.
257 for J
in Integer'Max (From
, Source
'First) .. Source
'Last loop
258 if Belongs
(Source
(J
), Set
, Test
) then
261 if J
< Source
'Last then
262 for K
in J
+ 1 .. Source
'Last loop
263 if not Belongs
(Source
(K
), Set
, Test
) then
268 pragma Loop_Invariant
269 (for all L
in J
.. K
=>
270 Belongs
(Source
(L
), Set
, Test
));
274 -- Here if J indexes first char of token, and all chars after J
281 pragma Loop_Invariant
282 (for all K
in Integer'Max (From
, Source
'First) .. J
=>
283 not (Belongs
(Source
(K
), Set
, Test
)));
286 -- Here if no token found
294 Set
: Maps
.Character_Set
;
296 First
: out Positive;
300 for J
in Source
'Range loop
301 if Belongs
(Source
(J
), Set
, Test
) then
304 if J
< Source
'Last then
305 for K
in J
+ 1 .. Source
'Last loop
306 if not Belongs
(Source
(K
), Set
, Test
) then
311 pragma Loop_Invariant
312 (for all L
in J
.. K
=>
313 Belongs
(Source
(L
), Set
, Test
));
317 -- Here if J indexes first char of token, and all chars after J
324 pragma Loop_Invariant
325 (for all K
in Source
'First .. J
=>
326 not (Belongs
(Source
(K
), Set
, Test
)));
329 -- Here if no token found
331 -- RM 2005 A.4.3 (68/1) specifies that an exception must be raised if
332 -- Source'First is not positive and is assigned to First. Formulation
333 -- is slightly different in RM 2012, but the intent seems similar, so
334 -- we check explicitly for that condition.
336 if Source
'First not in Positive then
337 raise Constraint_Error
;
340 First
:= Source
'First;
352 Going
: Direction
:= Forward
;
353 Mapping
: Maps
.Character_Mapping
:= Maps
.Identity
) return Natural
355 PL1
: constant Integer := Pattern
'Length - 1;
362 -- If Pattern is longer than Source, it can't be found
364 if Pattern
'Length > Source
'Length then
370 if Going
= Forward
then
372 -- Unmapped forward case
374 if Is_Identity
(Mapping
) then
375 for Ind
in Source
'First .. Source
'Last - PL1
loop
376 if Pattern
= Source
(Ind
.. Ind
+ PL1
) then
377 pragma Assert
(Match
(Source
, Pattern
, Mapping
, Ind
));
381 pragma Loop_Invariant
382 (for all J
in Source
'First .. Ind
=>
383 not (Match
(Source
, Pattern
, Mapping
, J
)));
386 -- Mapped forward case
389 for Ind
in Source
'First .. Source
'Last - PL1
loop
390 for K
in Pattern
'Range loop
391 if Pattern
(K
) /= Value
(Mapping
,
392 Source
(Ind
+ (K
- Pattern
'First)))
397 pragma Loop_Invariant
398 (for all J
in Pattern
'First .. K
=>
399 Pattern
(J
) = Value
(Mapping
,
400 Source
(Ind
+ (J
- Pattern
'First))));
403 pragma Assert
(Match
(Source
, Pattern
, Mapping
, Ind
));
407 pragma Loop_Invariant
408 (for all J
in Source
'First .. Ind
=>
409 not (Match
(Source
, Pattern
, Mapping
, J
)));
417 -- Unmapped backward case
419 if Is_Identity
(Mapping
) then
420 for Ind
in reverse Source
'First .. Source
'Last - PL1
loop
421 if Pattern
= Source
(Ind
.. Ind
+ PL1
) then
422 pragma Assert
(Match
(Source
, Pattern
, Mapping
, Ind
));
426 pragma Loop_Invariant
427 (for all J
in Ind
.. Source
'Last - PL1
=>
428 not (Match
(Source
, Pattern
, Mapping
, J
)));
431 -- Mapped backward case
434 for Ind
in reverse Source
'First .. Source
'Last - PL1
loop
435 for K
in Pattern
'Range loop
436 if Pattern
(K
) /= Value
(Mapping
,
437 Source
(Ind
+ (K
- Pattern
'First)))
442 pragma Loop_Invariant
443 (for all J
in Pattern
'First .. K
=>
444 Pattern
(J
) = Value
(Mapping
,
445 Source
(Ind
+ (J
- Pattern
'First))));
448 pragma Assert
(Match
(Source
, Pattern
, Mapping
, Ind
));
452 pragma Loop_Invariant
453 (for all J
in Ind
.. Source
'Last - PL1
=>
454 not (Match
(Source
, Pattern
, Mapping
, J
)));
460 -- Fall through if no match found. Note that the loops are skipped
461 -- completely in the case of the pattern being longer than the source.
469 Going
: Direction
:= Forward
;
470 Mapping
: Maps
.Character_Mapping_Function
) return Natural
472 PL1
: constant Integer := Pattern
'Length - 1;
478 -- Check for null pointer in case checks are off
480 if Mapping
= null then
481 raise Constraint_Error
;
484 -- If Pattern longer than Source it can't be found
486 if Pattern
'Length > Source
'Length then
492 if Going
= Forward
then
493 for Ind
in Source
'First .. Source
'Last - PL1
loop
494 for K
in Pattern
'Range loop
495 if Pattern
(K
) /= Mapping
.all
496 (Source
(Ind
+ (K
- Pattern
'First)))
498 pragma Annotate
(GNATprove
, False_Positive
,
499 "call via access-to-subprogram",
500 "function Mapping must always terminate");
504 pragma Loop_Invariant
505 (for all J
in Pattern
'First .. K
=>
506 Pattern
(J
) = Mapping
(Source
(Ind
+ (J
- Pattern
'First))));
507 pragma Annotate
(GNATprove
, False_Positive
,
508 "call via access-to-subprogram",
509 "function Mapping must always terminate");
512 pragma Assert
(Match
(Source
, Pattern
, Mapping
, Ind
));
516 pragma Loop_Invariant
517 (for all J
in Source
'First .. Ind
=>
518 not (Match
(Source
, Pattern
, Mapping
, J
)));
525 for Ind
in reverse Source
'First .. Source
'Last - PL1
loop
526 for K
in Pattern
'Range loop
527 if Pattern
(K
) /= Mapping
.all
528 (Source
(Ind
+ (K
- Pattern
'First)))
530 pragma Annotate
(GNATprove
, False_Positive
,
531 "call via access-to-subprogram",
532 "function Mapping must always terminate");
536 pragma Loop_Invariant
537 (for all J
in Pattern
'First .. K
=>
538 Pattern
(J
) = Mapping
(Source
(Ind
+ (J
- Pattern
'First))));
539 pragma Annotate
(GNATprove
, False_Positive
,
540 "call via access-to-subprogram",
541 "function Mapping must always terminate");
547 pragma Loop_Invariant
548 (for all J
in Ind
.. Source
'Last - PL1
=>
549 not (Match
(Source
, Pattern
, Mapping
, J
)));
554 -- Fall through if no match found. Note that the loops are skipped
555 -- completely in the case of the pattern being longer than the source.
562 Set
: Maps
.Character_Set
;
563 Test
: Membership
:= Inside
;
564 Going
: Direction
:= Forward
) return Natural
569 if Going
= Forward
then
570 for J
in Source
'Range loop
571 if Belongs
(Source
(J
), Set
, Test
) then
575 pragma Loop_Invariant
576 (for all C
of Source
(Source
'First .. J
) =>
577 not (Belongs
(C
, Set
, Test
)));
583 for J
in reverse Source
'Range loop
584 if Belongs
(Source
(J
), Set
, Test
) then
588 pragma Loop_Invariant
589 (for all C
of Source
(J
.. Source
'Last) =>
590 not (Belongs
(C
, Set
, Test
)));
594 -- Fall through if no match
603 Going
: Direction
:= Forward
;
604 Mapping
: Maps
.Character_Mapping
:= Maps
.Identity
) return Natural
607 PL1
: constant Integer := Pattern
'Length - 1;
610 -- AI05-056: If source is empty result is always zero
612 if Source
'Length = 0 then
615 elsif Going
= Forward
then
616 if From
< Source
'First then
621 Index
(Source
(From
.. Source
'Last), Pattern
, Forward
, Mapping
);
623 (if (for some J
in From
.. Source
'Last - PL1
=>
624 Match
(Source
, Pattern
, Mapping
, J
))
625 then Result
in From
.. Source
'Last - PL1
626 and then Match
(Source
, Pattern
, Mapping
, Result
)
630 if From
> Source
'Last then
635 Index
(Source
(Source
'First .. From
), Pattern
, Backward
, Mapping
);
637 (if (for some J
in Source
'First .. From
- PL1
=>
638 Match
(Source
, Pattern
, Mapping
, J
))
639 then Result
in Source
'First .. From
- PL1
640 and then Match
(Source
, Pattern
, Mapping
, Result
)
651 Going
: Direction
:= Forward
;
652 Mapping
: Maps
.Character_Mapping_Function
) return Natural
656 -- AI05-056: If source is empty result is always zero
658 if Source
'Length = 0 then
661 elsif Going
= Forward
then
662 if From
< Source
'First then
667 (Source
(From
.. Source
'Last), Pattern
, Forward
, Mapping
);
670 if From
> Source
'Last then
675 (Source
(Source
'First .. From
), Pattern
, Backward
, Mapping
);
681 Set
: Maps
.Character_Set
;
683 Test
: Membership
:= Inside
;
684 Going
: Direction
:= Forward
) return Natural
688 -- AI05-056 : if source is empty result is always 0.
690 if Source
'Length = 0 then
693 elsif Going
= Forward
then
694 if From
< Source
'First then
699 Index
(Source
(From
.. Source
'Last), Set
, Test
, Forward
);
702 if From
> Source
'Last then
707 Index
(Source
(Source
'First .. From
), Set
, Test
, Backward
);
711 ---------------------
712 -- Index_Non_Blank --
713 ---------------------
715 function Index_Non_Blank
717 Going
: Direction
:= Forward
) return Natural
720 if Going
= Forward
then
721 for J
in Source
'Range loop
722 if Source
(J
) /= ' ' then
726 pragma Loop_Invariant
727 (for all C
of Source
(Source
'First .. J
) => C
= ' ');
730 else -- Going = Backward
731 for J
in reverse Source
'Range loop
732 if Source
(J
) /= ' ' then
736 pragma Loop_Invariant
737 (for all C
of Source
(J
.. Source
'Last) => C
= ' ');
741 -- Fall through if no match
746 function Index_Non_Blank
749 Going
: Direction
:= Forward
) return Natural
753 -- For equivalence with Index, if Source is empty the result is 0
755 if Source
'Length = 0 then
759 if Going
= Forward
then
760 if From
< Source
'First then
765 Index_Non_Blank
(Source
(From
.. Source
'Last), Forward
);
768 if From
> Source
'Last then
773 Index_Non_Blank
(Source
(Source
'First .. From
), Backward
);
778 (Mapping
: Maps
.Character_Mapping
) return Boolean
779 with SPARK_Mode
=> Off
782 return Mapping
'Address = Maps
.Identity
'Address;
785 end Ada
.Strings
.Search
;