1 ------------------------------------------------------------------------------
3 -- GNAT COMPILER COMPONENTS --
9 -- Copyright (C) 2000-2003 Ada Core Technologies, Inc. --
11 -- GNAT is free software; you can redistribute it and/or modify it under --
12 -- terms of the GNU General Public License as published by the Free Soft- --
13 -- ware Foundation; either version 2, or (at your option) any later ver- --
14 -- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
15 -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
16 -- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License --
17 -- for more details. You should have received a copy of the GNU General --
18 -- Public License distributed with GNAT; see file COPYING. If not, write --
19 -- to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, --
20 -- MA 02111-1307, USA. --
22 -- As a special exception, if other files instantiate generics from this --
23 -- unit, or you link this unit with other files to produce an executable, --
24 -- this unit does not by itself cause the resulting executable to be --
25 -- covered by the GNU General Public License. This exception does not --
26 -- however invalidate any other reasons why the executable file might be --
27 -- covered by the GNU Public License. --
29 -- GNAT was originally developed by the GNAT team at New York University. --
30 -- Extensive contributions were provided by Ada Core Technologies Inc. --
32 ------------------------------------------------------------------------------
34 pragma Style_Checks (All_Checks);
35 -- Turn off alpha ordering check for subprograms, since we cannot
36 -- Put Finalize and Initialize in alpha order (see comments).
40 with Ada.Strings.Unbounded;
41 with Ada.Strings.Fixed;
42 with Ada.Strings.Maps;
43 with Ada.Unchecked_Deallocation;
45 with GNAT.Directory_Operations;
46 with GNAT.Dynamic_Tables;
49 package body GNAT.AWK is
52 use Ada.Strings.Unbounded;
60 type Mode is abstract tagged null record;
61 -- This is the main type which is declared abstract. This type must be
62 -- derived for each split style.
64 type Mode_Access is access Mode'Class;
66 procedure Current_Line (S : Mode; Session : Session_Type)
68 -- Split Session's current line using split mode.
70 ------------------------
71 -- Split on separator --
72 ------------------------
74 type Separator (Size : Positive) is new Mode with record
75 Separators : String (1 .. Size);
78 procedure Current_Line
80 Session : Session_Type);
86 type Column (Size : Positive) is new Mode with record
87 Columns : Widths_Set (1 .. Size);
90 procedure Current_Line (S : Column; Session : Session_Type);
94 procedure Free is new Unchecked_Deallocation
95 (Split.Mode'Class, Split.Mode_Access);
101 type AWK_File is access String;
103 package File_Table is
104 new Dynamic_Tables (AWK_File, Natural, 1, 5, 50);
105 -- List of filename associated with a Session.
107 procedure Free is new Unchecked_Deallocation (String, AWK_File);
113 type Field_Slice is record
117 -- This is a field slice (First .. Last) in session's current line.
119 package Field_Table is
120 new Dynamic_Tables (Field_Slice, Natural, 1, 10, 100);
121 -- List of fields for the current line.
127 -- Define all patterns style : exact string, regular expression, boolean
132 type Pattern is abstract tagged null record;
133 -- This is the main type which is declared abstract. This type must be
134 -- derived for each patterns style.
136 type Pattern_Access is access Pattern'Class;
140 Session : Session_Type)
143 -- Returns True if P match for the current session and False otherwise.
145 procedure Release (P : in out Pattern);
146 -- Release memory used by the pattern structure.
148 --------------------------
149 -- Exact string pattern --
150 --------------------------
152 type String_Pattern is new Pattern with record
153 Str : Unbounded_String;
159 Session : Session_Type)
162 --------------------------------
163 -- Regular expression pattern --
164 --------------------------------
166 type Pattern_Matcher_Access is access Regpat.Pattern_Matcher;
168 type Regexp_Pattern is new Pattern with record
169 Regx : Pattern_Matcher_Access;
175 Session : Session_Type)
178 procedure Release (P : in out Regexp_Pattern);
180 ------------------------------
181 -- Boolean function pattern --
182 ------------------------------
184 type Callback_Pattern is new Pattern with record
185 Pattern : Pattern_Callback;
189 (P : Callback_Pattern;
190 Session : Session_Type)
195 procedure Free is new Unchecked_Deallocation
196 (Patterns.Pattern'Class, Patterns.Pattern_Access);
202 -- Define all action style : simple call, call with matches
206 type Action is abstract tagged null record;
207 -- This is the main type which is declared abstract. This type must be
208 -- derived for each action style.
210 type Action_Access is access Action'Class;
214 Session : Session_Type)
216 -- Call action A as required.
222 type Simple_Action is new Action with record
223 Proc : Action_Callback;
228 Session : Session_Type);
230 -------------------------
231 -- Action with matches --
232 -------------------------
234 type Match_Action is new Action with record
235 Proc : Match_Action_Callback;
240 Session : Session_Type);
244 procedure Free is new Unchecked_Deallocation
245 (Actions.Action'Class, Actions.Action_Access);
247 --------------------------
248 -- Pattern/Action table --
249 --------------------------
251 type Pattern_Action is record
252 Pattern : Patterns.Pattern_Access; -- If Pattern is True
253 Action : Actions.Action_Access; -- Action will be called
256 package Pattern_Action_Table is
257 new Dynamic_Tables (Pattern_Action, Natural, 1, 5, 50);
263 type Session_Data is record
264 Current_File : Text_IO.File_Type;
265 Current_Line : Unbounded_String;
266 Separators : Split.Mode_Access;
267 Files : File_Table.Instance;
268 File_Index : Natural := 0;
269 Fields : Field_Table.Instance;
270 Filters : Pattern_Action_Table.Instance;
273 Matches : Regpat.Match_Array (0 .. 100);
274 -- latest matches for the regexp pattern
278 new Unchecked_Deallocation (Session_Data, Session_Data_Access);
284 procedure Initialize (Session : in out Session_Type) is
286 Session.Data := new Session_Data;
288 -- Initialize separators
290 Session.Data.Separators :=
291 new Split.Separator'(Default_Separators'Length, Default_Separators);
293 -- Initialize all tables
295 File_Table.Init (Session.Data.Files);
296 Field_Table.Init (Session.Data.Fields);
297 Pattern_Action_Table.Init (Session.Data.Filters);
300 -----------------------
301 -- Session Variables --
302 -----------------------
304 -- These must come after the body of Initialize, since they make
305 -- implicit calls to Initialize at elaboration time.
307 Def_Session : Session_Type;
308 Cur_Session : Session_Type;
314 -- Note: Finalize must come after Initialize and the definition
315 -- of the Def_Session and Cur_Session variables, since it references
318 procedure Finalize (Session : in out Session_Type) is
320 -- We release the session data only if it is not the default session.
322 if Session.Data /= Def_Session.Data then
325 -- Since we have closed the current session, set it to point
326 -- now to the default session.
328 Cur_Session.Data := Def_Session.Data;
332 ----------------------
333 -- Private Services --
334 ----------------------
336 function Always_True return Boolean;
337 -- A function that always returns True.
339 function Apply_Filters
340 (Session : Session_Type := Current_Session)
342 -- Apply any filters for which the Pattern is True for Session. It returns
343 -- True if a least one filters has been applied (i.e. associated action
344 -- callback has been called).
346 procedure Open_Next_File
347 (Session : Session_Type := Current_Session);
348 pragma Inline (Open_Next_File);
349 -- Open next file for Session closing current file if needed. It raises
350 -- End_Error if there is no more file in the table.
352 procedure Raise_With_Info
353 (E : Exceptions.Exception_Id;
355 Session : Session_Type);
356 pragma No_Return (Raise_With_Info);
357 -- Raises exception E with the message prepended with the current line
358 -- number and the filename if possible.
360 procedure Read_Line (Session : Session_Type);
361 -- Read a line for the Session and set Current_Line.
363 procedure Split_Line (Session : Session_Type);
364 -- Split session's Current_Line according to the session separators and
365 -- set the Fields table. This procedure can be called at any time.
367 ----------------------
368 -- Private Packages --
369 ----------------------
375 package body Actions is
383 Session : Session_Type)
385 pragma Unreferenced (Session);
397 Session : Session_Type)
400 A.Proc (Session.Data.Matches);
409 package body Patterns is
417 Session : Session_Type)
421 return P.Str = Field (P.Rank, Session);
430 Session : Session_Type)
433 use type Regpat.Match_Location;
437 (P.Regx.all, Field (P.Rank, Session), Session.Data.Matches);
438 return Session.Data.Matches (0) /= Regpat.No_Match;
446 (P : Callback_Pattern;
447 Session : Session_Type)
450 pragma Unreferenced (Session);
453 return P.Pattern.all;
460 procedure Release (P : in out Pattern) is
461 pragma Unreferenced (P);
471 procedure Release (P : in out Regexp_Pattern) is
472 procedure Free is new Unchecked_Deallocation
473 (Regpat.Pattern_Matcher, Pattern_Matcher_Access);
485 package body Split is
493 procedure Current_Line (S : Separator; Session : Session_Type) is
494 Line : constant String := To_String (Session.Data.Current_Line);
495 Fields : Field_Table.Instance renames Session.Data.Fields;
500 Seps : constant Maps.Character_Set := Maps.To_Set (S.Separators);
503 -- First field start here
507 -- Record the first field start position which is the first character
510 Field_Table.Increment_Last (Fields);
511 Fields.Table (Field_Table.Last (Fields)).First := Start;
514 -- Look for next separator
517 (Source => Line (Start .. Line'Last),
522 Fields.Table (Field_Table.Last (Fields)).Last := Stop - 1;
524 -- If separators are set to the default (space and tab) we skip
525 -- all spaces and tabs following current field.
527 if S.Separators = Default_Separators then
529 (Line (Stop + 1 .. Line'Last),
530 Maps.To_Set (Default_Separators),
541 -- Record in the field table the start of this new field
543 Field_Table.Increment_Last (Fields);
544 Fields.Table (Field_Table.Last (Fields)).First := Start;
548 Fields.Table (Field_Table.Last (Fields)).Last := Line'Last;
555 procedure Current_Line (S : Column; Session : Session_Type) is
556 Line : constant String := To_String (Session.Data.Current_Line);
557 Fields : Field_Table.Instance renames Session.Data.Fields;
558 Start : Positive := Line'First;
561 -- Record the first field start position which is the first character
564 for C in 1 .. S.Columns'Length loop
566 Field_Table.Increment_Last (Fields);
568 Fields.Table (Field_Table.Last (Fields)).First := Start;
570 Start := Start + S.Columns (C);
572 Fields.Table (Field_Table.Last (Fields)).Last := Start - 1;
576 -- If there is some remaining character on the line, add them in a
579 if Start - 1 < Line'Length then
581 Field_Table.Increment_Last (Fields);
583 Fields.Table (Field_Table.Last (Fields)).First := Start;
585 Fields.Table (Field_Table.Last (Fields)).Last := Line'Last;
597 Session : Session_Type := Current_Session)
599 Files : File_Table.Instance renames Session.Data.Files;
602 if OS_Lib.Is_Regular_File (Filename) then
603 File_Table.Increment_Last (Files);
604 Files.Table (File_Table.Last (Files)) := new String'(Filename);
607 (File_Error'Identity,
608 "File " & Filename & " not found.",
620 Number_Of_Files_Added : out Natural;
621 Session : Session_Type := Current_Session)
623 use Directory_Operations;
626 Filename : String (1 .. 200);
630 Number_Of_Files_Added := 0;
632 Open (Dir, Directory);
635 Read (Dir, Filename, Last);
638 Add_File (Filename (1 .. Last), Session);
639 Number_Of_Files_Added := Number_Of_Files_Added + 1;
647 (File_Error'Identity,
648 "Error scaning directory " & Directory
649 & " for files " & Filenames & '.',
657 function Always_True return Boolean is
666 function Apply_Filters
667 (Session : Session_Type := Current_Session)
670 Filters : Pattern_Action_Table.Instance renames Session.Data.Filters;
671 Results : Boolean := False;
674 -- Iterate through the filters table, if pattern match call action.
676 for F in 1 .. Pattern_Action_Table.Last (Filters) loop
677 if Patterns.Match (Filters.Table (F).Pattern.all, Session) then
679 Actions.Call (Filters.Table (F).Action.all, Session);
690 procedure Close (Session : Session_Type) is
691 Filters : Pattern_Action_Table.Instance renames Session.Data.Filters;
692 Files : File_Table.Instance renames Session.Data.Files;
695 -- Close current file if needed
697 if Text_IO.Is_Open (Session.Data.Current_File) then
698 Text_IO.Close (Session.Data.Current_File);
701 -- Release separators
703 Free (Session.Data.Separators);
705 -- Release Filters table
707 for F in 1 .. Pattern_Action_Table.Last (Filters) loop
708 Patterns.Release (Filters.Table (F).Pattern.all);
709 Free (Filters.Table (F).Pattern);
710 Free (Filters.Table (F).Action);
713 for F in 1 .. File_Table.Last (Files) loop
714 Free (Files.Table (F));
717 File_Table.Set_Last (Session.Data.Files, 0);
718 Field_Table.Set_Last (Session.Data.Fields, 0);
719 Pattern_Action_Table.Set_Last (Session.Data.Filters, 0);
721 Session.Data.NR := 0;
722 Session.Data.FNR := 0;
723 Session.Data.File_Index := 0;
724 Session.Data.Current_Line := Null_Unbounded_String;
727 ---------------------
728 -- Current_Session --
729 ---------------------
731 function Current_Session return Session_Type is
736 ---------------------
737 -- Default_Session --
738 ---------------------
740 function Default_Session return Session_Type is
749 function Discrete_Field
751 Session : Session_Type := Current_Session)
755 return Discrete'Value (Field (Rank, Session));
763 (Session : Session_Type := Current_Session)
767 return Session.Data.File_Index = File_Table.Last (Session.Data.Files)
768 and then End_Of_File (Session);
776 (Session : Session_Type := Current_Session)
780 return Text_IO.End_Of_File (Session.Data.Current_File);
789 Session : Session_Type := Current_Session)
792 Fields : Field_Table.Instance renames Session.Data.Fields;
795 if Rank > Number_Of_Fields (Session) then
797 (Field_Error'Identity,
798 "Field number" & Count'Image (Rank) & " does not exist.",
803 -- Returns the whole line, this is what $0 does under Session_Type.
805 return To_String (Session.Data.Current_Line);
808 return Slice (Session.Data.Current_Line,
809 Fields.Table (Positive (Rank)).First,
810 Fields.Table (Positive (Rank)).Last);
816 Session : Session_Type := Current_Session)
820 return Integer'Value (Field (Rank, Session));
823 when Constraint_Error =>
825 (Field_Error'Identity,
826 "Field number" & Count'Image (Rank)
827 & " cannot be converted to an integer.",
833 Session : Session_Type := Current_Session)
837 return Float'Value (Field (Rank, Session));
840 when Constraint_Error =>
842 (Field_Error'Identity,
843 "Field number" & Count'Image (Rank)
844 & " cannot be converted to a float.",
853 (Session : Session_Type := Current_Session)
856 Files : File_Table.Instance renames Session.Data.Files;
859 if Session.Data.File_Index = 0 then
862 return Files.Table (Session.Data.File_Index).all;
870 procedure For_Every_Line
871 (Separators : String := Use_Current;
872 Filename : String := Use_Current;
873 Callbacks : Callback_Mode := None;
874 Session : Session_Type := Current_Session)
879 Open (Separators, Filename, Session);
881 while not End_Of_Data (Session) loop
883 Split_Line (Session);
885 if Callbacks in Only .. Pass_Through then
888 pragma Unreferenced (Discard);
890 Discard := Apply_Filters (Session);
894 if Callbacks /= Only then
909 (Callbacks : Callback_Mode := None;
910 Session : Session_Type := Current_Session)
912 Filter_Active : Boolean;
915 if not Text_IO.Is_Open (Session.Data.Current_File) then
921 Split_Line (Session);
929 Filter_Active := Apply_Filters (Session);
930 exit when not Filter_Active;
933 Filter_Active := Apply_Filters (Session);
940 ----------------------
941 -- Number_Of_Fields --
942 ----------------------
944 function Number_Of_Fields
945 (Session : Session_Type := Current_Session)
949 return Count (Field_Table.Last (Session.Data.Fields));
950 end Number_Of_Fields;
952 --------------------------
953 -- Number_Of_File_Lines --
954 --------------------------
956 function Number_Of_File_Lines
957 (Session : Session_Type := Current_Session)
961 return Count (Session.Data.FNR);
962 end Number_Of_File_Lines;
964 ---------------------
965 -- Number_Of_Files --
966 ---------------------
968 function Number_Of_Files
969 (Session : Session_Type := Current_Session)
972 Files : File_Table.Instance renames Session.Data.Files;
975 return File_Table.Last (Files);
978 ---------------------
979 -- Number_Of_Lines --
980 ---------------------
982 function Number_Of_Lines
983 (Session : Session_Type := Current_Session)
987 return Count (Session.Data.NR);
995 (Separators : String := Use_Current;
996 Filename : String := Use_Current;
997 Session : Session_Type := Current_Session)
1000 if Text_IO.Is_Open (Session.Data.Current_File) then
1001 raise Session_Error;
1004 if Filename /= Use_Current then
1005 File_Table.Init (Session.Data.Files);
1006 Add_File (Filename, Session);
1009 if Separators /= Use_Current then
1010 Set_Field_Separators (Separators, Session);
1013 Open_Next_File (Session);
1020 --------------------
1021 -- Open_Next_File --
1022 --------------------
1024 procedure Open_Next_File
1025 (Session : Session_Type := Current_Session)
1027 Files : File_Table.Instance renames Session.Data.Files;
1030 if Text_IO.Is_Open (Session.Data.Current_File) then
1031 Text_IO.Close (Session.Data.Current_File);
1034 Session.Data.File_Index := Session.Data.File_Index + 1;
1036 -- If there are no mores file in the table, raise End_Error
1038 if Session.Data.File_Index > File_Table.Last (Files) then
1043 (File => Session.Data.Current_File,
1044 Name => Files.Table (Session.Data.File_Index).all,
1045 Mode => Text_IO.In_File);
1053 (Separators : String := Use_Current;
1054 Filename : String := Use_Current;
1055 Session : Session_Type := Current_Session)
1057 Filter_Active : Boolean;
1058 pragma Unreferenced (Filter_Active);
1061 Open (Separators, Filename, Session);
1063 while not End_Of_Data (Session) loop
1064 Get_Line (None, Session);
1065 Filter_Active := Apply_Filters (Session);
1071 ---------------------
1072 -- Raise_With_Info --
1073 ---------------------
1075 procedure Raise_With_Info
1076 (E : Exceptions.Exception_Id;
1078 Session : Session_Type)
1080 function Filename return String;
1081 -- Returns current filename and "??" if the informations is not
1084 function Line return String;
1085 -- Returns current line number without the leading space
1091 function Filename return String is
1092 File : constant String := AWK.File (Session);
1106 function Line return String is
1107 L : constant String := Natural'Image (Session.Data.FNR);
1110 return L (2 .. L'Last);
1113 -- Start of processing for Raise_With_Info
1116 Exceptions.Raise_Exception
1118 '[' & Filename & ':' & Line & "] " & Message);
1119 raise Constraint_Error; -- to please GNAT as this is a No_Return proc
1120 end Raise_With_Info;
1126 procedure Read_Line (Session : Session_Type) is
1128 function Read_Line return String;
1129 -- Read a line in the current file. This implementation is recursive
1130 -- and does not have a limitation on the line length.
1132 NR : Natural renames Session.Data.NR;
1133 FNR : Natural renames Session.Data.FNR;
1135 function Read_Line return String is
1136 Buffer : String (1 .. 1_024);
1140 Text_IO.Get_Line (Session.Data.Current_File, Buffer, Last);
1142 if Last = Buffer'Last then
1143 return Buffer & Read_Line;
1145 return Buffer (1 .. Last);
1149 -- Start of processing for Read_Line
1152 if End_Of_File (Session) then
1153 Open_Next_File (Session);
1157 Session.Data.Current_Line := To_Unbounded_String (Read_Line);
1170 Action : Action_Callback;
1171 Session : Session_Type := Current_Session)
1173 Filters : Pattern_Action_Table.Instance renames Session.Data.Filters;
1174 U_Pattern : constant Unbounded_String := To_Unbounded_String (Pattern);
1177 Pattern_Action_Table.Increment_Last (Filters);
1179 Filters.Table (Pattern_Action_Table.Last (Filters)) :=
1180 (Pattern => new Patterns.String_Pattern'(U_Pattern, Field),
1181 Action => new Actions.Simple_Action'(Proc => Action));
1186 Pattern : GNAT.Regpat.Pattern_Matcher;
1187 Action : Action_Callback;
1188 Session : Session_Type := Current_Session)
1190 Filters : Pattern_Action_Table.Instance renames Session.Data.Filters;
1192 A_Pattern : constant Patterns.Pattern_Matcher_Access :=
1193 new Regpat.Pattern_Matcher'(Pattern);
1195 Pattern_Action_Table.Increment_Last (Filters);
1197 Filters.Table (Pattern_Action_Table.Last (Filters)) :=
1198 (Pattern => new Patterns.Regexp_Pattern'(A_Pattern, Field),
1199 Action => new Actions.Simple_Action'(Proc => Action));
1204 Pattern : GNAT.Regpat.Pattern_Matcher;
1205 Action : Match_Action_Callback;
1206 Session : Session_Type := Current_Session)
1208 Filters : Pattern_Action_Table.Instance renames Session.Data.Filters;
1210 A_Pattern : constant Patterns.Pattern_Matcher_Access :=
1211 new Regpat.Pattern_Matcher'(Pattern);
1213 Pattern_Action_Table.Increment_Last (Filters);
1215 Filters.Table (Pattern_Action_Table.Last (Filters)) :=
1216 (Pattern => new Patterns.Regexp_Pattern'(A_Pattern, Field),
1217 Action => new Actions.Match_Action'(Proc => Action));
1221 (Pattern : Pattern_Callback;
1222 Action : Action_Callback;
1223 Session : Session_Type := Current_Session)
1225 Filters : Pattern_Action_Table.Instance renames Session.Data.Filters;
1228 Pattern_Action_Table.Increment_Last (Filters);
1230 Filters.Table (Pattern_Action_Table.Last (Filters)) :=
1231 (Pattern => new Patterns.Callback_Pattern'(Pattern => Pattern),
1232 Action => new Actions.Simple_Action'(Proc => Action));
1236 (Action : Action_Callback;
1237 Session : Session_Type := Current_Session)
1240 Register (Always_True'Access, Action, Session);
1247 procedure Set_Current (Session : Session_Type) is
1249 Cur_Session.Data := Session.Data;
1252 --------------------------
1253 -- Set_Field_Separators --
1254 --------------------------
1256 procedure Set_Field_Separators
1257 (Separators : String := Default_Separators;
1258 Session : Session_Type := Current_Session)
1261 Free (Session.Data.Separators);
1263 Session.Data.Separators :=
1264 new Split.Separator'(Separators'Length, Separators);
1266 -- If there is a current line read, split it according to the new
1269 if Session.Data.Current_Line /= Null_Unbounded_String then
1270 Split_Line (Session);
1272 end Set_Field_Separators;
1274 ----------------------
1275 -- Set_Field_Widths --
1276 ----------------------
1278 procedure Set_Field_Widths
1279 (Field_Widths : Widths_Set;
1280 Session : Session_Type := Current_Session) is
1283 Free (Session.Data.Separators);
1285 Session.Data.Separators :=
1286 new Split.Column'(Field_Widths'Length, Field_Widths);
1288 -- If there is a current line read, split it according to
1289 -- the new separators.
1291 if Session.Data.Current_Line /= Null_Unbounded_String then
1292 Split_Line (Session);
1294 end Set_Field_Widths;
1300 procedure Split_Line (Session : Session_Type) is
1301 Fields : Field_Table.Instance renames Session.Data.Fields;
1304 Field_Table.Init (Fields);
1306 Split.Current_Line (Session.Data.Separators.all, Session);
1310 -- We have declared two sessions but both should share the same data.
1311 -- The current session must point to the default session as its initial
1312 -- value. So first we release the session data then we set current
1313 -- session data to point to default session data.
1315 Free (Cur_Session.Data);
1316 Cur_Session.Data := Def_Session.Data;