This is part three of a three part post about JavaScript REPL console for Windows. In part one, I explained motivation and some initial steps, in part two, I added debug REPL and static breakpoints. In part three, I am going to add the ability to set breakpoints dynamically from within JSREPL.
As mentioned previously, the full source code of JSREPL is freely available under GPL 2 license on github.
Dynamic Breakpoints
While static breakpoints offer good benefits of being flexible, they are, well, static. When I am debugging a script, it is hard to predict where all the breakpoints would have to be placed. So, if I decide that I need another breakpoint in the middle of the debugging session, I need to edit the source code and restart the session.
It would be nice if I could set the breakpoints on the fly without the need to restart the session. It turns out, there is an easy way to do that for functions declared at the global
scope; either global
functions or anonymous functions referenced by global
variables. Furthermore, it is even possible to do this for anonymous functions declared within such global
functions, for example, object
methods. There are some caveats to the latter part, though.
Well then, my JSREPL is getting three new commands:
bp
– for adding new breakpoints
bd
– for deleting breakpoints
bl
– for listing breakpoints
The logic of adding a new breakpoint as well as removing one is quite simple – just figure out all the lines that the function needs to have breakpoints and generate a new string
with source code for the function. Eval
that at the global
scope and subsequent invocations of the function
will trigger breakpoints.
There is one fine point in all of this: whether function
is anonymous or not. But that is easily determined by the parseFunction
method from part two. If the function
is anonymous, then the eval
should be an assignment to a global
variable. Otherwise, it is just a function
declaration.
Figure 14. Method to update breakpoints
1 updateFnBreaks: function (bpInfo, bpLine) {
2 var lineNos = Arr(bpInfo.breaks).map(function(eBr) { return eBr.line; }).a;
3 if (typeof(bpLine) != "undefined") {
4 lineNos.push(bpLine);
5 }
6 lineNos.sort();
7
8 var fnLines = bpInfo.savedFunc.slice(0);
9 var bpLabelPrefix = bpInfo.name + "(";
10 for (iLN in lineNos) {
11 var offset = lineNos[iLN] +
parseInt(iLN);
12 if (offset < fnLines.length) {
13 var bpLabel = bpInfo.name +
"(" + lineNos[iLN].toString() + ")";
14 fnLines.splice(offset, 0 ,
"eval(dbgBreak(\"" +
bpLabel + "\")) // <dbg_break>");
15 } else {
16 println("ERROR: line ", lineNos[iLN],
" is outside of the function");
17 return null;
18 }
19 }
20 return fnLines.join("\n\r");
21 }
To illustrate the dynamic nature of the breakpoints, let’s look at another example. This time, we are dealing with class
method.
Figure 15. Source code for the example of dynamic breakpoints
1 classA = function (n) {
2 return {
3 name: n,
4 who: function () {
5 println("Name=", this.name);
6 return this.name;
7 }
8 };
9 };
In this case, I would like to set a breakpoint on line 6, but that is in the anonymous function, which happens to be a class
method. Here is how that worked out for me:
Figure 16. Session log of dynamic breakpoint demo
1 # list classA
2 0 function (n) {
3 1 return {
4 2 name: n,
5 3 who: function () {
6 4 println("Name=", this.name);
7 5 return this.name;
8 6 }
9 7 };
10 8 }
11 # bp classA 5
12 # var a1 = new classA("John")
13
14 # a1.who()
15 Name=John
16
17 dbg break – "classA(5)" ? – displays list of debugger commands.
18 dbg> w
19 Call stack:
20
21 Level Function
22 ~~~~~ ~~~~~~~~~~~~
23 0 <anonymous>()
24 dbg> a1.name
25 John
26 dbg> g
27 John
28 # bl
29 Active breakpoints:
30
31 Id Function(line)
32 ~~~~ ~~~~~~~~~~~~~~
33 0 classA(5)
34 # bd 0
35 # bl
36 Active breakpoints:
37
38 Id Function(line)
39 ~~~~ ~~~~~~~~~~~~~~
40 # a1.who()
41 Name=John
42
43 dbg break – "classA(5)" ?
– displays list of debugger commands.
44 dbg> g
45 John
46 # var a2 = new classA("Peter")
47
48 # a2.who()
49 Name=Peter
50 Peter
51 #
Setting the breakpoint (listing line 7) triggers the break into debug REPL (listing line 17). However, removing the breakpoint (listing line 43) does not remove the breakpoint from the method of object a1 (listing lines 40 – 44). However, creating a new instance of classA
after removing the breakpoint acts as expected (listing line 48).
This is it. While there are some caveats to dynamic breakpoints, it is still a very useful addition to JSREPL.
Next Steps
JSREPL is a work in progress and will be for quite some time. I am refining the functionality and addressing more corner cases as I run into those. If you use JSREPL and run into issues, please report them on the github issues page.
I already have plans for the future – I would like to add enabled/disabled flag to the breakpoint, such that the breakpoints that cannot be deleted, could at least be disabled. I would like to add an indicator of the current breakpoint in the function
source listing. The wiki on github needs updating as well.
Thank you for reading and happy debugging.