packages feed

peg-matching-0.1.0.0: src/Syntax/Pattern.hs

{-|
Module      : Syntax.Pattern
Description : Definitions of patterns and utilities for grammars.
Copyright   : (c) Guilherme Drummond, Rodrigo Ribeiro, 2025
License     : BSD-3-Clause
Maintainer  : rodrigo.ribeiro@ufop.edu.br
Stability   : experimental
Portability : POSIX

This module defines structures to represent patterns over grammars,
as well as utility functions for manipulating and replacing named patterns.
It also provides instances of the 'Pretty' class for formatted printing.
-}
module Syntax.Pattern
    ( Pattern(..)
    , SyntaxPattern(..)
    , NamedPattern
    , NamedSynPat
    , references
    , replaceSynPats
    ) where

import Syntax.Base (NonTerminal(..), Terminal(..), Pretty(..))
import Text.PrettyPrint.HughesPJ ((<+>), text, parens, Doc, brackets)
import Data.List (nub)
import Data.Bifunctor (Bifunctor(second))
import Data.Generics (everything, mkQ, Typeable, Data, everywhere, mkT)
import Syntax.Peg (Expression)

{-|
Represents a pattern in a grammar.

A 'Pattern' can be:
- 'PatEpsilon': Represents the empty pattern (ε).
- 'PatT': A terminal symbol.
- 'PatNT': A non-terminal symbol associated with another pattern.
- 'PatSeq': A sequence of two patterns.
- 'PatChoice': A choice between two patterns.
- 'PatStar': A repetition of zero or more times of a pattern.
- 'PatNot': A negation of a pattern.
- 'PatVar': A pattern associated with a variable.

@since 1.0.0
-}
data Pattern
    = PatEpsilon
    | PatT Terminal
    | PatNT NonTerminal Pattern
    | PatSeq Pattern Pattern
    | PatChoice Pattern Pattern
    | PatStar Pattern
    | PatStarSeq [Pattern]
    | PatNot Pattern
    | PatVar Expression String
    deriving (Eq, Show, Ord, Typeable, Data)

{-|
Represents a syntactic pattern in a grammar.

A 'SyntaxPattern' can be:
- 'SynEpsilon': Represents the empty pattern (ε).
- 'SynT': A terminal symbol.
- 'SynNT': A non-terminal symbol associated with another syntactic pattern.
- 'SynSeq': A sequence of two syntactic patterns.
- 'SynChoice': A choice between two syntactic patterns.
- 'SynStar': A repetition of zero or more times of a syntactic pattern.
- 'SynNot': A negation of a syntactic pattern.
- 'SynVar': A syntactic pattern associated with a variable.
- 'SynRef': A reference to a named pattern.

@since 1.0.0
-}
data SyntaxPattern
    = SynEpsilon
    | SynT Terminal
    | SynNT NonTerminal SyntaxPattern
    | SynSeq SyntaxPattern SyntaxPattern
    | SynChoice SyntaxPattern SyntaxPattern
    | SynStar SyntaxPattern
    | SynNot SyntaxPattern
    | SynVar Expression String
    | SynRef String
    deriving (Eq, Show, Ord, Typeable, Data)

{-|
A named pattern, which associates a name ('String') with a 'Pattern'.

@since 1.0.0
-}
type NamedPattern = (String, Pattern)

{-|
A named syntactic pattern, which associates a name ('String') with a 'SyntaxPattern'.

@since 1.0.0
-}
type NamedSynPat = (String, SyntaxPattern)

{-|
Instance of the 'Pretty' class for 'Pattern'.

Prints the pattern in a readable format, with operators like @/@ for choice,
@*@ for repetition, and @!@ for negation.

@since 1.0.0
-}
instance Pretty Pattern where
    pPrint :: Pattern -> Doc
    pPrint (PatNT nt p)      = pPrint nt <+> text ":=" <+> parens (pPrint p)
    pPrint (PatT t)          = pPrint t
    pPrint (PatVar s name)   = text ("#" ++ name) <> text ":" <> parens (pPrint s)
    pPrint PatEpsilon        = text "ε"
    pPrint (PatSeq p1 p2)    = pPrint p1 <+> pPrint p2
    pPrint (PatChoice p1 p2) = parens $ pPrint p1 <+> text "/" <+> pPrint p2
    pPrint (PatStar p)       = parens (pPrint p) <> text "*"
    pPrint (PatStarSeq ps)   = (brackets . pPrint) ps
    pPrint (PatNot p)        = text "!" <> parens (pPrint p)

{-|
Instance of the 'Pretty' class for 'SyntaxPattern'.

Prints the syntactic pattern in a readable format, with operators like @/@ for choice,
@*@ for repetition, and @!@ for negation.

@since 1.0.0
-}
instance Pretty SyntaxPattern where
    pPrint :: SyntaxPattern -> Doc
    pPrint (SynNT nt ps)     = pPrint nt <+> text ":=" <+> parens (pPrint ps)
    pPrint (SynT t)          = pPrint t
    pPrint (SynVar s name)   = text ("#" ++ name) <> text ":" <> parens (pPrint s)
    pPrint SynEpsilon        = text "ε"
    pPrint (SynSeq p1 p2)    = pPrint p1 <+> pPrint p2
    pPrint (SynChoice p1 p2) = parens $ pPrint p1 <+> text "/" <+> pPrint p2
    pPrint (SynStar p)       = parens (pPrint p) <> text "*"
    pPrint (SynNot p)        = text "!" <> parens (pPrint p)
    pPrint (SynRef name)     = text $ "@" ++ name

{-|
Instance of the 'Pretty' class for 'NamedPattern'.

Prints the named pattern in the format @pattern \<name\> : \<pattern\>@.

@since 1.0.0
-}
instance Pretty NamedPattern where
    pPrint :: NamedPattern -> Doc
    pPrint (name, pat) = text ("pattern " ++ name ++ " :") <+> pPrint pat <+> text "\n"

{-|
Instance of the 'Pretty' class for 'NamedSynPat'.

Prints the named syntactic pattern in the format @pattern \<name\> : \<pattern\>@.

@since 1.0.0
-}
instance Pretty NamedSynPat where
    pPrint :: NamedSynPat -> Doc
    pPrint (name, syn) = text ("pattern " ++ name ++ " :") <+> pPrint syn <+> text "\n"

-------------------------------------------------------------------------------

{-|
Replaces all references in a 'SyntaxPattern' with their corresponding patterns,
based on a named pattern.

@since 1.0.0
-}
replaceInPat :: NamedSynPat -> SyntaxPattern -> SyntaxPattern
replaceInPat ref = everywhere $ mkT (replace ref)
    where
        replace (n, p) (SynRef n') = if n == n' then p else SynRef n'
        replace _ p                = p

{-|
Returns a list of pattern names referenced in a 'SyntaxPattern'.

=== Usage examples:

>>> references (SynSeq (SynRef "A") (SynRef "B"))
["A","B"]

@since 1.0.0
-}
references :: SyntaxPattern -> [String]
references = nub <$> everything (++) ([] `mkQ` refs)
    where
        refs (SynRef s) = [s]
        refs _ = []

{-|
Replaces all references in a list of patterns ordered by dependency.

@since 1.0.0
-}
replaceSynPats :: [NamedSynPat] -> [NamedSynPat]
replaceSynPats ps = foldr replaceSynPat ps ps

{-|
Replaces all references in a pattern within a list of patterns.

@since 1.0.0
-}
replaceSynPat :: NamedSynPat -> [NamedSynPat] -> [NamedSynPat]
replaceSynPat p = map (second $ replaceInPat p)