--- title: "macro" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{macro} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` The **macro** package provides macro commands and a pre-processor for R programs. The package can be useful in several ways. Before illustrating the functionality, let's first discuss the macro concept at a high level. # Macro Concept A macro language is like a meta-language for code. The meta-language runs first, generates the regular code, and then the regular code is executed to produce some sort of output. It is, at heart, a text manipulation system. The SAS® macro language is commonly used in SAS programs. The R **macro** package was inspired by SAS. Specifically, the **macro** package aims to mimic SAS macro with "mprint" and "mfile" options turned on. These options generate a resolved code file. # An R Macro Language Clearly, a macro language for R will not be the same as the SAS macro language. The SAS macro language is built into the SAS execution environment. The SAS pre-processor is run automatically, and the macro syntax is understood by the SAS syntax checker. With R, none of the above applies. R does not have a built-in macro language, does not have a built-in pre-processor, and the R syntax checker will not expect any sort of macro language syntax. Therefore, to build macro-like functionality in R, we must get a little creative. # Macro Statements The first thing to note is that macro statements in the **macro** package are written as code comments. The comments ensure that the R syntax checker will not generate an error when parsing the macro functions. In SAS, macro functions are prefixed with a percent sign (%). In R, macro statements are therefore prefixed with a comment symbol (#) and a percent sign (%). A simple macro assignment looks like this: ```{r eval=FALSE, echo=TRUE} #%let x <- 1 ``` A simple macro conditional looks like this: ```{r eval=FALSE, echo=TRUE} #%if (&x == 1) print("x is one!") #%else print("x is something else!") #%end ``` Note that the above R macro syntax is not identical to the SAS syntax. The syntax devised for the **macro** package is a blend of SAS and R syntax. # Differences Between SAS Macro and R Macro Here are some of the major difference between the SAS macro language and the R macro language: * R macro statements do not end with a semicolon (";"). They are single-line commands, and end with a carriage return. * R macro commands can be used anywhere in the program. They are not restricted to use inside macro functions. * Single-quotes will not prevent macro variable resolution. In the R macro language, single and double quotes operate the same. * All operators in the R macro language use R syntax, including the comparison operator ("=="). * R macro variables in open code must be surrounded by backticks ("`") to avoid syntax errors. Inside comments, strings, and other macro commands, they do not need backticks. Otherwise, the R macro language is similar to SAS in terms of basic functionality. ## Available Functionality The **macro** package supports the most basic functionality of a macro system. That includes: 1. **Macro Comments**: Like regular R comments, but are not emitted to the output file. 2. **Macro Variables**: Allow you to assign a value to a macro variable, and use the macro variable as a text replacement token. 3. **Macro Conditionals**: If-Then-Else logic that allows you to conditionally execute a chunk of code. 4. **Macro Include**: Inserts the contents of an external file into your program. 5. **Built-in Macro Functions**: A small number of macro functions to make the system more practical. 6. **Macro Do Loops**: Copies code inside the do block for each iteration of the loop. 7. **User-Defined Macro Functions**: The ability to create a user-defined text-replacement function, with or without parameters. The above functionality covers the majority of use cases for a macro language. Now let's see how the R macro language is actually implemented. ### SAS to R Macro Language Map The table below maps SAS macro statements to the corresponding statement in R. This table can help SAS programmers quickly understand how the **macro** package works.
| SAS | R | |
| Comment: | %* Here is a comment; | #% Here is a comment |
| Assignment: | %let x = 1; | #%let x <- 1 |
| Conditional: |
%if (&x = 1) %then %do; %put "First condition"; %end; %else %if (&x = 2) %then %do; %put "Second condition"; %end; %else %do; %put "Default condition"; %end; |
#%if (&x == 1) print("First condition") #%elseif (&x == 2) print("Second condition") #%else print("Default condition") #%end |
| Include: | %include "myfile.sas"; | #%include "myfile.R" |
| Built in Functions: |
%sysfunc() %symexist() %symput() %nrstr() |
%sysfunc() %symexist() %symput() %nrstr() |
| Do Loop: |
%do y = 1 %to 3; %put Y is &y; %end; |
#%do y = 1 %to 3 print("Y is &y") #%end |
| User-Defined Function: |
%macro test(); %put "Hello!"; %mend; %test(); |
#%macro test() print("Hello!") #%mend #%test() |
### Console Output
If you execute the `msource()` function in the console, the above program
will run and display the following:
```
> msource()
---------
library(procs)
library(macro)
# Analysis of 'mpg'
anl_mpg <- proc_means(mtcars, mpg, class = cyl,
output = report)
# Analysis of 'disp'
anl_disp <- proc_means(mtcars, disp, class = cyl,
output = report)
# Analysis of 'drat'
anl_drat <- proc_means(mtcars, drat, class = cyl,
output = report)
# Combine analysis
final <- list(anl_mpg, anl_disp, anl_drat)
# Print to console
print(final)
# Print to viewer
proc_print(final,
titles = c("Analysis of Selected MTCARS Variables",
"by cylinders"))
---------
[[1]]
CLASS VAR N MEAN STD MIN MAX
1 4 mpg 11 26.66364 4.509828 21.4 33.9
2 6 mpg 7 19.74286 1.453567 17.8 21.4
3 8 mpg 14 15.10000 2.560048 10.4 19.2
[[2]]
CLASS VAR N MEAN STD MIN MAX
1 4 disp 11 105.1364 26.87159 71.1 146.7
2 6 disp 7 183.3143 41.56246 145.0 258.0
3 8 disp 14 353.1000 67.77132 275.8 472.0
[[3]]
CLASS VAR N MEAN STD MIN MAX
1 4 drat 11 4.070909 0.3654711 3.69 4.93
2 6 drat 7 3.585714 0.4760552 2.76 3.92
3 8 drat 14 3.229286 0.3723618 2.76 4.22
```
The `msource()` console output contains two sections. The first section is the generated
code. The second section is any printed output from the generated code. In this way,
you can see what code was actually sourced, and the output of that code.
If you do not wish to see the generated code, set the "echo" parameter on
`msource()` to FALSE.
### Viewer Output
The above macro program also displayed the analysis in the RStudio viewer.
It looks like this:
See the [procs](https://procs.r-sassy.org) package documentation for more
information about the `proc_means()` and `proc_print()` functions. Both
the **macro** package and the **procs** package are part of the
[sassy](https://r-sassy.org) system of packages.
# Additional Features
## Symbol Table Functions
The functions in the macro package operate via an internal memory register
called the macro symbol table. The symbol table stores the names and values
for any macro variables, and the definition of any macro functions. The symbol
table can be accessed from regular R code using a special set of functions.
The symbol table functions are as follows:
* **`symtable()`**: Returns an object containing the macro symbol table and
macro function list. By default, this object is printed to the console.
* **`symclear()`**: Clears the macro symbol table and deletes any macro function
definitions.
* **`symget()`**: Gets a macro variable value from the macro symbol table.
* **`symput()`**: Sets the value of a macro variable.
These functions can be used to observe and manipulate the macro symbol table
from regular R code or from the console.
### Example Symtable Function
For instance, we can follow up on the `msource()` example from above by calling
the `symtable()` function in the console. Like this:
```
> symtable()
# Macro Symbol Table: 5 macro variables
Name Value
1 &dat mtcars
2 &idx 3
3 &var drat
4 &varlst anl_mpg, anl_disp, anl_drat
5 &vars c("mpg", "disp", "drat")
# Macro Function List: 1 functions
# Function '%get_means': 2 parameters
- dat
- var
```
The above console output shows the macro symbol table and the macro function
list. This information can be useful for ensuring the program ran correctly
and for debugging.
For a full discussion on the symbol table functions, see the
[Symbol Table Functions](macro-symtable.html) vignette.
## Debugging
The **macro** package provides extensive debugging capabilities. These
capabilities can be enabled by activating the "debug" parameter on the
`msource()` function. When activated, the debug information will be sent
to the console.
Let's illustrate the debug feature by running `msource()`
again for the above sample program, but this time set the "debug"
parameter to TRUE:
```
> msource(debug = TRUE)
********************************************************************************
** Pre-Processing
********************************************************************************
- File In: C:/packages/macro/tests/testthat/programs/sample16.R
- File Out: C:\Users\dbosa\AppData\Local\Temp\RtmpQhWV50/sample16.R
********************************************************************************
[ In#][Out#]:
[ 1][ 1]:
[ 2][ 2]: library(procs)
[ 3][ 3]: library(macro)
[ 4][ 4]:
[ 5][ ]: #% Assign macro variables
[ 6][ ]: #%let dat <- mtcars
[ 7][ ]: #%let vars <- c("mpg", "disp", "drat")
[ 8][ ]:
[ 9][ ]: #% Macro to run proc_means
[ 10][ ]: #%macro get_means(dat, var)
[ 17][ ]:
[ 18][ ]: #% Loop over variables
[ 19][ ]: #%do idx = 1 %to 3
[ 20][ ]: #%let idx <- 1
[ 21][ ]: #%let var <- mpg
[ 22][ ]: #%get_means(&dat, &var)
[ 23][ ]: #%let dat <- mtcars
[ 24][ ]: #%let var <- mpg
[ 25][ ]:
[ 26][ 5]: # Analysis of 'mpg'
[ 27][ 6]: anl_mpg <- proc_means(mtcars, mpg, class = cyl,
[ 28][ 7]: output = report)
[ 29][ 8]:
[ 30][ ]: #%mend get_means
[ 31][ ]: #%let idx <- 2
[ 32][ ]: #%let var <- disp
[ 33][ ]: #%get_means(&dat, &var)
[ 34][ ]: #%let dat <- mtcars
[ 35][ ]: #%let var <- disp
[ 36][ ]:
[ 37][ 9]: # Analysis of 'disp'
[ 38][ 10]: anl_disp <- proc_means(mtcars, disp, class = cyl,
[ 39][ 11]: output = report)
[ 40][ 12]:
[ 41][ ]: #%mend get_means
[ 42][ ]: #%let idx <- 3
[ 43][ ]: #%let var <- drat
[ 44][ ]: #%get_means(&dat, &var)
[ 45][ ]: #%let dat <- mtcars
[ 46][ ]: #%let var <- drat
[ 47][ ]:
[ 48][ 13]: # Analysis of 'drat'
[ 49][ 14]: anl_drat <- proc_means(mtcars, drat, class = cyl,
[ 50][ 15]: output = report)
[ 51][ 16]:
[ 52][ ]: #%mend get_means
[ 53][ ]: #% end do
[ 54][ ]:
[ 55][ ]: #% Create list of analysis datasets
[ 56][ ]: #%let varlst <- anl_mpg, anl_disp, anl_drat
[ 57][ ]:
[ 58][ 17]: # Combine analysis
[ 59][ 18]: final <- list(anl_mpg, anl_disp, anl_drat)
[ 60][ 19]:
[ 61][ 20]: # Print to console
[ 62][ 21]: print(final)
[ 63][ 22]:
[ 64][ 23]: # Print to viewer
[ 65][ 24]: proc_print(final,
[ 66][ 25]: titles = c("Analysis of Selected MTCARS Variables",
[ 67][ 26]: "by cylinders"))
[ 68][ 27]:
********************************************************************************
** Execution
********************************************************************************
> library(procs)
> library(macro)
> # Analysis of 'mpg'
> anl_mpg <- proc_means(mtcars, mpg, class = cyl,
+ output = report)
> # Analysis of 'disp'
> anl_disp <- proc_means(mtcars, disp, class = cyl,
+ output = report)
> # Analysis of 'drat'
> anl_drat <- proc_means(mtcars, drat, class = cyl,
+ output = report)
> # Combine analysis
> final <- list(anl_mpg, anl_disp, anl_drat)
> # Print to console
> print(final)
[[1]]
CLASS VAR N MEAN STD MIN MAX
1 4 mpg 11 26.66364 4.509828 21.4 33.9
2 6 mpg 7 19.74286 1.453567 17.8 21.4
3 8 mpg 14 15.10000 2.560048 10.4 19.2
[[2]]
CLASS VAR N MEAN STD MIN MAX
1 4 disp 11 105.1364 26.87159 71.1 146.7
2 6 disp 7 183.3143 41.56246 145.0 258.0
3 8 disp 14 353.1000 67.77132 275.8 472.0
[[3]]
CLASS VAR N MEAN STD MIN MAX
1 4 drat 11 4.070909 0.3654711 3.69 4.93
2 6 drat 7 3.585714 0.4760552 2.76 3.92
3 8 drat 14 3.229286 0.3723618 2.76 4.22
> # Print to viewer
> proc_print(final,
+ titles = c("Analysis of Selected MTCARS Variables",
+ "by cylinders"))
********************************************************************************
** End
********************************************************************************
```
The debug information shows how the incoming macro code was processed. For
each incoming line, it shows whether that line was emitted to the generated
code file, and how it was resolved. Any errors during pre-processing
or execution will appear at the point they occurred. In this way, it
is very easy to find which line has the error.
For a full explanation of the debugging features in the **macro** package,
see the [Debugging](macro-debug.html) vignette.
# Next Steps
Here are some more articles for further exploration:
- [Installation and Setup](macro-setup.html)
- [Macro Language Reference](macro-language.html)
- [Symbol Table Functions](macro-symtable.html)
- [Debugging](macro-debug.html)
- [Global Options](macro-globals.html)
- [Example 1: Listing](macro-example1.html)
- [Example 2: Another Listing](macro-example2.html)
- [Example 3: Table](macro-example3.html)
- [Frequently Asked Questions](macro-faq.html)