-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathenv.r
257 lines (224 loc) · 8.27 KB
/
env.r
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#' Module namespace handling
#'
#' \code{make_namespace} creates a new module namespace.
#' @param info the module info.
#' @return \code{make_namespace} returns the newly created module namespace for
#' the module described by \code{info}.
#' @details
#' The namespace contains a module’s content. This schema is very much like R
#' package organisation. A good resource for this is:
#' <http://obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/>
#' @name namespace
#' @keywords internal
make_namespace = function (info) {
# Packages use `baseenv()` instead of `emptyenv()` for the parent
# environment of `.__NAMESPACE__.`. I don’t know why: there should never be
# any need for inherited name lookup. We’re only using an environment for
# `.__module__.` to get efficient name lookup and a mutable value store.
ns_attr = new.env(parent = emptyenv())
ns_attr$info = info
ns_env = new.env(parent = make_imports_env(info))
# FIXME: Why not use `.__NAMESPACE__.` here?
ns_env$.__module__. = ns_attr
# TODO: Set exports here!
enable_s3_lookup(ns_env, info)
structure(ns_env, name = paste0('namespace:', info$name), class = 'box$ns')
}
enable_s3_lookup = function (ns_env, info) {
ns_env$.packageName = info$name
# TODO: Create S3 methods table
}
import_decl = function (ns, spec, info) {
structure(list(ns = ns, spec = spec, info = info), class = 'box$import_decl')
}
make_imports_env = function (info) {
structure(
new.env(parent = import_env_parent),
name = paste0('imports:', info$name),
class = 'box$imports'
)
}
legacy_warn_msg = c(
'Using {call;"} inside a module may cause issues; see the FAQ at ',
'`{call("vignette", "faq", package = "box")}` for details.'
)
box_library = function (...) {
warning(fmt(legacy_warn_msg, call = 'library'))
eval.parent(`[[<-`(match.call(), 1L, library))
}
box_require = function (...) {
warning(fmt(legacy_warn_msg, call = 'require'))
eval.parent(`[[<-`(match.call(), 1L, require))
}
box_source = function (file, local = FALSE, ...) {
if (is.logical(local) && ! local) {
warning(fmt(legacy_warn_msg, call = 'source'))
}
eval.parent(`[[<-`(match.call(), 1L, source))
}
legacy_intercept_env = list2env(
list(
library = box_library,
require = box_require,
source = box_source
),
parent = baseenv()
)
#' \code{is_namespace} checks whether a given environment corresponds to a
#' module namespace.
#' @param env an environment that may be a module namespace.
#' @rdname namespace
is_namespace = function (env) {
exists('.__module__.', env, mode = 'environment', inherits = FALSE)
}
#' @param ns the module namespace environment.
#' @param which the key (as a length 1 character string) of the info to get/set.
#' @param default default value to use if the key is not set.
#' @rdname namespace
namespace_info = function (ns, which, default = NULL) {
get0(which, ns$.__module__., inherits = FALSE, ifnotfound = default)
}
#' @param value the value to assign to the specified key.
#' @rdname namespace
`namespace_info<-` = function (ns, which, value) {
assign(which, value, envir = ns$.__module__.)
ns
}
#' Get a module’s name
#'
#' @usage \special{box::name()}
#' @return \code{box::name} returns a character string containing the name of
#' the module, or \code{NULL} if called from outside a module.
#' @note Because this function returns \code{NULL} if not invoked inside a
#' module, the function can be used to check whether a code is being imported as
#' a module or called directly.
#' @export
name = function () {
mod_ns = current_mod()
if (is_namespace(mod_ns)) namespace_info(mod_ns, 'info')$name
}
# FIXME: Export?
current_mod = function (env = parent.frame(2L)) {
mod_topenv(env)
}
#' \code{mod_topenv} is the same as \code{topenv} for module namespaces.
#' @name namespace
mod_topenv = function (env = parent.frame()) {
while (! is_mod_topenv(env)) env = parent.env(env)
env
}
#' \code{is_mod_topenv} returns \code{TRUE} if \code{env} is a top level
#' environment.
#' @name namespace
is_mod_topenv = function (env) {
is_namespace(env) || identical(env, topenv(env)) || identical(env, emptyenv())
}
#' @keywords internal
make_export_env = function (info, spec, ns) {
structure(
new.env(parent = emptyenv()),
name = paste0('mod:', spec_name(spec)),
class = 'box$mod',
spec = spec,
info = info,
namespace = ns
)
}
strict_extract = function (e1, e2) {
# Implemented in C since this function is called very frequently and needs
# to be fast, and the C implementation is about 270% faster than an R
# implementation based on `get`, and provides more readable error messages.
# In fact, the fastest code that manages to provide a readable error message
# that contains the actual call ("foo$bar") rather than only mentioning the
# `get` function call, is more than 350% slower.
.Call(c_strict_extract, e1, e2)
}
#' @export
`$.box$mod` = strict_extract
#' @export
`$.box$ns` = strict_extract
#' @export
`print.box$mod` = function (x, ...) {
spec = attr(x, 'spec')
type = if (inherits(spec, 'pkg_spec')) 'package' else 'module'
cat(fmt('<{type}: {spec_name(spec)}>\n'))
invisible(x)
}
unlock_environment = function (env) {
invisible(.Call(c_unlock_env, env))
}
find_import_env = function (x, spec, info, mod_ns) {
UseMethod('find_import_env')
}
`find_import_env.box$ns` = function (x, spec, info, mod_ns) {
parent.env(x)
}
`find_import_env.box$mod` = function (x, spec, info, mod_ns) {
x
}
find_import_env.environment = function (x, spec, info, mod_ns) {
env = if (identical(x, .GlobalEnv)) {
# We need to use `attach` here: attempting to set
# `parent.env(.GlobalEnv)` causes R to segfault.
box_attach(NULL, name = paste0('mod:', spec_name(spec)))
} else {
parent.env(x) = new.env(parent = parent.env(x))
}
structure(env, class = 'box$mod', spec = spec, info = info, namespace = mod_ns)
}
import_into_env = function (to_env, to_names, from_env, from_names) {
for (i in seq_along(to_names)) {
if (
exists(from_names[i], from_env, inherits = FALSE) &&
bindingIsActive(from_names[i], from_env) &&
! inherits((fun = active_binding_function(from_names[i], from_env)), 'box$placeholder')
) {
makeActiveBinding(to_names[i], fun, to_env)
} else {
assign(to_names[i], env_get(from_env, from_names[i]), envir = to_env)
}
}
}
env_get = function (env, name) {
UseMethod('env_get')
}
# Method for package namespace environments. This distinction is necessary since
# lazydata in packages can’t be loaded via `get`.
env_get.environment = function (env, name) {
getExportedValue(env, name)
}
`env_get.box$mod` =
`env_get.box$ns` = function (env, name) {
# Explicitly allow inherited values, which is used to support re-exporting
# imports in modules.
get(name, envir = env)
}
active_binding_function = if (as.integer(version$major) >= 4L) {
function (sym, env) activeBindingFunction(sym, env)
} else {
function (sym, env) {
as.list(`class<-`(env, NULL), all.names = TRUE)[[sym]]
}
}
#' Wrap \dQuote{unsafe calls} functions
#'
#' \code{wrap_unsafe_function} declares a function wrapper to a function that
#' causes an \command{R CMD check} NOTE when called directly. We should usually
#' not call these functions, but we need some of them because we want to
#' explicitly support features they provide.
#' @param ns The namespace of the unsafe function.
#' @param name The name of the unsafe function.
#' @return \code{wrap_unsafe_calls} returns a wrapper function with the same
#' argument as the wrapped function that can be called without causing a NOTE.
#' @note Using an implementation that simply aliases \code{getExportedValue}
#' does not work, since \command{R CMD check} sees right through this
#' \dQuote{ruse}.
#' @keywords internal
wrap_unsafe_function = function (ns, name) {
f = getExportedValue(ns, name)
wrapper = function (...) eval.parent(`[[<-`(match.call(), 1L, f))
formals(wrapper) = formals(f)
wrapper
}
box_attach = wrap_unsafe_function(.BaseNamespaceEnv, 'attach')
box_unlock_binding = wrap_unsafe_function(.BaseNamespaceEnv, 'unlockBinding')