From 0d6b372c19a2a86db5549cdda4cd342dec3e312c Mon Sep 17 00:00:00 2001 From: Jade Lovelace Date: Mon, 27 Apr 2026 17:57:54 -0700 Subject: [PATCH] repl-characterization: document the syntax Gosh. Why was there never a README in here explaining what it does without requiring people to read C++? There was a half-assed grammar in a comment, but it was very half-assed, so let's replace it with a better-written one. I'm documenting this so that neither I nor anyone else have to read C++ while rewriting the parser in Python. See: https://git.lix.systems/lix-project/lix/issues/1196 Change-Id: Ia34ea7b02f109eb7753833e1c03d4b5d1d3c3b75 --- .../repl_characterization/README.md | 82 +++++++++++++++++++ .../tests/cli-literate-parser.hh | 11 +-- 2 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 tests/functional/repl_characterization/README.md diff --git a/tests/functional/repl_characterization/README.md b/tests/functional/repl_characterization/README.md new file mode 100644 index 000000000..51180618d --- /dev/null +++ b/tests/functional/repl_characterization/README.md @@ -0,0 +1,82 @@ +# repl characterization tests + +> or, literate programming for your REPL tests! + +The core idea behind repl-characterization is to make it very easy to write and maintain REPL tests by turning session transcripts directly into tests. + +This infrastructure is written in C++ but we want to [rewrite it in functional2][riif2] in the near future. + +[riif2]: https://git.lix.systems/lix-project/lix/issues/1196 + +To write a repl-characterization test, you can open up `nix repl` and then you copy paste the output into the test file, then indent it. +Add your unindented commentary alongside the output text. +Add any necessary args. +Then it should run! + +## Syntax + +The syntax of repl-characterization contains four types of things: +- Prompts to the repl, indented: ` nix-repl> 2 + 2` for example. +- Blocks of output, indented. + These are denoted by indented non-empty lines, perhaps with some blank lines between them which are part of the output. +- Directives, unindented. + These are: + - `@args some args with "shell quoting"`. + Adds the args to the invocation of `nix repl`. + Multiple may be used. + + `${PWD}` will be replaced by the actual working directory. + - `@should-start false` + This is used to indicate that the repl is expected to fail to start, giving some output and no prompt to the system. +- Commentary. + This is any unindented text, including blank lines outside output blocks. + +One of the core ideas of the syntax is that it's possible to write an unparser for it such that we can update test session files in-place, retaining your commentary. +This is [not implemented in the C++ version][bug-autoupdate], and I think we will probably only do it in the Python version. + +[bug-autoupdate]: https://git.lix.systems/lix-project/lix/issues/36 + +Here is a simplified version of the syntax in pseudo-[EBNF]: + +[EBNF]: https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form + +```ebnf +indent = " " (* space times config.indent; 2 in practice *) ; +line-char = ( ? anything but nl ? )+ ; +nl = "\n" ; + +prompt = "nix-repl>" ; +command = line-char* ; +prompt-line = indent , prompt , command ; + +bool = "true" | "false" ; + +args-directive = "args " , line-char+ ; +should-start-directive = "should-start " , bool ; +unknown-directive = ? this is an error ? ; (* there's a bug in the C++ impl where we don't actually error on this *) +directive = args-directive | should-start-directive | unknown-directive ; +directive-line = "@" , directive ; + +(* anything unindented is a comment *) +commentary-line = line-char+ ; + +output-line = indent , line-char+ ; +output-continuation = output-line? , nl ; +(* in other words: output blocks are considered to contain any *inner* blank + lines within them, but *not* trailing blank lines. This enables updating + them automatically while retaining the blank lines surrounding your stanzas + in your session. + + This is implemented as a postprocessing step so that our parsing doesn't + require lookahead. *) +output-block = output-line , nl , ((blank-line , nl)+ , output-line , nl)+ ; + +input = ((prompt-line | directive-line | commentary-line) , nl) | output-block ; +file = input* ; +``` + +## Usage + +Tests are written in `./data/*.test`, and have a corresponding macro call in `./repl_characterization.cc` (the macro thing is basically a hack because the infra was never quite finished). + +You can run the suite with `just install && just test --suite installcheck repl-characterization-tests`. diff --git a/tests/unit/libutil-support/tests/cli-literate-parser.hh b/tests/unit/libutil-support/tests/cli-literate-parser.hh index 55ea5eb81..79104689a 100644 --- a/tests/unit/libutil-support/tests/cli-literate-parser.hh +++ b/tests/unit/libutil-support/tests/cli-literate-parser.hh @@ -2,10 +2,7 @@ ///@file #include "lix/libutil/error.hh" -#include -#include #include -#include #include #include #include @@ -166,13 +163,7 @@ struct Config { * comments. * * Syntax: - * ``` - * ( COMMENTARY - * | INDENT PROMPT COMMAND - * | INDENT OUTPUT - * | @args ARGS - * | @should-start ( true | false )) * - * ``` + * See tests/functional/repl_characterization/README.md. * * e.g. * ```