-- Abstract : -- -- See spec. -- -- Copyright (C) 2020 - 2022 Free Software Foundation All Rights Reserved. -- -- This library is free software; you can redistribute it and/or modify it -- under terms of the GNU General Public License as published by the Free -- Software Foundation; either version 3, or (at your option) any later -- version. This library is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- -- TABILITY or FITNESS FOR A PARTICULAR PURPOSE. pragma License (GPL); with Ada.Directories; with Ada.Exceptions; with Ada.Tags; with GNAT.OS_Lib; with SAL.Gen_Unbounded_Definite_Red_Black_Trees; package body Wisi.Parse_Context is function Source_File_Name (Item : in Parse_Context_Access) return String is (Ada.Strings.Unbounded.To_String (Item.File_Name)); package File_Parse_Context_Maps is new SAL.Gen_Unbounded_Definite_Red_Black_Trees (Element_Type => Parse_Context_Access, Key_Type => String, Key => Source_File_Name, Key_Compare => SAL.String_Compare); Map : File_Parse_Context_Maps.Tree; function Create_No_File (Factory : in WisiToken.Parse.Factory; Trace : in WisiToken.Trace_Access) return Parse_Context_Access is use WisiToken; begin return Result : constant Parse_Context_Access := new Parse_Context do Result.Parser := Factory.all; if Trace_Incremental_Parse > Outline then Trace.Put_Line ("parse_context (no file) created, language " & Ada.Tags.Expanded_Name (Result.Parser.User_Data'Tag)); if Trace_Memory > Outline then Report_Memory (Trace.all, Prefix => True); end if; end if; end return; end Create_No_File; procedure Create_No_Text (File_Name : in String; Factory : in WisiToken.Parse.Factory; Trace : in WisiToken.Trace_Access) is Temp : constant Parse_Context_Access := Create_No_File (Factory, Trace); begin Set_File (File_Name, Temp); end Create_No_Text; procedure Set_File (File_Name : in String; Parse_Context : in Parse_Context_Access) is use File_Parse_Context_Maps; use WisiToken; use Ada.Strings.Unbounded; begin if Length (Parse_Context.File_Name) > 0 then raise Protocol_Error; end if; Parse_Context.File_Name := +File_Name; Map.Insert (Parse_Context); end Set_File; function Find_Create (File_Name : in String; Factory : in WisiToken.Parse.Factory; Trace : in WisiToken.Trace_Access) return Parse_Context_Access is begin if File_Name'Length = 0 then raise Wisi.Protocol_Error with "no file name given"; end if; declare use File_Parse_Context_Maps; use WisiToken; Found : constant Cursor := Map.Find (File_Name); begin if Has_Element (Found) then return Result : constant Parse_Context_Access := Element (Found) do if Trace_Incremental_Parse > Outline then Trace.Put_Line ("parse_context found"); end if; end return; end if; return Result : constant Parse_Context_Access := new Parse_Context' (File_Name => +File_Name, Text_Buffer => null, Text_Buffer_Byte_Last => 0, Text_Buffer_Char_Last => 0, Parser => Factory.all, Prev_Tree => <>, Save_Prev_Text_Tree => False, Root_Save_Edited_Name => <>, Save_Edited_Count => <>, Frozen => False) do Map.Insert (Result); if Trace_Incremental_Parse > Outline then Trace.Put_Line ("parse_context created, language " & Ada.Tags.Expanded_Name (Result.Parser.User_Data'Tag)); if Trace_Memory > Outline then Report_Memory (Trace.all, Prefix => True); end if; end if; end return; end; end Find_Create; function Find (File_Name : in String; Have_Text : in Boolean := False) return Parse_Context_Access is begin if File_Name'Length = 0 then raise Wisi.Protocol_Error with "no file name given"; end if; declare use File_Parse_Context_Maps; use WisiToken; use all type Ada.Strings.Unbounded.String_Access; Found : constant Cursor := Map.Find (File_Name); begin if Has_Element (Found) then return Result : constant Parse_Context_Access := Element (Found) do if Have_Text and (Result.Text_Buffer = null or else Result.Text_Buffer'Length = 0) then if Trace_Incremental_Parse > Outline then Result.Parser.Tree.Lexer.Trace.Put_Line ("parse_context found, but text buffer empty"); end if; raise Not_Found; end if; if Trace_Incremental_Parse > Outline then Result.Parser.Tree.Lexer.Trace.Put_Line ("parse_context found" & (if Have_Text then " and text present" else "")); end if; end return; else raise Not_Found; end if; end; end Find; procedure Kill (File_Name : in String; Free_Parser : in WisiToken.Parse.Free_Parser) is begin if File_Name'Length = 0 then raise Wisi.Protocol_Error with "no file name given"; end if; declare use File_Parse_Context_Maps; Found : constant Cursor := Map.Find (File_Name); begin if not Has_Element (Found) then -- already killed, or never opened null; else declare Context : Parse_Context_Access := Element (Found); begin Map.Delete (File_Name); Ada.Strings.Unbounded.Free (Context.Text_Buffer); Free_Parser (Context.Parser); Free (Context); end; end if; end; end Kill; procedure Clear is begin Map.Clear; end Clear; function Image (Item : in Change) return String is use WisiToken; begin return "(" & Item.Begin_Byte_Pos'Image & "," & Item.Begin_Char_Pos'Image & "," & Item.Inserted_End_Byte_Pos'Image & "," & Item.Inserted_End_Char_Pos'Image & "," & " +""" & (-Item.Inserted_Text) & """," & Item.Deleted_Bytes'Image & "," & Item.Deleted_Chars'Image & ")"; end Image; function Get_Emacs_Change_List (Command_Line : in String; Last : in out Integer) return Change_Lists.List is function Substitute_Escapes (Item : in String) return String is begin if Item'Length = 0 then return Item; else declare I : Integer := Item'First; J : Integer := Item'First; Result : String (Item'Range); begin loop -- See test_edit_source.adb String_Escape for rationale of what is -- handled here. if Item (I) = '\' and I < Item'Last then if Item (I + 1) = 'n' then Result (J) := ASCII.LF; I := @ + 2; elsif Item (I + 1) = '"' then Result (J) := '"'; I := @ + 2; elsif Item (I + 1) = '\' then Result (J) := '\'; I := @ + 2; else Result (J) := Item (I); I := @ + 1; end if; else Result (J) := Item (I); I := @ + 1; end if; exit when I > Item'Last; J := @ + 1; end loop; return Result (Result'First .. J); end; end if; end Substitute_Escapes; begin return Result : Change_Lists.List do Skip (Command_Line, Last, '('); -- start of changes list loop exit when Last = Command_Line'Last; exit when Command_Line (Last + 1) = ')'; declare use WisiToken; Item : Change; begin Skip (Command_Line, Last, '('); Item.Begin_Byte_Pos := Base_Buffer_Pos (Get_Integer (Command_Line, Last)); Item.Begin_Char_Pos := Base_Buffer_Pos (Get_Integer (Command_Line, Last)); Item.Inserted_End_Byte_Pos := Base_Buffer_Pos (Get_Integer (Command_Line, Last)); Item.Inserted_End_Char_Pos := Base_Buffer_Pos (Get_Integer (Command_Line, Last)); Item.Deleted_Bytes := Get_Integer (Command_Line, Last); Item.Deleted_Chars := Get_Integer (Command_Line, Last); Item.Inserted_Text := +Substitute_Escapes (Get_String (Command_Line, Last)); Skip (Command_Line, Last, ')'); if Integer (Item.Inserted_End_Byte_Pos - Item.Begin_Byte_Pos) /= Ada.Strings.Unbounded.Length (Item.Inserted_Text) then raise Protocol_Error with "invalid change; begin, end does not match inserted_text length" & Item.Begin_Byte_Pos'Image & Item.Inserted_End_Byte_Pos'Image & Integer'Image (Ada.Strings.Unbounded.Length (Item.Inserted_Text)); end if; Result.Append (Item); end; end loop; Skip (Command_Line, Last, ')'); -- end of edits list end return; end Get_Emacs_Change_List; procedure Edit_Source (Trace : in out WisiToken.Trace'Class; Parse_Context : in out Wisi.Parse_Context.Parse_Context; Changes : in Change_Lists.List; KMN_List : out WisiToken.Parse.KMN_Lists.List) is use Ada.Containers; use WisiToken; -- Changes is in increasing time order (ie _not_ in buffer pos -- order); KMN_List is in buffer pos order. Source : Ada.Strings.Unbounded.String_Access renames Parse_Context.Text_Buffer; Source_Byte_Last : Integer renames Parse_Context.Text_Buffer_Byte_Last; Source_Char_Last : Integer renames Parse_Context.Text_Buffer_Char_Last; Initial_Text_Byte_Region : constant Buffer_Region := (1, Base_Buffer_Pos (Source_Byte_Last)); Initial_Text_Char_Region : constant Buffer_Region := (1, Base_Buffer_Pos (Source_Char_Last)); Gap_First : Integer := Source_Byte_Last + 1; Gap_Last : Integer := Source'Last; function Gap_Invariant return Boolean is (Gap_Last - Gap_First = Source'Last - (Source_Byte_Last + 1)); Total_Inserted_Bytes : Integer := 0; function Reallocate return Boolean is begin -- This is a conservative analysis; Total_Inserted_Bytes is correct -- if all Changes are inserted with no overlap and no deletes; -- otherwise it is too large. But the savings from being more -- accurate are not large, and this simplifies the editing algorithm. if Changes.Length = 0 then return False; end if; for Change of Changes loop Total_Inserted_Bytes := @ + Ada.Strings.Unbounded.Length (Change.Inserted_Text); end loop; if Source_Byte_Last + Total_Inserted_Bytes > Source'Last then return True; else return False; end if; end Reallocate; procedure Move_Gap (New_Gap_First : in Integer) with Pre => New_Gap_First /= Gap_First and Gap_Invariant and (if New_Gap_First < Gap_First then (New_Gap_First + Gap_Last - Gap_First) + 1 in Source'First .. Source'Last and New_Gap_First in Source'First .. Source'Last else New_Gap_First - 1 in Source'First .. Source'Last and Gap_Last + 1 in Source'First .. Source'Last), Post => Gap_Invariant is -- Examples: -- gap_first : 15 -- gap_last : 19 -- -- new_gap_first: 5 -- new_gap_last := 9 -- source (10 .. 19) := source (5 .. 14) -- -- new_gap_first: 25 -- new_gap_last : 29 -- source (15 .. 24) := source (20 .. 29) New_Gap_Last : constant Integer := New_Gap_First + Gap_Last - Gap_First; begin if New_Gap_First < Gap_First then Source (New_Gap_Last + 1 .. Gap_Last) := Source (New_Gap_First .. Gap_First - 1); else Source (Gap_First .. New_Gap_First - 1) := Source (Gap_Last + 1 .. New_Gap_Last); end if; Gap_First := New_Gap_First; Gap_Last := New_Gap_Last; end Move_Gap; procedure Edit_Text (Change : in Wisi.Parse_Context.Change) with Pre => Gap_Invariant, Post => Gap_Invariant -- Apply Change to Source. Leaves Gap at edit point. is use Ada.Strings.Unbounded; Inserted_Bytes : constant Integer := Ada.Strings.Unbounded.Length (Change.Inserted_Text); begin if Gap_First /= Integer (Change.Begin_Byte_Pos) then Move_Gap (Integer (Change.Begin_Byte_Pos)); end if; if Change.Deleted_Bytes > 0 then Gap_Last := @ + Change.Deleted_Bytes; pragma Assert (Gap_Last <= Source'Last); Source_Byte_Last := @ - Change.Deleted_Bytes; Source_Char_Last := @ - Change.Deleted_Chars; end if; if Inserted_Bytes > 0 then pragma Assert (Gap_Last + 1 - Gap_First >= Inserted_Bytes); Source (Gap_First .. Gap_First + Inserted_Bytes - 1) := -Change.Inserted_Text; Gap_First := Gap_First + Inserted_Bytes; Source_Byte_Last := @ + Inserted_Bytes; Source_Char_Last := @ + Integer (Change.Inserted_End_Char_Pos - Change.Begin_Char_Pos); end if; end Edit_Text; procedure Delete_KMNs (KMN_Last_Byte : in Zero_Buffer_Pos; KMN_Last_Char : in Zero_Buffer_Pos; After : in Parse.KMN_Lists.Cursor; Last_Deleted_Byte : in Buffer_Pos; Last_Deleted_Char : in Buffer_Pos; KMN : in out Parse.KMN) -- Last_Deleted_Byte is deleted from current text by current -- Change. Delete KMNs after After whose Stable are entirely within -- Last_Deleted_Byte; merge into KMN (initially the current Change). -- Adjust following KMN if stable contains Last_Deleted_Byte. is use Parse.KMN_Lists; use all type Parse.KMN; Last_Byte : Zero_Buffer_Pos := KMN_Last_Byte + KMN_List (After).Stable_Bytes + KMN_List (After).Inserted_Bytes; -- end of After KMN and subsequent deleted KMN Last_Char : Zero_Buffer_Pos := KMN_Last_Char + KMN_List (After).Stable_Chars + KMN_List (After).Inserted_Chars; Cur : Cursor := Next (After); begin loop exit when not Has_Element (Cur); if Last_Byte + KMN_List (Cur).Stable_Bytes + KMN_List (Cur).Inserted_Bytes <= Last_Deleted_Byte then -- All of cur inserted are deleted, and some of next. -- test_edit_source.adb Edit_06. KMN.Deleted_Bytes := @ + KMN_List (Cur).Deleted_Bytes - KMN_List (Cur).Inserted_Bytes; KMN.Deleted_Chars := @ + KMN_List (Cur).Deleted_Chars - KMN_List (Cur).Inserted_Chars; Last_Byte := @ + KMN_List (Cur).Stable_Bytes + KMN_List (Cur).Inserted_Bytes; Last_Char := @ + KMN_List (Cur).Stable_Chars + KMN_List (Cur).Inserted_Chars; declare To_Delete : Cursor := Cur; begin Cur := Next (Cur); KMN_List.Delete (To_Delete); end; elsif Last_Byte + KMN_List (Cur).Stable_Bytes <= Last_Deleted_Byte then -- Some of Cur.inserted are deleted. test_edit_source.adb Edit_05. declare Deleted_Bytes : constant Zero_Buffer_Pos := Last_Deleted_Byte - (Last_Byte + KMN_List (Cur).Stable_Bytes); -- bytes of cur.inserted that are deleted Deleted_Chars : constant Zero_Buffer_Pos := Last_Deleted_Char - (Last_Char + KMN_List (Cur).Stable_Chars); begin KMN.Inserted_Bytes := @ - Deleted_Bytes + KMN_List (Cur).Inserted_Bytes; KMN.Inserted_Chars := @ - Deleted_Chars + KMN_List (Cur).Inserted_Chars; KMN.Deleted_Bytes := @ + KMN_List (Cur).Deleted_Bytes - Deleted_Bytes; KMN.Deleted_Chars := @ + KMN_List (Cur).Deleted_Chars - Deleted_Chars; KMN_List.Delete (Cur); exit; end; else -- Last_Byte is in Cur.stable KMN_List (Cur).Stable_Bytes := @ - (Last_Deleted_Byte - Last_Byte); KMN_List (Cur).Stable_Chars := @ - (Last_Deleted_Char - Last_Char); if KMN_List (Cur) = (others => 0) then KMN_List.Delete (Cur); end if; exit; end if; end loop; end Delete_KMNs; procedure Edit_KMN (Change : in Wisi.Parse_Context.Change) -- Apply Change to KMN list is use Parse.KMN_Lists; use all type Parse.KMN; Cur : Cursor := KMN_List.First; KMN_Last_Byte : Base_Buffer_Pos := 0; -- Last byte of prev KMN. KMN_Last_Char : Base_Buffer_Pos := 0; -- Last char of prev KMN. function To_KMN (Item : in Wisi.Parse_Context.Change) return Parse.KMN -- Assuming Change does not overlap any current KMN non-stable, -- return a new KMN for it. is (Stable_Bytes => Item.Begin_Byte_Pos - KMN_Last_Byte - 1, -- Begin_Byte_Pos is deleted or inserted Stable_Chars => Item.Begin_Char_Pos - KMN_Last_Char - 1, Inserted_Bytes => Item.Inserted_End_Byte_Pos - Item.Begin_Byte_Pos, -- End_Byte_Pos is after last inserted Inserted_Chars => Item.Inserted_End_Char_Pos - Item.Begin_Char_Pos, Deleted_Bytes => Base_Buffer_Pos (Item.Deleted_Bytes), Deleted_Chars => Base_Buffer_Pos (Item.Deleted_Chars)); begin loop declare Cur_KMN : Parse.KMN renames KMN_List (Cur); KMN : Parse.KMN := To_KMN (Change); Cur_Last_Inserted_Byte : constant Base_Buffer_Pos := KMN_Last_Byte + Cur_KMN.Stable_Bytes + Cur_KMN.Inserted_Bytes; Cur_Last_Inserted_Char : constant Base_Buffer_Pos := KMN_Last_Char + Cur_KMN.Stable_Chars + Cur_KMN.Inserted_Chars; Change_Last_Deleted_Byte : constant Base_Buffer_Pos := Change.Begin_Byte_Pos + Base_Buffer_Pos (Change.Deleted_Bytes) - 1; Change_Last_Deleted_Char : constant Base_Buffer_Pos := Change.Begin_Char_Pos + Base_Buffer_Pos (Change.Deleted_Chars) - 1; begin pragma Assert (KMN_Last_Byte < Change.Begin_Byte_Pos); if Change.Begin_Byte_Pos + Base_Buffer_Pos (Change.Deleted_Bytes) - 1 < KMN_Last_Byte + Cur_KMN.Stable_Bytes then -- Change is entirely within Cur_KMN.Stable_Bytes; -- test_edit_source.adb Edit_01 -- -- Or Change is inserting at end of text; Edit_10. Cur_KMN.Stable_Bytes := @ - (KMN.Stable_Bytes + KMN.Deleted_Bytes); Cur_KMN.Stable_Chars := @ - (KMN.Stable_Chars + KMN.Deleted_Chars); if KMN_List (Cur) = (others => 0) then Cur_KMN := KMN; else KMN_List.Insert (Before => Cur, Element => KMN); end if; exit; elsif Change.Begin_Byte_Pos <= KMN_Last_Byte + Cur_KMN.Stable_Bytes + 1 then -- Change starts in or immediately after Cur_KMN.Stable_Bytes, ends -- in or after Cur_KMN.Insert; merge Change into Cur_KMN. if Cur_Last_Inserted_Byte >= Change_Last_Deleted_Byte then -- Some of Cur_KMN.Inserted are preserved; test_edit_source.adb -- Edit_02, _03, Deindent. -- -- cur_kmn next_kmn -- stable| ins| stable| ins| ... -- -- change -- | ins | -- | del | Cur_KMN.Inserted_Bytes := KMN.Inserted_Bytes + Cur_Last_Inserted_Byte - Change_Last_Deleted_Byte; Cur_KMN.Inserted_Chars := KMN.Inserted_Chars + Cur_Last_Inserted_Char - Change_Last_Deleted_Char; Cur_KMN.Deleted_Bytes := @ + KMN_Last_Byte + Cur_KMN.Stable_Bytes + 1 - Change.Begin_Byte_Pos; Cur_KMN.Deleted_Chars := @ + KMN_Last_Char + Cur_KMN.Stable_Chars + 1 - Change.Begin_Char_Pos; else -- All of Cur_KMN.Inserted and some of following KMN are deleted; -- test_edit_source.adb Edit_04, _05, _06. -- cur_kmn next_kmn -- stable| ins| stable| ins| ... -- -- change -- | ins | -- | del | Delete_KMNs (KMN_Last_Byte, KMN_Last_Char, Cur, Last_Deleted_Byte => Change.Begin_Byte_Pos + KMN.Deleted_Bytes - 1, Last_Deleted_Char => Change.Begin_Char_Pos + KMN.Deleted_Chars - 1, KMN => KMN); Cur_KMN.Deleted_Bytes := @ + KMN.Deleted_Bytes - Cur_KMN.Inserted_Bytes; Cur_KMN.Deleted_Chars := @ + KMN.Deleted_Chars - Cur_KMN.Inserted_Chars; Cur_KMN.Inserted_Bytes := KMN.Inserted_Bytes; Cur_KMN.Inserted_Chars := KMN.Inserted_Chars; end if; Cur_KMN.Stable_Bytes := KMN.Stable_Bytes; Cur_KMN.Stable_Chars := KMN.Stable_Chars; exit; elsif Change.Begin_Byte_Pos <= KMN_Last_Byte + Cur_KMN.Stable_Bytes + Cur_KMN.Inserted_Bytes + 1 then -- Change starts in or immediately after Cur_KMN inserted; merge -- Change into Cur_KMN. test_edit_source.adb Edit_07, _08, _09, -- Insert_Deindent if Cur_Last_Inserted_Byte >= Change_Last_Deleted_Byte then -- Beginning and end of Cur_KMN.Inserted are preserved; test_edit_source.adb -- Edit_07. -- -- cur_kmn next_kmn -- stable| ins | stable| ins| ... -- -- change -- | ins | -- | del| Cur_KMN.Inserted_Bytes := KMN.Inserted_Bytes + Cur_KMN.Inserted_Bytes - KMN.Deleted_Bytes; Cur_KMN.Inserted_Chars := KMN.Inserted_Chars + Cur_KMN.Inserted_Chars - KMN.Deleted_Chars; -- Cur_KMN.Deleted_Bytes unchanged else -- Remainder of Cur_KMN.Inserted and some of following KMN are deleted; -- test_edit_source.adb Edit_08, _09 -- cur_kmn next_kmn -- stable| ins| stable| ins| ... -- -- change -- | ins | -- | del | Delete_KMNs (KMN_Last_Byte, KMN_Last_Char, Cur, Last_Deleted_Byte => Change.Begin_Byte_Pos + KMN.Deleted_Bytes - 1, Last_Deleted_Char => Change.Begin_Char_Pos + KMN.Deleted_Chars - 1, KMN => KMN); declare Remaining_Cur_Ins_Bytes : constant Zero_Buffer_Pos := Change.Begin_Byte_Pos - (KMN_Last_Byte + Cur_KMN.Stable_Bytes + 1); Remaining_Cur_Ins_Chars : constant Zero_Buffer_Pos := Change.Begin_Char_Pos - (KMN_Last_Char + Cur_KMN.Stable_Chars + 1); begin Cur_KMN.Deleted_Bytes := @ + KMN.Deleted_Bytes - (Cur_KMN.Inserted_Bytes - Remaining_Cur_Ins_Bytes); Cur_KMN.Deleted_Chars := @ + KMN.Deleted_Chars - (Cur_KMN.Inserted_Chars - Remaining_Cur_Ins_Chars); Cur_KMN.Inserted_Bytes := Remaining_Cur_Ins_Bytes + KMN.Inserted_Bytes; Cur_KMN.Inserted_Chars := Remaining_Cur_Ins_Chars + KMN.Inserted_Chars; end; end if; exit; else -- Change is entirely after Cur_KMN KMN_Last_Byte := @ + Cur_KMN.Stable_Bytes + Cur_KMN.Inserted_Bytes; KMN_Last_Char := @ + Cur_KMN.Stable_Chars + Cur_KMN.Inserted_Chars; Next (Cur); if not Has_Element (Cur) then -- Since KMN_List starts with one KMN covering all of Source, we -- should never get here. raise SAL.Programmer_Error; end if; end if; end; end loop; if Cur /= KMN_List.Last and then (KMN_List (Cur).Inserted_Bytes = 0 and KMN_List (Cur).Deleted_Bytes = 0) then -- Change undone; merge stable with next KMN. test_edit_source.adb Edit_11 declare To_Delete : Cursor := Cur; begin Next (Cur); KMN_List (Cur).Stable_Bytes := @ + KMN_List (To_Delete).Stable_Bytes; KMN_List (Cur).Stable_Chars := @ + KMN_List (To_Delete).Stable_Chars; KMN_List.Delete (To_Delete); end; end if; if Debug_Mode then begin WisiToken.Parse.Validate_KMN (List => KMN_List, Initial_Text_Byte_Region => Initial_Text_Byte_Region, Initial_Text_Char_Region => Initial_Text_Char_Region, Edited_Text_Byte_Region => Buffer_Region'(1, Base_Buffer_Pos (Source_Byte_Last)), Edited_Text_Char_Region => Buffer_Region'(1, Base_Buffer_Pos (Source_Char_Last))); exception when E : WisiToken.User_Error => raise Protocol_Error with Ada.Exceptions.Exception_Message (E); end; end if; end Edit_KMN; begin if Reallocate then declare New_Source : constant Ada.Strings.Unbounded.String_Access := new String (Source'First .. Source_Byte_Last + Total_Inserted_Bytes); begin New_Source (Source'First .. Source_Byte_Last) := Source (Source'First .. Source_Byte_Last); Ada.Strings.Unbounded.Free (Source); Source := New_Source; end; Gap_Last := Source'Last; end if; -- Start with one KMN with stable region = entire source. KMN_List.Append ((Stable_Bytes => Base_Buffer_Pos (Source_Byte_Last), Stable_Chars => Base_Buffer_Pos (Source_Char_Last), Deleted_Bytes => 0, Deleted_Chars => 0, Inserted_Bytes => 0, Inserted_Chars => 0)); for Change of Changes loop Edit_Text (Change); Edit_KMN (Change); if Trace_Incremental_Parse > Detail then Trace.Put_Line ("change:" & Image (Change)); Trace.Put_Line ("kmn_list:"); for KMN of KMN_List loop Trace.Put_Line (Parse.Image (KMN)); end loop; end if; end loop; if Gap_Last /= Source'Last then -- Remove the gap Source (Gap_First .. Source_Byte_Last) := Source (Gap_Last + 1 .. Source'Last); end if; end Edit_Source; procedure Dump_Prev_Tree (Context : in Parse_Context; File_Name : in String) is begin Context.Prev_Tree.Put_Tree (File_Name); end Dump_Prev_Tree; procedure Save_Text (Context : in Parse_Context; File_Name : in String) is use GNAT.OS_Lib; File : File_Descriptor; Written : Integer; pragma Unreferenced (Written); begin if Ada.Directories.Exists (File_Name) then Ada.Directories.Delete_File (File_Name); end if; File := Create_New_File (File_Name, Fmode => Binary); Written := Write (File, Context.Text_Buffer (Context.Text_Buffer'First)'Address, N => Context.Text_Buffer_Byte_Last - Context.Text_Buffer'First + 1); -- Written /= N on disk full; we don't check for that, because there's -- nothing to do. Close (File); Context.Parser.Tree.Lexer.Trace.Put_Line ("text saved to '" & File_Name & "'"); end Save_Text; procedure Save_Text_Auto (Context : in out Parse_Context) is begin Context.Save_Edited_Count := @ + 1; declare Save_File_Name : constant String := Ada.Strings.Unbounded.To_String (Context.Root_Save_Edited_Name) & "_" & Wisi.Integer_Filled_Image (Item => Context.Save_Edited_Count, Width => 3); begin Save_Text (Context, Save_File_Name); end; end Save_Text_Auto; end Wisi.Parse_Context;