------------------------------------------------------------------------------
-- XML/Ada - An XML suite for Ada95 --
-- --
-- Copyright (C) 2010-2012, AdaCore --
-- --
-- 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. --
-- --
-- --
-- --
-- --
-- --
-- You should have received a copy of the GNU General Public License and --
-- a copy of the GCC Runtime Library Exception along with this program; --
-- see the files COPYING3 and COPYING.RUNTIME respectively. If not, see --
-- . --
-- --
------------------------------------------------------------------------------
pragma ada_05;
with Ada.Exceptions; use Ada.Exceptions;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with GNAT.IO; use GNAT.IO;
with Ada.Unchecked_Deallocation;
package body Sax.State_Machines is
use Transition_Tables, State_Tables;
Debug : constant Boolean := False;
-- Whether to print on stdout the actions performed on the machine.
-- Copy-pasting those actions would allow recreating the exact same
-- machine.
----------------
-- Initialize --
----------------
procedure Initialize
(Self : in out nfa;
States_Are_Statefull : Boolean := False)
is
begin
Self.States_Are_Statefull := States_Are_Statefull;
Init (Self.States);
Init (Self.Transitions);
-- Create start state
Append
(Self.States,
state_data'
(Nested => No_State,
First_Transition => No_Transition,
Data => Default_Data));
end Initialize;
----------
-- Free --
----------
procedure Free (Self : in out nfa) is
begin
Free (Self.States);
Free (Self.Transitions);
end Free;
----------
-- Free --
----------
procedure Free (Automaton : in out nfa_access) is
procedure Unchecked_Free is new Ada.Unchecked_Deallocation
(nfa'class,
nfa_access);
begin
if Automaton /= null then
Free (Automaton.all);
Unchecked_Free (Automaton);
end if;
end Free;
---------------
-- Add_State --
---------------
function Add_State
(Self : access nfa;
Data : state_user_data := Default_Data) return state
is
begin
Append
(Self.States,
state_data'
(Nested => No_State,
Data => Data,
First_Transition => No_Transition));
if Debug then
Put_Line (Last (Self.States)'img & " := NFA.Add_State");
end if;
return Last (Self.States);
end Add_State;
--------------
-- Set_Data --
--------------
procedure Set_Data (Self : access nfa; S : state; Data : state_user_data) is
begin
Self.States.Table (S).Data := Data;
end Set_Data;
--------------
-- Get_Data --
--------------
function Get_Data (Self : access nfa; S : state) return state_data_access is
begin
return Self.States.Table (S).Data'access;
end Get_Data;
--------------------
-- Add_Transition --
--------------------
procedure Add_Transition
(Self : access nfa;
From : state;
To : state;
On_Symbol : transition_symbol)
is
begin
if From = Final_State then
Raise_Exception
(Program_Error'identity,
"Can't add transitions from final_state");
end if;
pragma assert (From /= No_State);
pragma assert (From <= Last (Self.States));
pragma assert (To /= No_State);
pragma assert (To = Final_State or else To <= Last (Self.States));
Append
(Self.Transitions,
transition'
(Kind => transition_on_symbol,
To_State => To,
Next_For_State => Self.States.Table (From).First_Transition,
Sym => On_Symbol));
Self.States.Table (From).First_Transition := Last (Self.Transitions);
end Add_Transition;
--------------------------
-- Add_Empty_Transition --
--------------------------
procedure Add_Empty_Transition
(Self : access nfa;
From : state;
To : state)
is
begin
if From = Final_State then
Raise_Exception
(Program_Error'identity,
"Can't add transitions from final_state");
end if;
Append
(Self.Transitions,
transition'
(Kind => transition_on_empty,
To_State => To,
Next_For_State => Self.States.Table (From).First_Transition));
Self.States.Table (From).First_Transition := Last (Self.Transitions);
end Add_Empty_Transition;
------------
-- Repeat --
------------
function Repeat
(Self : access nfa;
From, To : state;
Min_Occurs : Natural := 1;
Max_Occurs : Natural := 1) return state
is
type state_array is
array (State_Tables.First .. Last (Self.States)) of state;
procedure Clone_And_Count_Nodes
(Cloned : in out state_array;
Cloned_Count : out Natural);
-- Clone all nodes internal to the subautomaton.
-- The algorithm is as follows: starting from [From], we follow all
-- transitions until we reach [To]. We do not follow any transition
-- from [To]. In the end, the internal nodes are the ones with an
-- an entry in [Cloned].
function Complete_All_Clones
(Cloned : state_array;
Cloned_Count : Natural;
Max : Natural) return state;
-- [Clone_And_Count_Nodes] was used to do one clone of the internal
-- nodes (and count them). This procedure does the remaining number of
-- clones for [Max_Occurs] repeats.
-- On exit, [From} has been cloned to [Cloned (From)],
-- [Cloned (From)+Cloned_Count], [Cloned (From)+Cloned_Count*2],...
-- Returns the final node in the cloned automaton.
procedure Clone_Transitions
(Cloned : state_array;
Cloned_Count : Natural;
New_To : state;
Max : Natural);
-- Clone all transitions for all cloned nodes. Only the transitions
-- leading to internal nodes are cloned
-- |From|--|To| -- .. |Cloned (From)+Offset|...
-- becomes
-- |From|--|To| -- .. |Cloned (From)+Offset|...||
-- [New_To] is the final node in the cloned automaton
function Add_Stateless (New_To : state) return state;
-- Add a new stateless (ie with no user data) state at the end of the
-- subautomaton.
-- ...--|New_To|--
-- becomes
-- ...--|N|--|New_To|
-- where N is returned, has the user data of New_To, and New_To does not
-- have any user data.
---------------------------
-- Clone_And_Count_Nodes --
---------------------------
procedure Clone_And_Count_Nodes
(Cloned : in out state_array;
Cloned_Count : out Natural)
is
procedure Internal (S : state);
procedure Do_Transitions (First : transition_id);
procedure Do_Transitions (First : transition_id) is
T : transition_id := First;
begin
while T /= No_Transition loop
declare
Tr : transition renames Self.Transitions.Table (T);
begin
if Tr.To_State = Final_State then
null;
else
Internal (Tr.To_State);
end if;
T := Tr.Next_For_State;
end;
end loop;
end Do_Transitions;
procedure Internal (S : state) is
begin
if S = Final_State then
return;
elsif Cloned (S) /= No_State then
return;
elsif S = From then
Cloned (From) := To; -- Do not duplicate data or nested
if Debug then
Put_Line
("From:Clone(" &
From'img &
") = " &
Cloned (From)'img &
" don't copy data");
end if;
else
Cloned_Count := Cloned_Count + 1;
Cloned (S) := Add_State (Self, Self.States.Table (S).Data);
if Debug then
Put_Line ("Clone(" & S'img & ") = " & Cloned (S)'img);
end if;
Self.States.Table (Cloned (S)).Nested :=
Self.States.Table (S).Nested;
if S = To then
return; -- No need to examine transitions from [To]
end if;
end if;
Do_Transitions (Self.States.Table (S).First_Transition);
end Internal;
begin
Cloned_Count := 0;
Internal (From);
end Clone_And_Count_Nodes;
-------------------------
-- Complete_All_Clones --
-------------------------
function Complete_All_Clones
(Cloned : state_array;
Cloned_Count : Natural;
Max : Natural) return state
is
Tmp : state;
begin
if Max <= 2 then
if Min_Occurs = Max_Occurs or else Max_Occurs = Natural'last then
return Cloned (To);
else
return Add_Stateless (Cloned (To));
end if;
end if;
-- Reserve immediately the space for all the other repetitions, to
-- limit calls to malloc()
Set_Last
(Self.States,
Last (Self.States) + state (Cloned_Count * (Max - 2)));
for C in Cloned'range loop
if Cloned (C) /= No_State then
for R in 1 .. Max - 2 loop
Tmp := Cloned (C) + state (R * Cloned_Count);
if C /= From then
Self.States.Table (Tmp) :=
state_data'
(Nested => Self.States.Table (C).Nested,
Data => Self.States.Table (C).Data,
First_Transition => No_Transition);
if Debug then
Put_Line ("Extra clone(" & C'img & ") at " & Tmp'img);
end if;
else
if Debug then
Tmp := Cloned (To) + state ((R - 1) * Cloned_Count);
Put_Line
("Extra clone(" &
C'img &
") at " &
Tmp'img &
" (don't copy data)");
end if;
end if;
end loop;
end if;
end loop;
-- If needed, add an extra node at the end
if Min_Occurs = Max_Occurs or else Max_Occurs = Natural'last then
return Cloned (To) + state ((Max - 2) * Cloned_Count);
else
return Add_Stateless
(Cloned (To) + state ((Max - 2) * Cloned_Count));
end if;
end Complete_All_Clones;
-----------------------
-- Clone_Transitions --
-----------------------
procedure Clone_Transitions
(Cloned : state_array;
Cloned_Count : Natural;
New_To : state;
Max : Natural)
is
procedure Do_Transitions (S : state; First : transition_id);
procedure Do_Transitions (S : state; First : transition_id) is
T : transition_id;
Tmp : state;
Offs1, Offs2 : state;
begin
T := First;
while T /= No_Transition loop
declare
-- Not a "renames", because Self.Transitions might be
-- resized within this loop
Tr : constant transition := Self.Transitions.Table (T);
begin
if Tr.To_State = Final_State then
Tmp := Final_State;
if Cloned (S) = Cloned (To) then
Tmp := No_State; -- No copy, will be done later
end if;
elsif Tr.To_State > Cloned'last then
Tmp := No_State; -- Link to the outside
else
Tmp := Cloned (Tr.To_State);
end if;
if Tmp /= No_State then
for R in 0 .. Max - 2 loop
if S = From then
-- Since the first clone of [From] is already in
-- the NFA, the computation of the following clones
-- is more complex: the user might have inserted
-- more states in the NFA after inserting [To], so
-- the clones are not in the same order
if R = 0 then
Offs1 := Cloned (From);
else
Offs1 :=
Cloned (To) + state ((R - 1) * Cloned_Count);
end if;
else
Offs1 := Cloned (S) + state (R * Cloned_Count);
end if;
Offs2 := Tmp + state (R * Cloned_Count);
case Tr.Kind is
when transition_on_exit_empty =>
On_Empty_Nested_Exit (Self, Offs1, Offs2);
when transition_on_empty =>
if Debug then
Put_Line
("Empty: from" &
Offs1'img &
" to" &
Offs2'img &
" R=" &
R'img);
end if;
Add_Empty_Transition (Self, Offs1, Offs2);
when transition_on_exit_symbol =>
On_Nested_Exit (Self, Offs1, Offs2, Tr.Sym);
when transition_on_symbol =>
if Debug then
Put_Line
("Trans: from" &
Offs1'img &
" to" &
Offs2'img &
" on " &
Image (Tr.Sym));
end if;
Add_Transition (Self, Offs1, Offs2, Tr.Sym);
end case;
end loop;
end if;
T := Tr.Next_For_State;
end;
end loop;
end Do_Transitions;
Prev : transition_id;
T : transition_id;
begin
for S in reverse Cloned'range loop
if Cloned (S) /= No_State then
Do_Transitions (S, Self.States.Table (S).First_Transition);
end if;
end loop;
-- Last pass to move external transition from [To] to [New_To],
-- ie from the end of the sub-automaton
Prev := No_Transition;
T := Self.States.Table (To).First_Transition;
while T /= No_Transition loop
declare
Tr : transition renames Self.Transitions.Table (T);
Next : constant transition_id := Tr.Next_For_State;
begin
if Tr.To_State = Final_State
or else
(Tr.To_State /= To
and then Tr.To_State <= Cloned'last
and then Cloned (Tr.To_State) = No_State)
then
if Prev = No_Transition then
Self.States.Table (To).First_Transition :=
Tr.Next_For_State;
else
Self.Transitions.Table (Prev).Next_For_State :=
Tr.Next_For_State;
end if;
Tr.Next_For_State :=
Self.States.Table (New_To).First_Transition;
Self.States.Table (New_To).First_Transition := T;
else
Prev := T;
end if;
T := Next;
end;
end loop;
end Clone_Transitions;
-------------------
-- Add_Stateless --
-------------------
function Add_Stateless (New_To : state) return state is
N : state := New_To;
begin
if Self.States_Are_Statefull then
-- Add extra stateless node
N := Add_State (Self);
Add_Empty_Transition (Self, New_To, N);
end if;
return N;
end Add_Stateless;
N : state;
begin
if Debug then
Put_Line
("Repeat" &
From'img &
" to" &
To'img &
" Min,Max=" &
Min_Occurs'img &
Max_Occurs'img);
end if;
-- First the simple and usual cases (that cover the usual "*", "+" and
-- "?" operators in regular expressions. It is faster to first handle
-- those, since we don't need any additional new state for those.
if Min_Occurs = 1 and then Max_Occurs = 1 then
return To; -- Nothing to do
elsif Min_Occurs > Max_Occurs then
return To; -- As documented, nothing is done
elsif Max_Occurs = 0 then
Self.States.Table (From).First_Transition := No_Transition;
Add_Empty_Transition (Self, From, To);
return To;
elsif Min_Occurs = 0 and then Max_Occurs = 1 then
N := Add_Stateless (To);
Add_Empty_Transition (Self, From, N);
return N;
elsif Min_Occurs = 1 and then Max_Occurs = Natural'last then
Add_Empty_Transition (Self, From => To, To => From);
return To;
elsif Min_Occurs = 0 and then Max_Occurs = Natural'last then
N := Add_Stateless (To);
Add_Empty_Transition (Self, From, N);
Add_Empty_Transition (Self, N, From);
return N;
end if;
-- We now deal with the more complex cases (always Max_Occurs > 1)
declare
Cloned : state_array := (others => No_State);
Cloned_Count : Natural := 0;
-- Number of nodes in the subautomaton to clone.
New_To : state;
begin
Clone_And_Count_Nodes (Cloned, Cloned_Count);
if Max_Occurs = Natural'last then
New_To := Complete_All_Clones (Cloned, Cloned_Count, Min_Occurs);
Clone_Transitions (Cloned, Cloned_Count, New_To, Min_Occurs);
if Min_Occurs > 2 then
N := Cloned (To) + state ((Min_Occurs - 2) * Cloned_Count);
elsif Min_Occurs = 2 then
N := Cloned (From);
else
raise Program_Error; -- cases 0..* and 1..* already handled
end if;
Add_Empty_Transition (Self, New_To, N);
if Debug then
Put_Line ("Empty trans from" & New_To'img & " to" & N'img);
end if;
return New_To;
else
New_To := Complete_All_Clones (Cloned, Cloned_Count, Max_Occurs);
if Min_Occurs = 0 then
Add_Empty_Transition (Self, From, New_To);
end if;
for R in Integer'max (0, Min_Occurs - 1) .. Max_Occurs - 2 loop
if R = 0 then
N := Cloned (From);
else
N := Cloned (To) + state ((R - 1) * Cloned_Count);
end if;
if Debug then
Put_Line ("Empty trans from" & N'img & " to" & New_To'img);
end if;
Add_Empty_Transition (Self, N, New_To);
end loop;
Clone_Transitions (Cloned, Cloned_Count, New_To, Max_Occurs);
return New_To;
end if;
end;
end Repeat;
-------------------
-- Create_Nested --
-------------------
function Create_Nested
(Self : access nfa'class;
From : state) return nested_nfa
is
pragma unreferenced (Self);
begin
if Debug then
Put_Line ("E := Create_Nested (" & From'img & ")");
end if;
return (Default_Start => From);
end Create_Nested;
--------------------
-- On_Nested_Exit --
--------------------
procedure On_Nested_Exit
(Self : access nfa;
From : state;
To : state;
On_Symbol : transition_symbol)
is
begin
Append
(Self.Transitions,
transition'
(Kind => transition_on_exit_symbol,
To_State => To,
Next_For_State => Self.States.Table (From).First_Transition,
Sym => On_Symbol));
Self.States.Table (From).First_Transition := Last (Self.Transitions);
end On_Nested_Exit;
--------------------------
-- On_Empty_Nested_Exit --
--------------------------
procedure On_Empty_Nested_Exit
(Self : access nfa;
From : state;
To : state)
is
begin
Append
(Self.Transitions,
transition'
(Kind => transition_on_exit_empty,
To_State => To,
Next_For_State => Self.States.Table (From).First_Transition));
Self.States.Table (From).First_Transition := Last (Self.Transitions);
end On_Empty_Nested_Exit;
----------------
-- Set_Nested --
----------------
procedure Set_Nested (Self : access nfa; S : state; Nested : nested_nfa) is
begin
if Debug then
Put_Line
("Set_Nested (" & S'img & "," & Nested.Default_Start'img & ")");
end if;
Self.States.Table (S).Nested := Nested.Default_Start;
end Set_Nested;
----------------
-- Get_Nested --
----------------
function Get_Nested (Self : access nfa; S : state) return nested_nfa is
begin
return nested_nfa'(Default_Start => Self.States.Table (S).Nested);
end Get_Nested;
-------------------
-- Default_Image --
-------------------
function Default_Image
(Self : access nfa'class;
S : state;
Data : state_user_data) return String
is
pragma unreferenced (Self, Data);
Str : constant String := state'image (S);
begin
return "S" & Str (Str'first + 1 .. Str'last);
end Default_Image;
---------------------
-- Pretty_Printers --
---------------------
package body Pretty_Printers is
type state_array is array (state range <>) of Boolean;
function Node_Name
(Self : access nfa'class;
S : state;
Nested_In : state := No_State) return String;
procedure Append_Node
(Self : access nfa'class;
S : state;
R : in out Unbounded_String;
Nested_In : state := No_State);
procedure Newline (Result : in out Unbounded_String; Mode : dump_mode);
-- Append a newline to [Result] if needed
procedure Dump_Nested
(Self : access nfa'class;
Result : in out Unbounded_String;
Dumped : in out state_array;
S : state;
Mode : dump_mode;
Since : nfa_snapshot := No_NFA_Snapshot);
-- Dump a cluster that represents a nested NFA.
-- Such nested NFAs are represented only once, even though they can
-- in fact be nested within several nodes. That would make huge
-- graphs otherwise.
procedure Dump_Dot
(Self : access nfa'class;
Result : in out Unbounded_String;
Dumped : in out state_array;
Start_At : state;
Nested_In : state;
Prefix : String;
Mode : dump_mode;
Since : nfa_snapshot := No_NFA_Snapshot);
procedure Dump_Dot_Transitions
(Self : access nfa'class;
Result : in out Unbounded_String;
Dumped : in out state_array;
S : state;
First : transition_id;
Prefix : String;
Nested_In : state := No_State;
Mode : dump_mode;
Since : nfa_snapshot := No_NFA_Snapshot);
--------------------------
-- Dump_Dot_Transitions --
--------------------------
procedure Dump_Dot_Transitions
(Self : access nfa'class;
Result : in out Unbounded_String;
Dumped : in out state_array;
S : state;
First : transition_id;
Prefix : String;
Nested_In : state := No_State;
Mode : dump_mode;
Since : nfa_snapshot := No_NFA_Snapshot)
is
T : transition_id := First;
begin
while T /= No_Transition loop
declare
Tr : transition renames Self.Transitions.Table (T);
begin
if Tr.To_State > Since.States then
Append
(Result,
Prefix &
Node_Name (Self, S, Nested_In) &
"->" &
Node_Name (Self, Tr.To_State, Nested_In) &
"[");
case Tr.Kind is
when transition_on_symbol =>
Append (Result, "label=""" & Image (Tr.Sym) & """");
when transition_on_exit_symbol =>
Append
(Result,
"label=""on_exit:" &
Image (Tr.Sym) &
""" style=dotted");
when transition_on_empty =>
Append (Result, "style=dashed");
null;
when transition_on_exit_empty =>
Append (Result, "label=on_exit style=dotted");
end case;
Append (Result, "];");
Newline (Result, Mode);
if Tr.To_State /= Final_State then
Dump_Dot
(Self,
Result => Result,
Dumped => Dumped,
Start_At => Tr.To_State,
Nested_In => Nested_In,
Prefix => Prefix,
Mode => Mode,
Since => Since);
end if;
end if;
T := Tr.Next_For_State;
end;
end loop;
end Dump_Dot_Transitions;
--------------
-- Dump_Dot --
--------------
procedure Dump_Dot
(Self : access nfa'class;
Result : in out Unbounded_String;
Dumped : in out state_array;
Start_At : state;
Nested_In : state;
Prefix : String;
Mode : dump_mode;
Since : nfa_snapshot := No_NFA_Snapshot)
is
begin
if Start_At = Final_State or else Dumped (Start_At) then
return;
end if;
Dumped (Start_At) := True;
Dump_Dot_Transitions
(Self,
Result,
S => Start_At,
Dumped => Dumped,
First => Self.States.Table (Start_At).First_Transition,
Prefix => Prefix,
Nested_In => Nested_In,
Mode => Mode,
Since => Since);
end Dump_Dot;
-----------------
-- Dump_Nested --
-----------------
procedure Dump_Nested
(Self : access nfa'class;
Result : in out Unbounded_String;
Dumped : in out state_array;
S : state;
Mode : dump_mode;
Since : nfa_snapshot := No_NFA_Snapshot)
is
Name : constant String := Node_Name (Self, S);
Label : constant String := Node_Label (Self, S);
begin
if S > Since.States then
Append (Result, "subgraph cluster" & Name & "{");
Newline (Result, Mode);
Append (Result, " label=""" & Label & """;");
Newline (Result, Mode);
Append_Node (Self, S, Result, S);
Append_Node (Self, Final_State, Result, S);
Dump_Dot
(Self,
Result,
Dumped => Dumped,
Start_At => S,
Nested_In => S,
Prefix => " ",
Mode => Mode,
Since => Since);
Append (Result, "};");
Newline (Result, Mode);
end if;
end Dump_Nested;
-------------
-- Newline --
-------------
procedure Newline (Result : in out Unbounded_String; Mode : dump_mode) is
begin
case Mode is
when dump_compact | dump_dot_compact =>
null;
when others =>
Append (Result, ASCII.LF);
end case;
end Newline;
---------------
-- Node_Name --
---------------
function Node_Name
(Self : access nfa'class;
S : state;
Nested_In : state := No_State) return String
is
begin
if S = Start_State then
return "Start";
elsif S = Final_State then
if Nested_In /= No_State then
return "Sf" & Node_Name (Self, Nested_In);
else
return "Sf";
end if;
else
return Default_Image (Self, S, Default_Data);
end if;
end Node_Name;
----------------
-- Node_Label --
----------------
function Node_Label (Self : access nfa'class; S : state) return String is
begin
if S = Start_State then
return "Start";
elsif S = Final_State then
return "Final";
else
declare
Img : constant String :=
State_Image (Self, S, Self.States.Table (S).Data);
begin
if Img = "" then
if Self.States.Table (S).Nested /= No_State then
return Node_Name (Self, S) &
":" &
Node_Label (Self, Self.States.Table (S).Nested);
else
return Node_Name (Self, S);
end if;
else
if Self.States.Table (S).Nested /= No_State then
return Node_Name (Self, S) &
"_" &
Img &
":" &
Node_Label (Self, Self.States.Table (S).Nested);
else
return Node_Name (Self, S) & "_" & Img;
end if;
end if;
end;
end if;
end Node_Label;
-----------------
-- Append_Node --
-----------------
procedure Append_Node
(Self : access nfa'class;
S : state;
R : in out Unbounded_String;
Nested_In : state := No_State)
is
Name : constant String := Node_Name (Self, S, Nested_In);
Label : constant String := Node_Label (Self, S);
begin
Append (R, Name);
if Label /= Name then
if S = Start_State
or else S = Final_State
or else S = Nested_In
then
if Label /= "" then
Append (R, "[label=""" & Label & """ shape=doublecircle];");
else
Append (R, "[shape=doublecircle];");
end if;
elsif Label /= "" then
Append (R, "[label=""" & Label & """];");
else
Append (R, ";");
end if;
else
if S = Start_State or else S = Nested_In then
Append (R, "[shape=doublecircle];");
else
Append (R, ";");
end if;
end if;
end Append_Node;
----------
-- Dump --
----------
function Dump
(Self : access nfa'class;
Nested : nested_nfa;
Mode : dump_mode := dump_compact) return String
is
Dumped : state_array (State_Tables.First .. Last (Self.States)) :=
(others => False);
Result : Unbounded_String;
procedure Internal (S : state);
procedure Internal (S : state) is
T : transition_id;
begin
if Dumped (S) then
return;
end if;
Dumped (S) := True;
Append (Result, " " & Node_Label (Self, S));
T := Self.States.Table (S).First_Transition;
while T /= No_Transition loop
declare
Tr : transition renames Self.Transitions.Table (T);
begin
case Tr.Kind is
when transition_on_empty =>
Append (Result, "(");
when transition_on_exit_empty =>
Append (Result, "(Exit");
when transition_on_symbol =>
Append (Result, "(" & Image (Tr.Sym));
when transition_on_exit_symbol =>
Append (Result, "(Exit_" & Image (Tr.Sym));
end case;
Append (Result, "," & Node_Name (Self, Tr.To_State) & ")");
T := Tr.Next_For_State;
end;
end loop;
T := Self.States.Table (S).First_Transition;
while T /= No_Transition loop
declare
Tr : transition renames Self.Transitions.Table (T);
begin
if Tr.To_State /= Final_State then
Internal (Tr.To_State);
end if;
T := Tr.Next_For_State;
end;
end loop;
if Mode = dump_multiline then
Append (Result, ASCII.LF);
end if;
if Self.States.Table (S).Nested /= No_State
and then not Dumped (Self.States.Table (S).Nested)
then
Internal (Self.States.Table (S).Nested);
end if;
end Internal;
begin
case Mode is
when dump_compact | dump_multiline =>
Internal (Nested.Default_Start);
when dump_dot | dump_dot_compact =>
Append (Result, "Use dot -O -Tpdf file.dot" & ASCII.LF);
Append (Result, "digraph finite_state_machine{");
Newline (Result, Mode);
Append (Result, "compound=true;");
Newline (Result, Mode);
Append (Result, "rankdir=LR;");
Newline (Result, Mode);
Dump_Nested (Self, Result, Dumped, Nested.Default_Start, Mode);
Append (Result, "}" & ASCII.LF);
end case;
return To_String (Result);
end Dump;
----------
-- Dump --
----------
function Dump
(Self : access nfa'class;
Mode : dump_mode := dump_compact;
Show_Details : Boolean := True;
Show_Isolated_Nodes : Boolean := True;
Since : nfa_snapshot := No_NFA_Snapshot) return String
is
Dumped : state_array (State_Tables.First .. Last (Self.States)) :=
(others => False);
Result : Unbounded_String;
begin
Append (Result, "Total states:" & Last (Self.States)'img & ASCII.LF);
Append
(Result,
"Total transitions:" & Last (Self.Transitions)'img & ASCII.LF);
if Since /= No_NFA_Snapshot then
Append (Result, "Dump since " & Since.States'img & ASCII.LF);
end if;
if not Show_Details then
return To_String (Result);
end if;
case Mode is
when dump_multiline | dump_compact =>
return Dump
(Self => Self,
Nested => (Default_Start => Start_State),
Mode => Mode);
when dump_dot | dump_dot_compact =>
Append (Result, "Use dot -O -Tpdf file.dot" & ASCII.LF);
Append (Result, "digraph finite_state_machine{");
Newline (Result, Mode);
Append (Result, "compound=true;");
Newline (Result, Mode);
Append (Result, "rankdir=LR;");
Newline (Result, Mode);
Append_Node (Self, Start_State, Result);
Append_Node (Self, Final_State, Result);
-- First, create all the clusters for the nested NFA. That helps
-- remove their states from the global lists, so that we can then
-- only dump the toplevel states
for S in Since.States + 1 .. Last (Self.States) loop
if Self.States.Table (S).Nested /= No_State then
Dump_Nested
(Self,
Result,
Dumped,
Self.States.Table (S).Nested,
Mode,
Since);
end if;
end loop;
-- Now dump the labels for all nodes. These do not need to go
-- into the clusters, as long as the nodes where first encountered
-- there
for S in Since.States + 1 .. Last (Self.States) loop
if Show_Isolated_Nodes
or else Self.States.Table (S).Nested /= No_State
or else
Self.States.Table (S).First_Transition /=
No_Transition
then
Append_Node (Self, S, Result);
end if;
end loop;
-- Now dump the toplevel states (that is the ones that haven't
-- been dumped yet)
Dump_Dot
(Self => Self,
Result => Result,
Dumped => Dumped,
Start_At => Start_State,
Nested_In => No_State,
Prefix => "",
Mode => Mode,
Since => Since);
for S in Since.States + 1 .. Last (Self.States) loop
if S /= Start_State
and then
(Show_Isolated_Nodes
or else Self.States.Table (S).Nested /= No_State
or else
Self.States.Table (S).First_Transition /=
No_Transition)
then
Dump_Dot
(Self => Self,
Result => Result,
Dumped => Dumped,
Start_At => S,
Nested_In => No_State,
Prefix => "",
Mode => Mode,
Since => Since);
end if;
end loop;
Append (Result, "}" & ASCII.LF);
end case;
return To_String (Result);
end Dump;
end Pretty_Printers;
---------------------
-- Get_Start_State --
---------------------
function Get_Start_State (Self : nested_nfa) return state is
begin
return Self.Default_Start;
end Get_Start_State;
------------------
-- Get_Snapshot --
------------------
function Get_Snapshot (Self : access nfa) return nfa_snapshot is
begin
return
(States => Last (Self.States),
Transitions => Last (Self.Transitions),
Start_State_Transition =>
Self.States.Table (Start_State).First_Transition);
end Get_Snapshot;
-----------------------
-- Reset_To_Snapshot --
-----------------------
procedure Reset_To_Snapshot (Self : access nfa; Snapshot : nfa_snapshot) is
begin
if Snapshot /= No_NFA_Snapshot then
Set_Last (Self.States, Snapshot.States);
Set_Last (Self.Transitions, Snapshot.Transitions);
Self.States.Table (Start_State).First_Transition :=
Snapshot.Start_State_Transition;
end if;
end Reset_To_Snapshot;
------------
-- Exists --
------------
function Exists (Snapshot : nfa_snapshot; S : state) return Boolean is
begin
return S <= Snapshot.States;
end Exists;
--------------
-- Matchers --
--------------
package body Matchers is
use Matcher_State_Arrays;
procedure Mark_Active
(Self : in out nfa_matcher'class;
List_Start : in out matcher_state_index;
From : state;
First_Nested : matcher_state_index := No_Matcher_State;
Active_Data : active_state_data := No_Active_Data);
-- Mark [From] as active next time, as well as all states reachable
-- through an empty transition. THe nested state machine for the new
-- state is set to [First_Nested].
procedure Mark_Active_No_Check
(Self : in out nfa_matcher'class;
List_Start : in out matcher_state_index;
From : state;
First_Nested : matcher_state_index := No_Matcher_State;
Active_Data : active_state_data := No_Active_Data);
-- Same as [Mark_Active], but do not check whether [From] is already
-- active.
function Nested_In_Final
(Self : nfa_matcher'class;
S : matcher_state_index) return Boolean;
-- Return true if the nested NFA for [S] is in a final state, or if [S]
-- has no nested automaton.
-- [List_Start] is the first state in the level that contains [S]
function Is_Active
(Self : nfa_matcher'class;
List_Start : matcher_state_index;
S : state) return Boolean;
pragma inline (Is_Active);
-- Whether [S] is marked as active in the given list
procedure Internal_Next
(Self : nfa_matcher'class;
Iter : in out active_state_iterator;
Move_First : Boolean);
-- Internal implementation of the matcher iterator
--------------------
-- Is_Initialized --
--------------------
function Is_Initialized (Self : nfa_matcher) return Boolean is
begin
return Self.NFA /= null;
end Is_Initialized;
----------
-- Free --
----------
procedure Free (Self : in out nfa_matcher) is
begin
Free (Self.Active);
Self.First_Active := No_Matcher_State;
Self.NFA := null;
end Free;
---------------
-- Is_Active --
---------------
function Is_Active
(Self : nfa_matcher'class;
List_Start : matcher_state_index;
S : state) return Boolean
is
T : matcher_state_index := List_Start;
begin
while T /= No_Matcher_State loop
if Self.Active.Table (T).S = S then
return True;
end if;
T := Self.Active.Table (T).Next;
end loop;
return False;
end Is_Active;
--------------------------
-- Mark_Active_No_Check --
--------------------------
procedure Mark_Active_No_Check
(Self : in out nfa_matcher'class;
List_Start : in out matcher_state_index;
From : state;
First_Nested : matcher_state_index := No_Matcher_State;
Active_Data : active_state_data := No_Active_Data)
is
T : transition_id;
From_Index : matcher_state_index; -- Where we added [From]
Tmp2 : state;
Tmp : matcher_state_index;
begin
if Debug then
Put_Line ("Mark_Active " & From'img);
end if;
-- Always leave the Final_State first in the list
if List_Start /= No_Matcher_State
and then Self.Active.Table (List_Start).S = Final_State
then
Self.Active.Table (List_Start).S := From;
Self.Active.Table (List_Start).Nested := First_Nested;
From_Index := List_Start;
Append
(Self.Active,
matcher_state'
(S => Final_State,
Data_Is_Overridden => False,
Overridden_Data => <>,
Active_Data => Active_Data,
Next => List_Start,
Nested => No_Matcher_State));
else
Append
(Self.Active,
matcher_state'
(S => From,
Data_Is_Overridden => False,
Overridden_Data => <>,
Active_Data => Active_Data,
Next => List_Start,
Nested => First_Nested));
From_Index := Last (Self.Active);
end if;
List_Start := Last (Self.Active);
-- Mark (recursively) all states reachable from an empty transition
-- as active too.
if From /= Final_State then
T := Self.NFA.States.Table (From).First_Transition;
while T /= No_Transition loop
declare
Tr : transition renames Self.NFA.Transitions.Table (T);
begin
if Tr.Kind = transition_on_empty then
Mark_Active (Self, List_Start, Tr.To_State);
end if;
T := Tr.Next_For_State;
end;
end loop;
-- If we are entering any state with a nested NFA, we should
-- activate that NFA next turn (unless the nested NFA is already
-- active)
if Self.NFA.States.Table (From).Nested /= No_State
and then Self.Active.Table (From_Index).Nested = No_Matcher_State
then
-- We can't pass directly Self.Active.Table (From_Index) as
-- a parameter to Mark_Active: if the table Self.Active is
-- reallocated during that call, the address we passed becomes
-- invalid, and as a result the table is not updated and we
-- might event get a storage_error.
Tmp := Self.Active.Table (From_Index).Nested;
Tmp2 := Self.NFA.States.Table (From).Nested;
Mark_Active (Self, List_Start => Tmp, From => Tmp2);
Self.Active.Table (From_Index).Nested := Tmp;
end if;
end if;
end Mark_Active_No_Check;
-----------------
-- Mark_Active --
-----------------
procedure Mark_Active
(Self : in out nfa_matcher'class;
List_Start : in out matcher_state_index;
From : state;
First_Nested : matcher_state_index := No_Matcher_State;
Active_Data : active_state_data := No_Active_Data)
is
begin
-- ??? Not very efficient, but the lists are expected to be short. We
-- could try to use a state->boolean array, but then we need one such
-- array for all nested NFA, which requires a lot of storage.
if Is_Active (Self, List_Start, From) then
return;
end if;
Mark_Active_No_Check
(Self,
List_Start,
From,
First_Nested,
Active_Data => Active_Data);
end Mark_Active;
-----------------
-- Start_Match --
-----------------
procedure Start_Match
(Self : in out nfa_matcher;
On : access nfa'class;
Start_At : state := Start_State)
is
begin
Self.NFA := nfa_access (On);
Self.First_Active := No_Matcher_State;
Init (Self.Active);
Mark_Active (Self, Self.First_Active, Start_At);
end Start_Match;
------------------
-- Current_Data --
------------------
function Current_Data
(Self : nfa_matcher;
Iter : active_state_iterator) return state_user_data
is
Current : matcher_state_index;
begin
if Iter.Current_Level = No_Matcher_State then
return Default_Data;
else
Current := Iter.States (Iter.Current_Level);
if Self.Active.Table (Current).Data_Is_Overridden then
return Self.Active.Table (Current).Overridden_Data;
else
return Self.NFA.States.Table (Self.Active.Table (Current).S)
.Data;
end if;
end if;
end Current_Data;
---------------------------
-- For_Each_Active_State --
---------------------------
function For_Each_Active_State
(Self : nfa_matcher;
Ignore_If_Nested : Boolean := False;
Ignore_If_Default : Boolean := False) return active_state_iterator
is
Max : matcher_state_index;
begin
if Self.NFA = null then
Max := 0;
else
Max := Last (Self.Active);
end if;
declare
Iter : active_state_iterator (Max);
begin
Iter.Ignore_If_Nested := Ignore_If_Nested;
Iter.Ignore_If_Default := Ignore_If_Default;
if Iter.Max /= 0 then
Iter.States (1) := Self.First_Active;
Iter.Current_Level := 1;
Internal_Next (Self, Iter, Move_First => False);
else
Iter.Current_Level := No_Matcher_State;
end if;
return Iter;
end;
end For_Each_Active_State;
-------------------
-- Internal_Next --
-------------------
procedure Internal_Next
(Self : nfa_matcher'class;
Iter : in out active_state_iterator;
Move_First : Boolean)
is
procedure Move_To_Next;
procedure Move_To_Next is
Current : matcher_state_index;
begin
-- First explore the nested states of the current state, if any
Current := Iter.States (Iter.Current_Level);
if Self.Active.Table (Current).Nested /= No_Matcher_State then
Iter.Current_Level := Iter.Current_Level + 1;
Iter.States (Iter.Current_Level) :=
Self.Active.Table (Current).Nested;
return;
end if;
-- Else move to the next state in the current level
Iter.States (Iter.Current_Level) :=
Self.Active.Table (Current).Next;
-- Else move to the next state in the previous level (recursively)
while Iter.States (Iter.Current_Level) = No_Matcher_State loop
Iter.Current_Level := Iter.Current_Level - 1;
exit when Iter.Current_Level = No_Matcher_State;
Iter.States (Iter.Current_Level) :=
Self.Active.Table (Iter.States (Iter.Current_Level)).Next;
end loop;
end Move_To_Next;
Current : matcher_state_index;
S2 : state;
begin
if Iter.Current_Level = No_Matcher_State then
return;
end if;
if Move_First then
Move_To_Next;
end if;
while Iter.Current_Level /= No_Matcher_State loop
-- Is the state we found acceptable ?
Current := Iter.States (Iter.Current_Level);
S2 := Self.Active.Table (Current).S;
if S2 /= Final_State and then S2 /= No_State then
-- Either we have no nested automaton
-- Or we always want to return the states anyway
-- Or the nested state has completed
if not Iter.Ignore_If_Nested
or else Self.Active.Table (Current).Nested = No_Matcher_State
or else
Self.Active.Table (Self.Active.Table (Current).Nested).S =
Final_State
then
if not Iter.Ignore_If_Default
or else
(Self.Active.Table (Current).Data_Is_Overridden
and then
Self.Active.Table (Current).Overridden_Data /=
Default_Data)
or else
(not Self.Active.Table (Current).Data_Is_Overridden
and then Self.NFA.States.Table (S2).Data /= Default_Data)
then
return;
end if;
end if;
end if;
Move_To_Next;
end loop;
end Internal_Next;
----------
-- Next --
----------
procedure Next
(Self : nfa_matcher;
Iter : in out active_state_iterator)
is
begin
Internal_Next (Self, Iter, Move_First => True);
end Next;
-------------
-- Current --
-------------
function Current
(Self : nfa_matcher;
Iter : active_state_iterator) return state
is
Current : matcher_state_index;
begin
if Iter.Current_Level = No_Matcher_State then
return No_State;
else
Current := Iter.States (Iter.Current_Level);
return Self.Active.Table (Current).S;
end if;
end Current;
----------------
-- Has_Parent --
----------------
function Has_Parent (Iter : active_state_iterator) return Boolean is
begin
return Iter.Current_Level > No_Matcher_State + 1;
end Has_Parent;
------------
-- Parent --
------------
function Parent
(Iter : active_state_iterator) return active_state_iterator
is
begin
return active_state_iterator'
(Max => Iter.Max,
Ignore_If_Default => Iter.Ignore_If_Default,
Ignore_If_Nested => Iter.Ignore_If_Nested,
States => Iter.States,
Current_Level => Iter.Current_Level - 1);
end Parent;
-------------------
-- Replace_State --
-------------------
procedure Replace_State
(Self : in out nfa_matcher;
Iter : active_state_iterator;
S : state)
is
M : matcher_state_index;
begin
if Iter.Current_Level /= No_Matcher_State then
Self.Active.Table (Iter.States (Iter.Current_Level)).S := S;
if Iter.Current_Level = 1 then
Mark_Active_No_Check
(Self,
List_Start => Self.First_Active,
From => S);
else
M :=
Self.Active.Table (Iter.States (Iter.Current_Level - 1))
.Nested;
Mark_Active_No_Check (Self, List_Start => M, From => S);
end if;
end if;
end Replace_State;
-------------------
-- Override_Data --
-------------------
procedure Override_Data
(Self : nfa_matcher;
Iter : active_state_iterator;
Data : state_user_data)
is
Current : matcher_state_index;
begin
if Iter.Current_Level /= No_Matcher_State then
Current := Iter.States (Iter.Current_Level);
Self.Active.Table (Current).Data_Is_Overridden := True;
Self.Active.Table (Current).Overridden_Data := Data;
end if;
end Override_Data;
-------------
-- Process --
-------------
procedure Process
(Self : in out nfa_matcher;
Input : symbol;
Success : out Boolean)
is
NFA : constant nfa_access := Self.NFA;
Saved : constant Matcher_State_Arrays.table_type :=
Self.Active.Table (1 .. Last (Self.Active));
Saved_First_Active : constant matcher_state_index :=
Self.First_Active;
procedure Process_Level
(Parent_State : in out matcher_state;
New_First : in out matcher_state_index;
Success : out Boolean);
-- Process all the nodes with a common parent (either all toplevel
-- states, or all nested states within a specific state).
type transition_filter is array (transition_kind) of Boolean;
procedure Process_Transitions
(From : state;
Parent_State : in out matcher_state;
New_First : in out matcher_state_index;
Filter : transition_filter);
-- Check all transitions from [First].
-- Parent_State is the state that contains the nested automata.
-------------------------
-- Process_Transitions --
-------------------------
procedure Process_Transitions
(From : state;
Parent_State : in out matcher_state;
New_First : in out matcher_state_index;
Filter : transition_filter)
is
T : transition_id := NFA.States.Table (From).First_Transition;
Matched : Boolean;
begin
while T /= No_Transition loop
declare
Tr : transition renames NFA.Transitions.Table (T);
begin
if Filter (Tr.Kind) then
case Tr.Kind is
when transition_on_empty | transition_on_exit_empty =>
Mark_Active (Self, New_First, Tr.To_State);
when others =>
if not Is_Active (Self, New_First, Tr.To_State) then
Matched :=
Match
(Self'access,
From_State => From,
To_State => Tr.To_State,
Parent_State_Data =>
Parent_State.Active_Data'access,
Trans => Tr.Sym,
Input => Input);
if Matched then
Mark_Active (Self, New_First, Tr.To_State);
end if;
end if;
end case;
end if;
T := Tr.Next_For_State;
end;
end loop;
end Process_Transitions;
-------------------
-- Process_Level --
-------------------
procedure Process_Level
(Parent_State : in out matcher_state;
New_First : in out matcher_state_index;
Success : out Boolean)
is
First : constant matcher_state_index := Parent_State.Nested;
N : matcher_state_index := First;
Event_Processed_In_Nested : Boolean;
Nested_Final : Boolean;
S : matcher_state;
Nested_First : matcher_state_index;
At_Current_Level : matcher_state_index;
begin
-- For each currently live state:
-- - if there are nested NFA, we process these first. If the
-- event is processed by them, it will not be passed on to the
-- corresponding super state (event bubbling stopped).
-- - if there are no nested NFA, or they did not process the
-- event, the event is then processed directly by the super
-- state.
-- This corresponds to standard semantics of event bubbling in
-- hierarchical NFA.
while N /= No_Matcher_State loop
S := Saved (N);
Event_Processed_In_Nested := False;
Nested_Final := True;
if S.Nested /= No_Matcher_State then
declare
Tmp : matcher_state_index := New_First;
begin
At_Current_Level := No_Matcher_State;
while Tmp /= No_Matcher_State loop
if Self.Active.Table (Tmp).S = S.S then
At_Current_Level := Tmp;
exit;
end if;
Tmp := Self.Active.Table (Tmp).Next;
end loop;
end;
if At_Current_Level /= No_Matcher_State then
Process_Level
(Parent_State => S,
New_First =>
Self.Active.Table (At_Current_Level).Nested,
Success => Success);
Nested_First :=
Self.Active.Table (At_Current_Level).Nested;
else
Nested_First := No_Matcher_State;
Process_Level
(Parent_State => S,
New_First => Nested_First,
Success => Success);
end if;
if Success then
-- Exits the nested NFA, and thus transitions from its
-- super state. The super state itself is terminated.
-- ??? Should the superstate remain active, in case it
-- has standard transitions ?
Nested_Final := Nested_In_Final (Self, Nested_First);
Event_Processed_In_Nested := True;
if Nested_Final then
Process_Transitions
(From => S.S,
Parent_State => Parent_State,
New_First => New_First,
Filter =>
(transition_on_exit_empty => True,
transition_on_exit_symbol => True,
others => False));
else
Mark_Active
(Self,
New_First,
S.S,
Nested_First,
Active_Data => S.Active_Data);
end if;
else
Nested_Final := False;
-- Error: nothing matches anymore in the nested NFA. We
-- terminate it, but keep processing this event in its
-- superstate (for instance, a camera in state "on" has a
-- nested NFA "record"<->"play"). If the nested receives
-- the event "turn off", it won't match the nested, but
-- that's not an error because the event is handled by
-- the super state "on".
end if;
end if;
if S.S /= Final_State
and then S.S /= No_State
and then not Event_Processed_In_Nested
then
Process_Transitions
(From => S.S,
Parent_State => Parent_State,
New_First => New_First,
Filter =>
(transition_on_empty => False,
transition_on_symbol => True,
others => False));
end if;
N := S.Next;
end loop;
Success := New_First /= No_Matcher_State;
end Process_Level;
Null_Parent_State : matcher_state :=
matcher_state'
(S => No_State,
Data_Is_Overridden => False,
Overridden_Data => Default_Data,
Next => No_Matcher_State,
Nested => Saved_First_Active,
Active_Data => No_Active_Data);
-- A dummy state that represents the whole machine.
begin
-- Reset the matcher.
Set_Last (Self.Active, No_Matcher_State);
Self.First_Active := No_Matcher_State;
Process_Level
(Parent_State => Null_Parent_State,
New_First => Self.First_Active,
Success => Success);
pragma assert (Null_Parent_State.Active_Data = No_Active_Data);
if not Success then
Set_Last (Self.Active, Saved'last);
Self.Active.Table (1 .. Saved'last) := Saved;
Self.First_Active := Saved_First_Active;
end if;
end Process;
---------------------
-- Nested_In_Final --
---------------------
function Nested_In_Final
(Self : nfa_matcher'class;
S : matcher_state_index) return Boolean
is
begin
return S = No_Matcher_State
or else Self.Active.Table (S).S = Final_State;
end Nested_In_Final;
--------------
-- Expected --
--------------
function Expected (Self : nfa_matcher) return String is
Msg : Unbounded_String;
Iter : active_state_iterator := For_Each_Active_State (Self);
T : transition_id;
S : state;
Parent_Data : access active_state_data := null;
begin
loop
S := Current (Self, Iter);
exit when S = No_State;
Parent_Data := null;
declare
P : constant active_state_iterator := Parent (Iter);
begin
if P.Current_Level /= No_Matcher_State then
Parent_Data :=
Self.Active.Table (P.States (P.Current_Level)).Active_Data'
access;
end if;
end;
T := Self.NFA.States.Table (S).First_Transition;
while T /= No_Transition loop
declare
Tr : transition renames Self.NFA.Transitions.Table (T);
begin
case Tr.Kind is
when transition_on_empty |
transition_on_exit_empty |
transition_on_exit_symbol =>
null;
when transition_on_symbol =>
declare
Tmp : constant String :=
Expected
(Self => Self,
From_State => S,
To_State => Tr.To_State,
Parent_State_Data => Parent_Data,
Trans => Tr.Sym);
begin
if Tmp /= "" then
if Msg /= Null_Unbounded_String then
Append (Msg, "|");
end if;
Append (Msg, Tmp);
end if;
end;
end case;
T := Tr.Next_For_State;
end;
end loop;
Next (Self, Iter);
end loop;
return To_String (Msg);
end Expected;
--------------
-- In_Final --
--------------
function In_Final (Self : nfa_matcher) return Boolean is
begin
return Nested_In_Final (Self, Self.First_Active);
end In_Final;
-----------------
-- Debug_Print --
-----------------
procedure Debug_Print
(Self : nfa_matcher'class;
Mode : dump_mode := dump_multiline;
Prefix : String := "")
is
NFA : constant nfa_access := Self.NFA;
procedure Internal (From : matcher_state_index; Prefix : String);
procedure Internal (From : matcher_state_index; Prefix : String) is
F : matcher_state_index := From;
begin
while F /= No_Matcher_State loop
-- Unless explicitly disabled
if Self.Active.Table (F).S /= No_State then
Put (Node_Label (NFA, Self.Active.Table (F).S));
if Self.Active.Table (F).Nested /= No_Matcher_State then
if Mode = dump_multiline then
New_Line;
end if;
Put (Prefix & " [");
if Mode = dump_multiline then
Internal (Self.Active.Table (F).Nested, Prefix & " ");
else
Internal (Self.Active.Table (F).Nested, Prefix);
end if;
Put ("]");
end if;
else
Put ("");
end if;
F := Self.Active.Table (F).Next;
if F /= No_Matcher_State then
Put (" ");
end if;
end loop;
end Internal;
begin
if Self.First_Active = No_Matcher_State then
Put_Line (Prefix & "[no active state]");
else
Put (Prefix);
Internal (Self.First_Active, "");
New_Line;
end if;
end Debug_Print;
end Matchers;
end Sax.State_Machines;