///|
/// The whole surface in one run: read a declaration, bind its annotation, and
/// generate code from both.
fn main {
  tour() catch {
    e => abort("the tour stopped: \{e}")
  }
}

///|
fn tour() -> Unit raise {
  let source =
    #|#app.get("/users", summary="List users", tags="users")
    #|fn list_users() -> Array[String] {
    #|  []
    #|}
  // gen: declarations as plain views.
  guard @gen.read("routes.mbt", source) is [Func(name~, attrs~, ..)] else {
    abort("the tour's source declares one function")
  }
  // attr: the annotation's arguments, typed.
  guard @attr.find(attrs, "app.get") is Some(annotation) else {
    abort("the function is annotated")
  }
  let spec = @attr.Spec::new("app.get")
    .positional("path", Str)
    .optional("summary", Str)
    .many("tags", Str)
  let args = spec.bind(annotation)
  // quote: a template filled with what was read.
  let route =
    #|fn register(app : App) -> Unit {
    #|  app.get($path, $handler)
    #|}
  let code = @quote.items(route)
    .set("path", @quote.string(args.str("path").unwrap_or("/")))
    .set("handler", @quote.ident(name))
    .render()
  println(code)
}