phino 0.0.0.1 → 0.0.0.2
raw patch · 16 files changed
+462/−260 lines, 16 files
Files
- phino.cabal +6/−2
- src/Ast.hs +2/−0
- src/Builder.hs +15/−22
- src/CLI.hs +31/−28
- src/Matcher.hs +41/−91
- src/Parser.hs +25/−3
- src/Printer.hs +1/−1
- src/Rewriter.hs +75/−14
- src/Yaml.hs +58/−13
- test/BuilderSpec.hs +15/−0
- test/CLISpec.hs +25/−7
- test/MatcherSpec.hs +59/−65
- test/ParserSpec.hs +8/−5
- test/ReplacerSpec.hs +50/−0
- test/RewriterSpec.hs +29/−9
- test/YamlSpec.hs +22/−0
phino.cabal view
@@ -1,7 +1,7 @@ cabal-version: 3.0 name: phino-version: 0.0.0.1+version: 0.0.0.2 license: MIT synopsis: Command-Line Manipulator of 𝜑-Calculus Expressions description: Please see the README on GitHub at <https://github.com/objectionary/phino#readme>@@ -70,7 +70,11 @@ BuilderSpec, ReplacerSpec, PrinterSpec,- RewriterSpec+ RewriterSpec,+ YamlSpec,+ Paths_phino+ autogen-modules:+ Paths_phino default-extensions: ImportQualifiedPost ghc-options: -Wall
src/Ast.hs view
@@ -36,5 +36,7 @@ | AtAlpha Integer -- ~1 | AtPhi -- @ | AtRho -- ^+ | AtLambda -- λ, used only in yaml conditions+ | AtDelta -- Δ, used only in yaml conditions | AtMeta String -- !a deriving (Eq, Show, Generic)
src/Builder.hs view
@@ -4,13 +4,19 @@ -- The goal of the module is to build phi expression based on -- pattern expression and set of substitutions by replacing -- meta variables with appropriate meta values-module Builder where+module Builder+ ( buildExpressions,+ buildExpression,+ buildAttribute,+ buildBindings,+ )+where import Ast-import Misc-import Matcher-import qualified Data.Map.Strict as Map import Data.List (findIndex)+import qualified Data.Map.Strict as Map+import Matcher+import Misc buildAttribute :: Attribute -> Subst -> Maybe Attribute buildAttribute (AtMeta meta) (Subst mp) = case Map.lookup meta mp of@@ -40,27 +46,17 @@ _ -> Nothing buildBinding binding _ = Just [binding] --- Build bindings which don't contain meta binding (BiMeta)-buildExactBindings :: [Binding] -> Subst -> Maybe [Binding]-buildExactBindings [] _ = Just []-buildExactBindings (bd:rest) subst = do- first <- buildBinding bd subst- bds <- buildExactBindings rest subst- Just (head first : bds)- -- Build bindings that may contain meta binding (BiMeta) buildBindings :: [Binding] -> Subst -> Maybe [Binding] buildBindings [] _ = Just []-buildBindings bindings subst = case findIndex isMetaBinding bindings of- Just idx -> do- exact <- buildExactBindings (withoutAt idx bindings) subst- meta <- buildBinding (bindings !! idx) subst- Just (exact ++ meta)- _ -> buildExactBindings bindings subst+buildBindings (bd : rest) subst = do+ first <- buildBinding bd subst+ bds <- buildBindings rest subst+ Just (first ++ bds) buildExpressionWithTails :: Expression -> [Tail] -> Subst -> Expression buildExpressionWithTails expr [] _ = expr-buildExpressionWithTails expr (tail:rest) subst = case tail of+buildExpressionWithTails expr (tail : rest) subst = case tail of TaApplication taus -> buildExpressionWithTails (ExApplication expr taus) rest subst TaDispatch attr -> buildExpressionWithTails (ExDispatch expr attr) rest subst @@ -74,9 +70,6 @@ bindings <- buildBindings taus subst Just (ExApplication applied bindings) buildExpression (ExFormation bds) subst = buildBindings bds subst >>= (Just . ExFormation)--- buildExpression (ExFormation bds) subst = do--- bindings <- buildBindings bds subst--- Just (ExFormation bindings) buildExpression (ExMeta meta) (Subst mp) = case Map.lookup meta mp of Just (MvExpression expr) -> Just expr _ -> Nothing
src/CLI.hs view
@@ -1,5 +1,5 @@-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com@@ -7,33 +7,33 @@ module CLI (runCLI) where +import Control.Exception (Exception (displayException), SomeException, handle, throw, throwIO)+import Control.Exception.Base import Data.Version (showVersion)+import Misc (ensuredFile) import Options.Applicative import Paths_phino (version) import Rewriter (rewrite)-import Misc (ensuredFile)-import qualified Yaml as Y-import System.IO (hPutStrLn, stderr, getContents')-import System.Exit (exitFailure)-import Control.Exception (handle, SomeException, Exception (displayException), throwIO)-import Control.Exception.Base+import System.Exit (exitFailure, ExitCode (..))+import System.IO (getContents', hPutStrLn, stderr) import Text.Printf (printf)+import qualified Yaml as Y data CmdException = InvalidRewriteArguments | NormalizationIsNotSupported- | CouldNotReadFromStdin { message :: String }+ | CouldNotReadFromStdin {message :: String} deriving (Exception) instance Show CmdException where- show InvalidRewriteArguments = "Invalid set of arguments for 'rewrite' command: no --rules, no --normalize, no --nothing are provided"+ show InvalidRewriteArguments = "Invalid set of arguments for 'rewrite' command: no --rule, no --normalize, no --nothing are provided" show NormalizationIsNotSupported = "Normalization is not supported yet..."- show CouldNotReadFromStdin{..} = printf "Could not read 𝜑-expression from stdin\nReason: %s" message+ show CouldNotReadFromStdin {..} = printf "Could not read 𝜑-expression from stdin\nReason: %s" message newtype Command = CmdRewrite OptsRewrite data OptsRewrite = OptsRewrite- { rules :: Maybe FilePath,+ { rules :: [FilePath], phiInput :: Maybe FilePath, normalize :: Bool, nothing :: Bool@@ -43,8 +43,8 @@ rewriteParser = CmdRewrite <$> ( OptsRewrite- <$> optional (strOption (long "rules" <> metavar "FILENAME" <> help "Path to custom rules"))- <*> optional (strOption (long "phi-input" <> metavar "FILENAME" <> help "Path .phi file with 𝜑-expression"))+ <$> many (strOption (long "rule" <> metavar "FILE" <> help "Path to custom rule"))+ <*> optional (strOption (long "phi-input" <> metavar "FILE" <> help "Path .phi file with 𝜑-expression")) <*> switch (long "normalize" <> help "Use built-in normalization rules") <*> switch (long "nothing" <> help "Desugar provided 𝜑-expression") )@@ -59,28 +59,31 @@ (fullDesc <> header "Phino - CLI Manipulator of 𝜑-Calculus Expressions") handler :: SomeException -> IO ()-handler e = do- hPutStrLn stderr ("[error] " ++ displayException e)- exitFailure+handler e = case fromException e of+ Just ExitSuccess -> pure () -- prevent printing error on --version etc.+ _ -> do+ hPutStrLn stderr ("[error] " ++ displayException e)+ exitFailure runCLI :: [String] -> IO () runCLI args = handle handler $ do cmd <- handleParseResult (execParserPure defaultPrefs parserInfo args) case cmd of- CmdRewrite OptsRewrite{..} -> do+ CmdRewrite OptsRewrite {..} -> do prog <- case phiInput of Just pth -> readFile =<< ensuredFile pth Nothing -> getContents' `catch` (\(e :: SomeException) -> throwIO (CouldNotReadFromStdin (show e)))- rules' <- if nothing- then pure Nothing- else do- path <- case rules of- Nothing ->- if normalize- then throwIO NormalizationIsNotSupported- else throwIO InvalidRewriteArguments- Just pth -> ensuredFile pth- ruleSet <- Y.yamlRuleSet path- pure (Just ruleSet)+ rules' <-+ if nothing+ then pure []+ else do+ paths <-+ if null rules+ then+ if normalize+ then throwIO NormalizationIsNotSupported+ else throwIO InvalidRewriteArguments+ else mapM ensuredFile rules+ mapM Y.yamlRule paths rewritten <- rewrite prog rules' putStrLn rewritten
src/Matcher.hs view
@@ -6,12 +6,12 @@ module Matcher where import Ast-import Misc import Data.List (findIndex, partition) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe, isJust, maybeToList) import Data.Sequence (foldlWithIndex)+import Misc -- Meta value -- The right part of substitution@@ -44,14 +44,6 @@ substSingle :: String -> MetaValue -> Subst substSingle key value = Subst (Map.singleton key value) --- Returns True if given binding contains meta attribute on the left:--- !a -> ...--- !a -> ?-isBindingWithMetaAttr :: Binding -> Bool-isBindingWithMetaAttr (BiTau (AtMeta _) _) = True-isBindingWithMetaAttr (BiVoid (AtMeta _)) = True-isBindingWithMetaAttr _ = False- -- Combine two substitutions into a single one -- Fails if values by the same keys are not equal combine :: Subst -> Subst -> Maybe Subst@@ -87,79 +79,43 @@ combine mattr mexp matchBinding _ _ = Nothing --- Returns a tuple (X, Y) if given binding B is matched to any of bindings L--- X is an index of matched B in L--- Y is substiturion for matched B--- (-1, Nothing) is returned in case of no binding is matched in L-matchAnyBinding :: Binding -> [Binding] -> (Int, Maybe Subst)-matchAnyBinding pb [] = (-1, Nothing)-matchAnyBinding pb tbs = matchAnyBindingWithIndex 0 tbs- where- matchAnyBindingWithIndex :: Integer -> [Binding] -> (Int, Maybe Subst)- matchAnyBindingWithIndex idx [] = (-1, Nothing)- matchAnyBindingWithIndex idx (tb : rest) = case matchBinding pb tb of- Nothing -> matchAnyBindingWithIndex (idx + 1) rest- subst -> (fromInteger idx, subst)---- Match bindings with filtering target bindings--- !! Bindings may be placed in random order------ Returns a tuple (B, S) where:--- B is a list of rest target bindings excluding matched ones--- S is a substitution for matched bindings--- In case of not matching - S as Nothing is returned-matchBindingsWithFiltering :: [Binding] -> [Binding] -> ([Binding], Maybe Subst)-matchBindingsWithFiltering [] [] = ([], Just substEmpty)-matchBindingsWithFiltering [] tbs = (tbs, Just substEmpty)-matchBindingsWithFiltering (pb : rest) tbs = case matchAnyBinding pb tbs of- (-1, Nothing) -> (tbs, Nothing)- (idx, Just subst) -> case matchBindingsWithFiltering rest (withoutAt idx tbs) of- (bs, Just next) -> (bs, combine subst next)- _ -> (tbs, Nothing)---- Match non meta bindings--- !! Pattern bindings don't contain BiMeta--- !! Target and pattern bindings may be placed in random order--- !! Lengths of pattern and target bindings must be the same------ First it filters pattern bindings, only bindings without meta attributes are left (B)--- If B are not empty, it means we have bindings B with exact attributes, like "attr", "foo", etc.--- Since all the attributes in phi calculus are unique in the scope of formation or application--- we try to match these exact bindings first. While matching them we drop target bindings which--- we found a match for. When we're done with exact bindings, we have a list of target--- unmatched bindings (L). Now we're entering a new circle and trying to match rest "not exact"--- pattern bindings with left unmatched bindings L. If matching is succeeded - we just merge--- result substitutions.-matchNonMetaExactBindings :: [Binding] -> [Binding] -> Maybe Subst-matchNonMetaExactBindings [] [] = Just substEmpty-matchNonMetaExactBindings pbs tbs- | length tbs /= length pbs = Nothing- | otherwise = case partition isBindingWithMetaAttr pbs of- (with, []) -> case matchBindingsWithFiltering with tbs of- ([], Just subst) -> Just subst- (_, _) -> Nothing- (with, without) -> case matchBindingsWithFiltering without tbs of- (rest, Just subst) -> case matchNonMetaExactBindings with rest of- Just next -> combine subst next- Nothing -> Nothing- (_, Nothing) -> Nothing---- The same as `matchNonMetaExactBindings` but without the "same length" requirement-matchNonMetaBindingsWithFiltering :: [Binding] -> [Binding] -> ([Binding], Maybe Subst)-matchNonMetaBindingsWithFiltering [] [] = ([], Just substEmpty)-matchNonMetaBindingsWithFiltering [] tbs = (tbs, Just substEmpty)-matchNonMetaBindingsWithFiltering pbs [] = ([], Nothing)-matchNonMetaBindingsWithFiltering pbs tbs- | length pbs > length tbs = ([], Nothing)- | otherwise = case partition isBindingWithMetaAttr pbs of- (with, []) -> case matchBindingsWithFiltering with tbs of- (rest, Just subst) -> (rest, Just subst)- (_, Nothing) -> ([], Nothing)- (with, without) -> case matchBindingsWithFiltering without tbs of- (rest, Just subst) -> case matchNonMetaBindingsWithFiltering with rest of- (rest', Just next) -> (rest', combine subst next)- (_, Nothing) -> ([], Nothing)- (_, Nothing) -> ([], Nothing)+-- Match bindings with ordering+-- Function returns tuple (X, Y), where+-- - X - "before bindings" - list of bindings before each exact binding. It's used to join with+-- meta binding which goes before exact binding, if such is exist. If there's no one,+-- it'll be []+-- - Y - Substitution for bindings+-- +-- How it works:+-- We start process pattern bindings. +-- 1. If we meet meta binding (!B), we skip for now+-- and go the next recursive cycle with next pattern and wait for the result.+-- Result will contain list of "before bindings". This list will be+-- matched to current meta binding+-- 2. If we meet exact binding in pattern, like void, tau, delta, lambda, etc... we try to match it+-- with current target binding. If it matches, we go further and try to match next patterns with next targets.+-- If it does not match, there may be two options:+-- a) we came here from step 1. It means that we should skip this target binding and go the next+-- cycle. When we get the result, we join skipped target binding with returned list of "before" bindings+-- and return to the step one+-- b) we came from somewhere else. Then we just returns Nothing as substitution and don't go further+matchBindingsInOrder :: [Binding] -> [Binding] -> Bool -> ([Binding], Maybe Subst)+matchBindingsInOrder [] [] _ = ([], Just substEmpty)+matchBindingsInOrder [] tbs True = (tbs, Just substEmpty)+matchBindingsInOrder [] tbs False = ([], Nothing)+matchBindingsInOrder [BiMeta name] tbs _ = ([], Just (substSingle name (MvBindings tbs)))+matchBindingsInOrder ((BiMeta name) : pbs) tbs meta = case matchBindingsInOrder pbs tbs True of+ (_, Nothing) -> ([], Nothing)+ (before, Just subst) -> ([], combine (substSingle name (MvBindings before)) subst)+matchBindingsInOrder (pb : pbs) (tb : tbs) meta = case matchBinding pb tb of+ Nothing -> if meta + then case matchBindingsInOrder (pb : pbs) tbs meta of+ (_, Nothing) -> ([], Nothing)+ (before, Just subst) -> (tb : before, Just subst)+ else ([], Nothing)+ Just subst -> case matchBindingsInOrder pbs tbs False of+ (_, Nothing) -> ([], Nothing)+ (before, Just subst') -> (before, combine subst subst') -- Match pattern bindings to target bindings -- !! Pattern bindings list may contain only one BiMeta binding@@ -168,15 +124,9 @@ -- If pattern bindings contains only BiMeta binding - all the target bindings are matched matchBindings :: [Binding] -> [Binding] -> Maybe Subst matchBindings [] [] = Just substEmpty-matchBindings pbs tbs = case findIndex isMetaBinding pbs of- Just idx -> do- let (BiMeta name) = pbs !! idx- if length pbs == 1- then Just (substSingle name (MvBindings tbs))- else case matchNonMetaBindingsWithFiltering (withoutAt idx pbs) tbs of- (rest, Just subst) -> combine subst (substSingle name (MvBindings rest))- (_, Nothing) -> Nothing- _ -> matchNonMetaExactBindings pbs tbs+matchBindings pbs tbs = do+ let (_, subst) = matchBindingsInOrder pbs tbs False+ subst -- Recursively go through given target expression and try to find -- the head expression which matches to given pattern.
src/Parser.hs view
@@ -5,7 +5,15 @@ -- SPDX-License-Identifier: MIT -- The goal of the module is to parse given phi program to Ast-module Parser (parseProgram, parseProgramThrows, parseExpression, parseExpressionThrows) where+module Parser+ ( parseProgram,+ parseProgramThrows,+ parseExpression,+ parseExpressionThrows,+ parseAttribute,+ parseBinding+ )+where import Ast import Control.Exception (Exception, throwIO)@@ -154,6 +162,9 @@ return (BiTau attr' (ExFormation (voids ++ bs))) ] +metaBinding :: Parser Binding+metaBinding = BiMeta <$> meta 'B'+ -- binding -- 1. tau -- 2. void@@ -177,7 +188,7 @@ try $ do _ <- delta BiMetaDelta <$> meta 'b',- try (BiMeta <$> meta 'B'),+ try metaBinding, try $ do _ <- lambda BiLambda <$> function,@@ -305,7 +316,12 @@ _ <- symbol "(" bds <- choice- [ try $ tauBinding fullAttribute `sepBy1` symbol ",",+ [ try $+ choice+ [ try (tauBinding fullAttribute),+ metaBinding+ ]+ `sepBy1` symbol ",", do exprs <- expression `sepBy1` symbol "," return (zipWith (BiTau . AtAlpha) [0 ..] exprs) -- \idx expr -> BiTau (AtAlpha idx) expr@@ -362,6 +378,12 @@ case parsed of Right parsed' -> Right parsed' Left err -> Left (errorBundlePretty err)++parseBinding :: String -> Either String Binding+parseBinding = parse' "binding" binding++parseAttribute :: String -> Either String Attribute+parseAttribute = parse' "attribute" fullAttribute parseExpression :: String -> Either String Expression parseExpression = parse' "expression" expression
src/Printer.hs view
@@ -67,7 +67,7 @@ instance Pretty MetaValue where pretty (MvAttribute attr) = pretty attr pretty (MvBytes bytes) = pretty bytes- pretty (MvBindings bindings) = pretty bindings+ pretty (MvBindings bindings) = vsep [pretty "[", indent 2 (pretty bindings), pretty "]"] pretty (MvFunction func) = pretty func pretty (MvExpression expr) = pretty expr pretty (MvTail tails) = vsep (punctuate comma (map pretty tails))
src/Rewriter.hs view
@@ -9,8 +9,9 @@ module Rewriter (rewrite) where import Ast-import Builder (buildExpressions)+import Builder import Control.Exception+import Data.Text (intercalate) import Matcher (Subst, matchProgram) import Misc (ensuredFile) import Parser (parseProgram, parseProgramThrows)@@ -18,8 +19,8 @@ import Replacer (replaceProgram) import System.Directory import Text.Printf+import Yaml import qualified Yaml as Y-import Yaml (Rule) data RewriteException = CouldNotMatch {pattern :: Expression, program :: Program}@@ -45,27 +46,87 @@ (printExpression ptn) (printExpression res) +-- Check if given attribute is present in given binding+attrInBinding :: Attribute -> Binding -> Bool+attrInBinding attr (BiTau battr _) = attr == battr+attrInBinding attr (BiVoid battr) = attr == battr+attrInBinding AtLambda (BiLambda _) = True+attrInBinding AtDelta (BiDelta _) = True+attrInBinding _ _ = False++-- Check if all given attributes are present in given bindings+attrsInBindings :: [Attribute] -> [Binding] -> Bool+attrsInBindings [] _ = True+attrsInBindings attrs [] = False+attrsInBindings [attr] (bd : rest) = attrInBinding attr bd || attrsInBindings [attr] rest+attrsInBindings (attr : rest) bds = attrsInBindings [attr] bds && attrsInBindings rest bds++-- For each substitution check if it meets to given condition+-- If substitution does not meet the condition - it's thrown out+-- and is not used in replacement+meets :: Condition -> [Subst] -> [Subst]+meets _ [] = []+-- OR+meets (Or []) substs = substs+meets (Or (cond : rest)) [subst] = case meets cond [subst] of+ [] -> meets (Or rest) [subst]+ substs -> substs+-- AND+meets (And []) substs = substs+meets (And (cond : rest)) [subst] = case meets cond [subst] of+ [] -> []+ _ -> meets (And rest) [subst]+-- NOT+meets (Not cond) [subst] = case meets cond [subst] of+ [] -> [subst]+ _ -> []+-- IN+meets (In attrs bindings) [subst] =+ case (traverse (`buildAttribute` subst) attrs, buildBindings bindings subst) of+ (Just attrs', Just bds) -> [subst | attrsInBindings attrs' bds] -- if attrsInBindings attrs' bds then [subst] else []+ (_, _) -> []+meets (In attrs bindings) (subst : rest) = do+ let cond = In attrs bindings+ substs = meets cond [subst]+ head substs : meets cond rest+-- Any condition with many substitutions+meets cond (subst : rest) = do+ let first = meets cond [subst]+ next = meets cond rest+ if null first+ then next+ else head first : next++-- Build pattern and result expression and replace patterns to results in given program+buildAndReplace :: Program -> Expression -> Expression -> [Subst] -> IO Program+buildAndReplace program ptn res substs = + case (buildExpressions ptn substs, buildExpressions res substs) of+ (Just ptns, Just repls) -> case replaceProgram program ptns repls of+ Just prog -> pure prog+ _ -> throwIO (CouldNotReplace program ptn res)+ (Nothing, _) -> throwIO (CouldNotBuild ptn substs)+ (_, Nothing) -> throwIO (CouldNotBuild res substs)+ applyRules :: Program -> [Y.Rule] -> IO Program applyRules program [] = pure program applyRules program [rule] = do let ptn = Y.pattern rule- let res = Y.result rule+ res = Y.result rule case matchProgram ptn program of [] -> throwIO (CouldNotMatch ptn program)- substs -> case (buildExpressions ptn substs, buildExpressions res substs) of- (Just ptns, Just repls) -> case replaceProgram program ptns repls of- Just prog -> pure prog- _ -> throwIO (CouldNotReplace program ptn res)- (Nothing, _) -> throwIO (CouldNotBuild ptn substs)- (_, Nothing) -> throwIO (CouldNotBuild res substs)+ substs -> do+ let replaced = buildAndReplace program ptn res+ case Y.when rule of+ Just cond -> case meets cond substs of+ [] -> pure program+ substs' -> replaced substs'+ Nothing -> replaced substs applyRules program (rule : rest) = do prog <- applyRules program [rule] applyRules prog rest -rewrite :: String -> Maybe Y.RuleSet -> IO String-rewrite prog rulesSet = do+rewrite :: String -> [Y.Rule] -> IO String+rewrite prog rules = do program <- parseProgramThrows prog- rewritten <- case rulesSet of- Just set -> applyRules program (Y.rules set)- Nothing -> pure program+ rewritten <- applyRules program rules pure (printProgram rewritten)
src/Yaml.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-} -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com -- SPDX-License-Identifier: MIT@@ -7,34 +8,78 @@ module Yaml where import Ast+import Control.Applicative (asum) import Data.Aeson+import Data.Aeson.KeyMap (keys) import Data.String (IsString (..)) import Data.Text (unpack)+import Data.Yaml (Parser) import qualified Data.Yaml as Yaml+import Debug.Trace+import GHC.Base import GHC.Generics import Parser +parseJSON' :: String -> (String -> Either String a) -> Value -> Parser a+parseJSON' name func =+ withText+ name+ ( \txt -> case func (unpack txt) of+ Left err -> fail err+ Right parsed -> pure parsed+ )+ instance FromJSON Expression where+ parseJSON = parseJSON' "Expression" parseExpression++instance FromJSON Attribute where parseJSON = withText- "Expression"- ( \txt -> case parseExpression (unpack txt) of- Left err -> fail err- Right expr -> pure expr+ "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 Binding where+ parseJSON = parseJSON' "Binding" parseBinding -data RuleSet = RuleSet- { title :: String,- rules :: [Rule]- }- deriving (Generic, FromJSON, Show)+instance FromJSON Condition where+ parseJSON =+ withObject+ "Condition"+ ( \v ->+ asum+ [ And <$> v .: "and",+ Or <$> v .: "or",+ Not <$> v .: "not",+ do+ vals <- v .: "in"+ case vals of+ [attrs_, bindings_] -> do+ attrs' <- parseJSON attrs_+ bds <- parseJSON bindings_+ pure (In attrs' bds)+ _ -> fail "'in' must contain exactly two elements"+ ]+ ) +data Condition+ = And [Condition]+ | Or [Condition]+ | In [Attribute] [Binding]+ | Not Condition+ deriving (Generic, Show)+ data Rule = Rule- { name :: String,+ { name :: Maybe String, pattern :: Expression,- result :: Expression+ result :: Expression,+ when :: Maybe Condition } deriving (Generic, FromJSON, Show) -yamlRuleSet :: FilePath -> IO RuleSet-yamlRuleSet = Yaml.decodeFileThrow+yamlRule :: FilePath -> IO Rule+yamlRule = Yaml.decodeFileThrow
test/BuilderSpec.hs view
@@ -60,6 +60,21 @@ ("e2", MvExpression ExThis) ], Just (ExApplication (ExFormation []) [BiTau (AtLabel "x") ExGlobal, BiTau (AtLabel "y") ExThis])+ ),+ ( "⟦!a ↦ ∅, !B⟧.!a => (!a >> t, !B >> ⟦ x ↦ ξ.t ⟧ ) => ⟦ t ↦ ∅, x ↦ ξ.t ⟧.t",+ ExDispatch (ExFormation [BiVoid (AtMeta "a"), BiMeta "B"]) (AtMeta "a"),+ [ ("a", MvAttribute (AtLabel "t")),+ ("B", MvBindings [BiTau (AtLabel "x") (ExDispatch ExThis (AtLabel "t"))])+ ],+ Just+ ( ExDispatch+ ( ExFormation+ [ BiVoid (AtLabel "t"),+ BiTau (AtLabel "x") (ExDispatch ExThis (AtLabel "t"))+ ]+ )+ (AtLabel "t")+ ) ) ]
test/CLISpec.hs view
@@ -13,6 +13,8 @@ import System.IO import Control.Exception import GHC.IO.Handle+import Paths_phino (version)+import Data.Version (showVersion) withRedirectedStdin :: String -> IO a -> IO a withRedirectedStdin input action = do@@ -34,13 +36,29 @@ spec :: Spec spec = do- it "desugares with --nothing flag from file" $ do- let args = ["rewrite", "--nothing", "--phi-input=test/resources/cli/desugar.phi"]- output <- capture_ (runCLI args)- output `shouldContain` "Φ ↦ ⟦\n foo ↦ Φ.org.eolang\n⟧"+ it "prints version" $ do+ output <- capture_ (runCLI ["--version"])+ output `shouldContain` showVersion version - it "desugares with --nothing flag from stdin" $ do- withRedirectedStdin "{[[foo ↦ QQ]]}" $ do- let args = ["rewrite", "--nothing"]+ it "prints help" $ do+ output <- capture_ (runCLI ["--help"])+ output `shouldContain` "Phino - CLI Manipulator of 𝜑-Calculus Expressions"+ output `shouldContain` "Usage:"++ describe "rewrites" $ do+ it "desugares with --nothing flag from file" $ do+ let args = ["rewrite", "--nothing", "--phi-input=test-resources/cli/desugar.phi"] output <- capture_ (runCLI args) output `shouldContain` "Φ ↦ ⟦\n foo ↦ Φ.org.eolang\n⟧"++ it "desugares with --nothing flag from stdin" $ do+ withRedirectedStdin "{[[foo ↦ QQ]]}" $ do+ let args = ["rewrite", "--nothing"]+ output <- capture_ (runCLI args)+ output `shouldContain` "Φ ↦ ⟦\n foo ↦ Φ.org.eolang\n⟧"+ + it "rewrites with single rule" $ do+ withRedirectedStdin "{T(x -> Q.y)}" $ do+ let args = ["rewrite", "--rule=resources/dc.yaml"]+ output <- capture_ (runCLI args)+ output `shouldContain` "Φ ↦ ⊥"
test/MatcherSpec.hs view
@@ -1,5 +1,5 @@-{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com@@ -69,6 +69,31 @@ [ [("e", MvExpression (ExDispatch ExGlobal (AtLabel "org"))), ("a", MvAttribute (AtLabel "eolang"))], [("e", MvExpression ExGlobal), ("a", MvAttribute (AtLabel "org"))] ]+ ),+ ( "⟦!B1, !a ↦ ∅, !B2⟧.!a => ⟦ x ↦ ξ.t, t ↦ ∅ ⟧.t(ρ ↦ ⟦ x ↦ ξ.t, t ↦ ∅ ⟧) => [(!B1 >> ⟦x ↦ ξ.t⟧, !a >> t, !B2 >> ⟦⟧ )]",+ ExDispatch (ExFormation [BiMeta "B1", BiVoid (AtMeta "a"), BiMeta "B2"]) (AtMeta "a"),+ ExApplication+ ( ExDispatch+ ( ExFormation+ [ BiTau (AtLabel "x") (ExDispatch ExThis (AtLabel "t")),+ BiVoid (AtLabel "t")+ ]+ )+ (AtLabel "t")+ )+ [ BiTau+ AtRho+ ( ExFormation+ [ BiTau (AtLabel "x") (ExDispatch ExThis (AtLabel "t")),+ BiVoid (AtLabel "t")+ ]+ )+ ],+ [ [ ("B1", MvBindings [BiTau (AtLabel "x") (ExDispatch ExThis (AtLabel "t"))]),+ ("a", MvAttribute (AtLabel "t")),+ ("B2", MvBindings [])+ ]+ ] ) ] @@ -81,68 +106,68 @@ ("~0 => x => X", AtAlpha 0, AtLabel "x", Nothing) ] - describe "matchNonMetaExactBindings" $- test- matchNonMetaExactBindings- [ ( "[^ -> ?, !a -> ?] => [^ -> ?, x -> ?] => (!a >> x)",- [BiVoid AtRho, BiVoid (AtMeta "a")],- [BiVoid AtRho, BiVoid (AtLabel "x")],- Just [("a", MvAttribute (AtLabel "x"))]- ),- ( "[^ -> ?, !a -> ?] => [!x -> ?, ^ -> ?] => (!a >> x)",- [BiVoid AtRho, BiVoid (AtMeta "a")],- [BiVoid (AtLabel "x"), BiVoid AtRho],- Just [("a", MvAttribute (AtLabel "x"))]- ),- ( "[^ -> ?] => [^ -> ?] => ()",- [BiVoid AtRho],- [BiVoid AtRho],- Just []- )- ]- describe "matchBindings: [binding] => [binding] => substitution" $ test matchBindings- [ ( "[] => [] => ()",+ [ ( "[[]] => [[]] => ()", [], [], Just [] ),- ( "[!B] => T:[x -> ?, D> 01-, L> Func] => (!B >> T)",+ ( "[[!B]] => T:[[x -> ?, D> 01-, L> Func]] => (!B >> T)", [BiMeta "B"], [BiVoid (AtLabel "x"), BiDelta "01-", BiLambda "Func"], Just [("B", MvBindings [BiVoid (AtLabel "x"), BiDelta "01-", BiLambda "Func"])] ),- ( "[D> 00-] => [D> 00-, L> Func] => X",+ ( "[[D> 00-]] => [[D> 00-, L> Func]] => X", [BiDelta "00-"], [BiDelta "00-", BiLambda "Func"], Nothing ),- ( "[y -> ?, !a -> ?] => [x -> ?, y -> ?] => (!a >> x)",+ ( "[[y -> ?, !a -> ?]] => [[y -> ?, x -> ?]] => (!a >> x)", [BiVoid (AtLabel "y"), BiVoid (AtMeta "a")],- [BiVoid (AtLabel "x"), BiVoid (AtLabel "y")],+ [BiVoid (AtLabel "y"), BiVoid (AtLabel "x")], Just [("a", MvAttribute (AtLabel "x"))] ),- ( "[!B, x -> ?] => [x -> ?] => (!B >> [])",+ ( "[[!B, x -> ?]] => [[x -> ?]] => (!B >> [[]])", [BiMeta "B", BiVoid (AtLabel "x")], [BiVoid (AtLabel "x")], Just [("B", MvBindings [])] ),- ( "[!B, x -> ?] => [x -> ?, y -> ?] => (!B >> [y -> ?])",- [BiMeta "B", BiVoid (AtLabel "x")],+ ( "[[!B1, x -> ?, !B2]] => [[x -> ?, y -> ?]] => (!B1 >> [[]], !B2 >> [[y -> ?]])",+ [BiMeta "B1", BiVoid (AtLabel "x"), BiMeta "B2"], [BiVoid (AtLabel "x"), BiVoid (AtLabel "y")],- Just [("B", MvBindings [BiVoid (AtLabel "y")])]+ Just [("B1", MvBindings []), ("B2", MvBindings [BiVoid (AtLabel "y")])] ),- ( "[!B, !x -> ?] => [y -> ?, D> -> 00-, L> Func] => (!x >> y, !B >> [D> -> 00-, L> Func])",- [BiMeta "B", BiVoid (AtMeta "x")],+ ( "[[!B1, !x -> ?, !B2]] => [[y -> ?, D> -> 00-, L> Func]] => (!x >> y, !B1 >> [[]], !B2 >> [[D> -> 00-, L> Func]])",+ [BiMeta "B1", BiVoid (AtMeta "x"), BiMeta "B2"], [BiVoid (AtLabel "y"), BiDelta "00-", BiLambda "Func"],- Just [("B", MvBindings [BiDelta "00-", BiLambda "Func"]), ("x", MvAttribute (AtLabel "y"))]+ Just [("B1", MvBindings []), ("B2", MvBindings [BiDelta "00-", BiLambda "Func"]), ("x", MvAttribute (AtLabel "y"))] ),- ( "[!x -> ?, !y -> ?] => [a -> ?, b -> ?] => (!x >> a, !y >> b)",+ ( "[[!x -> ?, !y -> ?]] => [[a -> ?, b -> ?]] => (!x >> a, !y >> b)", [BiVoid (AtMeta "x"), BiVoid (AtMeta "y")], [BiVoid (AtLabel "a"), BiVoid (AtLabel "b")], Just [("x", MvAttribute (AtLabel "a")), ("y", MvAttribute (AtLabel "b"))]+ ),+ ( "[[t -> ?, !B]] => [[t -> ?, x -> Q, y -> $]] => (!B >> [[x -> Q, y -> $]])",+ [BiVoid (AtLabel "t"), BiMeta "B"],+ [BiVoid (AtLabel "t"), BiTau (AtLabel "x") ExGlobal, BiTau (AtLabel "y") ExThis],+ Just [("B", MvBindings [BiTau (AtLabel "x") ExGlobal, BiTau (AtLabel "y") ExThis])]+ ),+ ( "[[!B, z -> Q]] => [[x -> Q, y -> $, z -> Q]] => (!B >> [[x -> Q, y -> $]])",+ [BiMeta "B", BiTau (AtLabel "z") ExGlobal],+ [BiTau (AtLabel "x") ExGlobal, BiTau (AtLabel "y") ExThis, BiTau (AtLabel "z") ExGlobal],+ Just [("B", MvBindings [BiTau (AtLabel "x") ExGlobal, BiTau (AtLabel "y") ExThis])]+ ),+ ( "[[L> Func, D> 00-]] => [[D> 00-, L> Func]] => X",+ [BiLambda "Func", BiDelta "00-"],+ [BiDelta "00-", BiLambda "Func"],+ Nothing+ ),+ ( "[[t -> ?, !B]] => [[x -> ?, t -> ?]] => X",+ [BiVoid (AtLabel "t"), BiMeta "B"],+ [BiVoid (AtLabel "x"), BiVoid (AtLabel "t")],+ Nothing ) ] @@ -208,31 +233,6 @@ ) ] - describe "matchBindingsWithFiltering: [binding] => [binding] => ([binding], substitution)" $- test- matchBindingsWithFiltering- [ ( "[^ -> ?] => [^ -> ?] => ([], ())",- [BiVoid AtRho],- [BiVoid AtRho],- ([], [])- ),- ( "[x -> ?] => [x -> ?] => ([], ())",- [BiVoid (AtLabel "x")],- [BiVoid (AtLabel "x")],- ([], [])- ),- ( "[x -> ?] => [x -> ?, ^ -> ?] => ([^ -> ?], ())",- [BiVoid (AtLabel "x")],- [BiVoid (AtLabel "x"), BiVoid AtRho],- ([BiVoid AtRho], [])- ),- ( "[!a -> ?] => [x -> ?] => ([], (!a -> x))",- [BiVoid (AtMeta "a")],- [BiVoid (AtLabel "x")],- ([], [("a", MvAttribute (AtLabel "x"))])- )- ]- describe "combine" $ do it "combines empty substitutions" $ do combine substEmpty substEmpty `shouldBe` Just substEmpty@@ -272,9 +272,3 @@ ) second = Subst (Map.singleton "x" (MvAttribute AtPhi)) combine first second `shouldBe` Nothing-- describe "isBindingWithMetaAttr" $ do- it "does not touch simple attr" $ do- isBindingWithMetaAttr (BiVoid (AtLabel "x")) `shouldBe` False- it "recognizes void meta attr" $ do- isBindingWithMetaAttr (BiVoid (AtMeta "x")) `shouldBe` True
test/ParserSpec.hs view
@@ -200,7 +200,8 @@ ) ] )- )+ ),+ ("Q.x(!B)", Just (ExApplication (ExDispatch ExGlobal (AtLabel "x")) [BiMeta "B"])) ] describe "just parses" $@@ -222,7 +223,9 @@ "[[\n x -> \"Hi\",\n y -> 42\n]]", "[[x -> -42, y -> +34]]", "⟦x ↦ Φ.org.eolang(z ↦ ξ.f, x ↦ α0, φ ↦ ρ, t ↦ φ, first ↦ ⟦ λ ⤍ Function_name, Δ ⤍ 42- ⟧)⟧",- "[[x -> 1.00e+3, y -> 2.32e-4]]"+ "[[x -> 1.00e+3, y -> 2.32e-4]]",+ "Q.x(!B)",+ "Q.x(~1 -> Q.y, x -> 5, !B1)" ] (\expr -> it expr (parseExpression expr `shouldSatisfy` isRight)) @@ -238,7 +241,6 @@ "Q.x(x -> ?)", "Q.x(L> Func)", "Q.x(D> --)",- "Q.x(!B)", "Q.x(~1 -> ?)", "Q.x(L> !F)", "Q.x(D> !b)",@@ -246,12 +248,13 @@ "[[x(~1) -> [[]] ]]", "[[y(!e) -> [[]] ]]", "[[z(w) -> Q.x]]",- "Q.x(y(~1) -> [[]])"+ "Q.x(y(~1) -> [[]])",+ "Q.x(1, 2, !B)" ] ) describe "parse packs" $ do- packs <- runIO (allPathsIn "test/resources/parser-packs")+ packs <- runIO (allPathsIn "test-resources/parser-packs") forM_ packs ( \pack -> do
test/ReplacerSpec.hs view
@@ -51,5 +51,55 @@ [ExFormation [BiLambda "Func", BiDelta "00-"]], [ExGlobal], Just (Program ExGlobal)+ ),+ ( "Q -> Q.org.eolang => ([Q.org.eolang, Q.org], [$, $]) => $",+ Program (ExDispatch (ExDispatch ExGlobal (AtLabel "org")) (AtLabel "eolang")),+ [ExDispatch (ExDispatch ExGlobal (AtLabel "org")) (AtLabel "eolang"), ExDispatch ExGlobal (AtLabel "org")],+ [ExThis, ExThis],+ Just (Program ExThis)+ ),+ ( "",+ Program+ ( ExApplication+ ( ExDispatch+ ( ExFormation+ [ BiTau (AtLabel "x") (ExDispatch ExThis (AtLabel "t")),+ BiVoid (AtLabel "t")+ ]+ )+ (AtLabel "t")+ )+ [ BiTau+ AtRho+ ( ExFormation+ [ BiTau (AtLabel "x") (ExDispatch ExThis (AtLabel "t")),+ BiVoid (AtLabel "t")+ ]+ )+ ]+ ),+ [ ExDispatch+ ( ExFormation+ [ BiTau (AtLabel "x") (ExDispatch ExThis (AtLabel "t")),+ BiVoid (AtLabel "t")+ ]+ )+ (AtLabel "t")+ ],+ [ExTermination],+ Just+ ( Program+ ( ExApplication+ ExTermination+ [ BiTau+ AtRho+ ( ExFormation+ [ BiTau (AtLabel "x") (ExDispatch ExThis (AtLabel "t")),+ BiVoid (AtLabel "t")+ ]+ )+ ]+ )+ ) ) ]
test/RewriterSpec.hs view
@@ -11,16 +11,22 @@ import Data.Aeson import Data.Yaml qualified as Yaml import GHC.Generics-import Misc (allPathsIn)+import Misc (allPathsIn, ensuredFile) import Rewriter (rewrite)-import Test.Hspec (Spec, describe, runIO, shouldBe, it)-import qualified Yaml-import System.FilePath (takeBaseName)+import System.FilePath (makeRelative, replaceExtension, (</>))+import Test.Hspec (Spec, describe, it, runIO, shouldBe)+import Yaml qualified as Y +data Rules = Rules+ { basic :: Maybe [String],+ custom :: Maybe [Y.Rule]+ }+ deriving (Generic, FromJSON, Show)+ data YamlPack = YamlPack { input :: String, output :: String,- ruleSet :: Maybe Yaml.RuleSet+ rules :: Maybe Rules } deriving (Generic, FromJSON, Show) @@ -30,14 +36,28 @@ spec :: Spec spec = do describe "rewrite packs" $ do- packs <- runIO (allPathsIn "test/resources/rewriter-packs")+ let resources = "test-resources/rewriter-packs"+ packs <- runIO (allPathsIn resources) forM_ packs ( \pth -> do pack <- runIO $ yamlPack pth let output' = output pack input' = input pack- ruleSet' = ruleSet pack- rewritten <- runIO $ rewrite input' ruleSet'- it (takeBaseName pth) (rewritten `shouldBe` output')+ rules' <- case rules pack of+ Just _rules -> case custom _rules of+ Just custom' -> pure custom'+ _ -> case basic _rules of+ Just basic' ->+ runIO $+ mapM+ ( \name -> do+ yaml <- ensuredFile ("resources" </> replaceExtension name ".yaml")+ Y.yamlRule yaml+ )+ basic'+ _ -> pure []+ Nothing -> pure []+ rewritten <- runIO $ rewrite input' rules'+ it (makeRelative resources pth) (rewritten `shouldBe` output') )
+ test/YamlSpec.hs view
@@ -0,0 +1,22 @@+-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com+-- SPDX-License-Identifier: MIT++module YamlSpec where++import Control.Monad+import Misc+import System.FilePath+import Test.Hspec (Spec, describe, it, runIO, shouldReturn)+import Yaml (yamlRule)++spec :: Spec+spec = do+ describe "parses yaml rule" $ do+ let resources = "test-resources/yaml-packs"+ packs <- runIO (allPathsIn resources)+ forM_+ packs+ ( \pth -> it (makeRelative resources pth) $ do+ _ <- yamlRule pth+ pure () `shouldReturn` ()+ )