packages feed

eo-phi-normalizer-0.4.0: src/Language/EO/Phi/Rules/Fast.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module Language.EO.Phi.Rules.Fast where

-- import Debug.Trace (trace)

import Data.List.NonEmpty qualified as NonEmpty
import Language.EO.Phi.Rules.Common
import Language.EO.Phi.Rules.Yaml qualified as Yaml
import Language.EO.Phi.Syntax (printTree)
import Language.EO.Phi.Syntax.Abs
import System.IO.Unsafe (unsafePerformIO)

-- $setup
-- >>> :set -XOverloadedStrings

runWithYegorRules :: (Context -> Object -> Object) -> Object -> IO ()
runWithYegorRules f obj = putStrLn (printTree (f (defaultContext yegorRules obj) obj))

yegorRuleSet :: Yaml.RuleSet
{-# NOINLINE yegorRuleSet #-}
yegorRuleSet =
  unsafePerformIO $
    Yaml.parseRuleSetFromFile "eo-phi-normalizer/test/eo/phi/rules/yegor.yaml"

yegorRules :: [NamedRule]
yegorRules = map Yaml.convertRuleNamed (Yaml.rules yegorRuleSet)

withBinding :: (Context -> Object -> Object) -> Context -> Binding -> Binding
withBinding f ctx = \case
  AlphaBinding Rho obj -> AlphaBinding Rho obj -- do not apply f inside ρ-bindings
  AlphaBinding a obj -> AlphaBinding a (f ctx{currentAttr = a} obj)
  binding -> binding

isLambdaBinding :: Binding -> Bool
isLambdaBinding LambdaBinding{} = True
isLambdaBinding _ = False

withSubObjects :: (Context -> Object -> Object) -> Context -> Object -> Object
withSubObjects f ctx = go
 where
  go = \case
    root@(Formation bindings)
      | not (any isEmptyBinding bindings) && not (any isLambdaBinding bindings) ->
          let extendedContext = (extendContextWith root ctx){insideFormation = True}
           in Formation
                [ withBinding f extendedContext binding
                | binding <- bindings
                ]
    Application obj bindings ->
      Application
        (f ctx obj)
        [ withBinding f ctx binding
        | binding <- bindings
        ]
    ObjectDispatch obj a -> ObjectDispatch (f ctx obj) a
    obj -> obj

-- | Normalize an object, following a version of call-by-value strategy:
--
-- 1. Apply rules in subobjects/subterms before applying a rule at root.
-- 2. Do not apply rules under formations with at least one void (empty) binding.
--
-- > runWithYegorRules applyRulesInsideOut "⟦ x ↦ ⟦⟧, y ↦ ⟦ z ↦ ⟦ w ↦ ξ.ρ.ρ.x ⟧ ⟧ ⟧.y.z.w"
-- ⟦ ρ ↦ ⟦ ρ ↦ ⟦ ⟧ ⟧ ⟧
applyRulesInsideOut :: Context -> Object -> Object
applyRulesInsideOut ctx obj = do
  let obj' = withSubObjects applyRulesInsideOut ctx obj
  case applyOneRuleAtRoot ctx obj' of
    [] ->
      -- trace ("No rule can be applied to object\n   " <> printTree obj') $
      obj'
    (_ruleName, obj'') : _ ->
      -- trace (ruleName <> ": \n   " <> printTree obj' <> "\n → " <> printTree obj'') $
      applyRulesInsideOut ctx obj''

fastYegorInsideOutAsRule :: NamedRule
fastYegorInsideOutAsRule = ("Yegor's rules (hardcoded)", \ctx obj -> [fastYegorInsideOut ctx obj])

fastYegorInsideOut :: Context -> Object -> Object
fastYegorInsideOut ctx = \case
  root | insideSubObject ctx -> root -- this rule is only applied at root
  root@GlobalObject
    | not (insideFormation ctx) ->
        NonEmpty.last (outerFormations ctx)
    | otherwise -> root
  root@ThisObject
    | not (insideFormation ctx) ->
        NonEmpty.head (outerFormations ctx)
    | otherwise -> root
  ObjectDispatch obj a ->
    case fastYegorInsideOut ctx obj of
      this@(Formation bindings) ->
        case lookupBinding a bindings of
          Just objA -> fastYegorInsideOut ctx (Yaml.substThis this objA)
          Nothing ->
            case lookupBinding Phi bindings of
              Just objPhi -> fastYegorInsideOut ctx (ObjectDispatch (Yaml.substThis this objPhi) a)
              Nothing -> ObjectDispatch this a
      this -> ObjectDispatch this a
  Application obj argBindings ->
    case fastYegorInsideOut ctx obj of
      obj'@(Formation bindings) ->
        case argBindings of
          [AlphaBinding (Alpha "α0") arg0, AlphaBinding (Alpha "α1") arg1] ->
            case filter isEmptyBinding bindings of
              EmptyBinding a0 : EmptyBinding a1 : _ -> do
                let arg0' = fastYegorInsideOut ctx arg0
                let arg1' = fastYegorInsideOut ctx arg1
                Formation
                  ( AlphaBinding a0 arg0'
                      : AlphaBinding a1 arg1'
                      : [ binding
                        | binding <- bindings
                        , case binding of
                            EmptyBinding x | x `elem` [a0, a1] -> False
                            _ -> True
                        ]
                  )
              _ -> Application obj' argBindings
          [AlphaBinding (Alpha "α0") arg0] ->
            case filter isEmptyBinding bindings of
              EmptyBinding a0 : _ -> do
                let arg0' = fastYegorInsideOut ctx arg0
                Formation
                  ( AlphaBinding a0 arg0'
                      : [ binding
                        | binding <- bindings
                        , case binding of
                            EmptyBinding x | x == a0 -> False
                            _ -> True
                        ]
                  )
              _ -> Application obj' argBindings
          [AlphaBinding a argA] -> do
            let argA' = fastYegorInsideOut ctx argA
            Formation
              ( AlphaBinding a argA'
                  : [ binding
                    | binding <- bindings
                    , case binding of
                        EmptyBinding x | x == a -> False
                        _ -> True
                    ]
              )
          [DeltaBinding bytes] -> do
            Formation
              ( DeltaBinding bytes
                  : [ binding
                    | binding <- bindings
                    , case binding of
                        DeltaEmptyBinding -> False
                        _ -> True
                    ]
              )
          _ -> Application obj' argBindings
      obj' -> Application obj' argBindings
  root@(Formation bindings)
    | any isEmptyBinding bindings || any isLambdaBinding bindings -> root
    | otherwise ->
        Formation
          [ binding'
          | binding <- bindings
          , let binding' =
                  case binding of
                    AlphaBinding Rho _ -> binding
                    AlphaBinding a objA -> do
                      let ctx' = (extendContextWith root ctx){insideFormation = True, currentAttr = a}
                      AlphaBinding a (fastYegorInsideOut ctx' objA)
                    _ -> binding
          ]
  Termination -> Termination
  MetaSubstThis{} -> error "impossible MetaSubstThis!"
  MetaObject{} -> error "impossible MetaObject!"
  MetaFunction{} -> error "impossible MetaFunction!"