~skyvine/repl-user-interface

Utilities for a more shell-like experience in the Guile REPL
Add initial proof of concept

refs

trunk
browse  log 

clone

read-only
https://git.sr.ht/~skyvine/repl-user-interface
read/write
git@git.sr.ht:~skyvine/repl-user-interface

You can also use your local clone with git send-email.

This repository contains a proof of concept for a style of scheme-level function signatures which mimics the expectations around command-line arguments. Namely:

  • Positional and named arguments can appear in any order
  • Arguments can be given multiple times, with the latest value taking precedence
  • Flags can appear which do not accept an argument but resolve to a boolean value based on their presence and name

Additionally, it takes inspiration from python by allowing the caller to provide all arguments either positionally or by name, determined at the calling site. One caller might provide every argument by position, might provide every argument by name.

It is named "REPL User Interface", or RUI for short, since it takes inspiration from traditional Command Line Interfaces (CLI for short).

#Example

(read-set! keywords #f)
(use-modules ((repl-user-interface) #:prefix rui.))
(read-set! keywords 'postfix)

(rui.def (example (normal-argument alias: #:na default: "value") (flag flag?: #t))
	(list normal-argument flag))

; These evaluate to '("value #t")
(example)
(example "value" #t)
(example #t normal-argument: "value")
(example na: "value" #:flag)

; These evaluate to '("not-a-value =]" #f)
(example #:no-flag na: "not-a-value =]")
(example "not-a-value =]" #f)

#Keyword Syntax

Guile provides 3 styles for keyword: default (#:name), postfix (name:), and prefix (:name). In my experience the default style is the most popular. This project mixes the default and postfix styles for more meaningful syntax. If the keyword object is meant as a value itself in the context of its use then the default style is used. If it is being used to label the value that follows it then the postfix style is used. This can be seen in the example above where postfix is used when naming the value for normal-argument, but default is used when declaring the flag.

Note that keyword style is a global setting, not a per-file setting. This is why the example above calls read-set! before and after importing modules, so that the imported modules are loaded in the default state but the module itself supports the postfix style. This is the technique I recommend to handle keyword styling in all projects.

#Implementation

REPL user interfaces are implemented in 3 different contexts.

When a procedure that supports the RUI is defined, the library generates code which knows how to rewrite calls using the interface into calls that send all arguments positionally (as is the case with procedures defined using the standard define or lambda mechanisms).

When a dependent module calls a procedure the arguments are scanned for keywords that name an argument, and the arguments are rewritten so that they are all positional arguments and the keyword names are removed.

Both of the above contexts happen at compile time.

Once the rewriting is done, all that remains is a direct call to a procedure that is creating using the typical lambda mechanism. This is the only thing that remains once the code is compiled.

You can think about the above example as compiling to something that looks like this, although the reality is more complicated:

(read-set! keywords #f)
(use-modules ((repl-user-interface) #:prefix rui.))
(read-set! keywords 'postfix)

(define (example normal-argument flag)
	(list normal-argument flag))

; All 4 examples in the first section compile to the exact same thing
(example "value" #t)
(example "value" #t)
(example "value" #t)
(example "value" #t)

; Again, both of the examples in the second section compile to the same thing
(example "not-a-value =]" #f)
(example "not-a-value =]" #f)

#TODO

  • Extract test utils from personal repo & use here
  • Make the #:flag argument behave like a flag...
  • Auto-generate help text
  • Support configuration files?
  • Make argument processing available independently (like let-keywords)