File: engine.R

package info (click to toggle)
r-cran-assertive.base 0.0-9-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, trixie
  • size: 476 kB
  • sloc: sh: 13; makefile: 2
file content (147 lines) | stat: -rw-r--r-- 4,805 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#' Throws an error if a condition isn't met
#'
#' The workhorse of the package that creates an assertion from a predicate.  
#' If a condition isn't met, then an error is thrown.  This function is exported
#' for use by package developers so that they can create their own assert 
#' functions.  
#'
#' @param predicate Function that returns a logical value (possibly 
#' a vector).
#' @param ... Passed to the \code{predicate} function.
#' @param msg The error message, in the event of failure.
#' @param what Either 'all' or 'any', to reduce vectorised tests to a 
#' single value.
#' @param na_ignore A logical value.  If \code{FALSE}, \code{NA} values
#' cause an error; otherwise they do not.  Like \code{na.rm} in many
#' stats package functions, except that the position of the failing
#' values does not change.
#' @param severity How severe should the consequences of the assertion be?  
#' Either \code{"stop"}, \code{"warning"}, \code{"message"}, or \code{"none"}.
#' @return \code{FALSE} with the attribute \code{message}, as provided
#' in the input.
#' @note Missing values are considered as \code{FALSE} for the purposes of
#' whether or not an error is thrown.
#' @examples 
#' # Basic usage is like do.call; pass a predicate and the arguments to it.
#' dont_stop(assert_engine(is_true, c(TRUE, FALSE, NA)))
#' 
#' # Customise the error message
#' dont_stop(
#'   assert_engine(is_true, c(TRUE, FALSE, NA), msg = "Not everything is true")
#' )
#' 
#' # Only fail when no values match the predicate's conditions
#' dont_stop(assert_engine(is_true, logical(3), what = "any"))
#' 
#' # You can use base predicates, but the error message isn't as informative
#' dont_stop(assert_engine(is.matrix, 1:5))
#' 
#' # Reduce the severity of failure
#' assert_engine(is_true, c(TRUE, FALSE, NA), severity = "message")
#' 
#' @export
assert_engine <- function(predicate, ..., msg = "The assertion failed.", what = c("all", "any"), na_ignore = FALSE, severity = c("stop", "warning", "message", "none"))
{
  handler_type <- match.arg(severity)
  dots <- list(...)
  return_value <- if(length(dots) > 0) dots[[1]] else NULL
  if(handler_type == "none") 
  {
    return(invisible(return_value))
  }
  what <- match.fun(match.arg(what))
  predicate_name <- get_name_in_parent(predicate)
  
  ok <- predicate(...)
  if(inherits(ok, "scalar_with_cause"))
  {
    if(!isTRUE(ok))
    {
      if(missing(msg))
      {
        msg <- cause(ok)
      }
      give_feedback(handler_type, msg, predicate_name)
    }
  } else # inherits(ok, "vector_with_cause")
  {
    really_ok <- if(na_ignore)
    {
      # ok can be TRUE or NA; FALSE is bad
      ok | is.na(ok)
    } else
    {
      # ok can be TRUE; FALSE or NA is bad
      ok & !is.na(ok)
    }
    if(!what(really_ok))
    {
      # Append first few failure values and positions to the error message.
      msg <- paste(enc2utf8(msg), print_and_capture(ok), sep = "\n")
      give_feedback(handler_type, msg, predicate_name)
    }
  }
  invisible(return_value)
}

give_feedback <- function(handler_type, msg, predicate_name)
{
  handler <- match.fun(
    handler_type
  )
  ass_condition <- switch(
    handler_type,
    stop = assertionError,
    warning = assertionWarning,
    message = assertionMessage
  )
  # Throw error/warning/message
  caller <- if(sys.nframe() >= 3)
  {
    sys.call(-3)
  } else
  {
    NULL
  }
  
  # UTF-8 characters do not display correctly under Windows for some 
  # LC_CTYPE locale values, but there isn't much assertive can do about that.
  # https://stackoverflow.com/q/32696241/134830
  handler(ass_condition(paste(predicate_name, msg, sep = " : "), caller, predicate_name))
}

#' FALSE, with a cause of failure.
#'
#' Always returns the value \code{FALSE}, with a cause attribute.
#'
#' @param ... Passed to \code{gettextf} to create a cause of failure message.
#' @return \code{FALSE} with the attribute \code{cause}, as provided
#' in the input.
#' @seealso \code{\link{cause}} and \code{\link{na}}.
#' @export
false <- function(...)
{
  msg <- if(nargs() > 0L) sprintf(...) else ""
  x <- FALSE
  cause(x) <- msg[1]
  class(x) <- c("scalar_with_cause", "logical")
  x
}

#' NA, with a cause of failure.
#'
#' Always returns the value (logical) \code{NA}, with a cause attribute.
#'
#' @param ... Passed to \code{gettextf} to create a cause of failure message.
#' @return \code{NA} with the attribute \code{cause}, as provided
#' in the input.
#' @seealso \code{\link{cause}} and \code{\link{false}}.
#' @export
na <- function(...)
{
  msg <- if(nargs() > 0L) sprintf(...) else ""
  x <- NA
  cause(x) <- msg[1]
  class(x) <- c("scalar_with_cause", "logical")
  x
}