3)sqrt(x))expr() / enexpr()dbplyr as an example: R → SQLBasically, HTML tags become “ordinary” R functions
Same structure: nesting of function calls == nesting of tags.
Similar logic: unnamed arguments -> tag content, named arguments -> tag attributes.
User experience: special characters automatically escaped (e.g. "&" -> "&").
<body> is the top-level tag that contains all content.<h1> defines a top level heading.<p> defines a paragraph.<b> emboldens text.<img> embeds an image.<tag> </tag><tag /><tag name1='value1' name2='value2'></tag>Examples
& is escaped with &< is escaped with <> is escaped with >The steps…
Class constructor and dispatch
Methods that consider automatic escaping
#> <HTML> This is some text.
#> <HTML> x > 1 & y < 2
#> <HTML> This is some text. 1 > 2
#> <HTML> <hr />
<p> tag -> p() function
# Separate named and unnamed arguments
dots_partition <- function(...) {
dots <- list2(...)
if (is.null(names(dots))) {
is_named <- rep(FALSE, length(dots))
} else {
is_named <- names(dots) != ""
}
list(
named = dots[is_named], # Attributes
unnamed = dots[!is_named] # Contents
)
}
str(dots_partition(a = 1, 2, b = 3, 4))#> List of 2
#> $ named :List of 2
#> ..$ a: num 1
#> ..$ b: num 3
#> $ unnamed:List of 2
#> ..$ : num 2
#> ..$ : num 4
<p> tag via p() functionhtml_attributes()
Found among the textbook’s source code
html_attributes <- function(list) {
if (length(list) == 0) {
return("")
}
attr <- map2_chr(names(list), list, html_attribute)
paste0(" ", unlist(attr), collapse = "")
}
html_attribute <- function(name, value = NULL) {
if (length(value) == 0) {
return(name)
} # for attributes with no value
if (length(value) != 1) {
stop("`value` must be NULL or length 1")
}
if (is.logical(value)) {
# Convert T and F to true and false
value <- tolower(value)
} else {
value <- escape_attr(value)
}
paste0(name, "='", value, "'")
}
escape_attr <- function(x) {
x <- escape.character(x)
x <- gsub("\'", ''', x)
x <- gsub("\"", '"', x)
x <- gsub("\r", ' ', x)
x <- gsub("\n", ' ', x)
x
}p() function workstag <- function(tag) {
new_function(
exprs(... = ), # Capture the intended tag!
expr({
# Capture the code that contains the structure of tag functions
dots <- dots_partition(...)
attribs <- html_attributes(dots$named)
children <- map_chr(dots$unnamed, escape)
html(paste0(
!!paste0("<", tag),
attribs,
">",
paste(children, collapse = ""),
!!paste0("</", tag, ">")
))
}),
caller_env() # Get the environment of the caller frame
)
}#> function (...)
#> {
#> dots <- dots_partition(...)
#> attribs <- html_attributes(dots$named)
#> children <- map_chr(dots$unnamed, escape)
#> html(paste0("<b", attribs, ">", paste(children, collapse = ""),
#> "</b>"))
#> }
Re-run earlier example…
void_tag <- function(tag) {
new_function(
exprs(... = ),
expr({
dots <- dots_partition(...)
if (length(dots$unnamed) > 0) {
# Throws an error because void tags can't have children
abort(!!paste0("<", tag, "> must not have unnamed arguments"))
}
attribs <- html_attributes(dots$named)
html(paste0(!!paste0("<", tag), attribs, " />"))
}),
caller_env()
)
}#> function (...)
#> {
#> dots <- dots_partition(...)
#> if (length(dots$unnamed) > 0) {
#> abort("<img> must not have unnamed arguments")
#> }
#> attribs <- html_attributes(dots$named)
#> html(paste0("<img", attribs, " />"))
#> }
#> <HTML> <img src='myimage.png' width='100' height='100' />
tags <- c(
"a",
"abbr",
"address",
"article",
"aside",
"audio",
"b",
"bdi",
"bdo",
"blockquote",
"body",
"button",
"canvas",
"caption",
"cite",
"code",
"colgroup",
"data",
"datalist",
"dd",
"del",
"details",
"dfn",
"div",
"dl",
"dt",
"em",
"eventsource",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hgroup",
"html",
"i",
"iframe",
"ins",
"kbd",
"label",
"legend",
"li",
"mark",
"map",
"menu",
"meter",
"nav",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"pre",
"progress",
"q",
"ruby",
"rp",
"rt",
"s",
"samp",
"script",
"section",
"select",
"small",
"span",
"strong",
"style",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"u",
"ul",
"var",
"video"
)
void_tags <- c(
"area",
"base",
"br",
"col",
"command",
"embed",
"hr",
"img",
"input",
"keygen",
"link",
"meta",
"param",
"source",
"track",
"wbr"
)
list(), then an “environment”#> <HTML> <p class='mypara'>Some text. <b><i>some bold italic
#> text</i></b></p>
#> <HTML> <body><h1 id='first'>A heading</h1><p>Some text &<b>some
#> bold text.</b></p><img src='myimg.png' width='100' height='100'
#> /></body>
That look like this:\[\sin(\pi) + \mathrm{f}(a)\]
By doing this:
x * y, z ^ 5 = \(x * y, z^5\)\. For example, \pi = \(\pi\)\name{arg1}{arg2}. For example, fractions are \frac{a}{b} = \(\frac{a}{b}\)\textrm{f}(a * b) = \(\textrm{f}(a*b)\)Start with infrastructure (to_math()) and experiment until cover every use case.
Four stages:
Convert known symbols: pi → \pi
Leave other symbols unchanged: x → x, y → y
Convert known functions to their special forms: sqrt(frac(a, b)) → \sqrt{\frac{a}{b}}
Wrap unknown functions with \textrm: f(a) → \textrm{f}(a)
!! in order to evaluate normallyto_math()# Execution environment
to_math <- function(x) {
expr <- enexpr(x) # Capture expression (intended LaTeX)
out <- eval_bare(expr, latex_env(expr)) # Evaluate in a specific environment for this expression
latex(out)
}
# Class generator
latex <- function(x) structure(x, class = "advr_latex")
# Dispatch
print.advr_latex <- function(x) {
cat("<LATEX> ", x, "\n", sep = "")
}latex_env() is going to be created later. It depends on the expression.
greek <- c(
"alpha",
"theta",
"tau",
"beta",
"vartheta",
"pi",
"upsilon",
"gamma",
"varpi",
"phi",
"delta",
"kappa",
"rho",
"varphi",
"epsilon",
"lambda",
"varrho",
"chi",
"varepsilon",
"mu",
"sigma",
"psi",
"zeta",
"nu",
"varsigma",
"omega",
"eta",
"xi",
"Gamma",
"Lambda",
"Sigma",
"Psi",
"Delta",
"Xi",
"Upsilon",
"Omega",
"Theta",
"Pi",
"Phi"
)
greek_list <- set_names(paste0("\\", greek), greek)
greek_env <- as_environment(greek_list)Leave non-Greek symbols as-is, but we don´t know what symbols are going to be used.
#> [1] "x" "y" "a" "b" "c"
expr_type <- function(x) {
if (rlang::is_syntactic_literal(x)) {
"constant"
} else if (is.symbol(x)) {
"symbol"
} else if (is.call(x)) {
"call"
} else if (is.pairlist(x)) {
"pairlist"
} else {
typeof(x)
}
}
flat_map_chr <- function(.x, .f, ...) {
purrr::flatten_chr(purrr::map(.x, .f, ...))
}
switch_expr <- function(x, ...) {
switch(
expr_type(x),
...,
stop("Don't know how to handle type ", typeof(x), call. = FALSE)
)
}#> function (e1)
#> paste0("\\sqrt{", e1, "}")
#> function (e1, e2)
#> paste0(e1, "+", e2)
# Binary operators
f_env <- child_env(
.parent = empty_env(),
`+` = binary_op(" + "),
`-` = binary_op(" - "),
`*` = binary_op(" * "),
`/` = binary_op(" / "),
`^` = binary_op("^"),
`[` = binary_op("_"),
# Grouping
`{` = unary_op("\\left{ ", " \\right}"),
`(` = unary_op("\\left( ", " \\right)"),
paste = paste,
# Other math functions
sqrt = unary_op("\\sqrt{", "}"),
sin = unary_op("\\sin(", ")"),
log = unary_op("\\log(", ")"),
abs = unary_op("\\left| ", "\\right| "),
frac = function(a, b) {
paste0("\\frac{", a, "}{", b, "}")
},
# Labelling
hat = unary_op("\\hat{", "}"),
tilde = unary_op("\\tilde{", "}")
)#> [1] "f" "+" "d"
#> function (...)
#> {
#> contents <- paste(..., collapse = ", ")
#> paste0("\\mathrm{foo}(", contents, ")")
#> }
#> <environment: 0x0000025275b4bc10>
latex_env <- function(expr) {
calls <- all_calls(expr)
call_list <- map(set_names(calls), unknown_op)
call_env <- as_environment(call_list)
# Known functions
f_env <- env_clone(f_env, call_env)
# Default symbols
names <- all_names(expr)
symbol_env <- as_environment(set_names(names), parent = f_env)
# Known symbols
greek_env <- env_clone(greek_env, parent = symbol_env)
greek_env
}Creating an S3 for translation and automatic escaping
Creating a tag function structure
Creating a function factory for tags
Creating an execution environment: with_html()
Build an interface that contains a dynamic environment
Creating and environment of known symbols by declaring them
Creating and environment of unknown symbols by walking the AST of the expression
Creating and environment of known functions by declaring the most common ones
Creating and environment of unknown functions by walking the AST and using a function factory
Integrating all the environment in the dynamic environment contained in the to_math() interface