packages feed

phino-0.0.162: src/Yaml.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-orphans #-}

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

module Yaml where

import AST
import Control.Applicative (asum, (<|>))
import Control.Exception (throwIO)
import Data.Aeson
import qualified Data.Aeson.Key as Key
import qualified Data.Aeson.KeyMap as KeyMap
import Data.Aeson.Types (formatRelativePath)
import qualified Data.ByteString as BS
import Data.Conduit ((.|))
import qualified Data.Conduit.List as CL
import Data.FileEmbed (embedDir)
import Data.Maybe (fromMaybe)
import Data.Scientific (isInteger)
import Data.Text (Text, unpack)
import qualified Data.Text as T
import Data.Yaml (Parser)
import qualified Data.Yaml as Yaml
import Data.Yaml.Internal (Warning (DuplicateKey), decodeHelper_)
import GHC.Generics (Generic)
import Metas
import Parser
import Slots
import qualified Text.Libyaml as Libyaml
import Text.Printf (printf)
import Text.Regex.PCRE ((=~))

shiftYamlError :: String -> String
shiftYamlError msg =
  case (msg =~ ("^YAML parse exception at line ([0-9]+), column ([0-9]+)([:,])" :: String)) :: (String, String, String, [String]) of
    (before, _, after, [ln, col, sep]) ->
      printf "%sYAML parse exception at line %d, column %d%s%s" before (read ln + 1 :: Int) (read col + 1 :: Int) sep after
    _ -> msg

validateYamlObject :: (MonadFail a) => Object -> [String] -> a ()
validateYamlObject v keys
  | length current > 1 = fail ("Exactly one condition type is expected, when multiple condition types specified: " ++ show current)
  | null present = fail (printf "Unknown condition type '%s', expected one of: %s" (show current) (show keys))
  | otherwise = pure ()
  where
    present :: [Key.Key]
    present = filter (`KeyMap.member` v) (map Key.fromString keys)
    current :: [Key.Key]
    current = KeyMap.keys v

validateKnownYamlKeys :: (MonadFail a) => Object -> [String] -> a ()
validateKnownYamlKeys v keys =
  let unknown = filter (\key -> key `notElem` map Key.fromString keys) (KeyMap.keys v)
   in case unknown of
        [] -> pure ()
        key : _ -> fail (printf "Unknown key '%s', expected one of: %s" (show key) (show keys))

parseJSON' :: String -> (String -> Either String a) -> Value -> Parser a
parseJSON' nm func =
  withText
    nm
    ( \txt -> case func (unpack txt) of
        Left err -> fail err
        Right parsed -> pure parsed
    )

instance FromJSON Attribute where
  parseJSON =
    withText
      "Attribute"
      ( \txt -> case unpack txt of
          "λ" -> pure AtLambda
          "Δ" -> pure AtDelta
          other -> case parseAttribute other of
            Left err -> fail err
            Right attr -> pure attr
      )

instance FromJSON Alpha where
  parseJSON = parseJSON' "Alpha" parseAlpha

instance FromJSON Bytes where
  parseJSON = parseJSON' "Bytes" parseBytes

instance FromJSON Expression where
  parseJSON = parseJSON' "Expression" parseExpression

instance FromJSON Binding where
  parseJSON = parseJSON' "Binding" parseBinding

instance FromJSON Number where
  parseJSON v = case v of
    Object o -> do
      validateYamlObject o ["length", "domain"]
      asum
        [ Length <$> o .: "length"
        , Domain <$> o .: "domain"
        ]
    Number num
      | toRational (round num :: Integer) /= toRational num -> fail (printf "Expected an integer, got a fractional number %s" (show num))
      | (round num :: Integer) < toInteger (minBound :: Int) || (round num :: Integer) > toInteger (maxBound :: Int) -> fail (printf "The literal %s does not fit into Int" (show num))
      | otherwise -> pure (Literal (round num))
    String txt -> case parseIndex (unpack txt) of
      Right (Right mt) -> pure (MetaIndex mt)
      Right (Left slot) -> pure (AnyIndex slot)
      Left err -> fail err
    _ ->
      fail "Expected a numerable expression (object, number or index meta)"

instance FromJSON Comparable where
  parseJSON (Number num)
    | isInteger num && ((round num :: Integer) < toInteger (minBound :: Int) || (round num :: Integer) > toInteger (maxBound :: Int)) =
        fail (printf "The literal %s does not fit into Int" (show num))
  parseJSON v@(Number _) = CmpNum <$> parseJSON v
  parseJSON v@(Object _) = CmpNum <$> parseJSON v
  parseJSON v@(String _) =
    foldr1
      (<|>)
      [ CmpAttr <$> parseJSON v
      , CmpNum <$> parseJSON v
      , CmpExpr <$> parseJSON v
      ]
  parseJSON _ = fail "An argument of 'eq' or 'gt' is a number, an attribute, an expression, or an object of 'length' or 'domain'"

instance FromJSON Condition where
  parseJSON = withObject "Condition" $ \v -> do
    cond <- parseCondition v
    cond <$ either fail pure (wellCompared cond)

parseCondition :: Object -> Parser Condition
parseCondition v = do
  validateYamlObject v ["and", "or", "not", "nf", "absolute", "eq", "gt", "in", "matches", "part-of", "disjoint", "formation"]
  case KeyMap.keys v of
    [key] -> case Key.toString key of
      "and" -> do
        conds <- v .: "and"
        if null conds
          then fail "The 'and' condition requires at least one element"
          else pure (And conds)
      "or" -> do
        conds <- v .: "or"
        if null conds
          then fail "The 'or' condition requires at least one element"
          else pure (Or conds)
      "not" -> Not <$> v .: "not"
      "nf" -> NF <$> v .: "nf"
      "absolute" -> Absolute <$> v .: "absolute"
      "formation" -> IsFormation <$> v .: "formation"
      "disjoint" -> do
        vals <- v .: "disjoint"
        case vals of
          [attrs_, bds_] -> Disjoint <$> parseJSON attrs_ <*> parseJSON bds_
          _ -> fail "'disjoint' expects exactly two arguments"
      "eq" -> do
        vals <- v .: "eq"
        case vals of
          [left_, right_] -> Eq <$> parseJSON left_ <*> parseJSON right_
          _ -> fail "'eq' expects exactly two arguments"
      "gt" -> do
        vals <- v .: "gt"
        case vals of
          [left_, right_] -> Gt <$> parseJSON left_ <*> parseJSON right_
          _ -> fail "'gt' expects exactly two arguments"
      "in" -> do
        vals <- v .: "in"
        case vals of
          [attrs_, bds_] -> In <$> several attrs_ <*> several bds_
          _ -> fail "'in' expects exactly two arguments"
      "matches" -> do
        vals <- v .: "matches"
        case vals of
          [pat, ex] -> Matches <$> parseJSON pat <*> parseJSON ex
          _ -> fail "'matches' expects exactly two arguments"
      "part-of" -> do
        vals <- v .: "part-of"
        case vals of
          [ex, bd] -> PartOf <$> parseJSON ex <*> parseJSON bd
          _ -> fail "'part-of' expects exactly two arguments"
      _ -> fail "Unknown condition type"
    _ -> fail "Exactly one condition type is expected"
  where
    several :: FromJSON a => Value -> Parser [a]
    several val = parseJSON val <|> (pure <$> parseJSON val)

kindOf :: Comparable -> String
kindOf (CmpNum _) = "a number"
kindOf (CmpAttr _) = "an attribute"
kindOf (CmpExpr _) = "an expression"

wellCompared :: Condition -> Either String ()
wellCompared = \case
  And conds -> mapM_ wellCompared conds
  Or conds -> mapM_ wellCompared conds
  Not cond -> wellCompared cond
  Eq left right
    | kindOf left == kindOf right -> Right ()
    | otherwise -> mismatch "eq" "two numbers, two attributes or two expressions" left right
  Gt left right
    | kindOf left == "a number" && kindOf right == "a number" -> Right ()
    | otherwise -> mismatch "gt" "two numbers" left right
  _ -> Right ()
  where
    mismatch :: String -> String -> Comparable -> Comparable -> Either String ()
    mismatch verb allowed left right =
      Left (printf "The '%s' condition compares %s with %s, while it can compare only %s" verb (kindOf left) (kindOf right) allowed)

instance FromJSON ExtraArgument where
  parseJSON v@(String _) =
    foldr1
      (<|>)
      [ ArgAttribute <$> parseJSON v
      , ArgBinding <$> parseJSON v
      , ArgBytes <$> parseJSON v
      , ArgExpression <$> parseJSON v
      ]
  parseJSON _ = fail "An argument of a 'where' function is a string holding an attribute, a binding, bytes or an expression"

instance FromJSON Extra where
  parseJSON =
    withObject
      "Extra"
      ( \o -> do
          validateKnownYamlKeys o ["meta", "function", "args"]
          Extra
            <$> o .: "meta"
            <*> o .: "function"
            <*> o .:? "args" .!= []
      )

instance FromJSON Rule where
  parseJSON value@(Object fields) = do
    validateKnownYamlKeys fields ["name", "label", "description", "pattern", "result", "e-match", "when", "having", "where"]
    rule <-
      genericParseJSON
        defaultOptions
          { fieldLabelModifier = \case
              "where_" -> "where"
              other -> other
          }
        value
    universeless rule.name value
    referenceless rule.name "result" rule.result
    referenceless rule.name "when" rule.when
    referenceless rule.name "where" rule.where_
    referenceless rule.name "having" rule.having
    targets rule (metas rule.pattern) (fromMaybe [] rule.where_)
    unread rule (named (metas rule.pattern)) "when" (metas rule.when)
    steps rule (named (metas rule.pattern)) (fromMaybe [] rule.where_)
    pure rule
    where
      targets :: Rule -> [Text] -> [Extra] -> Parser ()
      targets _ _ [] = pure ()
      targets rule known (extra : rest) = case extra.meta of
        ArgExpression (ExMeta bound) -> fresh rule known bound >> targets rule (bound : known) rest
        ArgAttribute (AtMeta bound) -> fresh rule known bound >> targets rule (bound : known) rest
        ArgBinding (BiMeta bound) -> fresh rule known bound >> targets rule (bound : known) rest
        ArgBytes (BtMeta bound) -> fresh rule known bound >> targets rule (bound : known) rest
        _ -> fail (printf "The rule '%s' has a 'where' step whose 'meta' is not a meta, while only a meta can take its result" rule.name)
      fresh :: Rule -> [Text] -> Text -> Parser ()
      fresh rule known bound
        | bound `elem` known = fail (printf "The rule '%s' has a 'where' step that binds the meta '%s' again, while it is already bound" rule.name (unpack bound))
        | otherwise = pure ()
      named :: [Text] -> [Text]
      named = filter ((> 1) . T.length)
      steps :: Rule -> [Text] -> [Extra] -> Parser ()
      steps rule known [] = do
        unread rule known "result" (metas rule.result)
        unread rule known "having" (metas rule.having)
      steps rule known (extra : rest) = do
        unread rule known "where" (metas extra.args)
        steps rule (known ++ named (metas extra.meta)) rest
      unread :: Rule -> [Text] -> String -> [Text] -> Parser ()
      unread rule known field used = case filter (`notElem` known) (named used) of
        [] -> pure ()
        missing : _ ->
          fail
            ( printf
                "The rule '%s' reads the meta '%s' it never binds, in '%s', since neither its pattern nor an earlier 'where' step binds it"
                rule.name
                (unpack missing)
                field
            )
  parseJSON value = genericParseJSON defaultOptions value

data Number
  = MetaIndex Text
  | AnyIndex Slot
  | Length Binding
  | Domain Binding
  | Literal Int
  deriving (Eq, Generic, Show)

data Comparable
  = CmpAttr Attribute
  | CmpNum Number
  | CmpExpr Expression
  deriving (Eq, Generic, Show)

data Condition
  = And [Condition]
  | Or [Condition]
  | In [Attribute] [Binding]
  | Not Condition
  | Eq Comparable Comparable
  | Gt Comparable Comparable
  | NF Expression
  | Absolute Expression
  | Matches String Expression
  | PartOf Expression Binding
  | Disjoint [Attribute] [Binding]
  | IsFormation Expression
  deriving (Eq, Generic, Show)

data ExtraArgument
  = ArgAttribute Attribute
  | ArgExpression Expression
  | ArgBinding Binding
  | ArgBytes Bytes
  deriving (Generic, Show)

data Extra = Extra
  { meta :: ExtraArgument
  , function :: String
  , args :: [ExtraArgument]
  }
  deriving (Generic, Show)

data Rule = Rule
  { name :: String
  , label :: Maybe String
  , description :: Maybe String
  , pattern :: Expression
  , result :: Expression
  , when :: Maybe Condition
  , where_ :: Maybe [Extra]
  , having :: Maybe Condition
  }
  deriving (Generic, Show)

instance Slots Condition where
  slots (And conds) = slots conds
  slots (Or conds) = slots conds
  slots (Not cond) = slots cond
  slots (In attrs bds) = slots attrs ++ slots bds
  slots (Eq left right) = slots left ++ slots right
  slots (Gt left right) = slots left ++ slots right
  slots (NF expr) = slots expr
  slots (Absolute expr) = slots expr
  slots (Matches _ expr) = slots expr
  slots (PartOf expr bd) = slots expr ++ slots bd
  slots (Disjoint attrs bds) = slots attrs ++ slots bds
  slots (IsFormation expr) = slots expr

instance Slots Comparable where
  slots (CmpAttr attr) = slots attr
  slots (CmpNum num) = slots num
  slots (CmpExpr expr) = slots expr

instance Slots Number where
  slots (AnyIndex slot) = [slot]
  slots (Length bd) = slots bd
  slots (Domain bd) = slots bd
  slots (MetaIndex _) = []
  slots (Literal _) = []

instance Slots ExtraArgument where
  slots (ArgAttribute attr) = slots attr
  slots (ArgExpression expr) = slots expr
  slots (ArgBinding bd) = slots bd
  slots (ArgBytes bts) = slots bts

instance Slots Extra where
  slots extra = slots extra.meta ++ slots extra.args

instance Slots Premise where
  slots premise = slots premise.operation

instance Slots Operation where
  slots (OpMorph expr universe) = slots expr ++ slots universe
  slots (OpNormalize expr) = slots expr
  slots (OpEvaluate expr universe) = slots expr ++ slots universe
  slots (OpContextualize expr context) = slots expr ++ slots context
  slots (OpDataize expr universe) = slots expr ++ slots universe

instance Metas Condition where
  metas (And conds) = metas conds
  metas (Or conds) = metas conds
  metas (Not cond) = metas cond
  metas (In attrs bds) = metas attrs ++ metas bds
  metas (Eq left right) = metas left ++ metas right
  metas (Gt left right) = metas left ++ metas right
  metas (NF expr) = metas expr
  metas (Absolute expr) = metas expr
  metas (Matches _ expr) = metas expr
  metas (PartOf expr bd) = metas expr ++ metas bd
  metas (Disjoint attrs bds) = metas attrs ++ metas bds
  metas (IsFormation expr) = metas expr
  bare names (And conds) = And (bare names conds)
  bare names (Or conds) = Or (bare names conds)
  bare names (Not cond) = Not (bare names cond)
  bare names (In attrs bds) = In (bare names attrs) (bare names bds)
  bare names (Eq left right) = Eq (bare names left) (bare names right)
  bare names (Gt left right) = Gt (bare names left) (bare names right)
  bare names (NF expr) = NF (bare names expr)
  bare names (Absolute expr) = Absolute (bare names expr)
  bare names (Matches regex expr) = Matches regex (bare names expr)
  bare names (PartOf expr bd) = PartOf (bare names expr) (bare names bd)
  bare names (Disjoint attrs bds) = Disjoint (bare names attrs) (bare names bds)
  bare names (IsFormation expr) = IsFormation (bare names expr)

instance Metas Comparable where
  metas (CmpAttr attr) = metas attr
  metas (CmpNum num) = metas num
  metas (CmpExpr expr) = metas expr
  bare names (CmpAttr attr) = CmpAttr (bare names attr)
  bare names (CmpNum num) = CmpNum (bare names num)
  bare names (CmpExpr expr) = CmpExpr (bare names expr)

instance Metas Number where
  metas (MetaIndex named) = metas named
  metas (AnyIndex slot) = metas slot
  metas (Length bd) = metas bd
  metas (Domain bd) = metas bd
  metas (Literal _) = []
  bare names (MetaIndex named) = MetaIndex (bare names named)
  bare names (Length bd) = Length (bare names bd)
  bare names (Domain bd) = Domain (bare names bd)
  bare _ num = num

instance Metas ExtraArgument where
  metas (ArgAttribute attr) = metas attr
  metas (ArgExpression expr) = metas expr
  metas (ArgBinding bd) = metas bd
  metas (ArgBytes bts) = metas bts
  bare names (ArgAttribute attr) = ArgAttribute (bare names attr)
  bare names (ArgExpression expr) = ArgExpression (bare names expr)
  bare names (ArgBinding bd) = ArgBinding (bare names bd)
  bare names (ArgBytes bts) = ArgBytes (bare names bts)

instance Metas Extra where
  metas extra = metas extra.meta ++ metas extra.args
  bare names extra = extra{meta = bare names extra.meta, args = bare names extra.args}

instance Metas Premise where
  metas premise = metas premise.result ++ metas premise.operation
  bare names premise = premise{result = bare names premise.result, operation = bare names premise.operation}

instance Metas Operation where
  metas (OpMorph expr universe) = metas expr ++ metas universe
  metas (OpNormalize expr) = metas expr
  metas (OpEvaluate expr universe) = metas expr ++ metas universe
  metas (OpContextualize expr context) = metas expr ++ metas context
  metas (OpDataize expr universe) = metas expr ++ metas universe
  bare names (OpMorph expr universe) = OpMorph (bare names expr) (bare names universe)
  bare names (OpNormalize expr) = OpNormalize (bare names expr)
  bare names (OpEvaluate expr universe) = OpEvaluate (bare names expr) (bare names universe)
  bare names (OpContextualize expr context) = OpContextualize (bare names expr) (bare names context)
  bare names (OpDataize expr universe) = OpDataize (bare names expr) (bare names universe)

instance Metas Rule where
  metas rule = metas rule.pattern ++ metas rule.result ++ metas rule.when ++ metas rule.having ++ metas rule.where_
  bare names rule =
    rule
      { pattern = bare names rule.pattern
      , result = bare names rule.result
      , when = bare names rule.when
      , having = bare names rule.having
      , where_ = bare names rule.where_
      }

instance Metas MorphRule where
  metas rule = metas rule.match ++ metas rule.ematch ++ metas rule.nresult ++ metas rule.when ++ metas rule.premises
  bare names rule =
    rule
      { match = bare names rule.match
      , ematch = bare names rule.ematch
      , nresult = bare names rule.nresult
      , when = bare names rule.when
      , premises = bare names rule.premises
      }

instance Metas DataizeRule where
  metas rule = metas rule.match ++ metas rule.ematch ++ metas rule.dresult ++ metas rule.when ++ metas rule.premises
  bare names rule =
    rule
      { match = bare names rule.match
      , ematch = bare names rule.ematch
      , dresult = bare names rule.dresult
      , when = bare names rule.when
      , premises = bare names rule.premises
      }

instance Metas ContextualizeRule where
  metas rule = metas rule.match ++ metas rule.cmatch ++ metas rule.cresult ++ metas rule.premises
  bare names rule =
    rule
      { match = bare names rule.match
      , cmatch = bare names rule.cmatch
      , cresult = bare names rule.cresult
      , premises = bare names rule.premises
      }

universeless :: String -> Value -> Parser ()
universeless rule (Object fields)
  | KeyMap.member "e-match" fields = fail (printf "The rule '%s' carries an 'e-match', which only a morphing or a dataization rule may" rule)
universeless _ _ = pure ()

referenceless :: (MonadFail m, Slots a) => String -> String -> a -> m ()
referenceless rule field term = case anonymous term of
  Nothing -> pure ()
  Just kind ->
    fail
      ( printf
          "anonymous meta '!%s' cannot be referenced in '%s' of rule '%s'"
          (unpack kind)
          field
          rule
      )

decodeRule :: (FromJSON a) => (FilePath, BS.ByteString) -> a
decodeRule (path, bs) = case Yaml.decodeEither' bs of
  Right rule -> rule
  Left err -> error $ "YAML parse error in " ++ path ++ ": " ++ show err

normalizationRules :: [Rule]
{-# NOINLINE normalizationRules #-}
normalizationRules = map decodeRule $(embedDir "resources/normalize")

yamlRule :: FilePath -> IO Rule
yamlRule path = do
  decoded <- decodeHelper_ (Libyaml.decodeFile path .| CL.map worded)
  case decoded of
    Left err -> throwIO err
    Right (warnings, rule) -> rule <$ mapM_ reportDuplicateKey warnings
  where
    worded :: Libyaml.Event -> Libyaml.Event
    worded (Libyaml.EventScalar value Libyaml.NoTag style anchor)
      | value `elem` spelled = Libyaml.EventScalar value Libyaml.StrTag style anchor
    worded event = event
    spelled :: [BS.ByteString]
    spelled = ["y", "Y", "yes", "Yes", "YES", "n", "N", "no", "No", "NO", "on", "On", "ON", "off", "Off", "OFF"]
    reportDuplicateKey :: Warning -> IO ()
    reportDuplicateKey (DuplicateKey jsonPath) =
      fail (printf "The key '%s' is repeated in the rule file '%s', only the last value is kept" (formatRelativePath jsonPath) path)

data Premise = Premise
  { result :: Text
  , operation :: Operation
  }
  deriving (Eq, Generic, Show)

data Operation
  = OpMorph Expression Expression
  | OpNormalize Expression
  | OpEvaluate Expression Expression
  | OpContextualize Expression Expression
  | OpDataize Expression Expression
  deriving (Eq, Generic, Show)

data MorphRule = MorphRule
  { name :: String
  , label :: Maybe String
  , match :: Expression
  , ematch :: Expression
  , nresult :: Expression
  , when :: Maybe Condition
  , premises :: [Premise]
  }
  deriving (Generic, Show)

data DataizeRule = DataizeRule
  { name :: String
  , label :: Maybe String
  , match :: Expression
  , ematch :: Expression
  , dresult :: Bytes
  , when :: Maybe Condition
  , premises :: [Premise]
  }
  deriving (Generic, Show)

data ContextualizeRule = ContextualizeRule
  { name :: String
  , label :: Maybe String
  , match :: Expression
  , cmatch :: Expression
  , cresult :: Expression
  , premises :: [Premise]
  }
  deriving (Generic, Show)

instance FromJSON Premise where
  parseJSON =
    withObject
      "Premise"
      (\o -> Premise <$> premiseResult o <*> premiseOperation o)

premiseResult :: Object -> Parser Text
premiseResult o = do
  expr <- o .:? "n-result"
  case expr of
    Just (ExMeta metaName) -> pure metaName
    Just (ExAny _) -> fail "an anonymous 'n-result' meta cannot be referenced"
    Just _ -> fail "'n-result' must be an expression meta"
    Nothing -> do
      bytes <- o .:? "d-result"
      case bytes of
        Just (BtMeta metaName) -> pure metaName
        Just (BtAny _) -> fail "an anonymous 'd-result' meta cannot be referenced"
        Just _ -> fail "'d-result' must be a bytes meta"
        Nothing -> fail "a premise needs an 'n-result' or 'd-result' meta"

premiseOperation :: Object -> Parser Operation
premiseOperation o =
  asum
    [ binary "morph" OpMorph
    , OpNormalize <$> o .: "normalize"
    , binary "evaluate" OpEvaluate
    , binary "contextualize" OpContextualize
    , binary "dataize" OpDataize
    ]
  where
    binary :: Key -> (Expression -> Expression -> Operation) -> Parser Operation
    binary key verb = do
      vals <- o .: key
      case vals of
        [expr, second] -> verb <$> parseJSON expr <*> parseJSON second
        _ -> fail (printf "'%s' expects exactly two arguments" (Key.toString key))

parseLabel :: String -> Object -> Parser (Maybe String)
parseLabel ruleName o = do
  label' <- o .:? "label"
  if label' == Just ruleName
    then fail $ "'label' is redundant when it equals 'name' (" ++ ruleName ++ "); drop it"
    else pure label'

instance FromJSON MorphRule where
  parseJSON =
    withObject
      "MorphRule"
      ( \o -> do
          ruleName <- o .: "name"
          rule <-
            MorphRule ruleName
              <$> parseLabel ruleName o
              <*> o .: "match"
              <*> o .: "universe"
              <*> o .: "conclusion"
              <*> o .:? "when"
              <*> o .:? "premises" .!= []
          referenceless ruleName "conclusion" rule.nresult
          referenceless ruleName "when" rule.when
          referenceless ruleName "premises" rule.premises
          pure rule
      )

instance FromJSON DataizeRule where
  parseJSON =
    withObject
      "DataizeRule"
      ( \o -> do
          ruleName <- o .: "name"
          rule <-
            DataizeRule ruleName
              <$> parseLabel ruleName o
              <*> o .: "match"
              <*> o .: "universe"
              <*> o .: "conclusion"
              <*> o .:? "when"
              <*> o .:? "premises" .!= []
          referenceless ruleName "conclusion" rule.dresult
          referenceless ruleName "when" rule.when
          referenceless ruleName "premises" rule.premises
          pure rule
      )

instance FromJSON ContextualizeRule where
  parseJSON =
    withObject
      "ContextualizeRule"
      ( \o -> do
          ruleName <- o .: "name"
          rule <-
            ContextualizeRule ruleName
              <$> parseLabel ruleName o
              <*> o .: "match"
              <*> o .: "c-match"
              <*> o .: "c-result"
              <*> o .:? "premises" .!= []
          referenceless ruleName "c-result" rule.cresult
          referenceless ruleName "premises" rule.premises
          pure rule
      )

morphingRules :: [MorphRule]
{-# NOINLINE morphingRules #-}
morphingRules = map decodeRule $(embedDir "resources/morphing")

dataizationRules :: [DataizeRule]
{-# NOINLINE dataizationRules #-}
dataizationRules = map decodeRule $(embedDir "resources/dataization")

contextualizationRules :: [ContextualizeRule]
{-# NOINLINE contextualizationRules #-}
contextualizationRules = map decodeRule $(embedDir "resources/contextualization")