peg-matching-0.1.0.0: src/Syntax/Base.hs
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use newtype instead of data" #-}
{-|
Module : Syntax.Base
Description : Basic definitions for symbols and grammar utilities.
Copyright : (c) Guilherme Drummond, Rodrigo Ribeiro, 2025
License : BSD-3-Clause
Maintainer : rodrigo.ribeiro@ufop.edu.br
Stability : experimental
Portability : POSIX
This module defines the basic types to represent terminal and non-terminal symbols
in a grammar, as well as utilities for list manipulation and a class for formatted printing.
-}
module Syntax.Base
( NonTerminal(..)
, Terminal(..)
, Symbol
, Pretty(..)
, toMaybe
, duplicatesOfFirst
, filterByFirst
) where
import Text.PrettyPrint.HughesPJ (text, Doc, hcat)
import Data.Generics (Data, Typeable)
{-|
Represents a non-terminal symbol in a grammar.
A 'NonTerminal' is simply a string that identifies the non-terminal.
@since 1.0.0
-}
data NonTerminal
= NT String
deriving (Eq, Show, Ord, Typeable, Data)
{-|
Represents a terminal symbol in a grammar.
A 'Terminal' is a string that identifies the terminal.
@since 1.0.0
-}
data Terminal
= T String
deriving (Eq, Show, Ord, Typeable, Data)
{-|
Represents a "symbol," which is either a 'NonTerminal' or a 'Terminal'.
@since 1.0.0
-}
type Symbol = Either NonTerminal Terminal
{-|
Type class for types that can be printed as a text document (PrettyPrint).
The 'Pretty' class defines the 'pPrint' method to generate the representation in 'Doc' format.
@since 1.0.0
-}
class Pretty a where
{-|
Generates the formatted representation of a value as a 'Doc'.
@since 1.0.0
-}
pPrint :: a -> Doc
{-|
Instance of the 'Pretty' class for lists.
Prints each element of the list concatenated.
@since 1.0.0
-}
instance Pretty a => Pretty [a] where
pPrint :: [a] -> Doc
pPrint l = hcat (map pPrint l)
{-|
Instance of the 'Pretty' class for 'NonTerminal'.
Prints the name of the non-terminal.
@since 1.0.0
-}
instance Pretty NonTerminal where
pPrint :: NonTerminal -> Doc
pPrint (NT nt) = text nt
{-|
Instance of the 'Pretty' class for 'Terminal'.
Prints the name of the terminal in quotes.
@since 1.0.0
-}
instance Pretty Terminal where
pPrint :: Terminal -> Doc
pPrint (T t) = text (show t)
{-|
Instance of the 'Pretty' class for 'Symbol'.
Prints the symbol, whether it is a 'NonTerminal' or a 'Terminal'.
@since 1.0.0
-}
instance Pretty Symbol where
pPrint :: Symbol -> Doc
pPrint (Left nt) = pPrint nt
pPrint (Right t) = pPrint t
{-|
The 'toMaybe' function takes a boolean value and a generic value.
If the boolean value is 'True', it returns the value wrapped in a 'Just'.
Otherwise, it returns 'Nothing'.
=== Usage examples:
>>> toMaybe True "Hello"
Just "Hello"
>>> toMaybe False "Hello"
Nothing
@since 1.0.0
-}
toMaybe :: Bool -> a -> Maybe a
toMaybe False _ = Nothing
toMaybe True a = Just a
{-|
The 'duplicatesOfFirst' function takes a list of pairs @(a, b)@ and returns a list containing
the elements @a@ that appear more than once in the list. The function compares only the first
elements @(a)@ of the pairs.
=== Usage examples:
>>> duplicatesOfFirst [(1, "a"), (2, "b"), (1, "c"), (3, "d"), (2, "e")]
[2,1]
@since 1.0.0
-}
duplicatesOfFirst :: Eq a => [(a, b)] -> [a]
duplicatesOfFirst ls = duplicates ls [] []
where
duplicates [] _ dups = dups
duplicates (x:xs) checked dups =
if fst x `elem` checked
then duplicates xs checked (fst x:dups)
else duplicates xs (fst x:checked) dups
{-|
The 'filterByFirst' function takes a list of pairs @(a, b)@ and a value of type @a@.
It returns a list containing all the @b@ values that are associated with the given @a@
value. The function filters the pairs by the key @a@ and returns the corresponding values.
=== Usage examples:
>>> filterByFirst [(1, "a"), (2, "b"), (1, "c"), (3, "d")] 1
["a","c"]
@since 1.0.0
-}
filterByFirst :: Eq a => [(a, b)] -> a -> [b]
filterByFirst g' x = map snd $ filter ((x ==) . fst) g'