tests/testthat/test-source-positions.R

# Tests for source position tracking
# Verifies that line and column information is preserved through parsing and macro expansion

thin <- make_cran_thinner()

test_that("parsed expressions have source positions", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  parsed <- engine$read("(+ 1 2)")

  # Check that parsed result has some structure
  expect_true(length(parsed) > 0)

  # The actual position tracking depends on implementation
  # This test validates the basic structure exists
  expr <- parsed[[1]]
  expect_true(!is.null(expr))
})

test_that("multiline expressions preserve line numbers", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  code <- "(define foo (lambda (x)
  (+ x 1)
  (* x 2)))"

  parsed <- engine$read(code)
  expect_true(length(parsed) > 0)

  # Should be able to parse without error
  expect_no_error(engine$eval(parsed[[1]]))
})

test_that("nested expressions have positions", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  code <- "(* (+ 1 2) 3)"

  parsed <- engine$read(code)
  expect_true(length(parsed) > 0)

  # Verify it evaluates correctly
  result <- engine$eval(parsed[[1]])
  expect_equal(result, 9)
})

test_that("quoted expressions preserve positions", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  code <- "'(a b c)"

  parsed <- engine$read(code)
  expect_true(length(parsed) > 0)

  # Quote should work - returns a call/quote object
  result <- engine$eval(parsed[[1]])
  expect_true(is.call(result) || is.list(result))
})

test_that("quasiquoted expressions preserve positions", {
  thin()
  engine <- make_engine(load_prelude = FALSE)
  code <- "`(a ,b c)"

  parsed <- engine$read(code)
  expect_true(length(parsed) > 0)

  # The parsed quasiquote should be a call with unquote structure
  expr <- parsed[[1]]
  expect_true(is.call(expr))
  expect_equal(as.character(expr[[1]]), "quasiquote")
})

test_that("macro expansion preserves source info", {
  thin()
  engine <- make_engine(load_prelude = FALSE)

  # Define a simple macro
  engine$eval_text("(defmacro double (x) `(* 2 ,x))")

  # Use the macro
  code <- "(double 5)"
  parsed <- engine$read(code)

  # Should expand and evaluate correctly
  result <- engine$eval(parsed[[1]])
  expect_equal(result, 10)
})

test_that("error location is reported for syntax errors", {
  thin()
  engine <- make_engine(load_prelude = FALSE)

  # This should error with location info
  expect_error(
    engine$eval_text("(+ 1 "),  # Unclosed paren
    "Unclosed|parse|EOF|incomplete|unexpected end"
  )
})

test_that("error location for runtime errors", {
  thin()
  engine <- make_engine(load_prelude = FALSE)

  # Division by zero returns Inf in R, not an error
  # Let's test with an actual error: undefined function
  expect_error(
    engine$eval_text("(undefined-function 5)"),
    "not found|undefined|unknown"
  )
})

test_that("line numbers in multiline function definition", {
  thin()
  engine <- make_engine(load_prelude = FALSE)

  code <- "(define multi-line (lambda (a b c)
  (begin
    (define x (+ a b))
    (define y (+ x c))
    (* y 2))))"

  parsed <- engine$read(code)
  expect_no_error(engine$eval(parsed[[1]]))

  # Call the function
  result <- engine$eval_text("(multi-line 1 2 3)")
  expect_equal(result, 12)
})

test_that("positions preserved through multiple forms", {
  thin()
  engine <- make_engine(load_prelude = FALSE)

  code <- "(define x 5)\n(define y 10)\n(+ x y)"
  parsed <- engine$read(code)

  # Should have 3 forms
  expect_equal(length(parsed), 3)

  # Evaluate all
  for (form in parsed) {
    result <- engine$eval(form)
  }

  expect_equal(result, 15)
})

test_that("source positions in do blocks", {
  thin()
  engine <- make_engine(load_prelude = FALSE)

  code <- "(begin
  (define a 1)
  (define b 2)
  (+ a b))"

  parsed <- engine$read(code)
  result <- engine$eval(parsed[[1]])
  expect_equal(result, 3)
})

test_that("positions in nested function calls", {
  thin()
  engine <- make_engine(load_prelude = FALSE)

  code <- "(+ (* 2 3) (- 10 5))"
  parsed <- engine$read(code)
  result <- engine$eval(parsed[[1]])
  expect_equal(result, 11)
})

test_that("comment positions don't break parsing", {
  thin()
  engine <- make_engine(load_prelude = FALSE)

  code <- "; This is a comment\n(+ 1 2) ; inline comment\n; another comment"
  parsed <- engine$read(code)

  # Should get one expression (comments are ignored)
  expect_equal(length(parsed), 1)
  result <- engine$eval(parsed[[1]])
  expect_equal(result, 3)
})

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.