The msource
function is used to pre-process and source macro-enabled
programs. The function first runs the macro pre-processor to evaluate
any macro commands. During macro evaluation, an output file is created
that contains generated code. After pre-processing, this generated code
is sourced normally.
msource(
pth = Sys.path(),
file_out = NULL,
envir = globalenv(),
exec = TRUE,
debug = FALSE,
debug_out = NULL,
...
)
The path to the R program to process. This parameter is required. It will default to the currently activated program in the development environment.
If you want to save or view the generated code
from the msource
function, supply a full path and
file name on this parameter. Default is NULL. When NULL,
the function will create a temp file for the generated code. The temp
file will be deleted when processing is complete.
The environment to be used for program execution. Default is the global environment.
Whether or not to execute the output file after pre-processing.
Default is TRUE. When FALSE, only the pre-processing step is performed.
If a file_out
parameter is supplied, the generated code file
will still be created.
If TRUE, prints lines to the console as they are processed. This information case be useful for debugging macro code. Default is FALSE.
A path to a file to be used for debugging. If a path is supplied, debug output will be written to the file instead of the console. Default is NULL.
Follow-on parameters to the source
function. See
the source
function for additional information.
The results of the source()
function, invisibly. The path
of the resolved output file is also included under the "$output" list item.
R does not have a native macro language. The macro package attempts to make up for that deficiency. The package devised a set of macro commands inspired by SAS syntax, which can be added to any R script. The macro commands are placed in R comments, prefixed by the characters "#%".
The macro commands function as pre-processor directives, and the
msource
function is the pre-processor. These commands operate as
text replacement and branching functions. They allow you to perform
high-level manipulation of your program before the code is executed.
The msource
function works very much like the Base R source
function. You pass the path to the code file as the first parameter, and
msource
will run it. The difference is that msource
will
first pre-process the file and resolve any macro commands. The resolved
code is placed by default into a temp file and then executed. If you wish
to save the generated code, supply a local path on the file_out
parameter.
The msource
function can be run on the command line or from an R script.
When run from the command line, the function will take the currently active
program in RStudio as the default input. That means if you are working
interactively in RStudio, you can easily execute your macro code just by
running msource()
on the command line with no parameters.
Here is a summary of the available macro commands:
#%<comment>: A macro comment.
#%include '<path>': Inserts code from included file as text into current program.
#%if (<condition>): Begins a macro conditional block.
#%elseif (<condition>): Defines a subsequent conditional block.
#%else: Identifies the default behavior in a condition.
#%end: Ends a macro condition.
%sysfunc(<expression>): Evaluates an R expression as part of a macro command.
%symexist(<name>): Determines if a macro variable name exists in the macro symbol table.
Note that there are no "do" loop commands in the R macro language. This functionality would be redundant to existing R looping structures, and is therefore unnecessary.
You can find extensive documentation for the above macro functions in the
the Macro Language vignette. To access the vignette, run
vignette("macro-language")
on the R command line.
There are three main steps to processing a macro-enabled program: pre-process the input file, generate an output file, and then execute the output file.
The pre-processor works by inputting each line of the program line by line. For each line in the input script, the function will assign and replace any macro variables. The pre-processor also evaluates any macro conditions. For any macro conditions that are TRUE, the pre-processor will output that line to the generated code file. If a condition evaluates as FALSE, the lines inside that block will be ignored.
In short, the pre-processor scans the input program from top to bottom, spitting out lines or not depending on the macro conditions. This logic makes the macro package perfect for code generation.
Code generation in R is most often performed using string concatenation, and writing out strings to a file. The macro package gives you a much easier way to do it. Using pre-processor directives, you can write your code as normal code. These code lines will be subject to the syntax checker, and any errors in syntax will be highlighted immediately.
The macro package also makes it easy to construct code from
code snippets. You can store your snippets in separate files, and
then pull them together using #%include
and macro logic.
The collation process is further enhanced by the macro debugger. The debugger allows you to solve issues in the macro code much faster and easier than doing string concatenation.
The msource
function has a built-in debugger. The debugger can be
very useful when identifying problems in your macro-enabled program. The
debugger can be activated by setting debug = TRUE
on your call
to msource
. When activated, the debugger will by default send
debug information to the R console. The debug information can show you
which lines made it into the output file, and how those lines resolved.
It will also echo the source call for the generated code. If an error occurs
at either of these stages, the debug information will help you pinpoint
which line produced the error.
For a full explanation of the debugger capabilities and several examples,
see the debugging vignette at vignette('macro-debug')
.
Once the output file has been generated successfully, the msource
function will execute it normally using the Base R source
function.
At this point the generated code runs like a normal R program, and any
errors or warnings will be sent to the console by default.
If you do not wish to execute the generated code, use the exec
parameter
to turn off execution.
# Create temp file names
tmp <- file.path(tempdir(), "test.R")
out <- file.path(tempdir(), "test_mod.R")
# Write macro code to temp file
cat("#%let a <- 1\n", file = tmp)
cat("#%if (a. == 1)\n", file = tmp, append = TRUE)
cat("print('a is one!')\n", file =tmp, append = TRUE)
cat("#%else\n", file = tmp, append = TRUE)
cat("print('a is something else!')\n", file = tmp, append = TRUE)
cat("#%end", file = tmp, append = TRUE)
cat("\n", file = tmp, append = TRUE)
# Process temp file
res <- msource(tmp, out)
# [1] "a is one!"
# Read output file
readLines(res$output)
# [1] "print('a is one!')"
# Rerun in debugger
msource(tmp, out, debug = TRUE)
# ********************************************************************************
# ** Pre-Processing
# ********************************************************************************
# - File In: C:\Users\dbosa\AppData\Local\Temp\RtmpyM9Kep/test.R
# - File Out: C:\Users\dbosa\AppData\Local\Temp\RtmpyM9Kep/test_mod.R
# ********************************************************************************
# [ In#][Out#]:
# [ 1][ ]: #%let a <- 1
# [ 2][ ]: #%if (a. == 1)
# [ 3][ 1]: print('a is one!')
# [ 4][ ]: #%else
# [ 5][ ]: print('a is something else!')
# [ 6][ ]: #%end
# ********************************************************************************
# ** Execution
# ********************************************************************************
#
# > print('a is one!')
# [1] "a is one!"
#
# ********************************************************************************
# ** End
# ********************************************************************************