├── tests
├── core
├── unitizer
│ ├── helper
│ │ ├── emoji.txt
│ │ ├── UTF-8-test.txt
│ │ ├── latin-1.R
│ │ ├── UTF-8-unicode-10-ex.R
│ │ └── UTF-8-critical.R
│ ├── eval.unitizer
│ │ └── data.rds
│ ├── misc.unitizer
│ │ └── data.rds
│ ├── tev.unitizer
│ │ └── data.rds
│ ├── type.unitizer
│ │ └── data.rds
│ ├── _pre
│ │ ├── lorem.objs
│ │ │ ├── lorem.RDS
│ │ │ ├── lorem.cn.RDS
│ │ │ ├── lorem.ru.RDS
│ │ │ └── lorem.tr.RDS
│ │ └── lorem.R
│ ├── alike.unitizer
│ │ └── data.rds
│ ├── all-bw.unitizer
│ │ └── data.rds
│ ├── classes.unitizer
│ │ └── data.rds
│ ├── mytests.unitizer
│ │ └── data.rds
│ ├── notcran.unitizer
│ │ └── data.rds
│ ├── parse.unitizer
│ │ └── data.rds
│ ├── abstract.unitizer
│ │ └── data.rds
│ ├── cstringr.unitizer
│ │ └── data.rds
│ ├── internal.unitizer
│ │ └── data.rds
│ ├── language.unitizer
│ │ └── data.rds
│ ├── validate.unitizer
│ │ └── data.rds
│ ├── validate.args.unitizer
│ │ └── data.rds
│ ├── tev.R
│ ├── misc.R
│ ├── notcran.R
│ ├── type.R
│ ├── abstract.R
│ ├── classes.R
│ ├── parse.R
│ ├── eval.R
│ ├── validate.args.R
│ └── language.R
├── Rplots.pdf
├── run.R
└── valgrind
│ └── run.R
├── .github
├── .gitignore
└── workflows
│ └── R-CMD-check.yaml
├── extra
├── 020-fast.png
├── 010-like-vapply.png
├── 021-in-functions.png
├── 040-github-banner.png
├── 030-programmable-nse.png
├── 011-handles-complex-objs.png
├── compare-nav.Rmd
└── PUBLISH.Rmd
├── src
├── all-bw.h
├── valname.c
├── trackinghash.h
├── settings.h
├── cstringrtest.c
├── pfhash.h
├── backports.h
├── misc.c
├── cstringr.h
├── cstringr-ext.c
├── assumptions.c
├── type.c
├── validate.h
├── r-copied.c
├── recurse.c
├── envtrack.c
├── trackinghash.c
└── init.c
├── covr.R
├── .Rbuildignore
├── man
├── vetr-internal.Rd
├── abstract.ggplot.Rd
├── vetr-package.Rd
├── bench_mark.Rd
├── nullify.Rd
├── type_alike.Rd
├── all_bw.Rd
├── abstract.Rd
├── vetr.Rd
├── alike.Rd
├── vetr_settings.Rd
├── vet_token.Rd
└── vet.Rd
├── vignettes
├── rmdhunks
│ ├── microbenchmark.Rmd
│ ├── trust-but-verify.Rmd
│ ├── valaddin.Rmd
│ ├── vetting-expressions.Rmd
│ ├── related-packages.Rmd
│ └── declarative-checks.Rmd
└── styles.css
├── R
├── templates.R
├── system.R
├── vetr-package.R
├── helper.R
├── type.R
├── all-bw.R
├── benchmark.R
├── misc.R
└── alike.R
├── CRAN.md
├── DESCRIPTION
├── NAMESPACE
├── RELEASE.Rmd
└── CONTRIBUTING.md
/tests/core:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 |
--------------------------------------------------------------------------------
/tests/unitizer/helper/emoji.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/Rplots.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/Rplots.pdf
--------------------------------------------------------------------------------
/extra/020-fast.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/extra/020-fast.png
--------------------------------------------------------------------------------
/extra/010-like-vapply.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/extra/010-like-vapply.png
--------------------------------------------------------------------------------
/extra/021-in-functions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/extra/021-in-functions.png
--------------------------------------------------------------------------------
/extra/040-github-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/extra/040-github-banner.png
--------------------------------------------------------------------------------
/extra/030-programmable-nse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/extra/030-programmable-nse.png
--------------------------------------------------------------------------------
/extra/011-handles-complex-objs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/extra/011-handles-complex-objs.png
--------------------------------------------------------------------------------
/tests/unitizer/eval.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/eval.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/helper/UTF-8-test.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/helper/UTF-8-test.txt
--------------------------------------------------------------------------------
/tests/unitizer/misc.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/misc.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/tev.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/tev.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/type.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/type.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/_pre/lorem.objs/lorem.RDS:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/_pre/lorem.objs/lorem.RDS
--------------------------------------------------------------------------------
/tests/unitizer/alike.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/alike.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/all-bw.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/all-bw.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/classes.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/classes.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/mytests.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/mytests.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/notcran.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/notcran.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/parse.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/parse.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/abstract.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/abstract.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/cstringr.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/cstringr.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/internal.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/internal.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/language.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/language.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/validate.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/validate.unitizer/data.rds
--------------------------------------------------------------------------------
/tests/unitizer/_pre/lorem.objs/lorem.cn.RDS:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/_pre/lorem.objs/lorem.cn.RDS
--------------------------------------------------------------------------------
/tests/unitizer/_pre/lorem.objs/lorem.ru.RDS:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/_pre/lorem.objs/lorem.ru.RDS
--------------------------------------------------------------------------------
/tests/unitizer/_pre/lorem.objs/lorem.tr.RDS:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/_pre/lorem.objs/lorem.tr.RDS
--------------------------------------------------------------------------------
/tests/unitizer/validate.args.unitizer/data.rds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/brodieG/vetr/HEAD/tests/unitizer/validate.args.unitizer/data.rds
--------------------------------------------------------------------------------
/src/all-bw.h:
--------------------------------------------------------------------------------
1 | #include "cstringr.h"
2 |
3 | #ifndef _ALLBW_H
4 | #define _ALLBW_H
5 |
6 | SEXP VALC_all_bw(SEXP x, SEXP hi, SEXP lo, SEXP na_rm, SEXP include_bounds);
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/extra/compare-nav.Rmd:
--------------------------------------------------------------------------------
1 |
2 |
3 | | [Top](#top) | [Overview](#overview) | [Details](#details) | [Benchmarks](#benchmarks) | [Appendix](#appendix)
4 |
5 |
6 |
--------------------------------------------------------------------------------
/covr.R:
--------------------------------------------------------------------------------
1 | options(covr.exclude_end="(?://|#)[[:space:]]*nocov[[:space:]]*end")
2 | options(covr.exclude_start="(?://|#)[[:space:]]*nocov[[:space:]]*start")
3 | options(covr.exclude_pattern="(?://|#)[[:space:]]*nocov")
4 |
5 | covr::codecov()
6 |
--------------------------------------------------------------------------------
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | ^\.
2 | ^.*\.Rproj$
3 | ^\.Rproj\.user$
4 | ^DEVNOTES\.md$
5 | ^CONTRIBUTING\.md$
6 | ^CRAN\.md$
7 | ^\.travis\.yml$
8 | .Rdata
9 | ^covr\.R
10 | ^extra$
11 | ^scratch
12 | ^README\.Rmd
13 | ^README\.html
14 | ^RELEASE\.Rmd
15 | notcran
16 |
--------------------------------------------------------------------------------
/tests/unitizer/helper/latin-1.R:
--------------------------------------------------------------------------------
1 | # Some latin-1 codes, on own file due to Solaris issues with escaping
2 |
3 | lat.1.1 <- lat.1.2 <- c(
4 | "ni\xF1a",
5 | "hello",
6 | "\xB5 \xB6 \xBF \xC9 \xF4"
7 | )
8 | Encoding(lat.1.1) <- "latin1"
9 | Encoding(lat.1.2) <- "bytes"
10 |
11 |
--------------------------------------------------------------------------------
/man/vetr-internal.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/misc.R
3 | \docType{class}
4 | \name{vetr-interal}
5 | \alias{vetr-interal}
6 | \alias{vetr_bar-class}
7 | \alias{vetr_baz-class}
8 | \title{Test Objects}
9 | \description{
10 | Objects used for testing purposes only.
11 | }
12 | \keyword{internal}
13 |
--------------------------------------------------------------------------------
/tests/unitizer/helper/UTF-8-unicode-10-ex.R:
--------------------------------------------------------------------------------
1 | # These examples cannot be in-line in the unitizer because Solaris fails to
2 | # properly escape them, which then leads to failures.
3 |
4 | # Examples from the Unicode 10.0 docs
5 |
6 | unicode.10 <- c(
7 | "\xC2\x41\x41",
8 | "\x61\xF1\x80\x80",
9 | "\x61\xF1\x80\x80\xE1\x80",
10 | "\x61\xF1\x80\x80\xE1\x80\xC2\x62\x80\x63\x80\xBF\x64"
11 | )
12 | Encoding(unicode.10) <- "UTF-8"
13 |
14 |
--------------------------------------------------------------------------------
/tests/run.R:
--------------------------------------------------------------------------------
1 | cat(getwd(), "\n")
2 | if(suppressWarnings(require('unitizer'))) {
3 | local({
4 | suppressWarnings(RNGversion("3.5.2"));
5 | on.exit({
6 | RNGversion(as.character(getRversion()))
7 | })
8 | pattern <- 'alike'
9 | unitize_dir(
10 | 'unitizer',
11 | # pattern=pattern,
12 | state='recommended'
13 | )
14 | })
15 | } else {
16 | warning("Cannot run tests without package `unitizer`")
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/man/abstract.ggplot.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/abstract.R
3 | \name{abstract.ggplot}
4 | \alias{abstract.ggplot}
5 | \title{Experimental Abstraction Method for GGPlot}
6 | \usage{
7 | \method{abstract}{ggplot}(x, ...)
8 | }
9 | \description{
10 | Not entirely sure this can ever work well since so much of \code{ggplot} is
11 | done with \code{proto} objects and those do not really use meta data, which
12 | makes \code{alike} rather useless.
13 | }
14 | \keyword{internal}
15 |
--------------------------------------------------------------------------------
/tests/valgrind/run.R:
--------------------------------------------------------------------------------
1 | # overwrite unitizer_sect to just plain eval stuff
2 |
3 | unitizer_sect <- function(
4 | title = NULL, expr=expression(), details=character(), compare=identical
5 | ) {
6 | expr.sub <- substitute(expr)
7 | for(i in as.list(tail(expr.sub, -1L))) try(eval(i, envir=parent.frame()))
8 | }
9 |
10 | source('unitizer/_pre/lorem.R')
11 | source('unitizer/alike.R', echo=TRUE)
12 | source('unitizer/cstringr.R', echo=TRUE)
13 | source('unitizer/language.R', echo=TRUE)
14 | source('unitizer/validate.R', echo=TRUE)
15 |
--------------------------------------------------------------------------------
/vignettes/rmdhunks/microbenchmark.Rmd:
--------------------------------------------------------------------------------
1 |
2 |
3 | ```{r echo=FALSE}
4 | mb <- function(..., times=25) {
5 | if(require(microbenchmark, quietly=TRUE)) {
6 | mb.c <- match.call()
7 | mb.c[[1]] <- quote(microbenchmark::microbenchmark)
8 | res <- eval(mb.c, parent.frame())
9 | res.sum <- summary(res)
10 | cat(attr(res.sum, "unit"), "\n")
11 | print(res.sum[1:5])
12 | } else {
13 | warning("Package microbenchmark not available.")
14 | }
15 | }
16 | ```
17 |
--------------------------------------------------------------------------------
/vignettes/rmdhunks/trust-but-verify.Rmd:
--------------------------------------------------------------------------------
1 | ## Trust, but Verify
2 |
3 | ### Easily
4 |
5 | When you write functions that operate on S3 or unclassed objects you can either
6 | trust that your inputs will be structured as expected, or tediously check that
7 | they are.
8 |
9 | `vetr` takes the tedium out of structure verification so that you can trust,
10 | but verify. It lets you express structural requirements declaratively with
11 | templates, and it auto-generates human-friendly error messages as needed.
12 |
13 | ### Quickly
14 |
15 | `vetr` is written in C to minimize overhead from parameter checks in your
16 | functions. It has no dependencies.
17 |
18 |
--------------------------------------------------------------------------------
/R/templates.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | ## file currently empty
18 |
--------------------------------------------------------------------------------
/vignettes/rmdhunks/valaddin.Rmd:
--------------------------------------------------------------------------------
1 | # Valaddin
2 |
3 | Snippet used to compare `valaddin` to `vetr` performance. This is not actually
4 | generated as part of vignettes because it will then require `valaddin` as part
5 | of the build package, which brings in unwanted dependencies (via suggests) that
6 | make the build process slow on travis.
7 |
8 | ```{r}
9 | secant <- function(f, x, dx) (f(x + dx) - f(x)) / dx
10 |
11 | secant_valaddin <- valaddin::firmly(secant, list(~x, ~dx) ~ is.numeric)
12 |
13 | secant_stopifnot <- function(f, x, dx) {
14 | stopifnot(is.numeric(x), is.numeric(dx))
15 | secant(f, x, dx)
16 | }
17 |
18 | secant_vetr <- function(f, x, dx) {
19 | vetr(x=numeric(), dx=numeric())
20 | secant(f, x, dx)
21 | }
22 |
23 | library(microbenchmark)
24 | microbenchmark(
25 | secant_valaddin(log, 1, .1),
26 | secant_stopifnot(log, 1, .1),
27 | secant_vetr(log, 1, .1)
28 | )
29 | ```
30 |
--------------------------------------------------------------------------------
/CRAN.md:
--------------------------------------------------------------------------------
1 | ## Submission Checklist
2 |
3 | [x] Revdeps
4 | [x] Review CRAN policy
5 | [x] Check version
6 | [x] Run tests with
7 | [x] winbuilder
8 | [x] valgrind
9 | [ ] rchk
10 | [x] Check coverage
11 | [ ] Check build user
12 |
13 | ## Submission Notes:
14 |
15 | This is a minor release primarily intended
16 | to address the outstanding CRAN check warnings,
17 | and fix a minor bug.
18 |
19 | ## R CMD check --as-cran
20 |
21 | Completes with 'Status: OK'
22 |
23 | ## Test Environments
24 |
25 | I have tested this package against the following
26 | environments:
27 |
28 | * Travis Ubuntu 14.04.5 LTS
29 | * R devel (2018-06-20 r74923)
30 | * R version 3.5.0 (2017-01-27)
31 | * R version 3.4.4 (2017-01-27)
32 | * R version 3.2.5 (2017-01-27)
33 | * Winbuilder
34 | * R devel (2018-06-07 r74865)
35 | https://win-builder.r-project.org/T0GxmEvBfRne
36 | * Locally Mac OS 10.13.5
37 | * R Version 3.5.0 (2017-01-27)
38 |
39 |
--------------------------------------------------------------------------------
/R/system.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | ## Remove DLLs when package is unloaded
18 |
19 | # nocov start
20 | .onLoad <- function(libname, pkgname) {
21 | # Scheme defaults are fairly complex...
22 |
23 | check_assumptions()
24 | }
25 |
26 | .onUnload <- function(libpath) {
27 | library.dynam.unload("vetr", libpath)
28 | }
29 | # nocov end
30 |
--------------------------------------------------------------------------------
/src/valname.c:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include "alike.h"
20 |
21 | /*
22 | * External version for testing. See src/r-copied.c for implementation.
23 | */
24 | SEXP ALIKEC_is_valid_name_ext(SEXP name) {
25 | if(TYPEOF(name) != STRSXP || XLENGTH(name) != 1)
26 | error("Argument `name` must be character(1L)");
27 | return ScalarLogical(ALIKEC_is_valid_name(CHAR(asChar(name))));
28 | }
29 |
--------------------------------------------------------------------------------
/man/vetr-package.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/vetr-package.R
3 | \docType{package}
4 | \name{vetr-package}
5 | \alias{vetr-package}
6 | \title{Trust, but Verify}
7 | \description{
8 | Declarative template-based framework for verifying that objects meet
9 | structural requirements, and auto-composing error messages when they do not.
10 | }
11 | \seealso{
12 | Useful links:
13 | \itemize{
14 | \item \url{https://github.com/brodieG/vetr}
15 | \item Report bugs at \url{https://github.com/brodieG/vetr/issues}
16 | }
17 |
18 | }
19 | \author{
20 | \strong{Maintainer}: Brodie Gaslam \email{brodie.gaslam@yahoo.com}
21 |
22 | Other contributors:
23 | \itemize{
24 | \item Paxdiablo (Hash table implementation in src/pfhash.h) [copyright holder]
25 | \item R Core Team \email{R-core@r-project.org} (Used/adapted several code snippets from R sources, see src/r-copied.c) [copyright holder]
26 | \item Michael Chirico \email{michaelchirico4@gmail.com} (\href{https://orcid.org/0000-0003-0787-087X}{ORCID}) [contributor]
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/R/vetr-package.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | #' Trust, but Verify
18 | #'
19 | #' Declarative template-based framework for verifying that objects meet
20 | #' structural requirements, and auto-composing error messages when they do not.
21 | #'
22 | #' @name vetr-package
23 | #' @importFrom methods new
24 |
25 | "_PACKAGE"
26 |
27 | # importFrom methods needed for tests of reference classes because we don't want
28 | # to create the classes in the tests due to the topenv issues.
29 |
--------------------------------------------------------------------------------
/tests/unitizer/tev.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | library(vetr)
18 |
19 | unitizer_sect("tev", {
20 | tev(runif(2), numeric(2))
21 | tev(runif(3), numeric(2))
22 |
23 | # # we can no longer do this without including magrittr in suggests
24 | # has.magrittr <- suppressWarnings(require(magrittr, quietly=TRUE))
25 |
26 | # if(has.magrittr) runif(2) %>% tev(numeric(2)) %>% isTRUE else TRUE
27 | # if(has.magrittr) runif(3) %>% tev(numeric(2)) %>% isTRUE else FALSE
28 | })
29 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Package: vetr
2 | Title: Trust, but Verify
3 | Description: Declarative template-based framework for verifying that objects
4 | meet structural requirements, and auto-composing error messages when they do
5 | not.
6 | Version: 0.2.19
7 | Authors@R: c(
8 | person("Brodie", "Gaslam", email="brodie.gaslam@yahoo.com",
9 | role=c("aut", "cre")),
10 | person(given="Paxdiablo", role="cph", comment="Hash
11 | table implementation in src/pfhash.h"),
12 | person(given="R Core Team",
13 | email="R-core@r-project.org", role="cph",
14 | comment="Used/adapted several code snippets from R sources, see src/r-copied.c"),
15 | person("Michael","Chirico", role="ctb",
16 | email="michaelchirico4@gmail.com", comment = c(ORCID="0000-0003-0787-087X")
17 | ))
18 | Depends:
19 | R (>= 3.2.0)
20 | License: GPL (>=2)
21 | URL: https://github.com/brodieG/vetr
22 | BugReports: https://github.com/brodieG/vetr/issues
23 | VignetteBuilder: knitr
24 | Imports:
25 | methods,
26 | stats,
27 | utils
28 | Suggests:
29 | knitr,
30 | rmarkdown,
31 | unitizer
32 | RoxygenNote: 7.3.3
33 | Encoding: UTF-8
34 | Roxygen: list(markdown = TRUE, load = "installed")
35 |
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | S3method(abstract,array)
4 | S3method(abstract,data.frame)
5 | S3method(abstract,default)
6 | S3method(abstract,environment)
7 | S3method(abstract,ggplot)
8 | S3method(abstract,list)
9 | S3method(abstract,lm)
10 | S3method(abstract,matrix)
11 | S3method(abstract,ts)
12 | S3method(nullify,default)
13 | export(CHR)
14 | export(CHR.1)
15 | export(CPX)
16 | export(CPX.1)
17 | export(GT.0)
18 | export(GTE.0)
19 | export(INT)
20 | export(INT.1)
21 | export(INT.1.NEG)
22 | export(INT.1.NEG.STR)
23 | export(INT.1.POS)
24 | export(INT.1.POS.STR)
25 | export(INT.NEG)
26 | export(INT.NEG.STR)
27 | export(INT.POS)
28 | export(INT.POS.STR)
29 | export(LGL)
30 | export(LGL.1)
31 | export(LT.0)
32 | export(LTE.0)
33 | export(NO.INF)
34 | export(NO.NA)
35 | export(NUM)
36 | export(NUM.1)
37 | export(NUM.1.NEG)
38 | export(NUM.1.POS)
39 | export(NUM.NEG)
40 | export(NUM.POS)
41 | export(abstract)
42 | export(alike)
43 | export(all_bw)
44 | export(bench_mark)
45 | export(nullify)
46 | export(tev)
47 | export(type_alike)
48 | export(type_of)
49 | export(vet)
50 | export(vet_token)
51 | export(vetr)
52 | export(vetr_settings)
53 | importFrom(methods,new)
54 | importFrom(stats,median)
55 | importFrom(utils,modifyList)
56 | useDynLib(vetr, .registration=TRUE, .fixes="VALC_")
57 |
--------------------------------------------------------------------------------
/RELEASE.Rmd:
--------------------------------------------------------------------------------
1 | ## Release Steps
2 |
3 | [x] Coverage 100%
4 |
5 | Make sure to check codecov as the algorithms are different (and better?) there.
6 |
7 | [x] R CMD check
8 | [x] Devel / Release / Oldrel (Github Actions)
9 | [ ] winbuilder
10 | [x] Other locale (e.g. 8859-15, not "latin1")
11 |
12 | LC_ALL=ca_FR.ISO8859-15 LANG=ca_FR.ISO8859-15 R CMD check
13 |
14 | [x] Revdep
15 |
16 |
17 | Review the details of revdep: it claims everything is fine, but there are
18 | several failures (could be b/c of 10 minute timeout?). Also, it took ~10 hours
19 | to do the revdeps for 26 packages; what is going on with that? Possibly not
20 | enough RAM causing us to hit swap? Need more RAM for docker?
21 |
22 | Revdepcheck relies on crancache, which hasn't been updated in a while.
23 |
24 | [ ] CRAN policy
25 |
26 | [x] Review documentation
27 | [x] Update DESCRIPTION / NEWS
28 | [x] New README
29 | [x] Re-render vignette
30 |
31 | Compiled code issues (do these last as inevitably we end up making changes).
32 |
33 | [x] Rchk (rhub)
34 | [x] valgrind
35 | [x] Clang UBSAN (Rhub)
36 | [x] Clang ASAN (Rhub) (catches things GCCSAN does not).
37 |
38 | path <- 'vetr_0.2.19.tar.gz'
39 | rhub::rc_submit(
40 | path=path,
41 | platforms=c('clang-asan', 'clang-ubsan', 'valgrind', 'rchk')
42 | )
43 |
44 | Optional
45 |
46 | [ ] New animation
47 |
48 |
49 |
--------------------------------------------------------------------------------
/man/bench_mark.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/benchmark.R
3 | \name{bench_mark}
4 | \alias{bench_mark}
5 | \title{Lightweight Benchmarking Function}
6 | \usage{
7 | bench_mark(..., times = 1000L, deparse.width = 40)
8 | }
9 | \arguments{
10 | \item{...}{expressions to benchmark, are captured unevaluated}
11 |
12 | \item{times}{how many times to loop, defaults to 1000}
13 |
14 | \item{deparse.width}{how many characters to deparse for labels}
15 | }
16 | \value{
17 | NULL, invisibly, reports timings as a side effect as screen output
18 | }
19 | \description{
20 | Evaluates provided expression in a loop and reports mean evaluation time.
21 | This is inferior to \code{microbenchmark} and other benchmarking tools in many
22 | ways except that it has zero dependencies or suggests which helps with
23 | package build and test times. Used in vignettes.
24 | }
25 | \details{
26 | Runs \code{\link[=gc]{gc()}} before each expression is evaluated. Expressions are evaluated
27 | in the order provided. Attempts to estimate the overhead of the loop by
28 | running a loop that evaluates \code{NULL} the \code{times} times.
29 |
30 | Unfortunately because this computes the average of all iterations it is very
31 | susceptible to outliers in small sample runs, particularly with fast running
32 | code. For that reason the default number of iterations is one thousand.
33 | }
34 | \examples{
35 | bench_mark(runif(1000), Sys.sleep(0.001), times=10)
36 | }
37 |
--------------------------------------------------------------------------------
/man/nullify.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/abstract.R
3 | \name{nullify}
4 | \alias{nullify}
5 | \alias{nullify.default}
6 | \title{Set Element to NULL Without Removing It}
7 | \usage{
8 | nullify(obj, index)
9 |
10 | \method{nullify}{default}(obj, index)
11 | }
12 | \arguments{
13 | \item{obj}{the R object to NULL a value in}
14 |
15 | \item{index}{an indexing vectors of values to NULL}
16 | }
17 | \value{
18 | object with selected values NULLified
19 | }
20 | \description{
21 | This function is required because there is no straightforward way to
22 | over-write a value in a list with NULL without completely removing the entry
23 | from the list as well.
24 | }
25 | \details{
26 | This returns a copy of the object modified with null slots; it does
27 | not modify the input argument.
28 |
29 | Default method will attempt to convert non-list objects to lists
30 | with \code{\link{as.list}}, and then back to whatever they were by using a
31 | function with name \code{paste0("as.", class(obj)[[1L]])}
32 | if it exists and works. If the object cannot be coerced back
33 | to its original type the corresponding list will be returned.
34 |
35 | If this is not appropriate for your object type you can write an S3 method
36 | for it.
37 | }
38 | \note{
39 | attributes are copied from original object and re-applied to final
40 | object before return, which may
41 | not make sense in some circumstances.
42 | }
43 | \examples{
44 | nullify(list(1, 2, 3), 2)
45 | nullify(call("fun", 1, 2, 3), 2)
46 | }
47 |
--------------------------------------------------------------------------------
/src/trackinghash.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include "cstringr.h"
20 | #include "pfhash.h"
21 | #include "settings.h"
22 |
23 | #ifndef _TRACK_HASH_H
24 | #define _TRACK_HASH_H
25 |
26 | /*
27 | * Note: last value written to `contents` is at ->idx - 1, if ->idx is zero,
28 | * then the list is empty
29 | */
30 |
31 | struct track_hash {
32 | pfHashTable * hash;
33 | char ** contents; // an array of characters
34 | size_t idx; // location after last value in contents
35 | size_t idx_max; // how big the contents are
36 | };
37 | struct track_hash * VALC_create_track_hash(size_t size_init);
38 | int VALC_add_to_track_hash(
39 | struct track_hash * track_hash, const char * key, const char * value,
40 | size_t max_nchar
41 | );
42 | void VALC_reset_track_hash(
43 | struct track_hash * track_hash, size_t idx
44 | );
45 | SEXP VALC_track_hash_test(SEXP keys, SEXP size);
46 |
47 | #endif
48 |
--------------------------------------------------------------------------------
/vignettes/rmdhunks/vetting-expressions.Rmd:
--------------------------------------------------------------------------------
1 | You can combine templates with `&&` / `||`:
2 |
3 | ```{r}
4 | vet(numeric(1L) || NULL, NULL)
5 | vet(numeric(1L) || NULL, 42)
6 | vet(numeric(1L) || NULL, "foo")
7 | ```
8 |
9 | Templates only check structure. When you need to check values use `.` to
10 | refer to the object:
11 |
12 | ```{r}
13 | vet(numeric(1L) && . > 0, -42) # strictly positive scalar numeric
14 | vet(numeric(1L) && . > 0, 42)
15 | ```
16 |
17 | If you do use the `.` symbol in your vetting expressions in your packages, you
18 | will need to include `utils::globalVariables(".")` as a top-level call to avoid
19 | the "no visible binding for global variable '.'" R CMD check NOTE.
20 |
21 | You can compose vetting expressions as language objects and combine them:
22 |
23 | ```{r}
24 | scalar.num.pos <- quote(numeric(1L) && . > 0)
25 | foo.or.bar <- quote(character(1L) && . %in% c('foo', 'bar'))
26 | vet.exp <- quote(scalar.num.pos || foo.or.bar)
27 |
28 | vet(vet.exp, 42)
29 | vet(vet.exp, "foo")
30 | vet(vet.exp, "baz")
31 | ```
32 |
33 | `all_bw` is available for value range checks (~10x faster than
34 | `isTRUE(all(. >= x & . <= y))` for large vectors):
35 |
36 | ```{r}
37 | vet(all_bw(., 0, 1), runif(5) + 1)
38 | ```
39 |
40 | There are a number of predefined vetting tokens you can use in your
41 | vetting expressions such as:
42 |
43 | ```{r}
44 | vet(NUM.POS, -runif(5)) # positive numeric; see `?vet_token` for others
45 | ```
46 |
47 | Vetting expressions are designed to be intuitive to use, but their
48 | implementation is complex. We recommend you look at `example(vet)` for usage
49 | ideas, or at the ["Non Standard Evaluation" section of the vignette][3] for the
50 | gory details.
51 |
52 |
--------------------------------------------------------------------------------
/vignettes/rmdhunks/related-packages.Rmd:
--------------------------------------------------------------------------------
1 |
2 | * `stopifnot` by R Core
3 | * [`vetr`](https://github.com/brodieG/vetr) by Yours Truly
4 | * [`asserthat`](https://github.com/hadley/assertthat) by Hadley Wickham
5 | * [`assertive`](https://www.r-pkg.org/pkg/assertive) by Richie Cotton
6 | * [`checkmate`](https://github.com/mllg/checkmate) by Michel Lang
7 |
8 | The following packages also perform related tasks, although we have not used
9 | them and do not review them:
10 |
11 | * [`valaddin`](https://github.com/egnha/valaddin) v0.1.0 by Eugene Ha, a
12 | framework for augmenting existing functions with validation contracts.
13 | Currently the package is undergoing a major overhaul so we will add it to the
14 | comparison once the new release (v0.3.0) is out.
15 | * [`ensurer`](https://github.com/smbache/ensurer) v1.1 by Stefan M. Bache, a
16 | framework for flexibly creating and combining validation contracts. The
17 | development version adds an experimental method for creating type safe
18 | functions, but it is not published to CRAN so we do not test it here.
19 | * [`validate`](https://github.com/data-cleaning/validate) by Mark van
20 | der Loo and Edwin de Jonge, with a primary focus on validating data in data
21 | frames and similar data structures.
22 | * [`assertr`](https://github.com/tonyfischetti/assertr) by Tony Fischetti, also
23 | focused on data validation in data frames and similar structures.
24 | * [`types`](https://github.com/jimhester/types) by Jim Hester, which implements
25 | but does not enforce type hinting.
26 | * [`argufy`](https://github.com/gaborcsardi/argufy) by Gábor Csárdi, which
27 | implements parameter validation via roxygen tags (not released to CRAN).
28 | * [`typed`](https://moodymudskipper.github.io/typed/) by Antoine Fabri, which
29 | enforces types of symbols, function parameters, and return values.
30 | * [`erify`](https://github.com/flujoo/erify/) by Renfei Mao, with a focus on
31 | readable error messages.
32 |
--------------------------------------------------------------------------------
/man/type_alike.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/type.R
3 | \name{type_of}
4 | \alias{type_of}
5 | \alias{type_alike}
6 | \title{Fuzzily Compare Types of Objects}
7 | \usage{
8 | type_of(object)
9 |
10 | type_alike(target, current, settings = NULL)
11 | }
12 | \arguments{
13 | \item{object}{the object to check the type of}
14 |
15 | \item{target}{the object to test type alikeness against}
16 |
17 | \item{current}{the object to test the type alikeness of}
18 |
19 | \item{settings}{NULL, or a list as produced by \code{\link[=vetr_settings]{vetr_settings()}}}
20 | }
21 | \value{
22 | For \code{type_of} character(1L) the type of the object, for \code{type_alike}
23 | either TRUE, or a string describing why the types are not alike.
24 | }
25 | \description{
26 | Type evaluation and comparison is carried out with special treatment for
27 | numerics, integers, and function types. Whole number NA-free numeric vectors
28 | of sufficiently short length (<100 by default) representable in the integer
29 | type are considered to be type integer. Closures, built-ins, and specials
30 | are all treated as type closure.
31 | }
32 | \details{
33 | Specific behavior can be tuned with the \code{type.mode} parameter to the
34 | \code{\link[=vetr_settings]{vetr_settings()}} object passed as the \code{settings} parameter to this function.
35 | }
36 | \examples{
37 | type_of(1.0001) # numeric
38 | type_of(1.0) # integer (`typeof` returns numeric)
39 | type_of(1) # integer (`typeof` returns numeric)
40 | type_of(sum) # closure (`typeof` returns builtin)
41 | type_of(`$`) # closure (`typeof` returns special)
42 |
43 | type_alike(1L, 1)
44 | type_alike(1L, 1.1)
45 | type_alike(integer(), numeric(100))
46 | type_alike(integer(), numeric(101)) # too long
47 | }
48 | \seealso{
49 | \code{\link[=alike]{alike()}}, \code{\link[=vetr_settings]{vetr_settings()}}, in particular the section about
50 | the \code{type.mode} parameter which affects how this function behaves.
51 | }
52 |
--------------------------------------------------------------------------------
/src/settings.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include
20 |
21 | #ifndef _VETR_SET_H
22 | #define _VETR_SET_H
23 |
24 | // QUESTION, WHAT TYPE SHOULD ALL THE NUMBERS HERE BE, LONG? THAT WOULD SEEM
25 | // TO MAKE SENSE
26 |
27 | struct VALC_settings {
28 | // Original alike settings
29 |
30 | int type_mode, attr_mode, lang_mode, fun_mode, rec_mode;
31 |
32 | // Length of numeric vectors to consider for integer-likeness
33 |
34 | int fuzzy_int_max_len;
35 |
36 | int suppress_warnings;
37 |
38 | // internal, track whether we are recursing through attributes
39 |
40 | int in_attr;
41 |
42 | int width; // Tell alike what screen width to assume
43 |
44 | // what env to look for functions to match call in, substitute, etc, used
45 | // both by alike and by vet funs
46 |
47 | SEXP env;
48 |
49 | // Additional vet settings
50 |
51 | size_t env_depth_max; // how many envs to track when searching for env loop
52 | size_t nchar_max; // when do we stop looking for NULL?
53 | size_t symb_sub_depth_max; // how deep recursive substitution can go?
54 | size_t symb_size_max;
55 | size_t track_hash_content_size;
56 |
57 | int result_list_size_init;
58 | int result_list_size_max;
59 | };
60 | struct VALC_settings VALC_settings_init(void);
61 | struct VALC_settings VALC_settings_vet(SEXP set_list, SEXP env);
62 |
63 | #endif
64 |
--------------------------------------------------------------------------------
/tests/unitizer/misc.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | library(vetr)
18 |
19 | unitizer_sect("All", {
20 | vetr:::val_all(1:10) # -2
21 | vetr:::val_all(rep(TRUE, 10)) # 1
22 | vetr:::val_all(c(rep(TRUE, 10), FALSE, TRUE)) # 0
23 | vetr:::val_all(c(rep(TRUE, 5), NA, rep(TRUE, 5))) # -4
24 | vetr:::val_all(FALSE) # -1
25 | vetr:::val_all(TRUE) # 2
26 | vetr:::val_all(logical()) # 3, this used to be -5
27 | vetr:::val_all(NA) # -3
28 | vetr:::val_all(c(TRUE, TRUE, NA, TRUE)) # -4
29 | })
30 |
31 | unitizer_sect("Hash", {
32 | # these should all equal 193
33 | vetr:::hash_fun(c("f b", "n b", "n d", "t m", "b r", "n w", "q w", "o x"))
34 | })
35 | unitizer_sect("bench_mark", {
36 | # three different time frames that should trigger all the code, need to remove
37 | # the time piece so that the tests don't fail due to variations
38 |
39 | capt_wo_time <- function(x) {
40 | txt <- capture.output(x)
41 | gsub("~ *-?[0-9.e+\\-]*", "~", txt)
42 | }
43 | capt_wo_time(bench_mark(Sys.sleep(1.2), times=1))
44 | capt_wo_time(bench_mark(Sys.sleep(.01), times=10))
45 | capt_wo_time(bench_mark(1 + 1, NULL, times=100))
46 | })
47 | unitizer_sect("sort pair lists", {
48 | vetr:::list_as_sorted_vec(pairlist(c=1, a=list(), b=NULL))
49 | # # equal names not stable, but we should never hit this with attribute lists
50 | # vetr:::list_as_sorted_vec(pairlist(a=1, a=list(), a=NULL))
51 | vetr:::list_as_sorted_vec(pairlist(b=1, 2, a=3))
52 | vetr:::list_as_sorted_vec(pairlist())
53 | vetr:::list_as_sorted_vec(pairlist(a=1))
54 | })
55 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thank you for your interest in contributing to this package. To make sure you
4 | do not waste your time or mine, please read and follow the guidelines here.
5 |
6 | ## Reporting Issues
7 |
8 | Please create a:
9 |
10 | * minimal: as little code as possible.
11 | * reproducible: include the **minimal** input data.
12 | * example: include the expected output.
13 |
14 | Additionally:
15 |
16 | * Include output of `sessionInfo()`
17 | * Format the example code so that it can be copy-pasted into an R console
18 |
19 | ## Submitting PRs
20 |
21 | ### Before you Start
22 |
23 | Create an issue that highlights the problem, and describe how you hope to solve
24 | it. Do not be offended if your offer for help is refused. Accepting a PR
25 | creates a maintenance burden that I might not be willing to take on.
26 |
27 | I realize the requirements I lay out here are annoying. If they prevent you
28 | from making a contribution I am sorry and sympathize, having been on the
29 | opposite side of such requirements myself. Nonetheless the requirements stand
30 | to ensure your contribution does not end up creating more work than it
31 | saves.
32 |
33 | ### Requirements
34 |
35 | * Check the diff prior to submitting the PR and make sure there are no
36 | unnecessary changes (e.g. meaningless white space changes, etc.).
37 | * All PRs should be made as a new branch off of the "development" branch.
38 | * Every line of code you contribute should be tested as shown by `covr`.
39 | * Unit tests should be done in `unitizer`.
40 | * You should license all contributions you make with a license compatible with
41 | that of the package, and you should ensure you are the copyright holder for
42 | all the contributions.
43 |
44 | ### Style Guide
45 |
46 | Strict:
47 |
48 | * Wrap text at 80 columns
49 | * Indentations are 2 spaces
50 | * Strip trailing whitespace
51 |
52 | Suggested:
53 |
54 | * function_name()
55 | * object.name
56 | * FormalClassName
57 | * formalMethodName()
58 |
59 | ## Thank You!
60 |
61 | For taking the time to read these contribution guidelines. I apologize if they
62 | seem a little hostile, but time, yours and mine, is precious and it would be a
63 | shame to waste any of it.
64 |
--------------------------------------------------------------------------------
/R/helper.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | # This file contains interfaces to internal C functions for use with R based
18 | # unit testing frameworks
19 |
20 | ## Expand variable names to experessions if they contain language
21 | ##
22 | ## @keywords internal
23 |
24 | symb_sub <- function(symb, env=parent.frame())
25 | .Call(VALC_symb_sub, symb, env)
26 |
27 |
28 | ## Parse Expressions For \code{`validate`} Use
29 | ##
30 | ## Takes expressions provided to \code{`\link{validate}`} and identifies which
31 | ## ones are templates, which ones are normal expressions, and substitutes the
32 | ## actual argument name for \code{`.`}.
33 | ##
34 | ## Internal function exposed for unit testing purposes
35 | ##
36 | ## @keywords internal
37 | ## @param symb an R symbol
38 | ## @param arg_name another R symbol
39 | ## @param an environment to look for language expressions to substitute
40 | ## @return list
41 |
42 | parse_validator <- function(lang, arg_name, rho=parent.frame())
43 | .Call(VALC_parse, lang, arg_name, rho)
44 |
45 | ## Remove Parens and \code{`.(`} From Calls
46 | ##
47 | ## @keywords internal
48 |
49 | remove_parens <- function(lang)
50 | .Call(VALC_remove_parens, lang)
51 |
52 | ## Evaluates a test
53 | ##
54 | ## For unit testing
55 | ##
56 | ## @keywords internal
57 |
58 | eval_check <- function(lang, arg_name, arg_value, env=parent.frame())
59 | .Call(
60 | VALC_eval_check, lang, arg_name, arg_name, arg_value, sys.call(), env
61 | )
62 |
63 | ## Internal version of `all`
64 | ##
65 | ## @keywords internal
66 |
67 | val_all <- function(x) .Call(VALC_all, x)
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/src/cstringrtest.c:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include "cstringr.h"
20 | /*
21 | * Functions for testing corner cases in cstringr functions. Here because
22 | * size_t may be system dependent so we cannot test the results directly from R
23 | * where we have no mechanism for generating size_t values.
24 | *
25 | * These are all expected to produce errors
26 | */
27 |
28 | SEXP CSR_test_strmcpy(void) {
29 | size_t maxlen = 0;
30 | maxlen--; // size_t of max value
31 | CSR_strmcpy("hello", maxlen);
32 | return R_NilValue; // nocov
33 | }
34 |
35 | SEXP CSR_test_strappend(void) {
36 | size_t maxlen = 0;
37 | maxlen--; // size_t of max value
38 | CSR_strappend("hello", "hello", maxlen);
39 | return R_NilValue; // nocov
40 | }
41 |
42 | SEXP CSR_test_add_szt(void) {
43 | size_t maxlen = 0;
44 | maxlen--; // size_t of max value
45 | CSR_add_szt(maxlen, maxlen);
46 | return R_NilValue; // nocov
47 | }
48 | /*
49 | * Make sure all the variations on CSR_smprintf6 actually work
50 | */
51 | SEXP CSR_test_smprintfx(void) {
52 | return mkString(
53 | CSR_smprintf5(
54 | 10000, "%s\n%s\n%s\n%s\n",
55 | CSR_smprintf4(10000, "%s %s %s %s", "a", "b", "c", "d"),
56 | CSR_smprintf3(10000, "%s %s %s", "a", "b", "c"),
57 | CSR_smprintf2(10000, "%s %s", "a", "b"),
58 | CSR_smprintf1(10000, "%s", "a"),
59 | "the END"
60 | )
61 | );
62 | }
63 | // Make sure warning generated correctly
64 |
65 | SEXP CSR_test_strappend2(void) {
66 | char * str_new = R_alloc(20, sizeof(char));
67 | CSR_strappend(str_new, "hellothere", 5);
68 | return R_NilValue;
69 | }
70 |
--------------------------------------------------------------------------------
/src/pfhash.h:
--------------------------------------------------------------------------------
1 | /*
2 | copyright paxdiablo
3 | lifted directly from: http://powerfield-software.com/?p=615 under license:
4 |
5 | > Brodie, the code I hold copyright for in this article (which is the bulk of
6 | > it) is covered by the “do whatever the heck you want with it” licence, the
7 | > official text of which is:
8 | >
9 | > 1/ You are hereby permitted to do whatever the heck you want with it.
10 | >
11 | > I make no representations about the actual hashing functions themselves,
12 | > defaultFnKnR and defaultFnBJ. If you want to ensure you’re safe in respect to
13 | > those, either consult a lawyer, write your own, or see the addendum to this
14 | > article.
15 | */
16 | /*
17 | Copyright (C) 2023 Brodie Gaslam
18 |
19 | This file is part of "vetr - Trust, but Verify"
20 |
21 | This program is free software: you can redistribute it and/or modify
22 | it under the terms of the GNU General Public License as published by
23 | the Free Software Foundation, either version 2 of the License, or
24 | (at your option) any later version.
25 |
26 | This program is distributed in the hope that it will be useful,
27 | but WITHOUT ANY WARRANTY; without even the implied warranty of
28 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 | GNU General Public License for more details.
30 |
31 | Go to for a copy of the license.
32 | */
33 |
34 |
35 | #include
36 | #include
37 | #include
38 | #include
39 |
40 | #ifndef _PFHASH_H
41 | #define _PFHASH_H
42 |
43 |
44 | typedef struct sPfHashNode {
45 | const char *key;
46 | const char *data;
47 | struct sPfHashNode *next;
48 | } pfHashNode;
49 |
50 | typedef struct {
51 | uint32_t (*fn) (const char *);
52 | pfHashNode *lookup[];
53 | } pfHashTable;
54 |
55 | pfHashTable *pfHashCreate (uint32_t(*)(const char*));
56 | // void pfHashDestroy (pfHashTable*);
57 | int pfHashSet (pfHashTable*,const char*,const char*);
58 | int pfHashDel (pfHashTable*,const char*);
59 | const char *pfHashFind (pfHashTable*,const char*);
60 | void pfHashDebug (pfHashTable*,char*);
61 | SEXP pfHashTest(SEXP keys, SEXP values);
62 | SEXP pfHashTest2(SEXP keys, SEXP add);
63 | SEXP VALC_default_hash_fun(SEXP keys);
64 |
65 | #endif
66 |
--------------------------------------------------------------------------------
/tests/unitizer/notcran.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | # # Can't keep up with changes in ggplot. Leaving this here for legacy
18 | # # purposes.
19 | # library(vetr)
20 | #
21 | # # this file should be excluded via .Rbuildignore
22 | #
23 | # unitizer_sect("ggplot", {
24 | # # Rather experimental; we store the ggplot objects to avoid the suggests
25 | # df1 <- data.frame(x=runif(20), y=runif(20))
26 | # df2 <- data.frame(x=runif(20), y=runif(20), z=rep(c("a", "b"), 10))
27 | # df3 <- data.frame(a=runif(30), b=runif(30))
28 | #
29 | # if(
30 | # suppressWarnings(
31 | # suppressPackageStartupMessages(
32 | # require(ggplot2, quietly=TRUE)
33 | # ) ) ) {
34 | # # one day this will break, but can't figure out right now how to get the
35 | # # deprecation warnings consistently enough to figure out what to do about
36 | # # them
37 | #
38 | # old.opt <- options(lifecycle_verbose_soft_deprecation=FALSE)
39 | # on.exit(options(old.opt))
40 | # g1 <- ggplot(df1) + geom_point(aes(x=x, y=y))
41 | # g2 <- ggplot(df1) + geom_line(aes(x=x, y=y))
42 | # g3 <- ggplot(df3) + geom_point(aes(x=a, y=b))
43 | # g4 <- ggplot(df1, aes(x=x, y=y)) + geom_point() + geom_line()
44 | #
45 | # g.abs <- abstract(g1)
46 | #
47 | # list(alike(g.abs, g1), alike(g.abs, g2), alike(g.abs, g3))
48 | # } else {
49 | # # this is what the result should be so that this works when we skip the
50 | # # tests for lack of ggplot2
51 | #
52 | # list(
53 | # TRUE,
54 | # "`class(g2$layers[[1]]$geom)[2]` should be \"GeomPoint\" (is \"GeomPath\")",
55 | # TRUE
56 | # )
57 | # }
58 | # })
59 | #
60 |
--------------------------------------------------------------------------------
/src/backports.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 |
18 | This file contains code copied from the R's Writing R Extensions manual.
19 | Original copyright notices follow.
20 | */
21 | /*
22 | * R : A Computer Language for Statistical Data Analysis
23 | * Copyright (C) 1999--2025 The R Core Team.
24 | * Copyright (C) 1995, 1996 Robert Gentleman and Ross Ihaka
25 | *
26 | * This program is free software; you can redistribute it and/or modify
27 | * it under the terms of the GNU General Public License as published by
28 | * the Free Software Foundation; either version 2 of the License, or
29 | * (at your option) any later version.
30 | *
31 | * This program is distributed in the hope that it will be useful,
32 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
33 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 | * GNU General Public License for more details.
35 | *
36 | * You should have received a copy of the GNU General Public License
37 | * along with this program; if not, a copy is available at
38 | * https://www.R-project.org/Licenses/
39 | */
40 |
41 | /*
42 | * Include after R header files
43 | *
44 | * Code necessitated by the API restrictions implemented in R4.6+
45 | */
46 |
47 | #include
48 | #if R_VERSION < R_Version(4, 4, 1)
49 | #define allocLang Rf_allocLang
50 |
51 | static SEXP Rf_allocLang(int n)
52 | {
53 | if (n > 0)
54 | return LCONS(R_NilValue, Rf_allocList(n - 1));
55 | else
56 | return R_NilValue;
57 | }
58 | #endif
59 |
60 | #if R_VERSION < R_Version(4, 5, 0)
61 | # define R_ClosureFormals(x) FORMALS(x)
62 | # define R_ParentEnv(x) ENCLOS(x)
63 |
64 | #endif
65 |
--------------------------------------------------------------------------------
/.github/workflows/R-CMD-check.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - rc
5 | pull_request:
6 | branches:
7 | - master
8 | - development
9 |
10 | name: R-CMD-check
11 |
12 | env:
13 | cache-version: v2
14 |
15 | jobs:
16 | # Always run release
17 | R-CMD-check-release:
18 | runs-on: ubuntu-latest
19 | name: ubuntu-latest (release)
20 |
21 | env:
22 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
23 | _R_CHECK_FORCE_SUGGESTS_: false
24 |
25 | steps:
26 | - uses: actions/checkout@v4
27 | - uses: r-lib/actions/setup-pandoc@v2
28 | - uses: r-lib/actions/setup-r@v2
29 | with:
30 | r-version: 'release'
31 | use-public-rspm: true
32 | - uses: r-lib/actions/setup-r-dependencies@v2
33 | with:
34 | extra-packages: any::rcmdcheck, any::covr
35 | needs: check
36 | - name: Check
37 | uses: r-lib/actions/check-r-package@v2
38 | with:
39 | args: 'c("--no-manual", "--as-cran")'
40 |
41 | - name: Test coverage
42 | if: github.event_name == 'push' && github.ref == 'refs/heads/rc'
43 | env:
44 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
45 | run: |
46 | options(covr.exclude_end="(?://|#)[[:space:]]*nocov[[:space:]]*end")
47 | options(covr.exclude_start="(?://|#)[[:space:]]*nocov[[:space:]]*start")
48 | options(covr.exclude_pattern="(?://|#)[[:space:]]*nocov")
49 | covr::codecov(token = Sys.getenv("CODECOV_TOKEN"))
50 | shell: Rscript {0}
51 |
52 | # Only run on push to rc
53 | R-CMD-check-extended:
54 | if: github.event_name == 'push' && github.ref == 'refs/heads/rc'
55 | runs-on: ubuntu-latest
56 | name: ubuntu-latest (${{ matrix.r }})
57 |
58 | strategy:
59 | fail-fast: false
60 | matrix:
61 | r: ['devel', 'oldrel-1']
62 |
63 | env:
64 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true
65 | _R_CHECK_FORCE_SUGGESTS_: false
66 |
67 | steps:
68 | - uses: actions/checkout@v4
69 | - uses: r-lib/actions/setup-pandoc@v2
70 | - uses: r-lib/actions/setup-r@v2
71 | with:
72 | r-version: ${{ matrix.r }}
73 | use-public-rspm: true
74 | - uses: r-lib/actions/setup-r-dependencies@v2
75 | with:
76 | extra-packages: any::rcmdcheck
77 | needs: check
78 | - name: Check
79 | uses: r-lib/actions/check-r-package@v2
80 | with:
81 | args: 'c("--no-manual", "--as-cran")'
82 |
--------------------------------------------------------------------------------
/tests/unitizer/helper/UTF-8-critical.R:
--------------------------------------------------------------------------------
1 | # In own file due to Solaris issues preventing direct inclusion in unitizer
2 | #
3 | # Well-Formed UTF-8 Byte Sequences
4 | #
5 | # Code Points | Byte 1 | Byte 2 | Byte 3 | Byte 4
6 | # U+0000..U+007F | 00..7F |
7 | # U+0080..U+07FF | C2..DF | 80..BF
8 | # U+0800..U+0FFF | E0 | A0..BF | 80..BF
9 | # U+1000..U+CFFF | E1..EC | 80..BF | 80..BF
10 | # U+D000..U+D7FF | ED | 80..9F | 80..BF
11 | # U+E000..U+FFFF | EE..EF | 80..BF | 80..BF
12 | # U+10000..U+3FFFF | F0 | 90..BF | 80..BF | 80..BF
13 | # U+40000..U+FFFFF | F1..F3 | 80..BF | 80..BF | 80..BF
14 | # U+100000..U+10FFFF | F4 | 80..8F | 80..BF | 80..BF
15 |
16 | # Check all the critical cases where we transition from legal to illegal
17 | # sequences
18 |
19 | crit.1 <- c(
20 | n.0="\x7F",
21 | y.1="\x80"
22 | )
23 | Encoding(crit.1) <- "UTF-8"
24 | crit.2 <- c(
25 | n.0="\xC1\x91",
26 | n.1="\xC2\x79",
27 | y.2="\xC2\x80",
28 | y.3="\xDF\xBF",
29 | n.4="\xDF\xC0"
30 | )
31 | Encoding(crit.2) <- "UTF-8"
32 | crit.3 <- c(
33 | n.00="\xE0\x9F\x91",
34 | n.01="\xE0\xA0\x79",
35 | y.02="\xE0\xA0\x80",
36 | y.03="\xE0\xBF\xBF",
37 | n.04="\xE0\xBF\xC0",
38 | n.05="\xE1\x79\x91",
39 | y.06="\xE1\x80\x80",
40 | y.07="\xEC\xBF\xBF",
41 | n.08="\xEC\xBF\xC0",
42 | n.10="\xEC\xC0\xBF",
43 | n.11="\xED\x79\x80",
44 | n.12="\xED\x80\x79",
45 | y.13="\xED\x80\x80",
46 | y.14="\xED\x9F\xBF",
47 | n.16="\xED\x9F\xC0",
48 | n.17="\xED\xA0\xBF",
49 | n.18="\xEE\x80\x79",
50 | n.19="\xEE\x79\x80",
51 | y.20="\xEE\x80\x80",
52 | y.21="\xEF\xBF\xBF",
53 | n.22="\xEF\xBF\xC0",
54 | n.23="\xEF\xC0\xBF"
55 | )
56 | Encoding(crit.3) <- "UTF-8"
57 | crit.4 <- c(
58 | n.00="\xF0\x89\x80\x80",
59 | n.01="\xF0\x90\x79\x80",
60 | n.02="\xF0\x90\x80\x79",
61 | y.03="\xF0\x90\x80\x80",
62 | y.04="\xF0\xBF\xBF\xBF",
63 | n.06="\xF0\xBF\xBF\xC0",
64 | n.07="\xF0\xBF\xC0\xBF",
65 | n.08="\xF0\xC0\xBF\xBF",
66 | n.09="\xF1\x80\x80\x79",
67 | n.10="\xF1\x80\x79\x80",
68 | n.11="\xF1\x79\x80\x80",
69 | y.12="\xF1\x80\x80\x80",
70 | y.13="\xF3\xBF\xBF\xBF",
71 | n.14="\xF3\xBF\xBF\xC0",
72 | n.15="\xF3\xBF\xC0\xBF",
73 | n.16="\xF3\xC0\xBF\xBF",
74 | n.17="\xF4\x80\x80\x79",
75 | n.18="\xF4\x80\x79\x80",
76 | n.19="\xF4\x79\x80\x80",
77 | y.20="\xF4\x80\x80\x80",
78 | y.21="\xF4\x8F\xBF\xBF",
79 | n.22="\xF4\x8F\xBF\xC0",
80 | n.23="\xF4\x8F\xC0\xBF",
81 | n.24="\xF4\x90\x01\x01",
82 | n.25="\xF5\x81\x81\x81"
83 | )
84 | Encoding(crit.4) <- "UTF-8"
85 |
--------------------------------------------------------------------------------
/R/type.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | #' @export
18 | #' @rdname type_alike
19 |
20 | type_of <- function(object)
21 | .Call(VALC_typeof, object)
22 |
23 | #' Fuzzily Compare Types of Objects
24 | #'
25 | #' Type evaluation and comparison is carried out with special treatment for
26 | #' numerics, integers, and function types. Whole number NA-free numeric vectors
27 | #' of sufficiently short length (<100 by default) representable in the integer
28 | #' type are considered to be type integer. Closures, built-ins, and specials
29 | #' are all treated as type closure.
30 | #'
31 | #' Specific behavior can be tuned with the `type.mode` parameter to the
32 | #' [vetr_settings()] object passed as the `settings` parameter to this function.
33 | #'
34 | #' @seealso [alike()], [vetr_settings()], in particular the section about
35 | #' the `type.mode` parameter which affects how this function behaves.
36 | #' @param target the object to test type alikeness against
37 | #' @param current the object to test the type alikeness of
38 | #' @param settings NULL, or a list as produced by [vetr_settings()]
39 | #' @param object the object to check the type of
40 | #' @return For `type_of` character(1L) the type of the object, for `type_alike`
41 | #' either TRUE, or a string describing why the types are not alike.
42 | #' @export
43 | #' @examples
44 | #' type_of(1.0001) # numeric
45 | #' type_of(1.0) # integer (`typeof` returns numeric)
46 | #' type_of(1) # integer (`typeof` returns numeric)
47 | #' type_of(sum) # closure (`typeof` returns builtin)
48 | #' type_of(`$`) # closure (`typeof` returns special)
49 | #'
50 | #' type_alike(1L, 1)
51 | #' type_alike(1L, 1.1)
52 | #' type_alike(integer(), numeric(100))
53 | #' type_alike(integer(), numeric(101)) # too long
54 |
55 | type_alike <- function(target, current, settings=NULL)
56 | .Call(VALC_type_alike, target, current, substitute(current), settings)
57 |
--------------------------------------------------------------------------------
/vignettes/rmdhunks/declarative-checks.Rmd:
--------------------------------------------------------------------------------
1 | ## Declarative Checks with Templates
2 |
3 | ### Templates
4 |
5 | Declare a template that an object should conform to, and let `vetr` take care of
6 | the rest:
7 |
8 | ```{r}
9 | library(vetr)
10 | tpl <- numeric(1L)
11 | vet(tpl, 1:3)
12 | vet(tpl, "hello")
13 | vet(tpl, 42)
14 | ```
15 |
16 | The template concept is based on `vapply`, but generalizes to all S3 objects and
17 | adds some special features to facilitate comparison. For example, zero length
18 | templates match any length:
19 |
20 | ```{r}
21 | tpl <- integer()
22 | vet(tpl, 1L:3L)
23 | vet(tpl, 1L)
24 | ```
25 |
26 | And for convenience short (<= 100 length) integer-like numerics are considered
27 | integer:
28 |
29 | ```{r}
30 | tpl <- integer(1L)
31 | vet(tpl, 1) # this is a numeric, not an integer
32 | vet(tpl, 1.0001)
33 | ```
34 |
35 | `vetr` can compare recursive objects such as lists, or data.frames:
36 |
37 | ```{r}
38 | tpl.iris <- iris[0, ] # 0 row DF matches any number of rows in object
39 | iris.fake <- iris
40 | levels(iris.fake$Species)[3] <- "sibirica" # tweak levels
41 |
42 | vet(tpl.iris, iris)
43 | vet(tpl.iris, iris.fake)
44 | ```
45 |
46 | From our declared template `iris[0, ]`, `vetr` infers all the required checks.
47 | In this case, `vet(iris[0, ], iris.fake, stop=TRUE)` is equivalent to:
48 |
49 | ```{r}
50 | stopifnot_iris <- function(x) {
51 | stopifnot(
52 | is.data.frame(x),
53 | is.list(x),
54 | length(x) == length(iris),
55 | identical(lapply(x, class), lapply(iris, class)),
56 | is.integer(attr(x, 'row.names')),
57 | identical(names(x), names(iris)),
58 | identical(typeof(x$Species), "integer"),
59 | identical(levels(x$Species), levels(iris$Species))
60 | )
61 | }
62 | stopifnot_iris(iris.fake)
63 | ```
64 |
65 | `vetr` saved us typing, and the time and thought needed to come up with what
66 | needs to be compared.
67 |
68 | You could just as easily have created templates for nested lists, or data frames
69 | in lists. Templates are compared to objects with the `alike` function. For a
70 | thorough description of templates and how they work see the [`alike`
71 | vignette][2]. For template examples see `example(alike)`.
72 |
73 | ### Auto-Generated Error Messages
74 |
75 | Let's revisit the error message:
76 |
77 | ```{r}
78 | vet(tpl.iris, iris.fake)
79 | ```
80 |
81 | It tells us:
82 |
83 | * The reason for the failure
84 | * What structure would be acceptable instead
85 | * The location of failure `levels(iris.fake$Species)[3]`
86 |
87 | `vetr` does what it can to reduce the time from error to resolution. The
88 | location of failure is generated such that you can easily copy it in part or
89 | full to the R prompt for further examination.
90 |
--------------------------------------------------------------------------------
/R/all-bw.R:
--------------------------------------------------------------------------------
1 | #' Verify Values in Vector are Between Two Others
2 | #'
3 | #' Similar to \code{isTRUE(all(x >= lo & x <= hi))} with default settings,
4 | #' except that it is substantially faster and returns a string describing the
5 | #' first encountered violation rather than FALSE on failure.
6 | #'
7 | #' You can modify the comparison to be strictly greater/less than via the
8 | #' `bounds` parameter, and the treatment of NAs with `na.rm`. Note that NAs are
9 | #' considered to be out of bounds by default. While technically incorrect
10 | #' since we cannot know whether an NA value is in or out of bounds, this
11 | #' assumption is both conservative and convenient. Zero length `x` will always
12 | #' succeed.
13 | #'
14 | #' If `x` and `lo`/`hi` are different types, `lo`/`hi` will be coerced to the
15 | #' type of `x`. When `lo`/`hi` are numeric and `x` is integer, if `lo`/`hi`
16 | #' values are outside of the integer range then that side will be treated as if
17 | #' you had used `-Inf`/`Inf`. `-Inf` and `Inf` mean `lo` and `hi` will be
18 | #' unbounded for all data types.
19 | #'
20 | #' @export
21 | #' @param x vector logical (treated as integer), integer, numeric, or character.
22 | #' Factors are treated as their underlying integer vectors.
23 | #' @param lo scalar vector of type coercible to the type of `x`, cannot be NA,
24 | #' use `-Inf` to indicate unbounded (default).
25 | #' @param hi scalar vector of type coercible to the type of `x`, cannot be NA,
26 | #' use `Inf` to indicate unbounded (default), must be greater than or equal to
27 | #' `lo`.
28 | #' @param na.rm TRUE, or FALSE (default), whether NAs are considered to be
29 | #' in bounds. Unlike with [all()], for `all_bw` `na.rm=FALSE` returns an
30 | #' error string if there are NAs instead of NA. Arguably NA, but not NaN,
31 | #' should be considered to be in `[-Inf,Inf]`, but since `NA < Inf` is NA we
32 | #' treat them as always being out of bounds.
33 | #' @param bounds `character(1L)` for values between `lo` and `hi`:
34 | #' * \dQuote{[]} include `lo` and `hi`
35 | #' * \dQuote{()} exclude `lo` and `hi`
36 | #' * \dQuote{(]} exclude `lo`, include `hi`
37 | #' * \dQuote{[)} include `lo`, exclude `hi`
38 | #'
39 | #' @return TRUE if all values in `x` conform to the specified bounds, a string
40 | #' describing the first position that fails otherwise
41 | #' @examples
42 | #' all_bw(runif(100), 0, 1)
43 | #' all_bw(runif(100) * 2, 0, 1)
44 | #' all_bw(NA, 0, 1) # This is does not return NA
45 | #' all_bw(NA, 0, 1, na.rm=TRUE)
46 | #'
47 | #' vec <- c(runif(100, 0, 1e12), Inf, 0)
48 | #' all_bw(vec, 0) # All +ve numbers
49 | #' all_bw(vec, hi=0) # All -ve numbers
50 | #' all_bw(vec, 0, bounds="(]") # All strictly +ve nums
51 | #' all_bw(vec, 0, bounds="[)") # All finite +ve nums
52 |
53 | all_bw <- function(x, lo=-Inf, hi=Inf, na.rm=FALSE, bounds="[]")
54 | .Call(VALC_all_bw, x, lo, hi, na.rm, bounds)
55 |
56 |
57 |
--------------------------------------------------------------------------------
/tests/unitizer/type.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | library(vetr)
18 |
19 | # Redefine names; necessary because we renamed functions when moving to C
20 | # versions
21 |
22 | unitizer_sect("type_of", {
23 | type_of(1:100)
24 | type_of(1.1)
25 | type_of(1:100 + 1.0)
26 | type_of(1:100 + 1/1e9)
27 | type_of(NA_real_)
28 | type_of(Inf)
29 | type_of(-Inf)
30 | } )
31 | unitizer_sect("type_alike", {
32 | type_alike(1, 1.1) # TRUE, 1 is numeric
33 | type_alike(1L, 1.1) # FALSE
34 | type_alike(1L, 1.00000001) # FALSE
35 | type_alike(1L, 1.0) # TRUE
36 |
37 | type_alike(1, 1.1, vetr_settings(type.mode=1)) # TRUE, 1 is numeric
38 | type_alike(1L, 1.0, vetr_settings(type.mode=1)) # FALSE
39 | type_alike(1.0, 1L, vetr_settings(type.mode=1)) # TRUE
40 | type_alike(1.0, 1L, vetr_settings(type.mode=2)) # FALSE, must be num-num
41 |
42 | type_alike(1:100, 1:100 + 0.0) # TRUE
43 | type_alike(1:101, 1:101 + 0.0) # FALSE
44 | type_alike(1:101, 1:101 + 0.0, vetr_settings(fuzzy.int.max.len=200)) # TRUE
45 |
46 | type_alike(numeric(), c(1.1, 0.053, 41.8)) # TRUE
47 | type_alike(numeric(), list(1.1)) # FALSE
48 | type_alike(list(), integer()) # FALSE
49 | type_alike(1000000L, 1000000L + .1) # FALSE
50 | type_alike(1000000L, 1000000L + .0) # TRUE
51 | type_alike(data.frame(a=1:10), list()) # TRUE
52 | type_alike(NULL, NULL)
53 | type_alike(1/0, NA)
54 |
55 | # errors
56 |
57 | type_alike(1, 1.1, vetr_settings(type.mode=1:2))
58 | type_alike(1, 1.1, vetr_settings(fuzzy.int.max.len=1:2))
59 | } )
60 | unitizer_sect("functions", {
61 | type_alike(sd, var) # clo-clo
62 | type_alike(`&&`, sd) # spe-clo
63 | type_alike(`&&`, sum) # spe-blt
64 | type_alike(sum, sd) # blt-clo
65 | type_alike(sum, c) # blt-blt
66 | type_alike(`&&`, `[`) # spe-spe
67 |
68 | type_alike(sd, 1:3)
69 |
70 | type_alike(sd, var, vetr_settings(type.mode=1)) # clo-clo
71 | type_alike(`&&`, sd, vetr_settings(type.mode=1)) # spe-clo
72 | type_alike(`&&`, sum, vetr_settings(type.mode=1)) # spe-blt
73 | type_alike(sum, sd, vetr_settings(type.mode=1)) # blt-clo
74 | type_alike(sum, c, vetr_settings(type.mode=1)) # blt-blt
75 | type_alike(`&&`, `[`, vetr_settings(type.mode=1)) # spe-spe
76 | } )
77 |
--------------------------------------------------------------------------------
/man/all_bw.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/all-bw.R
3 | \name{all_bw}
4 | \alias{all_bw}
5 | \title{Verify Values in Vector are Between Two Others}
6 | \usage{
7 | all_bw(x, lo = -Inf, hi = Inf, na.rm = FALSE, bounds = "[]")
8 | }
9 | \arguments{
10 | \item{x}{vector logical (treated as integer), integer, numeric, or character.
11 | Factors are treated as their underlying integer vectors.}
12 |
13 | \item{lo}{scalar vector of type coercible to the type of \code{x}, cannot be NA,
14 | use \code{-Inf} to indicate unbounded (default).}
15 |
16 | \item{hi}{scalar vector of type coercible to the type of \code{x}, cannot be NA,
17 | use \code{Inf} to indicate unbounded (default), must be greater than or equal to
18 | \code{lo}.}
19 |
20 | \item{na.rm}{TRUE, or FALSE (default), whether NAs are considered to be
21 | in bounds. Unlike with \code{\link[=all]{all()}}, for \code{all_bw} \code{na.rm=FALSE} returns an
22 | error string if there are NAs instead of NA. Arguably NA, but not NaN,
23 | should be considered to be in \verb{[-Inf,Inf]}, but since \code{NA < Inf} is NA we
24 | treat them as always being out of bounds.}
25 |
26 | \item{bounds}{\code{character(1L)} for values between \code{lo} and \code{hi}:
27 | \itemize{
28 | \item \dQuote{[]} include \code{lo} and \code{hi}
29 | \item \dQuote{()} exclude \code{lo} and \code{hi}
30 | \item \dQuote{(]} exclude \code{lo}, include \code{hi}
31 | \item \dQuote{[)} include \code{lo}, exclude \code{hi}
32 | }}
33 | }
34 | \value{
35 | TRUE if all values in \code{x} conform to the specified bounds, a string
36 | describing the first position that fails otherwise
37 | }
38 | \description{
39 | Similar to \code{isTRUE(all(x >= lo & x <= hi))} with default settings,
40 | except that it is substantially faster and returns a string describing the
41 | first encountered violation rather than FALSE on failure.
42 | }
43 | \details{
44 | You can modify the comparison to be strictly greater/less than via the
45 | \code{bounds} parameter, and the treatment of NAs with \code{na.rm}. Note that NAs are
46 | considered to be out of bounds by default. While technically incorrect
47 | since we cannot know whether an NA value is in or out of bounds, this
48 | assumption is both conservative and convenient. Zero length \code{x} will always
49 | succeed.
50 |
51 | If \code{x} and \code{lo}/\code{hi} are different types, \code{lo}/\code{hi} will be coerced to the
52 | type of \code{x}. When \code{lo}/\code{hi} are numeric and \code{x} is integer, if \code{lo}/\code{hi}
53 | values are outside of the integer range then that side will be treated as if
54 | you had used \code{-Inf}/\code{Inf}. \code{-Inf} and \code{Inf} mean \code{lo} and \code{hi} will be
55 | unbounded for all data types.
56 | }
57 | \examples{
58 | all_bw(runif(100), 0, 1)
59 | all_bw(runif(100) * 2, 0, 1)
60 | all_bw(NA, 0, 1) # This is does not return NA
61 | all_bw(NA, 0, 1, na.rm=TRUE)
62 |
63 | vec <- c(runif(100, 0, 1e12), Inf, 0)
64 | all_bw(vec, 0) # All +ve numbers
65 | all_bw(vec, hi=0) # All -ve numbers
66 | all_bw(vec, 0, bounds="(]") # All strictly +ve nums
67 | all_bw(vec, 0, bounds="[)") # All finite +ve nums
68 | }
69 |
--------------------------------------------------------------------------------
/man/abstract.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/abstract.R
3 | \name{abstract}
4 | \alias{abstract}
5 | \alias{abstract.data.frame}
6 | \alias{abstract.default}
7 | \alias{abstract.array}
8 | \alias{abstract.matrix}
9 | \alias{abstract.list}
10 | \alias{abstract.lm}
11 | \alias{abstract.environment}
12 | \alias{abstract.ts}
13 | \title{Turn S3 Objects Into Templates}
14 | \usage{
15 | abstract(x, ...)
16 |
17 | \method{abstract}{data.frame}(x, ...)
18 |
19 | \method{abstract}{default}(x, ...)
20 |
21 | \method{abstract}{array}(x, ...)
22 |
23 | \method{abstract}{matrix}(x, ...)
24 |
25 | \method{abstract}{list}(x, ...)
26 |
27 | \method{abstract}{lm}(x, ...)
28 |
29 | \method{abstract}{environment}(x, ...)
30 |
31 | \method{abstract}{ts}(x, what = c("start", "end", "frequency"), ...)
32 | }
33 | \arguments{
34 | \item{x}{the object to abstract}
35 |
36 | \item{...}{arguments for methods that require further arguments}
37 |
38 | \item{what, }{for time series which portion of the \code{ts} attribute to
39 | abstract, by default all three are abstracted, but you can select, any one,
40 | two, or all}
41 | }
42 | \value{
43 | abstracted object
44 | }
45 | \description{
46 | Create templates for use by \code{\link{alike}}. Currently somewhat
47 | experimental; behavior may change in future.
48 | }
49 | \details{
50 | \code{abstract} is intended to create templates for use by
51 | \code{\link{alike}}. The result of abstraction is often a partially
52 | specified object. This type of object may not be suited for use in typical
53 | R computations and may cause errors (or worse) if you try to use them as
54 | normal R objects.
55 |
56 | There is no guarantee that the \code{abstract}ed object is suitable for use
57 | as a template to \code{alike} as is. You may need to modify it further so
58 | that it suits your purposes.
59 |
60 | \code{abstract} is an S3 generic. The default method will
61 | dispatch on implicit classes, so if you attempt to \code{abstract} an object
62 | without an explicit \code{abstract} method, it will get abstracted based on
63 | its implicit class. If you define your own \code{abstract} method and do not
64 | wish further abstraction based on implicit classes do not use
65 | \code{\link{NextMethod}}.
66 |
67 | S4 and RC objects are returned unchanged.
68 | }
69 | \section{Time Series}{
70 |
71 |
72 | \code{\link{alike}} will treat time series parameter components with zero in
73 | them as wildcards. This function allows you to create these wild card time
74 | series attributes since R does not allow direct creation/modification of
75 | \code{ts} attributes with zero values.
76 |
77 | Make sure you do not try to use the templates you create with this for
78 | anything other than as \code{\link{alike}} templates since the result is
79 | likely undefined given R expects non zero values for the \code{ts}
80 | attribute and attempts to prevent such attributes.
81 | }
82 |
83 | \examples{
84 | iris.tpl <- abstract(iris)
85 | alike(iris.tpl, iris[1:10, ])
86 | alike(iris.tpl, transform(iris, Species=as.character(Species)))
87 |
88 | abstract(1:10)
89 | abstract(matrix(1:9, nrow=3))
90 | abstract(list(1:9, runif(10)))
91 | }
92 |
--------------------------------------------------------------------------------
/tests/unitizer/abstract.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | library(vetr)
18 |
19 | unitizer_sect("Standard Methods", {
20 | abstract(1:10)
21 | abstract(list(a=1:10, b=runif(10)))
22 | abstract(matrix(1:9, nrow=3))
23 | abstract(
24 | array(1:8, c(2, 2, 2), dimnames=list(letters[1:2], LETTERS[1:2], NULL))
25 | )
26 | # non atomic
27 | list.arr <- replicate(8, list(1), simplify=FALSE)
28 | dim(list.arr) <- rep(2, 3)
29 | abstract(list.arr)
30 | abstract(list(1, NULL))
31 |
32 | # df
33 | alike(abstract(iris), iris[1:10, ])
34 | alike(abstract(iris), iris[1:10, 1:3])
35 | alike(abstract(iris), transform(iris, Species=as.character(Species)))
36 |
37 | my.env <- new.env()
38 | identical(my.env, abstract(my.env))
39 | })
40 | unitizer_sect("Time Series", {
41 | y <- ts(runif(12), start=1970, frequency=12)
42 | attr(abstract(y), "tsp")
43 | attr(abstract(y, "start"), "tsp")
44 | attr(abstract(y, "end"), "tsp")
45 | attr(abstract(y, "frequency"), "tsp")
46 | attr(abstract(y, c("start", "frequency")), "tsp")
47 |
48 | # Errors
49 |
50 | abstract(y, "boom")
51 | vetr:::abstract.ts(1:12)
52 | })
53 | unitizer_sect("s4", {
54 | ## s4 objects are unaffected
55 | obj <- new("unitizerGlobalState")
56 | abstract(obj)
57 | nullify(obj, 1)
58 | })
59 | unitizer_sect("lm", {
60 | set.seed(1)
61 | df1 <- data.frame(x = runif(10), y=runif(10), z=runif(10))
62 | df2 <- data.frame(a = runif(5), b=runif(5), c=runif(5))
63 | mdl <- lm(y ~ x + poly(z, 2), df1)
64 |
65 | alike(abstract(mdl), mdl)
66 |
67 | mdl2 <- lm(x ~ y + poly(z, 2), df1)
68 |
69 | alike(abstract(mdl), mdl2)
70 |
71 | mdl3 <- lm(a ~ b + log(c), df2)
72 |
73 | alike(abstract(mdl), mdl3)
74 |
75 | mdl4 <- lm(a ~ b, df2)
76 |
77 | alike(abstract(mdl), mdl4)
78 | })
79 | unitizer_sect("nullify", {
80 | nullify(list(1, 2, 3), 2)
81 | nullify(list(1, 2, 3), -2)
82 | nullify(list(1, 2, 3, 4), c(TRUE, FALSE))
83 | nullify(list(1, 2, 3, 4), c(TRUE, FALSE, FALSE))
84 | nullify(list(1, 2, 3, 4), c(rep(FALSE, 4), TRUE))
85 | nullify(list(a=1, b=2, 3, 4), c("a", "b"))
86 | nullify(list(1, 2, 3, 4), "hello")
87 |
88 | nullify(list(1, 2, 3), 4)
89 |
90 | ## can't print the data frame because the concomitant warning changes in
91 | ## r75024 (see issue#96), and limitations of unitizer (unitizer:issue249)
92 |
93 | iris.null <- nullify(iris[1:10, ], 4)
94 | as.list(iris.null)
95 | class(iris.null)
96 |
97 | nullify(letters, 5)
98 |
99 | nullify(structure(letters[1:2], class='xqwer892jahaksdf'), 2)
100 | })
101 |
--------------------------------------------------------------------------------
/tests/unitizer/classes.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | library(vetr)
18 |
19 | unitizer_sect("Class Matching", {
20 | obj2 <- structure(numeric())
21 | obj1 <- structure(numeric(), class="hello")
22 | alike(obj1, obj2)
23 | obj2 <- structure(numeric(), class=c(letters[10:12], letters[1:3], letters[8:9]))
24 | obj1 <- structure(numeric(), class=letters[1:3])
25 | alike(obj1, obj2)
26 | alike(obj2, obj1)
27 | obj2 <- structure(numeric(), class=c("b", "a", "c"))
28 | alike(obj1, obj2)
29 | obj2 <- structure(numeric(), class=c("a", "b", "x", "c"))
30 | alike(obj1, obj2)
31 | obj2 <- structure(numeric(), class=c("a", "b", "c"))
32 | alike(obj1, obj2) # TRUE
33 | obj2 <- structure(numeric(), class=c("x", "a", "b", "c"))
34 | alike(obj1, obj2) # TRUE
35 | alike(obj1, obj2, settings=vetr_settings(attr.mode=1)) # FALSE
36 | } )
37 | unitizer_sect("S4", {
38 | # We used to define classes here, but under unitizer by virtue of vetr being
39 | # the first package on the search path, that is where the class definitions
40 | # ended up due to logic in topenv(). That seemed fragile so we switched to
41 | # defining in package.
42 |
43 | x <- new("vetr_foo")
44 | y <- new("vetr_foo")
45 | z <- new("vetr_bar")
46 | v <- new("vetr_baz")
47 | w <- structure(list(a=character(), b=numeric()), class="vetr_foo")
48 |
49 | alike(x, y) # TRUE
50 | alike(x, z) # FALSE
51 | alike(x, w) # FALSE
52 | alike(w, x) # FALSE
53 | alike(x, v) # TRUE, because v contains x
54 | alike(v, x) # FALSE
55 |
56 | # S4 nested in list
57 |
58 | lst.2 <- list(list(11, 21), list(31, list(41, list(51, list(61)))))
59 | lst.5 <- lst.6 <- lst.2
60 | lst.5[[2]][[2]][[1]] <- x
61 | lst.6[[2]][[2]][[1]] <- v
62 |
63 | alike(lst.5, lst.6) # TRUE
64 | alike(lst.6, lst.5) # FALSE, child class is target, so parent can't match
65 |
66 | # Borked S4
67 | v2 <- v
68 | class(v2) <- c("vetr_baz", "vetr_foo")
69 | alike(x, v2)
70 |
71 | # Stress test installation of `inherits`; right now the inherits command is
72 | # evaluated in the base environment, which seems to work since the arguments
73 | # are already evaluated, but it suggests `inherits` can look up S4 definitions
74 | # irrespective of where they are defined...
75 |
76 | inherits <- function(x, y) stop("pwned!!!")
77 | alike(y, v) # TRUE
78 | } )
79 | unitizer_sect("R5", {
80 | Foo.1 <- vetr:::Foo$new()
81 | Foo.2 <- vetr:::Foo$new()
82 | Bar.1 <- vetr:::Bar$new()
83 |
84 | alike(Foo.1, Foo.2)
85 | alike(Foo.1, Bar.1)
86 |
87 | } )
88 | unitizer_sect("Non-Standard Class", {
89 | # Basically ensure that stuff still recurses even if they are lists/calls
90 | # but have another class
91 |
92 | var.1 <- list(1, 2, 3)
93 | var.2 <- list("hello", list(1, 2, 3), 5)
94 | class(var.1) <- "marbles"
95 | class(var.2) <- "marbles"
96 | # "mis-match at index [[1]]: should be integer instead of character"
97 | alike(var.1, var.2)
98 | } )
99 |
--------------------------------------------------------------------------------
/tests/unitizer/parse.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | library(vetr)
18 |
19 | unitizer_sect("remove parens", {
20 | vetr:::remove_parens(quote((a)))
21 | vetr:::remove_parens(quote(.(a)))
22 | vetr:::remove_parens(quote((((a)))))
23 | vetr:::remove_parens(quote((.((.(a))))))
24 | vetr:::remove_parens(quote((a) && .(a))) # Nothing should be removed
25 | })
26 | unitizer_sect("parse", {
27 | x <- quote(.(.) && ((a)))
28 | vetr:::parse_validator(x, quote(arg_to_validate))
29 | x # make sure unchanged from previous assignment
30 |
31 | vetr:::parse_validator(quote(FALSE), quote(arg_to_validate))
32 | vetr:::parse_validator(quote(((FALSE))), quote(arg_to_validate))
33 | vetr:::parse_validator(quote(((FALSE && ((TRUE))))), quote(arg_to_validate))
34 | vetr:::parse_validator(quote(.(FALSE)), quote(arg_to_validate))
35 | vetr:::parse_validator(quote(.), quote(arg_to_validate))
36 | vetr:::parse_validator(quote(. && a), quote(arg_to_validate))
37 | vetr:::parse_validator(quote(.(.)), quote(arg_to_validate))
38 | vetr:::parse_validator(quote(((a && b) || .(.))), quote(arg_to_validate))
39 | vetr:::parse_validator(quote(matrix(nrow=3)), quote(arg_to_validate))
40 | vetr:::parse_validator(quote(matrix(nrow=3) && .(.)), quote(arg_to_validate))
41 | vetr:::parse_validator(quote((a || ((b && c))) && .(a + .)), quote(arg_to_validate))
42 | vetr:::parse_validator(quote((a || ((b && .(c)))) && (a + .(.))), quote(arg_to_validate))
43 |
44 | vetr:::parse_validator(quote(a && (b + .(c))), quote(arg_to_validate)) # uninterpretable?
45 | vetr:::parse_validator(quote(a && .), "hello") # uninterpretable?
46 | } )
47 | unitizer_sect("token sub", {
48 | vetr:::symb_sub(INT.1)
49 | vetr:::symb_sub(NO.NA)
50 |
51 | # Dot unescaping
52 | `..` <- quote(yes)
53 | `.zzz` <- `zzz.` <- quote(yup)
54 | `.` <- quote(...)
55 | vetr:::symb_sub(quote(..))
56 | vetr:::symb_sub(quote(...))
57 | vetr:::symb_sub(quote(.zzz))
58 | vetr:::symb_sub(quote(zzz.))
59 |
60 | # Errors
61 | `.` <- quote(..)
62 | vetr:::symb_sub(quote(..))
63 | vetr:::symb_sub(quote(.))
64 |
65 | # Identity operations on non-symbols
66 | vetr:::symb_sub(quote(.(zzz)))
67 | vetr:::symb_sub("hello")
68 | })
69 |
70 | unitizer_sect("preset tokens", {
71 | x <- quote(integer(1L))
72 | y <- quote(integer(1L) || NULL)
73 | z <- quote(integer(1L) && .(!any(is.na(.))))
74 | vetr:::parse_validator(quote(x), quote(w))
75 | vetr:::parse_validator(quote(y), quote(w))
76 | vetr:::parse_validator(quote(z), quote(w))
77 | vetr:::parse_validator(quote(z || NULL), quote(w))
78 | } )
79 | unitizer_sect("validators", {
80 | vetr:::parse_validator(INT.1, quote(w))
81 | vetr:::parse_validator(INT, quote(w))
82 | vetr:::parse_validator(CHR.1, quote(w))
83 | vetr:::parse_validator(CHR, quote(w))
84 | vetr:::parse_validator(NUM.1, quote(w))
85 | vetr:::parse_validator(NUM, quote(w))
86 | vetr:::parse_validator(LGL.1, quote(w))
87 | vetr:::parse_validator(LGL, quote(w))
88 | vetr:::parse_validator(CPX.1, quote(w))
89 | vetr:::parse_validator(CPX, quote(w))
90 | } )
91 |
--------------------------------------------------------------------------------
/man/vetr.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/validate.R
3 | \name{vetr}
4 | \alias{vetr}
5 | \title{Verify Function Arguments Meet Structural Requirements}
6 | \usage{
7 | vetr(..., .VETR_SETTINGS = NULL)
8 | }
9 | \arguments{
10 | \item{...}{vetting expressions, each will be matched to the enclosing
11 | function formals as with \code{\link[=match.call]{match.call()}} and will be used to validate the
12 | value of the matching formal.}
13 |
14 | \item{.VETR_SETTINGS}{a settings list as produced by \code{\link[=vetr_settings]{vetr_settings()}}, or
15 | NULL to use the default settings. Note that this means you cannot use
16 | \code{vetr} with a function that takes a \code{.VETR_SETTINGS} argument}
17 | }
18 | \value{
19 | TRUE if validation succeeds, otherwise \code{stop} with error message
20 | detailing nature of failure.
21 | }
22 | \description{
23 | Use vetting expressions to enforce structural requirements for function
24 | arguments. Works just like \code{\link[=vet]{vet()}}, except that the formals of the
25 | enclosing function automatically matched to the vetting expressions provided
26 | in \code{...}.
27 | }
28 | \details{
29 | Only named arguments may be vetted; in other words it is not possible to vet
30 | arguments passed via \code{...}.
31 | }
32 | \note{
33 | \code{vetr} will force evaluation of any arguments that are being
34 | checked (you may omit arguments that should not be evaluate from
35 | \code{vetr})
36 | }
37 | \section{Vetting Expressions}{
38 |
39 |
40 | Vetting expressions can be template tokens, standard tokens, or any
41 | expression built with them, \code{||}, \code{&&}, and parentheses. Template tokens
42 | are R objects that define the required structure, much like the \code{FUN.VALUE}
43 | argument to \code{\link[=vapply]{vapply()}}. Standard tokens are R expressions evaluated and
44 | checked for being \code{all(TRUE)}.
45 |
46 | Standard tokens are distinguished from templates by whether they reference
47 | the \code{.} symbol or not. If you have a need to reference an object bound to
48 | \code{.} in a vetting expression, you can escape the \code{.} with an extra dot (i.e.
49 | use \code{..}, and \code{...} for \code{..}, and so forth for symbols comprising only
50 | dots). If you use standard tokens in your packages you will need to include
51 | \code{utils::globalVariables(".")} as a top-level call to avoid the "no visible
52 | binding for global variable '.'"' R CMD check NOTE. Standard tokens that
53 | return a string like e.g. \code{all.equal(x, .)} will result in that string being
54 | incorporated into the error message.
55 |
56 | See \code{vignette('vetr', package='vetr')} and examples for details on how
57 | to craft vetting expressions.
58 | }
59 |
60 | \examples{
61 | ## Look at `?vet` examples for more details on how to craft
62 | ## vetting expressions.
63 |
64 | fun1 <- function(x, y) {
65 | vetr(integer(), LGL.1)
66 | TRUE # do some work
67 | }
68 | fun1(1:10, TRUE)
69 | try(fun1(1:10, 1:10))
70 |
71 | ## only vet the second argument
72 | fun2 <- function(x, y) {
73 | vetr(y=LGL.1)
74 | TRUE # do some work
75 | }
76 | try(fun2(letters, 1:10))
77 |
78 | ## Nested templates; note, in packages you should consider
79 | ## defining templates outside of `vet` or `vetr` so that
80 | ## they are computed on load rather that at runtime
81 | tpl <- list(numeric(1L), matrix(integer(), 3))
82 | val.1 <- list(runif(1), rbind(1:10, 1:10, 1:10))
83 | val.2 <- list(runif(1), cbind(1:10, 1:10, 1:10))
84 | fun3 <- function(x, y) {
85 | vetr(x=tpl, y=tpl && ncol(.[[2]]) == ncol(x[[2]]))
86 | TRUE # do some work
87 | }
88 | fun3(val.1, val.1)
89 | try(fun3(val.1, val.2))
90 | val.1.a <- val.1
91 | val.1.a[[2]] <- val.1.a[[2]][, 1:8]
92 | try(fun3(val.1, val.1.a))
93 | }
94 | \seealso{
95 | \code{\link[=vet]{vet()}}, in particular \code{example(vet)}.
96 | }
97 |
--------------------------------------------------------------------------------
/src/misc.c:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include "validate.h"
20 |
21 | /* testing funs
22 | SEXP VALC_test1(SEXP a) {
23 | return ScalarReal(REAL(a)[0] + REAL(a)[0] + REAL(a)[0]);
24 | }
25 | SEXP VALC_test2(SEXP a, SEXP b) {
26 | return ScalarReal(REAL(a)[0] + REAL(b)[0] + REAL(a)[0]);
27 | }
28 | SEXP VALC_test3(SEXP a, SEXP b, SEXP c) {
29 | return ScalarReal(REAL(a)[0] + REAL(b)[0] + REAL(b)[0]);
30 | }
31 | */
32 |
33 | // - Helper Functions ----------------------------------------------------------
34 |
35 | /*
36 | Fake `stop`
37 |
38 | Main benefit is that it allows us to control the call that gets displayed.
39 | */
40 | void VALC_stop(SEXP call, const char * msg) {
41 | SEXP quot_call = PROTECT(Rf_lang2(VALC_SYM_quote, call));
42 | SEXP msg_string = PROTECT(ScalarString(mkChar(msg)));
43 | SEXP cond_call = PROTECT(
44 | Rf_lang3(install("simpleError"), msg_string, quot_call)
45 | );
46 | SEXP cond = PROTECT(eval(cond_call, R_GlobalEnv));
47 | SEXP err_call = PROTECT(Rf_lang2(install("stop"), cond));
48 | UNPROTECT(5);
49 | eval(err_call, R_GlobalEnv);
50 | // nocov start
51 | error("Internal Error: 3423; contact maintainer.");
52 | } // nocov end
53 |
54 | /*
55 | Create simple error for a tag
56 | */
57 | void VALC_arg_error(SEXP tag, SEXP fun_call, const char * err_base) {
58 | if(TYPEOF(tag) != SYMSXP) {
59 | // nocov start
60 | error(
61 | "Internal Error:"
62 | "non symbol arg names are not currently supported; "
63 | "contact maintainer."
64 | );
65 | // nocov end
66 | }
67 | const char * err_tag = CHAR(PRINTNAME(tag));
68 | char * err_msg = CSR_smprintf1(10001, err_base, err_tag);
69 | VALC_stop(fun_call, err_msg);
70 | // nocov start
71 | error("Internal Error: shouldn't get here 181; contact maintainer.");// nocov
72 | } // nocov end
73 | /*
74 | return
75 | * 3 if zero length, and hence true in the way all(logical()) is TRUE
76 | * 2 if isTRUE,
77 | * 1 if every element is TRUE,
78 | * 0 if there is at least one FALSE,
79 | * -1 if identical to FALSE,
80 | * -2 if not logical,
81 | * -3 if a single NA,
82 | * -4 if contains NAs,
83 | * -6 if the result is a string and has at least one element
84 | */
85 |
86 | int VALC_all(SEXP vec) {
87 | if(TYPEOF(vec) == STRSXP && xlength(vec)) return -6;
88 | if(TYPEOF(vec) != LGLSXP) return -2;
89 | int * vec_c = LOGICAL(vec);
90 | R_xlen_t i, i_end = XLENGTH(vec);
91 |
92 | if(!i_end) return 3;
93 | for(i = 0; i < i_end; i++) {
94 | if(vec_c[i] == NA_INTEGER)
95 | return i_end == 1 ? -3 : -4;
96 | if(vec_c[i] != 1) return i_end == 1 ? -1 : 0;
97 | }
98 | if(i_end == 1) return 2;
99 | return 1;
100 | }
101 | /*
102 | ext interface for testing
103 | */
104 | SEXP VALC_all_ext(SEXP vec) {
105 | return ScalarInteger(VALC_all(vec));
106 | }
107 |
108 | /*
109 | print current PROTECT stack height; used for debugging
110 | */
111 | // nocov start
112 | void psh(const char * lab) {
113 | PROTECT_INDEX i;
114 | PROTECT_WITH_INDEX(R_NilValue, &i);
115 | UNPROTECT(1);
116 | Rprintf("Protect Stack %s: %d\n", lab, i);
117 | }
118 | // nocov end
119 |
--------------------------------------------------------------------------------
/src/cstringr.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 |
24 | #ifndef _CSTRINGR_H
25 | #define _CSTRINGR_H
26 |
27 | // Constants
28 |
29 | #define CSR_MAX_CHAR 50000
30 |
31 | // Testing Functions
32 |
33 | SEXP CSR_len_chr_len_ext(SEXP a);
34 | SEXP CSR_len_as_chr_ext(SEXP a);
35 | SEXP CSR_strmlen_ext(SEXP str, SEXP maxlen);
36 | SEXP CSR_strmcpy_ext(SEXP str, SEXP maxlen);
37 | SEXP CSR_smprintf2_ext(SEXP maxlen, SEXP format, SEXP a, SEXP b);
38 | SEXP CSR_smprintf6_ext(
39 | SEXP maxlen, SEXP format, SEXP a, SEXP b, SEXP c, SEXP d, SEXP e, SEXP f
40 | );
41 | SEXP CSR_ucfirst_ext(SEXP str, SEXP maxlen);
42 | SEXP CSR_lcfirst_ext(SEXP str, SEXP maxlen);
43 | SEXP CSR_bullet_ext(SEXP str, SEXP bullet, SEXP ctd, SEXP maxlen);
44 | SEXP CSR_collapse_ext(SEXP str, SEXP sep, SEXP maxlen);
45 |
46 | SEXP CSR_strsub(SEXP string, SEXP chars, SEXP mark_trunc);
47 | SEXP CSR_nchar_u(SEXP string);
48 | SEXP CSR_char_offsets(SEXP string);
49 |
50 | SEXP CSR_test_strmcpy(void);
51 | SEXP CSR_test_strappend(void);
52 | SEXP CSR_test_strappend2(void);
53 | SEXP CSR_test_add_szt(void);
54 | SEXP CSR_test_smprintfx(void);
55 |
56 | // Internal Functions
57 |
58 | size_t CSR_len_chr_len(R_xlen_t a);
59 | char * CSR_len_as_chr(R_xlen_t a);
60 | char * CSR_num_as_chr(double a, int as_int);
61 | SEXP CSR_num_as_chr_ext(SEXP a, SEXP as_int);
62 | size_t CSR_strmlen_x(const char * str, size_t maxlen);
63 | size_t CSR_strmlen(const char * str, size_t maxlen);
64 | char * CSR_strmcpy(const char * str, size_t maxlen);
65 | char * CSR_strmcpy_int(const char * str, size_t maxlen, int warn);
66 | char * CSR_smprintf6(
67 | size_t maxlen, const char * format, const char * a, const char * b,
68 | const char * c, const char * d, const char * e, const char * f
69 | );
70 | char * CSR_smprintf5(
71 | size_t maxlen, const char * format, const char * a, const char * b,
72 | const char * c, const char * d, const char * e
73 | );
74 | char * CSR_smprintf4(
75 | size_t maxlen, const char * format, const char * a, const char * b,
76 | const char * c, const char * d
77 | );
78 | char * CSR_smprintf3(
79 | size_t maxlen, const char * format, const char * a, const char * b,
80 | const char * c
81 | );
82 | char * CSR_smprintf2(
83 | size_t maxlen, const char * format, const char * a, const char * b
84 | );
85 | char * CSR_smprintf1(size_t maxlen, const char * format, const char * a);
86 | const char * CSR_bullet(SEXP string, SEXP bullet, SEXP ctd, size_t max_len);
87 |
88 | char * CSR_ucfirst(const char * str, size_t maxlen);
89 | char * CSR_lcfirst(const char * str, size_t maxlen);
90 |
91 | char * CSR_collapse(SEXP str, const char *, size_t maxlen);
92 |
93 | void CSR_strappend(char * target, const char * str, size_t maxlen);
94 |
95 | size_t CSR_add_szt(size_t a, size_t b);
96 |
97 | // macros, offset is expected to be a pointer to a character
98 |
99 | #define UTF8_IS_CONT(offset) UTF8_BW(offset, 0x80, 0xBF)
100 | #define UTF8_BW(offset, a, b) ((*(offset) >= (a)) && (*(offset) <= (b)))
101 |
102 | #endif
103 |
--------------------------------------------------------------------------------
/R/benchmark.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | #' Lightweight Benchmarking Function
18 | #'
19 | #' Evaluates provided expression in a loop and reports mean evaluation time.
20 | #' This is inferior to `microbenchmark` and other benchmarking tools in many
21 | #' ways except that it has zero dependencies or suggests which helps with
22 | #' package build and test times. Used in vignettes.
23 | #'
24 | #' Runs [gc()] before each expression is evaluated. Expressions are evaluated
25 | #' in the order provided. Attempts to estimate the overhead of the loop by
26 | #' running a loop that evaluates `NULL` the `times` times.
27 | #'
28 | #' Unfortunately because this computes the average of all iterations it is very
29 | #' susceptible to outliers in small sample runs, particularly with fast running
30 | #' code. For that reason the default number of iterations is one thousand.
31 | #'
32 | #' @importFrom stats median
33 | #' @export
34 | #' @param ... expressions to benchmark, are captured unevaluated
35 | #' @param times how many times to loop, defaults to 1000
36 | #' @param deparse.width how many characters to deparse for labels
37 | #' @return NULL, invisibly, reports timings as a side effect as screen output
38 | #' @examples
39 | #' bench_mark(runif(1000), Sys.sleep(0.001), times=10)
40 |
41 | bench_mark <- function(..., times=1000L, deparse.width=40) {
42 | stopifnot(
43 | is.integer(times) || is.numeric(times), length(times) == 1, times > 0
44 | )
45 | times <- as.integer(times)
46 | dots <- as.list(match.call(expand.dots=FALSE)[["..."]])
47 | p.f <- parent.frame()
48 |
49 | timings <- vapply(
50 | dots, function(x) {
51 | call.q <- bquote({
52 | gc()
53 | start <- proc.time()
54 | for(i in 1:.(times)) .(x)
55 | stop <- proc.time()
56 | stop[['elapsed']] - start[['elapsed']]
57 | })
58 | eval(call.q, p.f)
59 | },
60 | numeric(1L)
61 | )
62 | # try to compute overhead
63 |
64 | o.h.times <- 10
65 | gc()
66 | overhead <- vapply(
67 | seq.int(o.h.times), function(x) {
68 | call.q.baseline <- bquote({
69 | start <- proc.time()
70 | for(j in 1:.(times)) NULL
71 | stop <- proc.time()
72 | stop[['elapsed']] - start[['elapsed']]
73 | })
74 | eval(call.q.baseline, p.f)
75 | },
76 | numeric(1L)
77 | )
78 | overhead.act <- median(overhead)
79 | timings.fin <- (timings - overhead.act) / times
80 | exps <- vapply(
81 | dots,
82 | function(x) dep_oneline(x, max.chars=deparse.width),
83 | character(1L)
84 | )
85 | timings.clean <- timings.fin[timings.fin >= 0]
86 |
87 | unit <- "seconds"
88 | mult <- 0
89 |
90 | if(length(timings.clean)) {
91 | min.time <- min(log(timings.clean, base=10))
92 | if(min.time <= -3) {
93 | unit <- "microseconds"
94 | mult <- 6
95 | } else if(min.time <= 0) {
96 | unit <- "milliseconds"
97 | mult <- 3
98 | }
99 | }
100 | timings.disp <- timings.fin * 10 ^ mult
101 |
102 | cat(
103 | sprintf(
104 | "Mean eval time from %d iteration%s, in %s:\n", times,
105 | if(times > 1) "s" else "", unit
106 | ) )
107 | cat(
108 | paste0(
109 | " ",
110 | format(exps), " ~ ",
111 | format(signif(timings.disp, 4), justify='right'), "\n"
112 | ),
113 | sep=""
114 | )
115 | invisible(data.frame(call=exps, mean.time=timings.fin))
116 | }
117 |
--------------------------------------------------------------------------------
/src/cstringr-ext.c:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include "cstringr.h"
20 |
21 | /*
22 | External interface to internal string functions, mostly for testing, note these
23 | aren't very careful about potential overflows from input so user should ensure
24 | inputs don't overflow int
25 | */
26 |
27 | void is_scalar_pos_int(SEXP obj) {
28 | if(TYPEOF(obj) != INTSXP || XLENGTH(obj) != 1L || asInteger(obj) < 0)
29 | error("Argument `maxlen` must be a positive scalar integer");
30 | }
31 | void is_scalar_chr(SEXP obj) {
32 | if(TYPEOF(obj) != STRSXP || XLENGTH(obj) != 1L)
33 | error("Argument `str` must be a scalar character");
34 | }
35 | SEXP CSR_len_chr_len_ext(SEXP a) {
36 | is_scalar_pos_int(a);
37 | return ScalarInteger(CSR_len_chr_len((R_xlen_t) asInteger(a)));
38 | }
39 | SEXP CSR_len_as_chr_ext(SEXP a) {
40 | is_scalar_pos_int(a);
41 | return mkString(CSR_len_as_chr((R_xlen_t) asInteger(a)));
42 | }
43 | SEXP CSR_strmlen_ext(SEXP str, SEXP maxlen) {
44 | is_scalar_chr(str);
45 | is_scalar_pos_int(maxlen);
46 | return(ScalarInteger(CSR_strmlen(CHAR(asChar(str)), asInteger(maxlen))));
47 | }
48 | SEXP CSR_strmcpy_ext(SEXP str, SEXP maxlen) {
49 | is_scalar_chr(str);
50 | is_scalar_pos_int(maxlen);
51 | return(mkString(CSR_strmcpy(CHAR(asChar(str)), asInteger(maxlen))));
52 | }
53 | SEXP CSR_smprintf2_ext(SEXP maxlen, SEXP format, SEXP a, SEXP b) {
54 | is_scalar_chr(format);
55 | is_scalar_chr(a);
56 | is_scalar_chr(b);
57 | is_scalar_pos_int(maxlen);
58 | char * res = CSR_smprintf2(
59 | asInteger(maxlen), CHAR(asChar(format)), CHAR(asChar(a)), CHAR(asChar(b))
60 | );
61 | return mkString(res);
62 | }
63 | SEXP CSR_smprintf6_ext(
64 | SEXP maxlen, SEXP format, SEXP a, SEXP b, SEXP c, SEXP d, SEXP e, SEXP f
65 | ) {
66 | is_scalar_chr(format);
67 | is_scalar_chr(a);
68 | is_scalar_chr(b);
69 | is_scalar_chr(c);
70 | is_scalar_chr(d);
71 | is_scalar_chr(e);
72 | is_scalar_chr(f);
73 | is_scalar_pos_int(maxlen);
74 | char * res = CSR_smprintf6(
75 | asInteger(maxlen), CHAR(asChar(format)), CHAR(asChar(a)), CHAR(asChar(b)),
76 | CHAR(asChar(c)), CHAR(asChar(d)), CHAR(asChar(e)), CHAR(asChar(f))
77 | );
78 | return mkString(res);
79 | }
80 | SEXP CSR_ucfirst_ext(SEXP str, SEXP maxlen) {
81 | is_scalar_chr(str);
82 | is_scalar_pos_int(maxlen);
83 | return(mkString(CSR_ucfirst(CHAR(asChar(str)), asInteger(maxlen))));
84 | }
85 | SEXP CSR_lcfirst_ext(SEXP str, SEXP maxlen) {
86 | is_scalar_chr(str);
87 | is_scalar_pos_int(maxlen);
88 | return(mkString(CSR_lcfirst(CHAR(asChar(str)), asInteger(maxlen))));
89 | }
90 | SEXP CSR_bullet_ext(SEXP str, SEXP bullet, SEXP ctd, SEXP maxlen) {
91 | if(TYPEOF(str) != STRSXP || TYPEOF(bullet) != STRSXP || TYPEOF(ctd) != STRSXP)
92 | error("First three arguments must be string");
93 | if(TYPEOF(maxlen) != INTSXP) error("Argument `maxlen` must be integer");
94 | if(XLENGTH(bullet) != 1) error("Argument `bullet` must be length 1");
95 | if(XLENGTH(ctd) != 1) error("Argument `ctd` must be length 1");
96 |
97 | R_xlen_t i, str_len = XLENGTH(str);
98 | SEXP res = PROTECT(allocVector(STRSXP, str_len));
99 |
100 | size_t st_ml = INTEGER(maxlen)[0];
101 |
102 | if(str_len) {
103 | for(i = 0; i < str_len; ++i) {
104 | const char * char_new = CSR_bullet(
105 | STRING_ELT(str, i),
106 | STRING_ELT(bullet, 0),
107 | STRING_ELT(ctd, 0),
108 | st_ml
109 | );
110 | SET_STRING_ELT(res, i, mkChar(char_new));
111 | } }
112 | UNPROTECT(1);
113 | return res;
114 | }
115 |
116 |
--------------------------------------------------------------------------------
/tests/unitizer/eval.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | # Runs the tests without attempting to fully compose the result message
18 |
19 | library(vetr)
20 |
21 | unitizer_sect("evaluate", {
22 | vetr:::eval_check(quote(logical(2L)), quote(xyz), 1:2)
23 | vetr:::eval_check(quote(logical(2L)), quote(xyz), c(TRUE, FALSE))
24 | vetr:::eval_check(quote(logical(2L)), quote(xyz), c(TRUE, FALSE, TRUE))
25 |
26 | vetr:::eval_check(quote(logical(2L) || NULL), quote(xyz), 1:2)
27 | vetr:::eval_check(quote(logical(2L) || NULL), quote(xyz), NULL)
28 | vetr:::eval_check(quote(logical(2L) || NULL), quote(xyz), c(TRUE, TRUE))
29 |
30 | vetr:::eval_check(
31 | quote(matrix(integer(), nrow=3) || NULL), quote(xyz), matrix(1:21, ncol=7)
32 | )
33 | vetr:::eval_check(quote(matrix(integer(), nrow=3) || NULL), quote(xyz), 1:21)
34 | vetr:::eval_check(
35 | quote(matrix(integer(), nrow=3) || vector("list", 2L)),
36 | quote(xyz), list("hello")
37 | )
38 | vetr:::eval_check(
39 | quote(matrix(integer(), nrow=3) || vector("list", 2L)),
40 | quote(xyz), list("hello", "goodbye")
41 | )
42 | vetr:::eval_check(
43 | quote(matrix(integer(), nrow=3) || list(character(1L), 1L)),
44 | quote(xyz), list("hello", "goodbye"))
45 | })
46 | unitizer_sect("evaluate with sub", {
47 | xyz <- c(TRUE, TRUE)
48 | vetr:::eval_check(quote(logical(2L) && .(all(xyz))), quote(xyz), xyz)
49 | vetr:::eval_check(quote(logical(2L) && .(all(.))), quote(xyz), xyz)
50 | vetr:::eval_check(quote(logical(2L) && .(!any(is.na(.)))), quote(xyz), xyz)
51 | xyz <- c(TRUE, NA)
52 | vetr:::eval_check(quote(logical(2L) && .(!any(is.na(.)))), quote(xyz), xyz)
53 | xyz <- c(TRUE, FALSE, TRUE)
54 | vetr:::eval_check(quote(logical(2L) && .(!any(is.na(.)))), quote(xyz), xyz)
55 |
56 | abc1 <- letters[1:5]
57 | vetr:::eval_check(
58 | quote(character(5L) && .(all(. %in% letters[1:3]))), quote(abc1), abc1
59 | )
60 | abc2 <- rep("a", 5)
61 | vetr:::eval_check(
62 | quote(character(5L) && .(all(. %in% letters[1:3]))), quote(abc2), abc2
63 | )
64 |
65 | mat1 <- matrix(1:30, ncol=3)
66 | vetr:::eval_check(
67 | quote(
68 | (
69 | matrix(numeric(), ncol=3) || matrix(integer(), nrow=10) ||
70 | character(10L)
71 | ) && .(length(.) < 100)
72 | ),
73 | quote(mat1), mat1
74 | )
75 | mat2 <- matrix(1:120, ncol=3)
76 | vetr:::eval_check(
77 | quote(
78 | (
79 | matrix(numeric(), ncol=3) || matrix(integer(), nrow=10) ||
80 | character(10L)
81 | ) &&
82 | .(length(.) < 100)
83 | ),
84 | quote(mat2), mat2
85 | )
86 | mat3 <- LETTERS[1:9]
87 | vetr:::eval_check( # Fail all
88 | quote(
89 | (
90 | matrix(numeric(), ncol=3) || matrix(integer(), nrow=10) ||
91 | character(10L)
92 | ) && .(length(.) < 100)
93 | ),
94 | quote(mat3), mat3
95 | )
96 | vetr:::eval_check( # Fail all
97 | quote(
98 | matrix(numeric(), ncol=3) || matrix(integer(), nrow=10) ||
99 | character(10L) || .(length(.) > 20)
100 | ),
101 | quote(mat3), mat3
102 | )
103 | })
104 | unitizer_sect("custom expressions", {
105 | x <- -1:1
106 | y <- 1
107 | z <- -1
108 | w <- NA_integer_
109 | u <- integer()
110 | t <- 1:3
111 | vetr:::eval_check(quote(. > 0), quote(x), x)
112 | vetr:::eval_check(quote(. > 0), quote(y), y)
113 | vetr:::eval_check(quote(. > 0), quote(z), z)
114 | vetr:::eval_check(quote(. > 0), quote(t), t)
115 | vetr:::eval_check(quote(. > 0), quote(w), w)
116 | vetr:::eval_check(quote(. > 0), quote(u), u)
117 | })
118 | unitizer_sect("Errors", {
119 | vetr:::eval_check(1:3, 1:3, TRUE, env=list(1:3))
120 | vetr:::eval_check(quote(y), quote(x), TRUE, env=list(1:3))
121 | })
122 |
--------------------------------------------------------------------------------
/src/assumptions.c:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 |
24 | /*
25 | * Check all the assumptions we're making
26 | *
27 | * Intended to be run onload to make sure there isn't some weird system where
28 | * our baseline assumptions are not met
29 | *
30 | * returns TRUE on success, errors on failure
31 | */
32 | // nocov start by definition none of the errors should be thrown, so no sense in
33 | // covering this
34 | SEXP VALC_check_assumptions(void) {
35 | const char * err_base = "Failed system assumption: %s%s";
36 | if(sizeof(R_len_t) < sizeof(int))
37 | warningcall(R_NilValue, err_base, "R_len_t is not gte to int", "");
38 |
39 | // Otherwise bit twiddling assumptions may not work as expected?
40 |
41 | if(CHAR_BIT != 8)
42 | warningcall(R_NilValue, err_base, "CHAR_BIT is not 8", "");
43 |
44 | // This is supposedly enforced by R
45 |
46 | if(sizeof(int) < 4)
47 | warningcall(R_NilValue, err_base, "ints are not at least 32 bits", "");
48 |
49 | // If this is not TRUE, there could be alignment issues for some of our
50 | // structs that use size_t elements given that R_alloc only guarantees double
51 | // alignment.
52 | //
53 | // This will likely cause problems on systems other than 32 and 64 bits,
54 | // particularly those with larger register sizes, probably the easiest
55 | // solution is to not use size_t in the structs if this becomes a problem
56 |
57 | if(sizeof(size_t) > sizeof(double))
58 | warningcall(
59 | R_NilValue, err_base, "size_t larger than double not same size", ""
60 | );
61 |
62 | // Important for some our boundary condition assumptions, in particular that
63 | // NA_INTEGER < int x.
64 |
65 | if(INT_MIN != NA_INTEGER) {
66 | warningcall(
67 | R_NilValue, err_base, "INT_MIN != NA_INTEGER but the code in this ",
68 | "package assumes that they are equal; please contact maintainer."
69 | );
70 | }
71 | #ifndef IEEE_754
72 | warningcall(
73 | R_NilValue, err_base, "This package assumes IEEE-754 real implementation ",
74 | "but that does not appear to be the case; please contact maintainer."
75 | );
76 | #else
77 | // If these checks fail they would be UB anyway because promotion rules are to
78 | // convert the integer to double (6.3.1.8), which would be UB.
79 | // if(INT_MIN < -DBL_MAX) {
80 | // warningcall(
81 | // R_NilValue, err_base, "INT_MIN < -DBL_MAX but the code in this ",
82 | // "package assumes the opposite; please contact maintainer."
83 | // );
84 | // }
85 | // if(INT_MAX > DBL_MAX) {
86 | // warningcall(
87 | // R_NilValue, err_base, "INT_MAX > DBL_MAX but the code in this ",
88 | // "package assumes the opposite; please contact maintainer."
89 | // );
90 | // }
91 | #endif
92 |
93 | // We would like to check because we try to represent R_xlen_t values with %.0f,
94 | // but for the same reason as above this would be UB.
95 | // if(R_XLEN_T_MAX >= DBL_MAX)
96 | // warningcall(R_NilValue, err_base, "R_XLEN_T_MAX is not less than DBL_MAX", "");
97 |
98 | if(sizeof(R_len_t) != sizeof(int))
99 | warningcall(R_NilValue, err_base, "R_len_t not same size as int", "");
100 |
101 | // Because we check that strings are no longer than this, but then allocate
102 | // memory as INT_MAX + 1 with a size_t, so need to make sure that fits
103 |
104 | if(SIZE_MAX - 1 < INT_MAX)
105 | warningcall(
106 | R_NilValue, err_base,
107 | "SIZE_MAX not sufficiently larger than INT_MAX", ""
108 | );
109 | // Because sometimes we use size_t to hold positive R_LEN_T_MAX values
110 |
111 | if(SIZE_MAX <= R_LEN_T_MAX)
112 | warningcall(
113 | R_NilValue, err_base,
114 | "SIZE_MAX smaller than or equal to R_LEN_T_MAX", ""
115 | );
116 | return ScalarLogical(1);
117 | }
118 | // nocov end
119 |
--------------------------------------------------------------------------------
/src/type.c:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include "alike.h"
20 |
21 | /*
22 | compare types, accounting for "integer like" numerics; empty string means
23 | success, otherwise outputs an a character string explaining why the types are
24 | not alike
25 |
26 | call is substituted current, only used when this is called by type_alike directly otherwise doesn't do much
27 | */
28 | struct ALIKEC_res ALIKEC_type_alike_internal(
29 | SEXP target, SEXP current, struct VALC_settings set
30 | ) {
31 | SEXPTYPE tar_type, cur_type, tar_type_raw, cur_type_raw;
32 | int int_like = 0;
33 | tar_type_raw = TYPEOF(target);
34 | cur_type_raw = TYPEOF(current);
35 |
36 | struct ALIKEC_res res = ALIKEC_res_init();
37 |
38 | if(tar_type_raw == cur_type_raw) return res;
39 |
40 | tar_type = tar_type_raw;
41 | cur_type = cur_type_raw;
42 |
43 | if(set.type_mode == 0) {
44 | if(
45 | tar_type_raw == INTSXP && (
46 | set.fuzzy_int_max_len < 0 ||
47 | (
48 | xlength(target) <= set.fuzzy_int_max_len &&
49 | xlength(current) <= set.fuzzy_int_max_len
50 | ) )
51 | ) {
52 | int_like = 1;
53 | }
54 | if(int_like || (
55 | tar_type_raw == CLOSXP || tar_type_raw == SPECIALSXP ||
56 | tar_type_raw == BUILTINSXP
57 | )
58 | ) {
59 | tar_type = ALIKEC_typeof_internal(target);
60 | cur_type = ALIKEC_typeof_internal(current);
61 | }
62 | }
63 | if(tar_type == cur_type) return res;
64 | if(
65 | cur_type == INTSXP && set.type_mode < 2 &&
66 | (tar_type == INTSXP || tar_type == REALSXP)
67 | ) {
68 | return res;
69 | }
70 | const char * what;
71 |
72 | if(set.type_mode == 0 && int_like) {
73 | what = "integer-like";
74 | } else if (set.type_mode < 2 && tar_type == REALSXP) {
75 | what = "numeric";
76 | } else if (set.type_mode == 0 && tar_type == CLOSXP) {
77 | what = "function";
78 | } else {
79 | what = type2char(tar_type);
80 | }
81 | struct ALIKEC_res res_fin = res;
82 |
83 | res_fin.success = 0;
84 | res_fin.dat.strings.target[0]= "type \"%s\"";
85 | res_fin.dat.strings.target[1]= what;
86 | res_fin.dat.strings.current[0] = "\"%s\"";
87 | res_fin.dat.strings.current[1] = type2char(cur_type);
88 | res_fin.wrap = allocVector(VECSXP, 2); // note not PROTECTing b/c return
89 | return res_fin;
90 | }
91 | SEXP ALIKEC_type_alike(
92 | SEXP target, SEXP current, SEXP call, SEXP settings
93 | ) {
94 | struct ALIKEC_res res;
95 | struct VALC_settings set = VALC_settings_vet(settings, R_BaseEnv);
96 |
97 | res = ALIKEC_type_alike_internal(target, current, set);
98 | PROTECT(res.wrap);
99 | SEXP res_sexp;
100 | if(!res.success) {
101 | res_sexp = PROTECT(ALIKEC_res_as_string(res, call, set));
102 | } else {
103 | res_sexp = PROTECT(ScalarLogical(1));
104 | }
105 | UNPROTECT(2);
106 | return(res_sexp);
107 | }
108 |
109 | /* - typeof ----------------------------------------------------------------- */
110 |
111 | SEXPTYPE ALIKEC_typeof_internal(SEXP object) {
112 | double * obj_real;
113 | SEXPTYPE obj_type = TYPEOF(object);
114 |
115 | switch(obj_type) {
116 | case REALSXP:
117 | {
118 | R_xlen_t obj_len = XLENGTH(object), i;
119 | obj_real = REAL(object);
120 | /*
121 | could optimize this more by using the magic number tricks or bit
122 | fiddling, but at end of day this still wouldn't be fast enough to
123 | realistically use on a very large vector, so it doesn't really matter
124 | */
125 | for(i = 0; i < obj_len; i++) {
126 | if(
127 | (
128 | isnan(obj_real[i]) ||
129 | obj_real[i] > INT_MAX || obj_real[i] <= INT_MIN ||
130 | obj_real[i] != (int)obj_real[i]
131 | )
132 | )
133 | return REALSXP;
134 | }
135 | return INTSXP;
136 | }
137 | break;
138 | case CLOSXP:
139 | case BUILTINSXP:
140 | case SPECIALSXP:
141 | return CLOSXP;
142 | }
143 | return(obj_type);
144 | }
145 | /*
146 | External interface for typeof, here mostly so we don't have to deal with the
147 | SEXP return in the internal use case
148 | */
149 |
150 | SEXP ALIKEC_typeof(SEXP object) {
151 | return mkString(type2char(ALIKEC_typeof_internal(object)));
152 | }
153 |
--------------------------------------------------------------------------------
/extra/PUBLISH.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: "vetR - Trust, but Verify"
3 | output:
4 | rmarkdown::html_vignette:
5 | toc: true
6 | css: vignettes/styles.css
7 | ---
8 |
9 | ```{r, echo=FALSE}
10 | knitr::opts_chunk$set(error=TRUE)
11 | library(vetr)
12 | ```
13 |
14 | # Tweets
15 |
16 | ## Tweet #2
17 |
18 | Fast #rstats bounds checks with `vetr::all_bw` https://github.com/brodieG/vetr;
19 | 10x improvement over primitives (1/3):
20 |
21 | ~30% of speedup is avoiding 3x1e6 temp logical vecs, rest is dedicated loops for
22 | input permutations (num/int/char, nas, infs, etc.) (2/3).
23 |
24 | Peeling back abstraction layer is eye-opening. Massive complexity to implement something seemingly so simple in optimized manner (3/3).
25 |
26 |
27 | ~1/3 of speedup is avoiding 3
28 | logical(1e6) intermediate vectors, rest is stripping inner loops to bare
29 | minimum:
30 |
31 |
32 | ## Tweet #1
33 |
34 | Declarative structural guarantees for #rstats S3 objects via templates with new
35 | pkg #vetr https://github.com/brodieG/vetr (1/4)
36 |
37 | ### Like vapply
38 |
39 | Declarative checks via templates, much like `vapply`:
40 |
41 | ```{r}
42 | vet(numeric(1L), 1:3)
43 | vet(numeric(1L), "hello")
44 | vet(numeric(1L), 42)
45 | ```
46 |
47 | ### Handles Complex Objects
48 |
49 | ```{r}
50 | vet(matrix(integer(), ncol=3), matrix(1:12, 4))
51 | vet(matrix(integer(), ncol=3), matrix(1:12, 3))
52 | ```
53 |
54 | Even recursive ones:
55 |
56 | ```{r}
57 | iris.template <- abstract(iris)
58 | levels(iris$Species)[3] <- 'Sibirica' # corrupt iris
59 | ```
60 | ```{r}
61 | vet(iris.template, iris[1:10,])
62 | ```
63 |
64 | Note the useful error messages.
65 |
66 | ## Tweet #2
67 |
68 | We made `vetr` fast to mitigate overhead concerns. There is a
69 | dedicated mode for fun param vetting (2/4)
70 |
71 | ### Fast
72 |
73 | Comparable in performance to `stopifnot` for simple checks, and faster for
74 | complex ones:
75 |
76 | ```{r}
77 | mx.3.col.num <- matrix(numeric(), ncol=3)
78 | mx1 <- matrix(1:12, 4)
79 |
80 | bench_mark(times=1e4,
81 | vet(mx.3.col.num, mx1),
82 | stopifnot(is.matrix(mx1), is.numeric(mx1), ncol(mx1) == 3)
83 | )
84 | ```
85 |
86 | ### In Functions
87 |
88 | `vetr()` streamlines function parameter vetting:
89 |
90 | ```{r}
91 | fun <- function(x, y) {
92 | vetr(integer(), character(1L) || NULL)
93 | }
94 | fun(1, 'hello')
95 | fun(1, NULL)
96 | fun(1, 2)
97 | ```
98 |
99 | ## Tweet #3
100 |
101 | Create complex vetting expressions with intuitively programmable
102 | non-standard-evaluation (3/4)
103 |
104 | ### Programmable NSE
105 |
106 | `vetr` implements programmable NSE via recursive substitution of language
107 | objects. This allows you to construct complex vetting expressions from simple
108 | ones:
109 |
110 | ```{r}
111 | a <- quote(integer() && . > 0)
112 | b <- quote(logical(1L) && !is.na(.))
113 | ```
114 | ```{r}
115 | vet(a || b, 1:3)
116 | vet(a || b, -1)
117 | ```
118 | ```{r}
119 | c <- quote(a || b) # equivalently
120 | ```
121 | ```{r}
122 | vet(c, -1)
123 | ```
124 |
125 | ## Tweet #4
126 |
127 | On CRAN, 100% coverage with #covr https://github.com/jimhester/covr
128 | and #unitizer https://github.com/brodieG/unitizer, but under dev so feedback
129 | welcome (4/4)
130 |
131 | # R Package Announce
132 |
133 | Announcing New Package vetr on CRAN
134 |
135 | `vetr` implements a declarative template-based approach to verify that objects
136 | meet structural requirements, and auto-compose error messages when they do not.
137 | This package is intended to simplify a more formal use of S3 objects.
138 |
139 | The template concept is borrowed from `vapply`:
140 |
141 | >> vet(numeric(1L), 1:3)
142 | > [1] "`1:3` should be length 1 (is 3)"
143 | >> vet(numeric(1L), "hello")
144 | > [1] "`\"hello\"` should be type \"numeric\" (is \"character\")"
145 | >> vet(numeric(1L), 42)
146 | > [1] TRUE
147 |
148 | There is no limit on template complexity:
149 |
150 | >> vet(matrix(integer(), ncol=3), matrix(1:12, 3))
151 | > [1] "`matrix(1:12, 3)` should have 3 columns (has 4)"
152 | >
153 | >> iris.template <- abstract(iris)
154 | >> levels(iris$Species)[3] <- 'sibirica'
155 | >> vet(iris.template, iris[1:10,])
156 | > [1] "`levels((iris[1:10, ])$Species)[3]` should be \"virginica\"
157 | > (is \"sibirica\")"
158 |
159 | `vetr` implements programmable non-standard evaluation via recursive
160 | substitution of language objects:
161 |
162 | >> a <- quote(integer() && . > 0)
163 | >> b <- quote(logical(1L) && !is.na(.))
164 | >> c <- quote(a || b)
165 | >> vet(c, -1)
166 | > [1] "At least one of these should pass:"
167 | > [2] " - `-1 > 0` is not TRUE (FALSE)"
168 | > [3] " - `-1` should be type \"logical\" (is \"double\")"
169 |
170 | Performance is comparable to `stopifnot` for simple checks, and is faster for
171 | complex (template based) ones. There is a mode that further streamlines
172 | parameter vetting in functions.
173 |
174 | The package is still under development, but the features should be mostly
175 | stable. Feedback welcome (https://github.com/brodieG/vetr).
176 |
177 | Best regards,
178 |
179 |
180 | Brodie.
181 |
--------------------------------------------------------------------------------
/R/misc.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | ## Internal Funs
18 | ##
19 | ## R interface for an internal C functions used by \code{alike}. Provided
20 | ## primarily for unit testing purposes
21 | ##
22 | ## @aliases name_compare class_compare dimname_compare dim_compare ts_compare
23 | ## lang_alike fun_alike dep_alike match_call_alike env_track
24 | ## @keywords internal
25 | ## @param int_mode
26 |
27 | attr_compare <- function(target, current, attr.mode=0L)
28 | .Call(VALC_compare_attributes, target, current, attr.mode)
29 |
30 | name_compare <- function(target, current)
31 | .Call(VALC_compare_names, target, current)
32 |
33 | class_compare <- function(target, current, rev=0) # `rev` is unused; here for legacy
34 | .Call(VALC_compare_class, target, current)
35 |
36 | dimname_compare <- function(target, current)
37 | .Call(VALC_compare_dimnames, target, current)
38 |
39 | dim_compare <- function(
40 | target, current, tar_obj=integer(), cur_obj=integer(), rev=0L
41 | )
42 | .Call(VALC_compare_dims, target, current, tar_obj, cur_obj, rev);
43 |
44 | ts_compare <- function(target, current)
45 | .Call(VALC_compare_ts, target, current)
46 |
47 | lang_alike <- function(target, current, match.call.env=parent.frame())
48 | .Call(VALC_lang_alike, target, current, match.call.env)
49 |
50 | lang_alike_chr <- function(target, current, match.call.env=parent.frame())
51 | .Call(VALC_lang_alike_chr, target, current, match.call.env)
52 |
53 | fun_alike <- function(target, current)
54 | .Call(VALC_fun_alike, target, current)
55 |
56 | dep_alike <- function(obj, width.cutoff=60L)
57 | .Call(VALC_deparse, obj, width.cutoff)
58 |
59 | dep_oneline <- function(obj, max.chars=20L, keep.at.end=0L)
60 | .Call(VALC_deparse_oneline, obj, max.chars, keep.at.end)
61 |
62 | pad <- function(obj, lines=-1, pad=-1)
63 | .Call(VALC_pad, obj, lines, pad)
64 |
65 | pad_or_quote <- function(obj, width=-1L, syntactic=-1L)
66 | .Call(VALC_pad_or_quote, obj, width, syntactic)
67 |
68 | match_call_alike <- function(call, env)
69 | .Call(VALC_match_call, call, quote(match.call(NULL, quote(NULL))), env)
70 |
71 | env_track <- function(env, size_init = 32, env_limit=65536L)
72 | .Call(VALC_env_track, env, size_init, env_limit)
73 |
74 | is_valid_name <- function(name)
75 | .Call(VALC_is_valid_name_ext, name)
76 |
77 | is_dfish <- function(obj)
78 | .Call(VALC_is_dfish, obj)
79 |
80 | alike_mode <- function(obj)
81 | .Call(VALC_mode, obj)
82 |
83 | syntactic_names <- function(lang) .Call(VALC_syntactic_names, lang)
84 |
85 | msg_sort <- function(messages)
86 | .Call(VALC_msg_sort, messages)
87 |
88 | msg_merge <- function(messages)
89 | .Call(VALC_msg_merge, messages)
90 |
91 | msg_merge_2 <- function(messages)
92 | .Call(VALC_msg_merge_2, messages)
93 |
94 | find_fun <- function(fun.name, env)
95 | .Call(VALC_find_fun, fun.name, env)
96 |
97 | hash_test <- function(values, keys) .Call(VALC_hash_test, values, keys);
98 |
99 | hash_test2 <- function(keys, add) .Call(VALC_hash_test2, keys, add);
100 |
101 | track_hash <- function(keys, size) .Call(VALC_track_hash, keys, size);
102 |
103 | hash_fun <- function(x) .Call(VALC_default_hash_fun, x)
104 |
105 | check_assumptions <- function() .Call(VALC_check_assumptions) # nocov
106 |
107 | list_as_sorted_vec <- function(x) .Call(VALC_list_as_sorted_vec, x)
108 |
109 | ### Testing C stuff; should be deleted eventually
110 | ##
111 | ## @export
112 | ##test1 <- function(a) .Call(VALC_test1, a)
113 | ## @export
114 | ##test2 <- function(a, b) .Call(VALC_test2, a, b)
115 | ## @export
116 | ##test3 <- function(a, b, c) .Call(VALC_test3, a, b, c)
117 |
118 | # Used for tests. We can't really create them test time as `where` is not
119 | # allowed, and even if it where, it only works becaue it seems topenv doesn't
120 | # anticipate the possibility globalenv will not be on the search path.
121 |
122 | #' Test Objects
123 | #'
124 | #' Objects used for testing purposes only.
125 | #'
126 | #' @rdname vetr-internal
127 | #' @name vetr-interal
128 | #' @keywords internal
129 |
130 | setClass("vetr_foo", representation(a = "character", b = "numeric"))
131 |
132 | #' @rdname vetr-internal
133 |
134 | setClass("vetr_bar", representation(d = "numeric", c = "numeric"))
135 |
136 | #' @rdname vetr-internal
137 |
138 | setClass("vetr_baz", contains="vetr_foo", list(c="character"))
139 |
140 | Foo <- setRefClass("Foo")
141 | Bar <- setRefClass("Bar")
142 |
143 |
--------------------------------------------------------------------------------
/src/validate.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include
20 | #include
21 | #include
22 | #include "trackinghash.h"
23 | #include "alike.h"
24 |
25 | // - Objects We Install Once ---------------------------------------------------
26 |
27 | // One question: are static objects not garbage collected? The examples from
28 | // "Writing R Extensions" don't seem to require the protection of these
29 |
30 | #ifndef _VETR_H
31 | #define _VETR_H
32 |
33 | // Our result holds the C and R data in separate structures mostly because it
34 | // would be slow to translate the C stuff into R so we defer that until we're
35 | // actually positive we have to make the conversion (i.e. all branches of OR
36 | // statements fail). We are quite wastefull of space here, but it is easier
37 | // this way and there shouldn't be that many objects
38 |
39 | struct VALC_res_dat {
40 | struct ALIKEC_res_dat tpl_dat; // Data from template token
41 |
42 | // The SEXP data, which can either be:
43 | //
44 | // * wrap data from template token, or:
45 | // * Standard token result, a 2 long VECSXP with the standard token
46 | // language in position 0, and the result of evaluating it in position 1
47 |
48 | SEXP sxp_dat;
49 | };
50 | // Holds all the template or standard token data
51 |
52 | struct VALC_res {
53 | struct VALC_res_dat dat;
54 | int tpl; // template or standard token res?
55 | int success;
56 | };
57 | // Holds all the template or standard token data, except for the SEXP data
58 | // which is kept separately. This is intended explicitly as a member of the
59 | // VALC_res_list array.
60 |
61 | struct VALC_res_node {
62 | struct ALIKEC_res_dat tpl_dat;
63 | int tpl; // template or standard token res?
64 | int success;
65 | };
66 | // Used to track the results of multiple tokens
67 |
68 | struct VALC_res_list {
69 | struct VALC_res_node * list_tpl;
70 | SEXP list_sxp; // this is a pairlist
71 | SEXP list_sxp_tail; // end of pairlist
72 |
73 | // index of free slot (and count of how many we have), note this means that
74 | // the last recorded result is at .list[.idx - 1], not .list[.idx]
75 | int idx;
76 | int idx_alloc; // how many we've allocated memory for
77 | int idx_alloc_max;// max we are allowed to allocate
78 | };
79 |
80 | extern SEXP VALC_SYM_one_dot;
81 | extern SEXP VALC_SYM_deparse;
82 | extern SEXP VALC_SYM_paren;
83 | extern SEXP VALC_SYM_quote;
84 | extern SEXP VALC_SYM_current;
85 | extern SEXP VALC_TRUE;
86 | extern SEXP VALC_SYM_errmsg;
87 |
88 | SEXP VALC_test1(SEXP a);
89 | SEXP VALC_test2(SEXP a, SEXP b);
90 | SEXP VALC_test3(SEXP a, SEXP b, SEXP c);
91 |
92 | SEXP VALC_check_assumptions(void);
93 |
94 | SEXP VALC_res_init(void);
95 | struct VALC_res_list VALC_res_add(
96 | struct VALC_res_list list, struct VALC_res res
97 | );
98 | struct VALC_res_list VALC_res_list_init(struct VALC_settings set);
99 |
100 | SEXP VALC_validate(
101 | SEXP target, SEXP current, SEXP cur_sub, SEXP par_call, SEXP rho,
102 | SEXP ret_mode_sxp, SEXP stop, SEXP settings
103 | );
104 | SEXP VALC_validate_args(
105 | SEXP fun, SEXP fun_call, SEXP val_call, SEXP fun_frame, SEXP settings
106 | );
107 | SEXP VALC_remove_parens(SEXP lang);
108 | SEXP VALC_name_sub_ext(SEXP symb, SEXP arg_lang);
109 | void VALC_stop(SEXP call, const char * msg);
110 | void VALC_stop2(SEXP call, const char * msg, SEXP rho);
111 | SEXP VALC_all_ext(SEXP vec);
112 | int VALC_all(SEXP vec);
113 | int IS_TRUE(SEXP x);
114 | int IS_LANG(SEXP x);
115 | SEXP VALC_parse(
116 | SEXP lang, SEXP arg_lang, struct VALC_settings settings, SEXP arg_tag
117 | );
118 | SEXP VALC_parse_ext(SEXP lang, SEXP arg_lang, SEXP rho);
119 | int VALC_parse_recurse(
120 | SEXP lang, SEXP lang2, SEXP lang_track, SEXP arg_lang,
121 | int token, struct VALC_settings set,
122 | struct track_hash * track_hash, struct track_hash * track_hash2,
123 | SEXP arg_tag
124 | );
125 | SEXP VALC_sub_symbol(
126 | SEXP lang, struct VALC_settings set, struct track_hash * track_hash,
127 | SEXP arg_tag
128 | );
129 | SEXP VALC_sub_symbol_ext(SEXP lang, SEXP rho);
130 | void VALC_install_objs(void);
131 | SEXP VALC_evaluate(
132 | SEXP lang, SEXP arg_lang, SEXP arg_tag, SEXP arg_value, SEXP lang_full,
133 | struct VALC_settings set, int use_lang_raw
134 | );
135 | SEXP VALC_evaluate_ext(
136 | SEXP lang, SEXP arg_lang, SEXP arg_tag, SEXP arg_value, SEXP lang_full,
137 | SEXP rho
138 | );
139 | void VALC_arg_error(SEXP tag, SEXP fun_call, const char * err_base);
140 | void psh(const char * lab);
141 | #endif
142 |
--------------------------------------------------------------------------------
/man/alike.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/alike.R
3 | \name{alike}
4 | \alias{alike}
5 | \title{Compare Object Structure}
6 | \usage{
7 | alike(target, current, env = parent.frame(), settings = NULL)
8 | }
9 | \arguments{
10 | \item{target}{the template to compare the object to}
11 |
12 | \item{current}{the object to determine alikeness of to the template}
13 |
14 | \item{env}{environment used internally when evaluating expressions; currently
15 | used only when looking up functions to \code{\link{match.call}} when
16 | testing language objects, note that this will be overridden by the
17 | environment specified in \code{settings} if any, defaults to the parent
18 | frame.}
19 |
20 | \item{settings}{a list of settings generated using \code{vetr_settings}, NULL
21 | for default}
22 | }
23 | \value{
24 | TRUE if target and current are alike, character(1L) describing why
25 | they are not if they are not
26 | }
27 | \description{
28 | Similar to \code{\link{all.equal}}, but compares object structure rather than
29 | value. The \code{target} argument defines a template that the \code{current}
30 | argument must match.
31 | }
32 | \note{
33 | The semantics of alikeness for language objects, formulas, and
34 | functions may change in the future.
35 | }
36 | \section{alikeness}{
37 |
38 |
39 | Generally speaking two objects are alike if they are of the same type (as
40 | determined by \code{\link{type_alike}}) and length. \code{\link{type_alike}}
41 | has special treatment for integer-like numerics and function-like objects.
42 |
43 | Attributes on the objects are required to be recursively \code{alike}, though
44 | the following attributes are treated specially: \code{class}, \code{dim},
45 | \code{dimnames}, \code{names}, \code{row.names}, \code{levels}, \code{tsp},
46 | and \code{srcref}.
47 |
48 | Exactly what makes two objects \code{alike} is complex, but should be
49 | intuitive. The best way to understand "alikeness" is to review the examples.
50 | For a thorough exposition see \href{../doc/alike.html}{the vignette}.
51 | }
52 |
53 | \examples{
54 | ## Type comparison
55 | alike(1L, 1.0) # TRUE, because 1.0 is integer-like
56 | alike(1L, 1.1) # FALSE, 1.1 is not integer-like
57 | alike(1.1, 1L) # TRUE, by default, integers are always considered real
58 |
59 | alike(1:100, 1:100 + 0.0) # TRUE
60 |
61 | ## We do not check numerics for integerness if longer than 100
62 | alike(1:101, 1:101 + 0.0)
63 |
64 | ## Scalarness can now be checked at same time as type
65 | alike(integer(1L), 1) # integer-like and length 1?
66 | alike(logical(1L), TRUE) # logical and length 1?
67 | alike(integer(1L), 1:3)
68 | alike(logical(1L), c(TRUE, TRUE))
69 |
70 | ## Zero length match any length of same type
71 | alike(integer(), 1:10)
72 | alike(1:10, integer()) # but not the other way around
73 |
74 | ## Recursive objects compared recursively
75 | alike(
76 | list(integer(), list(character(), logical(1L))),
77 | list(1:10, list(letters, TRUE))
78 | )
79 | alike(
80 | list(integer(), list(character(), logical(1L))),
81 | list(1:10, list(letters, c(TRUE, FALSE)))
82 | )
83 |
84 | ## `NULL` is a wild card when nested within recursive objects
85 | alike(list(NULL, NULL), list(iris, mtcars))
86 | alike(NULL, mtcars) # but not at top level
87 |
88 | ## Since `data.frame` are lists, we can compare them recursively:
89 | iris.fake <- transform(iris, Species=as.character(Species))
90 | alike(iris, iris.fake)
91 |
92 | ## we even check attributes (factor levels must match)!
93 | iris.fake2 <- iris
94 | levels(iris.fake2$Species) <- c("setosa", "versicolor", "africana")
95 | alike(iris, iris.fake2)
96 |
97 | ## We can use partially specified objects as templates
98 | iris.tpl <- abstract(iris)
99 | str(iris.tpl)
100 | alike(iris.tpl, iris)
101 | ## any row sample of iris matches our iris template
102 | alike(iris.tpl, iris[sample(1:nrow(iris), 10), ])
103 | ## but column order matters
104 | alike(iris.tpl, iris[c(2, 1, 3, 4, 5)])
105 |
106 | ## 3 x 3 integer
107 | alike(matrix(integer(), 3, 3), matrix(1:9, nrow=3))
108 | ## 3 x 3, but not integer!
109 | alike(matrix(integer(), 3, 3), matrix(runif(9), nrow=3))
110 | ## partial spec, any 3 row integer matrix
111 | alike(matrix(integer(), 3), matrix(1:12, nrow=3))
112 | alike(matrix(integer(), 3), matrix(1:12, nrow=4))
113 | ## Any logical matrix (but not arrays)
114 | alike(matrix(logical()), array(rep(TRUE, 8), rep(2, 3)))
115 |
116 | ## In order for objects to be alike, they must share a family
117 | ## tree, not just a common class
118 | obj.tpl <- structure(TRUE, class=letters[1:3])
119 | obj.cur.1 <- structure(TRUE, class=c("x", letters[1:3]))
120 | obj.cur.2 <- structure(TRUE, class=c(letters[1:3], "x"))
121 |
122 | alike(obj.tpl, obj.cur.1)
123 | alike(obj.tpl, obj.cur.2)
124 |
125 | ## You can compare language objects; these are alike if they are self
126 | ## consistent; we don't care what the symbols are, so long as they are used
127 | ## consistently across target and current:
128 |
129 | ## TRUE, symbols are consistent (adding two different symbols)
130 | alike(quote(x + y), quote(a + b))
131 | ## FALSE, different function
132 | alike(quote(x + y), quote(a - b))
133 | ## FALSE, inconsistent symbols
134 | alike(quote(x + y), quote(a + a))
135 | }
136 | \seealso{
137 | \code{\link{type_alike}}, \code{\link{type_of}},
138 | \code{\link{abstract}}, \code{\link{vetr_settings}} for more control of
139 | settings
140 | }
141 |
--------------------------------------------------------------------------------
/src/r-copied.c:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 |
18 | This file contains modified versions of functions copied from the R sources.
19 | Original copyright notices follow.
20 | */
21 |
22 | /*
23 | * R : A Computer Language for Statistical Data Analysis
24 | * Copyright (C) 1999--2022 The R Core Team.
25 | * Copyright (C) 1995, 1996 Robert Gentleman and Ross Ihaka
26 | *
27 | * This program is free software; you can redistribute it and/or modify
28 | * it under the terms of the GNU General Public License as published by
29 | * the Free Software Foundation; either version 2 of the License, or
30 | * (at your option) any later version.
31 | *
32 | * This program is distributed in the hope that it will be useful,
33 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
34 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 | * GNU General Public License for more details.
36 | *
37 | * You should have received a copy of the GNU General Public License
38 | * along with this program; if not, a copy is available at
39 | * https://www.R-project.org/Licenses/
40 | */
41 |
42 | /*
43 | Simplified version of R's internal findFun
44 |
45 | Doesn't do quick lookups for special symbols, or use the global cache if it is
46 | available.
47 |
48 | Most importantly, instead of failing if function is not found, returns
49 | R_UnboundValue.
50 |
51 | The code is copied almost verbatim from src/main/envir.c:findFun()
52 | */
53 |
54 | #include
55 | #include
56 | #include
57 | #include "alike.h"
58 | #include "backports.h" // For R_ParentEnv
59 |
60 | SEXP ALIKEC_findFun(SEXP symbol, SEXP rho) {
61 | if(TYPEOF(symbol) != SYMSXP)
62 | error("Internal Error: `symbol` must be symbol"); // nocov
63 | if(TYPEOF(rho) != ENVSXP)
64 | error("Internal Error: `rho` must be environment");// nocov
65 | SEXP vl;
66 | while (rho != R_EmptyEnv) {
67 | vl = findVarInFrame(rho, symbol);
68 | if (vl != R_UnboundValue) {
69 | if (TYPEOF(vl) == PROMSXP) {
70 | PROTECT(vl);
71 | vl = eval(vl, rho);
72 | UNPROTECT(1);
73 | }
74 | if (
75 | TYPEOF(vl) == CLOSXP || TYPEOF(vl) == BUILTINSXP ||
76 | TYPEOF(vl) == SPECIALSXP
77 | )
78 | return (vl);
79 | if (vl == R_MissingArg) {
80 | return R_UnboundValue;
81 | } } // nocov
82 | rho = R_ParentEnv(rho);
83 | }
84 | return R_UnboundValue;
85 | }
86 | SEXP ALIKEC_findFun_ext(SEXP symbol, SEXP rho) {
87 | SEXP res = ALIKEC_findFun(symbol, rho);
88 | if(res == R_UnboundValue) return R_NilValue;
89 | return res;
90 | }
91 |
92 | extern Rboolean mbcslocale;
93 | /* A version that reports failure as an error */
94 | /*
95 | * Taken and adapted from R 3.2.2 src/main/util.c@1324
96 | */
97 | size_t Mbrtowc(wchar_t *wc, const char *s, size_t n, mbstate_t *ps)
98 | {
99 | size_t used;
100 |
101 | if(n <= 0 || !*s) return (size_t)0;
102 | used = mbrtowc(wc, s, n, ps);
103 | if((int) used < 0) {
104 | /* This gets called from the menu setup in RGui */
105 | // if (!R_Is_Running) return (size_t)-1;
106 | /* let's try to print out a readable version */
107 | error("Internal Error: invalid multibyte string at"); // nocov
108 | }
109 | return used;
110 | }
111 | int ALIKEC_is_keyword(const char *name) {
112 | const char * keywords[19] = {
113 | "NULL", "NA", "TRUE", "FALSE", "Inf", "NaN", "NA_integer_", "NA_real_",
114 | "NA_character_", "NA_complex_", "function", "while", "repeat", "for",
115 | "if", "in", "else", "next", "break"
116 | };
117 | for (int i = 0; i < 19; i++)
118 | if (strcmp(keywords[i], name) == 0) return 1;
119 |
120 | return 0;
121 | }
122 | /*
123 | * Taken and adapted from R 3.2.2 src/main/gram.c@4915
124 | */
125 | int ALIKEC_is_valid_name(const char *name)
126 | {
127 | const char *p = name;
128 |
129 | if(mbcslocale) {
130 | /* the only way to establish which chars are alpha etc is to
131 | use the wchar variants */
132 | size_t n = strlen(name), used;
133 | wchar_t wc;
134 | used = Mbrtowc(&wc, p, n, NULL);
135 | if((int) used <= 0) return 0;
136 | p += used; n -= used;
137 | if (wc != L'.' && !iswalpha(wc) ) return 0;
138 | if (wc == L'.') {
139 | /* We don't care about other than ASCII digits */
140 | if(isdigit(0xff & (int)*p)) return 0;
141 | /* Mbrtowc(&wc, p, n, NULL); if(iswdigit(wc)) return 0; */
142 | }
143 | while((int)(used = Mbrtowc(&wc, p, n, NULL)) > 0) {
144 | if (!(iswalnum(wc) || wc == L'.' || wc == L'_')) break;
145 | p += used; n -= used;
146 | }
147 | if (*p != '\0') return 0;
148 | } else {
149 | // nocov start current local has MB_CUR_MAX > 1, so this never runs
150 | int c = 0xff & *p++;
151 | if (c != '.' && !isalpha(c) ) return 0;
152 | if (c == '.' && isdigit(0xff & (int)*p)) return 0;
153 | while ( c = 0xff & *p++, (isalnum(c) || c == '.' || c == '_') ) ;
154 | if (c != '\0') return 0;
155 | // nocov end
156 | }
157 | if (strcmp(name, "...") == 0) return 1;
158 | return !ALIKEC_is_keyword(name);
159 | }
160 |
--------------------------------------------------------------------------------
/vignettes/styles.css:
--------------------------------------------------------------------------------
1 | /*
2 | Styles primarily borrowed from rmarkdown/templates/html_vignette/resources/vignette.css
3 | at a time 12/2/2014 when rmarkdown was (and probably still is) under the GPL-3
4 | license
5 | */
6 |
7 | body {
8 | background-color: #fff;
9 | margin: 1em auto;
10 | max-width: 700px;
11 | overflow: visible;
12 | padding-left: 2em;
13 | padding-right: 2em;
14 | font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
15 | font-size: 14px;
16 | line-height: 1.5;
17 | }
18 |
19 | #header {
20 | text-align: center;
21 | }
22 |
23 | #TOC {
24 | clear: both;
25 | /*margin: 0 0 10px 10px;*/
26 | padding: 4px;
27 | width: 100%;
28 | border: 1px solid #CCCCCC;
29 | border-radius: 5px;
30 |
31 | background-color: #f6f6f6;
32 | font-size: 13px;
33 | line-height: 1.3;
34 | }
35 | #TOC .toctitle {
36 | font-weight: bold;
37 | font-size: 15px;
38 | margin-left: 5px;
39 | }
40 |
41 | #TOC ul {
42 | padding-left: 40px;
43 | margin-left: -1.5em;
44 | margin-top: 5px;
45 | margin-bottom: 5px;
46 | }
47 | #TOC ul ul {
48 | margin-left: -2em;
49 | }
50 | #TOC li {
51 | line-height: 16px;
52 | }
53 |
54 | table {
55 | margin: 1em auto;
56 | border-width: 1px;
57 | border-color: #DDDDDD;
58 | border-style: outset;
59 | border-collapse: collapse;
60 | }
61 | table th {
62 | border-width: 2px;
63 | padding: 5px;
64 | border-style: inset;
65 | }
66 | table td {
67 | border-width: 1px;
68 | border-style: inset;
69 | line-height: 18px;
70 | padding: 5px 5px;
71 | }
72 | table, table th, table td {
73 | border-left-style: none;
74 | border-right-style: none;
75 | }
76 | table thead, table tr.even {
77 | background-color: #f7f7f7;
78 | }
79 |
80 | p {
81 | margin: .5em 0;
82 | }
83 |
84 | blockquote {
85 | background-color: #f6f6f6;
86 | padding: 0.25em 0.75em;
87 | }
88 |
89 | hr {
90 | border-style: solid;
91 | border: none;
92 | border-top: 1px solid #777;
93 | margin: 28px 0;
94 | }
95 |
96 | dl {
97 | margin-left: 0;
98 | }
99 | dl dd {
100 | margin-bottom: 13px;
101 | margin-left: 13px;
102 | }
103 | dl dt {
104 | font-weight: bold;
105 | }
106 |
107 | ul {
108 | margin-top: 0;
109 | }
110 | ul li {
111 | list-style: circle outside;
112 | }
113 | ul ul {
114 | margin-bottom: 0;
115 | }
116 |
117 | h3.subtitle {
118 | margin-top: -23px;
119 | }
120 | pre, code {
121 | background-color: #EEE;
122 | color: #333;
123 | white-space: pre-wrap; /* Wrap long lines */
124 | /*border-radius: 3px;*/
125 | }
126 | code {font-size: 85%;}
127 | pre {
128 | border: 2px solid #EEE;
129 | overflow: auto;
130 | /*
131 | border-radius: 3px;
132 | */
133 | margin: 5px 0px;
134 | padding: 5px 10px;
135 | }
136 | pre:not([class]) {
137 | color: #353;
138 | /*border-radius: 0px 0px 3px 3px;*/
139 | }
140 | div.sourceCode pre, div.sourceCode code {
141 | background-color: #FAFAFA;
142 | }
143 | div.sourceCode pre{
144 | /*border-radius: 3px 3px 0px 0px;*/
145 | }
146 | div.sourceCode + pre,
147 | div.sourceCode + div.diffobj_container {
148 | margin-top: -14px;
149 | }
150 | div.diffobj_container pre{
151 | line-height: 1.3;
152 | }
153 | /*
154 | pre:not([class]) {
155 | background-color: #eee;
156 | }
157 | */
158 |
159 | code {
160 | font-family: Consolas, Monaco, 'Courier New', monospace;
161 | }
162 | p > code, li > code, h1 > code, h2 > code, h3 > code,
163 | h4 > code, h5 > code, h6 > code {
164 | padding: 2px 0px;
165 | line-height: 1;
166 | font-weight: bold;
167 | }
168 | div.figure {
169 | text-align: center;
170 | }
171 | img {
172 | background-color: #FFFFFF;
173 | padding: 2px;
174 | border: 1px solid #DDDDDD;
175 | border-radius: 3px;
176 | border: 1px solid #CCCCCC;
177 | margin: 0 5px;
178 | }
179 |
180 | h1 {
181 | margin-top: 0;
182 | padding-bottom: 3px;
183 | font-size: 35px;
184 | line-height: 40px;
185 | border-bottom: 1px solid #999;
186 | }
187 |
188 | h2 {
189 | border-bottom: 1px solid #999;
190 | padding-top: 5px;
191 | padding-bottom: 2px;
192 | font-size: 145%;
193 | }
194 |
195 | h3 {
196 | padding-top: 5px;
197 | font-size: 120%;
198 | }
199 |
200 | h4 {
201 | /*border-bottom: 1px solid #f7f7f7;*/
202 | color: #777;
203 | font-size: 105%;
204 | }
205 | h4.author {display: none;}
206 | h4.date {margin-top: -20px;}
207 |
208 | h5, h6 {
209 | /*border-bottom: 1px solid #ccc;*/
210 | font-size: 105%;
211 | }
212 |
213 | a {
214 | color: #2255dd;
215 | font-weight: bold;
216 | text-decoration: none;
217 | }
218 | a:hover {
219 | color: #6666ff; }
220 | a:visited {
221 | color: #800080; }
222 | a:visited:hover {
223 | color: #BB00BB; }
224 | a[href^="http:"] {
225 | text-decoration: underline; }
226 | a[href^="https:"] {
227 | text-decoration: underline; }
228 |
229 | /* Class described in https://benjeffrey.com/posts/pandoc-syntax-highlighting-css
230 | Colours from https://gist.github.com/robsimmons/1172277 */
231 |
232 | code > span.kw { color: #555; font-weight: bold; } /* Keyword */
233 | code > span.dt { color: #902000; } /* DataType */
234 | code > span.dv { color: #40a070; } /* DecVal (decimal values) */
235 | code > span.bn { color: #555; } /* BaseN */
236 | code > span.fl { color: #555; } /* Float */
237 | code > span.ch { color: #555; } /* Char */
238 | code > span.st { color: #40a070; } /* String */
239 | code > span.co { color: #888888; font-style: italic; } /* Comment */
240 | code > span.ot { color: #007020; } /* OtherToken */
241 | code > span.al { color: #ff0000; font-weight: bold; } /* AlertToken */
242 | code > span.fu { color: #900; font-weight: bold; } /* Function calls */
243 | code > span.er { color: #a61717; background-color: #e3d2d2; } /* ErrorTok */
244 |
--------------------------------------------------------------------------------
/src/recurse.c:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include "alike.h"
20 | /*
21 | Functions used to manage tracking recursion into list like objects
22 |
23 | To track recursion you must:
24 | - initalize the rec track object first,
25 | - increment each time you recurse,
26 | - decrement each time you recurse,
27 | - mark lvl_max when you hit an error
28 | */
29 | /*-----------------------------------------------------------------------------\
30 | \-----------------------------------------------------------------------------*/
31 | /*
32 | Allocate the storage for the indices; should be done at first error, then
33 | as we unwind recursion we record the values of the indices for each level
34 | prior to the error
35 |
36 | By design the rec.lvl should be 0 if there is no recursion.
37 | */
38 | struct ALIKEC_rec_track ALIKEC_rec_ind_init(struct ALIKEC_rec_track rec) {
39 | if(rec.lvl) {
40 | rec.indices = (struct ALIKEC_index *)
41 | R_alloc(rec.lvl, sizeof(struct ALIKEC_index));
42 | }
43 | return rec;
44 | }
45 | /*
46 | After error has been found, populate the last value in our index tracking
47 | structure, AND decrement the index level.
48 |
49 | See ALIKEC_rec_ind_init for details on index structure
50 | */
51 | struct ALIKEC_rec_track ALIKEC_rec_ind_set(
52 | struct ALIKEC_rec_track rec, struct ALIKEC_index ind
53 | ) {
54 | // Initialize indices if not initialized
55 |
56 | if(!rec.indices) {
57 | rec = ALIKEC_rec_ind_init(rec);
58 | rec.lvl_max = rec.lvl;
59 | }
60 | // Find correct spot in previously allocated indices spaces, clearly relies on
61 | // lvl being exactly correct...
62 |
63 | struct ALIKEC_index * cur_ind = rec.indices + rec.lvl - 1;
64 | *cur_ind = ind;
65 | return rec;
66 | }
67 | /*
68 | Record character or numeric index values
69 | */
70 | struct ALIKEC_rec_track ALIKEC_rec_ind_chr(
71 | struct ALIKEC_rec_track res, const char * ind
72 | ) {
73 | union ALIKEC_index_raw ind_u = {.chr = ind};
74 | return ALIKEC_rec_ind_set(res, (struct ALIKEC_index) {ind_u, 1});
75 | }
76 | struct ALIKEC_rec_track ALIKEC_rec_ind_num(
77 | struct ALIKEC_rec_track res, R_xlen_t ind
78 | ) {
79 | union ALIKEC_index_raw ind_u = {.num = ind};
80 | return ALIKEC_rec_ind_set(res, (struct ALIKEC_index) {ind_u, 0});
81 | }
82 | struct ALIKEC_rec_track ALIKEC_rec_track_init(void) {
83 | return (struct ALIKEC_rec_track) {
84 | .lvl = 0,
85 | .lvl_max = 0,
86 | .indices = 0, // NULL pointer
87 | .envs = 0, // NULL pointer
88 | .gp = 0
89 | };
90 | }
91 | /*
92 | increment recursion
93 |
94 | decrementing happens via ALIKEC_rec_ind_set
95 | */
96 | struct ALIKEC_rec_track ALIKEC_rec_inc(struct ALIKEC_rec_track rec) {
97 | size_t lvl_old = rec.lvl;
98 | rec.lvl++;
99 | if(rec.lvl < lvl_old) {
100 | // nocov start
101 | error(
102 | "Internal Error: %s; contact maintainer.",
103 | "max recursion depth exceeded, this really shouldn't happen"
104 | );
105 | // nocov end
106 | }
107 | return rec;
108 | }
109 | struct ALIKEC_rec_track ALIKEC_rec_dec(struct ALIKEC_rec_track rec) {
110 | if(!rec.lvl) {
111 | // nocov start
112 | error(
113 | "Internal Error: %s; contact maintainer.",
114 | "tried to decrement rec counter below zero"
115 | );
116 | // nocov end
117 | }
118 | rec.lvl--;
119 | return rec;
120 | }
121 | /*
122 | Closely related to ALIKEC_rec_ind_as_chr except that it return a list (vector)
123 | with the language call with all the indices subset, and the pointer to the
124 | location in the language call that needs to be substituted.
125 | */
126 | SEXP ALIKEC_rec_ind_as_lang(struct ALIKEC_rec_track rec) {
127 | SEXP res = PROTECT(allocVector(VECSXP, 2));
128 | setAttrib(res, ALIKEC_SYM_syntacticnames, ScalarLogical(1));
129 | SEXP lang = PROTECT(list1(R_NilValue));
130 | SEXP lang_cpy = lang;
131 |
132 | if(rec.lvl_max) { // Recursion occurred
133 | // Make call to `[[` or `$`. CAR is the `[[` or `$`, CADDR is the index
134 | // value, and CADR is the spot that will be filled in with what is being
135 | // subsetted: CADR$CADDR or CADR[[CADDR]]
136 |
137 | for(size_t i = rec.lvl_max; i > 0; i--) {
138 | size_t j = i - 1;
139 | SEXP index_call = PROTECT(lang3(R_NilValue, R_NilValue, R_NilValue));
140 | switch(rec.indices[j].type) {
141 | case 0:
142 | SETCAR(index_call, R_Bracket2Symbol);
143 | SETCADDR(index_call, ScalarReal(rec.indices[j].ind.num));
144 | break;
145 | case 1:
146 | SETCAR(index_call, R_DollarSymbol);
147 | SETCADDR(index_call, install(rec.indices[j].ind.chr));
148 | if(!ALIKEC_is_valid_name(rec.indices[j].ind.chr))
149 | setAttrib(res, ALIKEC_SYM_syntacticnames, ScalarLogical(0));
150 | break;
151 | // nocov start
152 | default: {
153 | error(
154 | "Internal Error: unexpected index type %d; contact maintainer.",
155 | rec.indices[j].type
156 | );
157 | // nocov end
158 | } }
159 | SETCAR(lang, index_call);
160 | UNPROTECT(1);
161 | lang = CDR(index_call);
162 | }
163 | SET_VECTOR_ELT(res, 0, CAR(lang_cpy));
164 | SET_VECTOR_ELT(res, 1, lang);
165 | }
166 | UNPROTECT(2);
167 | return res;
168 | }
169 |
--------------------------------------------------------------------------------
/tests/unitizer/validate.args.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | library(vetr)
18 |
19 | unitizer_sect("Single template validation", {
20 | fun0 <- function(x, y, z)
21 | vetr(x=matrix(integer(), ncol=3), y=integer(2L), z=logical(1L))
22 | fun0(1, 2, 3)
23 | fun0(matrix(1), 2, 3)
24 | fun0(matrix(1:3, nrow=1), 2, 3)
25 | fun0(matrix(1:3, nrow=1), 2:3, 3)
26 | fun0(matrix(1:3, nrow=1), c(2.0, 3.0), 3) # integer like
27 | fun0(matrix(1:3, nrow=1), c(2.0, 3.0), TRUE)
28 | })
29 | unitizer_sect("Multi-template validation", {
30 | fun1 <- function(x, y, z)
31 | vetr(
32 | x=matrix(integer(), ncol=3) || integer(3L),
33 | y=integer(2L) || NULL || logical(1L),
34 | z=logical(1L)
35 | )
36 | fun1(1:3, "fail", "fail") # x passes
37 | fun1(matrix(1:9, ncol=3), "fail", "fail") # x passes
38 | fun1(letters[1:3], "fail", "fail") # x fails
39 |
40 | fun1(1:3, 1:2, "fail") # x,y pass
41 | fun1(1:3, NULL, "fail") # x,y pass
42 | fun1(1:3, FALSE, "fail") # x,y pass
43 |
44 | fun1(1:3, FALSE, FALSE) # all pass
45 | })
46 | unitizer_sect("Template and Straight Eval", {
47 | fun2 <- function(x, y, z)
48 | vetr(
49 | x=(matrix(integer(), ncol=3) || integer(3L)) && .(!any(is.na(.))),
50 | y=integer(3L) && .(all(. > 0)),
51 | z=logical(1L) && .(!is.na(.))
52 | )
53 | fun2(matrix(c(1:8, NA), nrow=3), NULL, NULL)
54 | fun2(matrix(c(1:9), nrow=3), -1:1, NULL)
55 | fun2(matrix(c(1:9), nrow=3), 1:3, NA)
56 | fun2(matrix(c(1:9), nrow=3), 1:3, TRUE)
57 | })
58 | unitizer_sect("Complex OR outcomes", {
59 | fun2a <- function(x)
60 | vetr(
61 | x=setNames(character(3L), letters[1:3]) || matrix("", 3, 1) ||
62 | list(character(), x=integer())
63 | )
64 | fun2a(letters[1:3])
65 | })
66 | unitizer_sect("Errors in Arguments", {
67 | fun3 <- function(x, y)
68 | vetr(x=logical(1L), y=integer(3L))
69 | fun3(stop("boom"))
70 | fun3(TRUE, stop("boomBOOM"))
71 | fun3(1:3, stop("boomBOOM"))
72 |
73 | fun4 <- function(x, y)
74 | vetr(x=stop("BOOM"), y=integer(3L))
75 | fun4(NULL, 1:3)
76 |
77 | fun5 <- function(x, y)
78 | vetr(x=integer(3L), y=NULL || .(stop("hah")))
79 | fun5(1:3, NULL)
80 | fun5(1:2, NULL)
81 |
82 | fun6 <- function(x, y)
83 | vetr(x=integer(3L), y=NULL && .(stop("hah")))
84 | fun6(1:3, NULL)
85 | })
86 | unitizer_sect("Args evaled in correct env?", {
87 | fun7 <- function(x, y=z + 2) { z <- "boom"; vetr(x=TRUE, y=1L) }
88 | fun7a <- function(x, y=z + 2) { z <- 40; vetr(x=TRUE, y=1L) }
89 | z <- 1
90 | fun7(TRUE) # fail because z in fun is character
91 | fun7a(TRUE) # works
92 | fun8 <- function(x, y=z + 2) { a <- b <- TRUE; vetr(x=TRUE, y=1L) }
93 | fun8a <- function(x, y=z + 2) { a <- b <- NULL; vetr(x=TRUE, y=1L) }
94 | a <- NULL
95 | b <- TRUE
96 | fun8(a && b) # fail because a in parent is NULL
97 | a <- TRUE
98 | fun8a(a && b) # works despite NULLs in function
99 |
100 | # Make sure we can access defined templates in lexical parents
101 |
102 | fun_make <- function() {
103 | a <- matrix(1:9, 3)
104 | tpl <- matrix(numeric(), 3)
105 |
106 | function(x) {
107 | vetr(tpl)
108 | TRUE
109 | }
110 | }
111 | fun <- fun_make()
112 | a <- b <- 1:9
113 | local({
114 | NULL
115 | a <- character()
116 | fun(a)
117 | })
118 | local({
119 | b <- character()
120 | fun(b)
121 | })
122 | # make sure we can access variables that are not in fun lexical scope
123 |
124 | fun8b <- function(x) vetr(x=length(.) > 0 && integer())
125 | get("zfqwefkj") # should fail
126 | local({
127 | zfqwefkj <- 200L
128 | fun8b(zfqwefkj)
129 | })
130 | })
131 | unitizer_sect("Compound Expression Scope Issues", {
132 | a <- quote(!anyNA(.))
133 | fun <- function(x) {
134 | a <- quote(all(. > 0))
135 | b <- quote(is.vector(.))
136 | vetr(a && b)
137 | TRUE
138 | }
139 | fun(-(1:3))
140 | })
141 | unitizer_sect("Non-equal args and validation exps", {
142 | fun8 <- function(x="hello", y=TRUE, z)
143 | vetr(x=integer(), z=integer(2L))
144 |
145 | fun8(1L, NULL, 1:2)
146 | fun8(1L, 1:2, NULL)
147 |
148 | fun8(1L, 1:2)
149 | fun8(1L)
150 |
151 | # default argument fails validation
152 |
153 | fun8(z=1:2)
154 |
155 | })
156 | unitizer_sect("Referencing argument in vet exp error", {
157 | fun1 <- function(x, y) vetr(x > 0, . < 3)
158 | fun1(1:10, 1:10)
159 | fun2 <- function(x, y) vetr(. > 0 && all(y > 0), y < 3)
160 | fun2(TRUE, 1:10)
161 |
162 | # also check with vet, although not as important
163 |
164 | x <- 1:10
165 | vet(x > 0, x)
166 | vet((x + 1) > 0, x + 1) # this doesn't cause error, but maybe should?
167 | })
168 | unitizer_sect("Default arg mix-up", {
169 | fun10a <- function(x, y=TRUE, z=999) vetr(INT, LGL.1, INT.1)
170 | fun10a(1, z=1:3)
171 |
172 | fun10b <- function(x, y=TRUE, z=999) vetr(INT, z=INT.1)
173 | fun10b(1, z=1:3)
174 | })
175 | unitizer_sect("Dots", {
176 | f <- function(x, y=1L, z=1L, ...) vetr(1L, 1L, 1L)
177 | f(2L, w=3L)
178 |
179 | f <- function(x, y=1L, ...) vetr(1L, 1L, 1L)
180 | f(2L, z=3L)
181 | })
182 | unitizer_sect("Don't access promises in environments", {
183 | fenv <- function(env) vetr(environment())
184 | env <- FALSE
185 | fenv(environment())
186 | })
187 | unitizer_sect("Invocation via `do.call` (#109)", {
188 | f <- function(x) vetr(is.function(.))
189 | do.call(f, list(mean))
190 | })
191 |
--------------------------------------------------------------------------------
/tests/unitizer/language.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | # redefine funs to give us flexibility if we change packages without having
18 | # to export the internal functions
19 |
20 | library(vetr)
21 |
22 | unitizer_sect("Match Calls", {
23 | vetr:::match_call_alike(quote(var(y=1:10, runif(10))), baseenv())
24 | env0 <- new.env()
25 | env0$var <- function(yollo, zambia) NULL
26 | vetr:::match_call_alike(quote(var(y=1:10, runif(10))), env0)
27 | })
28 | unitizer_sect("Calls", {
29 | c0 <- quote(fun(a, b, a, 25))
30 | c1 <- quote(fun(x, y, x, "hello"))
31 | c2 <- quote(fun(x, y, z, "hello"))
32 | c3 <- quote(FUN(x, y, x, 1.01))
33 | c4 <- quote(fun(x, y, x, z))
34 | c5 <- quote(fun(a + b + a, FUN(z, a + 1)))
35 | c6 <- quote(fun(x + y + x, FUN(w, x + 2)))
36 | c7 <- quote(fun(x + y + x, FUN(w, y + 2)))
37 | c8 <- quote(fun(x + y + x, FUN(w, x - 2)))
38 | c9 <- quote(fun(x + y + x, FUN(w, x + "hello")))
39 | c10 <- quote(fun(1))
40 | c11 <- quote(fun(1, 2))
41 |
42 | c12 <- quote(a + b + c)
43 | c13 <- quote((a + b) + c)
44 | c14 <- quote(a + (b + c))
45 |
46 | vetr:::lang_alike(c0, c1, NULL) # TRUE
47 | vetr:::lang_alike(c0, c2, NULL) # no, inconsistent
48 | vetr:::lang_alike(c0, c3, NULL) # no, wrong fun name
49 | vetr:::lang_alike(c0, c4, NULL) # extra symbol
50 | vetr:::lang_alike(c5, c6, NULL) # TRUE
51 | vetr:::lang_alike(c5, c7, NULL) # inconsistent
52 | vetr:::lang_alike(c5, c8, NULL) # wrong call `-`
53 | vetr:::lang_alike(c5, c9, NULL) # TRUE
54 | vetr:::lang_alike(c11, c10, NULL)# Length mismatch
55 |
56 | # Parens
57 |
58 | vetr:::lang_alike(c12, c13) # equivalent
59 | vetr:::lang_alike(c12, c14) # not equivalent
60 | vetr:::lang_alike(c13, c14) # not equivalent
61 |
62 | vetr:::lang_alike(c14, c13) # not equivalent
63 |
64 | # with defined fun
65 |
66 | fun <- function(abc, bcd, efg) NULL
67 |
68 | ca <- quote(fun(a, b, a))
69 | cb <- quote(fun(x, e=x, y))
70 |
71 | vetr:::lang_alike(ca, cb, NULL) # shouldn't match without match.call
72 | vetr:::lang_alike(cb, ca, NULL) # false, different error
73 | vetr:::lang_alike(ca, cb) # TRUE, should match
74 |
75 | # Actually use a function (and not just name of fun)
76 |
77 | ca.1 <- ca
78 | cb.1 <- cb
79 |
80 | ca.1[[1]] <- fun
81 | cb.1[[1]] <- fun
82 |
83 | vetr:::lang_alike(ca.1, cb.1) # TRUE, should match
84 |
85 | # test nested match.call
86 |
87 | cc <- quote(fun(a, b, fun(b=1)))
88 | cd <- quote(fun(a, b, fun(c=1)))
89 |
90 | vetr:::lang_alike(cc, cd)
91 |
92 | # NULL in target matches anything
93 |
94 | ce <- quote(fun(a, b, NULL))
95 |
96 | vetr:::lang_alike(cc, ce) # FALSE
97 | vetr:::lang_alike(ce, cc) # TRUE
98 |
99 | # mismatched functions
100 |
101 | da <- quote(ff(a=1, b=2, c=3))
102 | db <- quote(ff(a=1, d=2, c=3))
103 |
104 | vetr:::lang_alike(da, db)
105 |
106 | # Errors
107 |
108 | vetr:::lang_alike(cc, 1:10)
109 | vetr:::lang_alike(ce, cc, match.call.env=1:10)
110 | })
111 | unitizer_sect("Calls as char", {
112 | vetr:::lang_alike_chr(c0, c1, NULL) # TRUE
113 | vetr:::lang_alike_chr(c0, c2, NULL) # no, inconsistent
114 | vetr:::lang_alike_chr(c0, c3, NULL) # no, wrong fun name
115 | vetr:::lang_alike_chr(c0, c4, NULL) # extra symbol
116 | vetr:::lang_alike_chr(c5, c6, NULL) # TRUE
117 | vetr:::lang_alike_chr(c5, c7, NULL) # inconsistent
118 | vetr:::lang_alike_chr(c5, c8, NULL) # wrong call `-`
119 | vetr:::lang_alike_chr(c5, c9, NULL) # TRUE
120 |
121 | vetr:::lang_alike_chr(ca, cb, NULL) # shouldn't match without match.call
122 | vetr:::lang_alike_chr(cb, ca, NULL) # false, different error
123 | vetr:::lang_alike_chr(ca, cb) # TRUE, should match
124 |
125 | # test nested match.call
126 |
127 | vetr:::lang_alike_chr(cc, cd)
128 |
129 | # NULL in target matches anything
130 |
131 | vetr:::lang_alike_chr(cc, ce) # FALSE
132 | vetr:::lang_alike_chr(ce, cc) # TRUE
133 | })
134 | unitizer_sect("Formulas", {
135 | f0 <- y ~ x + 1
136 | f1 <- a ~ b + 1
137 | f2 <- a ~ b + 2
138 | f3 <- y ~ x + log(x) + z - 1
139 | f4 <- a ~ b + log(b) + c - 1
140 | f5 <- a ~ b + log(c) + b - 1
141 | f6 <- a ~ b + ln(b) + c - 1
142 | f7 <- a ~ b + log(b) + c + 1
143 |
144 | vetr:::lang_alike(f0, f1, NULL) # TRUE
145 | vetr:::lang_alike(f0, f2, NULL) # FALSE
146 | vetr:::lang_alike(f3, f4, NULL) # TRUE
147 | vetr:::lang_alike(f3, f5, NULL) # FALSE
148 | vetr:::lang_alike(f3, f6, NULL) # FALSE
149 | vetr:::lang_alike(f3, f7, NULL) # FALSE
150 | })
151 | unitizer_sect("Deparse", {
152 | l0 <- quote(
153 | a + b + fun(x + funz(
154 | matrix_over[25, 32]) + transform(iris, x = Sepal.Width * 3) /
155 | the_donkey_ate_a_carrot %in% {
156 | paste0(
157 | match(letter, LETTERS),
158 | c("hello there")
159 | ) } ) )
160 | # simple deparse
161 |
162 | (dep.txt <- vetr:::dep_alike(l0))
163 | vetr:::dep_alike(l0, 30)
164 |
165 | # manip the deparse
166 |
167 | vetr:::pad(dep.txt)
168 | old.opt <- options(prompt=">>", continue=" |")
169 | vetr:::pad(dep.txt)
170 | options(old.opt)
171 | vetr:::pad(dep.txt, pad=4)
172 | vetr:::pad(dep.txt, pad=4, lines=2)
173 |
174 | # oneline
175 |
176 | vetr:::dep_oneline(quote(1 + 1 + 3 + 944254235), 10)
177 | vetr:::dep_oneline(quote(1 + 1 + 3), 10)
178 | vetr:::dep_oneline(quote(1 + 1 + 3), "hello")
179 | vetr:::dep_oneline(quote(1 + 1 + 3 - (mean(1:10) + 3)), 15, 1L)
180 | })
181 |
--------------------------------------------------------------------------------
/man/vetr_settings.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/settings.R
3 | \name{vetr_settings}
4 | \alias{vetr_settings}
5 | \title{Generate Control Settings For vetr and alike}
6 | \usage{
7 | vetr_settings(
8 | type.mode = 0L,
9 | attr.mode = 0L,
10 | lang.mode = 0L,
11 | fun.mode = 0L,
12 | rec.mode = 0L,
13 | suppress.warnings = FALSE,
14 | fuzzy.int.max.len = 100L,
15 | width = -1L,
16 | env.depth.max = 65535L,
17 | symb.sub.depth.max = 65535L,
18 | symb.size.max = 15000L,
19 | nchar.max = 65535L,
20 | track.hash.content.size = 63L,
21 | env = NULL,
22 | result.list.size.init = 64L,
23 | result.list.size.max = 1024L
24 | )
25 | }
26 | \arguments{
27 | \item{type.mode}{integer(1L) in 0:2, defaults to 0, determines how object
28 | types (as in \code{typeof}) are compared: \itemize{
29 | \item 0: integer like numerics (e.g. \code{1.0}) can match against
30 | integer templates, and integers always match real templates; all
31 | function types are considered of the same type
32 | \item 1: integers always match against numeric templates, but not vice
33 | versa, and integer-like numerics are treated only as numerics;
34 | functions only match same function type (i.e. closures only match
35 | closures, builtins builtins, and specials specials)
36 | \item 2: types must be equal for all objects types (for functions, this
37 | is unchanged from 1)
38 | }}
39 |
40 | \item{attr.mode}{integer(1L) in 0:2, defaults to 0, determines strictness of
41 | attribute comparison: \itemize{
42 | \item \code{0} only checks attributes that are present in target, and
43 | uses special comparisons for the special attributes (\code{class},
44 | \code{dim}, \code{dimnames}, \code{names}, \code{row.names},
45 | \code{levels}, \code{srcref}, and \code{tsp}) while requiring other
46 | attributes to be \code{alike}
47 | \item \code{1} is like \code{0}, except all atributes must be
48 | \code{alike}
49 | \item \code{2} requires all attributes to be present in \code{target} and
50 | \code{current} and to be alike
51 | }}
52 |
53 | \item{lang.mode}{integer(1L) in 0:1, defaults to 0, controls language
54 | matching, set to \code{1} to turn off use of \code{\link[=match.call]{match.call()}}}
55 |
56 | \item{fun.mode}{NOT IMPLEMENTED, controls how functions are compared}
57 |
58 | \item{rec.mode}{integer(1L) \code{0} currently unused, intended to control how
59 | recursive structures (other than language objects) are compared}
60 |
61 | \item{suppress.warnings}{logical(1L) suppress warnings if TRUE}
62 |
63 | \item{fuzzy.int.max.len}{max length of numeric vectors to consider for
64 | integer likeness (e.g. \code{c(1, 2)} can be considered "integer", even
65 | though it is numeric); currently we limit this check to vectors
66 | shorter than 100 to avoid a potentially expensive computation on large
67 | vectors, set to -1 to apply to all vectors irrespective of length}
68 |
69 | \item{width}{to use when deparsing expressions; default \code{-1}
70 | equivalent to \code{getOption("width")}}
71 |
72 | \item{env.depth.max}{integer(1L) maximum number of nested environments to
73 | recurse through, defaults to 65535L; these are tracked to make sure we do
74 | not get into an infinite recursion loop, but because they are tracked we
75 | keep a limit on how many we will go through, set to -1 to allow unlimited
76 | recursion depth. You should not need to change this unless you are running
77 | into the recursion limit.}
78 |
79 | \item{symb.sub.depth.max}{integer(1L) maximum recursion depth when
80 | recursively substituting symbols in vetting expression, defaults to 65535L}
81 |
82 | \item{symb.size.max}{integer(1L) maximum number of characters that a symbol
83 | is allowed to have in vetting expressions, defaults to 15000L.}
84 |
85 | \item{nchar.max}{integer(1L) defaults to 65535L, threshold after which
86 | strings encountered in C code are truncated. This is the read limit. In
87 | theory \code{vetr} can produce strings longer than that by combining multiple
88 | shorter pieces.}
89 |
90 | \item{track.hash.content.size}{integer(1L) (advanced) used to set the initial
91 | size of the symbol tracking vector used with the hash table that detects
92 | recursive symbol substitution. If the tracking vector fills up it will be
93 | grown by 2x. This parameter is exposed mostly for developer use.}
94 |
95 | \item{env}{what environment to use to match calls and evaluate vetting
96 | expressions, although typically you would specify this with the \code{env}
97 | argument to \code{vet}; if NULL will use the calling frame to
98 | \code{vet/vetr/alike}.}
99 |
100 | \item{result.list.size.init}{initial value for token tracking. This will be
101 | grown by a factor of two each time it fills up until we reach
102 | \code{result.list.size.max}.}
103 |
104 | \item{result.list.size.max}{maximum number of tokens we keep track of,
105 | intended mostly as a safeguard in case a logic error causes us to keep
106 | allocating memory. Set to 1024 as a default value since it should be
107 | exceedingly rare to have vetting expressions with such a large number of
108 | tokens, enough so that if we reach that number it is more likely something
109 | went wrong.}
110 | }
111 | \value{
112 | list with all the setting values
113 | }
114 | \description{
115 | Utility function to generate setting values. We strongly recommend
116 | that you generate the settings outside of function calls so that setting
117 | generation does not become part of the \code{vet/vetr/alike} evaluation as
118 | that could add noticeable overhead to the function evaluation.
119 | }
120 | \details{
121 | Settings after \code{fuzzy.int.max.len} are fairly low level and exposed mostly
122 | for testing purposes. You should generally not need to use them.
123 |
124 | Note that a successful evaluation of this function does not guarantee a
125 | correct settings list. Those checks are carried out internally by
126 | \code{vet/vetr/alike}.
127 | }
128 | \examples{
129 | type_alike(1L, 1.0, settings=vetr_settings(type.mode=2))
130 | ## better if you are going to re-use settings to reduce overhead
131 | set <- vetr_settings(type.mode=2)
132 | type_alike(1L, 1.0, settings=set)
133 | }
134 | \seealso{
135 | \code{\link{type_alike}}, \code{\link{alike}}, \code{\link{vetr}}
136 | }
137 |
--------------------------------------------------------------------------------
/src/envtrack.c:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include "alike.h"
20 |
21 | /*
22 | We need environment stack tracking that will not persist across .Call calls
23 | */
24 |
25 | /*
26 | Allocated and re-allocate our env stack tracking object
27 |
28 | Return 0 for failure, 1 for normal success, 2 for success requiring
29 | re-allocation, 3 for success requiring re-allocation and copying
30 |
31 | We need stack size to be strictly greater than stack_ind as next time we write
32 | we will write to stack_ind
33 | */
34 |
35 | int ALIKEC_env_stack_alloc(
36 | struct ALIKEC_env_track * envs, int env_limit
37 | ) {
38 | int success = 1;
39 | int stack_size = envs->stack_size;
40 | if(stack_size <= envs->stack_ind) {
41 | int stack_size_old = stack_size;
42 | if(stack_size > INT_MAX - stack_size) {
43 | // nocov start
44 | error(
45 | "%s%s",
46 | "Internal Error: cannot increase env stack size past INT_MAX; ",
47 | "contact maintainer"
48 | );
49 | // nocov end
50 | }
51 | stack_size += stack_size ? stack_size : envs->stack_size_init;
52 |
53 | if(stack_size <= envs->stack_ind) {
54 | // nocov start
55 | error(
56 | "%s%s",
57 | "Internal Error: env stack size increase is insufficient; ",
58 | "contact maintainer"
59 | );
60 | // nocov end
61 | }
62 | if(stack_size > env_limit) return 0;
63 |
64 | SEXP * env_stack_tmp = (SEXP *) R_alloc(stack_size, sizeof(SEXP));
65 | envs->stack_size = stack_size;
66 |
67 | success = 2;
68 | if(envs->env_stack == 0) {
69 | envs->env_stack = env_stack_tmp;
70 | } else if(envs->stack_size > stack_size_old) {
71 | // Prev allocation happened, need to copy pointers
72 | for(int i = 0; i < stack_size_old; i++)
73 | env_stack_tmp[i] = envs->env_stack[i];
74 | // ideally would free env_stack before repointing...
75 | envs->env_stack = env_stack_tmp;
76 | success = 3;
77 | }
78 | }
79 | return success;
80 | }
81 | /*
82 | Initialize our stack tracking object
83 | */
84 | struct ALIKEC_env_track * ALIKEC_env_set_create(
85 | int stack_size_init, int env_limit
86 | ) {
87 | if(stack_size_init < 1) {
88 | // nocov start
89 | error(
90 | "Internal Error: `alike` env stack size init should be greater than zero"
91 | );
92 | // nocov end
93 | }
94 | struct ALIKEC_env_track * envs =
95 | (struct ALIKEC_env_track *) R_alloc(1, sizeof(struct ALIKEC_env_track));
96 | envs->stack_size = envs->stack_ind = 0;
97 | envs->env_stack = 0;
98 | envs->no_rec = 0;
99 | envs->stack_size_init = stack_size_init;
100 | int res = ALIKEC_env_stack_alloc(envs, env_limit);
101 | if(!res) error("Unable to allocate `alike` environment stack");
102 | return envs;
103 | }
104 |
105 | /*
106 | Track what environments we've checked already
107 |
108 | Not super efficient allocation here; we should really free previous allocation
109 | instead of just leaving it hanging until .Call ends.
110 |
111 | Also, this should really be a linked list so we don't have to re-copy all the
112 | pointers every time we expand the list.
113 |
114 | Really taking solace in the point that this part of the code should be rarely
115 | activated.
116 |
117 | Returns
118 | * > 1 if the environment has not been seen before (and adds it to stack),
119 | really it is the result of the allocation attempt
120 | * 0 if the environment is found
121 | * -1 if we are out of space in the env stack
122 | */
123 |
124 | int ALIKEC_env_track(
125 | SEXP env, struct ALIKEC_env_track * envs, int env_limit
126 | ) {
127 | int alloc_res;
128 | if(!(alloc_res = ALIKEC_env_stack_alloc(envs, env_limit))) return -1;
129 | int env_found = 0;
130 | for(int i = 0; i < envs->stack_ind; i++) {
131 | if(env == envs->env_stack[i]) {
132 | env_found = 1;
133 | break;
134 | }
135 | }
136 | if(env_found) return 0;
137 | envs->env_stack[envs->stack_ind] = env;
138 | envs->stack_ind++;
139 | return alloc_res;
140 | }
141 | /*
142 | External interface purely for testing whether our environment hashing
143 | is working
144 | */
145 |
146 | SEXP ALIKEC_env_track_test(SEXP env_list, SEXP stack_size_init, SEXP env_limit) {
147 | int stack_init_int = asInteger(stack_size_init);
148 | if(stack_init_int == NA_INTEGER || stack_init_int < 0) {
149 | // nocov start
150 | error("Internal Error: stack_size_init must be positive");
151 | // nocov end
152 | }
153 | if(TYPEOF(env_list) != VECSXP) {
154 | // nocov start
155 | error("Internal Error: expected a list for argument `env_list`");
156 | // nocov end
157 | }
158 | if(TYPEOF(env_limit) != INTSXP) {
159 | // nocov start
160 | error("Internal Error: expected an integer for argument `env_limit`");
161 | // nocov end
162 | }
163 | int env_limit_int = asInteger(env_limit);
164 | struct ALIKEC_env_track * envs =
165 | ALIKEC_env_set_create(stack_init_int, env_limit_int);
166 |
167 | R_xlen_t len = XLENGTH(env_list);
168 | SEXP res = PROTECT(allocVector(INTSXP, len));
169 | int * res_int = INTEGER(res);
170 | R_xlen_t i;
171 |
172 | for(i = 0; i < len; i++) {
173 | SEXP env = VECTOR_ELT(env_list, i);
174 | if(TYPEOF(env) != ENVSXP)
175 | error(
176 | "All contents of `env` %s at item %jd\n",
177 | "should be environments; error ",
178 | i == INTMAX_MAX ? - 1 : (intmax_t) i + 1
179 | );
180 | res_int[i] = ALIKEC_env_track(env, envs, env_limit_int);
181 | }
182 | UNPROTECT(1);
183 | return res;
184 | }
185 |
--------------------------------------------------------------------------------
/R/alike.R:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 Brodie Gaslam
2 | #
3 | # This file is part of "vetr - Trust, but Verify"
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # Go to for a copy of the license.
16 |
17 | #' Compare Object Structure
18 | #'
19 | #' Similar to \code{\link{all.equal}}, but compares object structure rather than
20 | #' value. The \code{target} argument defines a template that the \code{current}
21 | #' argument must match.
22 | #'
23 | #' @section alikeness:
24 | #'
25 | #' Generally speaking two objects are alike if they are of the same type (as
26 | #' determined by \code{\link{type_alike}}) and length. \code{\link{type_alike}}
27 | #' has special treatment for integer-like numerics and function-like objects.
28 | #'
29 | #' Attributes on the objects are required to be recursively \code{alike}, though
30 | #' the following attributes are treated specially: \code{class}, \code{dim},
31 | #' \code{dimnames}, \code{names}, \code{row.names}, \code{levels}, \code{tsp},
32 | #' and \code{srcref}.
33 | #'
34 | #' Exactly what makes two objects \code{alike} is complex, but should be
35 | #' intuitive. The best way to understand "alikeness" is to review the examples.
36 | #' For a thorough exposition see \href{../doc/alike.html}{the vignette}.
37 | #'
38 | #' @note The semantics of alikeness for language objects, formulas, and
39 | #' functions may change in the future.
40 | #'
41 | #' @export
42 | #' @seealso \code{\link{type_alike}}, \code{\link{type_of}},
43 | #' \code{\link{abstract}}, \code{\link{vetr_settings}} for more control of
44 | #' settings
45 | #' @param target the template to compare the object to
46 | #' @param current the object to determine alikeness of to the template
47 | #' @param settings a list of settings generated using \code{vetr_settings}, NULL
48 | #' for default
49 | #' @param env environment used internally when evaluating expressions; currently
50 | #' used only when looking up functions to \code{\link{match.call}} when
51 | #' testing language objects, note that this will be overridden by the
52 | #' environment specified in \code{settings} if any, defaults to the parent
53 | #' frame.
54 | #' @return TRUE if target and current are alike, character(1L) describing why
55 | #' they are not if they are not
56 | #' @examples
57 | #' ## Type comparison
58 | #' alike(1L, 1.0) # TRUE, because 1.0 is integer-like
59 | #' alike(1L, 1.1) # FALSE, 1.1 is not integer-like
60 | #' alike(1.1, 1L) # TRUE, by default, integers are always considered real
61 | #'
62 | #' alike(1:100, 1:100 + 0.0) # TRUE
63 | #'
64 | #' ## We do not check numerics for integerness if longer than 100
65 | #' alike(1:101, 1:101 + 0.0)
66 | #'
67 | #' ## Scalarness can now be checked at same time as type
68 | #' alike(integer(1L), 1) # integer-like and length 1?
69 | #' alike(logical(1L), TRUE) # logical and length 1?
70 | #' alike(integer(1L), 1:3)
71 | #' alike(logical(1L), c(TRUE, TRUE))
72 | #'
73 | #' ## Zero length match any length of same type
74 | #' alike(integer(), 1:10)
75 | #' alike(1:10, integer()) # but not the other way around
76 | #'
77 | #' ## Recursive objects compared recursively
78 | #' alike(
79 | #' list(integer(), list(character(), logical(1L))),
80 | #' list(1:10, list(letters, TRUE))
81 | #' )
82 | #' alike(
83 | #' list(integer(), list(character(), logical(1L))),
84 | #' list(1:10, list(letters, c(TRUE, FALSE)))
85 | #' )
86 | #'
87 | #' ## `NULL` is a wild card when nested within recursive objects
88 | #' alike(list(NULL, NULL), list(iris, mtcars))
89 | #' alike(NULL, mtcars) # but not at top level
90 | #'
91 | #' ## Since `data.frame` are lists, we can compare them recursively:
92 | #' iris.fake <- transform(iris, Species=as.character(Species))
93 | #' alike(iris, iris.fake)
94 | #'
95 | #' ## we even check attributes (factor levels must match)!
96 | #' iris.fake2 <- iris
97 | #' levels(iris.fake2$Species) <- c("setosa", "versicolor", "africana")
98 | #' alike(iris, iris.fake2)
99 | #'
100 | #' ## We can use partially specified objects as templates
101 | #' iris.tpl <- abstract(iris)
102 | #' str(iris.tpl)
103 | #' alike(iris.tpl, iris)
104 | #' ## any row sample of iris matches our iris template
105 | #' alike(iris.tpl, iris[sample(1:nrow(iris), 10), ])
106 | #' ## but column order matters
107 | #' alike(iris.tpl, iris[c(2, 1, 3, 4, 5)])
108 | #'
109 | #' ## 3 x 3 integer
110 | #' alike(matrix(integer(), 3, 3), matrix(1:9, nrow=3))
111 | #' ## 3 x 3, but not integer!
112 | #' alike(matrix(integer(), 3, 3), matrix(runif(9), nrow=3))
113 | #' ## partial spec, any 3 row integer matrix
114 | #' alike(matrix(integer(), 3), matrix(1:12, nrow=3))
115 | #' alike(matrix(integer(), 3), matrix(1:12, nrow=4))
116 | #' ## Any logical matrix (but not arrays)
117 | #' alike(matrix(logical()), array(rep(TRUE, 8), rep(2, 3)))
118 | #'
119 | #' ## In order for objects to be alike, they must share a family
120 | #' ## tree, not just a common class
121 | #' obj.tpl <- structure(TRUE, class=letters[1:3])
122 | #' obj.cur.1 <- structure(TRUE, class=c("x", letters[1:3]))
123 | #' obj.cur.2 <- structure(TRUE, class=c(letters[1:3], "x"))
124 | #'
125 | #' alike(obj.tpl, obj.cur.1)
126 | #' alike(obj.tpl, obj.cur.2)
127 | #'
128 | #' ## You can compare language objects; these are alike if they are self
129 | #' ## consistent; we don't care what the symbols are, so long as they are used
130 | #' ## consistently across target and current:
131 | #'
132 | #' ## TRUE, symbols are consistent (adding two different symbols)
133 | #' alike(quote(x + y), quote(a + b))
134 | #' ## FALSE, different function
135 | #' alike(quote(x + y), quote(a - b))
136 | #' ## FALSE, inconsistent symbols
137 | #' alike(quote(x + y), quote(a + a))
138 |
139 | alike <- function(target, current, env=parent.frame(), settings=NULL)
140 | .Call(VALC_alike_ext, target, current, substitute(current), env, settings)
141 |
142 |
--------------------------------------------------------------------------------
/man/vet_token.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/validators.R
3 | \docType{data}
4 | \name{vet_token}
5 | \alias{vet_token}
6 | \alias{NO.NA}
7 | \alias{NO.INF}
8 | \alias{GTE.0}
9 | \alias{LTE.0}
10 | \alias{GT.0}
11 | \alias{LT.0}
12 | \alias{INT.1}
13 | \alias{INT.1.POS}
14 | \alias{INT.1.NEG}
15 | \alias{INT.1.POS.STR}
16 | \alias{INT.1.NEG.STR}
17 | \alias{INT}
18 | \alias{INT.POS}
19 | \alias{INT.NEG}
20 | \alias{INT.POS.STR}
21 | \alias{INT.NEG.STR}
22 | \alias{NUM.1}
23 | \alias{NUM.1.POS}
24 | \alias{NUM.1.NEG}
25 | \alias{NUM}
26 | \alias{NUM.POS}
27 | \alias{NUM.NEG}
28 | \alias{CHR.1}
29 | \alias{CHR}
30 | \alias{CPX}
31 | \alias{CPX.1}
32 | \alias{LGL}
33 | \alias{LGL.1}
34 | \title{Vetting Tokens With Custom Error Messages}
35 | \format{
36 | An object of class \code{call} of length 2.
37 |
38 | An object of class \code{call} of length 2.
39 |
40 | An object of class \code{call} of length 3.
41 |
42 | An object of class \code{call} of length 3.
43 |
44 | An object of class \code{call} of length 3.
45 |
46 | An object of class \code{call} of length 3.
47 |
48 | An object of class \code{call} of length 3.
49 |
50 | An object of class \code{call} of length 3.
51 |
52 | An object of class \code{call} of length 3.
53 |
54 | An object of class \code{call} of length 3.
55 |
56 | An object of class \code{call} of length 3.
57 |
58 | An object of class \code{call} of length 3.
59 |
60 | An object of class \code{call} of length 3.
61 |
62 | An object of class \code{call} of length 3.
63 |
64 | An object of class \code{call} of length 3.
65 |
66 | An object of class \code{call} of length 3.
67 |
68 | An object of class \code{call} of length 3.
69 |
70 | An object of class \code{call} of length 3.
71 |
72 | An object of class \code{call} of length 3.
73 |
74 | An object of class \code{call} of length 3.
75 |
76 | An object of class \code{call} of length 3.
77 |
78 | An object of class \code{call} of length 3.
79 |
80 | An object of class \code{call} of length 3.
81 |
82 | An object of class \code{call} of length 3.
83 |
84 | An object of class \code{call} of length 3.
85 |
86 | An object of class \code{call} of length 3.
87 |
88 | An object of class \code{call} of length 3.
89 |
90 | An object of class \code{call} of length 3.
91 | }
92 | \usage{
93 | vet_token(exp, err.msg = "\%s")
94 |
95 | NO.NA
96 |
97 | NO.INF
98 |
99 | GTE.0
100 |
101 | LTE.0
102 |
103 | GT.0
104 |
105 | LT.0
106 |
107 | INT.1
108 |
109 | INT.1.POS
110 |
111 | INT.1.NEG
112 |
113 | INT.1.POS.STR
114 |
115 | INT.1.NEG.STR
116 |
117 | INT
118 |
119 | INT.POS
120 |
121 | INT.NEG
122 |
123 | INT.POS.STR
124 |
125 | INT.NEG.STR
126 |
127 | NUM.1
128 |
129 | NUM.1.POS
130 |
131 | NUM.1.NEG
132 |
133 | NUM
134 |
135 | NUM.POS
136 |
137 | NUM.NEG
138 |
139 | CHR.1
140 |
141 | CHR
142 |
143 | CPX
144 |
145 | CPX.1
146 |
147 | LGL
148 |
149 | LGL.1
150 | }
151 | \arguments{
152 | \item{exp}{an expression which will be captured but not evaluated.}
153 |
154 | \item{err.msg}{character(1L) a message that tells the user what the
155 | expected value should be, should contain a "\%s" for \code{sprintf}
156 | to use (e.g. "\%s should be greater than 2").}
157 | }
158 | \value{
159 | a quoted expressions with \code{err.msg} attribute set
160 | }
161 | \description{
162 | Utility function to generate vetting tokens with attached error messages.
163 | You should only need to use this if the error message produced naturally by
164 | \code{vetr} is unclear. Several predefined tokens created by this function
165 | are also documented here.
166 | }
167 | \details{
168 | Allows you to supply error messages for vetting to use for each error
169 | token. Your token should not contain top level \code{&&} or \code{||}. If
170 | it does your error message will not be reported because \code{vetr} looks for
171 | error messages attached to atomic tokens. If your token must involve
172 | top level \code{&&} or \code{||}, use \code{I(x && y)} to ensure that
173 | your error message is used by \code{vet}, but beware than in doing so you do
174 | not use templates within the \code{I} call as everything therein will be
175 | interpreted as a vetting expression rather than a template.
176 |
177 | Error messages are typically of the form "\%s should be XXX".
178 |
179 | This package ships with many predefined tokens for common use cases. They
180 | are listed in the \code{Usage} section of this documentation. The tokens
181 | are named in format \code{TYPE[.LENGTH][.OTHER]}. For example
182 | \code{INT} will vet an integer vector, \code{INT.1} will vet a scalar integer
183 | vector, and \code{INT.1.POS.STR} will vet a strictly positive integer vector.
184 | At this time tokens are predefined for the basic types as scalars or
185 | any-length vectors. Some additional checks are available (e.g. positive only
186 | values).
187 |
188 | Every one of the predefined vetting tokens documented here implicitly
189 | disallows NAs. Numeric tokens also disallow infinite values. If you wish
190 | to allow NAs or infinite values just use a template object (e.g.
191 | \code{integer(1L)}).
192 | }
193 | \note{
194 | \strong{This will only work with standard tokens containing \code{.}}. Anything
195 | else will be interpreted as a template token.
196 | }
197 | \examples{
198 | ## Predefined tokens:
199 | vet(INT.1, 1:2)
200 | vet(INT.1 || LGL, 1:2)
201 | vet(INT.1 || LGL, c(TRUE, FALSE))
202 |
203 | ## Check squareness
204 | mx <- matrix(1:3)
205 | SQR <- vet_token(nrow(.) == ncol(.), "\%s should be square")
206 | vet(SQR, mx)
207 |
208 | ## Let `vetr` make up error message; note `quote` vs `vet_token`
209 | ## Often, `vetr` does fine without explictly specified err msg:
210 | SQR.V2 <- quote(nrow(.) == ncol(.))
211 | vet(SQR.V2, mx)
212 |
213 | ## Combine some tokens, notice how we use `quote` at the combining
214 | ## step:
215 | NUM.MX <- vet_token(matrix(numeric(), 0, 0), "\%s should be numeric matrix")
216 | SQR.NUM.MX <- quote(NUM.MX && SQR)
217 | vet(SQR.NUM.MX, mx)
218 |
219 | ## If instead we used `vet_token` the overall error message
220 | ## is not used; instead it falls back to the error message of
221 | ## the specific sub-token that fails:
222 | NUM.MX <- vet_token(matrix(numeric(), 0, 0), "\%s should be numeric matrix")
223 | SQR.NUM.MX.V2 <-
224 | vet_token(NUM.MX && SQR, "\%s should be a square numeric matrix")
225 | vet(SQR.NUM.MX.V2, mx)
226 | }
227 | \seealso{
228 | \code{\link[=vet]{vet()}}
229 | }
230 | \keyword{datasets}
231 |
--------------------------------------------------------------------------------
/tests/unitizer/_pre/lorem.R:
--------------------------------------------------------------------------------
1 | # we go through the trouble of saving stuff to RDS to quash valgrind errors
2 | # caused by heap alloc'ed strings that are strsplit with perl=TRUE
3 |
4 | lorem <- "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
5 | Encoding(lorem) <- "UTF-8"
6 |
7 | # lorem.phrases <- unlist(strsplit(lorem, "[.,]\\K ", perl=TRUE))
8 | # saveRDS(lorem.phrases, 'unitizer/_pre/lorem.objs/lorem.RDS')
9 | lorem.phrases <- readRDS('unitizer/_pre/lorem.objs/lorem.RDS')
10 |
11 | # From the Stalin russian wiki page:
12 | #
13 | #
14 | #
15 | # Licensed under GNU Free Documentation License since it is from before 2009
16 |
17 | # lorem.ru <- "Родился 6 (18) декабря 1878 года (по записи в метрической книге Горийской Успенской соборной церкви[6]Это подлинная дата рождения Сталина. Позже она подтверждалась в уведомлении Санкт-Петербургского жандармского управления и самим Сталиным в ответе на анкету шведской газеты «Folkets Dagblad Politiken» в 1920 г.[7]) в Грузии в городе Гори, хотя начиная с 1929 года[источник?] днём его рождения официально считалось 9 (21) декабря 1879. Был третьим сыном в семье, первые двое умерли в младенчестве. Его родным языком был грузинский, русский язык Сталин выучил позже, но всегда говорил с заметным грузинским акцентом [8]. Согласно утверждениям дочери Светланы, Сталин, однако, пел по-русски практически без акцента."
18 | # Encoding(lorem.ru) <- "UTF-8"
19 | # lorem.ru.phrases <- unlist(strsplit(lorem.ru, "[.,]\\K ", perl=TRUE))
20 | # saveRDS(lorem.ru.phrases, 'unitizer/_pre/lorem.objs/lorem.ru.RDS')
21 | lorem.ru.phrases <- readRDS('unitizer/_pre/lorem.objs/lorem.ru.RDS')
22 |
23 | # From the Stalin turkish wiki page:
24 | #
25 | #
26 | #
27 | # Licensed under GNU Free Documentation License since it is from before 2009
28 |
29 | # lorem.tr <- "Bu tartışmalı tarihsel dönemle ilgili olarak, Stalin'e düşman veya Stalin'den yana olan her iki tarafın da farklı tezleri vardır. Stalin karşıtlarının tezlerine göre, Hitlerle aralarındaki açıklanmayan gizli protokole bağlı olarak Finlandiya, Estonya, Letonya, Litvanya, Romanya ve Polonya'nin Naziler veya Sovyetler tarafından işgalinin yolu açılmıştır. Stalin'in doğru yaptığını savunanlara göre ise, 1937'deki Münih görüşmelerinde açıkça ortaya çıktığı gibi, İngiliz ve Fransız emperyalistleri ve dolaylı olarak da Amerikalılar, Nazileri kışkırtıyorlardı ve onların Sovyetler Birliği'ne saldırısının önünü açmaya çalışıyorlardı. Bu amaçla Avusturya'nın Almanya'ya katılmasına (Anschluss) ve Çekoslovakya'nın işgaline göz yummuş ve onaylamışlardı.Ne var ki, özellikle Çekoslovakya'nın işgalinden sonra Sovyetler Birliği'nin İngiltere ve Fransa ile ilişki kurma çabalarına rağmen bu iki ülke Nazi tehdidini birlikte ortadan kaldırma girişimini reddetti. Böylece Sovyetler Birliği, kendi sınırlarını güvence altına almak için bu protokolü imzaladı. Stalin'in amaçlarına göre, Polonya ve Baltık ülkelerinde oluşturulacak tampon bölgeler, Nazilerin Sovyetler Birliği'ne ulaşmasını engelleyecekti. Böylece 1939 yılında Nazi işgalinden sonra Sovyetler Polonya'nın kalan yarısını işgal edip Estonya, Litvanya ve Letonya'yı sınırlarına kattı. Finlandiya'ya saldırdı ve büyük kayıplar vermesine rağmen Mart 1940'da \"kış savaşı' olarak bilinen bu savaşı da kazandı. 1941'de Hitler'in Sovyetlere saldırması üzerine Stalin bu sefer müttefiklerin yanında yer aldı. II. Dünya Savaşı'nın en ağır bedeli ödeyen güç olarak (24 milyon ölü) müttefiklerin yanında Nazi Almanyası'na karşı kazandığı zafer uluslararası alanda gücünü artırdı."
30 | # Encoding(lorem.tr) <- "UTF-8"
31 | # lorem.tr.phrases <- unlist(strsplit(lorem.tr, "[.,]\\K ", perl=TRUE))
32 | # saveRDS(lorem.tr.phrases, 'unitizer/_pre/lorem.objs/lorem.tr.RDS')
33 | lorem.tr.phrases <- readRDS('unitizer/_pre/lorem.objs/lorem.tr.RDS')
34 |
35 | # From the PRC chinese wiki page:
36 | #
37 | #
38 | #
39 | # Licensed under GNU Free Documentation License since it is from before 2009
40 |
41 | # lorem.cn <- "中華人民共和國是單一制的多民族國家。全國劃分為23個省(其中台灣省并沒有實際管辖)、5個自治區、4個直轄市和2個根據一國兩制設立的特別行政區,均直屬於中央人民政府。中华人民共和国跨越五个地理时区,但全国均使用北京时间(UTC+8,东八区)作为标准时间。中華人民共和國官方認定的民族現有56個,其中最大民族汉族佔總人口的91.59%,其餘55族統稱為少数民族,所有民族統稱為中華民族;除回族外,其他54個少数民族如壮族、维吾尔族、滿族、蒙古族、藏族、朝鲜族等也多使用自己的語言與文字。主要宗教有佛教、道教、基督教(多指新教)、天主教和伊斯兰教等,但過半人口無特定宗教信仰。中华人民共和国的通用语言是汉语普通話,當中在中國大陸通行的漢字為簡體字,而在港澳台地區通行的漢字則為繁體字。"
42 | # Encoding(lorem.cn) <- "UTF-8"
43 | # cn.split <- "[、。]\\K"
44 | # Encoding(cn.split) <- "UTF-8"
45 | # lorem.cn.phrases <- unlist(strsplit(lorem.cn, cn.split, perl=TRUE))
46 | #
47 | # saveRDS(lorem.cn.phrases, 'unitizer/_pre/lorem.objs/lorem.cn.RDS')
48 | lorem.cn.phrases <- readRDS('unitizer/_pre/lorem.objs/lorem.cn.RDS')
49 |
50 | # Emoji from Unicode Site
51 |
52 | # emoji <- c(
53 | # "\U0001f600", "\U0001f619", "\U0001f61a", "\U0001f642", "\U0001f92f",
54 | # "\U0001f62c", "\U0001f630", "\U0001f631", "\U0001f633", "\U0001f92a",
55 | # "\U0001f635", "\U0001f637", "\U0001f912", "\U0001f915", "\U0001f922",
56 | # "\U0001f92e", "\U0001f927", "\U0001f607", "\U0001f920", "\U0001f921",
57 | # "\U0001f925", "\U0001f92b", "\U0001f92d", "\U0001f9d0", "\U0001f913",
58 | # "\U0001f608", "\U0001f4a9", "\U0001f63e",
59 | # "\U0001f469\U0001f3ff\U0000200d\U0001f3eb",
60 | # "\U0001f468\U0001f3fb\U0000200d\U00002696\U0000FE0F", "\U0001f46b",
61 | # "\U0001f469\U0000200D\U00002764\U0000200d\U0001f48b\U0000200d\U0001f468",
62 | # "\U0001f468\U0000200d\U0001f468\U0000200d\U0001f467",
63 | # "\U0001f468\U0000200d\U0001f468\U0000200d\U0001f467\U0000200d\U0001f466",
64 | # "\U0001f984", "\U0001f36b", "\U0001f1e6\U0001f1f7", "\U0001f1ed\U0001f1f0",
65 | # "\U0001f1ef\U0001f1f5", "\U0001f1f0\U0001f1f5", "\U0001f1fa\U0001f1f8",
66 | # "\U0001f3f4"
67 | # )
68 | # Encoding(emoji) <- "UTF-8"
69 | # lorem.emo <- paste(
70 | # sample(
71 | # c(emoji, rep(", ", 3), rep(". ", 3), rep(" ", 40)), 450, replace=TRUE
72 | # ),
73 | # collapse=""
74 | # )
75 | # lorem.emo.phrases <- unlist(strsplit(lorem.emo, "[,.]\\K", perl=TRUE))
76 |
77 |
--------------------------------------------------------------------------------
/man/vet.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/validate.R
3 | \name{vet}
4 | \alias{vet}
5 | \alias{tev}
6 | \title{Verify Objects Meet Structural Requirements}
7 | \usage{
8 | vet(
9 | target,
10 | current,
11 | env = parent.frame(),
12 | format = "text",
13 | stop = FALSE,
14 | settings = NULL
15 | )
16 |
17 | tev(
18 | current,
19 | target,
20 | env = parent.frame(),
21 | format = "text",
22 | stop = FALSE,
23 | settings = NULL
24 | )
25 | }
26 | \arguments{
27 | \item{target}{a template, a vetting expression, or a compound expression}
28 |
29 | \item{current}{an object to vet}
30 |
31 | \item{env}{the environment to match calls and evaluate vetting expressions
32 | in; will be ignored if an environment is also specified via
33 | \code{\link[=vetr_settings]{vetr_settings()}}. Defaults to calling frame.}
34 |
35 | \item{format}{character(1L), controls the format of the return value for
36 | \code{vet}, in case of failure. One of:\itemize{
37 | \item "text": (default) character(1L) message for use elsewhere in code
38 | \item "full": character(1L) the full error message used in "stop" mode,
39 | but actually returned instead of thrown as an error
40 | \item "raw": character(N) least processed version of the error message
41 | with none of the formatting or surrounding verbiage
42 | }}
43 |
44 | \item{stop}{TRUE or FALSE whether to call \code{\link[=stop]{stop()}} on failure
45 | or not (default)}
46 |
47 | \item{settings}{a settings list as produced by \code{\link[=vetr_settings]{vetr_settings()}}, or NULL to
48 | use the default settings}
49 | }
50 | \value{
51 | TRUE if validation succeeds, otherwise varies according to value
52 | chosen with parameter \code{stop}
53 | }
54 | \description{
55 | Use vetting expressions to enforce structural requirements and/or evaluate
56 | test conditions for truth. \code{tev} is identical to \code{vet} except with reversed
57 | arguments for pipe based workflows.
58 | }
59 | \section{Vetting Expressions}{
60 |
61 |
62 | Vetting expressions can be template tokens, standard tokens, or any
63 | expression built with them, \code{||}, \code{&&}, and parentheses. Template tokens
64 | are R objects that define the required structure, much like the \code{FUN.VALUE}
65 | argument to \code{\link[=vapply]{vapply()}}. Standard tokens are R expressions evaluated and
66 | checked for being \code{all(TRUE)}.
67 |
68 | Standard tokens are distinguished from templates by whether they reference
69 | the \code{.} symbol or not. If you have a need to reference an object bound to
70 | \code{.} in a vetting expression, you can escape the \code{.} with an extra dot (i.e.
71 | use \code{..}, and \code{...} for \code{..}, and so forth for symbols comprising only
72 | dots). If you use standard tokens in your packages you will need to include
73 | \code{utils::globalVariables(".")} as a top-level call to avoid the "no visible
74 | binding for global variable '.'"' R CMD check NOTE. Standard tokens that
75 | return a string like e.g. \code{all.equal(x, .)} will result in that string being
76 | incorporated into the error message.
77 |
78 | See \code{vignette('vetr', package='vetr')} and examples for details on how
79 | to craft vetting expressions.
80 | }
81 |
82 | \examples{
83 | ## Template token vetting
84 | vet(numeric(2L), runif(2))
85 | vet(numeric(2L), runif(3))
86 | vet(numeric(2L), letters)
87 | try(vet(numeric(2L), letters, stop=TRUE))
88 |
89 | ## Standard token vetting
90 | vet(. > 0, runif(2))
91 |
92 | ## Expression made of standard and template tokens.
93 | vet(numeric(1) && . > 0, 1)
94 | try(vet(numeric(1) && . > 0, 1:2))
95 | try(vet(numeric(1) && . > 0, -1))
96 |
97 | ## `tev` just reverses target and current
98 | ## if(getRversion() >= "4.1.0") { # would be a parse error so commented
99 | ## runif(2) |> tev(numeric(2L))
100 | ## runif(3) |> tev(numeric(2L))
101 | ## }
102 |
103 | ## Zero length templates are wild cards
104 | vet(numeric(), runif(2))
105 | vet(numeric(), runif(100))
106 | vet(numeric(), letters)
107 |
108 | ## This extends to data.frames
109 | iris.tpl <- iris[0,] # zero row matches any # of rows
110 | iris.1 <- iris[1:10,]
111 | iris.2 <- iris[1:10, c(1,2,3,5,4)] # change col order
112 | vet(iris.tpl, iris.1)
113 | vet(iris.tpl, iris.2)
114 |
115 | ## Short (<100 length) integer-like numerics will
116 | ## pass for integer
117 | vet(integer(), c(1, 2, 3))
118 | vet(integer(), c(1, 2, 3) + 0.1)
119 |
120 | ## Nested templates; note, in packages you should consider
121 | ## defining templates outside of `vet` or `vetr` so that
122 | ## they are computed on load rather that at runtime
123 | tpl <- list(numeric(1L), matrix(integer(), 3))
124 | val.1 <- list(runif(1), rbind(1:10, 1:10, 1:10))
125 | val.2 <- list(runif(1), cbind(1:10, 1:10, 1:10))
126 | vet(tpl, val.1)
127 | vet(tpl, val.2)
128 |
129 | ## See `example(alike)` for more template examples
130 |
131 | ## Standard tokens allow you to check values
132 | vet(. > 0, runif(10))
133 | vet(. > 0, -runif(10))
134 |
135 | ## Zero length token results are considered TRUE,
136 | ## as is the case with `all(logical(0))`
137 | vet(. > 0, numeric())
138 |
139 | ## `all_bw` is like `isTRUE(all(. >= x & . <= y))`, but
140 | ## ~10x faster for long vectors:
141 | vet(all_bw(., 0, 1), runif(1e6) + .1)
142 |
143 | ## You can combine templates and standard tokens with
144 | ## `&&` and/or `||`
145 | vet(numeric(2L) && . > 0, runif(2))
146 | vet(numeric(2L) && . > 0, runif(10))
147 | vet(numeric(2L) && . > 0, -runif(2))
148 |
149 | ## Using pre-defined tokens (see `?vet_token`)
150 | vet(INT.1, 1)
151 | vet(INT.1, 1:2)
152 | vet(INT.1 && . \%in\% 0:1 || LGL.1, TRUE)
153 | vet(INT.1 && . \%in\% 0:1 || LGL.1, 1)
154 | vet(INT.1 && . \%in\% 0:1 || LGL.1, NA)
155 |
156 | ## Vetting expressions can be assembled from previously
157 | ## defined tokens
158 | scalar.num.pos <- quote(numeric(1L) && . > 0)
159 | foo.or.bar <- quote(character(1L) && . \%in\% c('foo', 'bar'))
160 | vet.exp <- quote(scalar.num.pos || foo.or.bar)
161 |
162 | vet(vet.exp, 42)
163 | vet(scalar.num.pos || foo.or.bar, 42) # equivalently
164 | vet(vet.exp, "foo")
165 | vet(vet.exp, "baz")
166 |
167 | ## Standard tokens that return strings see the string shown
168 | ## in the error message:
169 | vet(all.equal(., 2), 1)
170 | }
171 | \seealso{
172 | \code{\link[=vetr]{vetr()}} for a version optimized to vet function arguments,
173 | \code{\link[=alike]{alike()}} for how templates are used, \code{\link[=vet_token]{vet_token()}} for how to specify
174 | custom error messages and also for predefined validation tokens for common
175 | use cases, \code{\link[=all_bw]{all_bw()}} for fast bounds checks.
176 | }
177 |
--------------------------------------------------------------------------------
/src/trackinghash.c:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include "pfhash.h"
20 | #include "cstringr.h"
21 | #include "trackinghash.h"
22 |
23 | /*
24 | * Implement a hash table with ancillary data that intended to allow checking
25 | * that a value exists in a hash table, and also quickly erase the last n values
26 | * that were written to the hash table
27 | *
28 | * size_init is the initial size of the content tracking character array, and
29 | * has no effect on the actual hash table.
30 | */
31 | struct track_hash * VALC_create_track_hash(size_t size_init) {
32 | pfHashTable * hash = pfHashCreate(NULL);
33 | char ** contents = (char **) R_alloc(size_init, sizeof(char *));
34 | struct track_hash * track_hash =
35 | (struct track_hash *) R_alloc(1, sizeof(struct track_hash));
36 |
37 | track_hash->hash = hash;
38 | track_hash->contents = contents;
39 | track_hash->idx = 0;
40 | track_hash->idx_max = size_init;
41 |
42 | return track_hash;
43 | }
44 |
45 | /*
46 | * Restores hash to original state at index idx by removing all entries in
47 | * contents that were defined up to and including that point.
48 | *
49 | * reset_track_hash(x, 0) will remove all entries.
50 | *
51 | * Modifies the hash table by reference.
52 | */
53 |
54 | void VALC_reset_track_hash(
55 | struct track_hash * track_hash, size_t idx
56 | ) {
57 | for(size_t i = track_hash->idx; i > idx; --i) {
58 |
59 | int del_res = pfHashDel(track_hash->hash, track_hash->contents[i - 1]);
60 | if(del_res)
61 | // nocov start
62 | error(
63 | "Internal Error: unable to delete key %s; contact maintainer.",
64 | track_hash->contents[i - 1]
65 | );
66 | // nocov end
67 | }
68 | track_hash->idx = idx;
69 | }
70 | /* Add an item to the hash table
71 | *
72 | * If it already exists return 0, else if it doesn't exist 1, unless an
73 | * allocation is required in which case return the size of the allocation.
74 | *
75 | * Modifies track_hash by reference
76 | */
77 |
78 | int VALC_add_to_track_hash(
79 | struct track_hash * track_hash, const char * key, const char * value,
80 | size_t max_nchar
81 | ) {
82 | int res = 1;
83 | int res_set = pfHashSet(track_hash->hash, key, value);
84 |
85 | if(res_set < 0) {
86 | // nocov start
87 | error(
88 | "Internal Error: failed setting value in hash table, contact maintainer."
89 | );
90 | // nocov end
91 | } else if(res_set) {
92 | // Already existed, so no need to add
93 | res = 0;
94 | } else {
95 | // Need to add a value to the hash, first make sure that there is enough
96 | // room in the content tracking to hold it, and if not double the size of
97 | // the tracking list
98 |
99 | if(track_hash->idx == track_hash->idx_max) {
100 |
101 | // first, make sure no issues with size_t -> long
102 |
103 | size_t new_size = CSR_add_szt(track_hash->idx_max, track_hash->idx_max);
104 | size_t max_long = 1;
105 | max_long = (max_long << sizeof(long)) / 2;
106 |
107 | if(new_size > max_long) {
108 | // nocov start
109 | error(
110 | "Internal Error: attempted to allocate hash content vector bigger "
111 | "than int size."
112 | );
113 | // nocov end
114 | }
115 | // re-allocate, note that we are re-allocating an array of pointers to
116 | // strings, but `S_realloc` is looking for a (char *) hence the coersion
117 |
118 | track_hash->contents = (char **) S_realloc(
119 | (char *) track_hash->contents, (long) new_size,
120 | (long) track_hash->idx_max,
121 | sizeof(char *)
122 | );
123 | res = (int) new_size;
124 | track_hash->idx_max = new_size;
125 | } else if (track_hash->idx > track_hash->idx_max) {
126 | // nocov start
127 | error("Internal Error: hash index corrupted; contact maintainer.");
128 | // nocov end
129 | }
130 | // We incur some cost in duplicating string here which may not be strictly
131 | // necessary since it's most likely every string we use here should be
132 | // present for the duration of execution, but cost is probably reasonably
133 | // low. Should revisit if this turns out to be wrong.
134 |
135 | char * key_cpy = CSR_strmcpy(key, max_nchar);
136 | track_hash->contents[track_hash->idx] = key_cpy;
137 | track_hash->idx++; // shouldn't be overflowable
138 | }
139 | return res;
140 | }
141 | /*
142 | * External function for testing
143 | *
144 | * Any NA values in `keys` are taken to mean to take the `as.numeric` value
145 | * of the next element as the reset index.
146 | *
147 |
148 | hash tracking, uses a hash to detect potential collisions, 1 means a value
149 | is added, >1 means a value was added and tracking array had to be resized
150 | to that size, 0 means it existed already, NA is a reset instruction, value
151 | following a reset instruction is what the reset was to
152 | */
153 |
154 | SEXP VALC_track_hash_test(SEXP keys, SEXP size) {
155 | if(TYPEOF(keys) != STRSXP) error("Arg keys must be character");
156 | if(TYPEOF(size) != INTSXP) error("Arg size must be integer");
157 |
158 | R_xlen_t i;
159 | R_xlen_t key_size = xlength(keys);
160 | SEXP res = PROTECT(allocVector(INTSXP, key_size));
161 |
162 | struct track_hash * track_hash = VALC_create_track_hash(asInteger(size));
163 | struct VALC_settings set = VALC_settings_init();
164 |
165 | for(i = 0; i < key_size; ++i) {
166 | if(STRING_ELT(keys, i) == NA_STRING) {
167 | INTEGER(res)[i] = NA_INTEGER;
168 | if(++i < key_size) {
169 | int reset_int = atoi(CHAR(STRING_ELT(keys, i)));
170 | if(reset_int < 0) error("Internal Error: negative reset key.");
171 | VALC_reset_track_hash(track_hash, (size_t) reset_int);
172 | INTEGER(res)[i] = reset_int;
173 | }
174 | } else {
175 | int add_res = VALC_add_to_track_hash(
176 | track_hash, CHAR(STRING_ELT(keys, i)), "42", set.nchar_max
177 | );
178 | INTEGER(res)[i] = add_res;
179 | }
180 | }
181 | UNPROTECT(1);
182 | return(res);
183 | }
184 |
--------------------------------------------------------------------------------
/src/init.c:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2023 Brodie Gaslam
3 |
4 | This file is part of "vetr - Trust, but Verify"
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 2 of the License, or
9 | (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | Go to for a copy of the license.
17 | */
18 |
19 | #include "validate.h"
20 | #include "all-bw.h"
21 | #include
22 |
23 | static const
24 | R_CallMethodDef callMethods[] = {
25 | {"validate", (DL_FUNC) &VALC_validate, 8},
26 | {"validate_args", (DL_FUNC) &VALC_validate_args, 5},
27 | {"symb_sub", (DL_FUNC) &VALC_sub_symbol_ext, 2},
28 | {"parse", (DL_FUNC) &VALC_parse_ext, 3},
29 | {"remove_parens", (DL_FUNC) &VALC_remove_parens, 1},
30 | {"eval_check", (DL_FUNC) &VALC_evaluate_ext, 6},
31 | {"all", (DL_FUNC) &VALC_all_ext, 1},
32 | {"track_hash", (DL_FUNC) &VALC_track_hash_test, 2},
33 | {"default_hash_fun", (DL_FUNC) &VALC_default_hash_fun, 1},
34 | {"all_bw", (DL_FUNC) &VALC_all_bw, 5},
35 | {"check_assumptions", (DL_FUNC) &VALC_check_assumptions, 0},
36 |
37 | /*
38 | {"test1", (DL_FUNC) &VALC_test1, 1},
39 | {"test2", (DL_FUNC) &VALC_test2, 2},
40 | {"test3", (DL_FUNC) &VALC_test3, 3},
41 | */
42 | {"alike_ext", (DL_FUNC) &ALIKEC_alike_ext, 5},
43 | {"typeof", (DL_FUNC) &ALIKEC_typeof, 1},
44 | {"mode", (DL_FUNC) &ALIKEC_mode, 1},
45 | {"type_alike", (DL_FUNC) &ALIKEC_type_alike, 4},
46 | {"syntactic_names", (DL_FUNC) &ALIKEC_syntactic_names_exp, 1},
47 | {"compare_attributes", (DL_FUNC) &ALIKEC_compare_attributes, 3},
48 | {"is_valid_name_ext", (DL_FUNC) &ALIKEC_is_valid_name_ext, 1},
49 | {"is_dfish", (DL_FUNC) &ALIKEC_is_dfish_ext, 1},
50 | {"compare_names", (DL_FUNC) &ALIKEC_compare_special_char_attrs, 2},
51 | {"compare_dimnames", (DL_FUNC) &ALIKEC_compare_dimnames_ext, 2},
52 | {"compare_class", (DL_FUNC) &ALIKEC_compare_class_ext, 2},
53 | {"compare_dims", (DL_FUNC) &ALIKEC_compare_dim_ext, 5},
54 | {"compare_ts", (DL_FUNC) &ALIKEC_compare_ts_ext, 2},
55 | {"lang_alike", (DL_FUNC) &ALIKEC_lang_alike_ext, 3},
56 | {"lang_alike_chr", (DL_FUNC) &ALIKEC_lang_alike_chr_ext, 3},
57 | {"fun_alike", (DL_FUNC) &ALIKEC_fun_alike_ext, 2},
58 | {"deparse", (DL_FUNC) &ALIKEC_deparse_ext, 2},
59 | {"deparse_oneline", (DL_FUNC) &ALIKEC_deparse_oneline_ext, 3},
60 | {"pad", (DL_FUNC) &ALIKEC_pad_ext, 3},
61 | {"pad_or_quote", (DL_FUNC) &ALIKEC_pad_or_quote_ext, 3},
62 | {"match_call", (DL_FUNC) &ALIKEC_match_call, 3},
63 | {"abstract_ts", (DL_FUNC) &ALIKEC_abstract_ts, 2},
64 | {"env_track", (DL_FUNC) &ALIKEC_env_track_test, 3},
65 | {"msg_sort", (DL_FUNC) &ALIKEC_sort_msg_ext, 1},
66 | {"msg_merge", (DL_FUNC) &ALIKEC_merge_msg_ext, 1},
67 | {"msg_merge_2", (DL_FUNC) &ALIKEC_merge_msg_2_ext, 1},
68 | {"hash_test", (DL_FUNC) &pfHashTest, 2},
69 | {"hash_test2", (DL_FUNC) &pfHashTest2, 2},
70 | {"find_fun", (DL_FUNC) &ALIKEC_findFun_ext, 2},
71 | {"list_as_sorted_vec", (DL_FUNC) &ALIKEC_list_as_sorted_vec, 1},
72 |
73 | {"len_chr_len_ext", (DL_FUNC) &CSR_len_chr_len_ext, 1},
74 | {"len_as_chr_ext", (DL_FUNC) &CSR_len_as_chr_ext, 1},
75 | {"num_as_chr", (DL_FUNC) &CSR_num_as_chr_ext, 2},
76 | {"strmlen_ext", (DL_FUNC) &CSR_strmlen_ext, 2},
77 | {"strmcpy_ext", (DL_FUNC) &CSR_strmcpy_ext, 2},
78 | {"collapse_ext", (DL_FUNC) &CSR_collapse_ext, 3},
79 | {"bullet_ext", (DL_FUNC) &CSR_bullet_ext, 4},
80 | {"strsub", (DL_FUNC) &CSR_strsub, 3},
81 | {"nchar_u", (DL_FUNC) &CSR_nchar_u, 1},
82 | {"char_offsets", (DL_FUNC) &CSR_char_offsets, 1},
83 | {"smprintf2_ext", (DL_FUNC) &CSR_smprintf2_ext, 4},
84 | {"smprintf6_ext", (DL_FUNC) &CSR_smprintf6_ext, 8},
85 | {"ucfirst_ext", (DL_FUNC) &CSR_ucfirst_ext, 2},
86 | {"lcfirst_ext", (DL_FUNC) &CSR_lcfirst_ext, 2},
87 | {"test_strmcpy", (DL_FUNC) &CSR_test_strmcpy, 0},
88 | {"test_strappend", (DL_FUNC) &CSR_test_strappend, 0},
89 | {"test_add_szt", (DL_FUNC) &CSR_test_add_szt, 0},
90 | {"test_smprintfx", (DL_FUNC) &CSR_test_smprintfx, 0},
91 | {"test_strappend2", (DL_FUNC) &CSR_test_strappend2, 0},
92 |
93 | {NULL, NULL, 0}
94 | };
95 | // Define global symbols (declarations in .h files)
96 |
97 | SEXP VALC_SYM_quote;
98 | SEXP VALC_SYM_deparse;
99 | SEXP VALC_SYM_one_dot;
100 | SEXP VALC_SYM_paren;
101 | SEXP VALC_SYM_current;
102 | SEXP VALC_SYM_errmsg;
103 | SEXP VALC_TRUE;
104 | SEXP ALIKEC_SYM_package;
105 | SEXP ALIKEC_SYM_inherits;
106 | SEXP ALIKEC_SYM_paren_open;
107 | SEXP ALIKEC_SYM_tilde;
108 | SEXP ALIKEC_SYM_args;
109 | SEXP ALIKEC_SYM_function;
110 | SEXP ALIKEC_SYM_deparse;
111 | SEXP ALIKEC_SYM_nlines;
112 | SEXP ALIKEC_SYM_widthcutoff;
113 | SEXP ALIKEC_SYM_getOption;
114 | SEXP ALIKEC_SYM_matchcall;
115 | SEXP ALIKEC_SYM_current;
116 | SEXP ALIKEC_SYM_attributes;
117 | SEXP ALIKEC_SYM_attr;
118 | SEXP ALIKEC_SYM_colnames;
119 | SEXP ALIKEC_SYM_length;
120 | SEXP ALIKEC_SYM_syntacticnames;
121 |
122 | void R_init_vetr(DllInfo *info)
123 | {
124 | /* Register the .C and .Call routines.
125 | No .Fortran() or .External() routines,
126 | so pass those arrays as NULL.
127 | */
128 | R_registerRoutines(info, NULL, callMethods, NULL, NULL);
129 | R_useDynamicSymbols(info, FALSE);
130 | R_forceSymbols(info, FALSE);
131 | VALC_SYM_quote = install("quote");
132 | VALC_SYM_deparse = install("deparse");
133 | VALC_SYM_one_dot = install(".");
134 | VALC_SYM_paren = install("(");
135 | VALC_SYM_current = install("current");
136 | VALC_SYM_errmsg = install("err.msg");
137 | VALC_TRUE = ScalarLogical(1);
138 |
139 | // Some overlap with previous since these used to be separate packages...
140 |
141 | ALIKEC_SYM_package = install("package");
142 | ALIKEC_SYM_inherits = install("inherits");
143 | ALIKEC_SYM_paren_open = install("(");
144 | ALIKEC_SYM_tilde = install("~");
145 | ALIKEC_SYM_args = install("args");
146 | ALIKEC_SYM_function = install("function");
147 | ALIKEC_SYM_deparse = install("deparse");
148 | ALIKEC_SYM_nlines = install("nlines");
149 | ALIKEC_SYM_widthcutoff = install("width.cutoff");
150 | ALIKEC_SYM_getOption = install("getOption");
151 | ALIKEC_SYM_matchcall = install("match.call");
152 | ALIKEC_SYM_current = install("current");
153 | ALIKEC_SYM_attributes = install("attributes");
154 | ALIKEC_SYM_attr = install("attr");
155 | ALIKEC_SYM_colnames = install("colnames");
156 | ALIKEC_SYM_length = install("length");
157 | ALIKEC_SYM_syntacticnames = install("syntacticnames");
158 | }
159 |
160 |
--------------------------------------------------------------------------------