Nanyx

Argument parsing

Walk a list of command-line arguments into a config record, with proc reporting the first error.

module argumentParsing

type Config = (
  help: bool
  verbose: bool
  input: string
)

type Arguments = list(string)

type ParseError =
  | #helpRequested
  | #missingArgument(string)

-- The value following a flag, found by walking the argument list two at a time
def requiredArgument: (Arguments, string) -> #some(string) | #error(#missingArgument(string))
  = { args, name ->
    rec loop = {
      | [] | [_] -> #error(#missingArgument(name))
      | [flag, value, ...rest] ->
        if flag == name then #some(value) else loop([value, ...rest])
    }
    loop(args)
  }

def optionalSwitch: (Arguments, string) -> bool = { args, name -> args \contains(name) }

def checkHelp: Arguments -> #some(()) | #error(#helpRequested) = { args ->
  if optionalSwitch(args, "--help") then #error(#helpRequested) else #some(())
}

-- `proc` short-circuits on the first `#error`, so the happy path reads top to bottom
def parseArguments: Arguments -> #some(Config) | #error(ParseError) = { args -> proc {
  try checkHelp(args)
  def input = try requiredArgument(args, "--input")

  return Config(
    help = false
    verbose = optionalSwitch(args, "--verbose")
    input = input
  )
}}

@entry
def main = {
  def args = ["--input", "foo.txt", "--verbose"]

  match parseArguments(args)
    | #some(config) -> println("Processing input '{config.input}' verbose={config.verbose}")
    | #error(#helpRequested) -> println("Usage: example [--help] [--verbose] [--input <input file>]")
    | #error(#missingArgument(name)) -> println("Error: command line argument {name} is missing")
}

This program lives in the repository as examples/functional-argument-parsing.nyx.