diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,3 +1,8 @@
+<!--
+SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+SPDX-License-Identifier: MIT
+-->
+
 # Command-Line Manipulator of 𝜑-Calculus Expressions
 
 [![DevOps By Rultor.com](https://www.rultor.com/b/objectionary/phino)](https://www.rultor.com/p/objectionary/phino)
@@ -29,7 +34,7 @@
 
 ```bash
 cabal update
-cabal install --overwrite-policy=always phino-0.0.0.66
+cabal install --overwrite-policy=always phino-0.0.0.67
 phino --version
 ```
 
@@ -78,6 +83,14 @@
 0.0.0.0
 ```
 
+You can ensure scripts are run with a specific version of `phino` using
+the `--pin` global option. It exits with an error when the version supplied
+doesn't match the installed one:
+
+```bash
+phino --pin=0.0.0.67 dataize hello.phi
+```
+
 ## Dataize
 
 Then, you dataize the program:
@@ -339,6 +352,81 @@
 Incorrect usage of meta variables in 𝜑-expression patterns leads to
 parsing errors.
 
+## Benchmark
+
+To run performance benchmarks, you need [Java 8+][java] and [curl][curl].
+Maven is downloaded automatically on first run via `benchmark/mvnw`.
+
+The benchmark uses the compiled [`Native`][jna-native] class from
+[JNA][jna] — a large real-world Java class — as its test input.
+On first run, `make bench` downloads the class, disassembles it to
+[XMIR][xmir] via [jeo-maven-plugin][jeo], converts it to 𝜑 using
+`phino rewrite`, and caches the results in `benchmark/tmp/`.
+Subsequent runs skip straight to the benchmarks.
+
+```bash
+make bench
+```
+
+<!-- benchmark_begin -->
+
+```text
+=== 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
+=== 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
+=== 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
+=== 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
+=== 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
+=== 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
+```
+
+The results were calculated in [this GHA job][benchmark-gha]
+on 2026-05-17 at 21:49,
+on Linux with 4 CPUs.
+
+<!-- benchmark_end -->
+
 ## How to Contribute
 
 Fork repository, make changes, then send us a [pull request][guidelines].
@@ -365,3 +453,9 @@
 [guidelines]: https://www.yegor256.com/2014/04/15/github-guidelines.html
 [xmir]: https://news.eolang.org/2022-11-25-xmir-guide.html
 [latex]: https://en.wikipedia.org/wiki/LaTeX
+[java]: https://www.java.com/en/download/
+[curl]: https://curl.se/
+[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
diff --git a/benchmark/Main.hs b/benchmark/Main.hs
new file mode 100644
--- /dev/null
+++ b/benchmark/Main.hs
@@ -0,0 +1,102 @@
+-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+-- SPDX-License-Identifier: MIT
+
+module Main where
+
+import AST (Expression (ExGlobal))
+import Control.Exception (evaluate)
+import Control.Monad (replicateM, replicateM_)
+import Data.Time.Clock
+import Deps (dontSaveStep)
+import Encoding (Encoding (UNICODE))
+import Functions (buildTerm)
+import Lining (LineFormat (MULTILINE, SINGLELINE))
+import Margin (defaultMargin)
+import Must (Must (MtDisabled))
+import Parser (parseProgramThrows)
+import Printer (printProgram')
+import Rewriter (RewriteContext (RewriteContext), rewrite)
+import Sugar (SugarType (SALTY, SWEET))
+import Text.Printf (printf)
+import XMIR (parseXMIRThrows, xmirToPhi)
+import Yaml (normalizationRules)
+
+warmups :: Int
+warmups = 3
+
+iterations :: Int
+iterations = 10
+
+targetBatchMs :: Double
+targetBatchMs = 20.0
+
+rewriteCtx :: RewriteContext
+rewriteCtx =
+  RewriteContext
+    ExGlobal
+    100
+    100
+    False
+    buildTerm
+    MtDisabled
+    Nothing
+    dontSaveStep
+
+timeAction :: IO a -> IO Double
+timeAction action = do
+  start <- getCurrentTime
+  _ <- evaluate =<< action
+  end <- getCurrentTime
+  pure (realToFrac (diffUTCTime end start) * 1e6)
+
+timeBatch :: Int -> IO a -> IO Double
+timeBatch batch action = do
+  start <- getCurrentTime
+  replicateM_ batch (evaluate =<< action)
+  end <- getCurrentTime
+  pure (realToFrac (diffUTCTime end start) * 1e6 / fromIntegral batch)
+
+calibrate :: IO a -> IO Int
+calibrate action = do
+  t <- timeAction action
+  pure (max 1 (round (targetBatchMs * 1000.0 / t)))
+
+stdDev :: [Double] -> Double -> Double
+stdDev xs avg = sqrt (sum (map (\x -> (x - avg) ^ (2 :: Int)) xs) / fromIntegral (length xs))
+
+runBench :: String -> IO a -> IO ()
+runBench name action = do
+  replicateM_ warmups action
+  batch <- calibrate action
+  times <- replicateM iterations (timeBatch batch action)
+  let total = sum times * fromIntegral batch
+      avg = sum times / fromIntegral iterations
+      mn = minimum times
+      mx = maximum times
+      sd = stdDev times avg
+  putStrLn $ "=== " ++ name ++ " ==="
+  putStrLn $ printf "  warmup:     %d iterations" warmups
+  putStrLn $ printf "  batches:    %d x %d" iterations batch
+  putStrLn $ printf "  total:      %.3f μs" total
+  putStrLn $ printf "  avg:        %.3f μs" avg
+  putStrLn $ printf "  min:        %.3f μs" mn
+  putStrLn $ printf "  max:        %.3f μs" mx
+  putStrLn $ printf "  std dev:    %.3f μs" sd
+
+main :: IO ()
+main = do
+  src <- readFile "benchmark/tmp/native.phi"
+  xsrc <- readFile "benchmark/tmp/Native.xmir"
+  prog <- parseProgramThrows src
+  runBench "parse/phi" (parseProgramThrows src)
+  runBench "parse/xmir" (parseXMIRThrows xsrc >>= xmirToPhi)
+  runBench "rewrite/normalize" (rewrite prog normalizationRules rewriteCtx)
+  runBench
+    "print/sweet/multiline"
+    (evaluate (length (printProgram' prog (SWEET, UNICODE, MULTILINE, defaultMargin))))
+  runBench
+    "print/sweet/flat"
+    (evaluate (length (printProgram' prog (SWEET, UNICODE, SINGLELINE, defaultMargin))))
+  runBench
+    "print/salty/multiline"
+    (evaluate (length (printProgram' prog (SALTY, UNICODE, MULTILINE, defaultMargin))))
diff --git a/phino.cabal b/phino.cabal
--- a/phino.cabal
+++ b/phino.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: phino
-version: 0.0.0.67
+version: 0.0.0.68
 license: MIT
 synopsis: Command-Line Manipulator of 𝜑-Calculus Expressions
 description: Please see the README on GitHub at <https://github.com/objectionary/phino#readme>
@@ -93,7 +93,7 @@
     regex-pcre-builtin >=0.95.2 && <0.96,
     scientific >=0.3.7 && <0.4,
     text >=2.0.2 && <2.2,
-    time >=1.12 && <1.16,
+    time >=1.12 && <1.17,
     utf8-string >=1.0.2 && <1.1,
     vector >=0.13.0 && <0.14,
     xml-conduit >=1.9 && <1.11,
@@ -166,11 +166,24 @@
     process >=1.6.17 && <1.7,
     silently >=1.2.5 && <1.3,
     text >=2.0.2 && <2.2,
-    time >=1.12 && <1.16,
+    time >=1.12 && <1.17,
     xml-conduit >=1.9 && <1.11,
     yaml >=0.11.8 && <0.12,
 
   build-tool-depends:
     hspec-discover:hspec-discover >=2.11.0 && <2.12
+
+  default-language: Haskell2010
+
+-- Benchmark suite
+benchmark bench
+  import: warnings
+  type: exitcode-stdio-1.0
+  main-is: Main.hs
+  hs-source-dirs: benchmark
+  build-depends:
+    base >=4.18.3.0 && <5,
+    phino,
+    time >=1.12 && <1.17,
 
   default-language: Haskell2010
diff --git a/src/AST.hs b/src/AST.hs
--- a/src/AST.hs
+++ b/src/AST.hs
@@ -6,6 +6,8 @@
 -- This module represents AST tree for parsed phi-calculus program
 module AST where
 
+import Data.Text (Text)
+import qualified Data.Text as T
 import GHC.Generics (Generic)
 
 newtype Program = Program Expression
@@ -18,8 +20,8 @@
   | ExTermination
   | ExApplication Expression Binding
   | ExDispatch Expression Attribute
-  | ExMeta String
-  | ExMetaTail Expression String
+  | ExMeta Text
+  | ExMetaTail Expression Text
   | ExPhiMeet (Maybe String) Int Expression
   | ExPhiAgain (Maybe String) Int Expression
   deriving (Eq, Ord, Show, Generic)
@@ -28,36 +30,36 @@
   = BiTau Attribute Expression
   | BiDelta Bytes
   | BiVoid Attribute
-  | BiLambda String
-  | BiMeta String
-  | BiMetaLambda String
+  | BiLambda Text
+  | BiMeta Text
+  | BiMetaLambda Text
   deriving (Eq, Ord, Show, Generic)
 
 data Bytes
   = BtEmpty
   | BtOne String
   | BtMany [String]
-  | BtMeta String
+  | BtMeta Text
   deriving (Eq, Ord, Show, Generic)
 
 data Attribute
-  = AtLabel String
+  = AtLabel Text
   | AtAlpha Int
   | AtPhi
   | AtRho
   | AtLambda
   | AtDelta
-  | AtMeta String
+  | AtMeta Text
   deriving (Eq, Generic, Ord)
 
 instance Show Attribute where
-  show (AtLabel label) = label
+  show (AtLabel label) = T.unpack label
   show (AtAlpha idx) = 'α' : show idx
   show AtRho = "ρ"
   show AtPhi = "φ"
   show AtDelta = "Δ"
   show AtLambda = "λ"
-  show (AtMeta meta) = '!' : meta
+  show (AtMeta meta) = '!' : T.unpack meta
 
 countNodes :: Expression -> Int
 countNodes (ExFormation bds) = 1 + sum (map nodesInBinding bds) + length bds
diff --git a/src/Builder.hs b/src/Builder.hs
--- a/src/Builder.hs
+++ b/src/Builder.hs
@@ -26,6 +26,8 @@
 import Control.Exception (Exception, throwIO)
 import Data.List.NonEmpty (NonEmpty (..))
 import qualified Data.Map.Strict as Map
+import Data.Text (Text)
+import qualified Data.Text as T
 import Matcher
 import Misc (uniqueBindings)
 import Printer
@@ -38,8 +40,8 @@
   | CouldNotBuildBytes {_bts :: Bytes, _msg :: String}
   deriving (Exception)
 
-metaMsg :: String -> String
-metaMsg = printf "meta '%s' is either does not exist or refers to an inappropriate term"
+metaMsg :: Text -> String
+metaMsg = printf "meta '%s' is either does not exist or refers to an inappropriate term" . T.unpack
 
 type Built a = Either String a
 
diff --git a/src/CLI.hs b/src/CLI.hs
--- a/src/CLI.hs
+++ b/src/CLI.hs
@@ -10,9 +10,11 @@
 import CLI.Parsers
 import CLI.Runners
 import CLI.Types
-import Control.Exception.Base (Exception (displayException), SomeException, fromException, handle)
+import Control.Exception.Base (Exception (displayException), SomeException, fromException, handle, throwIO)
+import Data.Version (showVersion)
 import Logger
 import Options.Applicative
+import Paths_phino (version)
 import System.Exit (ExitCode (..), exitFailure)
 
 handler :: SomeException -> IO ()
@@ -32,11 +34,20 @@
         CmdMatch OptsMatch{_logLevel, _logLines} -> (_logLevel, _logLines)
    in setLogConfig level lns
 
+checkPin :: Maybe String -> IO ()
+checkPin Nothing = pure ()
+checkPin (Just expected)
+  | expected == actual = pure ()
+  | otherwise = throwIO (VersionMismatch expected actual)
+  where
+    actual = showVersion version
+
 runCLI :: [String] -> IO ()
 runCLI args = handle handler $ do
-  cmd <- handleParseResult (execParserPure defaultPrefs parserInfo args)
-  setLogger cmd
-  case cmd of
+  CliArgs{_pin, _command} <- handleParseResult (execParserPure defaultPrefs parserInfo args)
+  checkPin _pin
+  setLogger _command
+  case _command of
     CmdRewrite opts -> runRewrite opts
     CmdDataize opts -> runDataize opts
     CmdExplain opts -> runExplain opts
diff --git a/src/CLI/Parsers.hs b/src/CLI/Parsers.hs
--- a/src/CLI/Parsers.hs
+++ b/src/CLI/Parsers.hs
@@ -282,6 +282,7 @@
             <*> optDepthSensitive
             <*> optNonumber
             <*> switch (long "in-place" <> help "Edit file in-place instead of printing to output")
+            <*> switch (long "update" <> help "Skip rewriting if --target file is newer than the input file")
             <*> optSequence
             <*> optCanonize
             <*> optCompress
@@ -344,8 +345,21 @@
         <> command "match" (info matchParser (progDesc "Match 𝜑-program against provided pattern and build matched substitutions"))
     )
 
-parserInfo :: ParserInfo Command
+optPin :: Parser (Maybe String)
+optPin =
+  optional
+    ( strOption
+        ( long "pin"
+            <> metavar "VERSION"
+            <> help "Fail if this version doesn't match the version of phino"
+        )
+    )
+
+cliArgsParser :: Parser CliArgs
+cliArgsParser = CliArgs <$> optPin <*> commandParser
+
+parserInfo :: ParserInfo CliArgs
 parserInfo =
   info
-    (commandParser <**> helper <**> simpleVersioner (showVersion version))
+    (cliArgsParser <**> helper <**> simpleVersioner (showVersion version))
     (fullDesc <> header "Phino - CLI Manipulator of 𝜑-Calculus Expressions")
diff --git a/src/CLI/Runners.hs b/src/CLI/Runners.hs
--- a/src/CLI/Runners.hs
+++ b/src/CLI/Runners.hs
@@ -29,6 +29,8 @@
 import qualified Printer as P
 import Rewriter
 import Rule (RuleContext (..), matchProgramWithRule)
+import System.Directory (doesFileExist, getModificationTime)
+import System.Exit (exitSuccess)
 import Text.Printf (printf)
 import XMIR
 import qualified Yaml as Y
@@ -36,6 +38,7 @@
 runRewrite :: OptsRewrite -> IO ()
 runRewrite OptsRewrite{..} = do
   validateOpts
+  checkUpdate
   excluded <- validatedDispatches "hide" _hide
   included <- validatedDispatches "show" _show
   [loc] <- validatedDispatches "locator" [_locator]
@@ -64,6 +67,9 @@
     validateOpts = do
       when (_inPlace && isNothing _inputFile) (invalidCLIArguments "The option --in-place requires an input file")
       when (_inPlace && isJust _targetFile) (invalidCLIArguments "The options --in-place and --target cannot be used together")
+      when (_update && _inPlace) (invalidCLIArguments "The options --update and --in-place cannot be used together")
+      when (_update && isNothing _targetFile) (invalidCLIArguments "The option --update requires --target")
+      when (_update && isNothing _inputFile) (invalidCLIArguments "The option --update requires an input file")
       when (length _show > 1) (invalidCLIArguments "The option --show can be used only once")
       validateLatexOptions
         _outputFormat
@@ -72,6 +78,16 @@
         [(_meetPopularity, "meet-popularity"), (_meetLength, "meet-length")]
       validateMust' _must
       validateXmirOptions _outputFormat [(_omitListing, "omit-listing"), (_omitComments, "omit-comments")] _focus
+    checkUpdate :: IO ()
+    checkUpdate = case (_update, _inputFile, _targetFile) of
+      (True, Just src, Just tgt) -> do
+        exists <- doesFileExist tgt
+        when exists $ do
+          newer <- (>) <$> getModificationTime tgt <*> getModificationTime src
+          when newer $ do
+            logDebug (printf "Target '%s' is newer than source '%s', skipping rewriting (--update)" tgt src)
+            exitSuccess
+      _ -> pure ()
     validateBreakpoint :: Maybe String -> [Y.Rule] -> IO ()
     validateBreakpoint Nothing _ = pure ()
     validateBreakpoint (Just rule) rules =
diff --git a/src/CLI/Types.hs b/src/CLI/Types.hs
--- a/src/CLI/Types.hs
+++ b/src/CLI/Types.hs
@@ -38,6 +38,7 @@
   | CouldNotDataize
   | CouldNotPrintExpressionInXMIR
   | EmptySubstsOnMatch
+  | VersionMismatch String String
   deriving (Exception)
 
 instance Show CmdException where
@@ -46,6 +47,8 @@
   show CouldNotDataize = "Could not dataize given program"
   show CouldNotPrintExpressionInXMIR = "Could not print expression with --output=xmir, only program printing is allowed"
   show EmptySubstsOnMatch = "Provided pattern was not matched, no substitutions are built"
+  show (VersionMismatch expected actual) =
+    printf "Version mismatch: --pin requires '%s', but this is phino %s" expected actual
 
 data Command
   = CmdRewrite OptsRewrite
@@ -54,6 +57,11 @@
   | CmdMerge OptsMerge
   | CmdMatch OptsMatch
 
+data CliArgs = CliArgs
+  { _pin :: Maybe String
+  , _command :: Command
+  }
+
 data IOFormat = XMIR | PHI | LATEX
   deriving (Eq)
 
@@ -117,6 +125,7 @@
   , _depthSensitive :: Bool
   , _nonumber :: Bool
   , _inPlace :: Bool
+  , _update :: Bool
   , _sequence :: Bool
   , _canonize :: Bool
   , _compress :: Bool
diff --git a/src/CST.hs b/src/CST.hs
--- a/src/CST.hs
+++ b/src/CST.hs
@@ -1,6 +1,7 @@
 {-# LANGUAGE DuplicateRecordFields #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# OPTIONS_GHC -Wno-partial-fields -Wno-name-shadowing #-}
 
@@ -11,6 +12,7 @@
 
 import AST
 import Data.Maybe (isJust)
+import qualified Data.Text as T
 import Misc
 import qualified Yaml as Y
 
@@ -92,7 +94,7 @@
 data EXCLAMATION = EXCL | NO_EXCL
   deriving (Eq, Show)
 
-data META = META {excl :: EXCLAMATION, hd :: META_HEAD, rest :: String}
+data META = META {excl :: EXCLAMATION, hd :: META_HEAD, rest :: T.Text}
   deriving (Eq, Show)
 
 data TAB
@@ -113,8 +115,8 @@
   = PA_TAU {attr :: ATTRIBUTE, arrow :: ARROW, expr :: EXPRESSION}
   | PA_FORMATION {attr :: ATTRIBUTE, voids :: [ATTRIBUTE], arrow :: ARROW, expr :: EXPRESSION}
   | PA_VOID {attr :: ATTRIBUTE, arrow :: ARROW, void :: VOID}
-  | PA_LAMBDA {func :: String}
-  | PA_LAMBDA' {func :: String} -- ASCII version of PA_LAMBDA
+  | PA_LAMBDA {func :: T.Text}
+  | PA_LAMBDA' {func :: T.Text} -- ASCII version of PA_LAMBDA
   | PA_META_LAMBDA {meta :: META}
   | PA_META_LAMBDA' {meta :: META} -- ASCII version of PA_META_LAMBDA'
   | PA_DELTA {bytes :: BYTES}
@@ -167,7 +169,7 @@
   deriving (Eq, Show)
 
 data ATTRIBUTE
-  = AT_LABEL {label :: String}
+  = AT_LABEL {label :: T.Text}
   | AT_ALPHA {alpha :: ALPHA, idx :: Int}
   | AT_RHO {rho :: RHO}
   | AT_PHI {phi :: PHI}
@@ -257,8 +259,8 @@
 toCST' :: ToCST a b => a -> b
 toCST' = (`toCST` (0, EOL))
 
-metaTail :: String -> String
-metaTail = drop 1
+metaTail :: T.Text -> T.Text
+metaTail = T.drop 1
 
 -- This class is used to convert AST to CST
 -- CST is created with sugar and unicode
@@ -340,7 +342,7 @@
                   (TAB tabs)
                   next
     where
-      primitives :: [String]
+      primitives :: [T.Text]
       primitives = ["number", "string"]
       withoutRhosInPrimitives :: Expression -> [Binding] -> ([Binding], [Binding])
       withoutRhosInPrimitives _ [] = ([], [])
@@ -437,7 +439,7 @@
   toCST (BiDelta bts) ctx = PA_DELTA (toCST bts ctx)
   toCST (BiLambda func) _ = PA_LAMBDA func
   toCST (BiMetaLambda mt) _ = PA_META_LAMBDA (META EXCL F (metaTail mt))
-  toCST (BiMeta mt) _ = error $ "BiMeta binding " ++ mt ++ " cannot be converted to PAIR"
+  toCST (BiMeta mt) _ = error $ "BiMeta binding " ++ T.unpack mt ++ " cannot be converted to PAIR"
 
 instance ToCST Binding APP_BINDING where
   toCST bd@(BiTau _ _) ctx = APP_BINDING (toCST bd ctx :: PAIR)
diff --git a/src/Canonizer.hs b/src/Canonizer.hs
--- a/src/Canonizer.hs
+++ b/src/Canonizer.hs
@@ -7,13 +7,14 @@
 module Canonizer (canonize) where
 
 import AST
+import qualified Data.Text as T
 import Rewriter (Rewritten)
 
 canonizeBindings :: [Binding] -> Int -> ([Binding], Int)
 canonizeBindings [] idx = ([], idx)
 canonizeBindings ((BiLambda _) : rest) idx =
   let (bds', idx') = canonizeBindings rest (idx + 1)
-   in (BiLambda ('F' : show idx) : bds', idx')
+   in (BiLambda (T.pack ('F' : show idx)) : bds', idx')
 canonizeBindings (BiTau attr expr : rest) idx =
   let (expr', idx') = canonizeExpression expr idx
       (bds', idx'') = canonizeBindings rest idx'
diff --git a/src/Dataize.hs b/src/Dataize.hs
--- a/src/Dataize.hs
+++ b/src/Dataize.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE DuplicateRecordFields #-}
 {-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# OPTIONS_GHC -Wno-name-shadowing #-}
 {-# OPTIONS_GHC -Wno-unused-record-wildcards #-}
@@ -15,6 +16,7 @@
 import Data.List (partition)
 import Data.List.NonEmpty (NonEmpty (..))
 import qualified Data.List.NonEmpty as NE
+import qualified Data.Text as T
 import Deps (BuildTermFunc, SaveStepFunc, Term (TeAttribute))
 import Locator (locatedExpression, withLocatedExpression)
 import Matcher (substEmpty)
@@ -87,7 +89,7 @@
 -- and expr is expression which program refers to.
 -- If Q refers to formation which contains binding with attribute == tau -
 -- the expression from this binding is returned.
-phiDispatch :: String -> Expression -> Maybe (Expression, String)
+phiDispatch :: T.Text -> Expression -> Maybe (Expression, String)
 phiDispatch tau expr = case expr of
   ExFormation bds -> boundExpr bds
   _ -> Nothing
@@ -213,7 +215,7 @@
   pure bts
 _dataize _ _ = throwIO (userError "Can't call _dataize from atoms with non-formation program")
 
-atom :: String -> Expression -> DataizeContext -> IO Expression
+atom :: T.Text -> Expression -> DataizeContext -> IO Expression
 atom "L_number_plus" self ctx = do
   left <- _dataize (ExDispatch self (AtLabel "x")) ctx
   right <- _dataize (ExDispatch self AtRho) ctx
@@ -245,4 +247,4 @@
         then pure (DataNumber (numToBts first))
         else pure (ExDispatch self (AtLabel "y"))
     _ -> pure ExTermination
-atom func _ _ = throwIO (userError (printf "Atom '%s' does not exist" func))
+atom func _ _ = throwIO (userError (printf "Atom '%s' does not exist" (T.unpack func)))
diff --git a/src/Functions.hs b/src/Functions.hs
--- a/src/Functions.hs
+++ b/src/Functions.hs
@@ -12,6 +12,7 @@
 import qualified Data.ByteString.Char8 as B
 import Data.Functor
 import qualified Data.Set as Set
+import qualified Data.Text as T
 import Deps
 import GHC.IO (unsafePerformIO)
 import Logger (logDebug)
@@ -75,7 +76,7 @@
 _randomTau args subst = do
   attrs <- argsToAttrs args
   tau <- randomTau attrs
-  pure (TeAttribute (AtLabel tau))
+  pure (TeAttribute (AtLabel (T.pack tau)))
   where
     argsToAttrs :: [Y.ExtraArgument] -> IO [String]
     argsToAttrs [] = pure []
@@ -186,7 +187,7 @@
       throwIO
         ( userError
             ( printf
-                "Couldn't convert given expression to 'Φ̇.string' object, only 'Φ̇.number' or 'Φ̇.string' are allowed\n%s"
+                "Couldn't convert given expression to 'Φ.string' object, only 'Φ.number' or 'Φ.string' are allowed\n%s"
                 (printExpression ex)
             )
         )
@@ -194,7 +195,7 @@
 _string [Y.ArgAttribute attr] subst = do
   attr' <- buildAttributeThrows attr subst
   pure (TeExpression (DataString (strToBts (printAttribute attr'))))
-_string _ _ = throwIO (userError "Function string() requires exactly 1 argument as attribute or data expression (Φ̇.number or Φ̇.string)")
+_string _ _ = throwIO (userError "Function string() requires exactly 1 argument as attribute or data expression (Φ.number or Φ.string)")
 
 _number :: BuildTermMethod
 _number [Y.ArgExpression expr] subst = do
@@ -203,8 +204,8 @@
     DataString bts -> do
       num <- parseNumberThrows (btsToUnescapedStr bts)
       pure (TeExpression num)
-    _ -> throwIO (userError (printf "Function number() expects expression to be 'Φ̇.string', but got:\n%s" (printExpression expr')))
-_number _ _ = throwIO (userError "Function number() requires exactly 1 argument as 'Φ̇.string'")
+    _ -> throwIO (userError (printf "Function number() expects expression to be 'Φ.string', but got:\n%s" (printExpression expr')))
+_number _ _ = throwIO (userError "Function number() requires exactly 1 argument as 'Φ.string'")
 
 _sum :: BuildTermMethod
 _sum args subst = do
diff --git a/src/LaTeX.hs b/src/LaTeX.hs
--- a/src/LaTeX.hs
+++ b/src/LaTeX.hs
@@ -1,6 +1,7 @@
 {-# LANGUAGE AllowAmbiguousTypes #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
@@ -23,6 +24,7 @@
 import CST
 import Data.List (intercalate, nub)
 import Data.Maybe (isJust)
+import qualified Data.Text as T
 import Encoding
 import Lining
 import Locator (locatedExpression)
@@ -124,7 +126,7 @@
     popularity = toDouble _meetPopularity / 100.0
 
 renderToLatex :: (ToSalty a, ToASCII a, ToSingleLine a, ToLaTeX a, WithMargin a, Render a) => a -> LatexContext -> String
-renderToLatex renderable LatexContext{..} = render (toLaTeX $ withLineFormat _line $ withMargin _margin $ withEncoding ASCII $ withSugarType _sugar renderable)
+renderToLatex renderable LatexContext{..} = T.unpack $ render (toLaTeX $ withLineFormat _line $ withMargin _margin $ withEncoding ASCII $ withSugarType _sugar renderable)
 
 phiquation :: LatexContext -> String
 phiquation LatexContext{_nonumber = True} = "phiquation*"
@@ -195,8 +197,8 @@
     , ending False ctx
     ]
 
-piped :: String -> String
-piped str = "|" <> str <> "|"
+piped :: T.Text -> T.Text
+piped str = "|" <> toLaTeX str <> "|"
 
 class ToLaTeX a where
   toLaTeX :: a -> a
@@ -218,7 +220,7 @@
   toLaTeX expr = expr
 
 instance ToLaTeX ATTRIBUTE where
-  toLaTeX AT_LABEL{..} = AT_LABEL (piped (toLaTeX label))
+  toLaTeX AT_LABEL{..} = AT_LABEL (piped label)
   toLaTeX AT_META{..} = AT_META (toLaTeX meta)
   toLaTeX AT_LAMBDA{} = AT_LAMBDA LAMBDA'
   toLaTeX AT_REST{} = AT_REST DOTS'
@@ -239,8 +241,8 @@
 
 instance ToLaTeX PAIR where
   toLaTeX PA_DELTA{..} = PA_DELTA' bytes
-  toLaTeX PA_LAMBDA{..} = PA_LAMBDA' (piped (toLaTeX func))
-  toLaTeX PA_LAMBDA'{..} = PA_LAMBDA' (piped (toLaTeX func))
+  toLaTeX PA_LAMBDA{..} = PA_LAMBDA' (piped func)
+  toLaTeX PA_LAMBDA'{..} = PA_LAMBDA' (piped func)
   toLaTeX PA_VOID{..} = PA_VOID (toLaTeX attr) arrow void
   toLaTeX PA_TAU{..} = PA_TAU (toLaTeX attr) arrow (toLaTeX expr)
   toLaTeX PA_FORMATION{..} = PA_FORMATION (toLaTeX attr) (map toLaTeX voids) arrow (toLaTeX expr)
@@ -252,8 +254,8 @@
 
 instance ToLaTeX META where
   toLaTeX META{..} =
-    let idx = readMaybe rest :: Maybe Int
-        rest' = if not (null rest) && length rest <= 2 && isJust idx then '_' : rest else rest
+    let idx = readMaybe (T.unpack rest) :: Maybe Int
+        rest' = if not (T.null rest) && T.length rest <= 2 && isJust idx then T.cons '_' rest else rest
      in META NO_EXCL (toLaTeX hd) rest'
 
 instance ToLaTeX META_HEAD where
@@ -271,17 +273,14 @@
   toLaTeX AAS_EXPR{..} = AAS_EXPR eol tab (toLaTeX expr) (toLaTeX args)
   toLaTeX args = args
 
-instance ToLaTeX String where
-  toLaTeX = escapeUnprintedChars
+instance ToLaTeX T.Text where
+  toLaTeX = T.concatMap escape
     where
-      escapeUnprintedChars :: String -> String
-      escapeUnprintedChars [] = []
-      escapeUnprintedChars (ch : rest) = case ch of
-        '$' -> "\\char36{}" <> escapeUnprintedChars rest
-        '@' -> "\\char64{}" <> escapeUnprintedChars rest
-        '^' -> "\\char94{}" <> escapeUnprintedChars rest
-        '_' -> "\\char95{}" <> escapeUnprintedChars rest
-        _ -> ch : escapeUnprintedChars rest
+      escape '$' = "\\char36{}"
+      escape '@' = "\\char64{}"
+      escape '^' = "\\char94{}"
+      escape '_' = "\\char95{}"
+      escape ch = T.singleton ch
 
 instance ToLaTeX SET where
   toLaTeX ST_BINDING{..} = ST_BINDING (toLaTeX binding)
diff --git a/src/Margin.hs b/src/Margin.hs
--- a/src/Margin.hs
+++ b/src/Margin.hs
@@ -8,6 +8,7 @@
 module Margin (defaultMargin, withMargin, WithMargin) where
 
 import CST
+import qualified Data.Text as T
 import Lining (ToSingleLine (..))
 import Render (Render (..))
 
@@ -42,7 +43,7 @@
     let single = toSingleLine ex
         main = withMargin' cfg expr
         singleMain = toSingleLine main
-        extra' = length (last (lines (render main))) + 4 -- 2 spaces + 2 braces around argument
+        extra' = T.length (last (T.lines (render main))) + 4 -- 2 spaces + 2 braces around argument
         arg = withMargin' (indt, margin) tau
         singleArg = toSingleLine arg
      in if
@@ -54,7 +55,7 @@
     let single = toSingleLine ex
         main = withMargin' cfg expr
         singleMain = toSingleLine main
-        extra' = length (last (lines (render main))) + 4 -- 2 spaces + 2 braces around arguments
+        extra' = T.length (last (T.lines (render main))) + 4 -- 2 spaces + 2 braces around arguments
         exprs = withMargin' (indt, margin) args
         singleExprs = toSingleLine exprs
      in if
@@ -66,7 +67,7 @@
     let single = toSingleLine ex
         main = withMargin' cfg expr
         singleMain = toSingleLine main
-        extra' = length (last (lines (render main))) + 4 -- 2 spaces + 2 braces around arguments
+        extra' = T.length (last (T.lines (render main))) + 4 -- 2 spaces + 2 braces around arguments
         taus' = withMargin' (indt, margin) taus
         singleTaus = toSingleLine taus'
      in if
@@ -118,4 +119,4 @@
   withMargin' _ = id
 
 lengthOf :: Render a => a -> Int
-lengthOf renderable = length (render renderable)
+lengthOf renderable = T.length (render renderable)
diff --git a/src/Matcher.hs b/src/Matcher.hs
--- a/src/Matcher.hs
+++ b/src/Matcher.hs
@@ -11,6 +11,7 @@
 import Data.Map.Strict (Map)
 import qualified Data.Map.Strict as Map
 import Data.Maybe (catMaybes)
+import Data.Text (Text)
 
 -- Meta value
 -- The right part of substitution
@@ -18,7 +19,7 @@
   = MvAttribute Attribute -- !a
   | MvBytes Bytes -- !b
   | MvBindings [Binding] -- !B
-  | MvFunction String -- !F
+  | MvFunction Text -- !F
   | MvExpression Expression Expression -- !e, the second expression is scope, which is closest formation
   | MvTail [Tail] -- !t
   deriving (Eq, Show)
@@ -32,7 +33,7 @@
 
 -- Substitution
 -- Shows the match of meta name to meta value
-newtype Subst = Subst (Map String MetaValue)
+newtype Subst = Subst (Map Text MetaValue)
   deriving (Eq, Show)
 
 -- Empty substitution
@@ -40,7 +41,7 @@
 substEmpty = Subst Map.empty
 
 -- Singleton substitution with one (key -> value) pair
-substSingle :: String -> MetaValue -> Subst
+substSingle :: Text -> MetaValue -> Subst
 substSingle key value = Subst (Map.singleton key value)
 
 defaultScope :: Expression
@@ -51,7 +52,7 @@
 combine :: Subst -> Subst -> Maybe Subst
 combine (Subst a) (Subst b) = combine' (Map.toList b) a
   where
-    combine' :: [(String, MetaValue)] -> Map String MetaValue -> Maybe Subst
+    combine' :: [(Text, MetaValue)] -> Map Text MetaValue -> Maybe Subst
     combine' [] acc = Just (Subst acc)
     combine' ((key, MvExpression tgt scope) : rest) acc = case Map.lookup key acc of
       Just (MvExpression expr' _)
diff --git a/src/Misc.hs b/src/Misc.hs
--- a/src/Misc.hs
+++ b/src/Misc.hs
@@ -3,6 +3,7 @@
 {-# LANGUAGE PatternSynonyms #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE ViewPatterns #-}
+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
 
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
@@ -42,12 +43,12 @@
 import qualified Data.Aeson.Key as Key
 import qualified Data.Aeson.KeyMap as KeyMap
 import Data.Binary.IEEE754
-import Data.Bits (Bits (shiftL), (.|.))
+import Data.Bits (Bits (shiftL, shiftR), (.&.), (.|.))
 import qualified Data.ByteString as B
 import Data.ByteString.Builder (toLazyByteString, word64BE)
 import Data.ByteString.Lazy (unpack)
 import qualified Data.ByteString.Lazy.UTF8 as U
-import Data.Char (isPrint, ord)
+import Data.Char (chr, isDigit, isPrint, ord)
 import Data.List (intercalate)
 import Data.Maybe (catMaybes)
 import qualified Data.Set as Set
@@ -75,22 +76,22 @@
   show FileDoesNotExist{..} = printf "File '%s' does not exist" _file
   show DirectoryDoesNotExist{..} = printf "Directory '%s' does not exist" _dir
 
-matchBaseObject :: Expression -> Maybe String
+matchBaseObject :: Expression -> Maybe T.Text
 matchBaseObject (ExDispatch ExGlobal (AtLabel label)) = Just label
 matchBaseObject _ = Nothing
 
-pattern BaseObject :: String -> Expression
+pattern BaseObject :: T.Text -> Expression
 pattern BaseObject label <- (matchBaseObject -> Just label)
   where
     BaseObject label = ExDispatch ExGlobal (AtLabel label)
 
 -- Minimal matcher function (required for view pattern)
-matchDataObject :: Expression -> Maybe (String, Bytes)
+matchDataObject :: Expression -> Maybe (T.Text, Bytes)
 matchDataObject (ExApplication outer (BiTau (AtAlpha 0) inner)) = case (matchOuter outer, matchInner inner) of
   (Just label, Just bts) -> Just (label, bts)
   _ -> Nothing
   where
-    matchOuter :: Expression -> Maybe String
+    matchOuter :: Expression -> Maybe T.Text
     matchOuter (BaseObject label) = Just label
     matchOuter (ExPhiAgain _ _ (BaseObject label)) = Just label
     matchOuter _ = Nothing
@@ -118,7 +119,7 @@
 pattern DataNumber :: Bytes -> Expression
 pattern DataNumber bts = DataObject "number" bts
 
-pattern DataObject :: String -> Bytes -> Expression
+pattern DataObject :: T.Text -> Bytes -> Expression
 pattern DataObject label bts <- (matchDataObject -> Just (label, bts))
   where
     DataObject label bts =
@@ -250,22 +251,35 @@
 -- [64,20,0,0,0,0,0,0]
 btsToWord8 :: Bytes -> [Word8]
 btsToWord8 BtEmpty = []
-btsToWord8 (BtOne bt) = case readHex bt of
-  [(hex, "")] -> [fromIntegral (hex :: Integer)]
+btsToWord8 (BtOne bt) = [hexByte bt]
+btsToWord8 (BtMany bts) = map hexByte bts
+btsToWord8 (BtMeta mt) = error $ "Cannot convert meta bytes to Word8; " ++ T.unpack mt
+
+hexByte :: String -> Word8
+hexByte [hi, lo] = (nibble hi `shiftL` 4) .|. nibble lo
+  where
+    nibble c
+      | isDigit c = fromIntegral (ord c - ord '0')
+      | c >= 'A' && c <= 'F' = fromIntegral (ord c - ord 'A' + 10)
+      | c >= 'a' && c <= 'f' = fromIntegral (ord c - ord 'a' + 10)
+      | otherwise = error ("Invalid hex digit: " ++ [c])
+hexByte bt = case readHex bt of
+  [(hex, "")] -> fromIntegral (hex :: Integer)
   _ -> error $ "Invalid hex byte; " ++ bt
-btsToWord8 (BtMany []) = []
-btsToWord8 (BtMany (bt : bts)) =
-  case btsToWord8 (BtOne bt) of
-    [byte] -> byte : btsToWord8 (BtMany bts)
-    _ -> error $ "Invalid hex byte; " ++ bt
-btsToWord8 (BtMeta mt) = error $ "Cannot convert meta bytes to Word8; " ++ mt
 
 -- >>> word8ToBytes [64, 20, 0]
 -- BtMany ["40","14","00"]
 word8ToBytes :: [Word8] -> Bytes
 word8ToBytes [] = BtEmpty
-word8ToBytes [w8] = BtOne (printf "%02X" w8)
-word8ToBytes bts = BtMany (map (printf "%02X") bts)
+word8ToBytes [w8] = BtOne (toHex w8)
+word8ToBytes bts = BtMany (map toHex bts)
+
+toHex :: Word8 -> String
+toHex w = [digit (w `shiftR` 4), digit (w .&. 0x0F)]
+  where
+    digit n
+      | n < 10 = chr (fromIntegral n + ord '0')
+      | otherwise = chr (fromIntegral n + ord 'A' - 10)
 
 -- Convert Bytes back to Double
 -- >>> btsToNum (BtMany ["40", "14", "00", "00", "00", "00", "00", "00"])
diff --git a/src/Parser.hs b/src/Parser.hs
--- a/src/Parser.hs
+++ b/src/Parser.hs
@@ -26,6 +26,7 @@
 import Control.Monad (guard)
 import Data.Char (isAsciiLower, isDigit)
 import Data.Scientific (toRealFloat)
+import qualified Data.Text as T
 import Data.Void
 import GHC.Char
 import Misc
@@ -58,7 +59,7 @@
   show CouldNotParseProgram{..} = printf "Couldn't parse given phi program, cause: %s" message
   show CouldNotParseExpression{..} = printf "Couldn't parse given phi expression, cause: %s" message
   show CouldNotParseAttribute{..} = printf "Couldn't parse given attribute, cause: %s" message
-  show CouldNotParseNumber{..} = printf "Couldn't parse given number to 'Φ̇.number', cause: %s" message
+  show CouldNotParseNumber{..} = printf "Couldn't parse given number to 'Φ.number', cause: %s" message
 
 -- White space consumer
 whiteSpace :: Parser ()
@@ -72,11 +73,12 @@
 symbol :: String -> Parser String
 symbol = L.symbol whiteSpace
 
-label' :: Parser String
+-- Parsed as String then packed to Text once; BiLambda keeps String so function stays String
+label' :: Parser T.Text
 label' = lexeme $ do
   first <- oneOf ['a' .. 'z']
   rest <- many (satisfy (`notElem` " \r\n\t,.|':;!?][}{)(⟧⟦") <?> "allowed character")
-  return (first : rest)
+  return (T.pack (first : rest))
 
 function :: Parser String
 function = lexeme $ do
@@ -115,21 +117,22 @@
 metaSuffix :: Parser String
 metaSuffix = lexeme (many (oneOf ('_' : '-' : ['0' .. '9'] ++ ['a' .. 'z'] ++ ['A' .. 'Z']) <?> "meta suffix"))
 
-meta :: Char -> Parser String
+-- Meta variable names are packed to Text once here; all AST meta fields are Text
+meta :: Char -> Parser T.Text
 meta ch = do
   _ <- char '!'
   c <- char ch
   suf <- metaSuffix
-  return (c : suf)
+  return (T.pack (c : suf))
 
-meta' :: Char -> String -> Parser String
+meta' :: Char -> String -> Parser T.Text
 meta' ch uni =
   choice
     [ meta ch
     , do
         _ <- symbol uni
         suf <- metaSuffix
-        return (ch : suf)
+        return (T.pack (ch : suf))
     ]
 
 byte :: Parser String
@@ -286,7 +289,7 @@
     , try metaBinding
     , try $ do
         _ <- lambda
-        BiLambda <$> function
+        BiLambda . T.pack <$> function
     , do
         _ <- lambda
         BiMetaLambda <$> meta 'F'
diff --git a/src/Printer.hs b/src/Printer.hs
--- a/src/Printer.hs
+++ b/src/Printer.hs
@@ -24,6 +24,7 @@
 import CST
 import Data.List (intercalate)
 import qualified Data.Map.Strict as Map
+import qualified Data.Text as T
 import Encoding
 import Lining
 import Margin (defaultMargin, withMargin)
@@ -42,19 +43,19 @@
 logPrintConfig = (SWEET, UNICODE, SINGLELINE, defaultMargin)
 
 printProgram' :: Program -> PrintConfig -> String
-printProgram' prog (sugar, encoding, line, margin) = render (withLineFormat line $ withMargin margin $ withEncoding encoding $ withSugarType sugar $ programToCST prog)
+printProgram' prog (sugar, encoding, line, margin) = T.unpack $ render (withLineFormat line $ withMargin margin $ withEncoding encoding $ withSugarType sugar $ programToCST prog)
 
 printProgram :: Program -> String
 printProgram prog = printProgram' prog defaultPrintConfig
 
 printExpression' :: Expression -> PrintConfig -> String
-printExpression' ex (sugar, encoding, line, margin) = render (withLineFormat line $ withMargin margin $ withEncoding encoding $ withSugarType sugar $ expressionToCST ex)
+printExpression' ex (sugar, encoding, line, margin) = T.unpack $ render (withLineFormat line $ withMargin margin $ withEncoding encoding $ withSugarType sugar $ expressionToCST ex)
 
 printExpression :: Expression -> String
 printExpression ex = printExpression' ex defaultPrintConfig
 
 printAttribute' :: Attribute -> Encoding -> String
-printAttribute' att encoding = render (withEncoding encoding (toCST att (0, NO_EOL) :: ATTRIBUTE))
+printAttribute' att encoding = T.unpack $ render (withEncoding encoding (toCST att (0, NO_EOL) :: ATTRIBUTE))
 
 printAttribute :: Attribute -> String
 printAttribute att =
@@ -68,7 +69,7 @@
 printBinding bd = printBinding' bd defaultPrintConfig
 
 printBytes :: Bytes -> String
-printBytes bts = render (toCST bts (0, NO_EOL) :: BYTES)
+printBytes bts = T.unpack $ render (toCST bts (0, NO_EOL) :: BYTES)
 
 printExtraArg' :: ExtraArgument -> PrintConfig -> String
 printExtraArg' (ArgAttribute att) (_, encoding, _, _) = printAttribute' att encoding
@@ -88,14 +89,14 @@
 printMetaValue (MvExpression ex _) config = printExpression' ex config
 printMetaValue (MvBytes bts) _ = printBytes bts
 printMetaValue (MvBindings bds) config = printExpression' (ExFormation bds) config
-printMetaValue (MvFunction fun) _ = fun
+printMetaValue (MvFunction fun) _ = T.unpack fun
 printMetaValue (MvTail tails) config = intercalate "," (map (`printTail` config) tails)
 
 printSubst :: Subst -> PrintConfig -> String
 printSubst (Subst mp) config =
   intercalate
     "\n"
-    (map (\(key, value) -> key <> " >> " <> printMetaValue value config) (Map.toList mp))
+    (map (\(key, value) -> T.unpack key <> " >> " <> printMetaValue value config) (Map.toList mp))
 
 printSubsts' :: [Subst] -> PrintConfig -> String
 printSubsts' [] _ = "------"
diff --git a/src/Render.hs b/src/Render.hs
--- a/src/Render.hs
+++ b/src/Render.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards #-}
 
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
@@ -8,26 +9,25 @@
 module Render where
 
 import CST
-import Data.List (intercalate)
-import Text.Printf (printf)
+import Data.Text (Text)
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Builder as TLB
 
 class Render a where
-  render :: a -> String
+  render :: a -> Text
 
 instance Render String where
-  render str = str
+  render = T.pack
 
-instance Render Integer where
-  render = show
+instance Render Text where
+  render = id
 
 instance Render Int where
-  render = show
-
-instance Render Double where
-  render = show
+  render = T.pack . show
 
 instance Render Char where
-  render ch = [ch]
+  render = T.singleton
 
 instance Render LCB where
   render LCB = "{"
@@ -102,7 +102,7 @@
 instance Render BYTES where
   render BT_EMPTY = "--"
   render (BT_ONE bte) = render bte <> "-"
-  render (BT_MANY bts) = intercalate "-" bts
+  render (BT_MANY bts) = T.intercalate "-" (map render bts)
   render (BT_META mt) = render mt
 
 instance Render EXCLAMATION where
@@ -130,7 +130,7 @@
   render ALPHA' = "~"
 
 instance Render TAB where
-  render TAB{..} = concat (replicate indent "  ")
+  render TAB{..} = T.replicate indent "  "
   render TAB' = " "
   render NO_TAB = ""
 
@@ -143,19 +143,22 @@
   render PA_FORMATION{voids = [], attr, arrow, expr} = render (PA_TAU attr arrow expr)
   render PA_FORMATION{..} = render attr <> "(" <> render voids <> ")" <> render SPACE <> render arrow <> render SPACE <> render expr
   render PA_LAMBDA{..} = render LAMBDA <> render SPACE <> render DASHED_ARROW <> render SPACE <> render func
-  render PA_LAMBDA'{..} = "L> " <> func
+  render PA_LAMBDA'{..} = "L> " <> render func
   render PA_VOID{..} = render attr <> render SPACE <> render arrow <> render SPACE <> render void
   render PA_DELTA{..} = render DELTA <> render SPACE <> render DASHED_ARROW <> render SPACE <> render bytes
   render PA_DELTA'{..} = "D> " <> render bytes
   render PA_META_LAMBDA{..} = render LAMBDA <> render SPACE <> render DASHED_ARROW <> render SPACE <> render meta
-  render PA_META_LAMBDA'{..} = render "L> " <> render meta
+  render PA_META_LAMBDA'{..} = "L> " <> render meta
   render PA_META_DELTA{..} = render DELTA <> render SPACE <> render DASHED_ARROW <> render SPACE <> render meta
-  render PA_META_DELTA'{..} = render "D> " <> render meta
+  render PA_META_DELTA'{..} = "D> " <> render meta
 
 instance Render BINDINGS where
-  render BDS_EMPTY{} = ""
-  render BDS_PAIR{..} = render COMMA <> render eol <> render tab <> render pair <> render bindings
-  render BDS_META{..} = render COMMA <> render eol <> render tab <> render meta <> render bindings
+  render = TL.toStrict . TLB.toLazyText . binds
+    where
+      binds :: BINDINGS -> TLB.Builder
+      binds BDS_EMPTY{} = mempty
+      binds BDS_PAIR{..} = TLB.fromText (render COMMA <> render eol <> render tab <> render pair) <> binds bindings
+      binds BDS_META{..} = TLB.fromText (render COMMA <> render eol <> render tab <> render meta) <> binds bindings
 
 instance Render APP_BINDING where
   render APP_BINDING{..} = render pair
@@ -169,8 +172,11 @@
   render APP_ARG{..} = render expr <> render args
 
 instance Render APP_ARGS where
-  render AAS_EMPTY = ""
-  render AAS_EXPR{..} = render COMMA <> render eol <> render tab <> render expr <> render args
+  render = TL.toStrict . TLB.toLazyText . arguments
+    where
+      arguments :: APP_ARGS -> TLB.Builder
+      arguments AAS_EMPTY = mempty
+      arguments AAS_EXPR{..} = TLB.fromText (render COMMA <> render eol <> render tab <> render expr) <> arguments args
 
 instance Render EXPRESSION where
   render EX_GLOBAL{..} = render global
@@ -182,18 +188,18 @@
   render EX_APPLICATION{..} = render expr <> render space <> "(" <> render eol <> render tab <> render tau <> render eol' <> render tab' <> ")"
   render EX_APPLICATION_TAUS{..} = render expr <> render space <> "(" <> render eol <> render tab <> render taus <> render eol' <> render tab' <> ")"
   render EX_APPLICATION_EXPRS{..} = render expr <> render space <> "(" <> render eol <> render tab <> render args <> render eol' <> render tab' <> ")"
-  render EX_STRING{..} = '"' : render str <> "\""
-  render EX_NUMBER{..} = either show show num
+  render EX_STRING{..} = "\"" <> render str <> "\""
+  render EX_NUMBER{..} = either (T.pack . show) (T.pack . show) num
   render EX_META{..} = render meta
   render EX_META_TAIL{..} = render expr <> " * " <> render meta
-  render EX_PHI_MEET{..} = "\\phiMeet{" <> maybe "" (++ ":") prefix <> render idx <> "}{ " <> render expr <> " }"
-  render EX_PHI_AGAIN{..} = "\\phiAgain{" <> maybe "" (++ ":") prefix <> render idx <> "}"
+  render EX_PHI_MEET{..} = "\\phiMeet{" <> maybe "" (\p -> T.pack p <> ":") prefix <> render idx <> "}{ " <> render expr <> " }"
+  render EX_PHI_AGAIN{..} = "\\phiAgain{" <> maybe "" (\p -> T.pack p <> ":") prefix <> render idx <> "}"
 
 instance Render [ATTRIBUTE] where
-  render attrs = intercalate ", " (map render attrs)
+  render attrs = T.intercalate ", " (map render attrs)
 
 instance Render ATTRIBUTE where
-  render AT_LABEL{..} = label
+  render AT_LABEL{..} = render label
   render AT_ALPHA{..} = render alpha <> render idx
   render AT_RHO{..} = render rho
   render AT_PHI{..} = render phi
@@ -208,16 +214,16 @@
 
 instance Render SET where
   render ST_BINDING{..} = render binding
-  render ST_ATTRIBUTES{..} = printf "[ %s ]" (intercalate ", " (map render attrs))
+  render ST_ATTRIBUTES{..} = "[ " <> T.intercalate ", " (map render attrs) <> " ]"
 
 instance Render LOGIC_OPERATOR where
   render AND = "\\;\\text{and}\\;"
   render OR = "\\;\\text{or}\\;"
 
 instance Render NUMBER where
-  render INDEX{..} = printf "\\indexof{ %s }" (render attr)
-  render LENGTH{..} = printf "\\vert %s \\vert" (render binding)
-  render LITERAL{..} = show num
+  render INDEX{..} = "\\indexof{ " <> render attr <> " }"
+  render LENGTH{..} = "\\vert " <> render binding <> " \\vert"
+  render LITERAL{..} = T.pack (show num)
 
 instance Render COMPARABLE where
   render CMP_ATTR{..} = render attr
@@ -229,23 +235,23 @@
   render NOT_EQUAL = "\\not="
 
 instance Render CONDITION where
-  render CO_BELONGS{..} = render attr <> render SPACE <> render belongs <> render SPACE <> render set
+  render CO_BELONGS{..} = render attr <> " " <> render belongs <> " " <> render set
   render CO_LOGIC{conditions = [cond]} = render cond
-  render CO_LOGIC{..} = intercalate (printf " %s " (render operator)) (map renderWrapped conditions)
+  render CO_LOGIC{..} = T.intercalate (" " <> render operator <> " ") (map renderWrapped conditions)
     where
-      renderWrapped :: CONDITION -> String
+      renderWrapped :: CONDITION -> Text
       renderWrapped CO_LOGIC{conditions = [cond]} = render cond
-      renderWrapped cond@CO_LOGIC{} = printf "( %s )" (render cond)
+      renderWrapped cond@CO_LOGIC{} = "( " <> render cond <> " )"
       renderWrapped cond = render cond
-  render CO_NF{..} = printf "\\isnormal{ %s }" (render expr)
+  render CO_NF{..} = "\\isnormal{ " <> render expr <> " }"
   render CO_NOT{..} = renderFunc "not" condition
-  render CO_COMPARE{..} = render left <> render SPACE <> render equal <> render SPACE <> render right
-  render CO_MATCHES{..} = printf "matches( %s, %s )" regex (render expr)
-  render CO_PART_OF{..} = printf "part-of( %s, %s )" (render expr) (render binding)
+  render CO_COMPARE{..} = render left <> " " <> render equal <> " " <> render right
+  render CO_MATCHES{..} = "matches( " <> T.pack regex <> ", " <> render expr <> " )"
+  render CO_PART_OF{..} = "part-of( " <> render expr <> ", " <> render binding <> " )"
   render CO_EMPTY = ""
 
-renderFunc :: Render a => String -> a -> String
-renderFunc func renderable = printf "%s( %s )" func (render renderable)
+renderFunc :: Render a => Text -> a -> Text
+renderFunc func renderable = func <> "( " <> render renderable <> " )"
 
 instance Render EXTRA_ARG where
   render ARG_ATTR{..} = render attr
@@ -254,6 +260,6 @@
   render ARG_BYTES{..} = render bytes
 
 instance Render EXTRA where
-  render EXTRA{func = "contextualize", args = arg : rest, ..} = printf "$ %s \\coloneqq \\ctx{ %s }{ %s } $" (render meta) (render arg) (intercalate ", " (map render rest))
-  render EXTRA{func = "scope", args = arg : _, ..} = printf "$ %s \\coloneqq \\scopeof{ %s } $" (render meta) (render arg)
-  render EXTRA{..} = printf "$ %s \\coloneqq %s( %s ) $" (render meta) func (intercalate ", " (map render args))
+  render EXTRA{func = "contextualize", args = arg : rest, ..} = "$ " <> render meta <> " \\coloneqq \\ctx{ " <> render arg <> " }{ " <> T.intercalate ", " (map render rest) <> " } $"
+  render EXTRA{func = "scope", args = arg : _, ..} = "$ " <> render meta <> " \\coloneqq \\scopeof{ " <> render arg <> " } $"
+  render EXTRA{..} = "$ " <> render meta <> " \\coloneqq " <> T.pack func <> "( " <> T.intercalate ", " (map render args) <> " ) $"
diff --git a/src/Sugar.hs b/src/Sugar.hs
--- a/src/Sugar.hs
+++ b/src/Sugar.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE InstanceSigs #-}
 {-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE PatternSynonyms #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# OPTIONS_GHC -Wno-name-shadowing #-}
diff --git a/src/XMIR.hs b/src/XMIR.hs
--- a/src/XMIR.hs
+++ b/src/XMIR.hs
@@ -142,10 +142,10 @@
 formationBinding :: Binding -> XmirContext -> IO (Maybe Node)
 formationBinding (BiTau (AtLabel label) (ExFormation bds)) ctx = do
   inners <- nestedBindings bds ctx
-  pure (Just (object [("name", label)] inners))
+  pure (Just (object [("name", T.unpack label)] inners))
 formationBinding (BiTau (AtLabel label) expr) ctx = do
   (base, children) <- expression expr ctx
-  pure (Just (object [("name", label), ("base", base)] children))
+  pure (Just (object [("name", T.unpack label), ("base", base)] children))
 formationBinding (BiTau AtPhi expr) ctx = do
   (base, children) <- expression expr ctx
   pure (Just (object [("name", show AtPhi), ("base", base)] children))
@@ -154,7 +154,7 @@
 formationBinding (BiLambda _) _ = pure (Just (object [("name", show AtLambda)] []))
 formationBinding (BiVoid AtRho) _ = pure Nothing
 formationBinding (BiVoid AtPhi) _ = pure (Just (object [("name", show AtPhi), ("base", "∅")] []))
-formationBinding (BiVoid (AtLabel label)) _ = pure (Just (object [("name", label), ("base", "∅")] []))
+formationBinding (BiVoid (AtLabel label)) _ = pure (Just (object [("name", T.unpack label), ("base", "∅")] []))
 formationBinding binding _ = throwIO (UnsupportedBinding binding)
 
 nestedBindings :: [Binding] -> XmirContext -> IO [Node]
@@ -207,10 +207,10 @@
     getPackage :: Expression -> IO ([String], Expression)
     getPackage (ExFormation [BiTau (AtLabel label) (ExFormation [bd, BiLambda "Package", BiVoid AtRho]), BiVoid AtRho]) = do
       (pckg, expr') <- getPackage (ExFormation [bd, BiLambda "Package", BiVoid AtRho])
-      pure (label : pckg, expr')
+      pure (T.unpack label : pckg, expr')
     getPackage (ExFormation [BiTau (AtLabel label) (ExFormation [bd, BiLambda "Package", BiVoid AtRho]), BiLambda "Package", BiVoid AtRho]) = do
       (pckg, expr') <- getPackage (ExFormation [bd, BiLambda "Package", BiVoid AtRho])
-      pure (label : pckg, expr')
+      pure (T.unpack label : pckg, expr')
     getPackage (ExFormation [BiTau at ex, BiLambda "Package", BiVoid AtRho]) = pure ([], ExFormation [BiTau at ex, BiVoid AtRho])
     getPackage (ExFormation [bd, BiVoid AtRho]) = pure ([], ExFormation [bd, BiVoid AtRho])
     getPackage ex = throwIO (userError (printf "Can't extract package from given expression:\n %s" (printExpression ex)))
@@ -378,7 +378,7 @@
               if null pckg
                 then pure (Program (ExFormation [obj, BiVoid AtRho]))
                 else
-                  let bd = foldr (\part acc -> BiTau (AtLabel part) (ExFormation [acc, BiLambda "Package", BiVoid AtRho])) obj pckg
+                  let bd = foldr (\part acc -> BiTau (AtLabel (T.pack part)) (ExFormation [acc, BiLambda "Package", BiVoid AtRho])) obj pckg
                    in pure (Program (ExFormation [bd, BiVoid AtRho]))
           | otherwise -> throwIO (InvalidXMIRFormat "Expected single <object> element" doc)
         _ -> throwIO (InvalidXMIRFormat "NodeElement is expected as root element" doc)
@@ -390,17 +390,17 @@
       name <- getAttr "name" cur
       bds <- mapM (`xmirToFormationBinding` (name : fqn)) (cur C.$/ C.element (toName "o")) >>= uniqueBindings'
       case name of
-        "λ" -> pure (BiLambda (intercalate "_" ("L" : reverse fqn)))
+        "λ" -> pure (BiLambda (T.pack (intercalate "_" ("L" : reverse fqn))))
         ('α' : _) -> throwIO (InvalidXMIRFormat "Formation child @name can't start with α" cur)
         "φ" -> pure (BiTau AtPhi (ExFormation (withVoidRho bds)))
-        _ -> pure (BiTau (AtLabel name) (ExFormation (withVoidRho bds)))
+        _ -> pure (BiTau (AtLabel (T.pack name)) (ExFormation (withVoidRho bds)))
   | otherwise = do
       name <- getAttr "name" cur
       base <- getAttr "base" cur
       attr <- case name of
         "φ" -> pure AtPhi
         ('α' : _) -> throwIO (InvalidXMIRFormat "Formation child @name can't start with α" cur)
-        _ -> pure (AtLabel name)
+        _ -> pure (AtLabel (T.pack name))
       case base of
         "∅" -> pure (BiVoid attr)
         _ -> do
@@ -496,7 +496,7 @@
   ch : _
     | ch `notElem` ['a' .. 'z'] -> throwIO (InvalidXMIRFormat (printf "The attribute '%s' must start with ['a'..'z']" attr) cur)
     | '.' `elem` attr -> throwIO (InvalidXMIRFormat "Attribute can't contain dots" cur)
-    | otherwise -> pure (AtLabel attr)
+    | otherwise -> pure (AtLabel (T.pack attr))
   _ -> throwIO (InvalidXMIRFormat (printf "Invalid attribute given: %s" attr) cur)
 
 hasAttr :: String -> C.Cursor -> Bool
diff --git a/test/ASTSpec.hs b/test/ASTSpec.hs
--- a/test/ASTSpec.hs
+++ b/test/ASTSpec.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE OverloadedStrings #-}
 {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
 
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
diff --git a/test/BuilderSpec.hs b/test/BuilderSpec.hs
--- a/test/BuilderSpec.hs
+++ b/test/BuilderSpec.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE OverloadedStrings #-}
+
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
 
@@ -8,10 +10,11 @@
 import Control.Monad
 import Data.Either (isLeft)
 import Data.Map.Strict qualified as Map
+import Data.Text qualified as T
 import Matcher
 import Test.Hspec (Example (Arg), Expectation, Spec, SpecWith, anyException, describe, it, shouldBe, shouldSatisfy, shouldThrow)
 
-test :: (Show a, Eq a) => (a -> Subst -> Either String (a, a)) -> [(String, a, [(String, MetaValue)], Either String (a, a))] -> SpecWith (Arg Expectation)
+test :: (Show a, Eq a) => (a -> Subst -> Either String (a, a)) -> [(String, a, [(T.Text, MetaValue)], Either String (a, a))] -> SpecWith (Arg Expectation)
 test function useCases =
   forM_ useCases $ \(desc, expr, mp, res) ->
     it desc $ function expr (Subst (Map.fromList mp)) `shouldBe` res
diff --git a/test/CLISpec.hs b/test/CLISpec.hs
--- a/test/CLISpec.hs
+++ b/test/CLISpec.hs
@@ -10,11 +10,12 @@
 import Control.Exception
 import Control.Monad (forM_, unless, when)
 import Data.List (intercalate, isInfixOf)
+import Data.Time.Clock (addUTCTime, getCurrentTime)
 import Data.Time.Clock.POSIX (getPOSIXTime)
 import Data.Version (showVersion)
 import GHC.IO.Handle
 import Paths_phino (version)
-import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getTemporaryDirectory, listDirectory, removeDirectoryRecursive, removeFile)
+import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getTemporaryDirectory, listDirectory, removeDirectoryRecursive, removeFile, setModificationTime)
 import System.Exit (ExitCode (ExitFailure))
 import System.FilePath ((</>))
 import System.IO
@@ -71,6 +72,13 @@
     (openTempFile "." pattern)
     (\(path, _) -> removeFile path)
 
+withTempFileContent :: String -> String -> (FilePath -> IO a) -> IO a
+withTempFileContent pattern content action =
+  withTempFile pattern $ \(path, h) -> do
+    hPutStr h content
+    hClose h
+    action path
+
 testCLI' :: [String] -> [String] -> Either ExitCode () -> Expectation
 testCLI' args outputs exit = do
   (out, result) <- withStdout (try (runCLI args) :: IO (Either ExitCode ()))
@@ -110,6 +118,25 @@
       ["--help"]
       ["Phino - CLI Manipulator of 𝜑-Calculus Expressions", "Usage:"]
 
+  describe "--pin" $ do
+    it "succeeds when --pin matches actual version" $
+      withStdin "Q -> [[ ]]" $
+        testCLISucceeded
+          ["--pin=" ++ showVersion version, "rewrite", "--sweet"]
+          ["{⟦⟧}"]
+
+    it "fails when --pin doesn't match actual version" $
+      withStdin "Q -> [[ ]]" $
+        testCLIFailed
+          ["--pin=9.9.9.9", "rewrite"]
+          ["Version mismatch: --pin requires '9.9.9.9', but this is phino " ++ showVersion version]
+
+    it "fails when --pin is empty" $
+      withStdin "Q -> [[ ]]" $
+        testCLIFailed
+          ["--pin=", "rewrite"]
+          ["Version mismatch: --pin requires ''"]
+
   it "prints debug info with --log-level=DEBUG" $
     withStdin "{[[]]}" $
       testCLISucceeded ["rewrite", "--log-level=DEBUG"] ["[DEBUG]:"]
@@ -154,6 +181,24 @@
             ["rewrite", "--in-place", "--target=output.phi", path]
             ["--in-place and --target cannot be used together"]
 
+      it "when --update is used without --target" $
+        withStdin "Q -> [[ ]]" $
+          testCLIFailed
+            ["rewrite", "--update"]
+            ["--update requires --target"]
+
+      it "when --update is used without an input file" $
+        withStdin "Q -> [[ ]]" $
+          testCLIFailed
+            ["rewrite", "--update", "--target=output.phi"]
+            ["--update requires an input file"]
+
+      it "when --update is used with --in-place" $
+        withStdin "Q -> [[ ]]" $
+          testCLIFailed
+            ["rewrite", "--update", "--in-place", "input.phi"]
+            ["--update and --in-place cannot be used together"]
+
       it "with --depth-sensitive" $
         withStdin "Q -> [[ x -> \"x\"]]" $
           testCLIFailed
@@ -693,6 +738,30 @@
         testCLISucceeded ["rewrite", rule "simple.yaml", "--in-place", "--sweet", path] []
         content <- readFile path
         content `shouldBe` "{⟦ x ↦ \"bar\" ⟧}"
+
+    it "skips rewriting with --update when target is newer than source" $
+      withTempFileContent "src-XXXXXX.phi" "Q -> [[ x -> \"foo\" ]]" $ \src ->
+        withTempFileContent "tgt-XXXXXX.phi" "ORIGINAL" $ \tgt -> do
+          now <- getCurrentTime
+          setModificationTime src (addUTCTime (-60) now)
+          setModificationTime tgt now
+          testCLISucceeded
+            ["rewrite", rule "simple.yaml", "--update", "--sweet", "--target=" ++ tgt, src]
+            []
+          content <- readFile tgt
+          content `shouldBe` "ORIGINAL"
+
+    it "rewrites with --update when source is newer than target" $
+      withTempFileContent "src-XXXXXX.phi" "Q -> [[ x -> \"foo\" ]]" $ \src ->
+        withTempFileContent "tgt-XXXXXX.phi" "ORIGINAL" $ \tgt -> do
+          now <- getCurrentTime
+          setModificationTime tgt (addUTCTime (-60) now)
+          setModificationTime src now
+          testCLISucceeded
+            ["rewrite", rule "simple.yaml", "--update", "--sweet", "--target=" ++ tgt, src]
+            []
+          content <- readFile tgt
+          content `shouldBe` "{⟦ x ↦ \"bar\" ⟧}"
 
     it "rewrites with cycles" $
       withStdin "Q -> [[ x -> \"x\" ]]" $
diff --git a/test/CSTSpec.hs b/test/CSTSpec.hs
--- a/test/CSTSpec.hs
+++ b/test/CSTSpec.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
@@ -10,6 +11,7 @@
 import CST
 import Control.Monad (forM_)
 import Data.Aeson
+import Data.Text qualified as T
 import Data.Yaml qualified as Yaml
 import Encoding (Encoding (ASCII), withEncoding)
 import GHC.Generics (Generic)
@@ -24,7 +26,7 @@
 
 data CSTPack = CSTPack
   { program :: String
-  , result :: String
+  , result :: T.Text
   }
   deriving (Generic, Show, FromJSON)
 
diff --git a/test/ConditionSpec.hs b/test/ConditionSpec.hs
--- a/test/ConditionSpec.hs
+++ b/test/ConditionSpec.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE OverloadedStrings #-}
+
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
 
diff --git a/test/DataizeSpec.hs b/test/DataizeSpec.hs
--- a/test/DataizeSpec.hs
+++ b/test/DataizeSpec.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE OverloadedStrings #-}
+
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
 
diff --git a/test/FunctionsSpec.hs b/test/FunctionsSpec.hs
--- a/test/FunctionsSpec.hs
+++ b/test/FunctionsSpec.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE OverloadedStrings #-}
+
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
 
diff --git a/test/MatcherSpec.hs b/test/MatcherSpec.hs
--- a/test/MatcherSpec.hs
+++ b/test/MatcherSpec.hs
@@ -1,6 +1,4 @@
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
@@ -11,16 +9,12 @@
 import Control.Monad (forM_)
 import Data.Map.Strict qualified as Map
 import Data.Maybe (fromMaybe)
+import Data.Text qualified as T
 import Matcher
 import Test.Hspec (Example (Arg), Expectation, Spec, SpecWith, describe, it, shouldBe)
 
-class Expected e where
-  type ExpectedResult e
-  toExpected :: e -> ExpectedResult e
-
-instance Expected [[(String, MetaValue)]] where
-  type ExpectedResult [[(String, MetaValue)]] = [Subst]
-  toExpected = map (Subst . Map.fromList)
+substs :: [[(T.Text, MetaValue)]] -> [Subst]
+substs = map (Subst . Map.fromList)
 
 maybeCombined :: Subst -> Subst -> Subst
 maybeCombined first second =
@@ -29,13 +23,12 @@
     (combine first second)
 
 test ::
-  (Expected e, ExpectedResult e ~ r, Eq r, Show r) =>
-  (a -> a -> b -> r) ->
-  [(String, a, a, b, e)] ->
+  (a -> a -> b -> [Subst]) ->
+  [(String, a, a, b, [Subst])] ->
   SpecWith (Arg Expectation)
 test function useCases =
-  forM_ useCases $ \(desc, ptn, tgt, scope, mp) ->
-    it desc $ function ptn tgt scope `shouldBe` toExpected mp
+  forM_ useCases $ \(desc, ptn, tgt, scope, expected) ->
+    it desc $ function ptn tgt scope `shouldBe` expected
 
 spec :: Spec
 spec = do
@@ -47,7 +40,7 @@
         , ExGlobal
         , ExFormation [BiTau AtPhi ExGlobal, BiTau AtRho ExGlobal]
         , defaultScope
-        , [[], []]
+        , substs [[], []]
         )
       ,
         ( "Q.!a => [[ @ -> Q.y, ^ -> [[ a -> Q.w ]], @ -> Q.y ]] => [(a >> y), (a >> w), (a >> y)]"
@@ -58,7 +51,7 @@
             , BiTau AtPhi (ExDispatch ExGlobal (AtLabel "y"))
             ]
         , defaultScope
-        , [[("a", MvAttribute (AtLabel "y"))], [("a", MvAttribute (AtLabel "w"))], [("a", MvAttribute (AtLabel "y"))]]
+        , substs [[("a", MvAttribute (AtLabel "y"))], [("a", MvAttribute (AtLabel "w"))], [("a", MvAttribute (AtLabel "y"))]]
         )
       ,
         ( "[[!a -> Q.org.!a]] => [[f -> [[x -> Q.org.x]], t -> [[y -> Q.org.y]] => [(!a >> x), (!a >> y)]"
@@ -68,27 +61,27 @@
             , BiTau (AtLabel "t") (ExFormation [BiTau (AtLabel "y") (ExDispatch (ExDispatch ExGlobal (AtLabel "org")) (AtLabel "y"))])
             ]
         , defaultScope
-        , [[("a", MvAttribute (AtLabel "x"))], [("a", MvAttribute (AtLabel "y"))]]
+        , substs [[("a", MvAttribute (AtLabel "x"))], [("a", MvAttribute (AtLabel "y"))]]
         )
       ,
         ( "!e => [[x -> Q]] => [(!e >> [[x -> Q]] ), (!e >> Q)]"
         , ExMeta "e"
         , ExFormation [BiTau (AtLabel "x") ExGlobal]
         , defaultScope
-        ,
-          [ [("e", MvExpression (ExFormation [BiTau (AtLabel "x") ExGlobal]) defaultScope)]
-          , [("e", MvExpression ExGlobal (ExFormation [BiTau (AtLabel "x") ExGlobal]))]
-          ]
+        , substs
+            [ [("e", MvExpression (ExFormation [BiTau (AtLabel "x") ExGlobal]) defaultScope)]
+            , [("e", MvExpression ExGlobal (ExFormation [BiTau (AtLabel "x") ExGlobal]))]
+            ]
         )
       ,
         ( "!e.!a => Q.org.eolang => [(!e >> Q.org, !a >> eolang), (!e >> Q, !a >> org)]"
         , ExDispatch (ExMeta "e") (AtMeta "a")
         , ExDispatch (ExDispatch ExGlobal (AtLabel "org")) (AtLabel "eolang")
         , defaultScope
-        ,
-          [ [("e", MvExpression (ExDispatch ExGlobal (AtLabel "org")) defaultScope), ("a", MvAttribute (AtLabel "eolang"))]
-          , [("e", MvExpression ExGlobal defaultScope), ("a", MvAttribute (AtLabel "org"))]
-          ]
+        , substs
+            [ [("e", MvExpression (ExDispatch ExGlobal (AtLabel "org")) defaultScope), ("a", MvAttribute (AtLabel "eolang"))]
+            , [("e", MvExpression ExGlobal defaultScope), ("a", MvAttribute (AtLabel "org"))]
+            ]
         )
       ,
         ( "⟦!B1, !a ↦ ∅, !B2⟧.!a => ⟦ x ↦ ξ.t, t ↦ ∅ ⟧.t(ρ ↦ ⟦ x ↦ ξ.t, t ↦ ∅ ⟧) => [(!B1 >> ⟦x ↦ ξ.t⟧, !a >> t, !B2 >> ⟦⟧ )]"
@@ -111,13 +104,13 @@
                 )
             )
         , defaultScope
-        ,
-          [
-            [ ("B1", MvBindings [BiTau (AtLabel "x") (ExDispatch ExThis (AtLabel "t"))])
-            , ("a", MvAttribute (AtLabel "t"))
-            , ("B2", MvBindings [])
+        , substs
+            [
+              [ ("B1", MvBindings [BiTau (AtLabel "x") (ExDispatch ExThis (AtLabel "t"))])
+              , ("a", MvAttribute (AtLabel "t"))
+              , ("B2", MvBindings [])
+              ]
             ]
-          ]
         )
       ,
         ( "somebody"
@@ -154,52 +147,52 @@
                 )
             ]
         , defaultScope
-        ,
-          [
+        , substs
             [
-              ( "e0"
-              , MvExpression
-                  ExGlobal
-                  ( ExFormation
-                      [ BiTau (AtLabel "a") ExGlobal
-                      , BiTau (AtLabel "b") ExThis
-                      ]
-                  )
-              )
-            ,
-              ( "e-first"
-              , MvExpression
-                  ExThis
-                  ( ExFormation
-                      [ BiTau (AtLabel "a") ExGlobal
-                      , BiTau (AtLabel "b") ExThis
-                      ]
-                  )
-              )
-            ,
-              ( "e-second"
-              , MvExpression
-                  (ExFormation [BiVoid AtPhi])
-                  ( ExFormation
-                      [ BiTau (AtLabel "a") ExGlobal
-                      , BiTau (AtLabel "b") (ExFormation [BiVoid AtPhi])
-                      ]
-                  )
-              )
+              [
+                ( "e0"
+                , MvExpression
+                    ExGlobal
+                    ( ExFormation
+                        [ BiTau (AtLabel "a") ExGlobal
+                        , BiTau (AtLabel "b") ExThis
+                        ]
+                    )
+                )
+              ,
+                ( "e-first"
+                , MvExpression
+                    ExThis
+                    ( ExFormation
+                        [ BiTau (AtLabel "a") ExGlobal
+                        , BiTau (AtLabel "b") ExThis
+                        ]
+                    )
+                )
+              ,
+                ( "e-second"
+                , MvExpression
+                    (ExFormation [BiVoid AtPhi])
+                    ( ExFormation
+                        [ BiTau (AtLabel "a") ExGlobal
+                        , BiTau (AtLabel "b") (ExFormation [BiVoid AtPhi])
+                        ]
+                    )
+                )
+              ]
             ]
-          ]
         )
       ]
 
   describe "matchAttribute: attribute => attribute => substitution" $
     forM_
-      [ ("~1 => ~1 => [()]", AtAlpha 1, AtAlpha 1, [[]])
-      , ("!a => ^ => [(!a >> ^)]", AtMeta "a", AtRho, [[("a", MvAttribute AtRho)]])
-      , ("!a => @ => [(!a >> @)]", AtMeta "a", AtPhi, [[("a", MvAttribute AtPhi)]])
-      , ("~0 => [] => [()]", AtAlpha 0, AtLabel "x", [])
+      [ ("~1 => ~1 => [()]", AtAlpha 1, AtAlpha 1, substs [[]])
+      , ("!a => ^ => [(!a >> ^)]", AtMeta "a", AtRho, substs [[("a", MvAttribute AtRho)]])
+      , ("!a => @ => [(!a >> @)]", AtMeta "a", AtPhi, substs [[("a", MvAttribute AtPhi)]])
+      , ("~0 => [] => [()]", AtAlpha 0, AtLabel "x", substs [])
       ]
-      ( \(desc, ptn, tgt, mp) ->
-          it desc $ matchAttribute ptn tgt `shouldBe` toExpected mp
+      ( \(desc, ptn, tgt, expected) ->
+          it desc $ matchAttribute ptn tgt `shouldBe` expected
       )
 
   describe "matchBindings: [binding] => [binding] => substitution" $
@@ -210,114 +203,114 @@
         , []
         , []
         , defaultScope
-        , [[]]
+        , substs [[]]
         )
       ,
         ( "[[!B]] => T:[[x -> ?, D> 01-, L> Func]] => (!B >> T)"
         , [BiMeta "B"]
         , [BiVoid (AtLabel "x"), BiDelta (BtOne "01"), BiLambda "Func"]
         , defaultScope
-        , [[("B", MvBindings [BiVoid (AtLabel "x"), BiDelta (BtOne "01"), BiLambda "Func"])]]
+        , substs [[("B", MvBindings [BiVoid (AtLabel "x"), BiDelta (BtOne "01"), BiLambda "Func"])]]
         )
       ,
         ( "[[D> 00-]] => [[D> 00-, L> Func]] => []"
         , [BiDelta (BtOne "00")]
         , [BiDelta (BtOne "00"), BiLambda "Func"]
         , defaultScope
-        , []
+        , substs []
         )
       ,
         ( "[[y -> ?, !a -> ?]] => [[y -> ?, x -> ?]] => (!a >> x)"
         , [BiVoid (AtLabel "y"), BiVoid (AtMeta "a")]
         , [BiVoid (AtLabel "y"), BiVoid (AtLabel "x")]
         , defaultScope
-        , [[("a", MvAttribute (AtLabel "x"))]]
+        , substs [[("a", MvAttribute (AtLabel "x"))]]
         )
       ,
         ( "[[!B, x -> ?]] => [[x -> ?]] => (!B >> [[]])"
         , [BiMeta "B", BiVoid (AtLabel "x")]
         , [BiVoid (AtLabel "x")]
         , defaultScope
-        , [[("B", MvBindings [])]]
+        , substs [[("B", MvBindings [])]]
         )
       ,
         ( "[[!B1, x -> ?, !B2]] => [[x -> ?, y -> ?]] => (!B1 >> [[]], !B2 >> [[y -> ?]])"
         , [BiMeta "B1", BiVoid (AtLabel "x"), BiMeta "B2"]
         , [BiVoid (AtLabel "x"), BiVoid (AtLabel "y")]
         , defaultScope
-        , [[("B1", MvBindings []), ("B2", MvBindings [BiVoid (AtLabel "y")])]]
+        , substs [[("B1", MvBindings []), ("B2", MvBindings [BiVoid (AtLabel "y")])]]
         )
       ,
         ( "[[!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 (BtOne "00"), BiLambda "Func"]
         , defaultScope
-        , [[("B1", MvBindings []), ("B2", MvBindings [BiDelta (BtOne "00"), BiLambda "Func"]), ("x", MvAttribute (AtLabel "y"))]]
+        , substs [[("B1", MvBindings []), ("B2", MvBindings [BiDelta (BtOne "00"), BiLambda "Func"]), ("x", MvAttribute (AtLabel "y"))]]
         )
       ,
         ( "[[!x -> ?, !y -> ?]] => [[a -> ?, b -> ?]] => (!x >> a, !y >> b)"
         , [BiVoid (AtMeta "x"), BiVoid (AtMeta "y")]
         , [BiVoid (AtLabel "a"), BiVoid (AtLabel "b")]
         , defaultScope
-        , [[("x", MvAttribute (AtLabel "a")), ("y", MvAttribute (AtLabel "b"))]]
+        , substs [[("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]
         , defaultScope
-        , [[("B", MvBindings [BiTau (AtLabel "x") ExGlobal, BiTau (AtLabel "y") ExThis])]]
+        , substs [[("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]
         , defaultScope
-        , [[("B", MvBindings [BiTau (AtLabel "x") ExGlobal, BiTau (AtLabel "y") ExThis])]]
+        , substs [[("B", MvBindings [BiTau (AtLabel "x") ExGlobal, BiTau (AtLabel "y") ExThis])]]
         )
       ,
         ( "[[L> Func, D> 00-]] => [[D> 00-, L> Func]] => []"
         , [BiLambda "Func", BiDelta (BtOne "00")]
         , [BiDelta (BtOne "00"), BiLambda "Func"]
         , defaultScope
-        , []
+        , substs []
         )
       ,
         ( "[[t -> ?, !B]] => [[x -> ?, t -> ?]] => []"
         , [BiVoid (AtLabel "t"), BiMeta "B"]
         , [BiVoid (AtLabel "x"), BiVoid (AtLabel "t")]
         , defaultScope
-        , []
+        , substs []
         )
       ,
         ( "[[!B, !a -> ?]] => [[x -> ?, y -> ?]] => (!a >> y, !B >> [[ x -> ? ]] )"
         , [BiMeta "B", BiVoid (AtMeta "a")]
         , [BiVoid (AtLabel "x"), BiVoid (AtLabel "y")]
         , defaultScope
-        , [[("a", MvAttribute (AtLabel "y")), ("B", MvBindings [BiVoid (AtLabel "x")])]]
+        , substs [[("a", MvAttribute (AtLabel "y")), ("B", MvBindings [BiVoid (AtLabel "x")])]]
         )
       ,
         ( "[[!B1, !a -> ?, !B2]] => [[ x -> ?, y -> ?, z -> ? ]] => [(!B1 >> [[]], !a >> x, !B2 >> [[ y -> ?, z -> ? ]]), (...), (...)]"
         , [BiMeta "B1", BiVoid (AtMeta "a"), BiMeta "B2"]
         , [BiVoid (AtLabel "x"), BiVoid (AtLabel "y"), BiVoid (AtLabel "z")]
         , defaultScope
-        ,
-          [
-            [ ("B1", MvBindings [])
-            , ("a", MvAttribute (AtLabel "x"))
-            , ("B2", MvBindings [BiVoid (AtLabel "y"), BiVoid (AtLabel "z")])
-            ]
-          ,
-            [ ("B1", MvBindings [BiVoid (AtLabel "x")])
-            , ("a", MvAttribute (AtLabel "y"))
-            , ("B2", MvBindings [BiVoid (AtLabel "z")])
-            ]
-          ,
-            [ ("B1", MvBindings [BiVoid (AtLabel "x"), BiVoid (AtLabel "y")])
-            , ("a", MvAttribute (AtLabel "z"))
-            , ("B2", MvBindings [])
+        , substs
+            [
+              [ ("B1", MvBindings [])
+              , ("a", MvAttribute (AtLabel "x"))
+              , ("B2", MvBindings [BiVoid (AtLabel "y"), BiVoid (AtLabel "z")])
+              ]
+            ,
+              [ ("B1", MvBindings [BiVoid (AtLabel "x")])
+              , ("a", MvAttribute (AtLabel "y"))
+              , ("B2", MvBindings [BiVoid (AtLabel "z")])
+              ]
+            ,
+              [ ("B1", MvBindings [BiVoid (AtLabel "x"), BiVoid (AtLabel "y")])
+              , ("a", MvAttribute (AtLabel "z"))
+              , ("B2", MvBindings [])
+              ]
             ]
-          ]
         )
       ,
         ( "[[!B1, !a1 -> ?, !B2, !a2 -> ?, !B3]] => [[ a -> ?, b -> ?, x -> ?, y -> ?, z -> ? ]] => [10 substs]"
@@ -330,113 +323,113 @@
           , BiVoid (AtLabel "z")
           ]
         , defaultScope
-        ,
-          [
-            [ ("B1", MvBindings [])
-            , ("a1", MvAttribute (AtLabel "a"))
-            , ("B2", MvBindings [])
-            , ("a2", MvAttribute (AtLabel "b"))
-            , ("B3", MvBindings [BiVoid (AtLabel "x"), BiVoid (AtLabel "y"), BiVoid (AtLabel "z")])
-            ]
-          ,
-            [ ("B1", MvBindings [])
-            , ("a1", MvAttribute (AtLabel "a"))
-            , ("B2", MvBindings [BiVoid (AtLabel "b")])
-            , ("a2", MvAttribute (AtLabel "x"))
-            , ("B3", MvBindings [BiVoid (AtLabel "y"), BiVoid (AtLabel "z")])
-            ]
-          ,
-            [ ("B1", MvBindings [])
-            , ("a1", MvAttribute (AtLabel "a"))
-            , ("B2", MvBindings [BiVoid (AtLabel "b"), BiVoid (AtLabel "x")])
-            , ("a2", MvAttribute (AtLabel "y"))
-            , ("B3", MvBindings [BiVoid (AtLabel "z")])
-            ]
-          ,
-            [ ("B1", MvBindings [])
-            , ("a1", MvAttribute (AtLabel "a"))
-            , ("B2", MvBindings [BiVoid (AtLabel "b"), BiVoid (AtLabel "x"), BiVoid (AtLabel "y")])
-            , ("a2", MvAttribute (AtLabel "z"))
-            , ("B3", MvBindings [])
-            ]
-          ,
-            [ ("B1", MvBindings [BiVoid (AtLabel "a")])
-            , ("a1", MvAttribute (AtLabel "b"))
-            , ("B2", MvBindings [])
-            , ("a2", MvAttribute (AtLabel "x"))
-            , ("B3", MvBindings [BiVoid (AtLabel "y"), BiVoid (AtLabel "z")])
-            ]
-          ,
-            [ ("B1", MvBindings [BiVoid (AtLabel "a")])
-            , ("a1", MvAttribute (AtLabel "b"))
-            , ("B2", MvBindings [BiVoid (AtLabel "x")])
-            , ("a2", MvAttribute (AtLabel "y"))
-            , ("B3", MvBindings [BiVoid (AtLabel "z")])
-            ]
-          ,
-            [ ("B1", MvBindings [BiVoid (AtLabel "a")])
-            , ("a1", MvAttribute (AtLabel "b"))
-            , ("B2", MvBindings [BiVoid (AtLabel "x"), BiVoid (AtLabel "y")])
-            , ("a2", MvAttribute (AtLabel "z"))
-            , ("B3", MvBindings [])
-            ]
-          ,
-            [ ("B1", MvBindings [BiVoid (AtLabel "a"), BiVoid (AtLabel "b")])
-            , ("a1", MvAttribute (AtLabel "x"))
-            , ("B2", MvBindings [])
-            , ("a2", MvAttribute (AtLabel "y"))
-            , ("B3", MvBindings [BiVoid (AtLabel "z")])
-            ]
-          ,
-            [ ("B1", MvBindings [BiVoid (AtLabel "a"), BiVoid (AtLabel "b")])
-            , ("a1", MvAttribute (AtLabel "x"))
-            , ("B2", MvBindings [BiVoid (AtLabel "y")])
-            , ("a2", MvAttribute (AtLabel "z"))
-            , ("B3", MvBindings [])
-            ]
-          ,
-            [ ("B1", MvBindings [BiVoid (AtLabel "a"), BiVoid (AtLabel "b"), BiVoid (AtLabel "x")])
-            , ("a1", MvAttribute (AtLabel "y"))
-            , ("B2", MvBindings [])
-            , ("a2", MvAttribute (AtLabel "z"))
-            , ("B3", MvBindings [])
+        , substs
+            [
+              [ ("B1", MvBindings [])
+              , ("a1", MvAttribute (AtLabel "a"))
+              , ("B2", MvBindings [])
+              , ("a2", MvAttribute (AtLabel "b"))
+              , ("B3", MvBindings [BiVoid (AtLabel "x"), BiVoid (AtLabel "y"), BiVoid (AtLabel "z")])
+              ]
+            ,
+              [ ("B1", MvBindings [])
+              , ("a1", MvAttribute (AtLabel "a"))
+              , ("B2", MvBindings [BiVoid (AtLabel "b")])
+              , ("a2", MvAttribute (AtLabel "x"))
+              , ("B3", MvBindings [BiVoid (AtLabel "y"), BiVoid (AtLabel "z")])
+              ]
+            ,
+              [ ("B1", MvBindings [])
+              , ("a1", MvAttribute (AtLabel "a"))
+              , ("B2", MvBindings [BiVoid (AtLabel "b"), BiVoid (AtLabel "x")])
+              , ("a2", MvAttribute (AtLabel "y"))
+              , ("B3", MvBindings [BiVoid (AtLabel "z")])
+              ]
+            ,
+              [ ("B1", MvBindings [])
+              , ("a1", MvAttribute (AtLabel "a"))
+              , ("B2", MvBindings [BiVoid (AtLabel "b"), BiVoid (AtLabel "x"), BiVoid (AtLabel "y")])
+              , ("a2", MvAttribute (AtLabel "z"))
+              , ("B3", MvBindings [])
+              ]
+            ,
+              [ ("B1", MvBindings [BiVoid (AtLabel "a")])
+              , ("a1", MvAttribute (AtLabel "b"))
+              , ("B2", MvBindings [])
+              , ("a2", MvAttribute (AtLabel "x"))
+              , ("B3", MvBindings [BiVoid (AtLabel "y"), BiVoid (AtLabel "z")])
+              ]
+            ,
+              [ ("B1", MvBindings [BiVoid (AtLabel "a")])
+              , ("a1", MvAttribute (AtLabel "b"))
+              , ("B2", MvBindings [BiVoid (AtLabel "x")])
+              , ("a2", MvAttribute (AtLabel "y"))
+              , ("B3", MvBindings [BiVoid (AtLabel "z")])
+              ]
+            ,
+              [ ("B1", MvBindings [BiVoid (AtLabel "a")])
+              , ("a1", MvAttribute (AtLabel "b"))
+              , ("B2", MvBindings [BiVoid (AtLabel "x"), BiVoid (AtLabel "y")])
+              , ("a2", MvAttribute (AtLabel "z"))
+              , ("B3", MvBindings [])
+              ]
+            ,
+              [ ("B1", MvBindings [BiVoid (AtLabel "a"), BiVoid (AtLabel "b")])
+              , ("a1", MvAttribute (AtLabel "x"))
+              , ("B2", MvBindings [])
+              , ("a2", MvAttribute (AtLabel "y"))
+              , ("B3", MvBindings [BiVoid (AtLabel "z")])
+              ]
+            ,
+              [ ("B1", MvBindings [BiVoid (AtLabel "a"), BiVoid (AtLabel "b")])
+              , ("a1", MvAttribute (AtLabel "x"))
+              , ("B2", MvBindings [BiVoid (AtLabel "y")])
+              , ("a2", MvAttribute (AtLabel "z"))
+              , ("B3", MvBindings [])
+              ]
+            ,
+              [ ("B1", MvBindings [BiVoid (AtLabel "a"), BiVoid (AtLabel "b"), BiVoid (AtLabel "x")])
+              , ("a1", MvAttribute (AtLabel "y"))
+              , ("B2", MvBindings [])
+              , ("a2", MvAttribute (AtLabel "z"))
+              , ("B3", MvBindings [])
+              ]
             ]
-          ]
         )
       ]
 
   describe "matchExpression: expression => pattern => substitution" $
     test
       matchExpression'
-      [ ("$ => $ => [()]", ExThis, ExThis, defaultScope, [[]])
-      , ("Q => Q => [()]", ExGlobal, ExGlobal, defaultScope, [[]])
+      [ ("$ => $ => [()]", ExThis, ExThis, defaultScope, substs [[]])
+      , ("Q => Q => [()]", ExGlobal, ExGlobal, defaultScope, substs [[]])
       ,
         ( "!e => Q => [(!e >> Q)]"
         , ExMeta "e"
         , ExGlobal
         , defaultScope
-        , [[("e", MvExpression ExGlobal defaultScope)]]
+        , substs [[("e", MvExpression ExGlobal defaultScope)]]
         )
       ,
         ( "!e => Q.org(x -> $) => [(!e >> Q.org(x -> $))]"
         , ExMeta "e"
         , ExApplication (ExDispatch ExGlobal (AtLabel "org")) (BiTau (AtLabel "x") ExThis)
         , defaultScope
-        , [[("e", MvExpression (ExApplication (ExDispatch ExGlobal (AtLabel "org")) (BiTau (AtLabel "x") ExThis)) defaultScope)]]
+        , substs [[("e", MvExpression (ExApplication (ExDispatch ExGlobal (AtLabel "org")) (BiTau (AtLabel "x") ExThis)) defaultScope)]]
         )
       ,
         ( "!e1.x => Q.org.x => [(!e1 >> Q.org)]"
         , ExDispatch (ExMeta "e1") (AtLabel "x")
         , ExDispatch (ExDispatch ExGlobal (AtLabel "org")) (AtLabel "x")
         , defaultScope
-        , [[("e1", MvExpression (ExDispatch ExGlobal (AtLabel "org")) defaultScope)]]
+        , substs [[("e1", MvExpression (ExDispatch ExGlobal (AtLabel "org")) defaultScope)]]
         )
       ,
         ( "!e.org.!a => $.org.x => [(!e >> $, !a >> x)]"
         , ExDispatch (ExDispatch (ExMeta "e") (AtLabel "org")) (AtMeta "a")
         , ExDispatch (ExDispatch ExThis (AtLabel "org")) (AtLabel "x")
         , defaultScope
-        , [[("e", MvExpression ExThis defaultScope), ("a", MvAttribute (AtLabel "x"))]]
+        , substs [[("e", MvExpression ExThis defaultScope), ("a", MvAttribute (AtLabel "x"))]]
         )
       ,
         ( "[[!a -> !e, !B]].!a => [[x -> Q, y -> $]].x => [(!a >> x, !e >> Q, !B >> [y -> $])]"
@@ -449,50 +442,50 @@
             )
             (AtLabel "x")
         , defaultScope
-        ,
-          [
-            [ ("a", MvAttribute (AtLabel "x"))
-            ,
-              ( "e"
-              , MvExpression
-                  ExGlobal
-                  ( ExFormation
-                      [ BiTau (AtLabel "x") ExGlobal
-                      , BiTau (AtLabel "y") ExThis
-                      ]
-                  )
-              )
-            , ("B", MvBindings [BiTau (AtLabel "y") ExThis])
+        , substs
+            [
+              [ ("a", MvAttribute (AtLabel "x"))
+              ,
+                ( "e"
+                , MvExpression
+                    ExGlobal
+                    ( ExFormation
+                        [ BiTau (AtLabel "x") ExGlobal
+                        , BiTau (AtLabel "y") ExThis
+                        ]
+                    )
+                )
+              , ("B", MvBindings [BiTau (AtLabel "y") ExThis])
+              ]
             ]
-          ]
         )
       ,
         ( "Q * !t => Q.org => [(!t >> [.org])]"
         , ExMetaTail ExGlobal "t"
         , ExDispatch ExGlobal (AtLabel "x")
         , defaultScope
-        , [[("t", MvTail [TaDispatch (AtLabel "x")])]]
+        , substs [[("t", MvTail [TaDispatch (AtLabel "x")])]]
         )
       ,
         ( "Q * !t => Q.org(x -> [[]]) => [(!t >> [.org, (x -> [[]])])]"
         , ExMetaTail ExGlobal "t"
         , ExApplication (ExDispatch ExGlobal (AtLabel "org")) (BiTau (AtLabel "x") defaultScope)
         , defaultScope
-        , [[("t", MvTail [TaDispatch (AtLabel "org"), TaApplication (BiTau (AtLabel "x") defaultScope)])]]
+        , substs [[("t", MvTail [TaDispatch (AtLabel "org"), TaApplication (BiTau (AtLabel "x") defaultScope)])]]
         )
       ,
         ( "Q.!a * !t => Q.org.eolang(x -> [[]]) => [(!a >> org, !t >> [ .eolang, ( x -> [[ ]] ) ])]"
         , ExMetaTail (ExDispatch ExGlobal (AtMeta "a")) "t"
         , ExApplication (ExDispatch (ExDispatch ExGlobal (AtLabel "org")) (AtLabel "eolang")) (BiTau (AtLabel "x") defaultScope)
         , defaultScope
-        , [[("a", MvAttribute (AtLabel "org")), ("t", MvTail [TaDispatch (AtLabel "eolang"), TaApplication (BiTau (AtLabel "x") defaultScope)])]]
+        , substs [[("a", MvAttribute (AtLabel "org")), ("t", MvTail [TaDispatch (AtLabel "eolang"), TaApplication (BiTau (AtLabel "x") defaultScope)])]]
         )
       ,
         ( "Q.x(y -> $ * !t1) * !t2 => Q.x(y -> $.q).p => [(!t1 >> [.q], !t2 >> [.p])]"
         , ExMetaTail (ExApplication (ExDispatch ExGlobal (AtLabel "x")) (BiTau (AtLabel "y") (ExMetaTail ExThis "t1"))) "t2"
         , ExDispatch (ExApplication (ExDispatch ExGlobal (AtLabel "x")) (BiTau (AtLabel "y") (ExDispatch ExThis (AtLabel "q")))) (AtLabel "p")
         , defaultScope
-        , [[("t1", MvTail [TaDispatch (AtLabel "q")]), ("t2", MvTail [TaDispatch (AtLabel "p")])]]
+        , substs [[("t1", MvTail [TaDispatch (AtLabel "q")]), ("t2", MvTail [TaDispatch (AtLabel "p")])]]
         )
       ,
         ( "[[!B1, !a ↦ !e1, !B2]](!a ↦ !e2) => ⟦ t ↦ ξ.k, x ↦ ξ.t, k ↦ ∅ ⟧(x ↦ ξ) => [(!B1 >> [[ t -> $.k ]], !a >> x, !B2 >> [[ k -> ? ]], !e1 >> $.t, !e2 >> $)]"
@@ -506,25 +499,25 @@
             )
             (BiTau (AtLabel "x") ExThis)
         , defaultScope
-        ,
-          [
-            [ ("B1", MvBindings [BiTau (AtLabel "t") (ExDispatch ExThis (AtLabel "k"))])
-            , ("a", MvAttribute (AtLabel "x"))
-            , ("B2", MvBindings [BiVoid (AtLabel "k")])
-            ,
-              ( "e1"
-              , MvExpression
-                  (ExDispatch ExThis (AtLabel "t"))
-                  ( ExFormation
-                      [ BiTau (AtLabel "t") (ExDispatch ExThis (AtLabel "k"))
-                      , BiTau (AtLabel "x") (ExDispatch ExThis (AtLabel "t"))
-                      , BiVoid (AtLabel "k")
-                      ]
-                  )
-              )
-            , ("e2", MvExpression ExThis defaultScope)
+        , substs
+            [
+              [ ("B1", MvBindings [BiTau (AtLabel "t") (ExDispatch ExThis (AtLabel "k"))])
+              , ("a", MvAttribute (AtLabel "x"))
+              , ("B2", MvBindings [BiVoid (AtLabel "k")])
+              ,
+                ( "e1"
+                , MvExpression
+                    (ExDispatch ExThis (AtLabel "t"))
+                    ( ExFormation
+                        [ BiTau (AtLabel "t") (ExDispatch ExThis (AtLabel "k"))
+                        , BiTau (AtLabel "x") (ExDispatch ExThis (AtLabel "t"))
+                        , BiVoid (AtLabel "k")
+                        ]
+                    )
+                )
+              , ("e2", MvExpression ExThis defaultScope)
+              ]
             ]
-          ]
         )
       ]
 
diff --git a/test/MiscSpec.hs b/test/MiscSpec.hs
--- a/test/MiscSpec.hs
+++ b/test/MiscSpec.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE OverloadedStrings #-}
+
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
 
diff --git a/test/ParserSpec.hs b/test/ParserSpec.hs
--- a/test/ParserSpec.hs
+++ b/test/ParserSpec.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE OverloadedStrings #-}
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
 {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
diff --git a/test/PrinterSpec.hs b/test/PrinterSpec.hs
--- a/test/PrinterSpec.hs
+++ b/test/PrinterSpec.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE OverloadedStrings #-}
+
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
 
diff --git a/test/ReplacerSpec.hs b/test/ReplacerSpec.hs
--- a/test/ReplacerSpec.hs
+++ b/test/ReplacerSpec.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE OverloadedStrings #-}
+
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
 
diff --git a/test/RuleSpec.hs b/test/RuleSpec.hs
--- a/test/RuleSpec.hs
+++ b/test/RuleSpec.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
