The Sway Reference Manual/Debugging

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

edit

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

edit

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.

edit

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

edit

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

edit

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

edit

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

edit

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