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 = NULL,
  file_out = NULL,
  envir = parent.frame(),
  exec = TRUE,
  debug = FALSE,
  debug_out = NULL,
  symbolgen = FALSE,
  echo = TRUE,
  clear = TRUE,
  ...
)

Arguments

pth

The path to the R program to process. This parameter is required. It will default to the currently activated program in the development environment.

file_out

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.

envir

The environment to be used for program execution, or the keyword "local". Default is the parent frame. If the parent frame is used, all variables and data initialized in the executed program will be available in the parent frame. If you do not want variables created in the parent frame, pass the quoted string "local" to have the function execute in a local environment.

exec

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.

debug

If TRUE, prints lines to the console as they are processed. This information case be useful for debugging macro code. Default is FALSE.

debug_out

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.

symbolgen

If debugger is on, this option will display the name and value of any macro variables encountered during resolution. Default is FALSE.

echo

Whether or not to echo the generated code to the console. Default is TRUE.

clear

Whether or not to clear the macro symbol table before pre-processing begins. Default is TRUE.

...

Follow-on parameters to the source function. See the source function for additional information.

Value

The results of the source() function, invisibly. The path of the resolved output file is also included under the "$output" list item.

Details

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.

How to Use

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 in RStudio, you can easily execute your macro code just by running msource() on the command line with no parameters.

In addition, the macro package registers addin menus in RStudio when installed. These addin menus can be tied to keyboard shortcuts, which can make running msource even easier. See vignette("macro-setup") to learn how to configure the keyboard shortcuts.

Macro Commands

Here is a summary of the available macro commands:

  • #%<comment>: A macro comment.

  • #%let <variable> <- <value>: Declares a macro variable and assigns it a value.

  • #%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.

  • #%do <variable> = <start> %to <end>: Defines a macro do loop block.

  • %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.

  • %symput(<expression>): Assigns the result of an expression in the execution environment to a macro variable.

  • %nrstr(<expression>): A macro quoting function that masks the enclosed expression to prevent resolution.

  • #%macro <name>(<parm1>, <parm2>, ...): Declares the start of a macro function, identifies the function name, and defines any parameters.

  • #%mend: Ends a macro function definition.

You can find extensive documentation for the above macro commands in the the Macro Language vignette. To access the vignette, run vignette("macro-language") on the R command line.

Pre-Processor

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

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.

Debugger

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').

Output File Execution

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.

If you do not wish to execute the generated code, use the exec parameter to turn off execution.

Examples


library(macro)

########################################################################
# Example 1:  Hello World Macro
########################################################################

# Get path to demo macro program
src <- system.file("extdata/Demo1.R", package = "macro")

# Display source code
# - This is the macro input code
cd <-readLines(src)
cat(paste(cd, "\n"))
# #%let a <- 1
# #%if (&a. == 1)
# print("Hello World!")
# #%else
# print("Goodbye!")
# #%end

# Macro Execute Source Code
# - Results displayed below
msource(src)
# ---------
# print("Hello World!")
# ---------
# [1] "Hello World!"

########################################################################
# Example 2:  Perform correlation analysis between variables in MTCARS
########################################################################

# Get path to demo macro program
src <- system.file("extdata/Demo2.R", package = "macro")

# Display source code
cd <- readLines(src)
cat(paste(cd, "\n"))
# #% Macro for Correlation Analysis
# #%macro get_correlation(dat, xvar, yvars)
#
# # Perform Analysis ------------------------------------
#
# #%do idx = 1 %to %sysfunc(length(&yvars))
#
# #%let yvar <- %sysfunc(&yvars[&idx])
# #%let xdat <- &dat$&xvar
# #%let ydat <- &dat$&yvar
# # Correlation between &xvar and &yvar
# anl_&yvar <- data.frame(XVAR = "&xvar",
#                         YVAR = "&yvar",
#                         COR = cor(`&xdat`, `&ydat`,
#                                   method = "pearson"))
# #%end
#
#
# # Bind Results ----------------------------------------
#
# #%let anl_lst <- %sysfunc(paste0("anl_", &yvars, collapse = ", "))
#
# # Combine all analysis data frames
# final <- rbind(`&anl_lst`)
#
# # Print Results
# print(final)
#
# #%mend
#
# #% Call Macro
# #%get_correlation(mtcars, mpg, c("cyl", "disp", "drat"))

# Macro Execute Source Code
msource(src)
# ---------
# # Perform Analysis ------------------------------------
#
#
# # Correlation between mpg and cyl
# anl_cyl <- data.frame(XVAR = "mpg",
#                       YVAR = "cyl",
#                       COR = cor(mtcars$mpg, mtcars$cyl,
#                                 method = "pearson"))
#
# # Correlation between mpg and disp
# anl_disp <- data.frame(XVAR = "mpg",
#                        YVAR = "disp",
#                        COR = cor(mtcars$mpg, mtcars$disp,
#                                  method = "pearson"))
#
# # Correlation between mpg and drat
# anl_drat <- data.frame(XVAR = "mpg",
#                        YVAR = "drat",
#                        COR = cor(mtcars$mpg, mtcars$drat,
#                                  method = "pearson"))
#
#
# # Bind Results ----------------------------------------
#
#
# # Combine all analysis data frames
# final <- rbind(anl_cyl, anl_disp, anl_drat)
#
# # Print Results
# print(final)
#
# ---------
#  XVAR YVAR        COR
# 1  mpg  cyl -0.8521620
# 2  mpg disp -0.8475514
# 3  mpg drat  0.6811719
#