6.3. Preprocessing
6.3.1. Libadalang.Preprocessing
This package implements a preprocessor for Ada sources that is compatible with GNATprep. It also provides a file reader implementing such a preprocessor, to be used in an analysis context. Please refer to GNATprep’s documentation for a description of the main concepts, of preprocessor data files and of preprocessing features. The API provided here closely follows these concepts.
6.3.1.1. Preprocessor data basics
The action of preprocessing an Ada source file is done according to
parameters (definition of preprocessor symbols, how to format directives
and disabled lines in the output, …). The File_Config type is used to
represent such parameters, and the Preprocess procedure taking a
File_Config argument can be used to preprocess a given source buffer.
-- Create a file configuration using symbol definitions from GNATprep's
-- "foo.txt" definition file, replacing directives and disabled lines
-- with blank lines.
Cfg : constant File_Config :=
(Enabled => True,
Definitions => Parse_Definition_File ("foo.txt"),
Line_Mode => Blank_Lines,
others => <>);
Input_Buffer : String := "...";
Output_Buffer : Preprocessed_Source;
Diagnostics : Langkit_Support.Diagnostics.Diagnostics_Vectors.Vector;
-- Preprocess the "Input_Buffer" source, writing the result to
-- ``Output_Buffer``.
Preprocess (Cfg, Input_Buffer, Output_Buffer, Diagnostics);
if not Diagnostics.Is_Empty then
-- Raise some error
else
declare
Buffer : String renames
Output_Buffer.Buffer (1 .. Output_Buffer.Last)
begin
-- Use the preprocessed source in "Buffer"
end;
end if;
Preprocessing for a whole Ada project is determined by a set of file
configurations: optionally several File_Config values for sources with
specific file names (see the File_Config_Maps.Map type), plus an
additional File_Config value to use for files not described in this
map (the “default” file config).
To create these data structures, it is possible to manipulate the Ada types
directly and call the Create_Preprocessor_Data function to get a
Preprocessor_Data value. It is also possible to parse GNATprep’s
“preprocessor data file” (i.e. the argument for the -gnatep compiler
switch) with the Parse_Preprocessor_Data_File function.
Path : constant Any_Path :=
Create_Path_From_Environ ("ADA_INCLUDE_PATH");
-- This path allows to find the preprocessor data file and the
-- definition files it references in the current directory or in any of
-- the directories pointed by the "ADA_INCLUDE_PATH" environment
-- variable.
Prep : constant Preprocessor_Data :=
Parse_Preprocessor_Data_File ("prep-data.txt", Path);
-- Parse the "prep-data.txt" preprocessor data file and create a full
-- preprocessor configuration from it.
6.3.1.2. Preprocessor data from GPR files
When the preprocessor for GNAT is set up with compiler switches
(-gnatep and -gnateD) registered in project files, it is convenient
to use the Extract_Preprocessor_Data_From_Project functions to extract
the preprocessor configuration from these compiler switches.
The following GPR attributes are searched for compiler switches:
Builder.Global_Compilation_SwitchesBuilder.Default_SwitchesBuilder.SwitchesCompiler.Default_SwitchesCompiler.Switches
Note that these functions produce an approximation, assuming that all compiler switches affect all files. This approximation should be a good fit for most projects, since when GNAT processes a compilation unit, it uses the same preprocessor configuration for all the files it analyzes (the source that is being compiled as well as all its closure). This is because it is not possible in the general case to produce a single preprocessor data that will match exactly what happens when compiling all the units in a project.
For instance, imagine the following sources:
-- foo.ads
package Foo is
end Foo;
-- bar.ads
with Foo;
package Bar is
end Bar;
-- no matter what there is in foo.adb and bar.adb
And the GPR files:
for Switches ("foo.adb") use ("-gnateDX=1");
for Switches ("bar.adb") use ("-gnateDX=2");
Then the preprocessor configuration used when analyzing foo.ads will
depend on whether it’s done during the compilation of foo.adb or of
bar.adb, which cannot be represented with the preprocessor data model
coming from GNATprep and GNAT itself.
For this simple example above, it is not a real problem because foo.ads
does not actually use the preprocessor, but this illustrates how
untractable it is to recover the preprocessor data from compiler switches
exactly as the compiler would see it.
6.3.1.3. Running the preprocessor
Once the preprocessor configuration is ready, it is possible to call the
Preprocess procedure, which takes a Preprocessor_Data argument,
plus the file name for the source file to preprocess (used to look up the
corresponding file configuration).
-- Preprocess the "Input_Buffer" source as being the content of a
-- "foo.adb" Ada source file, writing the result to "Output_Buffer".
Preprocess (Prep, "foo.adb", Input_Buffer, Output_Buffer, Diagnostics);
if not Diagnostics.Is_Empty then
-- Raise some error
else
declare
Buffer : String renames
Output_Buffer.Buffer (1 .. Output_Buffer.Last)
begin
-- Use the preprocessed source in "Buffer"
end;
end if;
Finally, in order to instruct a Libadalang analysis context to
automatically preprocess source files when loading files through the
Get_From_File function, one needs to use the file reader mechanism (see
Langkit_Support.File_Readers): first create a File_Reader_Reference
value that implements preprocessing (see the Create_Preprocessor and
Create_Preprocessor_From_File functions defined in this package) and
then pass it to the Create_Context context constructor.
FR : constant File_Reader_Reference :=
Create_Preprocessor_From_File ("prep-data.txt", Path);
Ctx : constant Analysis_Context := Create_Context (File_Reader => FR);
-- Analyze the "foo.adb" source file after preprocessing it according
-- to configuration for "foo.adb" files in "prep-data.txt". The
-- analysis of any other source file that this implies will also
-- trigger preprocessing for these files.
U : constant Analysis_Unit := Ctx.Get_From_File ("foo.adb");
6.3.1.4. Package contents
- type Value_Kind
- type Value_Type
- Discriminants:
Kind (
Value_Kind) –
- Components:
String_Value (
Ada.Strings.Unbounded.Unbounded_String) –Symbol_Value (
Ada.Strings.Unbounded.Unbounded_String) –
- function As_String (Value : Value_Type) Ada.Strings.Unbounded.Unbounded_String
Return the string that
Valuemust substitute to in the preprocessed sources.
- procedure Parse_Definition_Option (Option : Standard.String; Name : Ada.Strings.Unbounded.Unbounded_String; Value : Value_Type; Empty_Valid : Standard.Boolean)
If
Optionmatches<name>=<value>, where<name>is a valid preprocessor symbol name, setNameandValueto the corresponding values.If it matches
<name>or<name>=andEmpty_Validis true, setNameto it andValueto the empty value.Otherwise, raise a
Syntax_Errorexception.
- package Definition_Maps is new Ada.Containers.Hashed_Maps
package Definition_Maps is new Ada.Containers.Hashed_Maps (Key_Type => US.Unbounded_String, Element_Type => Value_Type, Hash => US.Hash, Equivalent_Keys => US."=");
- Instantiated generic package:
- function Parse_Definition_File (Filename : Standard.String) Libadalang.Preprocessing.Definition_Maps.Map
Parse the symbol file at
Filenameand return the corresponding definitions.See GNATprep’s documentation for a description of this file.
- type Any_Line_Mode
Determine how the preprocessor treats directives and disabled lines in the output.
Delete_LinesJust delete these lines: this breaks line number correspondance between the original source and the preprocessed one. This corresponds to GNATprep’s default mode.
Blank_LinesReplace these lines with empty lines. This corresponds to GNATprep’s
-boption.Comment_LinesPreserve these lines and emit a
--!comment marker in front of them. This corresponds to GNATprep’s-coption.Empty_Comment_LinesThese lines are replaced with exactly
--!. This corresponds to GNATprep’s-eoption.- function Create_Preprocessor_From_File (Filename : Standard.String; Path : GNATCOLL.File_Paths.Any_Path; Line_Mode : Any_Line_Mode) File_Reader_Reference
Convenience
Create_Preprocessor_From_Fileoverload, to force a given line mode for all source files on which the preprocessor is enabled.Forcing the line mode is often needed as the default is to remove lines that contain preprocessor directives and disabled code, which breaks the line number correspondance between original source code and preprocessed one. Forcing to
Blank_LinesorComment_Linespreserves this correspondance.
- function Create_Preprocessor_From_File (Filename : Standard.String; Path : GNATCOLL.File_Paths.Any_Path; Line_Mode : Any_Line_Mode) File_Refiner_Reference
- type File_Config
- Discriminants:
Enabled (
Standard.Boolean) –
- Components:
Definitions (
Libadalang.Preprocessing.Definition_Maps.Map) – Symbol/value associations for this file. Note that, in order for the preprocessing to work correctly, symbols must be lower case.Line_Mode (
Any_Line_Mode) – Determine how the preprocessor treats directives and disabled lines in the output.Print_Symbols (
Standard.Boolean) – Whether to print a sorted list of symbol and values on the standard output. Actually unused in this module.Undefined_Is_False (
Standard.Boolean) – Whether to treat undefined symbols as False in the context of a preprocessor test (see GNATprep’s-uoption).
Whether to treat undefined symbols as False in the context of a preprocessor test (see GNATprep’s
-uoption).- Disabled_File_Config : constant File_Config
- Object type:
- Default value:
(Enabled => False)
By default, the preprocessor is disabled on all Ada sources
- Base_Enabled_File_Config : constant File_Config
- Object type:
- Default value:
(Enabled => True, Line_Mode => Blank_Lines, others => <>)
Default file configuration when enabling preprocessing for a source file for the preprocessor integrated into GNAT.
- procedure Move (Target, Source : File_Config)
Move data from
SourcetoTarget. When this procedure returnsSourceisDisabled_File_Config.
- procedure Parse_Preprocessor_Data_File (Filename : Standard.String; Path : GNATCOLL.File_Paths.Any_Path; Default_Config : File_Config; File_Configs : Libadalang.Preprocessing.File_Config_Maps.Map)
Like the
Parse_Preprocessor_Data_Filefunction, but instead fill out theDefault_ConfigandFile_Configsarguments. This procedure is useful in order to modify the parsed configuration before creating thePreprocessor_Dataobject.See GNATprep’s documentation for a description of the preprocessor data file format.
- procedure Extract_Preprocessor_Data_From_Project (Tree : GNATCOLL.Projects.Project_Tree'Class; Project : GNATCOLL.Projects.Project_Type; Default_Config : File_Config; File_Configs : Libadalang.Preprocessing.File_Config_Maps.Map)
Like the
Extract_Preprocessor_Data_From_Projectfunction, but instead fill out theDefault_ConfigandFile_Configsarguments. This procedure is useful in order to modify the parsed configuration before creating thePreprocessor_Dataobject.
- procedure Extract_Preprocessor_Data_From_Project (Tree : GPR2.Project.Tree.Object; Project : GPR2.Project.View.Object; Default_Config : File_Config; File_Configs : Libadalang.Preprocessing.File_Config_Maps.Map)
Likewise, but with GPR2 projects
- function Create_Preprocessor_Data (Default_Config : File_Config; File_Configs : Libadalang.Preprocessing.File_Config_Maps.Map) Preprocessor_Data
Create preprocessor data using the given file-specific configurations and the given default configuration (for other files).
Note that this “consumes” both arguments, which are left respectively to
File_Config_Maps.EmptyandDisabled_File_Configupon return.
- procedure Iterate (Default_Config : File_Config; File_Configs : Libadalang.Preprocessing.File_Config_Maps.Map; Process : access procedure (Config : in out File_Config))
Call
Processon all the file configurations passed as arguments. This procedure helps forcing some configuration, for instance, forcing the line mode for all configurations.
- procedure Preprocess (Config : File_Config; Input : Standard.String; Contents : Preprocessed_Source; Diagnostics : Langkit_Support.Diagnostics.Diagnostics_Vectors.Vector)
Preprocess the
Inputsource buffer according to the givenConfigpreprocessor file configuration.On success, leave
Diagnosticsempty and return inContentsa newly allocated string containing the preprocessed source.On failure, leave
Contentsuninitialized and put error messages inDiagnostics.
- function Create_Preprocessor (Default_Config : File_Config; File_Configs : Libadalang.Preprocessing.File_Config_Maps.Map) File_Reader_Reference
Like
Create_Preprocessor_Data, but return a file reader implementing the preprocessing instead.
- function Create_Preprocessor (Default_Config : File_Config; File_Configs : Libadalang.Preprocessing.File_Config_Maps.Map) File_Refiner_Reference
- procedure Dump (Config : File_Config; Prefix : Standard.String)
Dump the content of
Configon the standard output, each line prefixed withPrefix.
- package File_Config_Maps is new Ada.Containers.Hashed_Maps
package File_Config_Maps is new Ada.Containers.Hashed_Maps (Key_Type => US.Unbounded_String, Element_Type => File_Config, Hash => US.Hash, Equivalent_Keys => US."=");
- Instantiated generic package:
For each source file (identifier by basename only), preprocessor configuration to use.
- type Preprocessor_Data
File-specific Symbol/value associations and options to run the preprocessor.
This type is a reference to constant preprocessing configuration: copying this object is cheap.
- No_Preprocessor_Data : constant Preprocessor_Data
- Object type:
No reference to preprocessor data
- function "=" (Left, Right : Preprocessor_Data) Standard.Boolean
- function Parse_Preprocessor_Data_File (Filename : Standard.String; Path : GNATCOLL.File_Paths.Any_Path) Preprocessor_Data
Parse the preprocessor data file at
Filenameand return the corresponding data.Pathis used to look for the preprocessor data file itself and for definition files that the preprocessor data file may refer to.See GNATprep’s documentation for a description of the preprocessor data file format.
- function Extract_Preprocessor_Data_From_Project (Tree : GNATCOLL.Projects.Project_Tree'Class; Project : GNATCOLL.Projects.Project_Type) Preprocessor_Data
Create preprocessor data from compiler arguments found in the given GPR project (
-gnatepand-gnateDarguments).If a non-null
Projectis given, look for compiler arguments in it and the other projects in its closure. IfProjectis left toNo_Project, try to use the whole project tree.Note that this function collects all arguments and returns an approximation from them: it does not replicates exactly gprbuild’s behavior.
- function Extract_Preprocessor_Data_From_Project (Tree : GPR2.Project.Tree.Object; Project : GPR2.Project.View.Object) Preprocessor_Data
Likewise, but with GPR2 projects
- function Default_Config (Data : Preprocessor_Data) File_Config
Return the default file configuration in
Data
- function File_Configs (Data : Preprocessor_Data) Libadalang.Preprocessing.File_Config_Maps.Map
Return all file configurations in
Data
- function Needs_Preprocessing (Data : Preprocessor_Data; Filename : Standard.String) Standard.Boolean
Return whether
Filenamemust be preprocessed according toData
- procedure Write_Preprocessor_Data_File (Data : Preprocessor_Data; Filename : Standard.String; Definition_Files_Directory : Standard.String)
Write a preprocessor data file that encodes the information in
DatatoFilename.This operation may need to create definition files: they are created in
Definition_Files_Directory, which must be a writeable directory. Note that all created definition files will be nameddefs-*.txt, so it is safe to write the preprocessor data file in the same directory with a different name.Exceptions from
Ada.Text_IOprimitives are propagated when there is trouble writing files.
- procedure Preprocess (Data : Preprocessor_Data; Filename, Input : Standard.String; Contents : Preprocessed_Source; Diagnostics : Langkit_Support.Diagnostics.Diagnostics_Vectors.Vector)
Preprocess the
Inputsource buffer according to the corresponding source filenameFilenameand the given preprocessor data.Note that
Filenameis used here only to look inDatafor theFile_Configvalue to use in order to preprocess theInputsource buffer.On success, leave
Diagnosticsempty and return inContentsa newly allocated string containing the preprocessed source.On failure, leave
Contentsuninitialized and put error messages inDiagnostics.
- procedure Dump (Data : Preprocessor_Data; Prefix : Standard.String)
Dump the content of
Dataon the standard output, each line prefixed withPrefix.
- type Preprocessed_Source
- Components:
Buffer (
System.Strings.String_Access) –Last (
Standard.Natural) –
- procedure Free (Self : Preprocessed_Source)
Deallocate the given source buffer
- function Create_Preprocessor_From_File (Filename : Standard.String; Path : GNATCOLL.File_Paths.Any_Path) File_Reader_Reference
Like
Parse_Preprocessor_Data_File, but return a file reader implementing the preprocessing instead.
- function Create_Preprocessor_From_File (Filename : Standard.String; Path : GNATCOLL.File_Paths.Any_Path) File_Refiner_Reference
- procedure Dump (Definitions : Libadalang.Preprocessing.Definition_Maps.Map; Prefix : Standard.String)
Dump the content of
Definitionson the standard output, each line prefixed withPrefix.