Files
markdown/source/parser/implementation/markdown-implementation-list_items.adb
Maxim Reznik 96ec9c3e4e Add Initialize procedure
to allow task-safe initialization.
2025-11-18 23:41:03 +02:00

228 lines
6.9 KiB
Ada

--
-- Copyright (C) 2021-2023, AdaCore
--
-- SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
--
with VSS.Regular_Expressions;
with VSS.Strings.Conversions;
with VSS.Strings.Cursors;
package body Markdown.Implementation.List_Items is
Bullet_List_Marker : constant Wide_Wide_String := "[-+*]";
Ordered_List_Marker : constant Wide_Wide_String := "([0-9][0-9]*)[.)]";
Marker_Pattern : constant Wide_Wide_String :=
"^((?: | | |)(" & -- 1 group (indent + marker), 2 group (the
Bullet_List_Marker & "|" & -- marker it-self)
Ordered_List_Marker & -- 3 group (inside Ordered_List_Marker)
")) *";
Prefix : VSS.Regular_Expressions.Regular_Expression;
-- Regexp of Marker_Pattern
Indent_1 : VSS.Regular_Expressions.Regular_Expression;
-- One or more spaces at the beginning of a string: "^ +"
function Marker_Value
(Match : VSS.Regular_Expressions.Regular_Expression_Match) return Natural;
-- Return numeric marker as an integer
----------------------------------
-- Consume_Continuation_Markers --
----------------------------------
overriding procedure Consume_Continuation_Markers
(Self : in out List_Item;
Input : in out Input_Position;
Ok : out Boolean)
is
Match : constant VSS.Regular_Expressions.Regular_Expression_Match :=
Indent_1.Match (Input.Line.Expanded, Input.First);
begin
Ok := Match.Has_Match and then
Match.Marker.Character_Length >= Self.Marker_Width;
if Ok then
-- We have enough spaces at the beginning, consume them
Self.Has_Blank_Line := Self.Has_Blank_Line
or Self.Ends_With_Blank_Line;
Self.Ends_With_Blank_Line := False;
Forward (Input.First, Self.Marker_Width);
elsif not Input.First.Has_Element then
-- We have characters, treat it as an empty line
Ok := not
(Self.Ends_With_Blank_Line
and Self.Starts_With_Blank_Line
and not Self.First_Line);
if Ok then
Self.Ends_With_Blank_Line := True;
end if;
end if;
Self.First_Line := False;
end Consume_Continuation_Markers;
------------
-- Create --
------------
overriding function Create
(Input : not null access Input_Position) return List_Item
is
Match : constant VSS.Regular_Expressions.Regular_Expression_Match :=
Prefix.Match (Input.Line.Expanded, Input.First);
Suffix_Length : constant VSS.Strings.Character_Count :=
Match.Marker.Character_Length - Match.Marker (1).Character_Length;
begin
return Result : List_Item do
Result.Is_Ordered := Match.Marker (3).Is_Valid;
Result.Marker := Match.Captured (2);
Result.Marker_Value := Marker_Value (Match);
if Suffix_Length <= 4 and then
Match.Last_Marker.Character_Index
= Input.Line.Expanded.Character_Length
then
-- Empty line marker: ^\ {0,3}(Marker)(\ {0,4}$).
Result.Marker_Width := Match.Marker (1).Character_Length + 1;
Result.First_Line := True;
Result.Starts_With_Blank_Line := True;
-- Shift Input.First to end-of-line
Input.First.Set_After_Last (Input.Line.Expanded);
elsif Suffix_Length > 4 then
-- Indented code in the list item
Result.Marker_Width := Match.Marker (1).Character_Length + 1;
Input.First.Set_At (Match.Last_Marker (1)); -- at marker end
-- Skip 1 space after marker
Forward (Input.First, 2);
else
Result.Marker_Width := Match.Marker.Character_Length;
-- Skip marker and all spaces if any
Input.First.Set_At (Match.Last_Marker);
Forward (Input.First);
end if;
end return;
end Create;
--------------
-- Detector --
--------------
procedure Detector
(Input : Input_Position;
Tag : in out Ada.Tags.Tag;
CIP : out Can_Interrupt_Paragraph)
is
Marker : VSS.Strings.Virtual_String;
Number : Natural;
Match : VSS.Regular_Expressions.Regular_Expression_Match;
Suffix_Length : VSS.Strings.Character_Count;
End_Of_Line_Matched : Boolean;
begin
Initialize;
CIP := True; -- Suppress a warning about uninitialized parameter
Match := Prefix.Match (Input.Line.Expanded, Input.First);
if Match.Has_Match then
End_Of_Line_Matched := Match.Last_Marker.Character_Index
= Input.Line.Expanded.Character_Length;
Marker := Match.Captured (2);
Suffix_Length :=
Match.Marker.Character_Length - Match.Marker (1).Character_Length;
if Marker.Character_Length > 10 then
-- no more than 9 digits in the marker are allowed
return;
elsif Suffix_Length = 0 and not End_Of_Line_Matched then
-- We have non-space characters just after marker. Not a list item
return;
end if;
Tag := List_Item'Tag;
Number := Marker_Value (Match);
-- Calculate Can_Interrupt_Paragraph
CIP :=
(if Suffix_Length <= 4 and then End_Of_Line_Matched then
-- Empty line marker: ^\ {0,3}(Marker)(\ {0,4}$).
-- An empty list item cannot interrupt a paragraph.
False
elsif Match.Marker (3).Is_Valid then
-- Check if numbered item is `1`
Number = 1
else
True);
end if;
end Detector;
----------------
-- Initialize --
----------------
procedure Initialize is
begin
if not Prefix.Is_Valid then -- Construct Prefix regexp
Prefix := VSS.Regular_Expressions.To_Regular_Expression
(VSS.Strings.To_Virtual_String (Marker_Pattern));
Indent_1 := VSS.Regular_Expressions.To_Regular_Expression
(VSS.Strings.To_Virtual_String ("^ *")); -- XXX: Fix with "^ +"
end if;
end Initialize;
----------------
-- Is_Ordered --
----------------
function Is_Ordered (Self : List_Item'Class) return Boolean is
(Self.Is_Ordered);
------------
-- Marker --
------------
function Marker (Self : List_Item'Class)
return VSS.Strings.Virtual_String is (Self.Marker);
function Marker (Self : List_Item'Class) return Natural
is (Self.Marker_Value);
------------------
-- Marker_Value --
------------------
function Marker_Value
(Match : VSS.Regular_Expressions.Regular_Expression_Match)
return Natural is
begin
-- GCC 12 generates wrong code if condition expression is used here
if Match.Marker (3).Is_Valid then
return Natural'Wide_Wide_Value
(VSS.Strings.Conversions.To_Wide_Wide_String
(Match.Captured (3)));
else
return 0;
end if;
end Marker_Value;
end Markdown.Implementation.List_Items;