411 REFERENCE · Wikibooks
The Sway Reference Manual/Debugging
Reference material by Wikibooks contributors. This retains its source identity and is not 411 news reporting.
At this point in time, you should be able to write some sophisticated
Sway programs. In addition, I'm sure you are incorporating some
sophisticated bugs, as well. Such is the life of a programmer!
There are two types of errors typically encountered in programming,
syntax errors, in which the source code has problems and cannot
be compiled and executed, and semantic errors, in which, the code
runs (after a fashion) and then terminates too early or produces
the wrong result.
Human languages have syntax and semantic errors.
Here are some examples:
The box mxyskd the water.
This sentence is not valid English since token 'mxyskd' is
not a word. This is a syntactic error.
The box drank water the
This sentence is also not valid English since the article 'the' is out of
place and there is no punctuation to indicate the end of the
sentence. These are also syntactic errors.
The box drank the water.
This sentence is syntactically correct, but doesn't make sense
semantically: boxes usually don't drink.
Programming languages have errors similar to these
and we will explore how
detect and fix them in the subsequent sections.
As you learn these techniques, remember, the
number one rule of debugging is to catch errors
as early as possible. That is, find the smallest
improvement to your code that gets you a
a step closer to the final version, then implement,
test, and debug that step. Then repeat.
Woe to the student that attempts to write the whole project
before testing!
Syntax Errors
Syntax errors always look something like this:
SYNTAX ERROR: :syntaxError
file prog1.s,line 113
expecting OPEN_PARENTHESIS, found function instead
error occurred prior to: ;[END OF LINE]
A file name and line number is given by the error
report. Usually this is exactly where the problem is
but realize that this point is where the
error was detected; the problem may have started
earlier in the file.
Many of the non-obvious errors involve putting a semicolon
in the wrong place or omitting one where it is necessary.
Remember, semicolons do not follow named function definitions
and certain function calls like ifs and whiles.
In the next chapter, you will learn which function calls
need to be followed by semicolons and which do not.
If you have a strange syntax error that you cannot track down, try
commenting out pieces of the code until the error
goes away. The error is likely within the code just
commented out.
Sway has three kinds of
comments. The first is the end-of-line comment. Anything
on the line after a double slash (two consecutive forward slashes)
is ignored.
//removing the next function
//function id(x) { x; }
The second is the triple slash. This causes the rest of
the file to be ignored and is quite useful for
debugging syntax.
///removing the rest of the file
function id(x) { x; }
To use the triple slash, start with the /// at the very
top of the file so that the whole file is ignored. This
should remove the syntax error. Then keep moving the /// downward into
the file until the syntax error appears. It is likely
that the newly uncovered section of code contains the
error.
The final kind of comment is the block comment. Anything
appearing between a slash-star and a star-slash is
ignored:
/* removing this function
function id(x) { x; }
*/
Note that a block comment cannot contain another block
comment.
Semantic Errors
Syntax errors are found when the Sway interpreter reads your code,
while semantic errors are found while the interpreter
is evaluating code.
Semantic error reports look like:
EVALUATION ERROR: :mathError
file prog1.s,line 3: division: cannot divide by zero
CALL TRACE:
initial call:
prog1.s,line 5: inspect(f(3));
in <function f(x)>...
prog1.s,line 3: x / 0;
The file name and line number are given; like syntax errors,
this is where the semantic error was detected. Following
the description of the error comes a call trace. The
call trace is read from top to bottom. In the example,
the problem started on line 5 when the inspect function
was called. This call triggered a call to the function f
where the actual divide-by-zero was attempted on line 3.
The call trace is extremely useful for tracking down
problems.
Semantic errors usually occur when an important variable
ends up having a different value than you expected.
Therefore, it is important to see the values of
your variables when debugging your code.
Print statements
You should get into the habit of being able to
'visualize' the state of your program. For example,
suppose you wish to see the value of a variable
named x at a number of different points
in your program. The simplest visualization is
a print statement of the form:
println("x is ",x);
I must comment, as a teacher, how rarely students
make use of this simple tool. You should liberally use print
statements to debug your semantic errors.
It gets rather tedious to add print statements of this sort,
however,
since you are only going to delete those lines once you solve
the problem. What's needed is a faster way to add print statements
Once such faster way is the inspect function. For example, the
following two function calls are equivalent:
println("x is ",x);
inspect(x);
The output of both calls is exactly the same. If the value of x is 5,
then the output of both calls is:
x is 5
Now the amount of time saved by using inspect isn't much in this case
(a savings of typing eight characters fewer), but the savings grow when
the expression to be inspected gets complicated. Consider these calls:
println("alpha . beta(gama,delta) is ",alpha . beta(gama,delta));
inspect(alpha . beta(gama,delta));
Again, the output of each are identical,
but the savings have grown when using
inspect.
Finally, if even inspect is too much to write, define a variable
such as vv, for view variable:
var vv = inspect;
and use it instead of inspect:
vv(x);
Tracing a function call
Sometimes it is useful to trace the execution of a function
call, line by line.
Suppose you wish to call a function named f and trace each line
of the function as it executes. Simply wrap the call with statements
that set the filter component of the function.
f . filter = trace*;
y = f(x);
f . filter = :null;
or more simply, using the wrapper function trace::
y = trace(f(x));
Settng the filter to trace* (which trace does)
turns on tracing of the called function;
setting the filter to null turns off tracing. You
will need to
include the debug library at the top of your file.
For example, this program:
include("debug");
function f(x)
{
var a = x + 1;
var b = x - 1;
a * b;
}
var result = trace(f(5));
inspect(result);
yields the following output:
prog.s,line 5: var a = x + 1;
prog.s,line 6: var b = x - 1;
prog.s,line 7: a * b;
result is 24
If you set the filter to trace, each line of traced function's
body will be displayed. You will need to press the <Enter> key
to move on to the next line in the function.
If you set a function's filter to trace* instead,
your program will pause after printing out the traced statement,
as with trace.
The difference is f you type any
character other than whitespace and hit return, you will be thrown
into a miniature Sway interpreter. Here you can do nearly anything
you can do in the interactive Sway interpreter. The only difference
is that each definition or expression to be executed must be
entered on a single line.
Stepping through functions
Stepping through functions is similar to tracing, expect your
program will pause before each statement is executed. If you
simply press <Enter>, the current statement is executed,
the next statement is displayed (if it exists), and the
program pauses again.
However, if you type anything else during the pause, your input
is evaluated as a Sway expression and the result displayed.
This evaluation is performed under the environment in force
during the evaluation of the function body. In other words,
you can examine (and modify) the variables in scope during
the function call. This mini-interpreter keeps on running
until you stop entering expressions to evaluate. When you
stop entering expressions, the program advances to the
next line in the function call.
Setting up stepping through a function is similar to
tracing:
f . filter = step*;
y = f(x);
f . filter = :null;
or more simply, using the wrapper function step::
y = step(f(x));
In the interaction (as above, but using step),
the value a is examined and b is modified:
prog.s,line 5: var a = x + 1;>
prog.s,line 6: var b = x - 1;> a;
INTEGER: 6
>
prog.s,line 6: var b = x - 1;>
prog.s,line 7: a * b;> b = 10;
INTEGER: 10
> b;
INTEGER: 10
>
prog.s,line 7: a * b;>
result is 60
Note that the line presented at any given point
has not yet been executed. Thus,
we had to wait until the variable a was declared until
we could examine it. As with tracing, you must include
the debug library to use stepping.
The main difference between the mini-interpreter and the
Sway interpreter is that the expression passed to the
mini-interpreter must be entered all on one line.
You can exit the mini-interpreter by entering a <Ctrl-d>
or by entering a blank line.
Breakpoints
Sometimes, you know exactly where in a function you wish to pause
and examine variables. Rather than use step, you can call
the mini-interpreter directly by using the sway function.
include("debug");
function f(x)
{
var a = x + 1;
var b = x - 1;
println("breakpoint!");
sway(); //call the mini-interpreter
println("done.");
a * b;
}
var result = f(5);
inspect(result);
Running this program yields:
breakpoint!
sway> a;
INTEGER: 6
sway> b = 10;
INTEGER: 10;
sway>
done.
result is 60
Like before, input to the mini-interpreter must be entered
on a single line.
The sway function is built-in; thus, you do not need to include
debug.
Assertions
There are two kinds of errors you must deal with.
The first is the user of your program has supplied erroneous
input. These kinds of errors are known as external errors.
'Bulletproofing' your code means adding the logic
to deal external errors.
The second kind of errors arise from errors in the code
itself. These are known as internal errors.
A good Computer scientist anticipates that these errors
will happen no matter how good a programmer he or she is
and will use assertions to find these errors early.
For example, suppose you 'know' that the input to
a function is always a non-negative integer. You
can use an assertion to detect variations from this
constraint:
function f(x)
{
assert(x >= 0);
...
}
During the development of code, the assertions make sure
you haven't called such functions inappropriately.
When you are finished with your code, you may comment
out your assertions.
More about Functions Conditionals