Using the JavaScript Debug Interpreter

This page describes how to use my JavaScript interpreter (written in JavaScript) for debugging JavaScript programs. When you feel you are ready to look at how it works, you should study my "Introduction to Interpreters" page. Everything you need for the interpreter (including this documentation) is in this download. There are several steps:

Installing your script
Setting parameters
Understanding the output
JavaScript interpreting JavaScript

Installing your script

First you need to write a JavaScript program and embed it in a web page document, as described in the accompanying "Teach Yourself Programming" tutorial. This interpreter does not handle the whole lnaguage, but only those language features described and advised in the tutorial.

Once you have saved your program in its HTML page, run the JStest.exe program and point it to your document. It will tell you how many lines of code it converted, then quit after you click OK. Now, using a text editor like WordPad, open up the Jscript.html page containing the interpreter, which (initially) starts out something like this:

<html> <head> <title>JSinterp 10 Dec 22</title> </head> <body> <script>

var theScript = ""
+ '\r function fact(n) {'
+ '\r   if (n==0) return 1;'
+ '\r   return fact(n-1)*n;}'
+ '\r document.write("3! = "+fact(3));'
+ '\r'
+ '\r var whom = 4;'
+ '\r document.write(" Hello "+(whom+3));'
+ "";

var Logging = 2; // =0 is no log; =1 shows source function calls & returns
  // =2 shows var ass'ts & some comments; =3 interp'd flow control (=4 includes if)
  // =5 shows interpreter flow control (nonterminal entry/exit) & tokens
  // =6 is full interp trace; =7 shows extra info; >7 logs in func defn & GeTok
var LogAtLine = 0; // >0 turns on Logging when it reaches that code line
var LogAfter = 0; // >0 turns on Logging after that many tokens
var ShortLog = 0; // ditto, but for 32 tokens, then reverts to previous setting
var WatchVar = ""; // turns on Logging when this (named) var..
var WatchValue = 0; // given this value
var LogStartLog = 3; // value for Logging after one of the above trips


except of course it's not in color. Delete the text shown here in red (or whatever was left there from your previous usage) between the two blank lines, and replace it with the contents of the clipboard (use the Edit->Paste menu). Be sure to leave both blank lines there to help you know what to delete when you make the next replacement. The first and last lines pasted in will always be the same as the first and last lines shown here in red. Your actual program will be between those two lines. Save the file.

Setting parameters

There are several useful modes of operation, most of which can be set as a single numeral in the first line of the interpreter, shown in green above. Here are your choices:
0  This displays only what your browser should display, plus any error messages.

1  This gives a minimal trace of function calls and returns.
2  This adds variable assignments and some of your comments
3  This adds loop control statements like while and for;
4  This adds also if and a few more details.

5  This is a minimal trace of the interpreter itself, flow control + the tokens
6  This is a full trace of the interpreter, useful for seeing how it works;
7  This adds some more details.

Because the higher levels of logging produce vast amounts of text, there are some additional settings for restricting it to areas of interest. Many of the trace lines note the line number and the character position currently being interpreted, and a (negative) token number representing a step count. Here is the output from the original script included in the interpreter, at Logging level 2, which is what you should see when you open this page in your browser:
New fact = 1073742094 @1.9 -2
** Function fact loaded at 1.14
New n = 3 @1.14 -40
Call fact(3) @4.29 -41
New n = 2 @1.14 -61
Call fact(2) @3.15 -62
New n = 1 @1.14 -82
Call fact(1) @3.15 -83
New n = 0 @1.14 -103
Call fact(0) @3.15 -104
Return 1 from fact @2.19 -114
Return 1 from fact @3.18 -119
Return 2 from fact @3.18 -124
Return 6 from fact @3.18 -129
doc.write(3! = 6) @4.30 -132
New whom = null @6.4 -134
var whom = 4 @6.9 -137
doc.write( Hello 7) @7.9 -150
*** Got to the end *** @7.36 -151
In this script, recursive function fact() was defined on line 1 (position 14, but that's not particularly useful to you), then called four times, first from line 4 character position 29, shown as "4.29". Parameter "n" was given the value 3 before the function started. The function called itself three times recursively from line 3.15, then each of the recursive calls returns, the innermost from position 2.19, and the rest from position 3.18. The parameter values are shown on the call line, and the function result is shown on the return lines. There are two "document.write" commands executed in this run. The negative numbers are the number of source code tokens seen, 41 to the beginning of the first function call, 21 more to the recursion, and so on, for a total of 151 steps.

Each time your program creates a new variable -- in this trace, the parameter "n" for each time the function is called, and also the declared variable "whom" on line 6.4 -- Logging level 2 or higher reports the creation. When the program changes a variable by assignment or increment, that also is reported, as on line 6.9 in this trace.

If I wanted to see what is happening leading up to the first return, I might set "LogAfter = 104;" so a fuller trace would begin just after the function is called, which is ten steps before the first return.

You can start your debug run with a low number in Logging (like 1 or 2), then when it gets to a particular line, switch to a much more detailed log. Put the line number in place of the zero in the "LogAtLine = 0;" line. You can also have it start logging when a specific value has been put into a particular (named) variable. If you want a specific level of logging when your starting condition trips, set it in the "LogStartLog = 3;" line.

There are several automatic conditions that also start logging, such as when you get close to hitting the programmed recursion limit currently set at 90 ("MaxRecur = 90;"), or in case of interpreter failures (which I was looking for when I was debugging the interpreter; see JavaScript interpreting JavaScript below). This is a program, and it probably still has undiscovered bugs. I found and fixed several today (Dec.22).

Understanding the output

We have already seen some of the interpreter output. There are a variety of trace information output lines enabled at various Logging levels. Most of these are distinguished by the first two or three characters in their respective lines:
*** (message) ***
Usually when Logging is turned up, this reports various error and status messages.
Enabled at Logging level 1 or higher, when your program executes a document.write output statement. At Logging level 0 the text is just written to the page as if your program itself were running.
Call (name) (param values)
Level 1 or higher, when your program calls a function. The (param values) will be replaced by actual parameters.
Return (value) from (name)
Level 1 or higher, when your program returns from a function by a return statement. The (value) will be omitted if you did not supply it.
Exit from (name)
Level 1 or higher, when your program reaches the end of a function without executing a return statement.
New (name) = (value)
Level 2 or higher, when you create a new variable, or a function parameter is given a value. New variables are created "undefined" then (if you did so) given a value immediately.
var (name) = (value)
Level 2 or higher, when one of your variables is given a new value.
while (bool)
Level 2 or higher, when your program executes a while statement, and each time through the loop until (bool) comes out false.
for (bool)
Level 2 or higher, when your program executes a for statement, and each time through the loop until (bool) comes out false. Both the initialization of the control variable and its subsequent increment will be displayed on the previous line of the trace using a var line.
if (bool)
Level 3 or higher, when your program executes an if statement. The (bool) will be either true or false, as computed.
Level 3 or higher, when your program executes a continue statement.
Level 3 or higher, when your program executes a break statement.
+- 00: (source line) -+
Level 4 or higher. The "00" shows the line number, and the rest of the line (shown in purple here, not including the "-+" end marker) is your actual source line. This is a convenience, as it helps you know where your program is executing. If this line starts within a block comment, an additional "(in comment)" is displayed at the end of the line.
++ (token) -- 00 @ (info)
Level 5 or higher, when the interpreter begins to examine the next (token) (symbol or name or number) in your program. The (info) tells you the line and character position of this token, and the numeral 00 is a unique reference number, so I know what part of the interpreter requested a token.
. (message) (values)
Usually the display of an array of internal values at level 5 or higher, typically identified as "ExStk" (temporary value stack), "vars" (named variables and functions) and "values" (values of those variables, or an encoded return line number for the function to return to), ".Pstk" (the internal function stack), ".Istk" (the internal information stack)
.call > (name) (values)
Level 5 or higher, when the interpreter calls an internal function. In Logging level 6 or higher it also shows the function stack (values) at that point and is always followed by...
... =00 (values)
Level 6 or higher, when the interpreter calls or returns from an internal function, showing the information stack at that point.
.rtn < (name)
Level 5 or higher, when the interpreter returns from an internal function. In Logging level 6 or higher this is always followed by the information stack line.
* push (value) (values)
Level 6 or higher, when the interpreter pushes an intermediate (value) on the value stack, showing the resulting stack array.
Logging levels 1 through 4 are useful for debugging your own code. Levels 5 and higher are useful for understanding how the interpreter itself works. These upper Logging levels produce huge volumes of output, making it necessary to limit the output in various ways. One way is to delay enabling the Logging until near where you want to watch what is going on. Another method is to terminate the run when the output reaches a certain size (set by default to 99,999 characters; much bigger makes your page unwieldy for the browser, and may crash your computer). You can also set interpreter variable "StopAfter" to a positive number of tokens. It will stop after that many have been consumed.

JavaScript interpreting JavaScript interpreting JavaScript

This one is the mind-boggler. Your web browser is a computer program, which among other things knows how to interpret (run, as if it were a computer) computer programs written in JavaScript. Every time you open a web page with a script, the interpreter runs the script program.

My JavaScript interpreter is also a computer program written in JavaScript. It looks at your computer program and interprets it, that is, it pretends to be a computer running your program, all while my program is being interpreted by your browser program.

Are you ready for this?

Because my interpreter knows how to interpret JavaScript, and because it is itself written in JavaScript, it can interpret itself. We have this data problem in JavaScript, so it can't actually interpret the running code, but we can make a copy of the interpreter and pass it as data to the interpreter -- which is still being interpreted in your web browser. Just point the JStest.exe program at the interpreter file -- I suggest you use an original copy of the Jscript.html page -- then (it takes a few minutes) paste the result in instead of the nine-line sample, then set Logging = 0 in the outer interpreter (not the one you just pasted in, but the next line after it). Save and open it in your browser. Or you can use the copy I prepared, here.

Windows Internet Explorer complained to me several times that the script was taking too long. I told it to keep running (not abort). Something over a half hour later I got my output. From the log I could see it had processed more than 6 million tokens.

Can it interpret JavaScript interpreting JavaScript interpreting JavaScript interpreting JavaScript? Probably, but it might take a whole week running continuously day and night. I didn't try.

One thing you can do with the interpreter interpreting itself, is you can increase the Logging level of the outer interpreter and watch how it works. A good way to learn programming is to spend time looking at programs. This is a huge program, almost two thousand lines, so you won't understand it all at once, but it will keep you out of trouble for many days.

The best help you can get in understanding how an interpreter works is to read my book, The Art of Compiler Design (Prentice Hall, ISBN 0-13-048190-4). There are additional links here. My "Introduction to Interpreters" page is an abbreviated discussion of some of  the technology I used, but you should expect to spend a lot of your own effort looking at the code.

Tom Pittman
Rev. 2010 December 24