packages feed

phino-0.0.114: test/CanonizerSpec.hs

{-# LANGUAGE OverloadedStrings #-}

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

module CanonizerSpec where

import AST
import Canonizer (canonize, canonizeExpr)
import Control.Monad (forM_)
import Test.Hspec (Spec, describe, it, shouldBe)

spec :: Spec
spec = do
  describe "canonizeExpr" $ do
    let nestedInput :: Expression
        nestedInput =
          ExFormation
            [ BiLambda (Function "First")
            , BiTau
                (AtLabel "child")
                (ExFormation [BiLambda (Function "Second")])
            , BiTau
                (AtLabel "app")
                ( ExApplication
                    (ExFormation [BiLambda (Function "Third")])
                    (ArTau (AtLabel "y") ExRoot)
                )
            ]
        nestedExpected :: Expression
        nestedExpected =
          ExFormation
            [ BiLambda (Function "Fn1")
            , BiTau
                (AtLabel "child")
                (ExFormation [BiLambda (Function "Fn2")])
            , BiTau
                (AtLabel "app")
                ( ExApplication
                    (ExFormation [BiLambda (Function "Fn3")])
                    (ArTau (AtLabel "y") ExRoot)
                )
            ]
        unchanged :: Expression
        unchanged = ExFormation [BiTau (AtLabel "x") ExRoot, BiVoid AtRho]
        metaLambda :: Expression
        metaLambda = ExFormation [BiLambda (FnMeta "F")]
    forM_
      [ ("leaves an expression with no lambdas unchanged", unchanged, unchanged)
      ,
        ( "renames a single top level lambda to Fn1"
        , ExFormation [BiLambda (Function "Foo")]
        , ExFormation [BiLambda (Function "Fn1")]
        )
      , ("leaves a meta lambda binding untouched", metaLambda, metaLambda)
      , ("numbers several lambdas at different nesting depths in document order", nestedInput, nestedExpected)
      ,
        ( "recurses through ExDispatch"
        , ExDispatch (ExFormation [BiLambda (Function "Wrapped")]) (AtLabel "attr")
        , ExDispatch (ExFormation [BiLambda (Function "Fn1")]) (AtLabel "attr")
        )
      ,
        ( "recurses through ExApplication's alpha argument"
        , ExApplication ExRoot (ArAlpha (Alpha 0) (ExFormation [BiLambda (Function "Wrapped")]))
        , ExApplication ExRoot (ArAlpha (Alpha 0) (ExFormation [BiLambda (Function "Fn1")]))
        )
      ,
        ( "recurses through ExPhiMeet"
        , ExPhiMeet (Just "p") 1 (ExFormation [BiLambda (Function "Meet")])
        , ExPhiMeet (Just "p") 1 (ExFormation [BiLambda (Function "Fn1")])
        )
      ,
        ( "recurses through ExPhiAgain"
        , ExPhiAgain Nothing 2 (ExFormation [BiLambda (Function "Again")])
        , ExPhiAgain Nothing 2 (ExFormation [BiLambda (Function "Fn1")])
        )
      ]
      (\(desc, input, expected) -> it desc (canonizeExpr input `shouldBe` expected))

  describe "canonize" $ do
    it "returns an empty list for an empty input" $
      canonize [] `shouldBe` []

    it "restarts the counter independently for each Rewritten in the chain" $ do
      let first = ExFormation [BiLambda (Function "X")]
          second = ExFormation [BiLambda (Function "Y")]
          expected = ExFormation [BiLambda (Function "Fn1")]
      canonize [(first, Just "rule-1"), (second, Just "rule-2")]
        `shouldBe` [(expected, Just "rule-1"), (expected, Just "rule-2")]

    it "preserves the rule tag alongside the canonized expression" $
      canonize [(ExFormation [BiLambda (Function "Foo")], Nothing)]
        `shouldBe` [(ExFormation [BiLambda (Function "Fn1")], Nothing)]