File: pp-buffers.adb

package info (click to toggle)
asis 2019-5
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 13,848 kB
  • sloc: ada: 156,772; makefile: 296; sh: 81; xml: 48; csh: 10
file content (1154 lines) | stat: -rw-r--r-- 36,135 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
------------------------------------------------------------------------------
--                                                                          --
--                            GNAT2XML COMPONENTS                           --
--                                                                          --
--                      G N A T 2 X M L . B U F F E R S                     --
--                                                                          --
--                                 B o d y                                  --
--                                                                          --
--                      Copyright (C) 2013-2016, AdaCore                    --
--                                                                          --
-- Gnat2xml is free software; you can redistribute it and/or modify it      --
-- under terms of the  GNU General Public License  as published by the Free --
-- Software Foundation;  either version 2,  or  (at your option)  any later --
-- version. Gnat2xml is distributed  in the hope  that it will be useful,   --
-- but WITHOUT ANY WARRANTY; without even the implied warranty of MER-      --
-- CHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General  --
-- Public License for more details. You should have received a copy of the  --
-- GNU General Public License distributed with GNAT; see file COPYING. If   --
-- not, write to the Free Software Foundation, 59 Temple Place Suite 330,   --
-- Boston, MA 02111-1307, USA.                                              --
-- The gnat2xml tool was derived from the Avatox sources.                   --
------------------------------------------------------------------------------

pragma Ada_2012;

with GNAT.Decode_String;
with GNAT.OS_Lib; use GNAT.OS_Lib;
with GNAT.Byte_Order_Mark;

package body Pp.Buffers is

   procedure Append_New_Marker (Buf : in out Buffer; Name : W_Char);
   --  Append a new Marker onto To_Markers, making it point to 'point'

   procedure Maybe_Move_Marker (Buf : in out Buffer);
   pragma Inline (Maybe_Move_Marker);
   --  If the first mark in From_Markers is at 'point', move it to the
   --  To_Markers array, adjusting its Position and Flag, to preserve
   --  the invariant.

   procedure Move_Marker (Buf : in out Buffer);
   --  Helper for Maybe_Move_Marker, out of line so we can inline
   --  Maybe_Move_Marker.

   procedure Maybe_Adjust_Marker (Buf : in out Buffer);
   --  See comment in Insert

   procedure Insert_Ada (Buf : in out Buffer; C : W_Char);
   --  Same as Insert_Any, except we don't need to call Maybe_Adjust_Marker

   function Valid_From (Buf : Buffer) return W_Str;
   --  Return the valid portion of Buf.From

   procedure Initialize_Buffer (Buf : in out Buffer);

   procedure Append_New_Marker (Buf : in out Buffer; Name : W_Char) is
   begin
      Append
        (Buf.Markers,
         Marker_Rec'
           (Position => Last_Index (Buf.To) + 1,
            Flag     => Buf.To_Flag,
            Name     => Name));
      Append (Buf.To_Markers, Last_Index (Buf.Markers));
   end Append_New_Marker;

   function At_End (Buf : Buffer) return Boolean is
   begin
      return Result : constant Boolean := Buf.Cur_Char = W_NUL do
         pragma Assert (Result = (Buf.From_First > Last_Index (Buf.From)));
      end return;
   end At_End;

   function At_Beginning (Buf : Buffer) return Boolean is
   begin
      return Is_Empty (Buf.To);
   end At_Beginning;

   function At_Point (Buf : Buffer; Mark : Marker) return Boolean is
      Rec : constant Marker_Rec := Buf.Markers (Mark);

   begin
      return Rec.Position = Last_Index (Buf.To) + 1
        and then Rec.Flag = Buf.To_Flag;
   end At_Point;

   function Char_At (Buf : Buffer; Mark : Marker) return W_Char is
      Rec    : constant Marker_Rec := Buf.Markers (Mark);
      Result : W_Char;

   begin
      if Rec.Flag = Buf.To_Flag then
         if Rec.Position = Last_Index (Buf.To) + 1 then
            Result := Buf.From (Buf.From_First);

         else
            Result := Buf.To (Rec.Position);
         end if;
      else
         Result := Buf.From (Rec.Position);
      end if;
      pragma Assert (Result = Char_At (Buf, Position (Buf, Mark)));
      return Result;
   end Char_At;

   function Char_At (Buf : Buffer; Position : Positive) return W_Char is
   begin
      if Position <= Last_Index (Buf.To) then
         return Buf.To (Position);
      else
         return Buf.From (Position - Last_Index (Buf.To) + Buf.From_First - 1);
      end if;
   end Char_At;

   procedure Clear (Buf : in out Buffer) is
   begin
      pragma Debug (Validate (Buf, "Clear"));

      Clear (Buf.From);
      Clear (Buf.To);
      Clear (Buf.Markers);
      Clear (Buf.From_Markers);
      Clear (Buf.To_Markers);
      Initialize_Buffer (Buf);
   end Clear;

   function Cur (Buf : Buffer) return W_Char is
   begin
      return Result : constant W_Char := Buf.Cur_Char do
         pragma Assert
           (if Buf.From_First > Last_Index (Buf.From) then Result = W_NUL
            else Result = Buf.From (Buf.From_First));
      end return;
   end Cur;

   function Cur_Column (Buf : Buffer) return Positive is
      pragma Assert (False); -- ???Not currently used.
   --  All adjustments of Cur_Column are commented out, except when inserting
   --  Ada source code.
   begin
      return Buf.Cur_Column;
   end Cur_Column;

   procedure Delete_Char (Buf : in out Buffer) is
      From_First : constant Positive := Buf.From_First + 1;
   begin
      Buf.From_First := From_First;
--         if C = NL then
--            Buf.Cur_Column := 1;
--         else
--            Buf.Cur_Column := Buf.Cur_Column + 1;
--         end if;
      Buf.Cur_Char :=
        (if From_First > Last_Index (Buf.From) then W_NUL
         else Buf.From (From_First));
      Maybe_Move_Marker (Buf);
   end Delete_Char;

   procedure Dump_Buf (Buf : Buffer) is
      use Dbg_Out;
   begin
      Put
        ("To[\1..\2] = <<\3>>end To[\1..\2]\n",
         "1",
         Image (Last_Index (Buf.To)),
         To_UTF8 (To_Array (Buf.To)));
      Put
        ("From[\1..\2] = <<\3>>end From[\1..\2]\n",
         Image (Buf.From_First),
         Image (Last_Index (Buf.From)),
         To_UTF8 (Valid_From (Buf)));

      if At_Beginning (Buf) then
         Put ("At_Beginning\n");
      end if;
      if At_End (Buf) then
         Put ("At_End\n");
      end if;
   end Dump_Buf;

   procedure Dump_Buffer (Buf : Buffer) is
      use Dbg_Out;
   begin
      if False then
         Put ("Buf = <<\1>>end Buf\n", To_String (Buf));
         Put ("Dbg = <<\1>>end Dbg\n", To_Debug_String (Buf));
      end if;

      Dump_Buf (Buf);

      Put ("To_Markers:\n");
      Indent;

      for X in 1 .. Last_Index (Buf.To_Markers) loop
         Dump_Marker (Buf, Buf.To_Markers (X));
      end loop;
      Outdent;
      Put ("end To_Markers\n");

      Put
        ("From_Markers[\1..\2]:\n",
         Image (Integer (Buf.From_Markers_First)),
         Image (Integer (Last_Index (Buf.From_Markers))));
      Indent;

      for X in Buf.From_Markers_First .. Last_Index (Buf.From_Markers) loop
         Dump_Marker (Buf, Buf.From_Markers (X));
      end loop;
      Outdent;
      Put ("end From_Markers\n");
   end Dump_Buffer;

   procedure Dump_Marker (Buf : Buffer; Mark : Marker) is
      use Dbg_Out;
      Rec : Marker_Rec renames Buf.Markers (Mark);
      Pos : constant Positive := Position (Buf, Mark);

   begin
      if Rec.Flag = Buf.To_Flag then
         Put ("to   ");

      else
         Put ("from ");
      end if;
      Put
        ("\1 --> \2 '\3'  \4\n",
         Image (Rec.Position),
         Image (Pos),
         To_UTF8 ((1 => Rec.Name)),
         Image (Modular (Mark)));

      Put ("<<");

      for P in Pos - 30 .. Pos + 30 loop
         if P = Pos then
            Put_Char ('[');
         end if;
         if P in 1 .. Last_Position (Buf) then
            if Char_At (Buf, P) = NL then
               Put_Char ('$');
            else
               Put ("\1", To_UTF8 ((1 => Char_At (Buf, P))));
            end if;
         end if;
         if P = Pos then
            Put_Char (']');
         end if;
      end loop;
      Put (">>\n");
   end Dump_Marker;

   function Mark_LT (Buf : Buffer; M, N : Marker) return Boolean is
   begin
      return Position (Buf, M) < Position (Buf, N);
   end Mark_LT;

   function Mark_LE (Buf : Buffer; M, N : Marker) return Boolean is
   begin
      return Position (Buf, M) = Position (Buf, N);
   end Mark_LE;

   procedure Move_Marker (Buf : in out Buffer) is
      Mark : constant Marker := Buf.From_Markers (Buf.From_Markers_First);
      Rec : Marker_Rec renames Buf.Markers (Mark);
   begin
      pragma Assert (Rec.Position >= Buf.From_First);
      if Rec.Position = Buf.From_First then
         Append (Buf.To_Markers, Mark);
         Rec :=
           (Flag     => not Rec.Flag,
            Position => Last_Index (Buf.To) + 1,
            Name     => Rec.Name);
         pragma Assert (Rec.Flag = Buf.To_Flag);
         Buf.From_Markers_First := Buf.From_Markers_First + 1;
         pragma Assert (At_Point (Buf, Mark));
      end if;
   end Move_Marker;

   procedure Maybe_Move_Marker (Buf : in out Buffer) is

      procedure Assert;
      --  Perform some consistency checks

      procedure Assert is
      begin
         if Buf.From_Markers_First <= Last_Index (Buf.From_Markers) then
            declare
               Mark : constant Marker :=
                 Buf.From_Markers (Buf.From_Markers_First);
               Rec : Marker_Rec renames Buf.Markers (Mark);

            begin
               pragma Assert
                 (Rec.Position in
                    Buf.From_First + 1 .. Last_Index (Buf.From) + 1);
            end;

         else
            pragma Assert
              (Buf.From_Markers_First = Last_Index (Buf.From_Markers) + 1);
         end if;
      end Assert;

   --  Start of processing for Maybe_Move_Marker

   begin
      --  We can't have two different Markers pointing to the same character
      --  (see Mark), so we don't need a while loop here.

      if Buf.From_Markers_First <= Last_Index (Buf.From_Markers) then
         Move_Marker (Buf);
      end if;

      pragma Debug (Assert);
   end Maybe_Move_Marker;

   procedure Move_Forward (Buf : in out Buffer) is
      pragma Assert (not At_End (Buf));
      From_First : constant Positive := Buf.From_First + 1;
   begin
      Buf.From_First := From_First;
      Append (Buf.To, Buf.Cur_Char);
--         if C = NL then
--            Buf.Cur_Column := 1;
--         else
--            Buf.Cur_Column := Buf.Cur_Column + 1;
--         end if;
      Buf.Cur_Char :=
        (if From_First > Last_Index (Buf.From) then W_NUL
         else Buf.From (From_First));
      Maybe_Move_Marker (Buf);
   end Move_Forward;

   function Move_Forward (Buf : in out Buffer) return W_Char is
      Result : W_Char;
      pragma Assert (not At_End (Buf));
      From_First : constant Positive := Buf.From_First + 1;
   begin
      Buf.From_First := From_First;
      Append (Buf.To, Buf.Cur_Char);
--         if C = NL then
--            Buf.Cur_Column := 1;
--         else
--            Buf.Cur_Column := Buf.Cur_Column + 1;
--         end if;
      Result :=
        (if From_First > Last_Index (Buf.From) then W_NUL
         else Buf.From (From_First));
      Buf.Cur_Char := Result;
      Maybe_Move_Marker (Buf);
      return Result;
   end Move_Forward;

   procedure Initialize_Buffer (Buf : in out Buffer) is
   begin
      Buf.From_First         := 1;
      Buf.From_Markers_First := 1;
      Buf.Cur_Char           := W_NUL;
      Buf.Cur_Column         := 1;
   end Initialize_Buffer;

   procedure Initialize (Buf : in out Buffer) renames Initialize_Buffer;

   procedure Maybe_Adjust_Marker (Buf : in out Buffer) is
   begin
      if not Is_Empty (Buf.To_Markers)
        and then Buf.From_First <= Last_Index (Buf.From)
      then
         declare
            Mark : constant Marker := Last_Element (Buf.To_Markers);

         begin
            if At_Point (Buf, Mark) then
               declare
                  Rec : Marker_Rec renames Buf.Markers (Mark);

               begin
                  Rec.Position := Rec.Position + 1;
               end;
            end if;
         end;
      end if;
   end Maybe_Adjust_Marker;

   procedure Insert (Buf : in out Buffer; C : W_Char) is
      pragma Assert (C /= W_NUL);
      pragma Assert (not Is_Line_Terminator (C));
      pragma Assert (C /= W_HT); -- ???For now
   begin
      --  The whole point of this package is that we don't need to adjust the
      --  Markers here! Markers in To_Markers are to the left of the newly
      --  inserted character, so their Position is correct. Valid Markers in
      --  From_Markers (in From_Markers(From_Markers_First..From_Markers'Last))
      --  have Positions relative to From, so also don't need to be adjusted.
      --  A Marker's Position only needs to be adjusted when 'point' is moved
      --  forward past the Marker.
      --
      --  There is one minor exception: A marker that is at 'point' needs to be
      --  adjusted, but only if 'point' is not at the end:

      Maybe_Adjust_Marker (Buf);

--      Buf.Cur_Column := Buf.Cur_Column + 1;
      Append (Buf.To, C);
   end Insert;

   procedure Insert (Buf : in out Buffer; S : W_Str) is
   begin
      for C of S loop
         Insert (Buf, C);
      end loop;
   end Insert;

   procedure Insert_Any (Buf : in out Buffer; C : W_Char) is
      pragma Assert (C /= W_NUL);
      pragma Assert (C /= W_HT); -- ???For now
   begin
      Maybe_Adjust_Marker (Buf);

      pragma Assert -- no trailing blanks allowed
        (if
           Is_Line_Terminator (C)
         then
           (Is_Empty (Buf.To) or else Last_Element (Buf.To) /= ' '));

--      if Is_Line_Terminator (C) then
--         Buf.Cur_Column := 1;
--      else
--         Buf.Cur_Column := Buf.Cur_Column + 1;
--      end if;
      Append (Buf.To, C);
   end Insert_Any;

   procedure Insert_Any (Buf : in out Buffer; S : W_Str) is
   begin
      for C of S loop
         Insert_Any (Buf, C);
      end loop;
   end Insert_Any;

   procedure Insert_NL (Buf : in out Buffer) is
   begin
      Maybe_Adjust_Marker (Buf);

--      Buf.Cur_Column := 1;
      pragma Assert -- no trailing blanks allowed
      (Is_Empty (Buf.To) or else Last_Element (Buf.To) /= ' ');

      Append (Buf.To, NL);
   end Insert_NL;

   procedure Append (Buf : in out Buffer; C : W_Char) is
      pragma Assert (False); -- not currently used
      pragma Assert (At_End (Buf));
      pragma Assert (not Is_Line_Terminator (C));
      pragma Assert (C /= W_HT); -- ???For now
   begin
      Maybe_Adjust_Marker (Buf);

--      Buf.Cur_Column := Buf.Cur_Column + 1;
      Append (Buf.To, C);
      pragma Assert (At_End (Buf));
   end Append;

   procedure Append (Buf : in out Buffer; S : W_Str) is
      pragma Assert (False); -- not currently used
   begin
      for C of S loop
         Append (Buf, C);
      end loop;
   end Append;

   procedure Append_Any (Buf : in out Buffer; C : W_Char) is
      pragma Assert (At_End (Buf));
      pragma Assert (C /= W_HT); -- ???For now
   begin
      Maybe_Adjust_Marker (Buf);

      pragma Assert -- no trailing blanks allowed
        (if
           Is_Line_Terminator (C)
         then
           (Is_Empty (Buf.To) or else Last_Element (Buf.To) /= ' '));

--      if Is_Line_Terminator (C) then
--         Buf.Cur_Column := 1;
--      else
--         Buf.Cur_Column := Buf.Cur_Column + 1;
--      end if;
      Append (Buf.To, C);
      pragma Assert (At_End (Buf));
   end Append_Any;

   procedure Append_Any (Buf : in out Buffer; S : W_Str) is
      pragma Assert (False); -- not currently used
   begin
      for C of S loop
         Append_Any (Buf, C);
      end loop;
   end Append_Any;

   procedure Append_NL (Buf : in out Buffer) is
      pragma Assert (False); -- not currently used
      pragma Assert (At_End (Buf));
   begin
      Maybe_Adjust_Marker (Buf);

--      Buf.Cur_Column := 1;
      pragma Assert -- no trailing blanks allowed
      (Is_Empty (Buf.To) or else Last_Element (Buf.To) /= ' ');

      Append (Buf.To, NL);
      pragma Assert (At_End (Buf));
   end Append_NL;

   procedure Insert_Keeping_Mark
     (Buf  : in out Buffer;
      Mark : Marker;
      C    : W_Char)
   is
   begin
      pragma Assert (False); -- ???Not used

      Append (Buf.To, C);
--      if C = NL then
--         Buf.Cur_Column := 1;
--      else
--         Buf.Cur_Column := Buf.Cur_Column + 1;
--      end if;

      pragma Assert (Char_At (Buf, Mark) = C);
   end Insert_Keeping_Mark;

   function Is_Empty (Buf : Buffer) return Boolean is
   begin
      return
        Result : constant Boolean :=
          Is_Empty (Buf.From) and then Is_Empty (Buf.To)
      do
         pragma Assert (Result = Is_Empty (Buf.Markers));
         pragma Assert (Result = Is_Empty (Buf.From_Markers));
         pragma Assert (Result = Is_Empty (Buf.To_Markers));
      end return;
   end Is_Empty;

   function Last_Position (Buf : Buffer) return Natural is
   begin
      return Last_Index (Buf.To) +
        (Last_Index (Buf.From) - Buf.From_First + 1);
   end Last_Position;

   function Lookahead (Buf : Buffer; Offset : Positive := 1) return W_Char is
   begin
      if Buf.From_First + Offset <= Last_Index (Buf.From) then
         return Buf.From (Buf.From_First + Offset);

      else
         return W_NUL;
      end if;
   end Lookahead;

   function Lookback (Buf : Buffer; Offset : Positive := 1) return W_Char is
   begin
      if Last_Index (Buf.To) - Offset + 1 >= 1 then
         return Buf.To (Last_Index (Buf.To) - Offset + 1);

      else
         return W_NUL;
      end if;
   end Lookback;

   function Mark (Buf : in out Buffer; Name : W_Char) return Marker is
   begin
      if Is_Empty (Buf.To_Markers) then
         Append_New_Marker (Buf, Name); -- Create a new one

      --  Avoid creating a new Marker if we already have one at 'point'

      elsif not At_Point (Buf, Last_Element (Buf.To_Markers)) then
         Append_New_Marker (Buf, Name); -- Create a new one
      end if;

      return Last_Element (Buf.To_Markers);
   end Mark;

   function Mark_Previous (Buf : in out Buffer; Name : W_Char) return Marker is
      L       : constant Marker_Index'Base := Last_Index (Buf.To_Markers);
      M, Save : Marker;
      At_P    : Boolean;

   begin
      if L >= 1
        and then Position (Buf, Buf.To_Markers (L)) = Last_Index (Buf.To)
      then
         return Buf.To_Markers (L);
      end if;
      if L >= 2
        and then Position (Buf, Buf.To_Markers (L - 1)) = Last_Index (Buf.To)
      then
         return Buf.To_Markers (L - 1);
      end if;

      At_P := L > 0 and then At_Point (Buf, Buf.To_Markers (L));
      if At_P then
         Save := Last_Element (Buf.To_Markers);
         Delete_Last (Buf.To_Markers);
      end if;

      Append
        (Buf.Markers,
         Marker_Rec'
           (Position => Last_Index (Buf.To),
            Flag     => Buf.To_Flag,
            Name     => Name));
      M := Last_Index (Buf.Markers);
      Append (Buf.To_Markers, M);
      if At_P then
         Append (Buf.To_Markers, Save);
      end if;

      return M;
   end Mark_Previous;

   function Name (Buf : Buffer; Mark : Marker) return W_Char is
   begin
      return Buf.Markers (Mark).Name;
   end Name;

   function Point (Buf : Buffer) return Positive is
   begin
      return Last_Index (Buf.To) + 1;
   end Point;

   function Position (Buf : Buffer; Mark : Marker) return Positive is
      Rec : constant Marker_Rec := Buf.Markers (Mark);

   begin
      if Rec.Flag = Buf.To_Flag then
         return Rec.Position;

      else
         return Last_Index (Buf.To) - (Buf.From_First - 1) + Rec.Position;
      end if;
   end Position;

   procedure Insert_Ada (Buf : in out Buffer; C : W_Char) is
      pragma Assert (C /= W_HT); -- ???For now
   begin
      pragma Assert (At_End (Buf));
      pragma Assert
        (Is_Empty (Buf.To_Markers) and then Is_Empty (Buf.From_Markers));

      if Is_Line_Terminator (C) then
         Buf.Cur_Column := 1;

         --  Delete trailing blanks at end of line. There are no markers to
         --  worry about here.

         while not Is_Empty (Buf.To) and then Is_Space (Last_Element (Buf.To))
         loop
            Delete_Last (Buf.To);
         end loop;
      else
         Buf.Cur_Column := Buf.Cur_Column + 1;
      end if;
      Append (Buf.To, C);
   end Insert_Ada;

   procedure Insert_Ada_Source
     (Buf         : in out Buffer;
      Input       : String;
      Wide_Character_Encoding_Method : System.WCh_Con.WC_Encoding_Method;
      Expand_Tabs : Boolean := False)
   is
      pragma Assert (Expand_Tabs); -- ???For now

      package Decoder is new GNAT.Decode_String
        (Encoding_Method => Wide_Character_Encoding_Method);
      package Brackets_Decoder is new GNAT.Decode_String
        (Encoding_Method => System.WCh_Con.WCEM_Brackets);
      Ptr     : Natural  := Input'First;
      C       : W_Char;
      Tab_Len : constant := 8;

      function At_Brackets_Start return Boolean with
         Pre => Input (Ptr) = '[';
         --  True if we're pointing to the start of a valid brackets sequence
         --  to be interpreted as a wide character.

      function At_Brackets_Start
        return Boolean is
        (Ptr + 2 <= Input'Last
         and then Input (Ptr + 1) = '"'
         and then Input (Ptr + 2) in '0' .. '9' | 'a' .. 'f' | 'A' .. 'F');

      type State_Enum is (In_Comment, In_String_Literal, Other);
      State : State_Enum := Other;
   --  We need to keep track of whether we're inside a comment, because
   --  brackets encoding is disabled in that case. We need to keep track of
   --  whether we're inside a string literal in order to keep track of whether
   --  we're inside a comment ('--' doesn't start a comment inside a string
   --  literal).

   begin
      while Ptr <= Input'Last loop
         --  Set C to the current wide character

         if Input (Ptr) = '[' then
            if State /= In_Comment and then At_Brackets_Start then
               Brackets_Decoder.Decode_Wide_Character (Input, Ptr, C);
            else
               C   := '[';
               Ptr := Ptr + 1;
            end if;
         else
            Decoder.Decode_Wide_Character (Input, Ptr, C);
         end if;

         --  Keep track of whether we're in a comment

         case State is
            when In_Comment =>
               if Is_Line_Terminator (C) then
                  if False and then C = W_VT then
                     --  Ignore VT characters in comments. This differs from
                     --  the behavior of the old gnatpp, which has an option
                     --  for that. ???Disable this, at least for now.
                     --  There's really no such thing as a VT in a comment,
                     --  because VT is a line terminator.
                     Ptr := Ptr + 1;
                     goto Continue;
                  end if;
                  State := Other;
               end if;
            when In_String_Literal =>
               if C in '"' | '%' then
                  State := Other;
               end if;
            when Other =>
               if C = '-'
                 and then Ptr <= Input'Last
                 and then Input (Ptr) = '-'
               then
                  State := In_Comment;
               elsif C in '"' | '%' then
                  State := In_String_Literal;
               end if;
         end case;

         --  Insert C into the buffer, expanding tabs

         if C = W_HT and then Expand_Tabs then
            loop
               Insert_Ada (Buf, ' ');
               exit when Buf.Cur_Column mod Tab_Len = 1;
            end loop;
         else
            Insert_Ada (Buf, C);
         end if;
         <<Continue>>
      end loop;

      --  Make sure last line is terminated by NL
      if C /= NL then
         Insert_Ada (Buf, NL);
      end if;
   end Insert_Ada_Source;

   procedure Read_Ada_File
     (Buf         : in out Buffer;
      File_Name   : String;
      Wide_Character_Encoding_Method : System.WCh_Con.WC_Encoding_Method :=
        System.WCh_Con.WCEM_Brackets;
      BOM_Seen    : out Boolean;
      Expand_Tabs : Boolean := False)
   is
      --  We read the file into a String, and convert to wide characters
      --  according to the encoding method.
      --
      --  No matter what the encoding method is, we recognize brackets
      --  encoding, but not within comments.
      --
      --  These behaviors are intended to match what the compiler does.

      Input : String_Access := Read_File (File_Name);
      First : Natural       := 1;

      use GNAT.Byte_Order_Mark;
      BOM     : BOM_Kind;
      BOM_Len : Natural;

   begin
      Clear (Buf);

      --  Check for BOM at start of file. The only supported BOM is
      --  UTF8_All. If present, when we're called from gnatpp, the
      --  Wide_Character_Encoding_Method should already be set to
      --  WCEM_UTF8, but when we're called from xml2gnat, we need to set it.

      Read_BOM (Input.all, BOM_Len, BOM);
      if BOM = UTF8_All then
         First := BOM_Len + 1; -- skip it
         BOM_Seen := True;
      else
         pragma Assert (BOM = Unknown); -- no BOM found
         BOM_Seen := False;
      end if;

      Insert_Ada_Source (Buf, Input (First .. Input'Last),
                         Wide_Character_Encoding_Method, Expand_Tabs);

      Free (Input);
      Reset (Buf);
   end Read_Ada_File;

   procedure Replace_Cur (Buf : in out Buffer; C : W_Char) is
   begin
      pragma Assert (not At_End (Buf));
      Buf.From (Buf.From_First) := C;
      Buf.Cur_Char              := C;
      pragma Assert -- no trailing blanks
        (if
           C = NL
         then
           (Is_Empty (Buf.To) or else Last_Element (Buf.To) /= ' '));

      --  No need to adjust Buf.Cur_Column
   end Replace_Cur;

   procedure Replace_Previous (Buf : in out Buffer; C : W_Char) is
   begin
      Buf.To (Last_Index (Buf.To)) := C;
      pragma Assert -- no trailing blanks
        (if
           C = NL
         then
           (Last_Index (Buf.To) = 1
            or else Buf.To (Last_Index (Buf.To) - 1) /= ' '));
--      if C = NL then
--         Buf.Cur_Column := 1;
--      end if;
   end Replace_Previous;

   procedure Reset (Buf : in out Buffer) is
   begin
      pragma Debug (Validate (Buf, "Reset"));

      pragma Assert (Buf.From_First = Last_Index (Buf.From) + 1);
      if Buf.From_Markers_First = Last_Index (Buf.From_Markers) then
         Move_Forward (Buf);
      end if;
      pragma Assert
        (Buf.From_Markers_First = Last_Index (Buf.From_Markers) + 1);

      Move (Target => Buf.From, Source => Buf.To);
      pragma Assert (Is_Empty (Buf.To));

      Move (Target => Buf.From_Markers, Source => Buf.To_Markers);
      pragma Assert (Is_Empty (Buf.To_Markers));

      Buf.From_First         := 1;
      Buf.From_Markers_First := 1;
      Buf.To_Flag            := not Buf.To_Flag;
      Buf.Cur_Char           :=
        (if Buf.From_First > Last_Index (Buf.From) then W_NUL
         else Buf.From (Buf.From_First));
      Buf.Cur_Column := 1;

      --  If there is a Marker pointing to the first character, we need to move
      --  it to To to preserve the invariant.

      Maybe_Move_Marker (Buf);
   end Reset;

   function Slice (Buf : Buffer; First, Last : Marker) return W_Str is
      pragma Assert (Position (Buf, First) <= Position (Buf, Last));
      F                          : constant Marker_Rec := Buf.Markers (First);
      L                          : constant Marker_Rec := Buf.Markers (Last);
      To_F, To_L, From_F, From_L : Natural;
   begin
      if F.Flag = Buf.To_Flag then
         To_F   := F.Position;
         From_F := 1;
      else
         To_F   := Last_Index (Buf.To) + 1;
         From_F := F.Position;
      end if;
      if L.Flag = Buf.To_Flag then
         To_L   := L.Position - 1;
         From_L := 0;
      else
         To_L   := Last_Index (Buf.To);
         From_L := L.Position - 1;
      end if;

      declare
         Result : constant W_Str :=
           Slice (Buf.To, To_F, To_L) & Slice (Buf.From, From_F, From_L);
         pragma Assert (Result'First = 1); -- Ensured by Slice
      begin
         if False then -- Too slow, but we keep it for documentation
            pragma Assert
              (Result =
               To_W_Str (Buf)
                 (Position (Buf, First) .. Position (Buf, Last) - 1));
         end if;
         return Result;
      end;
   end Slice;

   function Slice
     (Buf   : Buffer;
      First : Positive;
      Last  : Natural;
      Lines : Boolean := False)
      return  W_Str
   is
      F : Positive := First;
      L : Natural  := Last;
   begin
      if Lines then
         while F > 1 and then Char_At (Buf, F) /= NL loop
            F := F - 1;
         end loop;
         while L < Last_Position (Buf) and then Char_At (Buf, L) /= NL loop
            L := L + 1;
         end loop;
      end if;

      return Result : W_Str (1 .. L - F + 1) do
         for J in Result'Range loop
            Result (J) := Char_At (Buf, F + J - 1);
         end loop;
         if False then -- Slow
            pragma Assert (Result = To_W_Str (Buf) (F .. L));
         end if;
      end return;
   end Slice;

   function String_To_Buffer (S : W_Str) return Buffer is
   begin
      --  This is called only for debugging. "pragma Assert (Assert_Enabled);"
      --  doesn't work, because if Assert_Enabled is False, the pragma is
      --  disabled.

      if not Assert_Enabled then
         raise Program_Error;
      end if;

      return Buf : Buffer do
         Insert (Buf, S);
         Reset (Buf);
      end return;
   end String_To_Buffer;

   procedure Move (Target, Source : in out Buffer) is
      Source_Str : constant W_Str :=
        (if Assert_Enabled then To_W_Str (Source) else "");
      --  For Assert below
   begin
      Clear (Target);

      Move (Target => Target.From, Source => Source.From);
      Move (Target => Target.To, Source => Source.To);
      Move (Target => Target.Markers, Source => Source.Markers);
      Move (Target => Target.From_Markers, Source => Source.From_Markers);
      Move (Target => Target.To_Markers, Source => Source.To_Markers);

      Target.From_First := Source.From_First;
      Target.From_Markers_First := Source.From_Markers_First;
      Target.Cur_Char := Source.Cur_Char;
      Target.Cur_Column := Source.Cur_Column;

      pragma Assert (To_W_Str (Target) = Source_Str);
   end Move;

   function To_Debug_String (Buf : Buffer) return String is
      S     : constant W_Str        := To_W_Str (Buf);
      Marks : constant Marker_Array :=
        To_Array (Buf.To_Markers) &
        Slice
          (Buf.From_Markers,
           Buf.From_Markers_First,
           Last_Index (Buf.From_Markers));
      pragma Assert (Marks'First = 1);
      M_Index : Marker_Index := 1;
      Result  : Char_Vector;

   --  Start of processing for To_Debug_String

   begin
      for X in S'Range loop
         if M_Index <= Marks'Last then
            pragma Assert (X <= Position (Buf, Marks (M_Index)));
            if X = Position (Buf, Marks (M_Index)) then
               Append (Result, Name (Buf, Marks (M_Index)));
               M_Index := M_Index + 1;
            end if;

            while M_Index <= Marks'Last
              and then X = Position (Buf, Marks (M_Index))
            loop
               Append (Result, "[duplicate]");
               Append (Result, Name (Buf, Marks (M_Index)));
               M_Index := M_Index + 1;
            end loop;
         end if;
         Append (Result, S (X));
      end loop;

      --  Check for one last marker after the last character

      if M_Index = Marks'Last then
         Append (Result, Name (Buf, Marks (M_Index)));
         M_Index := M_Index + 1;
      end if;
      pragma Assert (M_Index = Marks'Last + 1);

      return To_UTF8 (To_Array (Result));
   end To_Debug_String;

   function To_W_Str (Buf : Buffer) return W_Str is
   begin
      return Result : constant W_Str := To_Array (Buf.To) & Valid_From (Buf) do
         pragma Assert (Result'First = 1);
      end return;
   end To_W_Str;

   function To_String (Buf : Buffer) return String is
   begin
      return To_UTF8 (To_W_Str (Buf));
   end To_String;

   function To_Vector (Buf : Buffer) return Char_Vector is
   begin
      pragma Assert (Is_Empty (Buf.To) and then Buf.From_First = 1);
      return Buf.From;
   end To_Vector;

   function Elements
     (Buf  : Buffer)
      return ASIS_UL.Char_Vectors.Char_Vectors.Big_Ptr
   is
   begin
      pragma Assert (Is_Empty (Buf.To) and then Buf.From_First = 1);
      return Elems (Buf.From);
   end Elements;

   function Valid_From (Buf : Buffer) return W_Str is
   begin
      return Slice (Buf.From, Buf.From_First, Last_Index (Buf.From));
   end Valid_From;

   Validations : Natural := 0;

   procedure Validate (Buf : Buffer; Message : String) is

      procedure Fail (Msg : String);

      procedure Fail (Msg : String) is
      begin
         Dbg_Out.Output_Enabled := True;
         Dbg_Out.Put ("Validate: \1: failure: \2\n", Message, Msg);
         Dump_Buffer (Buf);
         raise Program_Error;
      end Fail;

      function Marker_Less (X, Y : Marker) return Boolean;

      function Marker_Less (X, Y : Marker) return Boolean is
      begin
         return Position (Buf, X) < Position (Buf, Y);
      end Marker_Less;

      package Sorting is new Marker_Vectors.Generic_Sorting
        ("<" => Marker_Less);

   --  Start of processing for Validate

   begin
      if False then
         Dbg_Out.Output_Enabled := True;
         Dbg_Out.Put ("\nValidate: \1\n", Message);
         Dump_Buffer (Buf);
      end if;

      if not Sorting.Is_Sorted (Buf.To_Markers) then
         Fail ("To_Markers not sorted");
      end if;
      if not Sorting.Is_Sorted (Buf.From_Markers) then
         Fail ("From_Markers not sorted");
      end if;

      for M in 1 .. Last_Index (Buf.To_Markers) loop
         declare
            Mark : constant Marker     := Buf.To_Markers (M);
            Rec  : constant Marker_Rec := Buf.Markers (Mark);

         begin
            if Rec.Flag /= Buf.To_Flag then
               Fail ("Bad To_Marker flag");
            end if;
            pragma Warnings (Off);
            --  "lower bound check only fails if it is invalid"
            if Rec.Position not in 1 .. Last_Index (Buf.To) + 1 then
               pragma Warnings (On);
               Fail ("Bad To_Marker position");
            end if;
         end;
      end loop;

      for M in Buf.From_Markers_First .. Last_Index (Buf.From_Markers) loop
         declare
            Mark : constant Marker     := Buf.From_Markers (M);
            Rec  : constant Marker_Rec := Buf.Markers (Mark);

         begin
            if Rec.Flag /= not Buf.To_Flag then
               Fail ("Bad From_Marker flag");
            end if;
            if Rec.Position not in
                Buf.From_First + 1 .. Last_Index (Buf.From) + 1
            then
               Fail ("Bad From_Marker position");
            end if;
         end;
      end loop;

      if Last_Index (Buf.To_Markers) + Last_Index (Buf.From_Markers)
        < 100_000 -- arbitrary limit, to avoid stack overflow
      then
         declare
            Marks : constant Marker_Array :=
              To_Array (Buf.To_Markers) &
              Slice
              (Buf.From_Markers,
               Buf.From_Markers_First,
               Last_Index (Buf.From_Markers));
            pragma Assert (Marks'First = 1);
         begin
            for J in 2 .. Marks'Last loop
               if Position (Buf, Marks (J - 1))
                 = Position (Buf, Marks (J))
               then
                  Fail ("Duplicate marks");
               end if;
            end loop;
         end;
      end if;

      Validations := Validations + 1;
   end Validate;

end Pp.Buffers;