AdaCore / libadalang

Ada semantic analysis library.
https://www.adacore.com
Other
147 stars 42 forks source link

[Feature Request] Add support for creating new units programmatically #966

Closed andrewathalye closed 3 months ago

andrewathalye commented 4 months ago

Libadalang is already quite comprehensive and provides a function for returning the text form of a Unit. Would it be possible to add support for adding nodes to a unit? This would be ideal for, e.g., dynamically generating Ada bindings for foreign-language library headers.

thvnx commented 3 months ago

Hi @andrewathalye , thank you for the suggestion and sorry for the late reply.

Actually, this is a feature we are currently working on, it's still experimental, and not well documented yet but you may find it useful to read the specification of the Libadalang.Rewriting package and its content, which you should find in your libadalang package directory under src/libadalang-rewriting.ads.

Also, I joined an example below, which contains a small example of how to start using the API (it has been tested with the 24.2 version of Libadalang but it should be compatible with the 24.0.0 version available in the Alire registry).

with Ada.Characters.Latin_1; use Ada.Characters.Latin_1;
with Ada.Text_IO; use Ada.Text_IO;

with Libadalang.Analysis;
with Libadalang.Common;
with Libadalang.Generic_API.Introspection;
with Libadalang.Rewriting;

with Langkit_Support.Text; use Langkit_Support.Text;

procedure Lal_Rewriting_Example is
   package LAL  renames Libadalang.Analysis;
   package LALC renames Libadalang.Common;
   package LALI renames Libadalang.Generic_API.Introspection;
   package LALR renames Libadalang.Rewriting;

   --  Analysis objects
   Analysis_Ctx  : constant LAL.Analysis_Context := LAL.Create_Context;
   Analysis_Unit : constant LAL.Analysis_Unit := Analysis_Ctx.Get_From_Buffer
     (Filename => "main.adb",
      Buffer   => "with Ada.Text_IO; use Ada.Text_IO;" & LF &
                  "procedure Main is"                  & LF &
                  "begin"                              & LF &
                  "   Put_Line(""Hello!"");"           & LF &
                  "end Main;");

   --  Rewriting objects
   Ctx_Handle  : LALR.Rewriting_Handle;
   Unit_Handle : LALR.Unit_Rewriting_Handle;
   Apply_Res   : LALR.Apply_Result;
begin
   --  Display the initial parsing tree
   Put_Line ("Initial parsing tree:");
   Put_Line ("=====");
   Analysis_Unit.Root.Print;
   Put_Line ("=====");
   Put_Line ("");

   --  Start by calling the `Start_Rewriting` function to get a rewriting
   --  handle on the context
   Ctx_Handle  := LALR.Start_Rewriting (Analysis_Ctx);
   Unit_Handle := LALR.Handle (Analysis_Unit);

   --  Show the rewriting unit initial unparsing result, you can use the
   --  `Libadalang.Rewriting.Unparse` function
   Put_Line ("Rewriting unit handle unparsing before modifications:");
   Put_Line ("=====");
   Put_Line (To_UTF8 (To_Text (LALR.Unparse (Unit_Handle))));
   Put_Line ("=====");
   Put_Line ("");

   --  Change the displayed text using the Libadalang rewriting API
   LALR.Set_Child
     (Handle => LALR.Handle
        (Analysis_Unit.Root.As_Compilation_Unit
                      .F_Body.As_Library_Item
                      .F_Item.As_Subp_Body
                      .F_Stmts.As_Handled_Stmts
                      .F_Stmts.Child (1).As_Call_Stmt
                      .F_Call.As_Call_Expr
                      .F_Suffix.Child (1)),
      Field  => LALI.Member_Refs.Param_Assoc_F_R_Expr,
      Child  => LALR.Create_Token_Node
        (Ctx_Handle,
         LALC.Ada_String_Literal,
         To_Text ("""Replaced!""")));

   --  Display the modified rewriting unit unparsing result
   Put_Line ("Rewriting unit handle unparsing after modifications:");
   Put_Line ("=====");
   Put_Line (To_UTF8 (To_Text (LALR.Unparse (Unit_Handle))));
   Put_Line ("=====");
   Put_Line ("");

   --  Apply modifications to the original rewriting context
   Apply_Res := LALR.Apply (Ctx_Handle);

   --  Ensure the success of rewriting application
   if Apply_Res.Success then
      Put_Line ("Modified parsing tree:");
      Put_Line ("=====");
      Analysis_Unit.Root.Print;
      Put_Line ("=====");
   else
      Put_Line ("Error(s) during the rewriting application phase");
   end if;
end Lal_Rewriting_Example;

Do not hesitate to give us some feedback if you give it a try (please keep in mind that this API is experimental and might change in the future)!