packages feed

phino 0.0.0.70 → 0.0.0.71

raw patch · 4 files changed

+165/−47 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

README.md view
@@ -34,7 +34,7 @@  ```bash cabal update-cabal install --overwrite-policy=always phino-0.0.0.69+cabal install --overwrite-policy=always phino-0.0.0.70 phino --version ``` @@ -342,6 +342,13 @@   because such bindings cannot be renamed and would produce duplicates   when spliced at more than one position. When no binding matches the   sentinel, the output equals the input unchanged.+* `graft` - same call shape as `splice` (`𝐵-in`, sentinel, `𝐵-rep`) and the+  same renaming guarantees, but every matched sentinel binding is replaced by+  the renamed copy of `𝐵-rep` instead of preserved in place. Use it when the+  marker must not survive the substitution — for example, when fusing two+  bodies that both produce the same effect and the original marker would fire+  a second time. When no binding matches the sentinel, the output equals the+  input unchanged.  ## Meta variables @@ -385,55 +392,55 @@ === parse/phi ===   warmup:     3 iterations   batches:    10 x 1-  total:      1448938.560 μs-  avg:        144893.856 μs-  min:        129456.053 μs-  max:        178640.839 μs-  std dev:    16904.402 μs+  total:      1283128.483 μs+  avg:        128312.848 μs+  min:        117090.148 μs+  max:        159904.869 μs+  std dev:    16799.275 μs === parse/xmir ===   warmup:     3 iterations   batches:    10 x 1-  total:      7588032.720 μs-  avg:        758803.272 μs-  min:        700986.244 μs-  max:        821408.758 μs-  std dev:    37918.602 μs+  total:      7638674.160 μs+  avg:        763867.416 μs+  min:        696616.654 μs+  max:        834131.587 μs+  std dev:    46054.709 μs === rewrite/normalize ===   warmup:     3 iterations   batches:    10 x 1-  total:      388634.740 μs-  avg:        38863.474 μs-  min:        36132.373 μs-  max:        42039.094 μs-  std dev:    1775.255 μs+  total:      410101.450 μs+  avg:        41010.145 μs+  min:        37322.375 μs+  max:        46790.558 μs+  std dev:    3315.786 μs === print/sweet/multiline ===   warmup:     3 iterations   batches:    10 x 1-  total:      4021506.769 μs-  avg:        402150.677 μs-  min:        396372.506 μs-  max:        409920.442 μs-  std dev:    5075.454 μs+  total:      4536347.235 μs+  avg:        453634.724 μs+  min:        436266.864 μs+  max:        484874.805 μs+  std dev:    14385.579 μs === print/sweet/flat ===   warmup:     3 iterations   batches:    10 x 1-  total:      3909169.505 μs-  avg:        390916.950 μs-  min:        359212.755 μs-  max:        408481.102 μs-  std dev:    17796.878 μs+  total:      4391468.284 μs+  avg:        439146.828 μs+  min:        421949.972 μs+  max:        461548.132 μs+  std dev:    12926.007 μs === print/salty/multiline ===   warmup:     3 iterations   batches:    10 x 1-  total:      13573961.600 μs-  avg:        1357396.160 μs-  min:        1334521.176 μs-  max:        1370074.545 μs-  std dev:    12356.510 μs+  total:      13872760.194 μs+  avg:        1387276.019 μs+  min:        1347848.058 μs+  max:        1435996.600 μs+  std dev:    28902.131 μs ```  The results were calculated in [this GHA job][benchmark-gha]-on 2026-05-21 at 11:38,+on 2026-05-24 at 14:39, on Linux with 4 CPUs.  <!-- benchmark_end -->@@ -469,4 +476,4 @@ [jna]: https://github.com/java-native-access/jna [jna-native]: https://github.com/java-native-access/jna/blob/master/src/com/sun/jna/Native.java [jeo]: https://github.com/objectionary/jeo-maven-plugin-[benchmark-gha]: https://github.com/objectionary/phino/actions/runs/26223356318+[benchmark-gha]: https://github.com/objectionary/phino/actions/runs/26364073830
phino.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: phino-version: 0.0.0.70+version: 0.0.0.71 license: MIT synopsis: Command-Line Manipulator of 𝜑-Calculus Expressions description: Please see the README on GitHub at <https://github.com/objectionary/phino#readme>
src/Functions.hs view
@@ -45,6 +45,7 @@ buildTerm' "sum" = _sum buildTerm' "join" = _join buildTerm' "splice" = _splice+buildTerm' "graft" = _graft buildTerm' func = _unsupported func  argToBytes :: Y.ExtraArgument -> Subst -> IO Bytes@@ -250,14 +251,20 @@         _ -> AtLabel "unknown"  _splice :: BuildTermMethod-_splice [Y.ArgBinding inArg, Y.ArgExpression sentExpr, Y.ArgBinding repArg] subst = do+_splice = _spliceLike "splice" True++_graft :: BuildTermMethod+_graft = _spliceLike "graft" False++_spliceLike :: String -> Bool -> BuildTermMethod+_spliceLike name keepMarker [Y.ArgBinding inArg, Y.ArgExpression sentExpr, Y.ArgBinding repArg] subst = do   inBds <- buildBindingThrows inArg subst   repBds <- buildBindingThrows repArg subst   mapM_ validateRep repBds   (sentinel, _) <- buildExpressionThrows sentExpr subst   let avoid = map Y.ArgAttribute (attributesFromBindings (inBds ++ repBds))-  spliced <- splice inBds sentinel repBds avoid-  pure (TeBindings spliced)+  result <- walk inBds sentinel repBds avoid+  pure (TeBindings result)   where     validateRep :: Binding -> IO ()     validateRep (BiTau (AtLabel _) _) = pure ()@@ -266,19 +273,20 @@       throwIO         ( userError             ( printf-                "Function splice() can only rename τ-labelled bindings in the replacement group, but got '%s' which would produce duplicates when spliced more than once"+                "Function %s() can only rename τ-labelled bindings in the replacement group, but got '%s' which would produce duplicates when applied at more than one position"+                name                 (printBinding bd)             )         )-    splice :: [Binding] -> Expression -> [Binding] -> [Y.ExtraArgument] -> IO [Binding]-    splice [] _ _ _ = pure []-    splice (bd : rest) sent rep avoid+    walk :: [Binding] -> Expression -> [Binding] -> [Y.ExtraArgument] -> IO [Binding]+    walk [] _ _ _ = pure []+    walk (bd : rest) sent rep avoid       | matches sent bd = do           fresh <- traverse (renamed avoid) rep-          tail' <- splice rest sent rep avoid-          pure (fresh ++ bd : tail')+          tail' <- walk rest sent rep avoid+          pure (fresh ++ (if keepMarker then bd : tail' else tail'))       | otherwise = do-          tail' <- splice rest sent rep avoid+          tail' <- walk rest sent rep avoid           pure (bd : tail')     matches :: Expression -> Binding -> Bool     matches sent (BiTau _ (ExFormation bds)) = any (isPhi sent) bds@@ -291,14 +299,15 @@       term <- _randomTau avoid subst       case term of         TeAttribute attr -> pure (BiTau attr expr)-        _ -> throwIO (userError "Failed to generate fresh tau attribute for splice")+        _ -> throwIO (userError (printf "Failed to generate fresh tau attribute for %s" name))     renamed avoid (BiVoid (AtLabel _)) = do       term <- _randomTau avoid subst       case term of         TeAttribute attr -> pure (BiVoid attr)-        _ -> throwIO (userError "Failed to generate fresh tau attribute for splice")+        _ -> throwIO (userError (printf "Failed to generate fresh tau attribute for %s" name))     renamed _ bd = pure bd-_splice _ _ = throwIO (userError "Function splice() requires exactly 3 arguments: input bindings, sentinel expression, replacement bindings")+_spliceLike name _ _ _ =+  throwIO (userError (printf "Function %s() requires exactly 3 arguments: input bindings, sentinel expression, replacement bindings" name))  _unsupported :: BuildTermFunc _unsupported func _ _ = throwIO (userError (printf "Function %s() is not supported or does not exist" func))
test/FunctionsSpec.hs view
@@ -145,6 +145,108 @@         ) ::         IO (Either IOError ())     outcome `shouldSatisfy` isLeft+  Test.Hspec.it "grafts replacement in place of every sentinel match" $ do+    let foo = ExDispatch ExGlobal (AtLabel "foo")+        bar = ExDispatch ExGlobal (AtLabel "bar")+        cpsBody =+          [ phiBinding "x" foo+          , phiBinding "emit1" emit+          , phiBinding "y" bar+          , phiBinding "emit2" emit+          ]+        autoBody =+          [ phiBinding "add1" foo+          , phiBinding "add2" bar+          ]+        subst =+          Subst+            ( Map.fromList+                [ ("B-in", MvBindings cpsBody)+                , ("B-rep", MvBindings autoBody)+                ]+            )+    TeBindings result <-+      buildTerm+        "graft"+        [ ArgBinding (BiMeta "B-in")+        , ArgExpression emit+        , ArgBinding (BiMeta "B-rep")+        ]+        subst+    bds <- uniqueBindings' result+    logDebug (printf "Grafted bindings:\n%s" (printExpression (ExFormation bds)))+    map bodyOf bds+      `shouldBe` [ Just foo+                 , Just foo+                 , Just bar+                 , Just bar+                 , Just foo+                 , Just bar+                 ]+  Test.Hspec.it "returns the input unchanged when graft finds no sentinel" $ do+    let foo = ExDispatch ExGlobal (AtLabel "foo")+        body = [phiBinding "x" foo, phiBinding "y" foo]+        subst =+          Subst+            ( Map.fromList+                [ ("B-in", MvBindings body)+                , ("B-rep", MvBindings [phiBinding "z" foo])+                ]+            )+    TeBindings result <-+      buildTerm+        "graft"+        [ ArgBinding (BiMeta "B-in")+        , ArgExpression emit+        , ArgBinding (BiMeta "B-rep")+        ]+        subst+    result `shouldBe` body+  Test.Hspec.it "produces only unique attributes for many graft positions" $ do+    let foo = ExDispatch ExGlobal (AtLabel "foo")+        body = [phiBinding (T.pack ('e' : show i)) emit | i <- [1 .. 5 :: Int]]+        rep = [phiBinding "a" foo, phiBinding "b" foo]+        subst =+          Subst+            ( Map.fromList+                [ ("B-in", MvBindings body)+                , ("B-rep", MvBindings rep)+                ]+            )+    TeBindings result <-+      buildTerm+        "graft"+        [ ArgBinding (BiMeta "B-in")+        , ArgExpression emit+        , ArgBinding (BiMeta "B-rep")+        ]+        subst+    bds <- uniqueBindings' result+    bds `shouldSatisfy` ((== 5 * length rep) . length)+  Test.Hspec.it "fails fast when graft replacement contains a non-label binding" $ do+    let body = [phiBinding "e1" emit, phiBinding "e2" emit]+        rep = [phiBinding "a" emit, BiVoid AtRho]+        subst =+          Subst+            ( Map.fromList+                [ ("B-in", MvBindings body)+                , ("B-rep", MvBindings rep)+                ]+            )+    outcome <-+      try+        ( void+            ( buildTerm+                "graft"+                [ ArgBinding (BiMeta "B-in")+                , ArgExpression emit+                , ArgBinding (BiMeta "B-rep")+                ]+                subst+            )+        ) ::+        IO (Either IOError ())+    outcome `shouldSatisfy` isLeft   where     isLeft :: Either a b -> Bool     isLeft (Left _) = True