tests/testthat/test-load.R

thin <- make_cran_thinner()

test_that("engine$load_file_in_env evaluates source into environment", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  env <- engine$get_env()

  path <- tempfile(fileext = ".arl")
  writeLines(c("(define foo 42)", "(define bar (+ foo 1))"), path)
  on.exit(unlink(path), add = TRUE)

  engine$load_file_in_env(path)

  expect_equal(get("foo", envir = env), 42)
  expect_equal(get("bar", envir = env), 43)
})

test_that("(load ...) evaluates file in current environment", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  env <- engine$get_env()

  path <- tempfile(fileext = ".arl")
  writeLines("(define foo 7)", path)
  on.exit(unlink(path), add = TRUE)

  path_for_arl <- normalizePath(path, winslash = "/", mustWork = FALSE)
  exprs <- engine$read(paste0("(load \"", path_for_arl, "\")"))
  engine$eval(exprs[[1]], env = env)

  expect_equal(get("foo", envir = env), 7)
})

test_that("(load path env) evaluates file in the provided environment", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  caller_env <- engine$get_env()
  target_env <- new.env(parent = caller_env)

  path <- tempfile(fileext = ".arl")
  writeLines("(define scoped-value 11)", path)
  on.exit(unlink(path), add = TRUE)

  path_for_arl <- normalizePath(path, winslash = "/", mustWork = FALSE)
  exprs <- engine$read(paste0("(load \"", path_for_arl, "\" target-env)"))
  caller_env$`target-env` <- target_env
  engine$eval(exprs[[1]], env = caller_env)

  expect_equal(get("scoped-value", envir = target_env), 11)
  expect_false(exists("scoped-value", envir = caller_env, inherits = FALSE))
})

test_that("(load ...) does not resolve stdlib by name", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  env <- engine$get_env()

  # load should NOT do stdlib lookup; that's import's job
  expect_error(
    engine$eval(engine$read("(load \"control\")")[[1]], env = env),
    "File not found"
  )
})

test_that("(load ...) re-evaluates on each call", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  env <- engine$get_env()

  path <- tempfile(fileext = ".arl")
  writeLines("(set! counter (+ counter 1))", path)
  on.exit(unlink(path), add = TRUE)

  # Initialize counter
  assign("counter", 0L, envir = env)

  path_for_arl <- normalizePath(path, winslash = "/", mustWork = FALSE)
  expr <- engine$read(paste0("(load \"", path_for_arl, "\")"))[[1]]

  engine$eval(expr, env = env)
  expect_equal(get("counter", envir = env), 1L)

  engine$eval(expr, env = env)
  expect_equal(get("counter", envir = env), 2L)
})

test_that("stdlib modules register macros", {
  thin()
  engine <- make_engine()
  env <- engine$get_env()
  import_stdlib_modules(engine, c("control", "binding", "threading"))

  expect_true(engine_field(engine, "macro_expander")$is_macro(as.symbol("when"), env = env))
  expect_true(engine_field(engine, "macro_expander")$is_macro(as.symbol("unless"), env = env))
  expect_true(engine_field(engine, "macro_expander")$is_macro(as.symbol("let"), env = env))
  expect_true(engine_field(engine, "macro_expander")$is_macro(as.symbol("->"), env = env))
  expect_true(engine_field(engine, "macro_expander")$is_macro(as.symbol("try-catch"), env = env))
})

test_that("(import ...) loads module exports into environment", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  env <- engine$get_env()

  module_name <- paste0("math", sample.int(100000, 1))
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  old_dir <- getwd()
  setwd(tmp_dir)
  on.exit({
    setwd(old_dir)
    unlink(tmp_dir, recursive = TRUE)
  }, add = TRUE)

  module_file <- file.path(tmp_dir, paste0(module_name, ".arl"))
  writeLines(c(
    sprintf("(module %s", module_name),
    "  (export square inc)",
    "  (define square (lambda (x) (* x x)))",
    "  (define inc (lambda (x) (+ x 1))))"
  ), module_file)

  exprs <- engine$read(sprintf("(import %s :refer :all)", module_name))
  engine$eval(exprs[[1]], env = env)

  exprs <- engine$read("(square 3)")
  expect_equal(engine$eval(exprs[[1]], env = env), 9)
})

test_that("(import ...) does not re-evaluate loaded modules", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  env <- engine$get_env()

  module_name <- paste0("counter", sample.int(100000, 1))
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  old_dir <- getwd()
  setwd(tmp_dir)
  on.exit({
    setwd(old_dir)
    unlink(tmp_dir, recursive = TRUE)
  }, add = TRUE)

  module_file <- file.path(tmp_dir, paste0(module_name, ".arl"))
  writeLines(c(
    sprintf("(module %s", module_name),
    "  (export tick)",
    "  (define counter 0)",
    "  (define tick (lambda () (begin (set! counter (+ counter 1)) counter))))"
  ), module_file)

  exprs <- engine$read(sprintf("(import %s :refer :all)", module_name))
  engine$eval(exprs[[1]], env = env)

  exprs <- engine$read("(tick)")
  expect_equal(engine$eval(exprs[[1]], env = env), 1)
  expect_equal(engine$eval(exprs[[1]], env = env), 2)

  exprs <- engine$read(sprintf("(import %s :refer :all)", module_name))
  engine$eval(exprs[[1]], env = env)
  expect_equal(engine$eval(engine$read("(tick)")[[1]], env = env), 3)
})

test_that("(import ...) errors on missing modules and exports", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  env <- engine$get_env()

  missing_name <- paste0("missing", sample.int(100000, 1))
  exprs <- engine$read(sprintf("(import %s)", missing_name))
  expect_error(engine$eval(exprs[[1]], env = env), "Module not found")

  module_name <- paste0("bad", sample.int(100000, 1))
  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  old_dir <- getwd()
  setwd(tmp_dir)
  on.exit({
    setwd(old_dir)
    unlink(tmp_dir, recursive = TRUE)
  }, add = TRUE)

  module_file <- file.path(tmp_dir, paste0(module_name, ".arl"))
  writeLines(c(
    sprintf("(module %s", module_name),
    "  (export missing-value))"
  ), module_file)

  exprs <- engine$read(sprintf("(import %s)", module_name))
  expect_error(engine$eval(exprs[[1]], env = env), "not defined or imported")
})

test_that("(import \"path\") loads module by path and attaches exports", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  env <- engine$get_env()

  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  old_dir <- getwd()
  setwd(tmp_dir)
  on.exit({
    setwd(old_dir)
    unlink(tmp_dir, recursive = TRUE)
  }, add = TRUE)

  module_file <- file.path(tmp_dir, "pathmod.arl")
  writeLines(c(
    "(module pathmod",
    "  (export double)",
    "  (define double (lambda (x) (* x 2))))"
  ), module_file)

  exprs <- engine$read(sprintf('(import "%s" :refer :all)', arl_path(module_file)))
  engine$eval(exprs[[1]], env = env)

  exprs <- engine$read("(double 7)")
  expect_equal(engine$eval(exprs[[1]], env = env), 14)
})

test_that("second (import \"path\") does not reload module", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  env <- engine$get_env()

  tmp_dir <- tempfile()
  dir.create(tmp_dir)
  old_dir <- getwd()
  setwd(tmp_dir)
  on.exit({
    setwd(old_dir)
    unlink(tmp_dir, recursive = TRUE)
  }, add = TRUE)

  module_file <- file.path(tmp_dir, "countmod.arl")
  writeLines(c(
    "(module countmod",
    "  (export getn)",
    "  (define n 0)",
    "  (define getn (lambda () (begin (set! n (+ n 1)) n))))"
  ), module_file)

  path_abs <- normalizePath(module_file, winslash = "/", mustWork = TRUE)
  engine$eval(engine$read(sprintf('(import "%s" :refer :all)', path_abs))[[1]], env = env)
  expect_equal(engine$eval(engine$read("(getn)")[[1]], env = env), 1)

  engine$eval(engine$read(sprintf('(import "%s" :refer :all)', path_abs))[[1]], env = env)
  expect_equal(engine$eval(engine$read("(getn)")[[1]], env = env), 2)
})

test_that("relative import paths resolve from importing file's directory", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  env <- engine$get_env()

  # Create nested directory structure
  tmp_dir <- tempfile()
  dir.create(file.path(tmp_dir, "lib"), recursive = TRUE)

  # lib/helper.arl - a module in a subdirectory
  writeLines(c(
    "(module helper",
    "  (export helper-fn)",
    "  (define helper-fn (lambda () 99)))"
  ), file.path(tmp_dir, "lib", "helper.arl"))

  # lib/main.arl - imports sibling via relative path
  writeLines(c(
    '(import "helper.arl" :refer :all)',
    "(define main-val (helper-fn))"
  ), file.path(tmp_dir, "lib", "main.arl"))

  on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE)

  # CWD is NOT the lib directory — relative path must resolve from file, not CWD
  main_path <- normalizePath(file.path(tmp_dir, "lib", "main.arl"),
                             winslash = "/", mustWork = TRUE)
  engine$load_file_in_env(main_path)
  expect_equal(get("main-val", envir = env), 99)
})

test_that("(import symbol) is module name, (import \"string\") is path", {
  thin()
  engine <- make_engine()
  env <- engine$get_env()

  expect_silent(engine$eval(engine$read("(import control :refer :all)")[[1]], env = env))
  expect_true(engine_field(engine, "macro_expander")$is_macro(as.symbol("when"), env = env))

  missing_path <- tempfile(fileext = ".arl")
  expect_false(file.exists(missing_path))
  expect_error(
    engine$eval(engine$read(sprintf('(import "%s")', arl_path(missing_path)))[[1]], env = env),
    "Module not found"
  )
})

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.