phino-0.0.77: README.md
<!--
SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
SPDX-License-Identifier: MIT
-->
# Command-Line Manipulator of 𝜑-Calculus Expressions
[](https://www.rultor.com/p/objectionary/phino)
[](http://hackage.haskell.org/package/phino)
[](https://github.com/objectionary/phino/actions/workflows/cabal.yml)
[](https://github.com/objectionary/phino/actions/workflows/stack.yml)
[](https://app.codecov.io/gh/objectionary/phino)
[](https://objectionary.github.io/phino/)
[](LICENSES/MIT.txt)
[](https://hitsofcode.com/github/objectionary/phino/view?branch=master&label=Hits-of-Code)
[](https://www.0pdd.com/p?name=objectionary/phino)
This is a command-line normalizer, rewriter, and dataizer
of [𝜑-calculus](https://www.eolang.org) expressions.
First, you write a simple [𝜑-calculus](https://www.eolang.org) program
in the `hello.phi` file:
```text
Φ ↦ ⟦ φ ↦ ⟦ Δ ⤍ 68-65-6C-6C-6F ⟧, t ↦ ξ.k, k ↦ ⟦⟧ ⟧
```
## Installation
Then you can install `phino` in two ways:
Install [Cabal][cabal] first and then:
```bash
cabal update
cabal install --overwrite-policy=always phino-0.0.76
phino --version
```
Or download binary from the internet using [curl](https://curl.se/) or
[wget](https://en.wikipedia.org/wiki/Wget):
```bash
sudo curl -o /usr/local/bin/phino http://phino.objectionary.com/releases/macos-15/phino-latest
sudo chmod +x /usr/local/bin/phino
phino --version
```
Download paths are:
* Ubuntu 22.04: <http://phino.objectionary.com/releases/ubuntu-22.04/phino-latest>
* Ubuntu 24.04: <http://phino.objectionary.com/releases/ubuntu-24.04/phino-latest>
* MacOS (ARM): <http://phino.objectionary.com/releases/macos-15/phino-latest>
* MacOS (Intel): <http://phino.objectionary.com/releases/macos-14-large/phino-latest>
* Windows: <http://phino.objectionary.com/releases/windows-2022/phino-latest.exe>
## Build
To build `phino` from source, clone this repository:
```bash
git clone git@github.com:objectionary/phino.git
cd phino
```
Then, run the following command (ensure you have [Cabal][cabal] installed):
```bash
cabal build all
```
Next, run this command to install `phino` system-wide:
```bash
sudo cp "$(cabal list-bin phino)" /usr/local/bin/phino
```
Verify that `phino` is installed correctly:
```bash
$ phino --version
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:
```bash
$ phino dataize hello.phi
68-65-6C-6C-6F
```
## Rewrite
You can rewrite this expression with the help of [rules](#rule-structure)
defined in the `my-rule.yml` YAML file (here, the `!d` is a capturing group,
similar to regular expressions):
```yaml
name: My custom rule
pattern: Δ ⤍ !d
result: Δ ⤍ 62-79-65
```
Then, rewrite:
```bash
$ phino rewrite --rule=my-rule.yml hello.phi
Φ ↦ ⟦ φ ↦ ⟦ Δ ⤍ 62-79-65 ⟧, t ↦ ξ.k, k ↦ ⟦⟧ ⟧
```
If you want to use many rules, just use `--rule` as many times as you need:
```bash
phino rewrite --rule=rule1.yaml --rule=rule2.yaml ...
```
You can also use [built-in rules](resources), which are designed
to normalize expressions:
```bash
phino rewrite --normalize hello.phi
```
If no input file is provided, the 𝜑-expression is taken from `stdin`:
```bash
$ echo 'Φ ↦ ⟦ φ ↦ ⟦ Δ ⤍ 68-65-6C-6C-6F ⟧ ⟧' | phino rewrite --rule=my-rule.yml
Φ ↦ ⟦ φ ↦ ⟦ Δ ⤍ 62-79-65 ⟧ ⟧
```
You're able to pass [`XMIR`][xmir] as input. Use `--input=xmir` and `phino`
will parse given `XMIR` from file or `stdin` and convert it to `phi` AST.
```bash
phino rewrite --rule=my-rule.yaml --input=xmir file.xmir
```
Also `phino` supports 𝜑-expressions in
[ASCII](https://en.wikipedia.org/wiki/ASCII) format and with
syntax sugar. The `rewrite` command also allows you to desugar the expression
and print it in canonical syntax:
```bash
$ echo 'Q -> [[ @ -> Q.io.stdout("hello") ]]' | phino rewrite
Φ ↦ ⟦
φ ↦ Φ.io.stdout(
α0 ↦ Φ.string(
α0 ↦ Φ.bytes(
α0 ↦ ⟦ Δ ⤍ 68-65-6C-6C-6F ⟧
)
)
)
⟧
```
## Merge
You can merge several 𝜑-programs into a single one by merging their
top level formations:
```bash
$ cat bytes.phi
{⟦ bytes(data) ↦ ⟦ φ ↦ data ⟧ ⟧}
$ cat number.phi
{⟦
number(as-bytes) ↦ ⟦
φ ↦ as-bytes,
plus(x) ↦ ⟦ λ ⤍ L_number_plus ⟧
⟧
⟧}
$ cat minus.phi
{⟦ number ↦ ⟦ minus(x) ↦ ⟦ λ ⤍ L_number_minus ⟧ ⟧ ⟧}
$ phino merge bytes.phi number.phi minus.phi --sweet
{⟦
bytes(data) ↦ ⟦ φ ↦ data ⟧,
number(as-bytes) ↦ ⟦
φ ↦ as-bytes,
plus(x) ↦ ⟦ λ ⤍ L_number_plus ⟧,
minus(x) ↦ ⟦ λ ⤍ L_number_minus ⟧
⟧
⟧}
```
## Match
You can test the 𝜑-program matches against the [rule](#rule-structure)
pattern. The result output contains matched substitutions:
```bash
$ phino match --pattern='⟦ Δ ⤍ !d, !B ⟧' hello.phi
B >> ⟦ ρ ↦ ∅ ⟧
d >> 68-65-6C-6C-6F
```
## Explain
You can _explain_ the built-in rules by printing them in [LaTeX][latex]
format. Pass exactly one of `--normalize`, `--morph` or `--dataize` for
the rewriting, morphing (𝕄) or dataization (𝔻) rules (or `--rule` for a
custom rule file):
```bash
$ phino explain --normalize
\begin{tabular}{rl}
\trrule{alpha}
{ [[ B_1, \tau_1 -> ?, B_2 ]] ( \tau_2 -> e ) }
{ [[ B_1, \tau_1 -> ?, B_2 ]] ( \tau_1 -> e ) }
{ if $ \indexof{ \tau_2 } = \vert B_1 \vert $ }
{ }
\trrule{dc}
{ T ( \tau -> e ) }
{ T }
{ }
{ }
...
\trrule{stop}
{ [[ B ]] . \tau }
{ T }
{ if $ \tau \notin B \;\text{and}\; @ \notin B \;\text{and}\; L \notin B $ }
{ }
\end{tabular}
```
The morphing and dataization rules are printed the same way:
```bash
$ phino explain --morph
\begin{tabular}{rl}
\trrule{Mprim}
{ \mathbb{M}( e ) }
{ e }
{ if $ e \in \mathcal{P} $ }
{ }
...
\trrule{Mphi}
{ \mathbb{M}( Q . \tau * t ) }
{ \mathbb{M}( e * t ) }
{ }
{ where $ e \coloneqq global( \tau ) $ }
\end{tabular}
```
```bash
$ phino explain --dataize
\begin{tabular}{rl}
\trrule{delta}
{ \mathbb{D}( [[ B_1, D> δ, B_2 ]] ) }
{ δ }
{ }
{ }
...
\trrule{none}
{ \mathbb{D}( e ) }
{ \varnothing }
{ }
{ }
\end{tabular}
```
For more details, use `phino [COMMAND] --help` option.
## Rule structure
This is BNF-like yaml rule structure. Here types ended with
apostrophe, like `Attribute'` are built types from 𝜑-program [AST](src/AST.hs)
```bnfc
Rule:
name: String
pattern: String
result: String
when: Condition? # predicate, works with substitutions before extension
where: [Extension]? # substitution extensions
having: Condition? # predicate, works with substitutions after extension
Condition:
= and: [Condition] # logical AND
| or: [Condition] # logical OR
| not: Condition # logical NOT
| alpha: Attribute' # check if given attribute is alpha
| eq: # compare two comparable objects
- Comparable
- Comparable
| in: # check if attributes exist in bindings
- Attribute'
- Binding'
| nf: Expression' # returns True if given expression in normal form
# which means that no more other normalization rules
# can be applied
| xi-free: Expression' # special condition for Rcopy normalization rule to
# avoid infinite recursion while the condition checking
# returns True if there's no ξ outside of the formation
# in given expression.
| matches: # returns True if given expression after dataization
- String # matches to given regex
- Expression
| part-of: # returns True if given expression is attached to any
- Expression' # attribute in ginve bindings
- BiMeta'
Comparable: # comparable object that may be used in 'eq' condition
= Attribute'
| Number
| Expression'
Number: # comparable number
= Integer # just regular integer
| index: Attribute' # calculate index of alpha attribute
| length: BiMeta' # calculate length of bindings by given meta binding
Extension: # substitutions extension used to introduce new meta variables
meta: [ExtArgument] # new introduced meta variable
function: String # name of the function
args: [ExtArgument] # arguments of the function
ExtArgument
= Bytes' # !d
| Binding' # !B
| Expression' # !e
| Attribute' # !a
```
Here's list of functions that are supported for extensions:
* `contextualize` - function of two arguments, that rewrites given expression
depending on provided context according to the contextualization
[rules](assets/contextualize.jpg)
* `random-tau` - creates attribute with random unique name. Accepts bindings,
and attributes. Ensures that created attribute is not present in list of
provided attributes and does not exist as attribute in provided bindings.
* `dataize` - dataizes given expression and returns bytes.
* `concat` - accepts bytes or dataizable expressions as arguments,
concatenates them into single sequence and convert it to expression
that can be pretty printed as human readable string:
`Φ.string(Φ.bytes⟦ Δ ⤍ !d ⟧)`.
* `sed` - pattern replacer, works like unix `sed` function.
Accepts two arguments: target expression and pattern.
Pattern must start with `s/`, consists of three parts
separated by `/`, for example, this pattern `s/\\s+//g`
replaces all the spaces with empty string. To escape braces and slashes
in pattern and replacement parts - use them with `\\`,
e.g. `s/\\(.+\\)//g`.
* `random-string` - accepts dataizable expression or bytes as pattern.
Replaces `%x` and `%d` formatters with random hex numbers and
decimals accordingly. Uniqueness is guaranteed during one
execution of `phino`.
* `size` - accepts exactly one meta binding and returns size of it and
`Φ.number`.
* `tau` - accepts `Φ.string`, dataizes it and converts it to attribute.
If dataized string can't be converted to attribute - an error is thrown.
* `string` - accepts `Φ.string` or `Φ.number` or attribute and converts it
to `Φ.string`.
* `number` - accepts `Φ.string` and converts it `Φ.number`
* `sum` - accepts list of `Φ.number` or `Φ.bytes` and returns sum of them as `Φ.number`
* `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.
* `graft` - same call shape as `splice` (`𝐵-in`, sentinel, `𝐵-rep`) and the
same renaming guarantees, but every matched sentinel binding is replaced by
the renamed copy of `𝐵-rep` instead of preserved in place. Use it when the
marker must not survive the substitution — for example, when fusing two
bodies that both produce the same effect and the original marker would fire
a second time. When no binding matches the sentinel, the output equals the
input unchanged.
## Meta variables
The `phino` supports meta variables to write 𝜑-expression patterns for
capturing attributes, bindings, etc.
This is the list of supported meta variables:
* `!a` || `𝜏` - attribute
* `!e` || `𝑒` - any expression
* `!n` || `𝑛` - any expression that is already in normal form (behaves like
`!e`/`𝑒`, but only binds a sub-expression in NF, so no explicit
`nf:` guard is needed)
* `!B` || `𝐵` - list of bindings
* `!d` || `δ` - bytes in meta delta binding
* `!t` - tail after expression, a possibly empty sequence of applications
and/or dispatches
* `!F` - function name in meta lambda binding
Every meta variable may also be used with an integer index, like `!B1` or `𝜏0`.
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: 1424805.977 μs
avg: 142480.598 μs
min: 127610.262 μs
max: 174684.935 μs
std dev: 17290.645 μs
=== parse/xmir ===
warmup: 3 iterations
batches: 10 x 1
total: 7682830.477 μs
avg: 768283.048 μs
min: 705792.212 μs
max: 854675.506 μs
std dev: 39610.459 μs
=== rewrite/normalize ===
warmup: 3 iterations
batches: 10 x 1
total: 385725.280 μs
avg: 38572.528 μs
min: 37709.086 μs
max: 40438.410 μs
std dev: 706.313 μs
=== print/sweet/multiline ===
warmup: 3 iterations
batches: 10 x 1
total: 4012945.140 μs
avg: 401294.514 μs
min: 394876.422 μs
max: 410082.600 μs
std dev: 5070.401 μs
=== print/sweet/flat ===
warmup: 3 iterations
batches: 10 x 1
total: 3898651.004 μs
avg: 389865.100 μs
min: 364531.311 μs
max: 403024.894 μs
std dev: 13855.156 μs
=== print/salty/multiline ===
warmup: 3 iterations
batches: 10 x 1
total: 13604785.574 μs
avg: 1360478.557 μs
min: 1335616.393 μs
max: 1382380.875 μs
std dev: 15958.883 μs
```
The results were calculated in [this GHA job][benchmark-gha]
on 2026-06-08 at 20:09,
on Linux with 4 CPUs.
<!-- benchmark_end -->
## How to Contribute
Fork repository, make changes, then send us a [pull request][guidelines].
We will review your changes and apply them to the `master` branch shortly,
provided they don't violate our quality standards. To avoid frustration,
before sending us your pull request please make sure all your tests pass:
```bash
make all
```
To generate a local coverage report for development, run:
```bash
make coverage
```
You will need [GHC ≥ 9.6.7][GHC] and [Cabal ≥ 3.0 (recommended)][cabal]
or [Stack ≥ 3.0][stack] installed.
[cabal]: https://www.haskell.org/cabal/
[stack]: https://docs.haskellstack.org/en/stable/install_and_upgrade/
[GHC]: https://www.haskell.org/ghc/
[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/27163743003