tests/testthat/test-expected-demo.R

# Demonstration of Expected Element Error Reporting
# This file demonstrates the new functionality for showing expected elements
# in parser error messages, making debugging much easier.

test_that("Demo: Basic literal parser shows what was expected", {
  reset_LNR()

  # When a literal parser fails, it now tells you what it expected
  err <- tryCatch(
    reporter(literal("start") %then% eof())(c("wrong")),
    error = function(e) e
  )

  expect_match(err$message, "Expected: literal 'start'")
  expect_match(err$message, "line 1")
})

test_that("Demo: Multiple alternatives show all options", {
  reset_LNR()

  # When using %or%, the error shows all alternatives that could have worked
  command_parser <- literal("GET") %or%
    literal("POST") %or%
    literal("PUT") %or%
    literal("DELETE")

  err <- tryCatch(
    reporter(command_parser %then% eof())("PATCH"),
    error = function(e) e
  )

  expect_match(err$message, "Expected one of:")
  expect_match(err$message, "literal 'GET'")
  expect_match(err$message, "literal 'POST'")
  expect_match(err$message, "literal 'PUT'")
  expect_match(err$message, "literal 'DELETE'")
})

test_that("Demo: Named parsers provide semantic error messages", {
  reset_LNR()

  # Use named() to give semantic meaning to parsers
  DNA_base <- named(
    satisfy(function(x) x %in% c("A", "T", "G", "C")),
    "DNA base (A, T, G, or C)"
  )

  # Don't use eof() here - we want to see the DNA_base error, not eof error
  err <- tryCatch(
    reporter(DNA_base %then% eof())(c("X")),
    error = function(e) e
  )

  expect_match(err$message, "line 1")
  expect_match(err$message, "Expected: DNA base")
})

test_that("Demo: Complex parser with meaningful names", {
  reset_LNR()

  # Build a simple parser for structured data with meaningful names
  header <- named(
    satisfy(function(x) grepl("^#", x)),
    "header (line starting with #)"
  )

  number <- named(
    satisfy(function(x) grepl("^\\d+$", x)),
    "number"
  )

  text <- named(
    satisfy(function(x) grepl("^[a-zA-Z ]+$", x)),
    "text"
  )

  data_line <- number %or% text

  simple_file <- header %then% one_or_more(data_line) %then% eof()

  # Test the data_line parser directly to see its error message
  err <- tryCatch(
    reporter(data_line %then% eof())("!!!"),
    error = function(e) e
  )

  expect_match(err$message, "line 1")
  expect_match(err$message, "Expected one of:")
  expect_match(err$message, "number")
  expect_match(err$message, "text")
})

test_that("Demo: Fasta-like parser with clear error messages", {
  reset_LNR()

  # Realistic example: parsing sequence data
  fasta_header <- named(
    satisfy(function(x) grepl("^>\\w+", x)),
    "FASTA header (>sequence_name)"
  )

  nucleotide_line <- named(
    satisfy(function(x) grepl("^[GATC]+$", x)),
    "nucleotide sequence (only G, A, T, C)"
  )

  protein_line <- named(
    satisfy(function(x) grepl("^[ARNDCQEGHILKMFPSTWYV]+$", x)),
    "protein sequence"
  )

  sequence_line <- nucleotide_line %or% protein_line

  fasta_entry <- fasta_header %then% one_or_more(sequence_line)

  # Valid input works fine
  valid <- c(">seq1", "GATCGATC", "AAATTTGGG")
  result <- reporter(fasta_entry %then% eof())(valid)
  expect_true(is.list(result))

  # Invalid sequence gives clear error - test sequence_line directly
  err <- tryCatch(
    reporter(sequence_line %then% eof())("12345"),
    error = function(e) e
  )

  expect_match(err$message, "line 1")
  expect_match(err$message, "Expected one of:")
  expect_match(err$message, "nucleotide sequence")
  expect_match(err$message, "protein sequence")

  # Missing header gives clear error
  err2 <- tryCatch(
    reporter(fasta_entry %then% eof())(c("GATC", "AAATTT")),
    error = function(e) e
  )

  expect_match(err2$message, "line 1")
  expect_match(err2$message, "Expected: FASTA header")
})

test_that("Demo: Sequential parser errors point to exact failure", {
  reset_LNR()

  # Build a parser for a simple protocol
  version <- named(literal("v1.0"), "version 'v1.0'")
  separator <- literal("---")
  status_ok <- named(literal("OK"), "status 'OK'")
  status_err <- named(literal("ERROR"), "status 'ERROR'")
  status <- status_ok %or% status_err

  protocol <- version %then% separator %then% status %then% eof()

  # Error in first element
  err1 <- tryCatch(
    reporter(protocol)(c("v2.0", "---", "OK")),
    error = function(e) e
  )
  expect_match(err1$message, "line 1")
  expect_match(err1$message, "Expected: version 'v1.0'")

  # Error in middle element
  err2 <- tryCatch(
    reporter(protocol)(c("v1.0", "+++", "OK")),
    error = function(e) e
  )
  expect_match(err2$message, "line 2")
  expect_match(err2$message, "Expected: literal '---'")

  # Error in last element
  err3 <- tryCatch(
    reporter(protocol)(c("v1.0", "---", "PENDING")),
    error = function(e) e
  )
  expect_match(err3$message, "line 3")
  expect_match(err3$message, "Expected one of:")
  expect_match(err3$message, "status 'OK'")
  expect_match(err3$message, "status 'ERROR'")
})

test_that("Demo: Error messages help understand complex parser structure", {
  reset_LNR()

  # A parser for key-value configuration files
  comment <- named(
    satisfy(function(x) grepl("^#", x)),
    "comment line"
  )

  key_value <- named(
    satisfy(function(x) grepl("^\\w+\\s*=\\s*.+$", x)),
    "key=value pair"
  )

  config_line <- comment %or% EmptyLine() %or% key_value

  # This helps users understand what constitutes a valid line
  # Test config_line directly with invalid input
  err <- tryCatch(
    reporter(config_line %then% eof())("invalid line here"),
    error = function(e) e
  )

  expect_match(err$message, "line 1")
  # The error should mention what kinds of lines are acceptable
  expect_match(err$message, "Expected one of:")
})

test_that("Demo: Named wrappers replace underlying expectations", {
  reset_LNR()

  # Sometimes you want to hide implementation details
  # and show user-friendly names

  # Without named: shows technical details
  ip_octet_raw <- satisfy(function(x) {
    num <- suppressWarnings(as.numeric(x))
    !is.na(num) && num >= 0 && num <= 255
  })

  err_raw <- tryCatch(
    reporter(ip_octet_raw %then% eof())("999"),
    error = function(e) e
  )
  expect_match(err_raw$message, "matching input") # Generic message

  # With named: shows semantic meaning
  ip_octet <- named(
    satisfy(function(x) {
      num <- suppressWarnings(as.numeric(x))
      !is.na(num) && num >= 0 && num <= 255
    }),
    "IP octet (0-255)"
  )

  err_named <- tryCatch(
    reporter(ip_octet %then% eof())("999"),
    error = function(e) e
  )
  expect_match(err_named$message, "IP octet") # Clear message
})

Try the parcr package in your browser

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

parcr documentation built on Feb. 15, 2026, 5:07 p.m.