packages feed

phino-0.0.147: src/Condition.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE RecordWildCards #-}

-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
-- SPDX-License-Identifier: MIT

module Condition (parseCondition, parseConditionThrows) where

import AST (Attribute (AtDelta, AtLambda))
import Control.Exception (Exception)
import Data.Void (Void)
import Misc (orThrow)
import Parser (PhiParser (..), phiParser)
import Text.Megaparsec
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as L
import Text.Printf (printf)
import qualified Yaml as Y

newtype ConditionException = CouldNotParseCondition {message :: String}
  deriving (Exception)

instance Show ConditionException where
  show CouldNotParseCondition{..} = printf "Couldn't parse given condition, cause: %s" message

type Parser = Parsec Void String

whiteSpace :: Parser ()
whiteSpace = L.space hspace1 empty empty

lexeme :: Parser a -> Parser a
lexeme = L.lexeme whiteSpace

symbol :: String -> Parser String
symbol = L.symbol whiteSpace

lparen :: Parser String
lparen = symbol "("

rparen :: Parser String
rparen = symbol ")"

comma :: Parser String
comma = symbol ","

several :: Parser a -> Parser [a]
several item = choice [try (between (symbol "[") (symbol "]") (item `sepBy1` comma)), pure <$> item]

number :: Parser Y.Number
number =
  choice
    [ do
        _ <- symbol "length" >> lparen
        bd <- _binding phiParser
        _ <- rparen
        return (Y.Length bd)
    , do
        _ <- symbol "domain" >> lparen
        bd <- _binding phiParser
        _ <- rparen
        return (Y.Domain bd)
    , either Y.AnyIndex Y.MetaIndex <$> _index phiParser
    , do
        sign <- optional (choice [char '-', char '+'])
        unsigned <- lexeme L.decimal
        let signed = if sign == Just '-' then negate unsigned else unsigned :: Integer
        if signed < toInteger (minBound :: Int) || signed > toInteger (maxBound :: Int)
          then fail (printf "the literal %d does not fit into Int" signed)
          else return (Y.Literal (fromInteger signed))
    ]

comparable :: Parser Y.Comparable
comparable =
  choice
    [ try $ Y.CmpNum <$> number
    , try $ Y.CmpAttr <$> _attribute phiParser
    , Y.CmpExpr <$> _expression phiParser
    ]

condition :: Parser Y.Condition
condition =
  choice
    [ do
        _ <- symbol "and" >> lparen
        args <- condition `sepBy1` comma
        _ <- rparen
        return (Y.And args)
    , do
        _ <- symbol "or" >> lparen
        args <- condition `sepBy1` comma
        _ <- rparen
        return (Y.Or args)
    , do
        _ <- symbol "in" >> lparen
        attrs <- several (choice [AtLambda <$ symbol "λ", AtDelta <$ symbol "Δ", _attribute phiParser])
        _ <- comma
        bds <- several (_binding phiParser)
        _ <- rparen
        return (Y.In attrs bds)
    , do
        _ <- symbol "not" >> lparen
        cond <- condition
        _ <- rparen
        return (Y.Not cond)
    , do
        _ <- symbol "eq" >> lparen
        left <- comparable
        _ <- comma
        right <- comparable
        _ <- rparen
        return (Y.Eq left right)
    , do
        _ <- symbol "gt" >> lparen
        left <- comparable
        _ <- comma
        right <- comparable
        _ <- rparen
        return (Y.Gt left right)
    , do
        _ <- symbol "nf" >> lparen
        expr <- _expression phiParser
        _ <- rparen
        return (Y.NF expr)
    , do
        _ <- symbol "absolute" >> lparen
        expr <- _expression phiParser
        _ <- rparen
        return (Y.Absolute expr)
    , do
        _ <- symbol "matches" >> lparen
        ptn <- _string phiParser
        _ <- comma
        expr <- _expression phiParser
        _ <- rparen
        return (Y.Matches ptn expr)
    , do
        _ <- symbol "part-of" >> lparen
        expr <- _expression phiParser
        _ <- comma
        bd <- _binding phiParser
        _ <- rparen
        return (Y.PartOf expr bd)
    , do
        _ <- symbol "formation" >> lparen
        expr <- _expression phiParser
        _ <- rparen
        return (Y.IsFormation expr)
    ]

parseCondition :: String -> Either String Y.Condition
parseCondition input = do
  let parsed =
        runParser
          ( do
              _ <- whiteSpace
              p <- condition
              _ <- eof
              return p
          )
          "condition"
          input
  case parsed of
    Right parsed' -> Right parsed'
    Left err -> Left (errorBundlePretty err)

parseConditionThrows :: String -> IO Y.Condition
parseConditionThrows cnd = orThrow CouldNotParseCondition (parseCondition cnd)