# atproto_mlf
> **Alpha.** Pre-1.0 and evolving; expect breaking changes between 0.x
> releases. Upstream [mlf](https://mlf.lol/) is itself pre-release.
Parse and print [mlf](https://mlf.lol/) ("Matt's Lexicon Format") against the
[`atproto_lexicon`](https://hex.pm/packages/atproto_lexicon) AST, with a
bidirectional CLI for converting whole lexicon file trees. A clean-room Gleam
implementation of the upstream grammar, oracle-tested against upstream's own
snapshot fixtures.
## Installation
```sh
gleam add atproto_mlf
```
## Usage
Library:
```gleam
import atproto_mlf
let assert Ok(doc) = atproto_mlf.parse(source, "com.example.thread")
// doc is an atproto_lexicon ast.LexiconDoc; encode it, diff it, feed codegen.
```
CLI (walks both `.mlf` and `.json` inputs; four directions from one model:
compile, convert, normalize, fmt):
```sh
gleam run -m atproto_mlf -- <src-dir> <out-dir> [--to json|mlf]
```
NSIDs derive from file paths (`com/example/thread.mlf` becomes
`com.example.thread`). Constructs the lexicon spec allows but mlf cannot
express (e.g. a procedure with both query parameters and a non-JSON input
body) produce a spanned error naming the construct; nothing is ever silently
dropped.
## Architecture
| Module | Role |
| --------- | -------------------------------------------------------- |
| `lexer` | graphemes to spanned tokens (`///` docs are significant) |
| `parser` | recursive descent to an mlf tree |
| `lower` | mlf tree to `atproto_lexicon` AST; ref resolution |
| `printer` | AST to vanilla mlf text; total or `PrintError`, no loss |
| `cli` | bidirectional tree converter |