phino-0.0.78: test/CLISpec.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
-- SPDX-License-Identifier: MIT
module CLISpec (spec) where
import CLI (runCLI)
import Control.Exception
import Control.Monad (forM_, unless, when)
import Data.List (intercalate, isInfixOf)
import Data.Time.Clock (addUTCTime, getCurrentTime)
import Data.Time.Clock.POSIX (getPOSIXTime)
import Data.Version (showVersion)
import GHC.IO.Handle
import Paths_phino (version)
import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getTemporaryDirectory, listDirectory, removeDirectoryRecursive, removeFile, setModificationTime)
import System.Exit (ExitCode (ExitFailure))
import System.FilePath ((</>))
import System.IO
import Test.Hspec
import Text.Printf (printf)
withStdin :: String -> IO a -> IO a
withStdin input action =
bracket (openTempFile "." "stdinXXXXXX.tmp") cleanup $ \(filePath, h) -> do
hSetEncoding h utf8
hPutStr h input
hFlush h
hClose h
withFile filePath ReadMode $ \hIn -> do
hSetEncoding hIn utf8
bracket (hDuplicate stdin) restoreStdin $ \_ -> do
hDuplicateTo hIn stdin
hSetEncoding stdin utf8
action
where
restoreStdin orig = hDuplicateTo orig stdin >> hClose orig
cleanup (fp, _) = removeFile fp
withStdout :: IO a -> IO (String, a)
withStdout action =
bracket
(openTempFile "." "stdoutXXXXXX.tmp")
cleanup
( \(path, hTmp) -> do
hSetEncoding hTmp utf8
oldOut <- hDuplicate stdout
oldErr <- hDuplicate stderr
hDuplicateTo hTmp stdout
hDuplicateTo hTmp stderr
result <-
action `finally` do
hFlush stdout
hFlush stderr
hDuplicateTo oldOut stdout >> hClose oldOut
hDuplicateTo oldErr stderr >> hClose oldErr
hClose hTmp
captured <- readFile path
_ <- evaluate (length captured)
return (captured, result)
)
where
cleanup (fp, _) = removeFile fp
withTempFile :: String -> ((FilePath, Handle) -> IO a) -> IO a
withTempFile pattern =
bracket
(openTempFile "." pattern)
(\(path, _) -> removeFile path)
withTempFileContent :: String -> String -> (FilePath -> IO a) -> IO a
withTempFileContent pattern content action =
withTempFile pattern $ \(path, h) -> do
hPutStr h content
hClose h
action path
testCLI' :: [String] -> [String] -> Either ExitCode () -> Expectation
testCLI' args outputs exit = do
(out, result) <- withStdout (try (runCLI args) :: IO (Either ExitCode ()))
if null outputs
then
unless (null out) $
expectationFailure ("Expected that output is empty, but got:\n" ++ out)
else
forM_
outputs
( \output ->
unless (output `isInfixOf` out) $
expectationFailure
("Expected that output contains:\n" ++ output ++ "\nbut got:\n" ++ out)
)
result `shouldBe` exit
testCLISucceeded :: [String] -> [String] -> Expectation
testCLISucceeded args outputs = testCLI' args outputs (Right ())
testCLIFailed :: [String] -> [String] -> Expectation
testCLIFailed args outputs = testCLI' args outputs (Left (ExitFailure 1))
resource :: String -> String
resource file = "test-resources/cli/" <> file
rule :: String -> String
rule file = "--rule=" <> resource file
spec :: Spec
spec = do
it "prints version" $
testCLISucceeded ["--version"] [showVersion version]
it "prints help" $
testCLISucceeded
["--help"]
["Phino - CLI Manipulator of 𝜑-Calculus Expressions", "Usage:"]
describe "--pin" $ do
it "succeeds when --pin matches actual version" $
withStdin "Q -> [[ ]]" $
testCLISucceeded
["--pin=" ++ showVersion version, "rewrite", "--sweet"]
["{⟦⟧}"]
it "fails when --pin doesn't match actual version" $
withStdin "Q -> [[ ]]" $
testCLIFailed
["--pin=9.9.9.9", "rewrite"]
["Version mismatch: --pin requires '9.9.9.9', but this is phino " ++ showVersion version]
it "fails when --pin is empty" $
withStdin "Q -> [[ ]]" $
testCLIFailed
["--pin=", "rewrite"]
["Version mismatch: --pin requires ''"]
it "prints debug info with --log-level=DEBUG" $
withStdin "{[[]]}" $
testCLISucceeded ["rewrite", "--log-level=DEBUG"] ["[DEBUG]:"]
describe "rewriting" $ do
describe "fails" $ do
it "with --input=latex" $
withStdin "" $
testCLIFailed
["rewrite", "--input=latex"]
["The value 'latex' can't be used for '--input' option"]
it "with negative --log-lines" $
withStdin "" $
testCLIFailed
["rewrite", "--log-lines=-2"]
["--log-lines must be >= -1"]
it "with negative --max-depth" $
withStdin "" $
testCLIFailed
["rewrite", "--max-depth=-1"]
["--max-depth must be positive"]
it "with --normalize and --must=1" $
withStdin "{[[ x -> [[ y -> 5 ]].y ]].x}" $
testCLIFailed
["rewrite", "--max-cycles=2", "--max-depth=1", "--normalize", "--must=1"]
["it's expected rewriting cycles to be in range [1], but rewriting has already reached 2"]
it "when --in-place is used without input file" $
withStdin "Q -> [[ ]]" $
testCLIFailed
["rewrite", "--in-place"]
["--in-place requires an input file"]
it "when --in-place is used with --target" $
withTempFile "inplaceXXXXXX.phi" $ \(path, h) -> do
hPutStr h "Q -> [[ ]]"
hClose h
testCLIFailed
["rewrite", "--in-place", "--target=output.phi", path]
["--in-place and --target cannot be used together"]
it "when --update is used without --target" $
withStdin "Q -> [[ ]]" $
testCLIFailed
["rewrite", "--update"]
["--update requires --target"]
it "when --update is used without an input file" $
withStdin "Q -> [[ ]]" $
testCLIFailed
["rewrite", "--update", "--target=output.phi"]
["--update requires an input file"]
it "when --update is used with --in-place" $
withStdin "Q -> [[ ]]" $
testCLIFailed
["rewrite", "--update", "--in-place", "input.phi"]
["--update and --in-place cannot be used together"]
it "with --depth-sensitive" $
withStdin "Q -> [[ x -> \"x\"]]" $
testCLIFailed
["rewrite", "--depth-sensitive", "--max-depth=1", "--max-cycles=1", rule "infinite.yaml"]
["[ERROR]: With option --depth-sensitive it's expected rewriting iterations amount does not reach the limit: --max-depth=1"]
it "with looping rules" $
withStdin "Q -> [[ x -> \"0\" ]]" $
testCLIFailed
["rewrite", rule "first.yaml", rule "second.yaml", "--max-depth=1", "--max-cycles=3"]
["it seems rewriting is looping"]
it "with wrong attribute and valid error message" $
testCLIFailed
["rewrite", resource "with-$this-attribute.phi"]
[ "[ERROR]: Couldn't parse given phi program, cause: program:10:13:"
, "10 | $this ↦ ⟦⟧"
, " | ^^"
, "unexpected \"$t\""
]
it "with --output != latex and --nonumber" $
withStdin "" $
testCLIFailed
["rewrite", "--nonumber", "--output=xmir"]
["The --nonumber option can stay together with --output=latex only"]
it "with --omit-listing and --output != xmir" $
withStdin "" $
testCLIFailed
["rewrite", "--omit-listing", "--output=phi"]
["--omit-listing"]
it "with --omit-comments and --output != xmir" $
withStdin "" $
testCLIFailed
["rewrite", "--omit-comments", "--output=phi"]
["--omit-comments"]
it "with --expression and --output != latex" $
withStdin "" $
testCLIFailed
["rewrite", "--expression=foo", "--output=phi"]
["--expression option can stay together with --output=latex only"]
it "with --label and --output != latex" $
withStdin "" $
testCLIFailed
["rewrite", "--label=foo", "--output=phi"]
["--label option can stay together with --output=latex only"]
it "with --compress and --output != latex" $
withStdin "" $
testCLIFailed
["rewrite", "--compress", "--output=phi"]
["--compress option can stay together with --output=latex only"]
it "with --meet-prefix and --output != latex" $
withStdin "" $
testCLIFailed
["rewrite", "--meet-prefix=foo", "--output=phi"]
["--meet-prefix option can stay together with --output=latex only"]
it "with wrong --hide option" $
withStdin "" $
testCLIFailed
["rewrite", "--hide=Q.x(Q.y)"]
["[ERROR]: Invalid set of arguments: Only dispatch expression", "but given: Φ.x( Φ.y )"]
it "with many --show options" $
withStdin "" $
testCLIFailed
["rewrite", "--show=Q.x.y", "--show=hello"]
["The option --show can be used only once"]
it "with wrong --show option" $
withStdin "" $
testCLIFailed
["rewrite", "--show=Q.x(Q.y)"]
["[ERROR]:", "Only dispatch expression started with Φ (or Q) can be used in --show"]
it "with --meet-popularity < 0" $
withStdin "" $
testCLIFailed
["rewrite", "--meet-popularity=-1"]
["[ERROR]:", "--meet-popularity must be positive"]
it "with --meet-popularity > 100" $
withStdin "" $
testCLIFailed
["rewrite", "--meet-popularity=102"]
["[ERROR]:", "--meet-popularity must be <= 100"]
it "with --meet-popularity and output != latex" $
withStdin "" $
testCLIFailed
["rewrite", "--meet-popularity=51", "--output=phi"]
["[ERROR]:", "--meet-popularity option can stay together with --output=latex only"]
it "with --meet-length and output != latex" $
withStdin "" $
testCLIFailed
["rewrite", "--meet-length=4", "--output=phi"]
["[ERROR]:", "--meet-length option can stay together with --output=latex only"]
it "with non-dispatch --focus" $
withStdin "" $
testCLIFailed
["rewrite", "--focus=Q.x(Q.y)"]
["[ERROR]"]
it "with --focus!=Q and --output=XMIR" $
withStdin "" $
testCLIFailed
["rewrite", "--focus=Q.x", "--output=xmir"]
["[ERROR]"]
it "with --margin < 0" $
withStdin "" $
testCLIFailed
["rewrite", "--margin=-1"]
["[ERROR]"]
it "with --breakpoint which does not exist across the rules" $
withStdin "" $
testCLIFailed
["rewrite", "--breakpoint=hello", "--normalize"]
["[ERROR]"]
it "prints help" $
testCLISucceeded
["rewrite", "--help"]
["Rewrite the 𝜑-program"]
it "saves steps to dir with --steps-dir" $ do
let dir = "test-steps-temp"
dirExists <- doesDirectoryExist dir
when dirExists (removeDirectoryRecursive dir)
withStdin "Q -> [[ x -> \"hello\"]]" $ do
testCLISucceeded
["rewrite", rule "infinite.yaml", "--max-cycles=2", "--max-depth=2", "--steps-dir=" ++ dir, "--sweet"]
["hello_hi_hi"]
(`shouldBe` True) <$> doesDirectoryExist dir
files <- listDirectory dir
length files `shouldBe` 4
(`shouldBe` True) <$> doesFileExist (dir ++ "/00001.phi")
(`shouldBe` True) <$> doesFileExist (dir ++ "/00003.phi")
removeDirectoryRecursive dir
it "desugares without any rules flag from file" $
testCLISucceeded
["rewrite", resource "desugar.phi"]
["Φ ↦ ⟦ foo ↦ ξ.x, ρ ↦ ∅ ⟧"]
it "desugares with without any rules flag from stdin" $
withStdin "{[[foo ↦ x]]}" $
testCLISucceeded ["rewrite"] ["Φ ↦ ⟦ foo ↦ ξ.x, ρ ↦ ∅ ⟧"]
it "rewrites with single rule" $
withStdin "{T(x -> Q.y)}" $
testCLISucceeded ["rewrite", "--rule=resources/normalize/dc.yaml"] ["Φ ↦ ⊥"]
it "fails when a rewriting rule uses a dataization-only function" $
withStdin "{⟦⟧}" $
testCLIFailed
["rewrite", rule "lambda-in-rewrite.yaml"]
["Function 'lambda' in rule 'uses-lambda' is available only for dataization and morphing, not for rewriting"]
it "normalizes with --normalize flag" $
testCLISucceeded
["rewrite", "--normalize", resource "normalize.phi", "--margin=25"]
[ unlines
[ "Φ ↦ ⟦"
, " x ↦ ⟦"
, " ρ ↦ ⟦"
, " y ↦ ⟦ ρ ↦ ∅ ⟧,"
, " ρ ↦ ∅"
, " ⟧"
, " ⟧,"
, " ρ ↦ ∅"
, "⟧"
]
]
it "normalizes from stdin" $
withStdin "Φ ↦ ⟦ a ↦ ⟦ b ↦ ∅ ⟧ (b ↦ [[ ]]) ⟧" $
testCLISucceeded
["rewrite", "--normalize", "--margin=20"]
[ unlines
[ "Φ ↦ ⟦"
, " a ↦ ⟦"
, " b ↦ ⟦ ρ ↦ ∅ ⟧,"
, " ρ ↦ ∅"
, " ⟧,"
, " ρ ↦ ∅"
, "⟧"
]
]
it "rewrites with --sweet flag" $
withStdin "Q -> [[ x -> 5]]" $
testCLISucceeded
["rewrite", "--sweet"]
["{⟦ x ↦ 5 ⟧}"]
it "rewrites as XMIR" $
withStdin "Q -> [[ x -> Q.y ]]" $
testCLISucceeded
["rewrite", "--output=xmir"]
["<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<object", " <o base=\"Φ.y\" name=\"x\"/>"]
it "rewrites as LaTeX" $
withStdin "Q -> [[ x_o -> Q.z(y -> 5), q$ -> T, w -> $, ^ -> Q, @ -> 1, y -> \"H$@^M\", L> Fu_nc ]]" $
testCLISucceeded
["rewrite", "--output=latex", "--sweet"]
[ unlines
[ "\\begin{phiquation}"
, "\\Big\\{ [["
, " |x\\char95{}o| -> Q . |z| ( |y| -> 5 ),"
, " |q\\char36{}| -> T,"
, " |w| -> $,"
, " ^ -> Q,"
, " @ -> 1,"
, " |y| -> \"H$@^M\","
, " L> |Fu\\char95{}nc|"
, "]] \\Big\\}{.}"
, "\\end{phiquation}"
]
]
it "rewrites as LaTeX without numeration" $
withStdin "Q -> [[ x -> 5 ]]" $
testCLISucceeded
["rewrite", "--output=latex", "--sweet", "--nonumber", "--flat"]
[ unlines
[ "\\begin{phiquation*}"
, "\\Big\\{ [[ |x| -> 5 ]] \\Big\\}{.}"
, "\\end{phiquation*}"
]
]
it "rewrite as LaTeX with expression name" $
withStdin "Q -> [[ x -> 5 ]]" $
testCLISucceeded
["rewrite", "--output=latex", "--sweet", "--flat", "--expression=foo"]
[ unlines
[ "\\begin{phiquation}"
, "\\phiExpression{foo} \\Big\\{ [[ |x| -> 5 ]] \\Big\\}{.}"
, "\\end{phiquation}"
]
]
it "rewrite as LaTeX with label name" $
withStdin "Q -> [[ x -> 5 ]]" $
testCLISucceeded
["rewrite", "--output=latex", "--sweet", "--flat", "--label=foo"]
[ unlines
[ "\\begin{phiquation}\n\\label{foo}"
, "\\Big\\{ [[ |x| -> 5 ]] \\Big\\}{.}"
, "\\end{phiquation}"
]
]
it "rewrites with XMIR as input" $
withStdin "<object><o name=\"app\"><o name=\"x\" base=\"Φ.number\"/></o></object>" $
testCLISucceeded
["rewrite", "--input=xmir", "--sweet"]
["{⟦ app ↦ ⟦ x ↦ Φ.number ⟧ ⟧}"]
it "rewrites and prints with XMIR as input and output" $
withStdin
( intercalate
""
[ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
, "<object><o name=\"app\"><o name=\"x\" base=\"Φ.number\"/></o></object>"
]
)
( testCLISucceeded
["rewrite", "--input=xmir", "--output=xmir", "--sweet"]
[ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
, "<listing><?xml version="1.0" encoding="UTF-8"?><object><o name="app"><o name="x" base="Φ.number"/></o></object></listing>"
]
)
it "rewrites as XMIR with omit-listing flag" $
withStdin "Q -> [[ x -> Q.y ]]" $
testCLISucceeded
["rewrite", "--output=xmir", "--omit-listing"]
["<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<object", "<listing>1 line(s)</listing>", " <o base=\"Φ.y\" name=\"x\"/>"]
it "does not fail on exactly 1 rewriting" $
withStdin "{⟦ t ↦ ⟦ x ↦ \"foo\" ⟧ ⟧}" $
testCLISucceeded
["rewrite", rule "simple.yaml", "--must=1", "--sweet"]
["x ↦ \"bar\""]
it "prints many programs with --sequence" $
withStdin "{[[ x -> \"foo\" ]]}" $
testCLISucceeded
[ "rewrite"
, rule "first.yaml"
, rule "second.yaml"
, "--max-depth=1"
, "--max-cycles=2"
, "--sequence"
, "--sweet"
, "--flat"
]
[ unlines
[ "{⟦ x ↦ \"foo\" ⟧}"
, "{Φ.x( y ↦ \"foo\" )}"
, "{⟦ x ↦ \"foo\" ⟧}"
]
]
it "prints only one latex preamble with --sequence" $
withStdin "{[[ x -> \"foo\" ]]}" $
testCLISucceeded
[ "rewrite"
, rule "first.yaml"
, rule "second.yaml"
, "--max-depth=1"
, "--max-cycles=2"
, "--sequence"
, "--sweet"
, "--flat"
, "--output=latex"
]
[ unlines
[ "\\begin{phiquation}"
, "\\Big\\{ [[ |x| -> \"foo\" ]] \\Big\\} \\leadsto_{\\nameref{r:first}}"
, " \\leadsto \\Big\\{ Q . |x| ( |y| -> \"foo\" ) \\Big\\} \\leadsto_{\\nameref{r:second}}"
, " \\leadsto \\Big\\{ [[ |x| -> \"foo\" ]] \\Big\\}{.}"
, "\\end{phiquation}"
]
]
it "prints meet prefix with --meet-prefix=foo in LaTeX" $
withStdin "{[[ x -> ?, y -> $.x ]](x -> [[ D> 42- ]]).y}" $
testCLISucceeded
["rewrite", "--normalize", "--sweet", "--sequence", "--output=latex", "--flat", "--compress", "--meet-prefix=foo"]
[ unlines
[ "\\begin{phiquation}"
, "\\Big\\{ [[ |x| -> ?, |y| -> |x| ]] ( |x| -> \\phiMeet{foo:1}{ [[ D> 42- ]] } ) . |y| \\Big\\} \\leadsto_{\\nameref{r:copy}}"
, " \\leadsto \\Big\\{ \\phiMeet{foo:2}{ [[ |x| -> \\phiAgain{foo:1}, |y| -> |x| ]] } . |y| \\Big\\} \\leadsto_{\\nameref{r:dot}}"
, " \\leadsto \\Big\\{ \\phiAgain{foo:2} . |x| ( ^ -> \\phiAgain{foo:2} ) \\Big\\} \\leadsto_{\\nameref{r:dot}}"
, " \\leadsto \\Big\\{ \\phiAgain{foo:1} ( ^ -> \\phiAgain{foo:2}, ^ -> \\phiAgain{foo:2} ) \\Big\\} \\leadsto_{\\nameref{r:copy}}"
, " \\leadsto \\Big\\{ [[ D> 42-, ^ -> \\phiAgain{foo:2} ]] ( ^ -> \\phiAgain{foo:2} ) \\Big\\} \\leadsto_{\\nameref{r:stay}}"
, " \\leadsto \\Big\\{ [[ D> 42-, ^ -> \\phiAgain{foo:2} ]] \\Big\\}{.}"
, "\\end{phiquation}"
]
]
it "prints with compressed expressions in LaTeX" $
withStdin "{[[ x -> ?, y -> $.x ]](x -> [[ D> 42- ]]).y}" $
testCLISucceeded
["rewrite", "--normalize", "--sweet", "--sequence", "--output=latex", "--flat", "--compress"]
[ unlines
[ "\\begin{phiquation}"
, "\\Big\\{ [[ |x| -> ?, |y| -> |x| ]] ( |x| -> \\phiMeet{1}{ [[ D> 42- ]] } ) . |y| \\Big\\} \\leadsto_{\\nameref{r:copy}}"
, " \\leadsto \\Big\\{ \\phiMeet{2}{ [[ |x| -> \\phiAgain{1}, |y| -> |x| ]] } . |y| \\Big\\} \\leadsto_{\\nameref{r:dot}}"
, " \\leadsto \\Big\\{ \\phiAgain{2} . |x| ( ^ -> \\phiAgain{2} ) \\Big\\} \\leadsto_{\\nameref{r:dot}}"
, " \\leadsto \\Big\\{ \\phiAgain{1} ( ^ -> \\phiAgain{2}, ^ -> \\phiAgain{2} ) \\Big\\} \\leadsto_{\\nameref{r:copy}}"
, " \\leadsto \\Big\\{ [[ D> 42-, ^ -> \\phiAgain{2} ]] ( ^ -> \\phiAgain{2} ) \\Big\\} \\leadsto_{\\nameref{r:stay}}"
, " \\leadsto \\Big\\{ [[ D> 42-, ^ -> \\phiAgain{2} ]] \\Big\\}{.}"
, "\\end{phiquation}"
]
]
it "should not print \\phiMeet{} twice" $
withStdin "{[[ ex -> [[ x -> [[ y -> ?, k -> [[ t -> 42]] ]]( y -> [[ t -> 42 ]]) ]].i ]]}" $
testCLISucceeded
["rewrite", "--normalize", "--sequence", "--flat", "--compress", "--output=latex", "--sweet"]
[ unlines
[ "\\begin{phiquation}"
, "\\Big\\{ [[ |ex| -> [[ |x| -> [[ |y| -> ?, |k| -> \\phiMeet{1}{ [[ |t| -> 42 ]] } ]] ( |y| -> \\phiAgain{1} ) ]] . |i| ]] \\Big\\} \\leadsto_{\\nameref{r:copy}}"
, " \\leadsto \\Big\\{ [[ |ex| -> [[ |x| -> [[ |y| -> \\phiAgain{1}, |k| -> \\phiAgain{1} ]] ]] . |i| ]] \\Big\\} \\leadsto_{\\nameref{r:stop}}"
, " \\leadsto \\Big\\{ [[ |ex| -> T ]] \\Big\\}{.}"
, "\\end{phiquation}"
]
]
it "should not meet expression with high --meet-popularity" $
withStdin "{[[ ex -> [[ x -> [[ y -> ?, k -> [[ t -> 42]] ]]( y -> [[ t -> 42 ]]) ]].i ]]}" $
testCLISucceeded
["rewrite", "--normalize", "--sequence", "--flat", "--compress", "--output=latex", "--sweet", "--meet-popularity=70"]
[ unlines
[ "\\begin{phiquation}"
, "\\Big\\{ [[ |ex| -> [[ |x| -> [[ |y| -> ?, |k| -> [[ |t| -> 42 ]] ]] ( |y| -> [[ |t| -> 42 ]] ) ]] . |i| ]] \\Big\\} \\leadsto_{\\nameref{r:copy}}"
, " \\leadsto \\Big\\{ [[ |ex| -> [[ |x| -> [[ |y| -> [[ |t| -> 42 ]], |k| -> [[ |t| -> 42 ]] ]] ]] . |i| ]] \\Big\\} \\leadsto_{\\nameref{r:stop}}"
, " \\leadsto \\Big\\{ [[ |ex| -> T ]] \\Big\\}{.}"
, "\\end{phiquation}"
]
]
it "meets with --meet-length=32" $
withStdin "{[[ ex -> [[ x -> [[ y -> ?, k -> [[ t -> 42]] ]]( y -> [[ t -> 42 ]]) ]].i ]]}" $
testCLISucceeded
["rewrite", "--normalize", "--sequence", "--flat", "--compress", "--output=latex", "--sweet", "--meet-length=32"]
[ unlines
[ "\\begin{phiquation}"
, "\\Big\\{ [[ |ex| -> [[ |x| -> [[ |y| -> ?, |k| -> [[ |t| -> 42 ]] ]] ( |y| -> [[ |t| -> 42 ]] ) ]] . |i| ]] \\Big\\} \\leadsto_{\\nameref{r:copy}}"
, " \\leadsto \\Big\\{ [[ |ex| -> [[ |x| -> [[ |y| -> [[ |t| -> 42 ]], |k| -> [[ |t| -> 42 ]] ]] ]] . |i| ]] \\Big\\} \\leadsto_{\\nameref{r:stop}}"
, " \\leadsto \\Big\\{ [[ |ex| -> T ]] \\Big\\}{.}"
, "\\end{phiquation}"
]
]
it "focuses expression in latex with sequence" $
withStdin "{[[ ex -> [[ x -> [[ y -> ?, k -> [[ t -> 42]] ]]( y -> [[ t -> 42 ]]) ]].i ]]}" $
testCLISucceeded
["rewrite", "--normalize", "--sequence", "--flat", "--output=latex", "--sweet", "--focus=Q.ex"]
[ unlines
[ "\\begin{phiquation}"
, "[[ |x| -> [[ |y| -> ?, |k| -> [[ |t| -> 42 ]] ]] ( |y| -> [[ |t| -> 42 ]] ) ]] . |i| \\leadsto_{\\nameref{r:copy}}"
, " \\leadsto [[ |x| -> [[ |y| -> [[ |t| -> 42 ]], |k| -> [[ |t| -> 42 ]] ]] ]] . |i| \\leadsto_{\\nameref{r:stop}}"
, " \\leadsto T{.}"
, "\\end{phiquation}"
]
]
it "focuses expression in latex without sequence" $
withStdin "{[[ ex -> [[ x -> [[ y -> ?, k -> [[ t -> 42]] ]]( y -> [[ t -> 42 ]]) ]].i ]]}" $
testCLISucceeded
["rewrite", "--normalize", "--flat", "--output=latex", "--sweet", "--focus=Q.ex"]
[ unlines
[ "\\begin{phiquation}"
, "T{.}"
, "\\end{phiquation}"
]
]
it "shows exceeding of limits in latex" $
withStdin "{[[ x -> $.y, y -> $.x ]].x}" $
testCLISucceeded
["rewrite", "--normalize", "--flat", "--sequence", "--output=latex", "--sweet", "--max-depth=1", "--max-cycles=1"]
[ unlines
[ "\\begin{phiquation}"
, "\\Big\\{ [[ |x| -> |y|, |y| -> |x| ]] . |x| \\Big\\} \\leadsto_{\\nameref{r:dot}}"
, " \\leadsto \\Big\\{ [[ |x| -> |y|, |y| -> |x| ]] . |y| ( ^ -> [[ |x| -> |y|, |y| -> |x| ]] ) \\Big\\} \\leadsto"
, " \\leadsto \\dots"
, "\\end{phiquation}"
]
]
it "focuses expression in phi without sequence" $
withStdin "{[[ ex -> [[ x -> [[ y -> ?, k -> [[ t -> 42]] ]]( y -> [[ t -> 42 ]]) ]].i ]]}" $
testCLISucceeded
["rewrite", "--normalize", "--flat", "--output=phi", "--sweet", "--focus=Q.ex"]
["⊥"]
it "focuses expression in phi with sequence" $
withStdin "{[[ ex -> [[ x -> [[ y -> ?, k -> [[ t -> 42]] ]]( y -> [[ t -> 42 ]]) ]].i ]]}" $
testCLISucceeded
["rewrite", "--normalize", "--sequence", "--flat", "--output=phi", "--sweet", "--focus=Q.ex"]
[ unlines
[ "⟦ x ↦ ⟦ y ↦ ∅, k ↦ ⟦ t ↦ 42 ⟧ ⟧( y ↦ ⟦ t ↦ 42 ⟧ ) ⟧.i"
, "⟦ x ↦ ⟦ y ↦ ⟦ t ↦ 42 ⟧, k ↦ ⟦ t ↦ 42 ⟧ ⟧ ⟧.i"
, "⊥"
]
]
it "prints input as listing in XMIR" $
withStdin "{[[ app -> [[]] ]]}" $
testCLISucceeded
["rewrite", "--output=xmir", "--omit-comments", "--sweet", "--flat"]
[" <listing>{[[ app -> [[]] ]]}</listing>"]
it "print program in listing in XMIRs with --sequence" $
withStdin "{[[ x -> \"foo\" ]]}" $
testCLISucceeded
["rewrite", "--output=xmir", "--omit-comments", "--sweet", "--flat", "--sequence", rule "simple.yaml"]
[" <listing>{⟦ x ↦ \"foo\" ⟧}</listing>", " <listing>{⟦ x ↦ \"bar\" ⟧}</listing>"]
describe "must range tests" $ do
describe "fails" $ do
it "when cycles exceed range ..1" $
withStdin "Q -> [[ x -> [[ y -> 5 ]].y ]].x" $
testCLIFailed
["rewrite", "--max-depth=1", "--max-cycles=2", "--normalize", "--must=..1"]
["it's expected rewriting cycles to be in range [..1], but rewriting has already reached 2"]
it "when cycles below range 2.." $
withStdin "{⟦ t ↦ ⟦ x ↦ \"foo\" ⟧ ⟧}" $
testCLIFailed
["rewrite", rule "simple.yaml", "--must=2.."]
["it's expected rewriting cycles to be in range [2..], but rewriting stopped after 1"]
it "with invalid range 5..3" $
withStdin "Q -> [[ ]]" $
testCLIFailed
["rewrite", "--must=5..3"]
["cannot parse value `5..3'"]
it "with negative in range -1..5" $
withStdin "Q -> [[ ]]" $
testCLIFailed
["rewrite", "--must=-1..5"]
["cannot parse value `-1..5'"]
it "with malformed range syntax" $
withStdin "Q -> [[ ]]" $
testCLIFailed
["rewrite", "--must=3...5"]
["cannot parse value `3...5'"]
it "accepts range ..5 (0 to 5 cycles)" $
withStdin "Q -> [[ ]]" $
testCLISucceeded ["rewrite", "--must=..5", "--sweet"] ["{⟦⟧}"]
it "accepts range 0..0 (exactly 0 cycles)" $
withStdin "Q -> [[ ]]" $
testCLISucceeded ["rewrite", "--must=0..0", "--sweet"] ["{⟦⟧}"]
it "accepts range 1..1 (exactly 1 cycle)" $
withStdin "{⟦ t ↦ ⟦ x ↦ \"foo\" ⟧ ⟧}" $
testCLISucceeded
["rewrite", rule "simple.yaml", "--must=1..1", "--sweet"]
["x ↦ \"bar\""]
it "accepts range 1..3 when 1 cycle happens" $
withStdin "{⟦ t ↦ ⟦ x ↦ \"foo\" ⟧ ⟧}" $
testCLISucceeded
["rewrite", rule "simple.yaml", "--must=1..3", "--sweet"]
["x ↦ \"bar\""]
it "accepts range 0.. (0 or more)" $
withStdin "Q -> [[ ]]" $
testCLISucceeded ["rewrite", "--must=0..", "--sweet"] ["{⟦⟧}"]
it "prints to target file" $
withStdin "Q -> [[ ]]" $
withTempFile "targetXXXXXX.tmp" $ \(path, h) -> do
hClose h
testCLISucceeded ["rewrite", "--sweet", printf "--target=%s" path] []
content <- readFile path
content `shouldBe` "{⟦⟧}"
it "modifies file in-place" $
withTempFile "inplaceXXXXXX.phi" $ \(path, h) -> do
hPutStr h "Q -> [[ x -> \"foo\" ]]"
hClose h
testCLISucceeded ["rewrite", rule "simple.yaml", "--in-place", "--sweet", path] []
content <- readFile path
content `shouldBe` "{⟦ x ↦ \"bar\" ⟧}"
it "skips rewriting with --update when target is newer than source" $
withTempFileContent "src-XXXXXX.phi" "Q -> [[ x -> \"foo\" ]]" $ \src ->
withTempFileContent "tgt-XXXXXX.phi" "ORIGINAL" $ \tgt -> do
now <- getCurrentTime
setModificationTime src (addUTCTime (-60) now)
setModificationTime tgt now
testCLISucceeded
["rewrite", rule "simple.yaml", "--update", "--sweet", "--target=" ++ tgt, src]
[]
content <- readFile tgt
content `shouldBe` "ORIGINAL"
it "rewrites with --update when source is newer than target" $
withTempFileContent "src-XXXXXX.phi" "Q -> [[ x -> \"foo\" ]]" $ \src ->
withTempFileContent "tgt-XXXXXX.phi" "ORIGINAL" $ \tgt -> do
now <- getCurrentTime
setModificationTime tgt (addUTCTime (-60) now)
setModificationTime src now
testCLISucceeded
["rewrite", rule "simple.yaml", "--update", "--sweet", "--target=" ++ tgt, src]
[]
content <- readFile tgt
content `shouldBe` "{⟦ x ↦ \"bar\" ⟧}"
it "rewrites with cycles" $
withStdin "Q -> [[ x -> \"x\" ]]" $
testCLISucceeded
["rewrite", "--sweet", rule "infinite.yaml", "--max-depth=1", "--max-cycles=2"]
["{⟦ x ↦ \"x_hi_hi\" ⟧}"]
it "hides default package" $
withStdin "{[[ org -> [[ eolang -> [[ number -> [[]] ]]]], x -> 42 ]]}" $
testCLISucceeded
["rewrite", "--sweet", "--flat", "--hide=Q.org"]
["{⟦ x ↦ 42 ⟧}"]
it "hides several FQNs" $
withStdin "{[[ org -> [[ eolang -> Q.x, yegor256 -> Q.y ]], x -> 42 ]]}" $
testCLISucceeded
["rewrite", "--sweet", "--flat", "--hide=Q.org.eolang", "--hide=Q.org.yegor256"]
["{⟦ org ↦ ⟦⟧, x ↦ 42 ⟧}"]
it "shows and hides" $
withStdin "{[[ org -> [[ eolang -> Q.x, yegor256 -> Q.y ]], x -> 42 ]]}" $
testCLISucceeded
["rewrite", "--sweet", "--flat", "--show=Q.org", "--hide=Q.org.eolang"]
["{⟦ org ↦ ⟦ yegor256 ↦ Φ.y ⟧ ⟧}"]
it "prints in line with --flat" $
withStdin "Q -> [[ x -> 5, y -> \"hey\", z -> [[ w -> [[ ]] ]] ]]" $
testCLISucceeded
["rewrite", "--sweet", "--flat"]
["{⟦ x ↦ 5, y ↦ \"hey\", z ↦ ⟦ w ↦ ⟦⟧ ⟧ ⟧}"]
it "removes unnecessary rho bindings in primitive applications" $
withStdin
( unlines
[ "{[["
, " z -> [[ x -> [[ t -> 42 ]].t ]].x,"
, " org -> [[ eolang -> [[ bytes -> [[ data -> ? ]], number -> [[ as-bytes -> ? ]] ]] ]]"
, "]]}"
]
)
( testCLISucceeded
["rewrite", "--sweet", "--normalize", "--flat"]
["{⟦ z ↦ 42, org ↦ ⟦ eolang ↦ ⟦ bytes(data) ↦ ⟦⟧, number(as-bytes) ↦ ⟦⟧ ⟧ ⟧ ⟧}"]
)
it "reduces log message" $
withStdin "{[[ x -> [[ y -> ? ]](y -> 5) ]]}" $
testCLISucceeded
["rewrite", "--log-level=debug", "--log-lines=1", "--normalize"]
[ intercalate
"\n"
[ "[DEBUG]: Applied 'copy' (44 nodes -> 39 nodes)"
, "---| log is limited by --log-lines=1 option |---"
]
]
it "canonizes program" $
withStdin "{[[ x -> [[ y -> [[ L> Func ]].q, z -> Q.x(a -> [[ w -> [[ L> Atom ]], L> Hello ]]) ]], L> Package ]]}" $
testCLISucceeded
["rewrite", "--canonize", "--sweet", "--flat"]
["{⟦ x ↦ ⟦ y ↦ ⟦ λ ⤍ F1 ⟧.q, z ↦ Φ.x( a ↦ ⟦ w ↦ ⟦ λ ⤍ F2 ⟧, λ ⤍ F3 ⟧ ) ⟧, λ ⤍ F4 ⟧}"]
it "rewrites by locator" $
withStdin "{[[ ex -> [[ x -> [[ y -> 5 ]].y ]], abc -> [[ x -> ? ]](x -> 5) ]]}" $
testCLISucceeded
["rewrite", "--sweet", "--flat", "--locator=Q.ex", "--normalize"]
["{⟦ ex ↦ ⟦ x ↦ 5 ⟧, abc ↦ ⟦ x ↦ ∅ ⟧( x ↦ 5 ) ⟧}"]
it "returns original program on --breakpoint" $
withStdin "{[[ x -> ?, y -> $.x ]](x -> [[ D> 42- ]]).y}" $
testCLISucceeded
["rewrite", "--sweet", "--flat", "--normalize", "--breakpoint=stop", "--log-level=debug"]
[ "Applied 'copy' (30 nodes -> 25 nodes)"
, "Rule 'stop' is a breakpoint, dropping down all the previous rewritings..."
, "{⟦ x ↦ ∅, y ↦ x ⟧( x ↦ ⟦ Δ ⤍ 42- ⟧ ).y}"
]
describe "dataize" $ do
it "prints help" $
testCLISucceeded ["dataize", "--help"] ["Dataize the 𝜑-program"]
it "dataizes simple program" $
withStdin "Q -> [[ D> 01- ]]" $
testCLISucceeded ["dataize"] ["01-"]
it "dataizes to dead" $
withStdin "Q -> [[ ]]" $
testCLISucceeded ["dataize"] ["⊥"]
it "dataizes with --sequence" $
withStdin "{[[ @ -> [[ x -> [[ D> 01-, y -> ? ]](y -> [[ ]]) ]].x ]]}" $
testCLISucceeded
["dataize", "--sequence", "--output=latex", "--flat", "--sweet"]
[ intercalate
"\n"
[ "\\begin{phiquation}"
, "\\Big\\{ [[ @ -> [[ |x| -> [[ D> 01-, |y| -> ? ]] ( |y| -> [[]] ) ]] . |x| ]] \\Big\\} \\leadsto_{\\nameref{r:contextualize}}"
, " \\leadsto \\Big\\{ [[ |x| -> [[ D> 01-, |y| -> ? ]] ( |y| -> [[]] ) ]] . |x| \\Big\\} \\leadsto_{\\nameref{r:copy}}"
, " \\leadsto \\Big\\{ [[ |x| -> [[ D> 01-, |y| -> [[]] ]] ]] . |x| \\Big\\} \\leadsto_{\\nameref{r:dot}}"
, " \\leadsto \\Big\\{ [[ D> 01-, |y| -> [[]] ]] ( ^ -> [[ |x| -> [[ D> 01-, |y| -> [[]] ]] ]] ) \\Big\\} \\leadsto_{\\nameref{r:copy}}"
, " \\leadsto \\Big\\{ [[ D> 01-, |y| -> [[]], ^ -> [[ |x| -> [[ D> 01-, |y| -> [[]] ]] ]] ]] \\Big\\} \\leadsto_{\\nameref{r:Mprim}}"
, " \\leadsto \\Big\\{ [[ D> 01-, |y| -> [[]], ^ -> [[ |x| -> [[ D> 01-, |y| -> [[]] ]] ]] ]] \\Big\\}{.}"
, "\\end{phiquation}"
, "01-"
]
]
it "dataizes with --locator" $
withStdin "{[[ ex -> [[ @ -> Q.x ]], x -> [[ D> 42- ]] ]]}" $
testCLISucceeded ["dataize", "--locator=Q.ex"] ["42-"]
it "does not print bytes with --quiet" $
withStdin "Q -> [[ D> 01- ]]" $
testCLISucceeded ["dataize", "--quiet"] []
describe "fails" $ do
it "with --output != latex and --nonumber" $
withStdin "" $
testCLIFailed
["dataize", "--nonumber", "--output=xmir"]
["The --nonumber option can stay together with --output=latex only"]
it "with --omit-listing and --output != xmir" $
withStdin "" $
testCLIFailed
["dataize", "--omit-listing", "--output=phi"]
["--omit-listing"]
it "with --omit-comments and --output != xmir" $
withStdin "" $
testCLIFailed
["dataize", "--omit-comments", "--output=phi"]
["--omit-comments"]
it "with --expression and --output != latex" $
withStdin "" $
testCLIFailed
["dataize", "--expression=foo", "--output=phi"]
["--expression option can stay together with --output=latex only"]
it "with --label and --output != latex" $
withStdin "" $
testCLIFailed
["dataize", "--label=foo", "--output=phi"]
["--label option can stay together with --output=latex only"]
it "with wrong --hide option" $
withStdin "" $
testCLIFailed
["dataize", "--hide=Q.x(Q.y)"]
["[ERROR]: Invalid set of arguments: Only dispatch expression", "but given: Φ.x( Φ.y )"]
describe "explain" $ do
it "explains single rule" $
testCLISucceeded
["explain", "--rule=resources/normalize/copy.yaml"]
[ unlines
[ "\\begin{tabular}{rl}"
, "\\trrule{copy}"
, " { [[ B_1, \\tau -> ?, B_2 ]] ( \\tau -> k ) }"
, " { [[ B_1, \\tau -> k, B_2 ]] }"
, " { }"
, " { }"
, "\\end{tabular}"
]
]
it "explains multiple rules" $
testCLISucceeded
["explain", "--rule=resources/normalize/copy.yaml", "--rule=resources/normalize/alpha.yaml"]
["\\begin{tabular}{rl}", "\\trrule{copy}", "\\trrule{alpha}"]
it "explains normalization rules" $
testCLISucceeded
["explain", "--normalize"]
[ unlines
[ "\\begin{tabular}{rl}"
, "\\trrule{alpha}"
, " { [[ B_1, \\tau_1 -> ?, B_2 ]] ( \\tau_2 -> e ) }"
, " { [[ B_1, \\tau_1 -> ?, B_2 ]] ( \\tau_1 -> e ) }"
, " { if $ \\indexof{ \\tau_2 } = \\vert \\overline{ B_1 } \\vert $ }"
, " { }"
, "\\trrule{copy}"
, " { [[ B_1, \\tau -> ?, B_2 ]] ( \\tau -> k ) }"
, " { [[ B_1, \\tau -> k, B_2 ]] }"
, " { }"
, " { }"
, "\\trrule{dc}"
, " { T ( \\tau -> e ) }"
, " { T }"
, " { }"
, " { }"
, "\\trrule{dd}"
, " { T . \\tau }"
, " { T }"
, " { }"
, " { }"
, "\\trrule{dot}"
, " { [[ B_1, \\tau -> n, B_2 ]] . \\tau }"
, " { e ( ^ -> [[ B_1, \\tau -> n, B_2 ]] ) }"
, " { }"
, " { where $ e \\coloneqq \\ctx{ n }{ [[ B_1, \\tau -> n, B_2 ]] } $ }"
, "\\trrule{miss}"
, " { [[ B ]] ( \\tau -> e ) }"
, " { T }"
, " { if $ \\tau \\notin B \\;\\text{and}\\; \\tau \\notin [ ~0, ~1, \\dots ] $ }"
, " { }"
, "\\trrule{null}"
, " { [[ B_1, \\tau -> ?, B_2 ]] . \\tau }"
, " { T }"
, " { }"
, " { }"
, "\\trrule{over}"
, " { [[ B_1, \\tau -> e_1, B_2 ]] ( \\tau -> e_2 ) }"
, " { T }"
, " { if $ \\tau \\not= ^ $ }"
, " { }"
, "\\trrule{phi}"
, " { [[ B ]] . \\tau }"
, " { [[ B ]] . @ . \\tau }"
, " { if $ @ \\in B \\;\\text{and}\\; \\tau \\notin B $ }"
, " { }"
, "\\trrule{stay}"
, " { [[ B_1, ^ -> e_1, B_2 ]] ( ^ -> e_2 ) }"
, " { [[ B_1, ^ -> e_1, B_2 ]] }"
, " { }"
, " { }"
, "\\trrule{stop}"
, " { [[ B ]] . \\tau }"
, " { T }"
, " { if $ \\tau \\notin B \\;\\text{and}\\; @ \\notin B \\;\\text{and}\\; L \\notin B $ }"
, " { }"
, "\\end{tabular}"
]
]
it "explains morphing rules" $
testCLISucceeded
["explain", "--morph"]
[ unlines
[ "\\begin{tabular}{rl}"
, "\\trrule{Mprim}"
, " { \\mathbb{M}( e ) }"
, " { e }"
, " { if $ e \\in \\mathcal{P} $ }"
, " { }"
, "\\trrule{Mnmz}"
, " { \\mathbb{M}( e ) }"
, " { \\mathbb{M}( \\mathcal{N}( e ) ) }"
, " { if $ not( \\isnormal{ e } ) $ }"
, " { }"
, "\\trrule{Mlambda}"
, " { \\mathbb{M}( [[ B_1, L> F, B_2 ]] * t ) }"
, " { \\mathbb{M}( e * t ) }"
, " { }"
, " { where $ e \\coloneqq lambda( [[ B_1, L> F, B_2 ]] ) $ }"
, "\\trrule{Mphi}"
, " { \\mathbb{M}( Q . \\tau * t ) }"
, " { \\mathbb{M}( e * t ) }"
, " { }"
, " { where $ e \\coloneqq global( \\tau ) $ }"
, "\\trrule{Mstuck}"
, " { \\mathbb{M}( e ) }"
, " { T }"
, " { }"
, " { }"
, "\\end{tabular}"
]
]
it "explains dataization rules" $
testCLISucceeded
["explain", "--dataize"]
[ unlines
[ "\\begin{tabular}{rl}"
, "\\trrule{delta}"
, " { \\mathbb{D}( [[ B_1, D> \\delta, B_2 ]] ) }"
, " { \\delta }"
, " { }"
, " { }"
, "\\trrule{box}"
, " { \\mathbb{D}( [[ B_1, @ -> e, B_2 ]] ) }"
, " { \\mathbb{D}( e_1 ) }"
, " { if $ [ \\Delta, L ] \\cap ( B_1 \\cup B_2 ) = \\emptyset $ }"
, " { where $ e_1 \\coloneqq \\ctx{ e }{ [[ B_1, @ -> e, B_2 ]] } $ }"
, "\\trrule{norm}"
, " { \\mathbb{D}( e ) }"
, " { \\mathbb{D}( \\mathbb{M}( e ) ) }"
, " { if $ e \\notin \\mathcal{P} $ }"
, " { }"
, "\\trrule{none}"
, " { \\mathbb{D}( e ) }"
, " { \\varnothing }"
, " { }"
, " { }"
, "\\end{tabular}"
]
]
it "fails with no rules specified" $
testCLIFailed
["explain"]
["Either --rule, --normalize, --morph or --dataize must be specified"]
it "fails when more than one rule set is specified" $
testCLIFailed
["explain", "--morph", "--dataize"]
["Only one of --rule, --normalize, --morph or --dataize can be specified"]
it "writes to target file" $
bracket
( do
tmp <- getTemporaryDirectory
stamp <- getPOSIXTime
let dir = tmp </> ("phino-test-" ++ show (floor stamp :: Integer))
createDirectoryIfMissing True dir
pure (dir </> "explain.tex", dir)
)
(\(_, dir) -> removeDirectoryRecursive dir)
( \(path, _) -> do
testCLISucceeded ["explain", "--normalize", printf "--target=%s" path] []
content <- readFile path
_ <- evaluate (length content)
content `shouldContain` "\\begin{tabular}{rl}"
content `shouldContain` "\\end{tabular}"
)
describe "merge" $ do
it "merges single program" $
testCLISucceeded
["merge", resource "desugar.phi", "--sweet", "--flat"]
["{⟦ foo ↦ x ⟧}"]
it "merges EO programs" $
testCLISucceeded
["merge", "--sweet", resource "number.phi", resource "bytes.phi", resource "string.phi", "--margin=25"]
[ unlines
[ "{⟦"
, " org ↦ ⟦"
, " eolang ↦ ⟦"
, " number(φ) ↦ ⟦⟧,"
, " bytes(data) ↦ ⟦⟧,"
, " string(φ) ↦ ⟦⟧,"
, " λ ⤍ Package"
, " ⟧,"
, " λ ⤍ Package"
, " ⟧"
, "⟧}"
]
]
it "fails on merging non formations" $
testCLIFailed
["merge", resource "dispatch.phi", resource "number.phi"]
["Invalid program format, only programs with top level formations are supported for 'merge' command"]
it "fails on merging conflicted bindings" $
testCLIFailed
["merge", resource "foo.phi", resource "desugar.phi"]
["Can't merge two bindings, conflict found"]
it "fails on merging empty list of programs" $
testCLIFailed
["merge"]
["At least one input file must be specified for 'merge' command"]
describe "match" $ do
it "takes from stdin" $
withStdin "{[[]]}" $
testCLISucceeded ["match", "--log-level=debug"] ["[DEBUG]"]
it "takes from file" $
testCLISucceeded ["match", "test-resources/cli/foo.phi", "--log-level=debug"] ["[DEBUG]"]
it "does not print substitutions without pattern" $
withStdin "{[[]]}" $
testCLISucceeded ["match", "--log-level=debug"] ["[DEBUG]: The --pattern is not provided, no substitutions are built"]
it "prints one substitution" $
withStdin "{[[ x -> Q.x ]]}" $
testCLISucceeded ["match", "--pattern=Q.!a"] ["a >> x"]
it "prints many substitutions" $
withStdin "{[[ x -> Q.x, y -> Q.y ]]}" $
testCLISucceeded ["match", "--pattern=Q.!a"] ["a >> x\n------\na >> y"]
it "builds substitutions with conditions" $
withStdin "{[[ x -> Q.y ]].x}" $
testCLISucceeded
["match", "--pattern=[[ !a -> Q.y, !B ]].!a", "--when=and(not(alpha(!a)),eq(length(!B),1))"]
["B >> ⟦ ρ ↦ ∅ ⟧\na >> x"]
it "builds with condition from file" $
testCLISucceeded
["match", "--pattern=[[ !B ]]", "--when=eq(length(!B),2)", "test-resources/cli/foo.phi"]
["B >> ⟦ foo ↦ Φ.org.eolang.x, ρ ↦ ∅ ⟧"]
it "fails on parsing --when condition" $
withStdin "{[[]]}" $
testCLIFailed
["match", "--pattern=[[!B]]", "--when=hello"]
["[ERROR]: Couldn't parse given condition"]
it "fails on empty substitutions" $
withStdin "{Q.x.y}" $
testCLIFailed
["match", "--pattern=$.!a"]
["[ERROR]"]