「‍」 Lingenic

EMOJI

(⤓.txt ◇.txt); γ ≜ [2026-05-17T174254.656, 2026-05-17T174254.656] ∧ |γ| = 1

EMOJI MODULE
============

Standard: UTS #51 (Unicode Emoji)
Files:    src/lingenic_text-emoji_spec.ads   (ghost specification)
          src/lingenic_text-emoji.ads        (public API)
          src/lingenic_text-emoji.adb        (implementation)


PURPOSE
-------

The Emoji module provides boolean emoji property lookups and classifies
UTF-8 byte sequences into emoji sequence types per the UTS #51 grammar.


GHOST SPECIFICATION (Emoji_Spec)
---------------------------------

Sequence Types:

  type Emoji_Sequence_Type is
    (Not_Emoji,
     Emoji_Character,
     Emoji_Presentation_Seq,
     Emoji_Keycap_Seq,
     Emoji_Modifier_Seq,
     Emoji_Flag_Seq,
     Emoji_Tag_Seq,
     Emoji_ZWJ_Seq);

Structural Codepoint Constants:

  VS16        = 16#FE0F#     Variation Selector 16 (emoji presentation)
  Keycap      = 16#20E3#     Combining Enclosing Keycap
  ZWJ         = 16#200D#     Zero Width Joiner
  Cancel_Tag  = 16#E007F#    Cancel Tag
  RI_First    = 16#1F1E6#    First Regional Indicator
  RI_Last     = 16#1F1FF#    Last Regional Indicator
  Tag_First   = 16#E0020#    First Tag character
  Tag_Last    = 16#E007E#    Last Tag character

Structural Predicates:

  Is_Keycap_Base(CP)          CP is a valid keycap base (0-9, #, *)
  Is_Regional_Indicator(CP)   CP in RI_First..RI_Last
  Is_Tag_Spec(CP)             CP in Tag_First..Tag_Last

  Max_Sequence_Bytes = 256    Maximum input size for classification

Classification Logic:

  Classify_From_Flags
    (Is_Emoji_CP, Is_Keycap_B, Is_Mod_Base, Is_RI_First,
     Has_ZWJ, RI_Count, Has_Keycap, Tag_Done, Has_Mod, Has_VS16)

  Priority-ordered decision based on accumulated flags from the scan:

    Has_ZWJ and Is_Emoji_CP       -> Emoji_ZWJ_Seq
    RI_Count = 2 and Is_RI_First  -> Emoji_Flag_Seq
    Has_Keycap and Is_Keycap_B    -> Emoji_Keycap_Seq
    Tag_Done and Is_Emoji_CP      -> Emoji_Tag_Seq
    Has_Mod and Is_Mod_Base       -> Emoji_Modifier_Seq
    Has_VS16 and Is_Emoji_CP      -> Emoji_Presentation_Seq
    Is_Emoji_CP                   -> Emoji_Character
    otherwise                     -> Not_Emoji


INITIALIZATION
--------------

  procedure Initialize
    (UCD_Dir : String;
     Success : out Boolean);

    Post: if Success then Initialized

Reads from UCD_Dir:

  emoji-data.txt    5 boolean emoji properties

Loads flat Boolean arrays indexed by codepoint:

  Emoji                  Has the Emoji property
  Emoji_Presentation     Has the Emoji_Presentation property
  Emoji_Modifier         Has the Emoji_Modifier property
  Emoji_Modifier_Base    Has the Emoji_Modifier_Base property
  Emoji_Component        Has the Emoji_Component property

Does NOT depend on Properties.Initialized.  The emoji module is
self-contained.  Extended_Pictographic remains in the Properties module
(needed by grapheme segmentation at Properties init time).


PUBLIC API
----------

Boolean Property Lookups:

  Is_Emoji(CP) -> Boolean
  Is_Emoji_Presentation(CP) -> Boolean
  Is_Emoji_Modifier(CP) -> Boolean
  Is_Emoji_Modifier_Base(CP) -> Boolean
  Is_Emoji_Component(CP) -> Boolean

  All are O(1) flat array lookups.  Pre: Initialized.

Classify_Sequence:

  function Classify_Sequence
    (Input : Byte_Array) return Emoji_Sequence_Type;

    Pre:  Initialized
          Input'Length >= 1
          Input'Length <= Max_Sequence_Bytes
          Input'Last < Positive'Last

    Post: Result = Ghost_Classify_All(Input)

  Classifies a UTF-8 byte sequence (typically one grapheme cluster as
  returned by Next_Grapheme_Cluster_Break) into one of 8 emoji types.

  The classifier implements the UTS #51 EBNF grammar:

    possible_emoji := zwj_element (\x{200D} zwj_element)*
    flag_sequence  := \p{RI} \p{RI}
    zwj_element    := \p{Emoji} emoji_modification?
                    | flag_sequence
    emoji_modification := \p{EMod}
                        | \x{FE0F} \x{20E3}?
                        | tag_modifier
    tag_modifier   := [\x{E0020}-\x{E007E}]+ \x{E007F}

  The implementation is a single forward scan that accumulates flags
  in an Emoji_Scan_State record, then applies Classify_From_Flags.

  Invalid UTF-8 or non-emoji input returns Not_Emoji.


GHOST SPECIFICATION STRUCTURE
-----------------------------

Scan State Record (Emoji_Scan_State):

  First_CP     Codepoint of the first character in the sequence
  Has_ZWJ      True if ZWJ was seen (and not in an RI pair)
  Has_VS16     True if VS16 was seen
  Has_Keycap   True if Keycap (U+20E3) was seen
  Has_Mod      True if an Emoji_Modifier was seen
  Tag_Done     True if a tag sequence was completed (Cancel_Tag after tags)
  In_Tag       True if currently in a tag sequence
  RI_Count     Count of Regional_Indicator codepoints
  CP_Count     Total codepoints processed

Ghost Functions:

  Ghost_Update_Scan(St, CP, Max_CP) -> Emoji_Scan_State
    Updates the scan state for one codepoint.  Mirrors the body's
    flag-setting logic exactly.

  Ghost_Scan_Result(St) -> Emoji_Sequence_Type
    Final classification from accumulated state.
    Calls Classify_From_Flags with booleans derived from First_CP.

  Ghost_Scan(Input, Cur, St) -> Emoji_Sequence_Type
    Recursive ghost function simulating the forward scan.
    Terminates because Ghost_Step_Length >= 1 (Cur strictly increases).

  Ghost_Classify_All(Input) -> Emoji_Sequence_Type
    = Ghost_Scan(Input, Input'First, Ghost_Initial_State)
    Top-level entry point referenced by Classify_Sequence's postcondition.


USAGE PATTERN
-------------

Typical usage: classify grapheme clusters returned by the segmentation
module:

  Pos := Text'First;
  while Pos <= Text'Last loop
     Graphemes.Next_Grapheme_Cluster_Break (Text, Pos, Next_Pos);

     --  Classify the cluster
     Cluster := Text (Pos .. Next_Pos - 1);
     if Cluster'Length <= Emoji_Spec.Max_Sequence_Bytes then
        Seq_Type := Emoji.Classify_Sequence (Cluster);
        case Seq_Type is
           when Emoji_ZWJ_Seq =>
              --  ZWJ emoji sequence (e.g., family emoji)
           when Emoji_Flag_Seq =>
              --  Flag emoji (pair of regional indicators)
           when others =>
              null;
        end case;
     end if;

     Pos := Next_Pos;
  end loop;