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 +40/−33
- phino.cabal +1/−1
- src/Functions.hs +22/−13
- test/FunctionsSpec.hs +102/−0
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