Macro-enabling a program can make it more complicated to debug. Fortunately, there is a debug feature in the macro package that can make debugging easier.
To enhance debugging, the msource()
function contains
two parameters: debug
and debug_out
.
The debug
parameter turns debugging on or off. When set
to TRUE, the debug parameter will send debugging information to the
console. This information can be useful in helping debug the
program.
Here is a very simple macro program to illustrate the debug feature:
#%let env <- dev
#%let dev_path <- ./dev/data
#%let prod_path <- ./prod/data
# Path to data
#%if ("env." == "prod")
pth <- "prod_path./dm.sas7bdat"
#%else
pth <- "dev_path./dm.sas7bdat"
#%end
The above macro program sets the path to a SAS dataset. The code contains a macro condition to choose the “dev” or “prod” path. Then the path is constructed via supplied macro variables.
The above code is placed into an R script, and run via
msource()
on the command line. The generated output looks
like this:
# Path to data
pth <- "./dev/data/dm.sas7bdat"
Now let’s turn debugging on to examine the debug information. To turn
debugging on, simply set the debug
parameter to TRUE. Like
this:
> msource("./Debug1.R", "./Debug1_mod.R", debug = TRUE)
Normally, the msource()
function does not return
anything when executed. But when the debug
parameter is
TRUE, the following is printed to the console:
> msource("./Debug1.R", "./Debug1_mod.R", debug = TRUE)
********************************************************************************
** Pre-Processing
********************************************************************************
- File In: ./tests/testthat/examples/Debug1.R
- File Out: ./tests/testthat/examples/Debug1_mod.R
********************************************************************************
[ In#][Out#]:
[ 1][ ]: #%let env <- dev
[ 2][ ]: #%let dev_path <- ./dev/data
[ 3][ ]: #%let prod_path <- ./prod/data
[ 4][ 1]:
[ 5][ 2]: # Path to data
[ 6][ ]: #%if ("env." == "prod")
[ 7][ ]: pth <- "prod_path./dm.sas7bdat"
[ 8][ ]: #%else
[ 9][ 3]: pth <- "./dev/data/dm.sas7bdat"
[ 10][ ]: #%end
[ 11][ 4]:
********************************************************************************
** Execution
********************************************************************************
> # Path to data
> pth <- "./dev/data/dm.sas7bdat"
********************************************************************************
** End
********************************************************************************
Note the following about the above debug information:
Now let’s intentionally introduce some errors into this program, to see how they are handled in the debugger.
First, we’ll introduce a syntax error into a macro statement. On line 6 of the macro program, we’ll delete one of the parenthesis. Like this:
# Path to data
#%if ("env." == "prod"
Then we’ll run msource()
again. The debug info will then
show the following:
> msource("./Debug1.R", "./Debug1_mod.R", debug = TRUE)
********************************************************************************
** Pre-Processing
********************************************************************************
- File In: ./tests/testthat/examples/Debug1.R
- File Out: ./tests/testthat/examples/Debug1_mod.R
********************************************************************************
[ In#][Out#]:
[ 1][ ]: #%let env <- dev
[ 2][ ]: #%let dev_path <- ./dev/data
[ 3][ ]: #%let prod_path <- ./prod/data
[ 4][ 1]:
[ 5][ 2]: # Path to data
Error in str2expression(trimws(nr)) : <text>:2:0: unexpected end of input
1: ("dev" == "prod"
^
Observe that the pre-processor stopped at the moment the syntax error is encountered on line 6. The error encountered is displayed in the debug info, and the error message indicates that there is an unmatched parenthesis.
#%end
The next error we will introduce is a missing #%end
in
the macro condition. To introduce this error, remove the
#%end
on line 10 from the original script. Then rerun
msource()
. The debug info will display the following:
> msource("./Debug1.R", "./Debug1_mod.R", debug = TRUE)
********************************************************************************
** Pre-Processing
********************************************************************************
- File In: ./tests/testthat/examples/Debug1.R
- File Out: ./tests/testthat/examples/Debug1_mod.R
********************************************************************************
[ In#][Out#]:
[ 1][ ]: #%let env <- dev
[ 2][ ]: #%let dev_path <- ./dev/data
[ 3][ ]: #%let prod_path <- ./prod/data
[ 4][ 1]:
[ 5][ 2]: # Path to data
[ 6][ ]: #%if ("env." == "prod")
[ 7][ ]: pth <- "prod_path./dm.sas7bdat"
[ 8][ ]: #%else
[ 9][ 3]: pth <- "./dev/data/dm.sas7bdat"
[ 10][ ]:
[ 11][ 4]:
Error in mprocess(lns, debug) :
End of file reached with conditional block incomplete. Did you forget an '#%end'?
#%end
In the next example, we’ll add an extra #%end
instead of
removing it. Like this:
#%let env <- dev
#%let dev_path <- ./dev/data
#%let prod_path <- ./prod/data
# Path to data
#%if ("env." == "prod")
pth <- "prod_path./dm.sas7bdat"
#%else
pth <- "dev_path./dm.sas7bdat"
#%end
#%end
Here is the result:
> msource("./Debug1.R", "./Debug1_mod.R", debug = TRUE)
********************************************************************************
** Pre-Processing
********************************************************************************
- File In: ./tests/testthat/examples/Debug1.R
- File Out: ./tests/testthat/examples/Debug1_mod.R
********************************************************************************
[ In#][Out#]:
[ 1][ ]: #%let env <- dev
[ 2][ ]: #%let dev_path <- ./dev/data
[ 3][ ]: #%let prod_path <- ./prod/data
[ 4][ 1]:
[ 5][ 2]: # Path to data
[ 6][ ]: #%if ("env." == "prod")
[ 7][ ]: pth <- "prod_path./dm.sas7bdat"
[ 8][ ]: #%else
[ 9][ 3]: pth <- "./dev/data/dm.sas7bdat"
[ 10][ ]: #%end
Error in mprocess(lns, debug) : Unexpected '#%end' on line 11.
The debug info shows us that it encountered an unexpected macro command, and identifies the line number. This information is very useful when debugging your macro code.
What happens when there is an error in the generated code? Let’s introduce another error and find out.
For this example, we will put an extra space in the assignment arrow on line 9. Like this:
#%let env <- dev
#%let dev_path <- ./dev/data
#%let prod_path <- ./prod/data
# Path to data
#%if ("env." == "prod")
pth <- "prod_path./dm.sas7bdat"
#%else
pth < - "dev_path./dm.sas7bdat"
#%end
The above extra space should introduce a syntax error in the generated code. Here is what it does:
> msource("./Debug1.R", "./Debug1_mod.R", debug = TRUE)
********************************************************************************
** Pre-Processing
********************************************************************************
- File In: ./tests/testthat/examples/Debug1.R
- File Out: ./tests/testthat/examples/Debug1_mod.R
********************************************************************************
[ In#][Out#]:
[ 1][ ]: #%let env <- dev
[ 2][ ]: #%let dev_path <- ./dev/data
[ 3][ ]: #%let prod_path <- ./prod/data
[ 4][ 1]:
[ 5][ 2]: # Path to data
[ 6][ ]: #%if ("env." == "prod")
[ 7][ ]: pth <- "prod_path./dm.sas7bdat"
[ 8][ ]: #%else
[ 9][ 3]: pth < - "./dev/data/dm.sas7bdat"
[ 10][ ]: #%end
********************************************************************************
** Execution
********************************************************************************
> # Path to data
> pth < - "./dev/data/dm.sas7bdat"
Error in -"./dev/data/dm.sas7bdat" : invalid argument to unary operator
Notice that the pre-processing stage completed as normal, and the execution stage began. However, at the moment the syntax error was encountered in the generated code, an error message was produced. This printout should give you a clear indication of which line has a problem and how to fix it.
If desired, the debug information can be sent to a file instead of the console. This capability is especially valuable for large programs, where the number of lines may exceed the capacity of the console to display the full debug information.
To send the debug information to a file, simply assign a path to the
debug_out
parameter on msource()
:
> msource("./Debug1.R", "./Debug1_mod.R", debug = TRUE, debug_out = "./log/Debug1.txt")
The debug information will now be redirected to the debug file instead of the console. Here is the contents of the debug file:
********************************************************************************
** Pre-Processing
********************************************************************************
- File In: ./tests/testthat/examples/Debug1.R
- File Out: ./tests/testthat/examples/Debug1_mod.R
********************************************************************************
[ In#][Out#]:
[ 1][ ]: #%let env <- dev
[ 2][ ]: #%let dev_path <- ./dev/data
[ 3][ ]: #%let prod_path <- ./prod/data
[ 4][ 1]:
[ 5][ 2]: # Path to data
[ 6][ ]: #%if ("env." == "prod")
[ 7][ ]: pth <- "prod_path./dm.sas7bdat"
[ 8][ ]: #%else
[ 9][ 3]: pth <- "./dev/data/dm.sas7bdat"
[ 10][ ]: #%end
********************************************************************************
** Execution
********************************************************************************
> # Path to data
> pth <- "./dev/data/dm.sas7bdat"
********************************************************************************
** End
********************************************************************************
Observe that the information contained in the file is exactly the same information shown on the console in the examples above.
Saving the debug information to a file has some benefits:
Therefore, the debug output file can be advantageous in some scenarios.
Next: Example 1: Listing