with Ada.Characters.Handling; use Ada.Characters.Handling;
with Ada.Exceptions;
with Ada.Finalization;
with Ada.Strings.Fixed;
with Ada.Strings.UTF_Encoding;
with System.WCh_Cnv;
with System.WCh_Con;
with Text_IO, Ada.Wide_Text_IO;
with Pp.Buffers;              use Pp.Buffers;
with Pp.Command_Lines;        use Pp.Command_Lines;
with Pp.Error_Slocs;          use Pp.Error_Slocs;
with Pp.Formatting;           use Pp.Formatting;
with Pp.Formatting.Dictionaries;
with Pp.Scanner.Lines;        use Pp.Scanner.Lines;

with Ada.Directories; use Ada.Directories;
with Interfaces;
use type Interfaces.Unsigned_16;
with Unchecked_Deallocation;

with GNAT.Lock_Files;
with GNAT.OS_Lib; use GNAT.OS_Lib;

with Langkit_Support.Slocs; use Langkit_Support;
with Libadalang.Common;     use Libadalang.Common;
with LAL_Extensions;        use LAL_Extensions;

with Utils.Command_Lines.Common; use Utils.Command_Lines.Common;
with Utils.Dbg_Out;
with Utils.Formatted_Output;

with Utils.Vectors;

with Utils.Symbols;            use Utils.Symbols;
with Utils.Environment;
with Utils.Predefined_Symbols; use Utils.Predefined_Symbols;

package body Pp.Actions is

   use Utils.Char_Vectors.WChar_Vectors;

   function Image (X : Integer) return String renames
     Utils.String_Utilities.Image;

   pragma Warnings (Off);
   use Common_Flag_Switches, Common_String_Switches,
     Common_String_Seq_Switches, Common_Nat_Switches, Common_Boolean_Switches;

   use Pp_Flag_Switches, Pp_Boolean_Switches, Attribute_Casing_Switches,
     Keyword_Casing_Switches, Name_Casing_Switches, Enum_Casing_Switches,
     Type_Casing_Switches, Number_Casing_Switches, Pragma_Casing_Switches,
     Pp_String_Switches, Pp_Nat_Switches, Pp_String_Seq_Switches;
   pragma Warnings (On);

   File_Name_File_Name : String_Access;
   --  There is a "file name file"; this is its name. ASIS_Processing writes
   --  the output to a temp file, and Finalize moves the temp file to the
   --  actual output file. The file name file is used to pass the names of the
   --  temp and output files from ASIS_Processing to Finalize (both subunits of
   --  Utils.Source_Table.Processing).
   --
   --  ASIS_Processing is called once for each file, and it writes two lines to
   --  the file name file: the name of the temp file, and then the name of the
   --  output file. Finalize reads pairs of lines from the file name file, and
   --  moves temp --> output.
   --
   --  The reason for passing information via a file is that in
   --  Incremental_Mode, ASIS_Processing and Finalize are running in two
   --  different processes; the inner processes do ASIS_Processing, and need
   --  to pass those file names back to the outer process. The builder is in
   --  between inner and outer, and doesn't know how to cooperate in this
   --  dance.
   --
   --  The reason for doing all the renames at the end (after all
   --  ASIS_Processing is done) is again Incremental_Mode, specifically
   --  Replace_Modes. We don't want to replace the original input with the
   --  output during ASIS_Processing, because that would change timestamps and
   --  confuse the builder.
   --
   --  In Incremental_Mode, the File_Name_File_Name is constructed in the outer
   --  invocation (in Initialize), and passed down to the inner invocations via
   --  the command-line switch --file-name-file=. --file-name-file is not
   --  documented for users; it is for internal use only. In other modes, it is
   --  constructed in Initialize.
   --
   --  We use the file name file even in non-Incremental_Mode, even though it's
   --  not really necessary, just for uniformity/simplicity.
   --
   --  In Replace_Modes, we optimize by not overwriting the output (i.e. the
   --  input) if it didn't change. This is especially important in
   --  Incremental_Mode, because of the way the builder works: it will invoke
   --  gnatpp (in Mimic_gcc mode) on something.adb, which will pretty-print
   --  something.ads. If something.ads didn't need pretty-printing, we don't
   --  want to change its timestamp, causing real (code-generating) builds to
   --  do unnecessary recompiles.

   Null_Kind : constant Ada_Node_Kind_Type := Ada_Abort_Absent;
   --  ???We need a special value

   function Mimic_gcc (Cmd : Command_Line) return Boolean is
     (Arg (Cmd, Outer_Dir) /= null);

   ----------
   -- Init --
   ----------

   procedure Init (Tool : in out Pp_Tool; Cmd : in out Command_Line) is
      pragma Unreferenced (Tool);
      File_Name_File : Text_IO.File_Type;

      procedure Init_Pp_Off_And_On;
      --  Initialize Pp_Off_On_Delimiters

      procedure Init_Pp_Off_And_On is
      begin
         if Arg (Cmd, Pp_Off) /= null then
            pragma Assert (Arg (Cmd, Pp_Off).all /= "");
            Scanner.Pp_Off_On_Delimiters.Off :=
              new W_Str'("--" & To_Wide_String (Arg (Cmd, Pp_Off).all));
         end if;
         if Arg (Cmd, Pp_On) /= null then
            pragma Assert (Arg (Cmd, Pp_On).all /= "");
            Scanner.Pp_Off_On_Delimiters.On :=
              new W_Str'("--" & To_Wide_String (Arg (Cmd, Pp_On).all));
         end if;
      end Init_Pp_Off_And_On;

      --  Start of processing for Init

   begin
      --  Check that user did not specify --pp-off=X and --pp-on=X, where X = X

      if Arg (Cmd, Pp_Off) /= null and then Arg (Cmd, Pp_On) /= null then
         if Arg (Cmd, Pp_Off).all = Arg (Cmd, Pp_On).all then
            Text_IO.Put_Line -- Use Cmd_Error???
              (Text_IO.Standard_Error,
               "gnatpp: cannot specify --pp-off and --pp-on with same string");
            raise Command_Line_Error;
         end if;
      end if;

      Init_Pp_Off_And_On;

      --  ????Other checks from gnatpp/lal_ul-check_parameters.adb?

      if Arg (Cmd, Separate_Loop_Then)
        and then Arg (Cmd, No_Separate_Loop_Then)
      then
         Cmd_Error
           ("incompatible switches --separate-loop-then, " &
            "--no-separate-loop-then");
      end if;

      --  The --separate-loop-then switch is equivalent to --separate-loop and
      --  --separate-then. Likewise for the --no-... switches.

      if Arg (Cmd, Separate_Loop_Then) then
         Set_Arg (Cmd, Separate_Then);
         Set_Arg (Cmd, Separate_Loop);
      end if;

      if Arg (Cmd, No_Separate_Loop_Then) then
         Set_Arg (Cmd, No_Separate_Then);
         Set_Arg (Cmd, No_Separate_Loop);
      end if;

      --  If --source-line-breaks was given, then formatting options that
      --  control line-splitting make no sense.

      if Arg (Cmd, Source_Line_Breaks) then
         Set_Arg (Cmd, Comments_Fill, False);
         Set_Arg (Cmd, Preserve_Blank_Lines, False);
         Set_Arg (Cmd, Preserve_Line_Breaks, False);
         Set_Arg (Cmd, Split_Line_Before_Record, False);
         Set_Arg (Cmd, Split_Line_Before_Op, False);
         Set_Arg (Cmd, Vertical_Enum_Types, False);
         Set_Arg (Cmd, Vertical_Array_Types, False);
         Set_Arg (Cmd, Vertical_Named_Aggregates, False);
         Set_Arg (Cmd, Vertical_Case_Alternatives, False);
         Set_Arg (Cmd, Separate_Loop_Then, False);
         Set_Arg (Cmd, Separate_Then, False);
         Set_Arg (Cmd, Separate_Loop, False);
         Set_Arg (Cmd, Separate_Is, False);
      --  Others????????????????
      end if;

      pragma Assert (Environment.Initial_Dir = Current_Directory);
      if Mimic_gcc (Cmd) then
         pragma Assert (False);
         pragma Assert (Directories.Exists (File_Name_File_Name.all),
                        File_Name_File_Name.all & " not found");
      else
         File_Name_File_Name :=
           new String'
             (Directories.Compose
                (Environment.Tool_Temp_Dir.all, "file_names"));

         --  Create an empty file name file, so ASIS_Processing can append to
         --  it. (Small annoyance: the file is not actually empty; it contains
         --  a single blank line, and Finalize has to work around that.)

         Text_IO.Create (File_Name_File, Name => File_Name_File_Name.all);
         Text_IO.Close (File_Name_File);
      end if;

      Dictionaries.Scan_Dictionaries
        (Dictionary_File_Names => Arg (Cmd, Dictionary));
   end Init;

   -----------
   -- Final --
   -----------

   procedure Final (Tool : in out Pp_Tool; Cmd : Command_Line) is
      --  If this is the outer process of an incremental build, or it is a
      --  non-incremental build, we move all the temp files to the output
      --  files. We don't need any file locking here, because all the inner
      --  processes that were writing to the File_Name_File have finished.

      pragma Unreferenced (Tool);
      use Text_IO;
      File_Name_File : File_Type;
      Ignored        : Boolean;
      Count          : Natural := 0; -- number of files moved
   begin
      if Debug_Flag_1 then
         Utils.Dbg_Out.Output_Enabled := True;
         Utils.Symbols.Print_Statistics;
      end if;

      if not Mimic_gcc (Cmd)
      --  and then not Nothing_To_Do
         then
         begin
            Open (File_Name_File, In_File, Name => File_Name_File_Name.all);

            --  The File_Name_File contains an initial blank line, due to
            --  Text_IO weirdness, so we need to discard it.

            declare
               Ignore : constant String := Get_Line (File_Name_File);
            begin
               null;
            end;

            --  Read pairs of lines from the file name file, and do the moves.

            while not End_Of_File (File_Name_File) loop
               Count := Count + 1;
               declare
                  Temp_Output_Name : constant String :=
                    Get_Line (File_Name_File);
                  Output_Name : constant String := Get_Line (File_Name_File);
               begin
                  if False then
                     Put_Line ("mv " & Temp_Output_Name & " " & Output_Name);
                  end if;
                  Move_File
                    (Old_Name => Temp_Output_Name, New_Name => Output_Name);
               end;
            end loop;

            Close (File_Name_File);

            if not Debug_Flag_N then
               GNAT.OS_Lib.Delete_File (File_Name_File_Name.all, Ignored);
            --  No point in complaining on failure
            end if;
         exception
            when X : Move_Failure =>
               Cmd_Error (Ada.Exceptions.Exception_Message (X));
         end;
      end if;
   end Final;

   ---------------------
   -- Per_File_Action --
   ---------------------

   type Output_Modes is
     --  Defines the where and how gnatpp places the result source.

     (Pipe,
      --  Sends the output into Stderr.
      Output,
      --  Creates the file with the name specified in 'o' option. If the
      --  file with the given name already exists, does not erase it and gives
      --  up.
      Output_Force,
      --  Creates the file with the name specified in 'o' option. If the
      --  file with the given name already exists, erases the old file and
      --  replaces it with the pretty-printed source.
      Replace_Backup,
      --  Replaces the argument source with the pretty-printed source. The
      --  original source is stored in the file <arg_source>.npp. If the file
      --  with such a name already exists, gnatpp gives up.
      Replace_Force_Backup,
      --  Replaces the argument source with the pretty-printed source. The
      --  original source is stored in the file <arg_source>.npp. If the file
      --  with such a name already exists, gnatpp overrides it.

      Replace,
      --  Replaces the argument source with the pretty-printed source. The
      --  original source is not stored in any back-up file.
      Output_Directory);
   --  Put the result into <arg_source_simple_name> in directory Out_Dir.

   NPP_Suffix : constant String := ".npp";
   --  The suffixes for the file names for default result and backup copy
   --  files.

   subtype Create_Modes is Output_Modes with
       Predicate => Create_Modes in Output | Output_Force;
   pragma Unreferenced (Create_Modes);
   subtype Replace_Modes is Output_Modes with
       Predicate => Replace_Modes in Replace_Backup | Replace_Force_Backup |
          Replace;

   function Get_Output_Mode (Cmd : Command_Line) return Output_Modes;
   function Get_Output_Mode (Cmd : Command_Line) return Output_Modes is
      Result : Output_Modes := Replace;
   begin
      if Arg (Cmd, Output_Directory) /= null then
         Result := Output_Directory;
      end if;
      if Arg (Cmd, Pipe) then
         Result := Pipe;
      end if;
      if Arg (Cmd, Replace_Backup) then
         Result := Replace_Backup;
      end if;
      if Arg (Cmd, Replace_Force_Backup) then
         Result := Replace_Force_Backup;
      end if;
      if Arg (Cmd, Replace) then
         Result := Replace;
      end if;
      if Arg (Cmd, Output) /= null then
         Result := Output;
      end if;
      if Arg (Cmd, Output_Force) /= null then
         Result := Output_Force;
      end if;
      return Result;
   end Get_Output_Mode;

   ----------------

   use Line_Break_Vectors, Line_Break_Index_Vectors;
   use Tab_Vectors;
   Lines_Data : aliased Lines_Data_Rec;

   Cur_Indentation           : Natural renames Lines_Data.Cur_Indentation;
   Next_Line_Break_Unique_Id : Modular renames
     Lines_Data.Next_Line_Break_Unique_Id;
   All_LB                    : Line_Break_Vector renames Lines_Data.All_LB;
   All_LBI : Line_Break_Index_Vector renames Lines_Data.All_LBI;
   Tabs                      : Tab_Vector renames Lines_Data.Tabs;
   Src_Tokns                 : Scanner.Tokn_Vec renames Lines_Data.Src_Tokns;
   New_Tokns                 : Scanner.Tokn_Vec renames Lines_Data.New_Tokns;
   Check_Whitespace          : Boolean renames Lines_Data.Check_Whitespace;

   procedure Tree_To_Ada_2
     (Root    : Ada_Node; Cmd : Utils.Command_Lines.Command_Line;
      Partial : Boolean);
   --  Partial is True if we are not processing an entire file.

   --  Hard and soft line breaks:
   --
   --  A hard line break means a new-line WILL appear in the final output. A
   --  soft line break is a place where a new-line CAN appear; it will appear
   --  only if necessary to make lines short enough. Soft line breaks are
   --  prioritized: if there are several soft line breaks that can be used
   --  to shorten a given line, higher priority ones are chosen over lower
   --  priority ones. Normally, less nested ones are higher priority than
   --  more nested ones.

   type Str_Template is new W_Str;
   --  This is similar to Formatted_Output.Template, except instead of
   --  inserting strings into the template, it inserts subtrees. See
   --  Interpret_Template. The special character sequences are:
   --
   --      $ -- insert a hard line break
   --      $0 -- same as $, but doesn't affect comment indentation
   --           (see Line_Break.Affects_Comments)
   --      # -- insert a soft line break. May be followed by 1, 2, etc,
   --           to indicate additional nesting depth. Also +1, +2, etc
   --           (see below).
   --      { -- indent
   --      } -- outdent
   --      [ -- continuation-line indent
   --      ] -- continuation-line outdent
   --      * -- one space indent (see Spec_Alt template)
   --      _ -- one space outdent (see Spec_Alt template)
   --      ( -- insert a "(", and add "extra" indent by 1 character
   --      ???If a further indent ({ or [) is done without any
   --      intervening line break we should negate the effect of the
   --      "extra indent". See also Paren_Stack in PP.Formatting.
   --      Currently, we are assuming that "(" appears at the start
   --      of the line if indentation matters.
   --      ) -- insert a ")", and outdent the "extra"
   --      ^ -- tab based on following token. May be followed by 1, 2,
   --           etc, to indicate Index_In_Line.
   --      ` -- insertion point for next "^" tab. May be followed by 1, 2,
   --           etc, to indicate Index_In_Line.
   --      ! -- insert next required subtree
   --      ? -- insert next optional or list subtree
   --      ~ -- delimits arguments of ?
   --      !1, !2, !3, etc -- insert N'th required subtree
   --      ?1, ?2, ?3, etc -- insert N'th optional or list subtree
   --      / -- ignore next required subtree
   --  Other characters are inserted verbatim.
   --
   --  Note that we avoid conflicts with Ada tokens. If a special character
   --  sequence starts with a certain character, that character cannot start an
   --  Ada token that is different. The three "(", ")", and "/" are OK, because
   --  the special character sequence and the Ada token are identical. But we
   --  cannot, for example, have a special character sequence "%", because in
   --  Ada, "%" is the start of a string literal (albeit obsolescent).
   --
   --  All subtrees are required to be "used". If you don't want any output for
   --  a given subtree, then use / to ignore that subtree. Thus, all subtrees
   --  should be mentioned by one of: ! ? /.
   --
   --  ? takes three arguments, delimited by ~. If the subtree is a list, the
   --  first argument is placed before the list, the second in between list
   --  elements, and the third after the list, except if the list is empty,
   --  nothing is printed. If it's not a list, the first and third arguments
   --  are placed before and after, and the second must be empty, except if
   --  it's Null_Kind, nothing is printed.
   --
   --  Normally, the soft line breaks inserted by # have a priority based on
   --  the syntactic nesting depth. Less-nested breaks are enabled in favor of
   --  more deeply nested ones. However, if # is followed by a digit, that
   --  indicates an additional nesting depth not reflected in the syntax. For
   --  example, if we have "blah #blah #1blah", then the #1 is considered more
   --  nested than the #, so if the line is too long, we first enable the #,
   --  and only enable the #1 if the line is still too long.
   --
   --  # may also be followed by "+" and a digit, as in "#+1".
   --  The difference is that for "#1", all subtrees start out deeper than the
   --  deepest of the outer ones, whereas for "#+1", the subtrees are just one
   --  level deeper than the outer tree. So for example, suppose we have a tree
   --  T at depth 5. Its immediate subtrees will normally be at depth 6.
   --  However, if there is a "#1" in the template for T, the immediate
   --  subtrees will be at depth 7. But if we change "#1" to "#+1", then the
   --  immediate subtrees will normally be at depth 6. Thus, "#+1" allows a
   --  given soft line break to be of equal depth to those of subtrees.
   --
   --  Examples:
   --  "!X!X!" -- inserts three subtrees, with "X" in between. "!1X!2X!3" --
   --  same as "!X!X!"
   --
   --  "?(~,~)~" -- a parenthesized comma-separated list
   --
   --  There is no way to escape the special characters, so for example, you
   --  can't print a literal $. So far, that wasn't needed, because those
   --  characters were deliberately chosen not to be part of Ada syntax. They
   --  can of course appear inside string literals and comments, but they're
   --  not needed in the templates.
   --
   --  Pairs of {}, [], and () must match and be properly nested.
   --
   --  The extra indentation for "(" is needed for parenthesized syntax, like
   --  this:
   --
   --      Do_Something
   --        (This,
   --         That);
   --        ^
   --        | Extra blank needed there.
   --
   --  Note: If you want to add new special characters, look at the case
   --  statement in Interpret_Template.

   type Str_Template_Ptr is access all Str_Template;

   --  ???Use some renamings for now, to ease the transition from ASIS to
   --  libadalang:
   subtype Ada_Tree_Kind is Ada_Node_Kind_Type;
   subtype Opt_ASIS_Elems is Ada_Node_Kind_Type;
   subtype Query_Index is Positive;
   subtype Query_Count is Natural;
   subtype Ada_Tree_Base is Ada_Node;
   subtype Ada_Tree is Ada_Node;
   subtype Ada_Tree_Array is Ada_Node_Array;
   function Is_Nil (T : Ada_Node'Class) return Boolean is (T.Is_Null);
   function Present (T : Ada_Node'Class) return Boolean is (not Is_Nil (T));
   function Subtree_Count (T : Ada_Node'Class) return Query_Count is
     (if Is_Nil (T) then 0 else Last_Child_Index (T));
   function Empty_Tree_Array return Ada_Node_Array is ((1 .. 0 => <>));
   function Subtrees (T : Ada_Node'Class) return Ada_Tree_Array is
     (if Is_Nil (T) then Empty_Tree_Array else Children (T));
   function Subtree (T : Ada_Node'Class; X : Query_Index) return Ada_Tree is
     (Child (T, X));

   function Template_For_Kind (Kind : Ada_Tree_Kind) return Str_Template_Ptr;

   function L (T1 : Str_Template) return Str_Template_Ptr;
   function L (T1, T2 : Str_Template) return Str_Template_Ptr;
   function L (T1, T2, T3 : Str_Template) return Str_Template_Ptr;
   function L (T1, T2, T3, T4 : Str_Template) return Str_Template_Ptr;
   function L (T1, T2, T3, T4, T5 : Str_Template) return Str_Template_Ptr;
   function L (T1, T2, T3, T4, T5, T6 : Str_Template) return Str_Template_Ptr;
   function L
     (T1, T2, T3, T4, T5, T6, T7 : Str_Template) return Str_Template_Ptr;
   --  All the L functions form a template by concatenating together a bunch of
   --  lines.

   Aspects    : constant Str_Template := "?~~~";
   Aspects_Is : constant Str_Template := "?~~$~";
   --  The "_Is" template is used when the aspect specifications are followed
   --  by "is", which we want to put on a new line (if aspect specifications
   --  are present).

   function L (T1 : Str_Template) return Str_Template_Ptr is
   begin
      return new Str_Template'(T1);
   end L;

   function L (T1, T2 : Str_Template) return Str_Template_Ptr is
   begin
      return new Str_Template'(T1 & T2);
   end L;

   function L (T1, T2, T3 : Str_Template) return Str_Template_Ptr is
   begin
      return new Str_Template'(T1 & T2 & T3);
   end L;

   function L (T1, T2, T3, T4 : Str_Template) return Str_Template_Ptr is
   begin
      return new Str_Template'(T1 & T2 & T3 & T4);
   end L;

   function L (T1, T2, T3, T4, T5 : Str_Template) return Str_Template_Ptr is
   begin
      return new Str_Template'(T1 & T2 & T3 & T4 & T5);
   end L;

   function L (T1, T2, T3, T4, T5, T6 : Str_Template) return Str_Template_Ptr
   is
   begin
      return new Str_Template'(T1 & T2 & T3 & T4 & T5 & T6);
   end L;

   function L
     (T1, T2, T3, T4, T5, T6, T7 : Str_Template) return Str_Template_Ptr
   is
   begin
      return new Str_Template'(T1 & T2 & T3 & T4 & T5 & T6 & T7);
   end L;

   function Template_For_Kind (Kind : Ada_Tree_Kind) return Str_Template_Ptr is
   begin
      return
        (case Kind is
           when Ada_Discrete_Base_Subtype_Decl => null,
           when Ada_Discrete_Subtype_Name => null,
           when Ada_Unconstrained_Array_Index => null,
           when Ada_Contract_Case_Assoc => null,
           when Ada_Contract_Cases => null,
           when Ada_Multi_Dim_Array_Assoc => null,
           when Ada_Unconstrained_Array_Indices => null,
           when Ada_Error_Decl => null,
           when Ada_Error_Stmt => null,
           when Ada_Enum_Subp_Spec => null,
           when Ada_Enum_Lit_Synth_Type_Expr => null,
           --  ???The above are not used

           when Ada_Ada_List => null,
           when Ada_Subp_Spec => null,
           when Ada_Aggregate_Assoc => null,

           when Ada_Constrained_Array_Indices => L ("(?~,# ~~)"),
           when Ada_Aspect_Assoc => L ("!? ^=> ~~~"),
           when Ada_At_Clause => L ("for ! use at !"),
           when Ada_Attribute_Def_Clause | Ada_Enum_Rep_Clause =>
             L ("for ! use [!]"),
           when Ada_Record_Rep_Clause =>
             L ("for ! use record? at mod ~~;~$", "{?~;$~;$~}", "end record"),
           when Ada_Aspect_Spec => L ("? with$" & "{~,$~}~"),
           --  ???We could try something like the following:
           --  "? with[#1 ~,#1 ~]~"

           when Ada_Component_Decl => L ("?~,# ~~ ^: !? ^2:=[# ~~]~", Aspects),
           when Ada_Discriminant_Spec => L ("?~,# ~~ ^: !? ^2:=[# ~~]~"),
           when Ada_Params => null,
           when Ada_Param_Spec => null,
           when Ada_Base_Package_Decl =>
             L ("package !#", Aspects_Is, " is$", "!", "!", "end !1/"),
           when Ada_Abstract_Subp_Decl =>
             L ("?~~ ~!", " is abstract", Aspects),
           when Ada_Expr_Function => L ("?~~ ~!", " is[# !]", Aspects),
           when Ada_Null_Subp_Decl => L ("?~~ ~!", " is null", Aspects),
           when Ada_Subp_Renaming_Decl => L ("?~~ ~!!", Aspects),
           when Ada_Subp_Decl => L ("?~~ ~!" & Aspects),
           when Ada_Subp_Body_Stub => L ("?~~ ~! is separate" & Aspects),
           when Ada_Concrete_Formal_Subp_Decl =>
             L ("?~~ ~with !? is ~~~" & Aspects),
           when Ada_Abstract_Formal_Subp_Decl =>
             L ("?~~ ~with ! is abstract? ~~~" & Aspects),
           when Ada_Subp_Kind_Function => L ("function"),
           when Ada_Subp_Kind_Procedure => L ("procedure"),
           when Ada_Package_Body_Stub =>
             L ("package body ! is separate", Aspects),
           when Ada_Protected_Body_Stub =>
             L ("protected body ! is separate", Aspects),
           when Ada_Task_Body_Stub => L ("task body ! is separate", Aspects),
           when Ada_Package_Body =>
             L ("package body !", Aspects_Is, " is$", "!", "!", "end !1/"),
           when Ada_Protected_Body =>
             L ("protected body !", Aspects_Is, " is$", "!", "end !1/"),
           when Ada_Subp_Body =>
             L ("?~~ ~!", Aspects_Is, "#+1 is$", "!", "!", "end !"),
           --  We increase the level of the # before " is", so it will be
           --  equal to that of the formal parameters, so if the "is" goes
           --  on a new line, the parameters will be split as well.
   --
   --  The last "!" refers to the name of the procedure, which
   --  replaces the F_End_Id (see Do_Subp_Decl). This is necessary
   --  because the name of the subprogram is buried in a subtree.

           when Ada_Task_Body =>
             L ("task body !", Aspects_Is, " is$", "!", "!", "end !1/"),
           when Ada_Entry_Decl => L ("?~~ ~entry !", Aspects),
           when Ada_Entry_Spec => L ("!?[# (~~)]~?~~~"),
           when Ada_Entry_Body =>
             L ("entry !?[# (~~)]~?~~~[# when !# is]$",
                --  ???Perhaps "# is]" should be "#+1 is]" or "# ]is".
                "!", "!", "end !1/"),
           when Ada_Enum_Literal_Decl => L ("!"),
           when Ada_Exception_Decl => L ("?~,# ~~ ^: exception!", Aspects),
           when Ada_Generic_Package_Instantiation =>
             L ("package ! is new !?[# (~,# ~)]~", Aspects),
           when Ada_Generic_Subp_Instantiation =>
             L ("?~~ ~! ! is new !?[# (~,# ~)]~", Aspects),
           when Ada_Generic_Package_Decl => L ("generic$", "!", "!"),
           when Ada_Generic_Formal_Part => L ("{?~;$~;$~}"),
           when Ada_Generic_Formal_Obj_Decl | Ada_Generic_Formal_Subp_Decl |
             Ada_Generic_Formal_Type_Decl => L ("!"),
           when Ada_Generic_Formal_Package => L ("with !"),
           when Ada_Generic_Package_Renaming_Decl =>
             L ("generic package ! renames !", Aspects),
           when Ada_Generic_Subp_Renaming_Decl =>
             L ("generic ! ! renames !", Aspects),
           when Ada_Generic_Subp_Decl => L ("generic$", "!", "!"),
           when Ada_Generic_Subp_Internal => L ("!", Aspects),
           when Ada_Number_Decl => L ("?~,# ~~ ^: constant ^2:=[# !]"),
           when Ada_Object_Decl | Ada_Extended_Return_Stmt_Object_Decl =>
             L ("?~,# ~~ ^:? ~~~? ~~~? ~~~ !? ^2:=[# ~~]~!", Aspects),
           when Ada_Package_Renaming_Decl => L ("package !!", Aspects),
           when Ada_Single_Protected_Decl =>
             L ("protected !", Aspects_Is, " is? new ~ and ~ with~$", "!",
                "end !1"),
           when Ada_Protected_Type_Decl =>
             L ("protected type !!", Aspects_Is, " is? new ~ and ~ with~$",
                "!", "end !1"),
           --  ???The interfaces should be moved from
           --  Ada_Single_Protected_Decl and Ada_Protected_Type_Decl to
           --  Ada_Protected_Def.

           when Ada_Protected_Def => L ("!$", "!$/"),
           when Ada_Single_Task_Decl => L ("!"),
           when Ada_Single_Task_Type_Decl =>
             L ("task !!", Aspects_Is, "? is$~~~"),
           when Ada_Task_Type_Decl =>
             L ("task type !!", Aspects_Is, "? is~~~"),
           when Ada_Task_Def =>
             L ("? new ~ and ~ with~$", "!$", "!$", "end !"),
           --  The last "!" refers to the name of the task, which
           --  replaces the F_End_Id (see Do_Task_Def). This is necessary
           --  because the name of the task is buried in a subtree.

           when Ada_Enum_Type_Def => L ("(?~,#1 ~~)"),
           when Ada_Type_Decl => null,
           when Ada_Incomplete_Type_Decl => L ("type !!"), -- Aspects?

           when Ada_Incomplete_Tagged_Type_Decl => L ("type !! is /tagged"),
           --  The "/" is for F_Has_Abstract, which is always
           --  Abstract_Absent.

           when Ada_Classwide_Type_Decl => null,
           when Ada_Subtype_Decl => L ("subtype ! is[# !", Aspects, "]"),
           when Ada_Compilation_Unit => null,
           when Ada_Component_Def => L ("?~~ ~?~~ ~!"),
           --  The second "?~~ ~" is for Has_Constant, which should never
           --  print anything.

           when Ada_Delta_Constraint => L ("delta !? ~~~"),
           when Ada_Digits_Constraint => L ("digits !? ~~~"),
           when Ada_Discriminant_Assoc => null,
           when Ada_Discriminant_Constraint | Ada_Index_Constraint =>
             L ("?[#(~,# ~)]~"),
           when Ada_Range_Constraint => L ("!"),
           when Ada_Declarative_Part => L ("?${~;$~};$$~"),
           when Ada_Private_Part => L ("?$private${~;$~};$~"),
           when Ada_Public_Part => L ("?{~;$~};$~"),
           when Ada_Elsif_Expr_Part => L ("elsif[# !]# then[# !]"),
           when Ada_Entry_Index_Spec => L ("for ! in[# !]"),
           when Ada_Exception_Handler =>
             L ("when[? ~~ :~ ?~ ^|# ~~] ^=>$", "{?~;$~;$~}"),
           when Ada_Explicit_Deref => L ("!.all"),
           when Ada_Aggregate => null,
           when Ada_Null_Record_Aggregate =>
             L ("#(?~~ with #~" & "null record/)"),
           when Ada_Allocator => L ("new? #(~~)~ !"),
           when Ada_Attribute_Ref => L ("!'[#1!?# (~,# ~)~]"),
           --  ???This includes function calls to attributes, such as
           --  T'Max(X, Y), which isn't really right.

           when Ada_Update_Attribute_Ref => L ("!'[#1!# !]"),
           when Ada_Bin_Op | Ada_Relation_Op => null,
           when Ada_Call_Expr => null,
           when Ada_Case_Expr => L ("case ! is[# ?#~,# ~~]"),
           when Ada_Case_Expr_Alternative => L ("when[ ?~ |# ~~] ^=>[# !]"),
           --  ???Ada_Case_Stmt_Alternative has a "^" just before the "|"

           when Ada_Box_Expr => L ("<>"),
           when Ada_If_Expr =>
             L ("if[#1 !]#1 then[#1 !]", "? #~ #~~", "?# else[ ~~]~"),
           when Ada_Membership_Expr => L ("! ![# ?[#~ ^|# ~]~]"),
           when Ada_Dotted_Name => L ("![#.!]"),
           when Ada_End_Name => L ("!"),
           when Ada_Defining_Name => L ("!"),
           when Ada_Char_Literal => null,
           when Ada_Identifier => null,
           when Ada_String_Literal => null,
           when Ada_Null_Literal => L ("null"),
           when Ada_Real_Literal => null,
           when Ada_Int_Literal => null,
           when Ada_Qual_Expr => L ("!'[#!]"),
           --  ???There are no parentheses here, because the subexpression
           --  is either a parenthesized expression or an aggregate.

           when Ada_Quantified_Expr => L ("for ! ! ^=>[# !]"),
           when Ada_Raise_Expr => L ("raise !?[# with ~~]~"),
           when Ada_Un_Op => null,
           when Ada_Handled_Stmts => null,
           when Ada_Library_Item => L ("?~~ ~!"),
           when Ada_Null_Component_Decl => L ("null"),
           when Ada_Others_Designator => L ("others"),
           when Ada_Param_Assoc => null,
           when Ada_Pragma_Argument_Assoc => null,
           when Ada_Pragma_Node => null,
           when Ada_Component_Clause => null, -- ?

           when Ada_Renaming_Clause => L ("? renames[# ~~]~"),
           when Ada_Select_Stmt =>
             L ("select", "!", "?else$", "{~;$~;$}~", "?then abort$",
                "{~;$~;$}~", "end select"),
           when Ada_Select_When_Part => null,
           when Ada_Accept_Stmt => L ("accept !? #(~~)~?~~~"),
           when Ada_Accept_Stmt_With_Stmts =>
             L ("accept !? #(~~)~?~~~", "!", "end !1/"),
           when Ada_Null_Record_Def => L ("null record/"),
           --  Null_Record_Def inherits F_Components from
           --  Base_Record_Def_Type, and it returns null.

           when Ada_Record_Def => L ("record$", "!", "end record"),
           when Ada_Record_Type_Def => L ("?~~ ~?~~ ~?~~ ~!"),
           when Ada_Component_List => L ("{?~;$~;$~}", "{?~~;$~}"),
           when Ada_Variant => L ("when[ ?~ ^|# ~~] ^=>$", "!"),
           when Ada_Case_Stmt_Alternative =>
             L ("when[ ?~ ^|# ~~] ^=>$", "{?~;$~;$~}"),
           when Ada_Case_Stmt | Ada_Variant_Part =>
             L ("case !# is$", "{!}", "end case"),
           when Ada_Extended_Return_Stmt =>
             L ("return[# !]", "!", "end return"),
           when Ada_If_Stmt =>
             L ("if[ !]# then$", "{?~;$~;$~}", "?~~~", "?else$", "{~;$~;$}~",
                "end if"),
           when Ada_Elsif_Stmt_Part => L ("elsif[ !]# then$", "{?~;$~;$~}"),
           when Ada_Named_Stmt => L ("! :$! !1"),
           when Ada_Named_Stmt_Decl => L ("!"),
           when Ada_Begin_Block => L ("!", "end/"),
           when Ada_Decl_Block => L ("?declare$", "~~~", "!", "end/"),
           --  For Ada_Begin_Block and Ada_Decl_Block, the "begin" comes
           --  from Do_Handled_Stmts.

           when Ada_Loop_Stmt | Ada_For_Loop_Stmt | Ada_While_Loop_Stmt =>
             L ("?~~# ~loop$", "{?~;$~;$~}", "end loop/"),
           when Ada_For_Loop_Spec => null,
           when Ada_For_Loop_Var_Decl => L ("!? : ~~~"),
           when Ada_While_Loop_Spec => L ("while[ !]"),
           when Ada_Abort_Stmt => L ("abort ?~, ~~"),
           when Ada_Assign_Stmt => L ("! ^:=[# !]"),
           when Ada_Target_Name => L ("@"), when Ada_Call_Stmt => L ("!"),
           when Ada_Delay_Stmt => L ("delay? ~~~ !"),
           when Ada_Exit_Stmt => L ("exit? ~~~? when[ ~~]~"),
           when Ada_Goto_Stmt => L ("goto !"), when Ada_Label => L ("<<!>>"),
           when Ada_Label_Decl => L ("!"), when Ada_Null_Stmt => L ("null"),
           when Ada_Raise_Stmt => L ("raise? ~~~?[# with ~~]~"),
           when Ada_Requeue_Stmt => L ("requeue !? ~~~"),
           when Ada_Return_Stmt => L ("return[? ~~~]"),
           when Ada_Terminate_Alternative => L ("terminate"),
           when Ada_Subunit => L ("separate (!)$", "!"),
           when Ada_Type_Access_Def => L ("?~~ ~access? ~~~? ~~~ !"),
           when Ada_Array_Type_Def => L ("array[# !] of !"),
           when Ada_Derived_Type_Def =>
             L ("?~~ ~?~~ ~?~~ ~new !? and[# ~ and# ~]~? with# ~~~? ~~~"),

           when Ada_Formal_Discrete_Type_Def => L ("#(<>)"),
           when Ada_Interface_Type_Def =>
             L ("?~~ ~interface? and[# ~ and# ~]~"),
           when Ada_Mod_Int_Type_Def => L ("mod !"),
           when Ada_Private_Type_Def => L ("?~~ ~?~~ ~?~~ ~private"),
           when Ada_Range_Spec => L ("range !"),
           when Ada_Decimal_Fixed_Point_Def => L ("delta ! digits !? ~~~"),
           when Ada_Floating_Point_Def => L ("digits !? ~~~"),
           when Ada_Ordinary_Fixed_Point_Def => L ("delta !? ~~~"),

           when Ada_Signed_Int_Type_Def => L ("!"),
           when Ada_Known_Discriminant_Part => L ("?[# (~;# ~)]~#"),
           when Ada_Unknown_Discriminant_Part => L (" #(<>)"),
           when Ada_Access_To_Subp_Def => L ("?~~ ~access? ~~~ !"),
           when Ada_Anonymous_Type_Decl => L ("//!", Aspects),
           when Ada_Synth_Anonymous_Type_Decl => null,
           when Ada_Anonymous_Type_Access_Def => null,
           when Ada_Subtype_Indication | Ada_Constrained_Subtype_Indication |
             Ada_Discrete_Subtype_Indication => L ("?~~ ~!? ~~~"),
           when Ada_Anonymous_Type => L ("!"),
           when Ada_Use_Package_Clause => L ("use[# ?~,# ~~]"),
           when Ada_Use_Type_Clause => L ("use? ~~~ type[# ?~,# ~~]"),
           when Ada_With_Clause => L ("?~~ ~?~~ ~with ^?~, ~~"),
           --  Note: the tab ('^') is ignored for limited/private 'with's
           --  (see Append_Tab).

           when Ada_Paren_Expr => L ("#(!)"), when Ada_Abort_Absent => null,
           when Ada_Abort_Present => L ("with abort"),
           when Ada_Abstract_Absent => null,
           when Ada_Abstract_Present => L ("abstract"),
           when Ada_Aliased_Absent => null,
           when Ada_Aliased_Present => L ("aliased"),
           when Ada_All_Absent => null, when Ada_All_Present => L ("all"),
           when Ada_Constant_Absent => null,
           when Ada_Constant_Present => L ("constant"),
           when Ada_Mode_Default => null, when Ada_Mode_In => L ("in"),
           when Ada_Mode_In_Out => L ("in out"),
           when Ada_Mode_Out => L ("out"),
           when Ada_Interface_Kind_Limited => L ("limited"),
           when Ada_Interface_Kind_Protected => L ("protected"),
           when Ada_Interface_Kind_Synchronized => L ("synchronized"),
           when Ada_Interface_Kind_Task => L ("task"),
           when Ada_Iter_Type_In => L ("in"),
           when Ada_Iter_Type_Of => L ("of"), when Ada_Limited_Absent => null,
           when Ada_Limited_Present => L ("limited"),
           when Ada_Not_Null_Absent => null,
           when Ada_Not_Null_Present => L ("not null"),

           when Ada_Op_In => L ("in"), when Ada_Op_Not_In => L ("not in"),

           when Ada_Op_And => null, when Ada_Op_Or => null,
           when Ada_Op_Or_Else => null, when Ada_Op_And_Then => null,
           when Ada_Op_Concat => null, when Ada_Op_Xor => null,
           when Ada_Op_Abs => null, when Ada_Op_Not => null,
           when Ada_Op_Pow => null, when Ada_Op_Mult => null,
           when Ada_Op_Div => null, when Ada_Op_Mod => null,
           when Ada_Op_Rem => null, when Ada_Op_Plus => null,
           when Ada_Op_Minus => null, when Ada_Op_Eq => null,
           when Ada_Op_Neq => null, when Ada_Op_Lt => null,
           when Ada_Op_Lte => null, when Ada_Op_Gt => null,
           when Ada_Op_Gte => null, when Ada_Op_Double_Dot => null,

           when Ada_Overriding_Not_Overriding => L ("not overriding"),
           when Ada_Overriding_Overriding => L ("overriding"),
           when Ada_Overriding_Unspecified => null,
           when Ada_Private_Absent => null,
           when Ada_Private_Present => L ("private"),
           when Ada_Protected_Absent => null,
           when Ada_Protected_Present => L ("protected"),
           when Ada_Quantifier_All => L ("all"),
           when Ada_Quantifier_Some => L ("some"),
           when Ada_Reverse_Absent => null,
           when Ada_Reverse_Present => L ("reverse"),
           when Ada_Synchronized_Absent => null,
           when Ada_Synchronized_Present => L ("synchronized"),
           when Ada_Tagged_Absent => null,
           when Ada_Tagged_Present => L ("tagged"),
           when Ada_Until_Absent => null,
           when Ada_Until_Present => L ("until"),
           when Ada_With_Private_Absent => null,
           when Ada_With_Private_Present => L ("with private")); -- end case
   end Template_For_Kind;

   Template_Tables_Initialized : Boolean := False;

   Str_Template_Table : array (Ada_Tree_Kind) of Str_Template_Ptr;

   type Instr_Kind is
     (Hard_Break,            -- "$"

      Hard_Break_No_Comment, -- "$0"

      Soft_Break,            -- "#" "#1", "#+1"

      Indent,                -- "{"

      Outdent,               -- "}"

      Continuation_Indent,   -- "["

      Continuation_Outdent,  -- "]"

      One_Space_Indent,      -- "*"

      One_Space_Outdent,     -- "_"

      '(',
      ')',
      Tab,                   -- "^", "^1"

      Tab_Insert_Point,      -- "`", "`1"

      Required_Subtree,      -- "!", "!1"

      Opt_Subtree_Or_List,   -- "?pre~between~post", "?1pre~between~post"

      Ignore_Subtree,        -- "/"

      Verbatim);

   type Instr_Array;
   type Instr_Array_Ptr is access all Instr_Array;

   type Tok_Template is record
      Instructions : Instr_Array_Ptr;

      Max_Nesting_Increment : Nesting_Level_Increment;
   --  If a digit occurs after '#', this is an additional "nesting
   --  increment" to be added to the nesting level when we recursively
   --  process the subtree. This is intended to allow some line breaks to
   --  have precedence over others. If no such digit occurs, the default is
   --  zero. This is the maximum such nesting increment in the template.
   --
   --  Note that "#+1" is ignored for Max_Nesting_Increment.
   end record;

   type Instr (Kind : Instr_Kind := Ignore_Subtree) is record
      --  "Instr" = one "instruction" in a Tok_Template.
         case Kind is
         when Hard_Break | Hard_Break_No_Comment =>
            null;
         when Soft_Break =>
            Plus      : Boolean;
            Level_Inc : Nesting_Level_Increment;
         when Indent | Outdent | Continuation_Indent | Continuation_Outdent |
           One_Space_Indent | One_Space_Outdent | '(' | ')' =>
            null;
         when Tab | Tab_Insert_Point =>
            Index_In_Line : Tab_Index_In_Line;
         when Required_Subtree | Opt_Subtree_Or_List =>
            Index : Query_Count; -- zero for "next"
            case Kind is
               when Opt_Subtree_Or_List =>
                  Pre, Between, Post : Tok_Template;
               when others =>
                  null;
            end case;
         when Ignore_Subtree =>
            null;
         when Verbatim =>
            --  A token to be printed verbatim in the output. All we need is
            --  Kind and Text -- the Sloc and comment-specific fields of tokens
            --  are not used in templates.
            T_Kind : Scanner.Token_Kind;
            Text   : Symbol;
      end case;
   end record;

   type Instr_Index is new Positive;
   type Instr_Array is array (Instr_Index range <>) of Instr;
   package Instr_Vectors is new Utils.Vectors (Instr_Index, Instr,
                                               Instr_Array);
   subtype Instr_Vector is Instr_Vectors.Vector;

   Tok_Template_Table : array (Ada_Tree_Kind) of Tok_Template;

   --  We have two representations for templates -- Str_Template is a sequence
   --  of characters, and Tok_Template is a sequence of tokens. We create the
   --  templates initially as Str_Templates, then convert them to Tok_Template,
   --  and use the Tok_Templates for further processing.

   function Back_To_Str_Templ (T : Tok_Template) return Str_Template;
   --  Convert back to Str_Template for assertion

   function Back_To_Str_Templ (T : Tok_Template) return Str_Template is
      Result : Bounded_W_Str (Max_Length => 80);

      function Im (Val : Integer) return W_Str is (From_UTF8 (Image (Val)));
   begin
      for X of T.Instructions.all loop
         case X.Kind is
            when Hard_Break =>
               Append (Result, "$");
            when Hard_Break_No_Comment =>
               Append (Result, "$0");

            when Soft_Break =>
               Append (Result, "#");
               if X.Plus then
                  Append (Result, "+");
               end if;
               if X.Level_Inc /= 0 then
                  Append (Result, Im (Integer (X.Level_Inc)));
               end if;

            when Indent =>
               Append (Result, "{");
            when Outdent =>
               Append (Result, "}");
            when Continuation_Indent =>
               Append (Result, "[");
            when Continuation_Outdent =>
               Append (Result, "]");
            when One_Space_Indent =>
               Append (Result, "*");
            when One_Space_Outdent =>
               Append (Result, "_");
            when '(' =>
               Append (Result, "(");
            when ')' =>
               Append (Result, ")");

            when Tab =>
               Append (Result, "^");
               if X.Index_In_Line /= 1 then
                  Append (Result, Im (Integer (X.Index_In_Line)));
               end if;

            when Tab_Insert_Point =>
               Append (Result, "`");
               if X.Index_In_Line /= 1 then
                  Append (Result, Im (Integer (X.Index_In_Line)));
               end if;

            when Required_Subtree =>
               Append (Result, "!");
               if X.Index /= 0 then
                  Append (Result, Im (Integer (X.Index)));
               end if;

            when Opt_Subtree_Or_List =>
               Append (Result, "?");
               if X.Index /= 0 then
                  Append (Result, Im (Integer (X.Index)));
               end if;
               Append (Result, W_Str (Back_To_Str_Templ (X.Pre)));
               Append (Result, "~");
               Append (Result, W_Str (Back_To_Str_Templ (X.Between)));
               Append (Result, "~");
               Append (Result, W_Str (Back_To_Str_Templ (X.Post)));
               Append (Result, "~");

            when Ignore_Subtree =>
               Append (Result, "/");

            when Verbatim =>
               Append (Result, To_W_Str (X.Text));
         end case;
      end loop;

      return Str_Template (+Result);
   end Back_To_Str_Templ;

   function Fix_RM_Spacing
     (Cmd : Command_Line; T : Str_Template; Kind : Ada_Tree_Kind := Null_Kind)
      return Str_Template;
   --  If the --RM-style-spacing switch was specified, modify the template as
   --  appropriate.

   function Fix_RM_Spacing
     (Cmd : Command_Line; T : Str_Template; Kind : Ada_Tree_Kind := Null_Kind)
      return Str_Template
   is
   begin
      if not Arg (Cmd, Rm_Style_Spacing) then
         return T;
      end if;
      declare
         Result : Bounded_W_Str (Max_Length => T'Length * 2);
         X      : Natural := T'First;
         function C return W_Char is (T (X));
         function Match (S : Str_Template) return Boolean is
           (T (X .. Natural'Min (T'Last, X + S'Length - 1)) = S);
      --  True if T contains S starting at X
         begin
         while X <= T'Last loop
            if Match (" (") or else Match (" #(") then
               X := X + 1; -- skip ' ' before '('
            elsif Match (" ^:") and then not Match (" ^:=") then
               X := X + 1; -- skip ' ' before ':'
            elsif Kind in Ada_Named_Stmt_Decl and then Match (" :") then
               X := X + 1; -- skip ' ' before ':' for statement name
            elsif Kind = Ada_Array_Type_Def and then Match (" !] of") then
               X := X + 1; -- skip ' ' before '('
            end if;

            Append (Result, C);
            X := X + 1;
         end loop;

         return Str_Template (To_String (Result));
      end;
   end Fix_RM_Spacing;

   function Replacements
     (Cmd : Command_Line; T : Str_Template) return Str_Template;

   function Replacements
     (Cmd : Command_Line; T : Str_Template) return Str_Template
   is
      Temp : W_Str_Access := new W_Str'(W_Str (T));
   begin
      --  Replacements for --no-separate-is

      if not Arg (Cmd, Separate_Is) then
         Temp := Replace_All (Temp, "# is", " is");
         Temp := Replace_All (Temp, "#+1 is", " is");
      end if;

      --  If the --no-end-id switch was given, do not insert names after "end"
      --  during the Convert_Tree_To_Ada pass. Instead, insert them during
      --  Insert_Comments_And_Blank_Lines, and only if they are present in the
      --  source.

      if not Arg (Cmd, End_Id) then
         Temp := Replace_All (Temp, "end !1", "end");
         Temp := Replace_All (Temp, "end !", "end/"); -- for Subp_Body
         Temp := Replace_All (Temp, "end?1 ~~~", "end");
         Temp := Replace_All (Temp, "end?2 ~~~", "end");
      end if;

      --  The --insert-blank-lines switch is mostly handled by
      --  Maybe_Blank_Line, but we need to handle "else" here,
      --  because it's not at the start of any construct.

      if Insert_Blank_Lines (Cmd) then
         Temp := Replace_All (Temp, "?else$", "?$else$");
      end if;

      return Result : constant Str_Template := Str_Template (Temp.all) do
         Free (Temp);
      end return;
   end Replacements;

   procedure Free is new Unchecked_Deallocation (Str_Template,
                                                 Str_Template_Ptr);

   function Replace_One
     (Kind : Ada_Tree_Kind; From, To : W_Str) return Str_Template;
   procedure Replace_One (Kind : Ada_Tree_Kind; From, To : W_Str);
   --  Replace From with To in the template for Kind. The function returns
   --  the new template; the procedure replaces it in the table.
   procedure Replace_Tmp (Kind : Ada_Tree_Kind; From, To : W_Str);
   --  Same as Replace_One, but requires that the entire template be
   --  replaced.

   function Replace_One
     (Kind : Ada_Tree_Kind; From, To : W_Str) return Str_Template
   is
      pragma Assert (From /= To);
      Temp : Str_Template renames Str_Template_Table (Kind).all;
   begin
      return Str_Template (Must_Replace (W_Str (Temp), From, To));
   end Replace_One;

   procedure Replace_One (Kind : Ada_Tree_Kind; From, To : W_Str) is
      Temp : Str_Template_Ptr := Str_Template_Table (Kind);
   begin
      Str_Template_Table (Kind) :=
        new Str_Template'(Replace_One (Kind, From, To));
      Free (Temp);
   end Replace_One;

   procedure Replace_Tmp (Kind : Ada_Tree_Kind; From, To : W_Str) is
   begin
      pragma Assert (From = W_Str (Str_Template_Table (Kind).all));
      Replace_One (Kind, From, To);
   end Replace_Tmp;

   package Alternative_Templates is

      --  Some templates that are used instead of the ones in
      --  Str_Template_Table.

      type Alt_Templates is
        (Empty_Alt, Hard_Break_Alt, Semi_LB_Alt, Semi_LB_LB_Alt, Semi_Soft,
         Subtree_Alt, Comma_Soft, Pragma_Alt, Parameter_Specification_Alt,
         Extended_Return_Stmt_Alt, Vertical_Agg_Alt, Nonvertical_Agg_Alt,
         Obj_Decl_Vertical_Agg_Alt, Assign_Vertical_Agg_Alt,
         Pos_Notation_Assoc_Alt, Single_Name_Vertical_Assoc_Alt,
         Single_Name_Assoc_Alt, Multi_Name_Vertical_Assoc_Alt,
         Multi_Name_Assoc_Alt, Comp_Clause_Alt, Handled_Stmts_With_Begin_Alt,
         Handled_Stmts_With_Do_Alt, Depends_Hack_Alt, Un_Op_No_Space_Alt,
         Un_Op_Space_Alt, Dot_Dot_Wrong_Alt, Dot_Dot_For_Alt, Dot_Dot_Alt,
         Indent_Soft_Alt, Outdent_Alt, Soft_Alt, For_Loop_Spec_Stmt_Alt,
         For_Loop_Spec_Quant_Alt, Tab_2_Alt, Tab_3_Alt, AM_Tab_4_Alt,
         Not_AM_Default_Alt, Qualified_Aggr_Alt, Select_When_Alt,
         Select_Or_When_Alt, Call_Threshold_Alt, Call_Alt, Par_Threshold_Alt,
         Par_Alt, Spec_Threshold_Alt, Spec_Alt, Subtype_Ind_Index_Alt,
         Subtype_Ind_Alt, Record_Type_Decl_Split_Alt, Record_Type_Decl_Alt,
         Record_Type_Decl_Aspects_Alt, Access_To_Subp_Decl_Alt,
         Enum_Array_Decl_Alt, Type_Decl_Alt, Boxy_Constrained_Alt);

      Str_Alt_Table : array (Alt_Templates) of Str_Template_Ptr;

      subtype Subp_Decl_Body_Kind is Ada_Tree_Kind with
          Predicate => Subp_Decl_Body_Kind in Ada_Subp_Decl |
             Ada_Subp_Renaming_Decl | Ada_Access_To_Subp_Def |
             Ada_Entry_Decl | Ada_Formal_Subp_Decl | Ada_Generic_Subp_Decl |
             Ada_Subp_Body_Stub | Ada_Subp_Body | Ada_Abstract_Subp_Decl |
             Ada_Expr_Function | Ada_Null_Subp_Decl | Ada_Entry_Body;

      Str_Subp_Decl_With_Hard_Breaks_Alt_Table : array
        (Ada_Tree_Kind) of Str_Template_Ptr;

      Tok_Alt_Table : array (Alt_Templates) of Tok_Template;

      Tok_Subp_Decl_With_Hard_Breaks_Alt_Table : array
        (Ada_Tree_Kind) of Tok_Template;

   end Alternative_Templates;

   procedure Init_Template_Tables (Cmd : Command_Line);
   --  We call this to initialize the template tables the first time
   --  Tree_To_Ada is called, so that we can base the initialization
   --  in part on the command-line options.

   procedure Init_Template_Tables (Cmd : Command_Line) is
      use Alternative_Templates;

      function Subp_Decl_With_Hard_Breaks
        (Cmd : Command_Line; Kind : Subp_Decl_Body_Kind) return Str_Template;
      --  For implementing Par_Threshold. This replaces the soft line break
      --  between parameters with a hard line break. If Is_Function is True,
      --  put a hard line break before "return". Put a hard line break before
      --  "is", if any.

      procedure Init_Alternative_Templates;

      procedure Init_Tok_Templates;

      function Subp_Decl_With_Hard_Breaks
        (Cmd : Command_Line; Kind : Subp_Decl_Body_Kind) return Str_Template
      is
         Has_Is : constant Boolean :=
           (case Kind is
              when Ada_Subp_Decl | Ada_Subp_Renaming_Decl |
                Ada_Access_To_Subp_Def | Ada_Entry_Decl |
                Ada_Formal_Subp_Decl | Ada_Generic_Subp_Decl => False,
              when Ada_Subp_Body_Stub | Ada_Subp_Body |
                Ada_Abstract_Subp_Decl | Ada_Expr_Function |
                Ada_Null_Subp_Decl | Ada_Entry_Body => True);

         T  : constant W_Str := W_Str (Str_Template_Table (Kind).all);
         T2 : constant W_Str :=
           (if Has_Is and then Arg (Cmd, Separate_Is) then
              Replace_All (T, "# is$", "$is$")
            else T);
         T3 : constant W_Str :=
           (if Has_Is and then Arg (Cmd, Separate_Is) then
              Replace_All (T2, "#+1 is$", "$is$")
            else T2);
      begin
         return Result : constant Str_Template := Str_Template (T3) do
            pragma Assert (True or else W_Str (Result) /= T); -- ???
         end return;
      end Subp_Decl_With_Hard_Breaks;

      procedure Init_Alternative_Templates is
         Stmts_And_Handlers : constant Str_Template :=
           "{~;$~;$}~" & "?exception$" & "{~$~}~";
      begin
         Str_Alt_Table :=
           (Empty_Alt                   => L (""),
            Hard_Break_Alt              => L ("$"),
            Semi_LB_Alt                 => L (";$"),
            Semi_LB_LB_Alt              => L (";$$"),
            Semi_Soft                   => L (";# "),
            Subtree_Alt                 => L ("!"),
            Comma_Soft                  => L (",# "),
            Pragma_Alt                  => L ("/?[ #(~,# ~)]~"),
            Parameter_Specification_Alt => L (" ^: "),
            Extended_Return_Stmt_Alt    => L ("return !/"),
            Vertical_Agg_Alt            => L ("(?~~ with #~?~,$~~)"),
            Nonvertical_Agg_Alt         => L ("#(?~~ with #~?~,# ~~)"),
            Obj_Decl_Vertical_Agg_Alt   =>
              L (Replace_One
                   (Ada_Object_Decl, From => ":=[# ~~]~!", To => ":=[$~~]~!")),
            Assign_Vertical_Agg_Alt     =>
              L (Replace_One
                   (Ada_Assign_Stmt, From => ":=[# !]", To => ":=[$!]")),
            Pos_Notation_Assoc_Alt      =>
              L ("?~~~!"), -- The "?~~~" generates nothing.

            Single_Name_Vertical_Assoc_Alt => L ("?~~ ^=>[$~!]"),
            Single_Name_Assoc_Alt          => L ("?~~ ^=>[# ~!]"),
            Multi_Name_Vertical_Assoc_Alt  => L ("?~ ^|#1 ~ ^=>[$~!]"),
            Multi_Name_Assoc_Alt           => L ("?~ ^|#1 ~ ^=>[# ~!]"),
            Comp_Clause_Alt => L ("! ^at `2! ^2range [#`3! ^3../[# `4!^4]]"),
            --  We need to ignore the ".." subtree, and put it explicitly in
            --  the template, because function Tab_Token checks for the ".".

            Handled_Stmts_With_Begin_Alt => L ("?begin$" & Stmts_And_Handlers),
            Handled_Stmts_With_Do_Alt    => L ("# ?do$" & Stmts_And_Handlers),
            Depends_Hack_Alt             => L ("?~~ ^=>~!"),
            Un_Op_No_Space_Alt => L ("/!"), Un_Op_Space_Alt => L ("/ !"),
            Dot_Dot_Wrong_Alt            => L ("[[#! ../[# !]]]"),
            --  This is wrong formatting, but gnatpp has an extra level
            --  of indentation here. And it doesn't have "#1", which
            --  actually would improve.???

            Dot_Dot_For_Alt         => L ("[#! ../[#1 !]]"),
            Dot_Dot_Alt => L ("[#! ../[# !]]"), Indent_Soft_Alt => L ("[#"),
            Outdent_Alt             => L ("]"), Soft_Alt => L ("#"),
            For_Loop_Spec_Stmt_Alt  => L ("for ! !? ~~~ !"),
            For_Loop_Spec_Quant_Alt => L ("! !? ~~~ !#"),
            Tab_2_Alt               => L ("^2"), Tab_3_Alt => L ("^3"),
            AM_Tab_4_Alt            => L (" ^4:=[# !]"),
            Not_AM_Default_Alt      => L (" :=[# !]"),
            Qualified_Aggr_Alt      => L ("!'[#!]"),
            --  If the thing after the ' is an aggregate, we leave out the
            --  parentheses here, because the aggregate will insert them. We
            --  want T'(X, Y, Z), not T'((X, Y, Z)).

            Select_When_Alt    => L ("? when ~~ =>~$" & "{?~;$~;$~}"),
            Select_Or_When_Alt => L ("or? when ~~ =>~$" & "{?~;$~;$~}"),
            Call_Threshold_Alt => L ("!?[$0(~,$0~)]~"),
            --  We use $0 instead of $ here, so that the indentation of these
            --  will not affect following comments.

            Call_Alt           => L ("!?[# (~,#1 ~)]~"),
            Par_Threshold_Alt  => L ("?[$(~;$~)]~"),
            Par_Alt            => L ("?[# (~;#1 ~)]~"),
            Spec_Threshold_Alt => L ("!? ~~~?~~~?[$ return] ~~~"),
            Spec_Alt           => L ("!? ~~~?~~~?[*#+2 return_] ~~~"),
            --  The above is the only place "one space in/outdent" is used (the
            --  "*" and "_" characters). This is to deal with something like:
            --
            --     function Some_Function
            --       (A_Parameter       : A_Parameter_Type;
            --        Another_Parameter : Another_Parameter_Type)
            --        return Result_Type;
            --       ^ Here we want the "return" indented one character
            --       | with respect to the "(", even though it is not
            --         inside the parentheses.

            Subtype_Ind_Index_Alt      => L ("?~~ ~!?~~~"),
            Subtype_Ind_Alt            => L ("?~~ ~!? ~~~"),
            Record_Type_Decl_Split_Alt => L ("type !! is${!}" & Aspects),
            Record_Type_Decl_Alt       => L ("type !! is !" & Aspects),
            --  Otherwise, we could have a line break just before the
            --  last semicolon.

            Record_Type_Decl_Aspects_Alt => L ("type !! is !" & Aspects),
            Access_To_Subp_Decl_Alt      => L ("type !! is !" & Aspects),
            --  gnatpp doesn't put a line break after "is" in this case.

            Enum_Array_Decl_Alt  => L ("type !! is$[!]" & Aspects),
            Type_Decl_Alt        => L ("type !! is[# !]" & Aspects),
            Boxy_Constrained_Alt => L ("(?~,# ~~)"));

         for Alt in Alt_Templates loop
            declare
               Temp : Str_Template_Ptr := Str_Alt_Table (Alt);
            begin
               Str_Alt_Table (Alt) :=
                 new Str_Template'(Fix_RM_Spacing (Cmd, Temp.all));
               Free (Temp);
            end;
         end loop;

         for K in Subp_Decl_Body_Kind loop
            Str_Subp_Decl_With_Hard_Breaks_Alt_Table (K) :=
              L (Fix_RM_Spacing (Cmd, Subp_Decl_With_Hard_Breaks (Cmd, K)));
         end loop;
      end Init_Alternative_Templates;

      procedure Init_Tok_Templates is
         use Scanner;
         function Compile_To_Instructions
           (Str : Str_Template_Ptr) return Tok_Template;
         --  Compile a Str_Template into a Tok_Template
         function Compile_Tokens
           (Cur : in out Tokn_Cursor; Stop : Scanner.Token_Kind)
            return Tok_Template;
         --  Compile, starting at Cur, stopping when we see Stop. This is
         --  called by Compile_To_Instructions with Stop = End_Of_Input,
         --  and recursively by Parse_Instruction for '?' (Opt_Subtree_Or_List)
         --  with Stop = '~'. Moves Cur past the Stop token.

         function Parse_Instruction (Cur : in out Tokn_Cursor) return Instr;

         --  Note that Compile_Tokens and Parse_Instruction are functions with
         --  side effect (on Cur), so we need to take care not to call them in
         --  a context that allows arbitrary order of evaluation.

         function Parse_Instruction (Cur : in out Tokn_Cursor) return Instr is
            procedure Check_Between (Result : Instr);
            --  Assert that Between doesn't contain any indentation or similar,
            --  so we don't need special processing in Interpret_Template to
            --  extract it.

            procedure Check_Between (Result : Instr) is
            begin
               if Result.Kind = Opt_Subtree_Or_List then
                  for X of Result.Between.Instructions.all loop
                     pragma Assert
                       (X.Kind in Hard_Break | Hard_Break_No_Comment |
                          Soft_Break | Tab | Verbatim);
                  end loop;
               end if;
            end Check_Between;

            subtype Illegal_Chars is Character with
                Predicate => Illegal_Chars in '~' | '"' | '\' | '%' |
                     '0' .. '9';
            pragma Assert (Str (Text (Cur)).S (1) not in Illegal_Chars);

            --  Start of processing for Parse_Instruction

         begin
            return Result : Instr do
               case Kind (Cur) is
                  when '$' =>
                     Next (Cur);
                     if Kind (Cur) = Numeric_Literal then
                        pragma Assert (Text (Cur) = Intern ("0"));
                        Result := (Kind => Hard_Break_No_Comment);
                        Next (Cur);
                     else
                        Result := (Kind => Hard_Break);
                     end if;

                  when '#' =>
                     Next (Cur);
                     declare
                        Plus      : constant Boolean := Kind (Cur) = '+';
                        Level_Inc : Nesting_Level_Increment;
                     begin
                        if Plus then
                           Next (Cur);
                        end if;

                        if Kind (Cur) = Numeric_Literal then
                           Level_Inc :=
                             Nesting_Level_Increment'Value
                               (Str (Text (Cur)).S);
                           Next (Cur);
                        else
                           Level_Inc := 0;
                        end if;

                        Result := (Soft_Break, Plus, Level_Inc);
                     end;

                  when '{' =>
                     Result := (Kind => Indent);
                     Next (Cur);
                  when '}' =>
                     Result := (Kind => Outdent);
                     Next (Cur);
                  when '[' =>
                     Result := (Kind => Continuation_Indent);
                     Next (Cur);
                  when ']' =>
                     Result := (Kind => Continuation_Outdent);
                     Next (Cur);
                  when '*' =>
                     Result := (Kind => One_Space_Indent);
                     Next (Cur);
                  when '_' =>
                     Result := (Kind => One_Space_Outdent);
                     Next (Cur);
                  when '(' =>
                     Result := (Kind => '(');
                     Next (Cur);
                  when ')' =>
                     Result := (Kind => ')');
                     Next (Cur);

                  when '^' =>
                     Next (Cur);
                     if Kind (Cur) = Numeric_Literal then
                        Result :=
                          (Tab,
                           Index_In_Line =>
                             Tab_Index_In_Line'Value (Str (Text (Cur)).S));
                        Next (Cur);
                     else
                        Result := (Tab, Index_In_Line => 1);
                     end if;

                  when '`' =>
                     Next (Cur);
                     if Kind (Cur) = Numeric_Literal then
                        Result :=
                          (Tab_Insert_Point,
                           Index_In_Line =>
                             Tab_Index_In_Line'Value (Str (Text (Cur)).S));
                        Next (Cur);
                     else
                        Result := (Tab_Insert_Point, Index_In_Line => 1);
                     end if;

                  when '!' =>
                     Next (Cur);
                     if Kind (Cur) = Numeric_Literal then
                        Result :=
                          (Required_Subtree,
                           Index => Query_Count'Value (Str (Text (Cur)).S));
                        Next (Cur);
                     else
                        Result := (Required_Subtree, Index => 0);
                     end if;

                  when '?' =>
                     Next (Cur);
                     declare
                        Index : Query_Count := 0;
                     begin
                        if Kind (Cur) = Numeric_Literal then
                           Index := Query_Count'Value (Str (Text (Cur)).S);
                           Next (Cur);
                        end if;

                        declare
                           Pre     : constant Tok_Template :=
                             Compile_Tokens (Cur, Stop => '~');
                           Between : constant Tok_Template :=
                             Compile_Tokens (Cur, Stop => '~');
                           Post    : constant Tok_Template :=
                             Compile_Tokens (Cur, Stop => '~');
                        begin
                           Result :=
                             (Opt_Subtree_Or_List, Index, Pre, Between, Post);
                        end;
                     end;

                  when '/' =>
                     Result := (Kind => Ignore_Subtree);
                     Next (Cur);

                  when others =>
                     for C of Str (Text (Cur)).S loop
                        pragma Assert (C not in Illegal_Chars);
                     end loop;

                     Result :=
                       (Kind => Verbatim, T_Kind => Kind (Cur),
                        Text => Text (Cur));
                     Next (Cur);
               end case;

               pragma Debug (Check_Between (Result));
            end return;
         end Parse_Instruction;

         function Compile_Tokens
           (Cur : in out Tokn_Cursor; Stop : Scanner.Token_Kind)
            return Tok_Template
         is
            Instructions : Instr_Vector;
            use Instr_Vectors;
            Max_Nesting_Increment : Nesting_Level_Increment := 0;

            procedure Set_Max (Level_Inc : Nesting_Level_Increment);
            --  Set Max_Nesting_Increment to account for Level_Inc

            procedure Set_Max (Level_Inc : Nesting_Level_Increment) is
            begin
               Max_Nesting_Increment :=
                 Nesting_Level_Increment'Max (Max_Nesting_Increment,
                                            Level_Inc);
            end Set_Max;
         begin
            while Kind (Cur) /= Stop loop
               declare
                  Inst : constant Instr := Parse_Instruction (Cur);
               begin
                  Append (Instructions, Inst);

                  if Inst.Kind = Soft_Break and then not Inst.Plus then
                     Set_Max (Inst.Level_Inc);
                  end if;

                  if Inst.Kind = Opt_Subtree_Or_List then
                     Set_Max (Inst.Pre.Max_Nesting_Increment);
                     Set_Max (Inst.Between.Max_Nesting_Increment);
                     Set_Max (Inst.Post.Max_Nesting_Increment);
                  end if;
               end;
            end loop;
            Next (Cur); -- skip the Stop token

            return (Instructions => new Instr_Array'(To_Array (Instructions)),
               Max_Nesting_Increment => Max_Nesting_Increment);
         end Compile_Tokens;

         function Compile_To_Instructions
           (Str : Str_Template_Ptr) return Tok_Template
         is
            Tokens  : aliased Tokn_Vec;
            Buf     : Buffer      := String_To_Buffer (W_Str (Str.all));
            Ignored : Boolean     :=
              Get_Tokns
                (Buf, Tokens, Utils.Ada_Version, Lang => Template_Lang);
            Cur     : Tokn_Cursor := First (Tokens'Unchecked_Access);
         begin
            pragma Assert (Kind (Cur) = Start_Of_Input);
            Next (Cur);
            return
              Result : constant Tok_Template :=
                Compile_Tokens (Cur, Stop => End_Of_Input) do
               pragma Assert (Back_To_Str_Templ (Result) = Str.all);
            end return;
         end Compile_To_Instructions;

         --  Start of processing for Init_Tok_Templates

      begin
         for K in Ada_Tree_Kind loop
            if Str_Template_Table (K) = null then
               pragma Assert (Tok_Template_Table (K).Instructions = null);
            else
               Tok_Template_Table (K) :=
                 Compile_To_Instructions (Str_Template_Table (K));
            end if;
         end loop;

         for Alt in Alt_Templates loop
            Tok_Alt_Table (Alt) :=
              Compile_To_Instructions (Str_Alt_Table (Alt));
         end loop;

         for K in Ada_Tree_Kind loop
            if Str_Subp_Decl_With_Hard_Breaks_Alt_Table (K) = null then
               pragma Assert
                 (Tok_Subp_Decl_With_Hard_Breaks_Alt_Table (K).Instructions =
                  null);
            else
               Tok_Subp_Decl_With_Hard_Breaks_Alt_Table (K) :=
                 Compile_To_Instructions
                   (Str_Subp_Decl_With_Hard_Breaks_Alt_Table (K));
            end if;
         end loop;
      end Init_Tok_Templates;

      --  Start of processing for Init_Template_Tables

   begin
      pragma Assert (not Template_Tables_Initialized);
      Template_Tables_Initialized := True;

      --  We can't initialize Str_Template_Table with an aggregate, because we
      --  refer to the Kind. The following case-within-loop construction may
      --  look odd, but it accomplishes two goals: the 'case' requires full
      --  coverage, so the items left null are done so explicitly, and the
      --  'for' provides the Kind value to each sub-case that needs it.
      --  The 'case' we're talking about is in Template_For_Kind.

      for Kind in Ada_Tree_Kind loop
         declare
            Temp : Str_Template_Ptr := Template_For_Kind (Kind);
         begin
            if Temp = null then
               Str_Template_Table (Kind) := null;
            else
               Str_Template_Table (Kind) :=
                 new Str_Template'
                   (Fix_RM_Spacing (Cmd, Replacements (Cmd, Temp.all), Kind));
               Free (Temp);
            end if;
         end;
      end loop;

      --  Some more-specific replacements

      --  For Separate_Loop, we want a hard line break before "loop"

      if Arg (Cmd, Separate_Loop) then
         Replace_One (Ada_Loop_Stmt, "?~~# ~loop$", "?~~$~loop$");
         Replace_One (Ada_For_Loop_Stmt, "?~~# ~loop$", "?~~$~loop$");
         Replace_One (Ada_While_Loop_Stmt, "?~~# ~loop$", "?~~$~loop$");
      end if;

      --  For No_Separate_Loop, we remove the soft line break before "loop"

      if Arg (Cmd, No_Separate_Loop) then
         Replace_One (Ada_Loop_Stmt, "?~~# ~loop$", "?~~ ~loop$");
         Replace_One (Ada_For_Loop_Stmt, "?~~# ~loop$", "?~~ ~loop$");
         Replace_One (Ada_While_Loop_Stmt, "?~~# ~loop$", "?~~ ~loop$");
      end if;

      --  For Separate_Then, we want a hard line break before "then"

      if Arg (Cmd, Separate_Then) then
         Replace_One (Ada_If_Stmt, "# then$", "$then$");
         Replace_One (Ada_Elsif_Stmt_Part, "# then$", "$then$");
      end if;

      --  For No_Separate_Then, we remove the soft line break before "then"

      if Arg (Cmd, No_Separate_Then) then
         Replace_One (Ada_If_Stmt, "# then$", " then$");
         Replace_One (Ada_Elsif_Stmt_Part, "# then$", " then$");
      end if;

      --  Replacements for Vertical_Enum_Types

      if Arg (Cmd, Vertical_Enum_Types) then
         Replace_Tmp (Ada_Enum_Type_Def, "(?~,#1 ~~)", "(?~,$~~)");
      end if;

      --  Replacements for Vertical_Array_Types

      if Arg (Cmd, Vertical_Array_Types) then
         Replace_Tmp (Ada_Array_Type_Def, "array[# !] of !", "array!$of !");
         Replace_Tmp
           (Ada_Constrained_Array_Indices, "(?~,# ~~)", "?{{ (~,# ~)}}~");
      --  Note the double indentation. It just happens that 3 more
      --  characters place us just after "array ". Perhaps we should
      --  use the Paren_Stack mechanism in PP.Formatting.
      end if;

      --  Replacements for Vertical_Named_Aggregates

      if Arg (Cmd, Vertical_Named_Aggregates) then
         Replace_Tmp (Ada_Enum_Rep_Clause, "for ! use [!]", "for ! use$[!]");
      end if;

      --  Replacements for Vertical_Case_Alternatives

      declare
         subtype When_Kinds is Ada_Node_Kind_Type with
             Predicate => When_Kinds in Ada_Case_Stmt_Alternative |
                --  ???                                  Ada_Case_Expr_Alternative |

                Ada_Variant;
      --  Things that start with "when" that we want to treat
      --  alike here.
         begin
         if Arg (Cmd, Vertical_Case_Alternatives) then
            for X in When_Kinds loop
               Replace_One (X, "when[ ?~ ^|# ~~]", "when{ ?~$| ~~}");
            --  Perhaps this should be unconditional, not just for
            --  Vertical_Case_Alternatives.
            end loop;
            Replace_One
              (Ada_Case_Expr_Alternative, "when[ ?~ |# ~~]", "when{ ?~$| ~~}");
         end if;
      end;

      --  Replacements for Split_Line_Before_Record

      if Arg (Cmd, Split_Line_Before_Record) then
         Replace_Tmp
           (Ada_Record_Rep_Clause,
            "for ! use record? at mod ~~;~${?~;$~;$~}end record",
            "for ! use${record? at mod ~~;~${?~;$~;$~}end record}");
      end if;

      --  Replacements for Indent_Named_Statements

      if Arg (Cmd, Indent_Named_Statements) then
         Replace_Tmp (Ada_Named_Stmt, "! :$! !1", "! :${! !1}");
      end if;

      Init_Alternative_Templates;

      --  Now do some validity checking on the templates

      for Kind in Ada_Tree_Kind loop
         declare
            T : constant Str_Template_Ptr := Str_Template_Table (Kind);
         begin
            if T /= null then
               declare
                  subtype Constrained_Query_Count is Query_Count range 1 .. 9;
                  --  ???lal doesn't support reflection: Num_Queries (Kind);
                  Subtree_Count : Query_Count := 0;
               begin
                  for J in T'Range loop
                     case T (J) is
                        when '!' | '?' =>
                           if J < T'Last and then T (J + 1) in '1' .. '9' then
                              pragma Warnings (Off, "if it is invalid");
                              pragma Assert
                                (Query_Index (Char_To_Digit (T (J + 1))) in
                                   Constrained_Query_Count);
                              pragma Warnings (On, "if it is invalid");
                           else
                              Subtree_Count := Subtree_Count + 1;
                           end if;

                           --  ??? "{" is always preceded by "$" (not always
                           --  true for lalpp); we might want a short-hand for
                           --  "${".

                        when '{' =>
                           if Kind in Ada_Component_List | Ada_Public_Part |
                               Ada_Generic_Formal_Part | Ada_Array_Type_Def |
                               Ada_Constrained_Array_Indices |

                               Ada_Case_Stmt_Alternative |
                               Ada_Case_Expr_Alternative | Ada_Variant
                           then
                              null;
                           else
                              pragma Assert (T (J - 1) = '$');
                           end if;

                        when others =>
                           null;
                     end case;
                  end loop;

                  if Subtree_Count /= Constrained_Query_Count'Last then
                     if False then -- ???See above.
                        raise Program_Error
                          with "Wrong Subtree_Count: " & Kind'Img;
                     end if;
                  end if;
               end;
            end if;
         end;
      end loop;

      Init_Tok_Templates;
   end Init_Template_Tables;

   --  Debugging printouts:
   --  See also Libadalang.Debug.
   pragma Warnings (Off);
   pragma Style_Checks (Off);
   procedure knd (X : Ada_Node) is
      use Utils.Dbg_Out;
   begin
      Utils.Dbg_Out.Output_Enabled := True;
      Put ("\1\n", Kind (X)'Img);
   end knd;

   procedure psloc (X : Ada_Node) is

      function Lines_String
        (Sloc_Range : Slocs.Source_Location_Range) return String is
        (Image (Integer (Sloc_Range.Start_Line)) & ": " &
         Image (Integer (Sloc_Range.End_Line)));

      use Utils.Dbg_Out;
   begin
      Utils.Dbg_Out.Output_Enabled := True;
      Put ("\1\n", Lines_String (Sloc_Range (X)));
   end psloc;

   procedure nn (X : Ada_Node) is
      use Utils.Dbg_Out;
   begin
      Utils.Dbg_Out.Output_Enabled := True;
      Put ("\1\n", (if Is_Nil (X) then "null" else Short_Image (X)));
   end nn;

   procedure ppp (X : Ada_Node) is
      use Utils.Dbg_Out;
   begin
      nn (X);
      if Present (X) then
         Print (X);
      end if;
   end ppp;

   procedure Put_Ada_Node_Array (X : Ada_Node_Array) is
      use Utils.Dbg_Out;
   begin
      for N of X loop
         nn (N);
         Put ("----------------\n");
      end loop;
   end Put_Ada_Node_Array;

   procedure Put_Child_Record (C : Child_Record) is
      use Utils.Dbg_Out;
   begin
      case C.Kind is
         when Child =>
            Put ("Child: \1\n", Short_Image (C.Node));
         when Trivia =>
            declare
               Trivia_Data : constant Token_Data_Type := Data (C.Trivia);
            begin
               Put
                 ("Trivia: \1 ""\2"" \3\n", Kind (Trivia_Data)'Img,
                  To_UTF8 (Text_To_W_Str (Text (C.Trivia))),
                  Slocs.Image (Sloc_Range (Trivia_Data)));
            end;
      end case;
   end Put_Child_Record;

   procedure Put_Children_Array (A : Children_Array) is
      use Utils.Dbg_Out;
   begin
      for I in A'Range loop
         Put ("\1: ", Image (I));
         Put_Child_Record (A (I));
      end loop;
   end Put_Children_Array;

   procedure Dump (Tool : in out Pp_Tool; Message : String := "") is
      pragma Unreferenced (Tool);
      use Utils.Formatted_Output;
   begin
      if Debug_Flag_V then
         Put ("\1\n", Message);
      end if;
   end Dump;

   procedure Put_Str_Templates is
      use Formatted_Output, Ada.Strings.Fixed;
   begin
      Put ("--  Templates:\n");

      for Kind in Ada_Tree_Kind loop
         if Str_Template_Table (Kind) /= null then
            declare
               T : constant String :=
                 To_UTF8 (W_Str (Str_Template_Table (Kind).all));
            begin
               Put ("--  \1 => \2", Capitalize (Kind'Img), """" & T & """");
               if Count (T, "[") /= Count (T, "]") then
                  Put ("    MISMATCHED [...]");
                  raise Program_Error;
               end if;
               if Count (T, "{") /= Count (T, "}") then
                  Put ("    MISMATCHED {...}");
                  raise Program_Error;
               end if;
               if Count (T, "(") /= Count (T, ")") then
                  Put ("    MISMATCHED (...)");
                  raise Program_Error;
               end if;
               Put ("\n");
            end;
         end if;
      end loop;
      Put ("--  End templates.\n");
   end Put_Str_Templates;
   pragma Style_Checks (On);
   pragma Warnings (On);

   function Is_Generic_Formal_Object_Decl (Tree : Ada_Tree) return Boolean;
   --  True if Tree is a generic formal object declaration

   function Is_Generic_Formal_Object_Decl (Tree : Ada_Tree) return Boolean is
      P       : Ada_Tree := Parent (Tree);
      Formals : Ada_Tree;
   begin
      return Result : Boolean := False do
         if Tree.Kind = Ada_Object_Decl then
            if P.Kind = Ada_Ada_Node_List then
               P := Parent (P);
               if P.Kind in Ada_Generic_Package_Decl | Ada_Generic_Subp_Decl
               then
                  if P.Kind = Ada_Generic_Package_Decl then
                     Formals :=
                       P.As_Generic_Package_Decl.F_Formal_Part.As_Ada_Node;
                  else
                     Formals :=
                       P.As_Generic_Subp_Decl.F_Formal_Part.As_Ada_Node;
                  end if;
                  for Formal of Formals.Children loop
                     if Tree = Formal then
                        Result := True;
                        exit;
                     end if;
                  end loop;
               end if;
            end if;
         end if;
      end return;
   end Is_Generic_Formal_Object_Decl;

   pragma Style_Checks ("M85");
   procedure Tree_To_Ada_2
     (Root    : Ada_Node; Cmd : Utils.Command_Lines.Command_Line;
      Partial : Boolean)
   is
      function Id_With_Casing
        (Id : W_Str; Kind : Opt_ASIS_Elems; Is_Predef : Boolean) return W_Str;
      --  This handles casing of defining names and usage names, converting to
      --  the appropriate case based on command-line options. Kind is the kind of
      --  declaration denoted by Id, or an attribute, or nil. Is_Predef is True if
      --  Id denotes a predefined Ada or GNAT identifier.
      --
      --  This is called early (during Subtree_To_Ada). Casing of reserved words
      --  is handled later, in a separate pass (see Keyword_Casing), because they
      --  are not explicit in the tree, except that operator symbols are handled
      --  here. All of the Str_Templates have reserved words in lower case.
      --
      --  Id_With_Casing is used for Def_Names, Usage_Names and pragmas. For
      --  Def_Names, the Kind comes from the Symbol_Table, which only works
      --  because it's within one unit. That doesn't work for Usage_Names; we
      --  use the Decl_Kind attribute, which includes declared entities and
      --  attributes. For pragmas, we use the Kind of the pragma node.
      --
      --  Is_Predef is True if Id is a usage name that denotes a predefined
      --  entity. It is always False for defining names and pragmas.

      function Init_Use_Dictionary return Boolean;
      function Init_Use_Dictionary return Boolean is
      begin
         for D_Name of Arg (Cmd, Dictionary) loop
            if D_Name.all /= "-" then
               return True;
            end if;
         end loop;
         return False;
      end Init_Use_Dictionary;

      Use_Dictionary : constant Boolean := Init_Use_Dictionary;
      --  True if there are any dictionary files to use

      function Init_Use_Predefined_Casing return Boolean;
      function Init_Use_Predefined_Casing return Boolean is
      begin
         for D_Name of Arg (Cmd, Dictionary) loop
            if D_Name.all = "-" then
               return False;
            end if;
         end loop;
         return True;
      end Init_Use_Predefined_Casing;

      Use_Predefined_Casing : constant Boolean := Init_Use_Predefined_Casing;
      --  True if the -D- switch was NOT given

      Name_CPP_Class       : aliased constant W_Str := "CPP_Class";
      Name_CPP_Constructor : aliased constant W_Str := "CPP_Constructor";
      Name_CPP_Virtual     : aliased constant W_Str := "CPP_Virtual";
      Name_CPP_Vtable      : aliased constant W_Str := "CPP_Vtable ";
      Name_CPU             : aliased constant W_Str := "CPU";
      Name_Persistent_BSS  : aliased constant W_Str := "Persistent_BSS";
      Name_SPARK_Mode      : aliased constant W_Str := "SPARK_Mode";
      Name_Use_VADS_Size   : aliased constant W_Str := "Use_VADS_Size";
      Name_VADS_Size       : aliased constant W_Str := "VADS_size";

      Special_Case_Names : constant array
        (Positive range <>) of access constant W_Str :=
        (Name_CPP_Class'Access, Name_CPP_Constructor'Access,
         Name_CPP_Virtual'Access, Name_CPP_Vtable'Access, Name_CPU'Access,
         Name_Persistent_BSS'Access, Name_SPARK_Mode'Access,
         Name_Use_VADS_Size'Access, Name_VADS_Size'Access);

      function Id_With_Casing
        (Id : W_Str; Kind : Opt_ASIS_Elems; Is_Predef : Boolean) return W_Str
      is
         pragma Assert (Id'First = 1);

         --  If it's a character literal, we want As_Declared -- it would be
         --  unfortunate to turn 'a' into 'A'. Operators go by keyword casing.
         --  Operator symbols (quoted) do so also, which seems wrong, but we're
         --  going to mimic the old gnatpp for now. Note that some reserved
         --  words can be an operator or an attribute name; hence the check for
         --  Ada_Attribute_Ref below. Predefined names use As_Declared unless
         --  Use_Predefined_Casing is turned off. For everything else, we use
         --  the appropriate option based on the Kind.

         Casing : constant PP_Casing :=
           (if
              Id (1) = '''
            then
              As_Declared
            elsif
              Id (1) = '"'
            then PP_Keyword_Casing (Cmd)
            elsif Is_Predef and then Use_Predefined_Casing then As_Declared
            else
              (case Kind is
                 when Ada_Attribute_Ref | Ada_Update_Attribute_Ref =>
                   PP_Attribute_Casing (Cmd),
                 when Ada_Pragma_Node => PP_Pragma_Casing (Cmd),
                 when Ada_Enum_Literal_Decl => PP_Enum_Casing (Cmd),
                 when Ada_Type_Decl | Ada_Incomplete_Type_Decl |
                   Ada_Incomplete_Tagged_Type_Decl | Ada_Subtype_Decl |
                   Ada_Task_Type_Decl | Ada_Task_Body | Ada_Protected_Body |
                   Ada_Protected_Type_Decl | Ada_Generic_Formal_Type_Decl =>
                   PP_Type_Casing (Cmd),
                 when Ada_Number_Decl => PP_Number_Casing (Cmd),
                 when Null_Kind =>
                   --                   (if PP_Name_Casing (Cmd) = As_Declared then Mixed
         --                    else PP_Name_Casing (Cmd)),
         PP_Name_Casing (Cmd),
                 when others => PP_Name_Casing (Cmd)));
         --  The Null_Kind case is for identifiers specific to pragmas
         --  and the like.

         use Dictionaries;
      begin
         if Use_Dictionary then
            return Result : W_Str := Id do
               Check_With_Dictionary (Ada_Name => Result, Casing => Casing);
            end return;
         else
            case Casing is
               when Lower_Case =>
                  return To_Lower (Id);

               when Upper_Case =>
                  return To_Upper (Id);

               when Mixed =>
                  if Kind in Ada_Attribute_Ref | Ada_Update_Attribute_Ref |
                      Ada_Pragma_Node
                  then
                     --  Handle pragma and attribute names that are special cases
                     --  (some portion should be in ALL CAPS).

                     declare
                        Lower : constant W_Str := To_Lower (Id);
                     begin
                        for Special of Special_Case_Names loop
                           if Lower = To_Lower (Special.all) then
                              return Special.all;
                           end if;
                        end loop;
                     end;
                  end if;

                  return Capitalize (Id);

               when As_Declared =>
                  return Id;
            end case;
         end if;
      end Id_With_Casing;

      use Scanner;

      --  The following append a token to V, and also put the text in the
      --  output buffer. We should get rid of the textual output.

      procedure Append_And_Put (V : in out Tokn_Vec; X : Same_Text_Kind);
      procedure Append_And_Put
        (V : in out Tokn_Vec; X : Stored_Text_Kind; Tx : Symbol);
      --  Call Scanner.Append_Tokn, and also sends to the Out_Buf

      procedure Append_And_Put (V : in out Tokn_Vec; X : Ada_Op);

      procedure Append_And_Put (V : in out Tokn_Vec; X : Same_Text_Kind) is
      begin
         pragma Assert (X not in EOL_Token);
         Append_Tokn (V, X);
      end Append_And_Put;

      procedure Append_And_Put
        (V : in out Tokn_Vec; X : Stored_Text_Kind; Tx : Symbol)
      is
      begin
         Append_Tokn (V, X, Tx);
      end Append_And_Put;

      procedure Append_And_Put (V : in out Tokn_Vec; X : Ada_Op) is
      begin
         case X is
            when Ada_Op_And =>
               Append_And_Put (V, Res_And);
            when Ada_Op_Or =>
               Append_And_Put (V, Res_Or);
            when Ada_Op_Or_Else =>
               Append_And_Put (V, Res_Or);
               Append_And_Put (V, Spaces, Name_Space);
               Append_And_Put (V, Res_Else);
            when Ada_Op_And_Then =>
               Append_And_Put (V, Res_And);
               Append_And_Put (V, Spaces, Name_Space);
               Append_And_Put (V, Res_Then);
            when Ada_Op_Concat =>
               Append_And_Put (V, '&');
            when Ada_Op_Xor =>
               Append_And_Put (V, Res_Xor);
            when Ada_Op_In =>
               Append_And_Put (V, Res_In);
            when Ada_Op_Not_In =>
               Append_And_Put (V, Res_Not);
               Append_And_Put (V, Spaces, Name_Space);
               Append_And_Put (V, Res_In);
            when Ada_Op_Abs =>
               Append_And_Put (V, Res_Abs);
            when Ada_Op_Not =>
               Append_And_Put (V, Res_Not);
            when Ada_Op_Pow =>
               Append_And_Put (V, Exp_Op);
            when Ada_Op_Mult =>
               Append_And_Put (V, '*');
            when Ada_Op_Div =>
               Append_And_Put (V, '/');
            when Ada_Op_Mod =>
               Append_And_Put (V, Res_Mod);
            when Ada_Op_Rem =>
               Append_And_Put (V, Res_Rem);
            when Ada_Op_Plus =>
               Append_And_Put (V, '+');
            when Ada_Op_Minus =>
               Append_And_Put (V, '-');
            when Ada_Op_Eq =>
               Append_And_Put (V, '=');
            when Ada_Op_Neq =>
               Append_And_Put (V, Not_Equal);
            when Ada_Op_Lt =>
               Append_And_Put (V, '<');
            when Ada_Op_Lte =>
               Append_And_Put (V, Less_Or_Equal);
            when Ada_Op_Gt =>
               Append_And_Put (V, '>');
            when Ada_Op_Gte =>
               Append_And_Put (V, Greater_Or_Equal);
            when Ada_Op_Double_Dot =>
               Append_And_Put (V, Dot_Dot);
         end case;
      end Append_And_Put;

      procedure Indent (Amount : Integer);
      --  Indent by the given number of columns. Negative Amount for "outdent".

      procedure Indent (Amount : Integer) is
         pragma Assert
           (abs Amount in 0 | 1 | PP_Indentation (Cmd) |
              PP_Indent_Continuation (Cmd) | Arg (Cmd, Initial_Indentation));
         Last_LBI : constant Line_Break_Index := All_LBI (Last (All_LBI));
         Last_LB  : Line_Break renames All_LB (Last_LBI);
      begin
         Cur_Indentation := Cur_Indentation + Amount;

         if Last_LB.Hard and then Last_LB.Tok = Last (New_Tokns'Access) then
            Last_LB.Indentation := Cur_Indentation;
         end if;
      end Indent;

      procedure Append_Line_Break
        (Hard : Boolean; Affects_Comments : Boolean; Level : Nesting_Level;
         Kind : Ada_Tree_Kind);

      function New_Level
        (Cur_Level : Nesting_Level; TT : Tok_Template) return Nesting_Level;
      --  Compute a new nesting level for a subtree. This is usually one more than
      --  the current level, but we also add in Max_Nesting_Increment.

      procedure Append_Line_Break
        (Hard : Boolean; Affects_Comments : Boolean; Level : Nesting_Level;
         Kind : Ada_Tree_Kind)
      is
         pragma Unreferenced (Kind);
      begin
         --  If we see two line breaks in a row, we take the least indented one.

         if not Is_Empty (All_LBI) then
            declare
               Last_LBI : constant Line_Break_Index :=
                 All_LBI (Last (All_LBI));
               Last_LB  : Line_Break renames All_LB (Last_LBI);
            begin
               if Hard
                 and then Scanner.Kind (Last (New_Tokns'Access)) =
                   Enabled_LB_Token
               then
                  if Last_LB.Indentation > Cur_Indentation then
                     Last_LB.Indentation := Cur_Indentation;
                  end if;

                  if not Insert_Blank_Lines (Cmd) then
                     return;
                  end if;
               end if;
            end;
         end if;

         declare
            Tok : constant Scanner.Tokn_Cursor :=
              Next (Last (New_Tokns'Access));
         begin
            Append_Line_Break_Tokn
              (New_Tokns, Enabled => Hard, Index => Last_Index (All_LB) + 1);
            --  Note that the Line_Break_Token replaces the EOL_Token token

            Append
              (All_LB,
               Line_Break'
                 (Tok => Tok, Tokn_Val => Token_At_Cursor (Tok), Hard => Hard,
                  Affects_Comments => Affects_Comments, Enabled => Hard,
                  Source_Line_Breaks_Kludge => False, Level => Level,
                  Indentation               => Cur_Indentation, Length => <>,
                  --            Kind        => Kind,

                  Internal_To_Comment => False,
                  UID                 => Next_Line_Break_Unique_Id));
         end;
         Next_Line_Break_Unique_Id := Next_Line_Break_Unique_Id + 1;
         Append (All_LBI, Last_Index (All_LB));
      end Append_Line_Break;

      function New_Level
        (Cur_Level : Nesting_Level; TT : Tok_Template) return Nesting_Level
      is
      begin
         return Cur_Level + TT.Max_Nesting_Increment + 1;
      end New_Level;

      procedure Subtree_To_Ada
        (Tree            : Ada_Tree; Cur_Level : Nesting_Level;
         Index_In_Parent : Query_Index);
      --  We recursively walk the tree, and for most nodes, take the template
      --  from Str_Template_Table, and pass it to Interpret_Template. Some nodes
      --  need special casing, and bypass the Str_Template_Table. Subtree_To_Ada is
      --  directly recursive, and also mutually recursive with Interpret_Template.

      procedure Convert_Tree_To_Ada (Tree : Ada_Tree);
      --  Subtree_To_Ada with initial values for Cur_Level and Index_In_Parent,
      --  along with some fix-ups. In particular, we add a sentinel Line_Break
      --  at the beginning, and a sentinel Tab at the end.

      type Tree_Stack_Index is new Positive;
      subtype Tree_Stack_Count is
        Tree_Stack_Index'Base range 0 .. Tree_Stack_Index'Last;
      type Tree_Array is array (Tree_Stack_Index range <>) of Ada_Tree;
      package Tree_Stacks is new Utils.Vectors (Tree_Stack_Index, Ada_Tree,
                                                Tree_Array);
      use Tree_Stacks;
      --  use all type Tree_Stacks.Vector;

      Tree_Stack : Tree_Stacks.Vector;
      --  Stack of trees that we're in the process of traversing. Pushed and
      --  popped at the beginning and end of Subtree_To_Ada.

      function Ancestor_Tree (N : Tree_Stack_Count) return Ada_Tree;
      --  Returns the N'th ancestor of the current tree. Ancestor_Tree (0) is
      --  the current tree, Ancestor_Tree (1) is the parent of the current
      --  tree, Ancestor (2) is the grandparent of the current tree, and so
      --  on. Nil if the tree isn't deep enough.

      function Ancestor_Tree (N : Tree_Stack_Count) return Ada_Tree is
      begin
         if Last_Index (Tree_Stack) <= N then
            return No_Ada_Node;
         else
            return Tree_Stack (Last_Index (Tree_Stack) - N);
         end if;
      end Ancestor_Tree;

      function Parent_Tree return Ada_Tree is (Ancestor_Tree (1));

      pragma Warnings (Off); -- for debugging
      procedure Dump_Ancestors;
      procedure Dump_Ancestors is
         N    : Tree_Stack_Count := 0;
         Tree : Ada_Tree;
         use Utils.Dbg_Out;
      begin
         Utils.Dbg_Out.Output_Enabled := True;
         Put ("Ancestors:\n");
         loop
            Tree := Ancestor_Tree (N);
            exit when Tree.Is_Null;
            Put ("\1\t\2\n", Image (Integer (N)), Short_Image (Tree));
            N := N + 1;
         end loop;
      end Dump_Ancestors;
      pragma Warnings (On);

      Label_Seen : Boolean := False;
      --  See the comments in Do_Label below for an explanation of this.

      procedure Subtree_To_Ada
        (Tree            : Ada_Tree; Cur_Level : Nesting_Level;
         Index_In_Parent : Query_Index)
      is
         procedure Subtrees_To_Ada
           (Tree : Ada_Tree; Pre, Between, Post : Tok_Template);

         procedure Interpret_Template
           (TT        : Tok_Template   := Tok_Template_Table (Tree.Kind);
            Subtrees  : Ada_Tree_Array := Pp.Actions.Subtrees (Tree);
            Cur_Level : Nesting_Level  := Subtree_To_Ada.Cur_Level;
            Kind      : Ada_Tree_Kind  := Tree.Kind);
         --  Interpret the template, printing literal characters, and recursively
         --  calling Subtree_To_Ada when the template calls for a subnode. Kind is
         --  for debugging.

         procedure Interpret_Alt_Template
           (Alt       : Alternative_Templates.Alt_Templates;
            Subtrees  : Ada_Tree_Array := Pp.Actions.Subtrees (Tree);
            Cur_Level : Nesting_Level  := Subtree_To_Ada.Cur_Level;
            Kind      : Ada_Tree_Kind  := Tree.Kind);
         --  Call Interpret_Template with one of the alternative templates

         procedure Interpret_Alt_Template
           (Alt       : Alternative_Templates.Alt_Templates;
            Subtrees  : Ada_Tree_Array := Pp.Actions.Subtrees (Tree);
            Cur_Level : Nesting_Level  := Subtree_To_Ada.Cur_Level;
            Kind      : Ada_Tree_Kind  := Tree.Kind)
         is
         begin
            Interpret_Template
              (Alternative_Templates.Tok_Alt_Table (Alt), Subtrees, Cur_Level,
               Kind);
         end Interpret_Alt_Template;

         procedure Append_Tab
           (Parent, Tree  : Ada_Tree_Base; Token_Text : Symbol;
            Index_In_Line : Tab_Index_In_Line; Is_Insertion_Point : Boolean);
         --  Append a Tab_Rec onto Tabs.
         --
         --  Handling of "fake tabs":
         --  Fake tabs are used to deal with situations like this:
         --
         --     A_Long_Var_Name      : T          := 123;
         --     X                    : Ada_Long_Type_Name;
         --     A_Long_Constant_Name : constant T := 123;
         --
         --  where we wish to align the ":" and ":=" tokens. But the
         --  Insert_Alignment algorithm doesn't align things unless subsequent
         --  lines "match", which includes having the same number of tabs. But X
         --  has no ":=", so we add a fake tab so it will match the preceding and
         --  following lines.
         --
         --  Append_Tab inserts a fake tab after each ":" tab. If there is no
         --  ":=" following, the fake tab remains. If there IS a ":=", a real
         --  tab replaces the fake one.
         --
         --  Fake tabs initially have the same position as the preceding ":" tab.
         --  When Insert_Alignment calculates Max_Col, it ignores the fake ones,
         --  so they won't push anything further to the right. It sets the Col of
         --  the fake ones to Max_Col; hence Num_Blanks will be zero, so fake tabs
         --  won't insert any blanks.
         --
         --  Context clauses are handled in a similar manner:
         --
         --     with Ada.Characters.Handling; use Ada.Characters.Handling;
         --     with Ada.Exceptions;
         --     with Ada.Strings;             use Ada.Strings;

         procedure Append_Tab
           (Parent, Tree  : Ada_Tree_Base; Token_Text : Symbol;
            Index_In_Line : Tab_Index_In_Line; Is_Insertion_Point : Boolean)
         is
            pragma Assert
              (Token_Text in Name_Tab_Insertion_Point | Name_With | Name_Use |
                 Name_Tab_In_Out | Name_Assign | Name_Colon | Name_Arrow |
                 Name_Bar | Name_At | Name_Range | Name_Dot_Dot | Name_R_Sq);

            Pa : Ada_Tree_Base := Parent;
            Tr : Ada_Tree_Base := Tree;

            procedure Maybe_Replace_Fake_Tab;
            --  Replace a fake tab with a real one, if appropriate. In
            --  particular, if the last tab is fake, and the current one has
            --  the same Index_In_Line, Tree, and Parent, then the current one
            --  replaces the fake one. We don't physically delete the Tab_Rec
            --  from the table, nor the Tab_Token from the token stream; we
            --  just mark it as Deleted, so later phases know to ignore it.

            procedure Maybe_Replace_Fake_Tab is
            begin
               if Is_Empty (Tabs) then
                  return;
               end if;

               declare
                  Tb : Tab_Rec renames Last_Ptr (Tabs).all;
               begin
                  if Tb.Is_Fake and then Tb.Index_In_Line = Index_In_Line
                    and then Tb.Tree = Tr and then Tb.Parent = Pa
                  then
                     pragma Assert (Tb.Token = Token_Text);
                     pragma Assert
                       ((Token_Text = Name_Assign
                         and then Index_In_Line in 2 | 4)
                        or else
                        (Token_Text = Name_Use and then Index_In_Line = 2));
                     pragma Assert (not Is_Insertion_Point);
                     pragma Assert (not Tb.Deleted);
                     Tb.Deleted := True;
                  end if;
               end;
            end Maybe_Replace_Fake_Tab;

            --  Start of processing for Append_Tab

         begin
            if not Alignment_Enabled (Cmd) then
               return;
            end if;

            if Present (Tree) and then Tree.Kind = Ada_With_Clause then
               if not Tree.As_With_Clause.F_Has_Limited
                 and then not Tree.As_With_Clause.F_Has_Private
               then
                  Pa := No_Ada_Node;
                  Tr := No_Ada_Node;
               else
                  return; -- ignore "limited with" and "private with"
               end if;
            end if;

            Maybe_Replace_Fake_Tab;

            Append
              (Tabs,
               Tab_Rec'
                 (Pa, Tr, Token => Token_Text, Insertion_Point => <>,
                  Index_In_Line => Index_In_Line, Col => <>, Num_Blanks => <>,
                  Is_Fake => False, Is_Insertion_Point => Is_Insertion_Point,
                  Deleted       => False));
            Append_Tab_Tokn (New_Tokns, Last_Index (Tabs));

            --  Append a fake tab if appropriate

            if Present (Tree) and then not Is_Insertion_Point then
               case Tree.Kind is
                  when Ada_Object_Decl | Ada_Extended_Return_Stmt_Object_Decl |
                    Ada_Number_Decl | Ada_Discriminant_Spec |
                    Ada_Component_Decl =>
                     if Is_Generic_Formal_Object_Decl (Tree) then
                        pragma Assert (Tree.Kind = Ada_Object_Decl);
                        --  generic formal object

                        if Index_In_Line = 3 then
                           pragma Assert (Token_Text = Name_Tab_In_Out);
                           Append
                             (Tabs,
                              Tab_Rec'
                                (Parent             => Pa, Tree => Tr,
                                 Token => Name_Assign, Insertion_Point => <>,
                                 Index_In_Line      => 4, Col => <>,
                                 Num_Blanks         => <>, Is_Fake => True,
                                 Is_Insertion_Point => False,
                                 Deleted            => False));
                           Append_Tab_Tokn (New_Tokns, Last_Index (Tabs));
                        end if;
                     else
                        if Index_In_Line = 1 then
                           pragma Assert (Token_Text = Name_Colon);
                           Append
                             (Tabs,
                              Tab_Rec'
                                (Parent             => Pa, Tree => Tr,
                                 Token => Name_Assign, Insertion_Point => <>,
                                 Index_In_Line      => 2, Col => <>,
                                 Num_Blanks         => <>, Is_Fake => True,
                                 Is_Insertion_Point => False,
                                 Deleted            => False));
                           Append_Tab_Tokn (New_Tokns, Last_Index (Tabs));
                        end if;
                     end if;

                  when Ada_Param_Spec =>
                     if Index_In_Line = 3 then
                        pragma Assert (Token_Text = Name_Tab_In_Out);
                        Append
                          (Tabs,
                           Tab_Rec'
                             (Parent => Pa, Tree => Tr, Token => Name_Assign,
                              Insertion_Point    => <>, Index_In_Line => 4,
                              Col => <>, Num_Blanks => <>, Is_Fake => True,
                              Is_Insertion_Point => False, Deleted => False));
                        Append_Tab_Tokn (New_Tokns, Last_Index (Tabs));
                     end if;

                  when Ada_With_Clause =>
                     if Index_In_Line = 1 then
                        pragma Assert (Token_Text = Name_With);
                        Append
                          (Tabs,
                           Tab_Rec'
                             (Parent => Pa, Tree => Tr, Token => Name_Use,
                              Insertion_Point    => <>, Index_In_Line => 2,
                              Col => <>, Num_Blanks => <>, Is_Fake => True,
                              Is_Insertion_Point => False, Deleted => False));
                        Append_Tab_Tokn (New_Tokns, Last_Index (Tabs));
                     end if;

                  when Ada_Variant | Ada_Quantified_Expr | Ada_Assign_Stmt |
                    Ada_Case_Stmt_Alternative | Ada_Case_Expr_Alternative |
                    Ada_Select_When_Part | Ada_Component_Clause |
                    Ada_Exception_Handler | Ada_Exception_Decl |
                    Ada_Membership_Expr =>
                     null;

                  when Ada_Pragma_Argument_Assoc | Ada_Aspect_Assoc |
                    Ada_Discriminant_Assoc | Ada_Aggregate_Assoc |
                    Ada_Param_Assoc =>
                     null;

                  when others =>
                     --  No other tree kinds have tabs
                     pragma Assert (False, Tree.Kind'Img);
               end case;
            end if;
         end Append_Tab;

         procedure Subtrees_To_Ada
           (Tree : Ada_Tree; Pre, Between, Post : Tok_Template)
         is
            pragma Assert (Tree.Kind in Ada_Ada_List);
            Prev_With : With_Clause := No_With_Clause;
         --  See Use_Same_Line below
            begin
            if Subtree_Count (Tree) = 0 then
               return;
            end if;

            Interpret_Template (Pre, Subtrees => Empty_Tree_Array);

            for Index in 1 .. Subtree_Count (Tree) loop
               declare
                  Subt : constant Ada_Tree := Subtree (Tree, Index);

                  function Use_Same_Line return Boolean;
                  --  Special case for use_package_clauses: We want to print "with
                  --  A.B; use A.B;" on one line. Also, things like "with A.B; use
                  --  A; use A.B;". This returns True in these cases. We don't do
                  --  this special processing for use type clauses.

                  function Has_Prefix (X, Y : Ada_Tree) return Boolean with
                    Pre => X.Kind in Ada_Identifier | Ada_Dotted_Name
                     and then Y.Kind in Ada_Identifier | Ada_Dotted_Name;
                  --  True if X contains Y, as in "A.B.C.D" contains "A.B".
                  --  I.e. if Y is a prefix of X.

                  function Has_Prefix (X, Y : Ada_Tree) return Boolean is
                  begin
                     return Has_Prefix
                         (L_Full_Name (X.As_Name), L_Full_Name (Y.As_Name));
                  end Has_Prefix;

                  function Use_Same_Line return Boolean is
                  begin
                     --  For a with clause followed by one or more use package
                     --  clauses, Prev_With will be the with clause when
                     --  processing the use clauses. Otherwise, Prev_With is null.

                     if Is_Nil (Prev_With) or else Arg (Cmd, Use_On_New_Line)
                     then
                        return False; -- usual case
                     end if;

                     declare
                        pragma Assert (Prev_With.Kind = Ada_With_Clause);
                        With_Names   : constant Name_List :=
                          F_Packages (Prev_With);
                        Next_Subtree : constant Ada_Tree  :=
                          Subtree (Tree, Index + 1);
                     begin
                        if Next_Subtree.Kind = Ada_Use_Package_Clause then
                           declare
                              Use_Names : constant Name_List :=
                                Next_Subtree.As_Use_Package_Clause.F_Packages;
                           begin
                              if Subtree_Count (With_Names) = 1
                                and then Subtree_Count (Use_Names) = 1
                              then
                                 declare
                                    W : constant Ada_Tree :=
                                      Subtree (With_Names, 1);
                                    U : constant Ada_Tree :=
                                      Subtree (Use_Names, 1);
                                 begin
                                    if Has_Prefix (W, U)
                                      or else Has_Prefix (U, W)
                                    then
                                       return True;
                                    end if;
                                 end;
                              end if;
                           end;
                        end if;
                     end;

                     return False; -- usual case
                  end Use_Same_Line;

               begin
                  pragma Assert
                    (Tree.Kind not in Ada_If_Stmt | Ada_Elsif_Stmt_Part);
                  --  No need for If_Stmt_Check here

                  declare
                     New_Lev : Nesting_Level := New_Level (Cur_Level, Pre);
                  begin
                     New_Lev :=
                       Nesting_Level'Max (New_Lev,
                                        New_Level (Cur_Level, Between));
                     New_Lev :=
                       Nesting_Level'Max (New_Lev,
                                        New_Level (Cur_Level, Post));
                     --  ???Shouldn't New_Lev use the entire template?
                     Subtree_To_Ada (Subt, New_Lev, Index);
                  end;

                  if Present (Subt) then
                     case Subt.Kind is
                        when Ada_With_Clause =>
                           if not Subt.As_With_Clause.F_Has_Limited
                             and then not Subt.As_With_Clause.F_Has_Private
                           then
                              Prev_With := Subt.As_With_Clause;
                           else
                              --  ignore "limited with" and "private with"
                              Prev_With := No_With_Clause;
                           end if;
                        when Ada_Use_Package_Clause =>
                           null; -- Leave Prev_With alone
                        when others =>
                           Prev_With := No_With_Clause;
                     end case;

                     if Index < Subtree_Count (Tree) then
                        declare
                           Same_Line : constant Boolean := Use_Same_Line;
                           use Alternative_Templates;
                           pragma Assert
                             (if Same_Line then
                                Between = Tok_Alt_Table (Semi_LB_Alt));
                           Tween : constant Tok_Template :=
                             (if Same_Line then
                                (if
                                   Ada_Tree (Prev_With) = Subtree (Tree, Index)
                                 then Tok_Alt_Table (Semi_Soft)
                                 else Tok_Alt_Table (Semi_LB_Alt))
                              else Between);
                        begin
                           Interpret_Template
                             (Tween, Subtrees => Empty_Tree_Array);
                           if Same_Line then
                              Append_Tab
                                (Parent => No_Ada_Node, Tree => No_Ada_Node,
                                 Token_Text => Name_Use, Index_In_Line => 2,
                                 Is_Insertion_Point => False);
                           end if;
                        end;

                     else
                        pragma Assert (Index = Subtree_Count (Tree));
                        Interpret_Template
                          (Post, Subtrees => Empty_Tree_Array);
                     end if;
                  end if;
               end;
            end loop;
         end Subtrees_To_Ada;

         procedure Interpret_Template
           (TT        : Tok_Template   := Tok_Template_Table (Tree.Kind);
            Subtrees  : Ada_Tree_Array := Pp.Actions.Subtrees (Tree);
            Cur_Level : Nesting_Level  := Subtree_To_Ada.Cur_Level;
            Kind      : Ada_Tree_Kind  := Tree.Kind)
         is
            subtype Subtrees_Index is Query_Index range 1 .. Subtrees'Last;
            Used : array (Subtrees_Index) of Boolean := (others => False);
            Cur_Subtree_Index : Query_Count                       := 0;
            Inst              : Instr;

            procedure Do_Tab (Inst_Index : Instr_Index);

            procedure Do_Subtree (Subtree_Index : Query_Index);

            procedure Do_Opt_Subtree_Or_List
              (Subt : Ada_Tree; Subtree_Index : Query_Index);

            subtype Absent_Kinds is Ada_Node_Kind_Type with
                Predicate => Absent_Kinds in Ada_Abort_Absent |
                   Ada_Abstract_Absent | Ada_Aliased_Absent |
                   Ada_All_Absent | Ada_Constant_Absent |
                   Ada_Limited_Absent | Ada_Not_Null_Absent |
                   Ada_Private_Absent | Ada_Protected_Absent |
                   Ada_Reverse_Absent | Ada_Synchronized_Absent |
                   Ada_Tagged_Absent | Ada_Until_Absent |
                   Ada_With_Private_Absent |
                   Ada_Mode_Default |
                   Ada_Overriding_Unspecified;
            --  This is needed because we have templates like "?~~ ~", which
            --  inserts a space after the subtree, which might be
            --  "private". But if "private" is not present, we don't want the
            --  space. Perhaps we should get rid of this, and move the space
            --  into the subtree, as in "private ".

            procedure Do_Opt_Subtree_Or_List
              (Subt : Ada_Tree; Subtree_Index : Query_Index)
            is
            begin
               if Present (Subt) then
                  case Subt.Kind is
                     when Absent_Kinds =>
                        null;
                     when Ada_Ada_List =>
                        Push (Tree_Stack, Subt);
                        Subtrees_To_Ada
                          (Subt, Inst.Pre, Inst.Between, Inst.Post);
                        Pop (Tree_Stack);

                     when others =>
                        Interpret_Template
                          (Inst.Pre, Subtrees => Empty_Tree_Array);
                        pragma Assert
                          (Kind not in Ada_If_Stmt | Ada_Elsif_Stmt_Part);
                        --  No need for If_Stmt_Check here
                        Subtree_To_Ada
                          (Subt, New_Level (Cur_Level, TT), Subtree_Index);
                        Interpret_Template
                          (Inst.Post, Subtrees => Empty_Tree_Array);
                  end case;
               end if;
            end Do_Opt_Subtree_Or_List;

            procedure Do_Subtree (Subtree_Index : Query_Index) is
               pragma Assert (Subtree_Index in Subtrees_Index);
               Subt : constant Ada_Tree := Subtrees (Subtree_Index);
            begin
               Used (Subtree_Index) := True;
               if Inst.Kind = Required_Subtree then
                  Subtree_To_Ada
                    (Subt, New_Level (Cur_Level, TT), Subtree_Index);
               else
                  pragma Assert (Inst.Kind = Opt_Subtree_Or_List);
                  Do_Opt_Subtree_Or_List (Subt, Subtree_Index);
               end if;
            end Do_Subtree;

            procedure Do_Tab (Inst_Index : Instr_Index) is
               Par : constant Ada_Tree :=
                 (if Tree = Parent_Tree then
                    Ancestor_Tree (2) -- up one more level

                  else Parent_Tree);

               function Token_Text return Symbol;
               --  Computes the token to be associated with the tab.

               function Token_Text return Symbol is
               begin
                  if Inst.Kind = Tab_Insert_Point then
                     return Name_Tab_Insertion_Point;
                  elsif Tree.Kind = Ada_With_Clause then
                     return Name_With;
                  elsif Inst_Index = TT.Instructions'Last then
                     pragma Assert
                       (Tree.Kind in Ada_Param_Spec | Ada_Object_Decl |
                          Ada_Extended_Return_Stmt_Object_Decl);
                     return Name_Tab_In_Out;

                     --  Except for the above special cases, we return
                     --  the text of the token after "^" in the template.

                  else
                     declare
                        Next_Inst : Instr renames
                          TT.Instructions (Inst_Index + 1);
                     begin
                        if Next_Inst.Kind = Continuation_Outdent then
                           return Name_R_Sq;
                        --  This happens for Comp_Clause_Alt.
                           else
                           pragma Assert (Next_Inst.Kind = Verbatim);
                           return Next_Inst.Text;
                        end if;
                     end;
                  end if;
               end Token_Text;

            begin
               Append_Tab
                 (Par, Tree, Token_Text, Index_In_Line => Inst.Index_In_Line,
                  Is_Insertion_Point => Inst.Kind = Tab_Insert_Point);
            end Do_Tab;

            --  Start of processing for Interpret_Template

         begin
            for Inst_Index in TT.Instructions'Range loop
               Inst := TT.Instructions (Inst_Index);
               case Inst.Kind is
                  when Hard_Break | Hard_Break_No_Comment =>
                     Append_Line_Break
                       (Hard             => True,
                        Affects_Comments => Inst.Kind = Hard_Break,
                        Level            => Cur_Level, Kind => Kind);

                  when Soft_Break =>
                     --  "#+n" is treated the same as "#n" (where n is a
                     --  digit), except that Max_Nesting_Increment ignores
                     --  the former.
                     Append_Line_Break
                       (Hard  => False, Affects_Comments => False,
                        Level => Cur_Level + Inst.Level_Inc, Kind => Kind);

                  when Indent =>
                     Indent (PP_Indentation (Cmd));
                  when Outdent =>
                     Indent (-PP_Indentation (Cmd));

                  when Continuation_Indent =>
                     Indent (PP_Indent_Continuation (Cmd));
                  when Continuation_Outdent =>
                     Indent (-PP_Indent_Continuation (Cmd));

                  when One_Space_Indent =>
                     Indent (1);
                  when One_Space_Outdent =>
                     Indent (-1);

                  when '(' =>
                     Append_And_Put (New_Tokns, '(');
                     Indent (1); -- extra indentation

                  when ')' =>
                     Append_And_Put (New_Tokns, ')');
                     Indent (-1);

                  when Tab | Tab_Insert_Point =>
                     Do_Tab (Inst_Index);

                  when Ignore_Subtree =>
                     Cur_Subtree_Index        := Cur_Subtree_Index + 1;
                     Used (Cur_Subtree_Index) := True;

                  when Required_Subtree | Opt_Subtree_Or_List =>
                     if Inst.Index = 0 then
                        Cur_Subtree_Index := Cur_Subtree_Index + 1;
                     end if;

                     Do_Subtree
                       (Subtree_Index =>
                          (if Inst.Index = 0 then Cur_Subtree_Index
                           else Inst.Index));

                  when Verbatim =>
                     if Label_Seen and then Inst.T_Kind = ';' then
                        Label_Seen := False;
                     else
                        case Scanner.Token_Kind'(Inst.T_Kind) is
                           when Same_Text_Kind =>
                              Append_And_Put (New_Tokns, Inst.T_Kind);
                           when Stored_Text_Kind =>
                              Append_And_Put
                                (New_Tokns, Inst.T_Kind, Inst.Text);
                        end case;
                     end if;
               end case;
            end loop;
            pragma Assert (Used = (Subtrees_Index => True),
                           "Not all used: " & Kind'Img);
         end Interpret_Template;

         use Alternative_Templates;

         procedure Maybe_Blank_Line;
         --  Implement the --insert-blank-lines. See also Replacements.

         procedure Maybe_Blank_Line is
            Insert_Blank_Line_Before : Boolean := False;
         begin
            if not Insert_Blank_Lines (Cmd) then
               return;
            end if;

            if Tree.Kind = Ada_Compilation_Unit then
               Insert_Blank_Line_Before := True;
            end if;

            case Tree.Kind is
               when Ada_Type_Decl |
                 Ada_Task_Type_Decl |
                 Ada_Protected_Type_Decl |
                 Ada_Single_Task_Decl |
                 Ada_Single_Protected_Decl |
                 Ada_Subp_Body |
                 Ada_Package_Decl | -- ???(non lib unit)

                 Ada_Package_Body | Ada_Task_Body | Ada_Protected_Body |
                 Ada_Entry_Body | Ada_Generic_Subp_Decl |
                 Ada_Generic_Package_Decl | Ada_Loop_Stmt | Ada_For_Loop_Stmt |
                 Ada_While_Loop_Stmt | Ada_Block_Stmt |
                 Ada_Extended_Return_Stmt | Ada_Accept_Stmt |
                 Ada_Accept_Stmt_With_Stmts | Ada_Select_Stmt | Ada_If_Stmt |
                 Ada_Record_Rep_Clause | Ada_Case_Stmt | Ada_Variant_Part
                 --           Ada_Exception_Handler |???
                  =>
                  declare
                     Parent : constant Ada_Tree := Parent_Tree;
                  begin
                     if Parent.Kind in Ada_Ada_List then
                        if Subtree (Parent, 1) /= Tree then
                           Insert_Blank_Line_Before := True;
                        end if;
                     end if;
                  end;
               when Ada_Elsif_Stmt_Part =>
                  Insert_Blank_Line_Before := True;
               when others =>
                  null;
            end case;

            if Insert_Blank_Line_Before then
               pragma Assert (All_LB (All_LBI (Last (All_LBI))).Hard);
               Append_Line_Break
                 (Hard => True, Affects_Comments => False, Level => 1,
                  Kind => Tree.Kind);
            end if;
         end Maybe_Blank_Line;

         function Is_Vertical_Aggregate (X : Ada_Tree'Class) return Boolean;
         --  True if X is an aggregate that should be formatted vertically. In
         --  particular, this is true if the --vertical-named-aggregates switch
         --  was given, and X is an aggregate, and all component associations
         --  are in named notation, and the aggregate appears in an appropriate
         --  context. An appropriate context is as the initial value of an
         --  object declaration, as the aggregate in an enumeration
         --  representation clause, or as the expression of a component
         --  association of an outer aggregate that is itself in such a
         --  context.

         --  Procedures for formatting the various kinds of node that are not
         --  fully covered by Str_Template_Table:

         procedure Do_Aggregate;
         procedure Do_Assign_Stmt;
         procedure Do_Compilation_Unit;
         procedure Do_Component_Clause;
         procedure Do_Handled_Stmts;
         procedure Do_Extended_Return_Stmt;
         procedure Do_For_Loop_Spec;

         procedure Do_Assoc;
         procedure Do_Un_Op (Tree : Ada_Tree);

         procedure Do_Bin_Op
           (Tree : Ada_Tree; Is_Right : Boolean; Cur_Level : Nesting_Level);
         --  Also handles some things that look like operators, like "and then".
         --  Is_Right is True if Tree is the right-hand argument of an outer
         --  binary operator. Otherwise (Tree is the left-hand argument, or Tree's
         --  parent is something else, like a parenthesized expression), Is_Right
         --  is False.

         procedure Do_List;
         procedure Do_Literal;
         procedure Do_Label;
         procedure Do_Parameter_Specification; -- also Formal_Object_Declaration
         procedure Do_Pragma;
         procedure Do_Qual_Expr;
         procedure Do_Select_When_Part;
         procedure Do_Params;
         procedure Do_Subp_Spec;
         procedure Do_Subp_Decl; -- subprograms and the like
         procedure Do_Call_Expr;
         procedure Do_Subtype_Indication;
         procedure Do_Task_Def;
         procedure Do_Type_Decl;
         procedure Do_Def_Or_Usage_Name;

         procedure Do_Others; -- anything not listed above

         function Is_Vertical_Aggregate (X : Ada_Tree'Class) return Boolean is
            function All_Named return Boolean is
              (Present (Subtree (Subtree (F_Assocs (X.As_Aggregate), 1), 1)));
         --  True if all component associations are named. We only need to
         --  check the first one, because of the restriction in Ada that
         --  positional associations can't follow named ones.
            begin
            return Result : Boolean := False do
               if Arg (Cmd, Vertical_Named_Aggregates) and then Present (X)
                 and then X.Kind = Ada_Aggregate and then All_Named
               then
                  if X.Parent.Kind in Ada_Object_Decl | Ada_Assign_Stmt |
                      Ada_Enum_Rep_Clause
                  then
                     Result := True;
                  elsif X.Parent.Kind = Ada_Aggregate_Assoc then
                     declare
                        Agg : constant Ada_Tree := X.Parent.Parent.Parent;
                        pragma Assert (Agg.Kind = Ada_Aggregate);
                     begin
                        Result := Is_Vertical_Aggregate (Agg);
                     --  Recurse on outer aggregate
                     end;
                  end if;
               end if;
            end return;
         end Is_Vertical_Aggregate;

         procedure Do_Aggregate is
         begin
            if Is_Vertical_Aggregate (Tree) then
               Interpret_Alt_Template (Vertical_Agg_Alt);
            else
               Interpret_Alt_Template (Nonvertical_Agg_Alt);
            end if;
         end Do_Aggregate;

         procedure Do_Assign_Stmt is
         begin
            if Is_Vertical_Aggregate (F_Expr (Tree.As_Assign_Stmt)) then
               Interpret_Alt_Template (Assign_Vertical_Agg_Alt);
            else
               Interpret_Template;
            end if;
         end Do_Assign_Stmt;

         procedure Do_Compilation_Unit is
         begin
            --          Put ("--  \1 = \2", "Unit_Kind", Capitalize (Tree.Unit_Kind'Img));
            --          Interpret_Template ("$", Subtrees => Empty_Tree_Array);
            --          Put
            --            ("--  \1 = \2",
            --             "Unit_Class",
            --             Capitalize (Tree.Unit_Class'Img));
            --          Interpret_Template ("$", Subtrees => Empty_Tree_Array);
            --          Put
            --            ("--  \1 = \2",
            --             "Unit_Origin",
            --             Capitalize (Tree.Unit_Origin'Img));
            --          Interpret_Template ("$", Subtrees => Empty_Tree_Array);
            --          Interpret_Template ("$", Subtrees => Empty_Tree_Array);
            Subtrees_To_Ada
              (Subtree (Tree, 1), Pre => Tok_Alt_Table (Empty_Alt),
               Between                => Tok_Alt_Table (Semi_LB_Alt),
               Post                   => Tok_Alt_Table (Semi_LB_LB_Alt));
            Subtree_To_Ada
              (Subtree (Tree, 2), Cur_Level + 1, Index_In_Parent => 2);
            Append_And_Put (New_Tokns, ';');
            Interpret_Alt_Template
              (Hard_Break_Alt, Subtrees => Empty_Tree_Array);
            Subtrees_To_Ada
              (Subtree (Tree, 3), Pre => Tok_Alt_Table (Empty_Alt),
               Between                => Tok_Alt_Table (Semi_LB_Alt),
               Post                   => Tok_Alt_Table (Semi_LB_Alt));
         end Do_Compilation_Unit;

         procedure Do_Component_Clause is
            --  We use "`" to right-justify the three expressions X, Y, and Z in
            --  "at X range Y .. Z". We need to lift the Y and Z expressions up so
            --  they appear at the same level as X, so the Tree and Parent of the
            --  "`" will match that of the following "^". The Index_In_Lines must
            --  also match. The end result will be something like:
            --     Thing   at 0 range   0 ..  127;
            --     Thing_2 at 0 range 128 .. 1023;

            pragma Assert
              (Subtree (Tree, 3).As_Range_Spec.F_Range.As_Bin_Op.F_Op =
               Ada_Op_Double_Dot);
            R     : constant Ada_Tree       :=
              Subtree (Tree, 3).As_Range_Spec.F_Range.As_Ada_Node;
            Subts : constant Ada_Tree_Array :=
              Subtrees (Tree) (1 .. 2) & Subtrees (R);
            pragma Assert (Subts'Last = 5);
         begin
            Interpret_Alt_Template (Comp_Clause_Alt, Subts);
         end Do_Component_Clause;

         procedure Do_Handled_Stmts is
         begin
            case Ada_Node'(Parent (Tree)).Kind is
               when Ada_Entry_Body | Ada_Package_Body | Ada_Subp_Body |
                 Ada_Task_Body | Ada_Begin_Block | Ada_Decl_Block =>
                  Interpret_Alt_Template (Handled_Stmts_With_Begin_Alt);
               when Ada_Extended_Return_Stmt | Ada_Accept_Stmt_With_Stmts =>
                  Interpret_Alt_Template (Handled_Stmts_With_Do_Alt);
               when others =>
                  raise Program_Error;
            end case;
         end Do_Handled_Stmts;

         procedure Do_Extended_Return_Stmt is
         begin
            --  If there are no statements or exception handlers, use short form

            if Is_Nil (Tree.As_Extended_Return_Stmt.F_Stmts) then
               Interpret_Alt_Template (Extended_Return_Stmt_Alt);
            else
               Interpret_Template;
            end if;
         end Do_Extended_Return_Stmt;

         type Precedence_Level is range 1 .. 8;
         function Precedence (Expr : Ada_Tree) return Precedence_Level;

         function Precedence (Expr : Ada_Tree) return Precedence_Level is
         begin
            case Expr.Kind is
               when Ada_Bin_Op | Ada_Relation_Op =>
                  case Expr.As_Bin_Op.F_Op is
                     when Ada_Op_In | Ada_Op_Not_In =>
                        raise Program_Error;
                     --  ???Don't treat membership tests as operators, for now
                        --               return 1;

                     when Ada_Op_And_Then | Ada_Op_Or_Else | Ada_Op_And |
                       Ada_Op_Or | Ada_Op_Xor =>
                        return 2;

                     when Ada_Op_Eq | Ada_Op_Neq | Ada_Op_Gt | Ada_Op_Gte |
                       Ada_Op_Lt | Ada_Op_Lte =>
                        return 3;

                     when Ada_Op_Double_Dot =>
                        return 4; -- ???

                     when Ada_Op_Plus | Ada_Op_Minus | Ada_Op_Concat =>
                        return 5;

                     when Ada_Op_Mult | Ada_Op_Div | Ada_Op_Mod | Ada_Op_Rem =>
                        return 6;

                     when Ada_Op_Pow =>
                        return 7;

                        --  Unary-only operator

                     when Ada_Op_Abs | Ada_Op_Not =>
                        raise Program_Error;
                  end case;

                  --  Assume anything else is a unary operator or a primary
                  --  (highest precedence)

               when others =>
                  return 8;
            end case;
         end Precedence;

         function Depends_RHS (Tree : Ada_Tree) return Ada_Tree is
           --  For a tree of the form "Depends => (A => xxx)", this returns
           --  the xxx.

           (Subtree (Subtree (Subtree (Subtree (Tree, 2), 2), 1), 2));

         function Depends_Hack (Tree : Ada_Tree) return Boolean is
           --  True if Tree is an Aspect_Assoc of the form "Depends => (A =>+ B)"
           --  or the same for Refined_Depends.

           (Tree.Kind = Ada_Aspect_Assoc
            and then W_Intern (Id_Name (Subtree (Tree, 1))) in Name_Depends |
              Name_Refined_Depends
            and then Depends_RHS (Tree).Kind = Ada_Un_Op
            and then Subtree (Depends_RHS (Tree), 1).Kind = Ada_Op_Plus);

         procedure Do_Assoc is
            --  Some have a single name before the "=>", and some have a list
            --  separated by "|".
            --  Positional_Notation is True if there are no names (no "=>").
            --  Single_Name is True if there is a single name before "=>",
            --  regardless of whether a list is allowed.

            Designator          : constant Ada_Tree := Subtree (Tree, 1);
            Positional_Notation : constant Boolean  :=
              Is_Nil (Designator)
              or else
              (Designator.Kind in Ada_Ada_List
               and then Subtree_Count (Designator) = 0);
         begin
            if Positional_Notation then
               Interpret_Alt_Template (Pos_Notation_Assoc_Alt);
            else
               declare
                  Single_Name : constant Boolean :=
                    (if Tree.Kind = Ada_Discriminant_Assoc then
                       Subtree_Count (Tree.As_Discriminant_Assoc.F_Ids) = 1
                     elsif Tree.Kind = Ada_Aggregate_Assoc then
                       Subtree_Count (Tree.As_Aggregate_Assoc.F_Designators) =
                       1
                     else True);
                  Vertical    : constant Boolean :=
                    Tree.Kind = Ada_Aggregate_Assoc
                    and then Is_Vertical_Aggregate
                      (Tree.As_Aggregate_Assoc.F_R_Expr);
               begin
                  --  The Single_Name test is needed because the "[]" is not
                  --  properly nested with the "?~~~".
                  --  "! ^=>[# !]" doesn't work for discrims.
                     if Single_Name then
                     if Depends_Hack (Ancestor_Tree (3)) then
                        Interpret_Alt_Template (Depends_Hack_Alt);
                     --  Avoid the usual " " after "=>"; see Do_Un_Op below for an
                     --  explanation.
                        elsif Vertical then
                        Interpret_Alt_Template
                          (Single_Name_Vertical_Assoc_Alt);
                     else
                        Interpret_Alt_Template (Single_Name_Assoc_Alt);
                     end if;
                  else
                     if Vertical then
                        Interpret_Alt_Template (Multi_Name_Vertical_Assoc_Alt);
                     else
                        Interpret_Alt_Template (Multi_Name_Assoc_Alt);
                     end if;
                  end if;
               end;
            end if;
         end Do_Assoc;

         procedure Do_Un_Op (Tree : Ada_Tree) is
            Expr : constant Un_Op := Tree.As_Un_Op;
         begin
            Append_And_Put (New_Tokns, F_Op (Expr));

            --  First we have a special case for the Depends and
            --  Refined_Depends aspect specifications. We want to pretend that
            --  "=>+" is an operator, so we print: "Depends => (A =>+ B)"
            --  instead of "Depends => (A => +B)". We don't bother with this
            --  for pragma [Refined_]Depends, because that's mainly for the
            --  compiler's implementation of the aspect, so we don't expect it
            --  to be used much.

            if Depends_Hack (Ancestor_Tree (4)) then
               pragma Assert (Subtree (Expr, 1).Kind = Ada_Op_Plus);
               Interpret_Alt_Template (Un_Op_Space_Alt, Subtrees (Expr));

               --  No special "[Refined_]Depends" case. Put a space after the
               --  operator, except for "+" and "-".

            else
               case F_Op (Expr) is
                  when Ada_Op_Plus | Ada_Op_Minus =>
                     Interpret_Alt_Template
                       (Un_Op_No_Space_Alt, Subtrees (Expr));
                  when Ada_Op_Abs | Ada_Op_Not =>
                     Interpret_Alt_Template (Un_Op_Space_Alt, Subtrees (Expr));
                  when others =>
                     raise Program_Error;
               end case;
            end if;
         end Do_Un_Op;

         procedure Do_Bin_Op
           (Tree : Ada_Tree; Is_Right : Boolean; Cur_Level : Nesting_Level)
         is
            Expr       : constant Bin_Op   := Tree.As_Bin_Op;
            Oper       : constant Ada_Op   := F_Op (Expr);
            Is_Short_C : constant Boolean  :=
              Oper in Ada_Op_And_Then | Ada_Op_Or_Else;
            Arg1       : constant Ada_Tree := F_Left (Expr).As_Ada_Node;
            Arg2       : constant Ada_Tree := F_Right (Expr).As_Ada_Node;

            --  The arguments can't have lower precedence than the expression as
            --  a whole; that's what precedence means -- you need parens to put
            --  a "+" inside a "*". The right-hand argument can't have equal
            --  precedence, because Ada has no right-associative binary operators.

            pragma Assert (Precedence (Arg1) >= Precedence (Tree));
            pragma Assert (Precedence (Arg2) > Precedence (Tree));

            Arg1_Higher : constant Boolean :=
              Precedence (Arg1) > Precedence (Tree);
         --  Arg1 is higher precedence than Expr

         --  Calculate template fragments for the args (Arg1/2_T), that indent
         --  if the arg is a higher precedence binary operator than the whole
         --  expression.

         --  Start of processing for Do_Bin_Op

         begin
            if Oper = Ada_Op_Double_Dot then
               --  Old gnatpp did this separately from Do_Bin_Op.
                  if Ancestor_Tree (3).Kind = Ada_Derived_Type_Def then
                  Interpret_Alt_Template (Dot_Dot_Wrong_Alt);
               elsif Parent_Tree.Kind = Ada_For_Loop_Spec then
                  Interpret_Alt_Template (Dot_Dot_For_Alt);
               else
                  Interpret_Alt_Template (Dot_Dot_Alt);
               end if;
               return;
            end if;

            --  The recursive calls to Do_Bin_Op below bypass the
            --  normal recursion via Subtree_To_Ada, so we need to pass along the
            --  Cur_Level to Interpret_Template. When we reach something that's
            --  not a binary op, we switch back to the normal recursion via
            --  Interpret_Template on the Arg. We split lines after the
            --  operator symbol, as in:
            --     Some_Long_Thing +
            --     Some_Other_Long_Thing
            --  except in the case of short circuits:
            --     Some_Long_Thing
            --     and then Some_Other_Long_Thing
            --  The --split-line-before-op switch causes all operators to be
            --  treated like short circuits in this regard.
            --
            --  All binary operators are surrounded by blanks, except for "**":
            --     Max : constant := 2**31 - 1;

            if Arg1.Kind in Ada_Bin_Op | Ada_Relation_Op then
               if Is_Right and then Arg1_Higher then
                  Interpret_Alt_Template
                    (Indent_Soft_Alt, Empty_Tree_Array, Cur_Level);
               end if;
               Do_Bin_Op
                 (Arg1, Is_Right => Is_Right,
                  Cur_Level => Cur_Level + (if Arg1_Higher then 1 else 0));
               if Is_Right and then Arg1_Higher then
                  Interpret_Alt_Template
                    (Outdent_Alt, Empty_Tree_Array, Cur_Level);
               end if;

            else
               Interpret_Alt_Template
                 (Subtree_Alt, Subtrees => (1 => Arg1),
                  Cur_Level             => Cur_Level);
            end if;

            --  Don't split lines before or after "**"

            if (Is_Short_C or Arg (Cmd, Split_Line_Before_Op)) and
                Oper /= Ada_Op_Pow
            then
               Interpret_Alt_Template (Soft_Alt, Empty_Tree_Array, Cur_Level);
            end if;

            if Oper = Ada_Op_Pow then
               Append_And_Put (New_Tokns, Oper); -- no blanks for "**"
            else
               Append_And_Put (New_Tokns, Spaces, Name_Space);
               Append_And_Put (New_Tokns, Oper);
               Append_And_Put (New_Tokns, Spaces, Name_Space);
            end if;

            if not (Is_Short_C or Arg (Cmd, Split_Line_Before_Op)) and
                Oper /= Ada_Op_Pow
            then
               Interpret_Alt_Template (Soft_Alt, Empty_Tree_Array, Cur_Level);
            end if;

            if Arg2.Kind in Ada_Bin_Op | Ada_Relation_Op then
               Interpret_Alt_Template
                 (Indent_Soft_Alt, Empty_Tree_Array, Cur_Level + 1);
               Do_Bin_Op (Arg2, Is_Right => True, Cur_Level => Cur_Level + 1);
               Interpret_Alt_Template
                 (Outdent_Alt, Empty_Tree_Array, Cur_Level + 1);

            else
               Interpret_Alt_Template
                 (Subtree_Alt, Subtrees => (1 => Arg2),
                  Cur_Level             => Cur_Level + 1);
            end if;
         end Do_Bin_Op;

         procedure Do_For_Loop_Spec is
         begin
            case Ada_Node'(Parent (Tree)).Kind is
               when Ada_For_Loop_Stmt =>
                  Interpret_Alt_Template (For_Loop_Spec_Stmt_Alt);
               when Ada_Quantified_Expr =>
                  --  In this case, the quantified_expression already printed
                  --  "for ".
                  Interpret_Alt_Template (For_Loop_Spec_Quant_Alt);
               when others =>
                  raise Program_Error;
            end case;
         end Do_For_Loop_Spec;

         procedure Do_List is
         --  This formats the list elements with a hard line break in between. It
         --  is called when a "!" in a template refers to a list subtree. If you
         --  don't want this formatting, you must use "?" instead of "!". See,
         --  for example, the template for Ada_If_Expression, where we want soft
         --  line breaks in between paths. Sometimes this is called for a list
         --  of one element, in which case the Between doesn't matter (e.g.
         --  Defining_Name_List, where there is only one).
            begin
            Subtrees_To_Ada
              (Tree, Pre => Tok_Alt_Table (Empty_Alt),
               Between   => Tok_Alt_Table (Hard_Break_Alt),
               Post      => Tok_Alt_Table (Empty_Alt));
         end Do_List;

         procedure Do_Literal is
            S : constant W_Str := Id_Name (Tree);
            V : Bounded_W_Str (Max_Length => 100);

            function Last_Digit
              (First : Positive; Based : Boolean) return Positive;
            --  Returns the index of the last digit in S starting at
            --  First

            procedure Put_With_Underscores
              (Part : W_Str; Grouping : Positive; Int : Boolean);
            --  Part is the integer part (before the '.', if any) or the
            --  fractional part (after the '.'). Int is True for the integer part.
            --  For example, for "16#12345.67890#e2", this will be called for Part
            --  = "12345" and Int = True, then for Part = "67890" and Int = False.
            --  We want to get "16#1_2345.6789_0#e2" (assuming Grouping = 4).

            procedure Put_With_Underscores
              (Part : W_Str; Grouping : Positive; Int : Boolean)
            is
               Count : Natural          := (if Int then Part'Length else 0);
               Inc   : constant Integer := (if Int then -1 else 1);
            --  For the integer part, we count downward from the Length; for
            --  the fractional part, we count upward from zero. If Count is
            --  divisible by Grouping, the next character should be preceded by
            --  an underscore, except there is never a leading underscore.
               begin
               for J in Part'Range loop
                  if J /= Part'First and then Count mod Grouping = 0 then
                     Append (V, '_');
                  end if;
                  Append (V, Part (J));
                  Count := Count + Inc;
               end loop;
            end Put_With_Underscores;

            function Last_Digit
              (First : Positive; Based : Boolean) return Positive
            is
            begin
               for J in First .. S'Last loop
                  if Is_Digit (S (J)) then
                     null;
                  elsif Based and then Is_Letter (S (J)) then
                     null;
                  else
                     return J - 1;
                  end if;
               end loop;
               return S'Last;
            end Last_Digit;

            --  Start of processing for Do_Literal

         begin
            pragma Assert (Check_Whitespace);
            Check_Whitespace := False;

            --  In most cases, we simply print out S. All of the complicated code
            --  below is for the --decimal-grouping and --based-grouping
            --  switches. If --decimal-grouping was used to specify a nonzero
            --  value, and we have a numeric literal without a base, and that
            --  literal contains no underscores, we insert underscores. Similarly
            --  for --based-grouping. A based literal is one containing "#" or
            --  ":"; note that "10#...#" is considered based, not decimal.

            case Tree.Kind is
               when Ada_String_Literal =>
                  Append_And_Put (New_Tokns, String_Lit, W_Intern (S));

               when Ada_Char_Literal =>
                  Append_And_Put (New_Tokns, Character_Literal, W_Intern (S));

               when Ada_Int_Literal | Ada_Real_Literal =>
                  declare
                     Sharp      : constant Natural :=
                       (if Find (S, "#") /= 0 then Find (S, "#")
                        else Find (S, ":"));
                     Underscore : constant Natural := Find (S, "_");

                     Grouping : constant Natural :=
                       (if Underscore /= 0 then 0
                        elsif Sharp = 0 then Arg (Cmd, Decimal_Grouping)
                        else Arg (Cmd, Based_Grouping));

                     Int_First, Int_Last, Frac_First, Frac_Last : Natural;
                  --  These point to the slices of the literal that should
                  --  have underscores inserted. For example:
                     --     For 12345 or 12345E6:
                     --       S (Int_First .. Int_Last) = "12345"
                     --     For 12345.6789 or 16#12345.6789#E-3:
                     --       S (Int_First .. Int_Last) = "12345", and
                     --       S (Frac_First .. Frac_Last) = "6789"
                     begin
                     if Grouping = 0 then
                        Append_And_Put
                          (New_Tokns, Numeric_Literal, W_Intern (S));
                     else
                        Int_First := Sharp + 1;
                        Int_Last  :=
                          Last_Digit (Int_First, Based => Sharp /= 0);
                        Append (V, S (1 .. Sharp));
                        Put_With_Underscores
                          (S (Int_First .. Int_Last), Grouping, Int => True);
                        if Tree.Kind = Ada_Int_Literal then
                           Append (V, S (Int_Last + 1 .. S'Last));
                        else
                           Frac_First := Int_Last + 2; -- skip '.'
                           Frac_Last  :=
                             Last_Digit (Frac_First, Based => Sharp /= 0);
                           pragma Assert
                             (S (Int_Last + 1 .. Frac_First - 1) = ".");
                           Append (V, ".");
                           Put_With_Underscores
                             (S (Frac_First .. Frac_Last), Grouping,
                              Int => False);
                           Append (V, S (Frac_Last + 1 .. S'Last));
                        end if;
                        Append_And_Put
                          (New_Tokns, Numeric_Literal, W_Intern (+V));
                     end if;
                  end;

               when others =>
                  raise Program_Error;
            end case;

            Check_Whitespace := True;
         end Do_Literal;

         procedure Do_Label is
         begin
            --  We don't want to put ";" after a label; it's not really a
            --  statement. The Label_Seen flag suppresses the ";" that normally
            --  follows statements.

            Label_Seen := True;
            Interpret_Template;
         end Do_Label;

         procedure Do_Others is
         begin
            if Str_Template_Table (Tree.Kind) = null then
               raise Program_Error with "null template: " & Short_Image (Tree);
            else
               Interpret_Template;
            end if;
         end Do_Others;

         procedure Do_Parameter_Specification is
            Index : Query_Index      := 1;
            AM    : constant Boolean := Arg (Cmd, Align_Modes);
         begin
            --  F_Ids:
            Subtrees_To_Ada
              (Subtree (Tree, Index), Pre => Tok_Alt_Table (Empty_Alt),
               Between                    => Tok_Alt_Table (Comma_Soft),
               Post                       => Tok_Alt_Table (Empty_Alt));
            Interpret_Alt_Template
              (Parameter_Specification_Alt, Subtrees => Empty_Tree_Array);

            --  F_Has_Aliased:
            Index := Index + 1;

            if Subtree (Tree, Index).Kind = Ada_Aliased_Present then
               Subtree_To_Ada (Subtree (Tree, Index), Cur_Level + 1, Index);
               Append_And_Put (New_Tokns, Spaces, Name_Space);
            end if;

            --  Skip F_Has_Constant:
            if Tree.Kind = Ada_Object_Decl then
               Index := Index + 1;
            end if;

            --  F_Mode/F_Inout: ???Why not use the same name?
            Index := Index + 1;
            if Subtree (Tree, Index).Kind in Ada_Mode_In | Ada_Mode_In_Out then
               Append_And_Put (New_Tokns, Res_In);
               Append_And_Put (New_Tokns, Spaces, Name_Space);
            end if;
            if AM then
               Interpret_Alt_Template
                 (Tab_2_Alt, Subtrees => Empty_Tree_Array);
            end if;
            if Subtree (Tree, Index).Kind in Ada_Mode_Out | Ada_Mode_In_Out
            then
               Append_And_Put (New_Tokns, Res_Out);
               Append_And_Put (New_Tokns, Spaces, Name_Space);
            end if;
            if AM then
               Interpret_Alt_Template
                 (Tab_3_Alt, Subtrees => Empty_Tree_Array);
            end if;

            --  F_Type_Expr:
            Index := Index + 1;
            Subtree_To_Ada (Subtree (Tree, Index), Cur_Level + 1, Index);

            --  F_Default_Expr:
            Index := Index + 1;
            if Present (Subtree (Tree, Index)) then
               Interpret_Alt_Template
                 ((if AM then AM_Tab_4_Alt else Not_AM_Default_Alt),
                  Subtrees => (1 => Subtree (Tree, Index)));
            end if;

            --  Skip F_Renaming_Clause, F_Aspects:
            if Tree.Kind = Ada_Object_Decl then
               Index := Index + 1;
               Index := Index + 1;
            end if;

            pragma Assert (Index = Subtree_Count (Tree));
         end Do_Parameter_Specification;

         procedure Do_Pragma is
            With_Casing : constant W_Str :=
              Id_With_Casing
                (Id_Name (Tree.As_Pragma_Node.F_Id), Tree.Kind,
                 Is_Predef => False);
         begin
            Append_And_Put (New_Tokns, Res_Pragma);
            Append_And_Put (New_Tokns, Spaces, Name_Space);
            Append_And_Put (New_Tokns, Ident, W_Intern (With_Casing));
            Interpret_Alt_Template (Pragma_Alt);
         end Do_Pragma;

         procedure Do_Qual_Expr is
         begin
            if Subtree (Tree, 2).Kind in Ada_Aggregate |
                Ada_Null_Record_Aggregate
            then
               Interpret_Alt_Template (Qualified_Aggr_Alt);
            else
               Interpret_Template;
            end if;
         end Do_Qual_Expr;

         procedure Do_Select_When_Part is
         begin
            if Index_In_Parent = 1 then
               Interpret_Alt_Template (Select_When_Alt);
            else
               Interpret_Alt_Template (Select_Or_When_Alt);
            end if;
         end Do_Select_When_Part;

         procedure Do_Params is
            Is_Function : constant Boolean :=
              (if
                 Is_Nil (Parent_Tree)
                 or else Parent_Tree.Kind in Ada_Entry_Spec | Ada_Entry_Body |
                   Ada_Accept_Stmt | Ada_Accept_Stmt_With_Stmts
               then False
               else Present (Parent_Tree.As_Subp_Spec.F_Subp_Returns));
            Param_Count : Query_Count      :=
              Subtree_Count (Tree.As_Params.F_Params);
         begin
            if Is_Function then
               Param_Count :=
                 Param_Count + 1; -- Add one extra for function result
            end if;
            if (Arg (Cmd, Par_Threshold) = 0 and then Arg (Cmd, Separate_Is))
              or else Param_Count > Query_Count (Arg (Cmd, Par_Threshold))
            then
               Interpret_Alt_Template (Par_Threshold_Alt);
            else
               Interpret_Alt_Template (Par_Alt);
            end if;
         end Do_Params;

         procedure Do_Subp_Spec is
            Params      : constant Param_Spec_List :=
              (if Present (Tree.As_Subp_Spec.F_Subp_Params) then
                 F_Params (Tree.As_Subp_Spec.F_Subp_Params)
               else No_Param_Spec_List);
            Is_Function : constant Boolean         :=
              Present (Tree.As_Subp_Spec.F_Subp_Returns);
            Param_Count : Query_Count              := Subtree_Count (Params);
         begin
            if Is_Function then
               Param_Count :=
                 Param_Count + 1; -- Add one extra for function result
            end if;
            if (Arg (Cmd, Par_Threshold) = 0 and then Arg (Cmd, Separate_Is))
              or else Param_Count > Query_Count (Arg (Cmd, Par_Threshold))
            then
               Interpret_Alt_Template (Spec_Threshold_Alt);
            else
               Interpret_Alt_Template (Spec_Alt);
            --  F_Name is optional for access-to-subp.
            end if;
         end Do_Subp_Spec;

         procedure Do_Subp_Decl is
            --  This is for subprogram declarations and the like -- everything
            --  that has a formal parameter list. Also subprogram
            --  instantiations, which have no such list.

            Spec : constant Subp_Spec :=
              (case Tree.Kind is
                 when Ada_Entry_Decl | Ada_Entry_Body => No_Subp_Spec,
                 when others => Get_Subp_Spec (Tree));

            Params : constant Param_Spec_List :=
              (case Tree.Kind is
                 when Ada_Entry_Decl =>
                   (if Present (Tree.As_Entry_Decl.F_Spec.F_Entry_Params) then
                      Tree.As_Entry_Decl.F_Spec.F_Entry_Params.F_Params
                    else No_Param_Spec_List),
                 when Ada_Entry_Body =>
                   (if Present (Tree.As_Entry_Body.F_Params) then
                      Tree.As_Entry_Body.F_Params.F_Params
                    else No_Param_Spec_List),
                 when others =>
                   (if Present (F_Subp_Params (Spec)) then
                      F_Params (F_Subp_Params (Spec))
                    else No_Param_Spec_List));

            --  ???The following gets SIGSEGV:
            --            Is_Function : constant Boolean :=
            --              Present (Spec) and then Present (F_Subp_Returns (Spec));
            --            Param_Count : constant Query_Count :=
            --              Subtree_Count (Params) +
            --              Boolean'Pos (Is_Function); -- Add one extra for function result
            Is_Function : Boolean;
            Param_Count : Query_Count :=
              (if Params.Is_Null then 0 else Subtree_Count (Params));
         begin
            if Tree.Kind in Ada_Entry_Decl | Ada_Entry_Body then
               Is_Function := False;
            else
               Is_Function := Present (F_Subp_Returns (Spec));
               if Is_Function then
                  Param_Count := Param_Count + 1;
               --  Add one extra for function result
               end if;
            end if;
            declare
               Subs : constant Ada_Tree_Array :=
                 (if Tree.Kind = Ada_Subp_Body then
                    Subtrees (Tree) (1 .. Subtree_Count (Tree) - 1) &
                    Tree.As_Subp_Body.P_Defining_Name.As_Ada_Node
                  else Subtrees (Tree));
            begin
               if
                 (Arg (Cmd, Par_Threshold) = 0 and then Arg (Cmd, Separate_Is))
                 or else Param_Count > Query_Count (Arg (Cmd, Par_Threshold))
               then
                  Interpret_Template
                    (Tok_Subp_Decl_With_Hard_Breaks_Alt_Table (Tree.Kind),
                     Subtrees => Subs);
               else
                  Interpret_Template (Subtrees => Subs);
               end if;
            end;
         end Do_Subp_Decl;

         procedure Do_Call_Expr is
            function Past_Call_Threshold (Actuals : Ada_Tree) return Boolean is
              (Natural (Subtree_Count (Actuals)) > Arg (Cmd, Call_Threshold)
               and then
               (for some Assoc of Subtrees (Actuals) =>
                  Present (Subtree (Assoc, 1))));
         --  True if there are more parameter associations than the threshold,
         --  and at least one of them is named.
            begin
            if Past_Call_Threshold (Tree.As_Call_Expr.F_Suffix) then
               Interpret_Alt_Template (Call_Threshold_Alt);
            else
               Interpret_Alt_Template (Call_Alt);
            end if;
         end Do_Call_Expr;

         procedure Do_Subtype_Indication is
         begin
            --  Why not discriminant constraint as well???
            --  If we put the "extra" space in the constraint,
            --  we could use Fix_RM_Spacing and get rid of
            --  Do_Subtype_Indication.
               if Arg (Cmd, Rm_Style_Spacing) and then Present (Subtree (Tree, 3))
              and then Subtree (Tree, 3).Kind = Ada_Index_Constraint
            then
               Interpret_Alt_Template (Subtype_Ind_Index_Alt);
            else
               Interpret_Alt_Template (Subtype_Ind_Alt);
            end if;
         end Do_Subtype_Indication;

         procedure Do_Task_Def is
            --  Replace the F_End_Id with the name found in our parent, which
            --  is an Ada_Task_Type_Decl or Ada_Single_Task_Decl.
            Subs : constant Ada_Tree_Array :=
              Subtrees (Tree) (1 .. Subtree_Count (Tree) - 1) &
              Tree.Parent.As_Basic_Decl.P_Defining_Name.As_Ada_Node;
         begin
            Interpret_Template (Subtrees => Subs);
         end Do_Task_Def;

         procedure Do_Type_Decl is
            Def : constant Type_Def := Tree.As_Type_Decl.F_Type_Def;
         begin
            if Def.Kind = Ada_Record_Type_Def
              or else
              (Def.Kind = Ada_Derived_Type_Def
               and then Present (Def.As_Derived_Type_Def.F_Record_Extension))
            then
               if Is_Nil (Tree.As_Type_Decl.F_Aspects) then
                  if Arg (Cmd, Split_Line_Before_Record) then
                     Interpret_Alt_Template (Record_Type_Decl_Split_Alt);
                  else
                     Interpret_Alt_Template (Record_Type_Decl_Alt);
                  end if;
               else
                  Interpret_Alt_Template (Record_Type_Decl_Aspects_Alt);
               end if;
            elsif Def.Kind = Ada_Access_To_Subp_Def then
               Interpret_Alt_Template (Access_To_Subp_Decl_Alt);
            elsif
              (Def.Kind = Ada_Enum_Type_Def
               and then Arg (Cmd, Vertical_Enum_Types))
              or else
              (Def.Kind = Ada_Array_Type_Def
               and then Arg (Cmd, Vertical_Array_Types))
            then
               Interpret_Alt_Template (Enum_Array_Decl_Alt);
            else
               Interpret_Alt_Template (Type_Decl_Alt);
            end if;
         end Do_Type_Decl;

         function Denoted_Decl (Id : Base_Id) return Basic_Decl;
         --  Returns the declaration denoted by Id. No_Basic_Decl if it doesn't
         --  denote anything. P_Referenced_Decl can raise Property_Error, in
         --  which case we return No_Basic_Decl.

         function Denoted_Def_Name
           (Decl : Basic_Decl; Id : Base_Id) return Base_Id;
         --  Returns the defining names denoted by Id.
         --  Decl is the declaration denoted by Id, or null.
         --  If Id doesn't denote anything, returns Id.
         --  ???Possible optimization: If we have never seen
         --  two differently-cased versions of the same identifier,
         --  we don't need to know what it denotes to use the
         --  right case.

         function Denoted_Decl (Id : Base_Id) return Basic_Decl is
         begin
            return Id.P_Referenced_Decl;
         exception
            --  ???At least some of these exceptions are bugs or
            --  not-yet-implemented features of libadalang.

            when Property_Error =>
               return No_Basic_Decl;
         --  Uncomment the following to recover from libadalang failures:
         --            when others =>
         --               return No_Basic_Decl;
         end Denoted_Decl;

         function Denoted_Def_Name
           (Decl : Basic_Decl; Id : Base_Id) return Base_Id
         is
         begin
            if not Decl.Is_Null then
               --  Search through the defining names of the declaration to find
               --  one with the same name.
               --  ???Use Xref instead (see metrics-actions.adb)?
                  for Def_Name of Decl.P_Defining_Names loop
                  if L_Name (Def_Name.P_Relative_Name) = L_Name (Id) then
                     return Def_Name.P_Relative_Name.As_Base_Id;
                  end if;
               end loop;
            end if;

            --  ??? Apparently sometimes Decl is passed but we still cannot
            --  find the defining id, which is why we fallback from the if
            --  above to this return.
            return Id;
         --  Uncomment the following to recover from libadalang failures:
         --         exception
         --            when others =>
         --               return Id;
         end Denoted_Def_Name;

         procedure Do_Def_Or_Usage_Name is
            Def : constant Boolean := Is_Def_Name (Tree.As_Base_Id);

            Decl     : constant Basic_Decl :=
              (if Def then Tree.Parent.As_Basic_Decl
               elsif Arg (Cmd, Syntax_Only) then No_Basic_Decl
               else Denoted_Decl (Tree.As_Base_Id));
            Def_Name : constant Base_Id    :=
              Denoted_Def_Name (Decl, Tree.As_Base_Id);
            pragma Assert (if Def then Def_Name = Tree.As_Base_Id);
            K           : constant Ada_Node_Kind_Type :=
              (if Decl.Is_Null then Null_Kind else Decl.Kind);
            With_Casing : constant W_Str              :=
              Id_With_Casing
                (Id_Name (Def_Name), Kind => K, Is_Predef => False);
         --  ???Is_Predef is wrong.
            begin
            Append_And_Put (New_Tokns, Ident, W_Intern (With_Casing));
         end Do_Def_Or_Usage_Name;

         --  Start of processing for Subtree_To_Ada

      begin
         if Is_Nil (Tree) then -- ???
            return;
         end if;

         Error_Sloc := Slocs.Start_Sloc (Sloc_Range (Tree));
         Push (Tree_Stack, Tree);

         Maybe_Blank_Line;

         case Tree.Kind is
            when Ada_Discrete_Subtype_Name | Ada_Unconstrained_Array_Index |
              Ada_Contract_Case_Assoc | Ada_Contract_Cases |
              Ada_Multi_Dim_Array_Assoc | Ada_Unconstrained_Array_Indices =>
               raise Program_Error with Short_Image (Tree) & " encountered";
            --  ???The above are not used

            when Ada_Compilation_Unit =>
               Do_Compilation_Unit;

            when Ada_Identifier =>
               Do_Def_Or_Usage_Name;

            when Ada_Int_Literal | Ada_Real_Literal | Ada_String_Literal |
              Ada_Char_Literal =>
               Do_Literal;

            when Ada_Label =>
               Do_Label;

            when Ada_Pragma_Node =>
               Do_Pragma;

            when Ada_Un_Op =>
               Do_Un_Op (Tree);

            when Ada_Bin_Op | Ada_Relation_Op =>
               Do_Bin_Op (Tree, Is_Right => False, Cur_Level => Cur_Level);

            when Ada_For_Loop_Spec =>
               Do_For_Loop_Spec;

            when Ada_Task_Def =>
               Do_Task_Def;

            when Ada_Assign_Stmt =>
               Do_Assign_Stmt;

            when Ada_Param_Assoc | Ada_Aggregate_Assoc |
              Ada_Discriminant_Assoc | Ada_Pragma_Argument_Assoc =>
               Do_Assoc;

            when Ada_Aggregate =>
               Do_Aggregate;

            when Ada_Subtype_Indication =>
               Do_Subtype_Indication;

            when Ada_Component_Clause =>
               Do_Component_Clause;

            when Ada_Handled_Stmts =>
               Do_Handled_Stmts;

            when Ada_Extended_Return_Stmt =>
               Do_Extended_Return_Stmt;

            when Ada_Qual_Expr =>
               Do_Qual_Expr;

            when Ada_Param_Spec => -- ???generic formal object: | Ada_Object_Decl =>
               Do_Parameter_Specification;

            when Ada_Object_Decl =>
               if Is_Generic_Formal_Object_Decl (Tree) then
                  Do_Parameter_Specification;
               elsif Is_Vertical_Aggregate
                   (F_Default_Expr (Tree.As_Object_Decl))
               then
                  Interpret_Alt_Template (Obj_Decl_Vertical_Agg_Alt);
               else
                  Interpret_Template;
               end if;

            when Ada_Type_Decl =>
               Do_Type_Decl;

            when Ada_Constrained_Array_Indices =>
               declare
                  SI : constant Ada_Tree :=
                    Subtree (Tree.As_Constrained_Array_Indices.F_List, 1);
               begin
                  if Kind (SI) = Ada_Subtype_Indication then
                     declare
                        C : constant Constraint :=
                          SI.As_Subtype_Indication.F_Constraint;
                     begin
                        if Present (C) and then Kind (C) = Ada_Range_Constraint
                          and then Kind (C.As_Range_Constraint.F_Range) =
                            Ada_Box_Expr
                        then
                           Interpret_Alt_Template (Boxy_Constrained_Alt);
                           goto Done_Constrained_Array_Indices;
                        end if;
                     end;
                  end if;
               end;
               Interpret_Template;

               <<Done_Constrained_Array_Indices>>

            when Ada_Select_When_Part =>
               Do_Select_When_Part;

            when Ada_Params =>
               Do_Params;

            when Ada_Subp_Spec =>
               Do_Subp_Spec;

            when Ada_Subp_Decl | Ada_Abstract_Subp_Decl | Ada_Expr_Function |
              Ada_Null_Subp_Decl | Ada_Subp_Renaming_Decl |
              Ada_Subp_Body_Stub | Ada_Formal_Subp_Decl | Ada_Subp_Body |
              Ada_Access_To_Subp_Def | Ada_Generic_Subp_Decl | Ada_Entry_Body |
              Ada_Entry_Decl =>
               Do_Subp_Decl;

            when Ada_Call_Expr =>
               Do_Call_Expr;

            when Ada_Ada_List =>
               Do_List;

            when others =>
               Do_Others;
         end case;

         Pop (Tree_Stack);
      end Subtree_To_Ada;

      procedure Convert_Tree_To_Ada (Tree : Ada_Tree) is
      begin
         Scanner.Append_Tokn (New_Tokns, Scanner.Start_Of_Input);

         --  Append first link break. The Kind here doesn't matter.
         Append_Line_Break
           (Hard => True, Affects_Comments => True, Level => 1,
            Kind => Null_Kind);

         Indent (Arg (Cmd, Initial_Indentation));

         pragma Assert (Check_Whitespace);
         Subtree_To_Ada (Tree, Cur_Level => 1, Index_In_Parent => 1);

         --  In Partial mode, we might need to add a line break

         if Partial or else Arg (Cmd, Source_Line_Breaks) then
            --            pragma Assert
            --              (Kind (Last (New_Tokns'Access)) not in Line_Break_Token);
            Append_Line_Break
              (Hard => True, -- Why hard????????????????

               Affects_Comments => True, Level => 1,
               Kind             => Null_Kind);
         end if;

         if Alignment_Enabled (Cmd) then
            Append
              (Tabs, Tab_Rec'(Parent | Tree => No_Ada_Node, others => <>));
         --  Append a sentinel tab, whose Position is greater than any
         --  actual position. This ensures that as we step through Tabs,
         --  there is always one more. We don't need the sentinel in the
         --  token stream.
         end if;

         Scanner.Append_Tokn (New_Tokns, Scanner.End_Of_Input);

         Indent (-Arg (Cmd, Initial_Indentation)); -- note negation
         pragma Assert (Is_Empty (Tree_Stack));
         pragma Assert (Cur_Indentation = 0);
      end Convert_Tree_To_Ada;

      --  Start of processing for Tree_To_Ada_2

   begin
      if not Template_Tables_Initialized then
         Init_Template_Tables (Cmd);

         if Debug_Mode then
            Put_Str_Templates;
         end if;
      end if;

      Convert_Tree_To_Ada (Root);
   end Tree_To_Ada_2;

   procedure Format_Vector
     (Cmd       :     Command_Line; Input : Char_Vector; Node : Ada_Node;
      In_Range  :     Char_Subrange; Output : out Char_Vector;
      Out_Range : out Char_Subrange;
      Messages  : out Pp.Scanner.Source_Message_Vector)
   is
      pragma Unreferenced (In_Range, Out_Range);
      --  ???These are not yet implemented.

      Partial : constant Boolean := Is_Empty (Input);

      Src_Buf : Buffer;
      --  Buffer containing the text of the original source file

      Wide_Char_Encoding : constant System.WCh_Con.WC_Encoding_Method :=
        Wide_Character_Encoding (Cmd);

      In_File_Format : Scanner.Optional_EOL_Formats;

      function Count_Lines (V : Char_Vector) return Natural;
      --  Number of line terminators in V

      function Get_End_Of_Line return Scanner.Optional_EOL_Formats;
      --  Returns the end-of-line convention specified by the --eol switch, or
      --  Nil.

      function Out_File_Format return Scanner.EOL_Formats;
      --  Returns the end-of-line convention for the output, as specified by
      --  the --eol switch, and defaulting to the same as the input.

      procedure Tree_To_Ada;

      procedure Tree_To_Ada is
      begin
         if Debug_Mode then
            Utils.Dbg_Out.Output_Enabled := True;
         end if;

         Scanner.Get_Tokns
           (Src_Buf, Src_Tokns, In_File_Format, Utils.Ada_Version);
         if Debug_Mode then
            Dbg_Out.Put ("Src_Tokens:\n");
            Scanner.Put_Tokens (Src_Tokns);
            Dbg_Out.Put ("end Src_Tokens:\n");
         end if;

         --  Note that if we're processing multiple files, we will get here
         --  multiple times, so data structures left over from last time must
         --  have been cleared out.

         pragma Assert (Cur_Indentation = 0);
         Assert_No_LB (Lines_Data);
         pragma Assert (Is_Empty (Tabs));
         Clear (Lines_Data.Out_Buf);
         Scanner.Clear (New_Tokns);

         --  If --comments-only was specified, format the comments and quit

         if Arg (Cmd, Comments_Only) then
            Do_Comments_Only (Lines_Data'Access, Src_Buf, Cmd);
         else
            --  Otherwise, convert the tree to text, and then run all the
            --  text-based passes.

            Tree_To_Ada_2 (Node, Cmd, Partial);
            Post_Tree_Phases
              (Lines_Data'Access, Messages, Src_Buf, Cmd, Partial);
         end if;
      end Tree_To_Ada;

      function Get_End_Of_Line return Scanner.Optional_EOL_Formats is
         Val : constant String_Ref := Arg (Cmd, End_Of_Line);
         use Scanner;
      begin
         if Val = null then
            return Nil;
         else
            declare
               Lower : constant String := To_Lower (Val.all);
            begin
               if Lower in "dos" | "crlf" then
                  return CRLF;
               elsif Lower in "unix" | "lf" then
                  return LF;
               else
                  Cmd_Error ("Unrecognized --eol switch: " & Val.all);
               end if;
            end;
         end if;
      end Get_End_Of_Line;

      Requested_End_Of_Line : constant Scanner.Optional_EOL_Formats :=
        Get_End_Of_Line;

      function Out_File_Format return Scanner.EOL_Formats is
         use Scanner;
      begin
         return
           (if Requested_End_Of_Line = Nil then In_File_Format
            else Requested_End_Of_Line);
      end Out_File_Format;

      function Remove_Extra_Line_Breaks (Add_CR : Boolean) return WChar_Vector;
      --  Removes extra NL's. The result has exactly one NL at the beginning, and
      --  exactly one at the end. Also, if Preserve_Blank_Lines is False, we
      --  collapse 3 or more NL's in a row down to 2.  ???It would be cleaner if
      --  we didn't put multiple blank lines in in the first place.
      --
      --  This also converts LF to CRLF if appropriate.
      --
      --  Add_CR is True if we should convert LF to CRLF.
      --
      --  Wide_Text_IO accepts a Form parameter that inserts CR's on windows, but
      --  it doesn't do that on unix, so we insert CR's by hand.

      function Remove_Extra_Line_Breaks (Add_CR : Boolean) return WChar_Vector
      is
         Out_Buf : Buffer renames Lines_Data.Out_Buf;
      begin
         if Preserve_Blank_Lines (Cmd) or else Arg (Cmd, Source_Line_Breaks)
         then
            if Add_CR then
               return Result : WChar_Vector do
                  --  The first sentinel NL doesn't get CR

                  pragma Assert (Cur (Out_Buf) = NL);
                  Append (Result, Cur (Out_Buf));
                  Move_Forward (Out_Buf);

                  while not At_End (Out_Buf) loop
                     if Cur (Out_Buf) = NL then
                        Append (Result, W_CR);
                     end if;

                     Append (Result, Cur (Out_Buf));
                     Move_Forward (Out_Buf);
                  end loop;

                  Reset (Out_Buf);

                  --  If the last line of the was not terminated by a newline,
                  --  delete the last CR and LF to match the input.

                  pragma Assert (Last_Element (Result) = W_LF);
                  if Last_Element (Input) /= ASCII.LF then
                     Delete_Last (Result);
                     pragma Assert (Last_Element (Result) = W_CR);
                     Delete_Last (Result);
                  end if;
               end return;

               --  Optimize the case where we're not changing anything. The reason
               --  Remove_Extra_Line_Breaks keeps the initial NL is that this
               --  optimization wouldn't work otherwise.

            else
               return Result : WChar_Vector := To_Vector (Out_Buf) do

                  --  If the last line of the was not terminated by a newline,
                  --  delete the last CR and LF to match the input.

                  pragma Assert (Last_Element (Result) = W_LF);
                  if Last_Element (Input) /= ASCII.LF then
                     Delete_Last (Result);
                  end if;
               end return;
            end if;
         end if;

         declare
            Result : WChar_Vector; -- extended ret ????????????????
         begin
            while Cur (Out_Buf) = NL loop
               Move_Forward (Out_Buf);
            end loop;
            Append (Result, W_LF);
            --  We don't want a CR here; caller skips the one LF character

            loop
               declare
                  NL_Count : Natural := 0;
               begin
                  while Cur (Out_Buf) = NL loop
                     Move_Forward (Out_Buf);
                     NL_Count := NL_Count + 1;
                  end loop;

                  exit when At_End (Out_Buf);

                  if NL_Count > 2 then
                     NL_Count := 2;
                  end if;

                  for J in 1 .. NL_Count loop
                     if Add_CR then
                        Append (Result, W_CR);
                     end if;
                     Append (Result, W_LF);
                  end loop;
               end;

               pragma Assert (Cur (Out_Buf) /= NL);
               Append (Result, Cur (Out_Buf));
               Move_Forward (Out_Buf);
            end loop;

            if Add_CR then
               Append (Result, W_CR);
            end if;
            Append (Result, W_LF);
            Reset (Out_Buf);
            pragma Assert (Result (1) = NL);
            pragma Assert (Result (2) /= NL);
            if not Add_CR then
               pragma Assert (Result (Last_Index (Result) - 1) /= NL);
               pragma Assert (Result (Last_Index (Result)) = NL);
            end if;
            return Result;
         end;
      end Remove_Extra_Line_Breaks;

      function Count_Lines (V : Char_Vector) return Natural is
         S : String renames Elems (V) (1 .. Last_Index (V));
      begin
         return R : Natural := 0 do
            for X of S loop
               if Is_Line_Terminator (X) then
                  R := R + 1;
               end if;
            end loop;
         end return;
      end Count_Lines;

      --  Start of processing for Format_Vector

   begin
      Clear (Src_Buf);
      Insert_Ada_Source
        (Src_Buf, Elems (Input) (1 .. Last_Index (Input)), Wide_Char_Encoding,
         Expand_Tabs => True);
      --  Expand tabs unconditionally. This differs from the behavior of
      --  the old gnatpp, which has an option for that (but only for
      --  comments).
      --  ???Encoding needs to match the call to libadalang.
      Reset (Src_Buf);

      Tree_To_Ada;

      if Scanner.Source_Message_Vectors.Is_Empty (Messages) then
         declare
            use Scanner;
            Out_Vec : constant WChar_Vector :=
              Remove_Extra_Line_Breaks (Add_CR => Out_File_Format = CRLF);
            Out_Buf : Buffer renames Lines_Data.Out_Buf;
            Junk1 : W_Str renames Elems (Out_Vec) (1 .. Last_Index (Out_Vec));
            Last2   : constant Natural      :=
              Last_Position (Out_Buf) -
              (if Last_Element (Input) = ASCII.LF then 0 else 1);
            Junk2   : W_Str renames Elements (Out_Buf) (1 .. Last2);
            pragma Assert -- ????????????????Won't work on windows
              (if Arg (Cmd, Source_Line_Breaks) then
                 (if Out_File_Format /= CRLF then Junk1 = Junk2));
            Out_Arr : W_Str renames
              Elems (Out_Vec) (2 .. Last_Index (Out_Vec));
            --  2 to skip sentinel newline

            procedure Append_One (C : Character);
            procedure Append_One (C : Character) is
            begin
               Append (Output, C);
            end Append_One;
            procedure Encode is new System.WCh_Cnv.Wide_Char_To_Char_Sequence
              (Append_One);
         begin
            Clear (Output); -- ????????????????Assert empty?
            for WC of Out_Arr loop
               Encode (WC, Wide_Char_Encoding);
            end loop;
         end;

         if not Disable_Final_Check and then Enable_Token_Mismatch
           and then Arg (Cmd, Source_Line_Breaks)
         then
            declare
               Src_Lines : constant Natural := Count_Lines (Input);
               Out_Lines : constant Natural := Count_Lines (Output);
               Comp      : constant String  :=
                 (if Src_Lines < Out_Lines then " < " else " > ");
            begin
               if Src_Lines /= Out_Lines then
                  --  There is an extra sentinel newline in Out_Lines, so subtract
                  --  that off.????????????????

                  Text_IO.Put_Line
                    (Text_IO.Standard_Output,
                     "Incorrect line count: " & Src_Lines'Image & Comp &
                     Natural'(Out_Lines)'Image);
                  Debug_Flag_L := True;
                  if not Debug_Flag_L then -- ????????????????
                     raise Program_Error;
                  end if;
               end if;
            end;
         end if;
      end if;
   end Format_Vector;

   procedure Per_File_Action
     (Tool  : in out Pp_Tool; Cmd : Command_Line; File_Name : String;
      Input :        String; BOM_Seen : Boolean; Unit : Analysis_Unit)
   is
      pragma Unreferenced (Tool);

      Output_Mode : constant Output_Modes := Get_Output_Mode (Cmd);
      Do_Diff     : constant Boolean      := Output_Mode in Replace_Modes;

      In_Vec, Out_Vec : Char_Vector;

      --  We initially write the output to Temp_Output_Name, then later rename it
      --  to Output_Name (except in Pipe mode). These are full pathnames. If we
      --  are overwriting the Source_Name, and it's a link link-->file, we want to
      --  overwrite file. But we put the temp file in the directory containing
      --  link, in case the directory containing file is not writable.

      function Get_Output_Name (Resolve_Links : Boolean) return String;
      function Get_Output_Name (Resolve_Links : Boolean) return String is
      begin
         pragma Assert (Environment.Initial_Dir = Current_Directory);
         return
           (case Output_Mode is
              when Pipe => "", -- not used

              when Output => Arg (Cmd, Output).all,
              when Output_Force => Arg (Cmd, Output_Force).all,
              when Replace_Modes =>
                Normalize_Pathname
                  (File_Name, Resolve_Links => Resolve_Links,
                   Case_Sensitive           => True),

              when Output_Directory =>
                Compose
                  (Arg (Cmd, Output_Directory).all, Simple_Name (File_Name)));
      end Get_Output_Name;

      Output_Name : constant String := Get_Output_Name (Resolve_Links => True);

      Temp_Output_Name : constant String :=
        (if Output_Mode = Pipe then "" -- means standard output

         else Get_Output_Name (Resolve_Links => False) & "__GNATPP-TEMP");

      Output_Written : Boolean := False;
      --  True if Tree_To_Ada wrote the output to Temp_Output_Name. It always
      --  does, except in Replace_Modes if the output would be identical to the
      --  input.

      procedure Write_File_Name_File;
      --  If the Output_Mode /= Pipe, and Output_Written is True, add a pair of
      --  lines to the file name file.

      procedure Write_File_Name_File is
         use Text_IO, GNAT.Lock_Files;
         Lock_File_Name : constant String := File_Name_File_Name.all & ".lock";

         procedure Do_Writes;
         --  Write the two file names to the file name file. This is split out
         --  into a procedure so we can call it with and without file locking, as
         --  appropriate.

         procedure Do_Writes is
            File_Name_File : File_Type;
         begin
            Open
              (File_Name_File, Mode => Append_File,
               Name                 => File_Name_File_Name.all);
            Put_Line (File_Name_File, Temp_Output_Name);
            Put_Line (File_Name_File, Output_Name);
            Close (File_Name_File);
         end Do_Writes;

         --  Start of processing for Write_File_Name_File

      begin
         if Output_Mode /= Pipe then
            --  In -r, -rf, and -rnb modes, if the output was identical to the
            --  input, Output_Written will be False, so there is no
            --  Temp_Output_Name file, so we don't move it in that case. This can
            --  also happen if the exception handler at the end of Tree_To_Ada is
            --  executed.

            pragma Assert
              (if Output_Mode not in Replace_Modes then Output_Written);
            if not Output_Written then
               return;
            end if;

            --            if Mimic_gcc and then (Verbose_Mode or else Debug_Flag_V) then
            --               Put_Line
            --                 ((if Output_Mode in Replace_Modes
            --                     then "updating "
            --                     else "creating ") &
            --                  (if Debug_Flag_V then Short_Source_Name (SF) else Output_Name));
            --            end if;

            --  The temp file was created, so write a pair (Temp_Output_Name,
            --  Output_Name) of lines to the file name file, so Finalize will know
            --  to rename temp --> output. This is done under lock, in case this
            --  is an inner process of an incremental build, and the -j switch of
            --  the builder is used to invoke this in parallel.

            if Arg (Cmd, Outer_Parallel) then
               pragma Assert (Mimic_gcc (Cmd));
               Lock_File (Lock_File_Name, Wait => 0.1, Retries => 5 * 60 * 10);
               --  Retry for 5 minutes, every 100 milliseconds.
                  declare
                  --  We create a dummy object whose finalization calls
                  --  Unlock_File, so we don't leave stale lock files around even
                  --  in case of unhandled exceptions.

                  type Dummy_Type is new Ada.Finalization
                    .Limited_Controlled with
                  null record;
                  procedure Finalize (Ignore : in out Dummy_Type);
                  procedure Finalize (Ignore : in out Dummy_Type) is
                  begin
                     Unlock_File (Lock_File_Name);
                  end Finalize;

                  Dummy : Dummy_Type;

               begin
                  Do_Writes;
               end;

               --  Otherwise, it's safe to do the writes without any locking. We want
               --  to avoid locking when possible, because it reduces the likelihood
               --  of stale locks left lying around. It's a little more efficient,
               --  too.

            else
               Do_Writes;
            end if;
         end if;
      --      exception
      --         when Lock_Error =>
      --            Utils.Output.Error ("cannot create " & Lock_File_Name);
      --            Utils.Output.Error ("delete it by hand if stale");
      --            raise;
      end Write_File_Name_File;

      procedure Write_Str (Out_Vec : Char_Vector);
      procedure Write_Out_Buf;
      procedure Write_Src_Buf;
      --  Write_Out_Buf writes Out_Buf to the output. This is the normal
      --  case. Write_Src_Buf writes the Src_Buf to the output. Write_Str is the
      --  code common to both Write_Out_Buf and Write_Src_Buf.

      procedure Write_Str (Out_Vec : Char_Vector) is
         Out_File   : File_Descriptor := Standout;
         Out_String : String renames
           Elems (Out_Vec) (1 .. Last_Index (Out_Vec));
         Status     : Boolean;
         use System.WCh_Con;
      begin
         --  ???
         --      if False then -- ???Messes up the diff's.
         --         Formatted_Output.Put
         --           ("--  ???Inner_Loop_Count = \1\n",
         --            Image (Inner_Loop_Count));
         --      end if;

         Output_Written := True;
         if Temp_Output_Name /= "" then
            --  If Temp_Output_Name = "", use standard output; otherwise open
            --  the file.
            Out_File := Create_File (Temp_Output_Name, Fmode => Binary);
            if Out_File = Invalid_FD then
               raise Program_Error
                 with "write of " & Temp_Output_Name & " failed";
            end if;
         end if;

         --  If a BOM (byte order mark) was found in the input, we want to put it
         --  in the output.

         if BOM_Seen then
            pragma Assert (Wide_Character_Encoding (Cmd) = WCEM_UTF8);
            Write_File (Out_File, Ada.Strings.UTF_Encoding.BOM_8);
         end if;

         Write_File (Out_File, Out_String);

         if Temp_Output_Name /= "" then
            Close (Out_File, Status);
            if not Status then
               raise Program_Error
                 with "write of " & Temp_Output_Name & " failed";
            end if;
         end if;
      end Write_Str;

      procedure Write_Out_Buf is
      begin
         --  In Do_Diff mode, don't write the output if it is identical to the
         --  input.

         if Do_Diff and then Out_Vec = In_Vec then
            pragma Assert (not Output_Written);
            return;
         end if;

         Write_Str (Out_Vec);
      end Write_Out_Buf;

      procedure Write_Src_Buf is
      begin
         pragma Assert (Is_Empty (Out_Vec));
         Write_Str (In_Vec);
      end Write_Src_Buf;

      --  Start of processing for Per_File_Action

   begin
      if Debug_Mode then
         Print (Unit);
      end if;

      if Output_Mode in Replace_Backup | Replace_Force_Backup then
         declare
            Backup_Simple_Name : constant String := File_Name & NPP_Suffix;
            Backup_Name        : constant String :=
              (if Arg (Cmd, Output_Directory) = null then Backup_Simple_Name
               else Compose
                   (Arg (Cmd, Output_Directory).all,
                    Simple_Name (Backup_Simple_Name)));
            Success            : Boolean;
            use Wide_Text_IO;
         begin
            if Output_Mode = Replace_Backup
              and then Is_Regular_File (Backup_Name)
            then
               Put (Standard_Error, "gnatpp: file ");
               Put (Standard_Error, To_Wide_String (Backup_Name));
               Put
                 (Standard_Error,
                  " exists. Use '--replace-force-backup' option to override");
               New_Line (Standard_Error);
               return;
            end if;

            Copy_File
              (Name => File_Name, Pathname => Backup_Name, Success => Success,
               Mode => Overwrite);

            if not Success then
               Put (Standard_Error, "gnatpp: cannot create backup file ");
               Put (Standard_Error, To_Wide_String (Backup_Name));
               New_Line (Standard_Error);
            end if;
         end;

      end if;

      --      pragma Assert (Is_Empty (Symtab));
      Append (In_Vec, Input);
      declare
         Messages : Scanner.Source_Message_Vector;
         use Scanner.Source_Message_Vectors;
         use type Ada.Containers.Count_Type;
         In_Range          : constant Char_Subrange :=
           Utils.Char_Vectors.Char_Vectors.Full_Range (In_Vec);
         Ignored_Out_Range : Char_Subrange;
      begin
         Format_Vector
           (Cmd, In_Vec, Root (Unit), In_Range, Out_Vec, Ignored_Out_Range,
            Messages);
         --        (CU, Cmd, Output_Name, Form_String,
            --         Do_Diff, Output_Written, To_Ada => True);
            --      --  We have to flush the cache here, because Unit_Id's get reused between
            --      --  runs of this.
            --      Flush_Cache;
            --      Clear (Symtab);

         if not Is_Empty (Messages) then
            pragma Assert (Length (Messages) = 1);
            --  We only handle one message for now.
            Cmd_Error_No_Tool_Name
              (Scanner.Message_Image (File_Name, Messages (1).Sloc) & ": " &
               String (To_Array (Messages (1).Text)));
         end if;
      end;

      --  Finally, print out the result

      Write_Out_Buf;
      Write_File_Name_File;
   exception
      --  If we got an error, don't produce output

      when Command_Line_Error | Command_Line_Error_No_Tool_Name =>
         raise;

      when others =>
         --  In order to avoid damaging the user's source code, if there is a bug
         --  (like a token mismatch in Final_Check), we avoid writing the output
         --  file in Do_Diff mode; otherwise, we write the input to the output
         --  unchanged. This happens only in production builds.
            --
            --  Include the source location in the error message, if available.

         declare
            use type Slocs.Source_Location;
            Loc : constant String :=
              (if Error_Sloc = Slocs.No_Source_Location then ""
               else ":" & Slocs.Image (Error_Sloc));
         begin
            Text_IO.Put_Line
              (Text_IO.Standard_Error,
               Simple_Name (File_Name) & Loc &
               ": pretty printing failed; unable to format");
         end;

         if Enable_Token_Mismatch then
            raise;
         else
            if Do_Diff then
               pragma Assert (not Output_Written);
            else
               Write_Src_Buf;
            end if;

            --  Reset Lines_Data to its initial state, so we don't blow up on
            --  subsequent files.

            Lines_Data := (others => <>);
         end if;
   end Per_File_Action;

   ---------------
   -- Tool_Help --
   ---------------

   procedure Tool_Help (Tool : Pp_Tool) is
      pragma Unreferenced (Tool);
      use Utils.Formatted_Output;
   begin
      pragma Style_Checks ("M200"); -- Allow long lines

      Put ("usage: gnatpp [options] {filename}\n");
      Put (" options:\n");
      Put (" --version - Display version and exit\n");
      Put (" --help    - Display usage and exit\n");
      Put ("\n");
      Put (" -Pproject     - use project file project\n");
      Put (" -U            - process all sources of the argument project\n");
      Put
        (" -U main       - process the closure of units rooted at unit main\n");
      Put
        (" -Xname=value  - specify an external reference for argument project file\n");
      Put
        (" -eL           - follow all symbolic links when processing project files\n");

      Put ("\n");
      Put (" --alignment -- alignment ON (default)\n");
      Put (" --no-alignment -- alignment OFF\n");
      Put (" --align-modes -- alignment of parameter modes ON (default)\n");
      Put (" --no-align-modes -- alignment of parameter modes OFF\n");

      Put ("casing switches:\n");
      Put (" --name-case-as-declared - usage names as declared (default)\n");
      Put (" --name-lower-case - names in lower case\n");
      Put (" --name-upper-case - names in upper case\n");
      Put (" --name-mixed-case - names in mixed case\n");

      Put ("attribute casing:\n");
      Put (" --attribute-lower-case\n");
      Put (" --attribute-upper-case\n");
      Put (" --attribute-mixed-case (default)\n");

      Put ("reserved word casing:\n");
      Put (" --keyword-lower-case (default)\n");
      Put (" --keyword-upper-case\n");

      Put ("enumeration literal casing:\n");
      Put (" --enum-case-as-declared\n");
      Put (" --enum-lower-case\n");
      Put (" --enum-upper-case\n");
      Put (" --enum-mixed-case\n");

      Put ("type and subtype casing:\n");
      Put (" --type-case-as-declared\n");
      Put (" --type-lower-case\n");
      Put (" --type-upper-case\n");
      Put (" --type-mixed-case\n");

      Put ("named number casing:\n");
      Put (" --number-case-as-declared\n");
      Put (" --number-lower-case\n");
      Put (" --number-upper-case\n");
      Put (" --number-mixed-case\n");

      Put ("pragma casing:\n");
      Put (" --pragma-mixed-case\n");
      Put (" --pragma-lower-case\n");
      Put (" --pragma-upper-case\n");
      Put ("\n");

      Put
        (" --based-grouping=n  - underscores in based literals every n characters\n");

      Put (" --comments-unchanged - do not format comments\n");
      Put
        (" --comments-gnat-indentation - GNAT style comment line indentation (default)\n");
      Put (" --comments-gnat-beginning - GNAT style comment beginning\n");
      Put (" --comments-fill - fill comment blocks\n");
      Put
        (" --comments-special - do not change comments with a special character just after --\n");
      Put (" --comments-only - format just the comments\n");

      Put
        (" --indentation=n - indentation level, n from 1 .. 9 (default 3)\n");
      Put
        (" --indent-continuation - indentation level for continuation lines (default one less than --indentation)\n");

      Put
        (" --dictionary=<file> - set <file> as the dictionary file defining casing exceptions\n");
      Put
        (" --dictionary=-      - do not use RM-defined casing for predefined names\n");

      Put
        (" --decimal-grouping=n  - underscores in decimal literals every n characters\n");

      Put (" --ff-after-pragma-page - put Form Feed after a pragma Page\n");

      Put (" --max-line-length=nnn - set maximum line length (default 79)\n");

      Put (" --pp-off=xxx - Use ""--xxx"" as the comment string to disable\n");
      Put
        ("                pretty printing instead of the default ""--!pp off""\n");
      Put (" --pp-on=xxx - Use ""--xxx"" as the comment string to reenable\n");
      Put
        ("                pretty printing instead of the default ""--!pp on""\n");

      Put (" --RTS=<dir> - the same as gcc --RTS option\n");

      Put (" --quiet / -q  - quiet mode\n");

      Put
        (" --no-separate-is        - try not to place 'IS' on a separate line in\n");
      Put ("                           a subprogram body\n");
      Put (" --separate-loop         - use a separate line for LOOP\n");
      Put (" --separate-then         - use a separate line for THEN\n");
      Put (" --separate-loop-then    - above two combined\n");
      Put (" --no-separate-loop      - do not use a separate line for LOOP\n");
      Put (" --no-separate-then      - do not use a separate line for THEN\n");
      Put (" --no-separate-loop-then - above two combined\n");

      Put (" --use-on-new-line       - use separate lines for USE clauses\n");
      Put ("                           in a context clause\n");

      Put
        (" --insert-blank-lines    - insert blank lines where appropriate\n");

      Put (" --preserve-blank-lines  - preserve blank lines in the input\n");

      if False then -- ????????????????NYI
         Put
           (" --source-line-breaks    - take line breaks only from source\n");
      else
         Put
           (" --insert-line-breaks    - insert line breaks where appropriate (default)\n");
         Put (" --no-insert-line-breaks    - do not insert line breaks\n");
      end if;

      Put (" --split-line-before-op  - operator on next line\n");
      Put (" --split-line-before-record  - ""record"" on next line\n");
      Put
        (" --indent-named-statements - named statements indented more than name\n");

      Put (" --RM-style-spacing      - no extra space before '(' and ':'\n");

      Put
        (" --par-threshold=nnn     - if the number of parameter specifications is greater\n");
      Put
        ("                           than nnn, each specification starts from a new line\n");

      Put
        (" --call-threshold=nnn    - if the number of parameter associations in a call is\n");
      Put
        ("                           greater than nnn and there is at least one named\n");
      Put
        ("                           association, each association starts from a new line\n");

      Put (" --vertical-enum-types - multi-line enumeration types\n");
      Put (" --vertical-array-types - multi-line array types\n");
      Put (" --vertical-named-aggregates - multi-line named aggregates\n");
      Put (" --vertical-case-alternatives -multi-line case alternatives\n");

      Put (" --incremental -- incremental processing on a per-file basis\n");
      Put (" -jn - n is the maximal number of processes to carry out\n");

      Put (" --syntax-only  - do not run semantic analysis (default)\n");
      Put (" --no-syntax-only  - run semantic analysis\n");

      Put (" --verbose / -v  - verbose mode\n");

      Put (" -dd - progress indicator verbose mode\n");
      Put ("\n");

      Put ("Output file control:\n");
      Put
        (" --replace - replace the argument source with the pretty-printed source (default)\n");
      Put (" --output-dir=dir / --dir=dir -- create output files in dir\n");
      Put
        (" --replace-backup  - replace the argument source with the pretty-printed source and copy the\n");
      Put ("        argument source into filename.npp\n");
      Put
        (" --replace-force-backup   - same as --replace-backup, but overwrites an existing filename.npp\n");
      Put (" --pipe - send the output to standard output\n");
      Put
        (" --output=output_file - write the output into output_file. Give up if output_file\n");
      Put ("                  already exists\n");
      Put
        (" --output-force=output_file - write the output into output_file, overriding the existing\n");
      Put ("                   file\n");

      Put ("\n");

      Put (" filename - the name of the Ada source file to be reformatted.\n");
      Put ("            Wildcards are allowed\n");
      Put (" --files=filename - the name of a text file containing a list\n");
      Put ("                   of Ada source files to reformat\n");
      Put (" --ignore=filename - do not process sources listed in filename\n");
      Put
        (" --eol=text_format - sets the format of the gnatpp output file(s),\n");
      Put
        ("       text_format can be - 'unix' or 'lf'   - lines end with LF character\n");
      Put
        ("                          - 'dos'  or 'crlf' - lines end with CRLF characters\n");

      Put
        (" --wide-character-encoding=(8|b) - set the wide character encoding of the result file\n");
      Put ("    8 - UTF-8 encoding\n");
      Put ("    b - Brackets encoding (default)\n");
      Put ("\n");

      Put ("\n\nReport bugs to report@adacore.com\n");

      pragma Style_Checks ("M79");
   end Tool_Help;

end Pp.Actions;
