packages feed

phino 0.0.136 → 0.0.137

raw patch · 7 files changed

+111/−40 lines, 7 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ CLI.Helpers: parseInputWithAtoms :: String -> IOFormat -> IO (Expression, Atoms)
+ XMIR: type Atoms = Map Text String
+ XMIR: xmirAtoms :: Document -> IO Atoms
- XMIR: XmirContext :: Bool -> Bool -> Bool -> (Expression -> String) -> XmirContext
+ XMIR: XmirContext :: Bool -> Bool -> Bool -> (Expression -> String) -> Atoms -> XmirContext

Files

README.md view
@@ -34,7 +34,7 @@  ```bash cabal update-cabal install --overwrite-policy=always phino-0.0.135+cabal install --overwrite-policy=always phino-0.0.136 phino --version ``` 
phino.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: phino-version: 0.0.136+version: 0.0.137 license: MIT synopsis: Command-Line Manipulator of 𝜑-Calculus Expressions description: Please see the README on GitHub at <https://github.com/objectionary/phino#readme>
src/CLI/Helpers.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}  module CLI.Helpers where @@ -16,6 +17,7 @@ import Data.Functor ((<&>)) import Data.IORef import Data.List (intercalate, nub)+import qualified Data.Map.Strict as M import Data.Maybe import qualified Data.Text as T import Deps (Evaluation (EvRun), Judgment, SaveEvalFunc, SaveStepFunc, State (..), dontSaveEval, emptyNesting, emptyProtocol, endEvalXml, saveEval, saveEvalXml, saveStep)@@ -37,7 +39,7 @@ import System.FilePath (takeDirectory, takeExtension) import System.IO (Handle, IOMode (WriteMode), getContents', hClose, hSetEncoding, openFile, utf8) import Text.Printf (printf)-import XMIR (expressionToXMIR, parseXMIRThrows, printXMIR, xmirToPhi)+import XMIR (Atoms, expressionToXMIR, parseXMIRThrows, printXMIR, xmirAtoms, xmirToPhi) import Yaml (normalizationRules) import qualified Yaml as Y @@ -181,6 +183,14 @@ parseInput phi PHI = parseExpressionThrows phi parseInput xmir XMIR = parseXMIRThrows xmir >>= xmirToPhi parseInput _ LATEX = invalidCLIArguments "LaTeX cannot be used as input format"++-- Parse expression like 'parseInput' does, together with the result types+-- the atoms of an XMIR input carry, which the XMIR writer restores (#1389)+parseInputWithAtoms :: String -> IOFormat -> IO (Expression, Atoms)+parseInputWithAtoms xmir XMIR = do+  doc <- parseXMIRThrows xmir+  (,) <$> xmirToPhi doc <*> xmirAtoms doc+parseInputWithAtoms input format = (,M.empty) <$> parseInput input format  -- The LaTeX sequence path canonizes inside 'rewrittensToLatex', after the meet -- compression (see 'canonizedRewrittens' there); the remaining formats have no
src/CLI/Runners.hs view
@@ -59,7 +59,7 @@   rules <- getRules _normalize _shuffle _rules   validateBreakpoint _breakpoint rules   input <- readInput _inputFile-  expr <- parseInput input _inputFormat+  (expr, atoms) <- parseInputWithAtoms input _inputFormat   validateXmirTopLevel _outputFormat expr   seedTaus expr   logDebug (printf "Amount of rewriting cycles across all the rules: %d, per rule: %d" _maxCycles _maxDepth)@@ -67,7 +67,7 @@         ([], XMIR, XMIR) -> (\_ -> escapeXML input)         ([], _, _) -> (\_ -> escapeXMLText input)         (_, _, _) -> (\rewritten -> escapeXMLText (P.printExpression' rewritten (_sugarType, UNICODE, _flat, _margin)))-      xmirCtx = XmirContext _omitListing _omitComments _hideRho listing+      xmirCtx = XmirContext _omitListing _omitComments _hideRho listing atoms       printCtx = toPrintCtx xmirCtx foc       exclude = (`F.exclude` excluded)       include = (`F.include` included)@@ -157,10 +157,10 @@   [foc] <- validatedDispatches "focus" [_focus]   validateNoOverlap "show" included "hide" excluded   input <- readInput _inputFile-  expr <- parseInput input _inputFormat+  (expr, atoms) <- parseInputWithAtoms input _inputFormat   setStdGen (mkStdGen _seed)   seedTaus expr-  let printCtx = toPrintCtx foc+  let printCtx = toPrintCtx atoms foc       exclude = (`F.exclude` excluded)       include = (`F.include` included)   save <- saveStepFunc _stepsDir printCtx@@ -201,14 +201,14 @@       when         (isJust _inside && _locator /= "Q")         (invalidCLIArguments "The options --inside and --locator cannot be used together, since --inside aims the run at the binding it mints")-    toPrintCtx :: Expression -> PrintContext-    toPrintCtx focus =+    toPrintCtx :: Atoms -> Expression -> PrintContext+    toPrintCtx atoms focus =       PrintCtx         _sugarType         _hideRho         _flat         _margin-        (XmirContext _omitListing _omitComments _hideRho listing)+        (XmirContext _omitListing _omitComments _hideRho listing atoms)         _nonumber         _compress         _canonize@@ -243,10 +243,10 @@   [foc] <- validatedDispatches "focus" [_focus]   validateNoOverlap "show" included "hide" excluded   input <- readInput _inputFile-  expr <- parseInput input _inputFormat+  (expr, atoms) <- parseInputWithAtoms input _inputFormat   setStdGen (mkStdGen _seed)   seedTaus expr-  let printCtx = toPrintCtx foc+  let printCtx = toPrintCtx atoms foc       exclude = (`F.exclude` excluded)       include = (`F.include` included)   save <- saveStepFunc _stepsDir printCtx@@ -275,14 +275,14 @@       when         (isJust _inside && _locator /= "Q")         (invalidCLIArguments "The options --inside and --locator cannot be used together, since --inside aims the run at the binding it mints")-    toPrintCtx :: Expression -> PrintContext-    toPrintCtx focus =+    toPrintCtx :: Atoms -> Expression -> PrintContext+    toPrintCtx atoms focus =       PrintCtx         _sugarType         _hideRho         _flat         _margin-        (XmirContext _omitListing _omitComments _hideRho listing)+        (XmirContext _omitListing _omitComments _hideRho listing atoms)         _nonumber         _compress         _canonize@@ -329,11 +329,11 @@   validateOpts   inputs' <- traverse (readInput . Just) _inputs   setStdGen (mkStdGen _seed)-  exprs <- traverse (`parseInput` _inputFormat) inputs'+  (exprs, atoms) <- unzip <$> traverse (`parseInputWithAtoms` _inputFormat) inputs'   expr <- merge exprs   validateXmirTopLevel _outputFormat expr   let listing = const (escapeXMLText (P.printExpression' expr (_sugarType, UNICODE, _flat, _margin)))-      xmirCtx = XmirContext _omitListing _omitComments False listing+      xmirCtx = XmirContext _omitListing _omitComments False listing (Map.unions atoms)       printCtx = toPrintCtx xmirCtx   expr' <- printInFormat printCtx expr   printOut _targetFile expr'
src/XMIR.hs view
@@ -14,6 +14,8 @@   , parseXMIR   , parseXMIRThrows   , xmirToPhi+  , xmirAtoms+  , Atoms   , defaultXmirContext   , escapeXML   , escapeXMLText@@ -52,8 +54,14 @@   , _omitComments :: Bool   , _hideRho :: Bool   , _listing :: Expression -> String+  , _atoms :: Atoms   } +-- The result type an atom of the EO parser carries in its @atom attribute,+-- like 'Φ.number', keyed by the name of its λ function. The type is no name+-- phino can read, so it lives beside the λ binding and not in it (#1389)+type Atoms = M.Map T.Text String+ -- The 7-character Git SHA of the phino build that produced the document, -- matching the XMIR schema pattern [0-9a-f]{7}. When built outside a git -- checkout gitrev yields "UNKNOWN", which the schema allows us to omit.@@ -61,7 +69,7 @@ gitRevision = take 7 $(gitHash)  defaultXmirContext :: XmirContext-defaultXmirContext = XmirContext True True False (const "")+defaultXmirContext = XmirContext True True False (const "") M.empty  data XMIRException   = UnsupportedTopExpression Expression@@ -160,9 +168,8 @@ formationBinding (BiTau AtRho expr) ctx = Just <$> namedBinding (show AtRho) expr ctx formationBinding (BiTau AtPhi expr) ctx = Just <$> namedBinding (show AtPhi) expr ctx formationBinding (BiDelta bytes) _ = pure (Just (NodeContent (T.pack (printBytes bytes))))-formationBinding (BiLambda (Function name)) _-  | "Φ." `T.isPrefixOf` name = pure (Just (object [("atom", T.unpack name), ("name", show AtLambda)] []))-  | otherwise = pure (Just (object [("name", show AtLambda)] [NodeContent name]))+formationBinding (BiLambda (Function name)) XmirContext{..} =+  pure (Just (object (maybe [] (\atom -> [("atom", atom)]) (M.lookup name _atoms) ++ [("name", show AtLambda)]) [NodeContent name])) formationBinding (BiVoid AtRho) _ = pure Nothing formationBinding (BiVoid AtPhi) _ = pure (Just (object [("name", show AtPhi), ("base", "∅")] [])) formationBinding (BiVoid (AtLabel label)) _ = pure (Just (object [("name", T.unpack label), ("base", "∅")] []))@@ -483,7 +490,7 @@   | not (hasAttr "base" cur) = do       name <- getAttr "name" cur       case name of-        "λ" -> BiLambda . Function <$> atomOrLambda+        "λ" -> BiLambda . Function <$> lambdaName cur fqn         ('α' : _) -> throwIO (InvalidXMIRFormat "Formation child @name can't start with α" cur)         "φ" -> BiTau AtPhi <$> xmirToFormation cur (name : fqn)         "ρ" -> BiTau AtRho <$> xmirToFormation cur (name : fqn)@@ -501,24 +508,48 @@         _ -> do           expr <- xmirToExpression cur fqn           pure (BiTau attr expr)-  where-    -- The λ function name is carried by the text of the marker element. XMIR-    -- coming from elsewhere holds no name, so fall back to the position in the-    -- tree, which is the only hint left-    atomOrLambda :: IO T.Text-    atomOrLambda-      | hasAttr "atom" cur = T.pack <$> getAttr "atom" cur-      | otherwise = lambdaFunction -    lambdaFunction :: IO T.Text-    lambdaFunction-      | hasText cur = T.strip . T.pack <$> getText cur-      | otherwise = pure (T.pack (intercalate "_" ("L" : map (map spell) (reverse fqn))))+-- The λ function name is carried by the text of the marker element. XMIR+-- coming from elsewhere holds no name, so fall back to the position in the+-- tree, which is the only hint left. The @atom attribute the EO parser writes+-- is the result type of the atom and never its name (#1389)+lambdaName :: C.Cursor -> [String] -> IO T.Text+lambdaName cur fqn+  | hasText cur = T.strip . T.pack <$> getText cur+  | otherwise = pure (T.pack (intercalate "_" ("L" : map (map spell) (reverse fqn))))+  where     -- A binding label admits nearly any character, while 'function' admits a     -- digit, an ASCII lowercase letter, '_' and 'φ' only, so everything else     -- folds into '_' and the derived name stays readable back (#1188)     spell :: Char -> Char     spell ch = if isDigit ch || isAsciiLower ch || ch == '_' || ch == 'φ' then ch else '_'++-- The result types of the atoms in a document, keyed by the names 'xmirToPhi'+-- gives their λ functions, so that 'expressionToXMIR' writes them back (#1389).+-- The reader grows the locator of a λ marker by every formation it descends+-- into, that is by every enclosing <o> with @name and neither @base nor @as+xmirAtoms :: Document -> IO Atoms+xmirAtoms xmir = M.fromList <$> mapM entry markers+  where+    markers :: [C.Cursor]+    markers =+      C.fromDocument xmir+        C.$// C.element (toName "o")+        C.>=> C.attributeIs (toName "name") "λ"+        C.>=> C.check (hasAttr "atom")+    entry :: C.Cursor -> IO (T.Text, String)+    entry cur = do+      name <- lambdaName cur (locator cur)+      atom <- getAttr "atom" cur+      pure (name, atom)+    locator :: C.Cursor -> [String]+    locator cur =+      [ T.unpack label+      | enclosing <- cur C.$| (C.ancestor C.>=> C.element (toName "o"))+      , not (hasAttr "base" enclosing)+      , not (hasAttr "as" enclosing)+      , label <- C.attribute (toName "name") enclosing+      ]  -- A formation keeps its Δ data in the text content of its own element, the way -- the printer emits a Δ binding, while the rest of the bindings live in the
test/CLISpec.hs view
@@ -2481,6 +2481,19 @@         ["merge", resource "desugar.phi", "--output=xmir"]         ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<listing>⟦ foo ↦ ξ.x, ρ ↦ ∅ ⟧</listing>", "<o base=\"ξ.x\" name=\"foo\"/>"] +    -- The @atom of an EO atom is its result type, not the name of its λ+    -- function, so the merged 𝜑 names the function after its locator and+    -- the XMIR printed back restores the type (#1389)+    it "names an atom of XMIR after its locator and keeps its type" $ do+      let xmir = "<object><o name=\"number\"><o name=\"plus\"><o base=\"∅\" name=\"b\"/><o atom=\"Φ.number\" name=\"λ\"/></o></o></object>"+      withTempFileContent "phino-atom.xmir" xmir $ \file -> do+        testCLISucceeded+          ["merge", "--input=xmir", "--sweet", "--flat", file]+          ["λ ⤍ L_number_plus"]+        testCLISucceeded+          ["merge", "--input=xmir", "--output=xmir", file]+          ["<o atom=\"Φ.number\" name=\"λ\">L_number_plus</o>"]+     it "reproduces the same output for the same --seed" $ do       let args =             [ "merge"
test/XMIRSpec.hs view
@@ -25,7 +25,7 @@ import Test.Hspec (Spec, anyException, describe, expectationFailure, it, runIO, shouldBe, shouldContain, shouldNotContain, shouldReturn, shouldThrow) import Text.XML (Document (..), Element (..), Node (NodeElement), Prologue (..)) import Text.XML.Cursor qualified as C-import XMIR (XmirContext (XmirContext), defaultXmirContext, escapeXML, expressionToXMIR, parseXMIRThrows, printXMIR, toName, xmirToPhi)+import XMIR (XmirContext (XmirContext), defaultXmirContext, escapeXML, expressionToXMIR, parseXMIRThrows, printXMIR, toName, xmirAtoms, xmirToPhi)  data ParsePack = ParsePack   { failure :: Maybe Bool@@ -233,19 +233,36 @@       expr <- xmirToPhi doc       parseExpressionThrows (printExpression expr) `shouldReturn` expr -  describe "atom result types in XMIR" $+  describe "atom result types in XMIR" $ do+    let atom :: String+        atom = "<object><o name=\"number\"><o name=\"plus\"><o base=\"∅\" name=\"b\"/><o atom=\"Φ.number\" name=\"λ\"/></o></o></object>"     it "survives a round trip through a λ marker" $ do-      doc <- parseXMIRThrows "<object><o name=\"bar\"><o base=\"∅\" name=\"x\"/><o atom=\"Φ.number\" name=\"λ\"/></o></object>"+      doc <- parseXMIRThrows atom       expr <- xmirToPhi doc-      result <- expressionToXMIR expr defaultXmirContext+      atoms <- xmirAtoms doc+      result <- expressionToXMIR expr (XmirContext True True False (const "") atoms)       let printed = printXMIR result       printed `shouldContain` "atom=\"Φ.number\""       printed `shouldNotContain` "<o name=\"λ\">"+    -- The @atom attribute is the result type of the atom, not its name, so+    -- the λ function is still named after its locator (#1389)+    it "names the λ function after its locator, not after the type" $ do+      expr <- parseXMIRThrows atom >>= xmirToPhi+      printExpression expr `shouldContain` "L_number_plus"+      printExpression expr `shouldNotContain` "Φ.number"+      parseExpressionThrows (printExpression expr) `shouldReturn` expr+    it "keeps the type keyed by the λ function name" $+      (parseXMIRThrows atom >>= xmirAtoms) `shouldReturn` M.fromList [("L_number_plus", "Φ.number")]+    it "keeps the type of a λ marker that carries its name" $+      ( parseXMIRThrows "<object><o name=\"x\"><o atom=\"?\" name=\"λ\">Foo</o></o></object>"+          >>= xmirAtoms+      )+        `shouldReturn` M.fromList [("Foo", "?")]    describe "--hide-rho in XMIR" $     it "drops every bound ρ from the printed document" $ do       expr <- parseExpressionThrows "[[ x -> 4, ^ -> [[ y -> 5 ]] ]]"-      doc <- expressionToXMIR expr (XmirContext True False True (const ""))+      doc <- expressionToXMIR expr (XmirContext True False True (const "") M.empty)       let printed = printXMIR doc       printed `shouldContain` "name=\"x\""       printed `shouldNotContain` "name=\"ρ\""@@ -371,7 +388,7 @@    describe "XMIR comments" $ do     let commentedContext :: XmirContext-        commentedContext = XmirContext True False False (const "")+        commentedContext = XmirContext True False False (const "") M.empty      it "includes a decimal comment for a number when comments aren't omitted" $ do       expr <- parseExpressionThrows "[[ x -> 5 ]]"