markdown_24.0.0_70ffe37b/source/parser/implementation/markdown-parsers.adb

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
--
--  Copyright (C) 2021-2023, AdaCore
--
--  SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
--

with Ada.Tags.Generic_Dispatching_Constructor;

with Markdown.Documents.Internals;
with Markdown.Implementation.ATX_Headings;
with Markdown.Implementation.Auto_Links;
with Markdown.Implementation.Code_Spans;
with Markdown.Implementation.Documents;
with Markdown.Implementation.Fenced_Code_Blocks;
with Markdown.Implementation.Indented_Code_Blocks;
with Markdown.Implementation.List_Items;
with Markdown.Implementation.Paragraphs;
with Markdown.Implementation.Quotes;
with Markdown.Implementation.Thematic_Breaks;

package body Markdown.Parsers is

   procedure Register_Common_Mark_Blocks (Self : in out Markdown_Parser'Class);
   --  Register CommonMark block detectors

   procedure Find_Block_Start
     (Self : Markdown_Parser'Class;
      Line : Input_Position;
      Tag  : out Ada.Tags.Tag;
      CIP  : out Can_Interrupt_Paragraph);
   --  Find the block that could start in given line. Return its tag and CIP.

   procedure Create_Block
     (Line       : aliased in out Input_Position;
      Tag        : Ada.Tags.Tag;
      Containers : in out Container_Vectors.Vector;
      Leaf       : out Abstract_Block_Access);
   --  Create new block with given Tag and consume related characters from Line
   --  If the block is a container block append it to the Containers, otherwise
   --  put it into Leaf.

   procedure Start_Parsing (Self : in out Markdown_Parser'Class);
   procedure Complete_Parsing (Self : in out Markdown_Parser'Class);
   --  Complete parsing and produce Self.Document

   ----------------------
   -- Complete_Parsing --
   ----------------------

   procedure Complete_Parsing (Self : in out Markdown_Parser'Class) is
   begin
      Self.State := Completed;

      Self.Open.First_Element.Complete_Parsing (Self.Inline_Parser);

      Markdown.Documents.Internals.Set
        (Self.Document, Self.Open.First_Element.all);
   end Complete_Parsing;

   ------------------
   -- Create_Block --
   ------------------

   procedure Create_Block
     (Line       : aliased in out Input_Position;
      Tag        : Ada.Tags.Tag;
      Containers : in out Container_Vectors.Vector;
      Leaf       : out Abstract_Block_Access)
   is
      function Constructor is new Ada.Tags.Generic_Dispatching_Constructor
        (Abstract_Block, Input_Position, Create);

      Block  : Abstract_Block_Access;
   begin
      Block := new Abstract_Block'Class'(Constructor (Tag, Line'Access));

      if not Containers.Is_Empty then
         Containers.Last_Element.Children.Append (Block);
      end if;

      if Block.Is_Container then
         Containers.Append (Abstract_Container_Block_Access (Block));
      else
         Leaf := Block;
      end if;
   end Create_Block;

   --------------
   -- Document --
   --------------

   function Document
     (Self : in out Markdown_Parser) return Markdown.Documents.Document is
   begin
      case Self.State is
         when Initial =>
            Self.Start_Parsing;
            Self.Complete_Parsing;
         when Started =>
            Self.Complete_Parsing;
         when Completed =>
            null;
      end case;

      return Self.Document;
   end Document;

   ----------------------
   -- Find_Block_Start --
   ----------------------

   procedure Find_Block_Start
     (Self : Markdown_Parser'Class;
      Line : Input_Position;
      Tag  : out Ada.Tags.Tag;
      CIP  : out Can_Interrupt_Paragraph)
   is
      use type Ada.Tags.Tag;
   begin
      Tag := Ada.Tags.No_Tag;

      for Detector of Self.Block_Detectors loop
         Detector (Line, Tag, CIP);

         exit when Tag /= Ada.Tags.No_Tag;
      end loop;
   end Find_Block_Start;

   ----------------
   -- Parse_Line --
   ----------------

   procedure Parse_Line
     (Self : in out Markdown_Parser'Class;
      Line : VSS.Strings.Virtual_String)
   is
      use type Ada.Tags.Tag;

      Done           : Boolean := False;
      Tag            : Ada.Tags.Tag;
      Open           : Container_Vectors.Vector;
      --  Subset of Self.Open block which are still open after this line
      New_Containers : Container_Vectors.Vector;
      New_Leaf       : Abstract_Block_Access;
      CIP            : Can_Interrupt_Paragraph;
      Expanded_Line  : aliased Input_Line := (Line, Line);
      Input          : aliased Input_Position :=
        (Line  => Expanded_Line'Unchecked_Access,
         First => Expanded_Line.Expanded.At_First_Character);
      Match          : Boolean := False;
   begin
      case Self.State is
         when Initial =>
            Self.Start_Parsing;
         when Completed =>
            return;
         when Started =>
            null;
      end case;

      --  First we iterate through the open container blocks. Each block
      --  imposes a condition that the line must satisfy if the block is to
      --  remain open. For example, a block quote requires a '>' character. We
      --  consume such continuation markers and keep list of such blocks in
      --  Open vector.
      for Block of Self.Open loop
         Block.Consume_Continuation_Markers (Input, Match);

         exit when not Match;

         Open.Append (Block);
      end loop;

      --  Try to find a new block and check if it can interrupt paragraph
      Self.Find_Block_Start (Input, Tag, CIP);

      --  Try to append Input to Self.Open_Leaf if any, taking CIP into account
      if not Match then
         Self.Open_Leaf := null;
      elsif Self.Open_Leaf.Assigned then
         Match := False;
         Self.Open_Leaf.Append_Line (Input, CIP, Match);

         if Match then
            Done := True;
            Tag := Ada.Tags.No_Tag;
         else
            Self.Open_Leaf := null;
         end if;
      end if;

      --  Otherwise create new blocks
      if not Done and Input.First.Has_Element then
         while Tag /= Ada.Tags.No_Tag and not New_Leaf.Assigned loop
            Create_Block (Input, Tag, New_Containers, New_Leaf);
            Self.Find_Block_Start (Input, Tag, CIP);
         end loop;

         Self.Open_Leaf := New_Leaf;
         --  pragma Assert (Blank_Pattern.Find_Match (Input).Is_Matched);
      end if;

      Self.Open.Move (Source => Open);  --  Replace Self.Open with Open

      if New_Leaf.Assigned then
         if New_Containers.Is_Empty then
            Self.Open.Last_Element.Children.Append (New_Leaf);
         end if;
      end if;

      if not New_Containers.Is_Empty then
         Self.Open.Last_Element.Children.Append
           (Abstract_Block_Access (New_Containers.First_Element));
      end if;

      --  Self.Open.Append_Vector (New_Containers);  fails on old compiler
      for Item of New_Containers loop
         Self.Open.Append (Item);
      end loop;
   end Parse_Line;

   --------------------
   -- Register_Block --
   --------------------

   procedure Register_Block
     (Self     : in out Markdown_Parser'Class;
      Detector : Block_Detector) is
   begin
      Self.Block_Detectors.Append (Detector);
   end Register_Block;

   ---------------------------------
   -- Register_Common_Mark_Blocks --
   ---------------------------------

   procedure Register_Common_Mark_Blocks
     (Self : in out Markdown_Parser'Class)
   is
      package MI renames Markdown.Implementation;
   begin
      Self.Register_Block (MI.Thematic_Breaks.Detector'Access);
      Self.Register_Block (MI.ATX_Headings.Detector'Access);
      Self.Register_Block (MI.Indented_Code_Blocks.Detector'Access);
      Self.Register_Block (MI.Fenced_Code_Blocks.Detector'Access);
      Self.Register_Block (MI.List_Items.Detector'Access);
      Self.Register_Block (MI.Quotes.Detector'Access);
      Self.Register_Block (MI.Paragraphs.Detector'Access);

      Self.Inline_Parser.Register
        (Markdown.Implementation.Code_Spans.Parse_Code_Span'Access);
      Self.Inline_Parser.Register
        (Markdown.Implementation.Auto_Links.Parse_Auto_Link'Access);
   end Register_Common_Mark_Blocks;

   -------------------
   -- Start_Parsing --
   -------------------

   procedure Start_Parsing (Self : in out Markdown_Parser'Class) is
      Root_Block : constant Abstract_Container_Block_Access :=
        new Markdown.Implementation.Documents.Document;
   begin
      Self.State := Started;

      if Self.Block_Detectors.Is_Empty then
         Self.Register_Common_Mark_Blocks;
      end if;

      Self.Open.Append (Root_Block);
   end Start_Parsing;

end Markdown.Parsers;