phino 0.0.0.68 → 0.0.0.69
raw patch · 4 files changed
+222/−38 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- README.md +44/−33
- phino.cabal +1/−1
- src/Functions.hs +53/−1
- test/FunctionsSpec.hs +124/−3
README.md view
@@ -34,7 +34,7 @@ ```bash cabal update-cabal install --overwrite-policy=always phino-0.0.0.67+cabal install --overwrite-policy=always phino-0.0.0.68 phino --version ``` @@ -331,6 +331,17 @@ * `join` - accepts list of bindings and returns list of joined bindings. Duplicated `ρ`, `Δ` and `λ` attributes are ignored, all other duplicated attributes are replaced with unique attributes using `random-tau` function.+* `splice` - accepts three arguments: input bindings `𝐵-in`, a sentinel expression,+ and replacement bindings `𝐵-rep`. Returns a new binding group where `𝐵-rep`+ is inserted in front of every binding in `𝐵-in` whose value is a formation+ with `φ` equal to the sentinel. Every spliced copy of `𝐵-rep` has its+ `τ`-labelled attributes renamed via `random-tau` so the resulting binding+ group has no duplicates. `𝐵-rep` must contain only `τ`-labelled bindings+ (`BiTau (AtLabel _) _` or `BiVoid (AtLabel _)`); any `ρ`, `Δ`, `λ`, `φ`,+ `α`, or meta-attribute binding in `𝐵-rep` makes the function fail fast+ 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. ## Meta variables @@ -374,55 +385,55 @@ === parse/phi === warmup: 3 iterations batches: 10 x 1- total: 1031210.405 μs- avg: 103121.040 μs- min: 93623.015 μs- max: 126930.202 μs- std dev: 13077.810 μs+ total: 1431411.012 μs+ avg: 143141.101 μs+ min: 127073.145 μs+ max: 171130.864 μs+ std dev: 15649.917 μs === parse/xmir === warmup: 3 iterations batches: 10 x 1- total: 6217322.833 μs- avg: 621732.283 μs- min: 554694.491 μs- max: 755833.179 μs- std dev: 55468.859 μs+ total: 7598416.608 μs+ avg: 759841.661 μs+ min: 697811.420 μs+ max: 822708.096 μs+ std dev: 41396.013 μs === rewrite/normalize === warmup: 3 iterations batches: 10 x 1- total: 312902.089 μs- avg: 31290.209 μs- min: 29879.534 μs- max: 34353.613 μs- std dev: 1360.426 μs+ total: 364316.104 μs+ avg: 36431.610 μs+ min: 35585.946 μs+ max: 38039.285 μs+ std dev: 724.383 μs === print/sweet/multiline === warmup: 3 iterations batches: 10 x 1- total: 3650499.458 μs- avg: 365049.946 μs- min: 336866.083 μs- max: 392609.052 μs- std dev: 14447.496 μs+ total: 3936466.911 μs+ avg: 393646.691 μs+ min: 388074.584 μs+ max: 398140.650 μs+ std dev: 3449.956 μs === print/sweet/flat === warmup: 3 iterations batches: 10 x 1- total: 3666297.385 μs- avg: 366629.738 μs- min: 342205.526 μs- max: 380537.332 μs- std dev: 10914.667 μs+ total: 3778721.383 μs+ avg: 377872.138 μs+ min: 355092.443 μs+ max: 394891.090 μs+ std dev: 13649.562 μs === print/salty/multiline === warmup: 3 iterations batches: 10 x 1- total: 11397878.380 μs- avg: 1139787.838 μs- min: 1105293.829 μs- max: 1179067.130 μs- std dev: 23579.734 μs+ total: 13588867.661 μs+ avg: 1358886.766 μs+ min: 1332657.250 μs+ max: 1380376.704 μs+ std dev: 13948.257 μs ``` The results were calculated in [this GHA job][benchmark-gha]-on 2026-05-17 at 21:49,+on 2026-05-18 at 22:53, on Linux with 4 CPUs. <!-- benchmark_end -->@@ -458,4 +469,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/26003582110+[benchmark-gha]: https://github.com/objectionary/phino/actions/runs/26064953685
phino.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: phino-version: 0.0.0.68+version: 0.0.0.69 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
@@ -19,7 +19,7 @@ import Matcher import Misc import Parser (parseAttributeThrows, parseNumberThrows)-import Printer (printAttribute, printExpression, printExtraArg)+import Printer (printAttribute, printBinding, printExpression, printExtraArg) import Random (randomString) import Regexp import Text.Printf (printf)@@ -44,6 +44,7 @@ buildTerm' "number" = _number buildTerm' "sum" = _sum buildTerm' "join" = _join+buildTerm' "splice" = _splice buildTerm' func = _unsupported func argToBytes :: Y.ExtraArgument -> Subst -> IO Bytes@@ -247,6 +248,57 @@ case unsafePerformIO (_randomTau (map Y.ArgAttribute (Set.toList attrs)) subst) of TeAttribute attr' -> attr' _ -> AtLabel "unknown"++_splice :: BuildTermMethod+_splice [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)+ where+ validateRep :: Binding -> IO ()+ validateRep (BiTau (AtLabel _) _) = pure ()+ validateRep (BiVoid (AtLabel _)) = pure ()+ validateRep bd =+ 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"+ (printBinding bd)+ )+ )+ splice :: [Binding] -> Expression -> [Binding] -> [Y.ExtraArgument] -> IO [Binding]+ splice [] _ _ _ = pure []+ splice (bd : rest) sent rep avoid+ | matches sent bd = do+ fresh <- traverse (renamed avoid) rep+ tail' <- splice rest sent rep avoid+ pure (fresh ++ bd : tail')+ | otherwise = do+ tail' <- splice rest sent rep avoid+ pure (bd : tail')+ matches :: Expression -> Binding -> Bool+ matches sent (BiTau _ (ExFormation bds)) = any (isPhi sent) bds+ matches _ _ = False+ isPhi :: Expression -> Binding -> Bool+ isPhi sent (BiTau AtPhi expr) = expr == sent+ isPhi _ _ = False+ renamed :: [Y.ExtraArgument] -> Binding -> IO Binding+ renamed avoid (BiTau (AtLabel _) expr) = do+ term <- _randomTau avoid subst+ case term of+ TeAttribute attr -> pure (BiTau attr expr)+ _ -> throwIO (userError "Failed to generate fresh tau attribute for splice")+ 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")+ renamed _ bd = pure bd+_splice _ _ = throwIO (userError "Function splice() requires exactly 3 arguments: input bindings, sentinel expression, replacement bindings") _unsupported :: BuildTermFunc _unsupported func _ _ = throwIO (userError (printf "Function %s() is not supported or does not exist" func))
test/FunctionsSpec.hs view
@@ -6,19 +6,32 @@ module FunctionsSpec where import AST+import Control.Exception (try)+import Control.Monad (void) import Data.Map.Strict qualified as Map+import Data.Text qualified as T import Deps (Term (TeBindings)) import Functions (buildTerm) import Logger (logDebug) import Matcher (MetaValue (MvBindings), Subst (Subst)) import Misc (uniqueBindings') import Printer (printExpression)-import Test.Hspec (Spec, describe, it, shouldBe)+import Test.Hspec (Spec, describe, it, shouldBe, shouldSatisfy) import Text.Printf (printf)-import Yaml (ExtraArgument (ArgBinding))+import Yaml (ExtraArgument (ArgBinding, ArgExpression)) +emit :: Expression+emit = ExDispatch (ExDispatch ExGlobal (AtLabel "hone")) (AtLabel "emit")++phiBinding :: T.Text -> Expression -> Binding+phiBinding name body = BiTau (AtLabel name) (ExFormation [BiTau AtPhi body])++bodyOf :: Binding -> Maybe Expression+bodyOf (BiTau _ (ExFormation [BiTau AtPhi expr])) = Just expr+bodyOf _ = Nothing+ spec :: Test.Hspec.Spec-spec = describe "Functions" $+spec = describe "Functions" $ do Test.Hspec.it "contains only unique bindings after 'join'" $ do let first = ("B1", MvBindings [BiVoid AtRho, BiDelta BtEmpty, BiTau (AtLabel "x") ExGlobal, BiVoid (AtAlpha 0)]) second = ("B2", MvBindings [BiTau AtRho ExThis, BiLambda "Func", BiDelta (BtOne "00"), BiVoid (AtAlpha 1)])@@ -28,3 +41,111 @@ bds' <- uniqueBindings' bds logDebug (printf "Joined bindings:\n%s" (printExpression (ExFormation bds'))) length bds' `shouldBe` 9+ Test.Hspec.it "splices replacement in the correct order before 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+ "splice"+ [ ArgBinding (BiMeta "B-in")+ , ArgExpression emit+ , ArgBinding (BiMeta "B-rep")+ ]+ subst+ bds <- uniqueBindings' result+ logDebug (printf "Spliced bindings:\n%s" (printExpression (ExFormation bds)))+ map bodyOf bds+ `shouldBe` [ Just foo+ , Just foo+ , Just bar+ , Just emit+ , Just bar+ , Just foo+ , Just bar+ , Just emit+ ]+ Test.Hspec.it "returns the input unchanged when no sentinel match is found" $ 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+ "splice"+ [ ArgBinding (BiMeta "B-in")+ , ArgExpression emit+ , ArgBinding (BiMeta "B-rep")+ ]+ subst+ result `shouldBe` body+ Test.Hspec.it "produces only unique attributes for many splice 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+ "splice"+ [ ArgBinding (BiMeta "B-in")+ , ArgExpression emit+ , ArgBinding (BiMeta "B-rep")+ ]+ subst+ bds <- uniqueBindings' result+ bds `shouldSatisfy` ((== length body + 5 * length rep) . length)+ Test.Hspec.it "fails fast when 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+ "splice"+ [ 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+ isLeft _ = False