tests/testthat/test-doc-parser.R

thin <- make_cran_thinner()

test_that("DocParser parses @description annotations", {
  thin()
  tmp <- tempfile(fileext = ".arl")
  on.exit(unlink(tmp))
  writeLines(c(
    "(module test-mod",
    "  (export my-fn)",
    "",
    "  ;;' @description Add two numbers.",
    "  ;;' @examples",
    "  ;;' (my-fn 1 2)  ; => 3",
    "  ;;' @seealso sub-fn",
    "  (define my-fn",
    "    (lambda (a b) (+ a b)))",
    ")"
  ), tmp)

  parser <- DocParser$new()
  result <- parser$parse_file(tmp)

  expect_true("my-fn" %in% names(result$functions))
  fn <- result$functions[["my-fn"]]
  expect_equal(fn$description, "Add two numbers.")
  expect_match(fn$examples, "my-fn 1 2")
  expect_equal(fn$seealso, "sub-fn")
})

test_that("DocParser parses @section annotations", {
  thin()
  tmp <- tempfile(fileext = ".arl")
  on.exit(unlink(tmp))
  writeLines(c(
    "(module test-mod",
    "  (export f1 f2)",
    "",
    "  ;;' @section Math Helpers",
    "",
    "  ;;' @description Double a number.",
    "  (define f1 (lambda (x) (* x 2)))",
    "",
    "  ;;' @description Triple a number.",
    "  (define f2 (lambda (x) (* x 3)))",
    ")"
  ), tmp)

  parser <- DocParser$new()
  result <- parser$parse_file(tmp)

  expect_equal(length(result$sections), 1)
  expect_equal(result$sections[[1]]$name, "Math Helpers")
  expect_equal(result$functions[["f1"]]$section, "Math Helpers")
  expect_equal(result$functions[["f2"]]$section, "Math Helpers")
})

test_that("DocParser parses @note and @signature", {
  thin()
  tmp <- tempfile(fileext = ".arl")
  on.exit(unlink(tmp))
  writeLines(c(
    "(module test-mod",
    "  (export my-fn)",
    "",
    "  ;;' @signature (my-fn a b)",
    "  ;;' @description A function.",
    "  ;;' @note This is a note.",
    "  (define my-fn (lambda (a b) (+ a b)))",
    ")"
  ), tmp)

  parser <- DocParser$new()
  result <- parser$parse_file(tmp)

  fn <- result$functions[["my-fn"]]
  expect_equal(fn$note, "This is a note.")
  expect_equal(fn$signature, "(my-fn a b)")
})

test_that("DocParser get_exports extracts module exports", {
  thin()
  tmp <- tempfile(fileext = ".arl")
  on.exit(unlink(tmp))
  writeLines(c(
    "(module test-mod",
    "  (export foo bar baz)",
    "  (define foo 1)",
    "  (define bar 2)",
    "  (define baz 3)",
    ")"
  ), tmp)

  parser <- DocParser$new()
  exports <- parser$get_exports(tmp)

  expect_true("foo" %in% exports)
  expect_true("bar" %in% exports)
  expect_true("baz" %in% exports)
})

test_that("DocParser handles defmacro definitions", {
  thin()
  tmp <- tempfile(fileext = ".arl")
  on.exit(unlink(tmp))
  writeLines(c(
    "(module test-mod",
    "  (export my-macro)",
    "",
    "  ;;' @description A test macro.",
    "  ;;' @examples",
    "  ;;' (my-macro x)",
    "  (defmacro my-macro (x) x)",
    ")"
  ), tmp)

  parser <- DocParser$new()
  result <- parser$parse_file(tmp)

  expect_true("my-macro" %in% names(result$functions))
  fn <- result$functions[["my-macro"]]
  expect_equal(fn$description, "A test macro.")
  expect_match(fn$examples, "my-macro x")
})

test_that("DocParser skips standalone sections without definitions", {
  thin()
  tmp <- tempfile(fileext = ".arl")
  on.exit(unlink(tmp))
  writeLines(c(
    "(module test-mod",
    "  (export f1)",
    "",
    "  ;;' @section Standalone Section",
    "  ;;' This section has prose but no following definition.",
    "",
    "  ;;' @description A function.",
    "  (define f1 (lambda () 42))",
    ")"
  ), tmp)

  parser <- DocParser$new()
  result <- parser$parse_file(tmp)

  # Standalone section should be recorded
  expect_true(length(result$sections) >= 1)
  # f1 should still be parsed
  expect_true("f1" %in% names(result$functions))
})

test_that("parse_text parses annotations from string input", {
  thin()
  code <- "(module str-mod
  (export add)

  ;;' @description Add two numbers together.
  ;;' @examples
  ;;' (add 1 2)  ; => 3
  (define add (lambda (a b) (+ a b)))
)"

  parser <- DocParser$new()
  result <- parser$parse_text(code)

  expect_true("add" %in% names(result$functions))
  fn <- result$functions[["add"]]
  expect_equal(fn$description, "Add two numbers together.")
  expect_match(fn$examples, "add 1 2")
  expect_null(result$file)
})

test_that("string-input modules get annotation-based docs via eval_text", {
  thin()
  code <- "(module str-ann-mod
  (export greet)

  ;;' @description Create a greeting message.
  ;;' @examples
  ;;' (greet \"world\")  ; => \"hello, world\"
  (define greet (lambda (name) (string-append \"hello, \" name)))
)"

  engine <- make_engine()
  env <- engine$get_env()
  engine$eval_text(code, env = env)
  engine$eval_text("(import str-ann-mod :refer :all)", env = env)

  fn <- engine$eval_text("greet", env = env)
  doc <- attr(fn, "arl_doc", exact = TRUE)
  expect_false(is.null(doc))
  expect_equal(doc$description, "Create a greeting message.")
  expect_match(doc$examples, "greet")
})

test_that("DocParser parses @internal tag", {
  thin()
  tmp <- tempfile(fileext = ".arl")
  on.exit(unlink(tmp))
  writeLines(c(
    "(module test-mod",
    "  (export-all)",
    "",
    "  ;;' @internal",
    "  ;;' @description Internal helper.",
    "  (define __helper (lambda (x) x))",
    "",
    "  ;;' @description Public function.",
    "  (define pub-fn (lambda (x) x))",
    ")"
  ), tmp)

  parser <- DocParser$new()
  result <- parser$parse_file(tmp)

  expect_true(result$functions[["__helper"]]$internal)
  expect_false(result$functions[["pub-fn"]]$internal)
})

test_that("DocParser parses @noeval tag", {
  thin()
  tmp <- tempfile(fileext = ".arl")
  on.exit(unlink(tmp))
  writeLines(c(
    "(module test-mod",
    "  (export io-fn pure-fn)",
    "",
    "  ;;' @noeval",
    "  ;;' @description Reads a file.",
    "  ;;' @examples",
    "  ;;' (io-fn \"test.txt\")",
    "  (define io-fn (lambda (path) path))",
    "",
    "  ;;' @description Pure function.",
    "  ;;' @examples",
    "  ;;' (pure-fn 42)",
    "  (define pure-fn (lambda (x) x))",
    ")"
  ), tmp)

  parser <- DocParser$new()
  result <- parser$parse_file(tmp)

  expect_true(result$functions[["io-fn"]]$noeval)
  expect_false(result$functions[["pure-fn"]]$noeval)
})

test_that("annotation-based docs are available via compiler", {
  thin()
  tmp <- tempfile(fileext = ".arl")
  on.exit(unlink(tmp))
  writeLines(c(
    "(module test-ann-mod",
    "  (export add)",
    "",
    "  ;;' @description Add two numbers together.",
    "  ;;' @examples",
    "  ;;' (add 1 2)  ; => 3",
    "  ;;' @seealso sub",
    "  (define add",
    "    (lambda (a b) (+ a b)))",
    ")"
  ), tmp)

  engine <- make_engine()
  env <- engine$get_env()

  # Load the module from file (triggers annotation parsing in compiler)
  engine$eval(engine$read(sprintf('(load "%s")', arl_path(tmp)))[[1]], env = env)
  engine$eval(engine$read("(import test-ann-mod :refer :all)")[[1]], env = env)

  # The function should have annotation-based arl_doc
  fn <- engine$eval(engine$read("add")[[1]], env = env)
  doc <- attr(fn, "arl_doc", exact = TRUE)
  expect_false(is.null(doc))
  expect_equal(doc$description, "Add two numbers together.")
  expect_match(doc$examples, "add 1 2")
  expect_equal(doc$seealso, "sub")
})

test_that("DocParser loads reference docs and filters by kind", {
  thin()
  parser <- DocParser$new()
  docs_path <- system.file("reference-docs.dcf", package = "arl")
  expect_true(nzchar(docs_path))
  all_docs <- parser$load_reference_docs(docs_path)

  expect_true(length(all_docs) > 0)
  expect_true("if" %in% names(all_docs))
  expect_true("eval" %in% names(all_docs))
  expect_equal(all_docs[["if"]]$kind, "special-form")

  builtins <- parser$load_builtins(docs_path)
  expect_true("eval" %in% names(builtins))
  expect_false("if" %in% names(builtins))
})

test_that("@param tags are parsed correctly", {
  thin()
  parser <- DocParser$new()
  result <- parser$parse_text(paste(
    "(module param-test",
    "  (export add)",
    "",
    "  ;;' @description Add two numbers.",
    "  ;;' @param a First number",
    "  ;;' @param b Second number",
    "  (define add (lambda (a b) (+ a b)))",
    ")",
    sep = "\n"
  ))

  fn <- result$functions[["add"]]
  expect_false(is.null(fn))
  expect_equal(length(fn$params), 2)
  expect_equal(fn$params[[1]]$name, "a")
  expect_equal(fn$params[[1]]$description, "First number")
  expect_equal(fn$params[[2]]$name, "b")
  expect_equal(fn$params[[2]]$description, "Second number")
})

test_that("unknown @ tags emit a warning", {
  thin()
  parser <- DocParser$new()
  expect_warning(
    parser$parse_text(paste(
      "(module warn-test",
      "  (export foo)",
      "",
      "  ;;' @description A function.",
      "  ;;' @bogus something",
      "  (define foo (lambda () 1))",
      ")",
      sep = "\n"
    )),
    "@bogus"
  )
})

test_that("@param tags produce arl_doc$arguments via compiler", {
  thin()
  tmp <- tempfile(fileext = ".arl")
  on.exit(unlink(tmp))
  writeLines(c(
    "(module param-compiler-test",
    "  (export add)",
    "",
    "  ;;' @description Add two numbers.",
    "  ;;' @param a First number",
    "  ;;' @param b Second number",
    "  (define add",
    "    (lambda (a b) (+ a b)))",
    ")"
  ), tmp)

  engine <- make_engine()
  env <- engine$get_env()
  engine$eval(engine$read(sprintf('(load "%s")', arl_path(tmp)))[[1]], env = env)
  engine$eval(engine$read("(import param-compiler-test :refer :all)")[[1]], env = env)

  fn <- engine$eval(engine$read("add")[[1]], env = env)
  doc <- attr(fn, "arl_doc", exact = TRUE)
  expect_false(is.null(doc))
  expect_false(is.null(doc$arguments))
  expect_match(doc$arguments, "a")
  expect_match(doc$arguments, "First number")
  expect_match(doc$arguments, "b")
  expect_match(doc$arguments, "Second number")
})

Try the arl package in your browser

Any scripts or data that you put into this service are public.

arl documentation built on March 19, 2026, 5:09 p.m.