001: /*
002: * @(#)TerminalDebugger.java 1.2 04/12/06
003: *
004: * Copyright (c) 1997-2004 Sun Microsystems, Inc. All Rights Reserved.
005: *
006: * See the file "LICENSE.txt" for information on usage and redistribution
007: * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
008: */
009: package pnuts.tools;
010:
011: import java.io.BufferedReader;
012: import java.io.File;
013: import java.io.IOException;
014: import java.io.InputStreamReader;
015: import java.io.PrintWriter;
016: import java.io.Reader;
017: import java.net.URL;
018: import java.util.Enumeration;
019: import java.util.Hashtable;
020: import java.util.MissingResourceException;
021: import java.util.ResourceBundle;
022: import java.util.Vector;
023:
024: import pnuts.lang.Context;
025: import pnuts.lang.Function;
026: import pnuts.lang.Pnuts;
027: import pnuts.lang.Runtime;
028: import pnuts.lang.SimpleNode;
029: import pnuts.lang.PnutsParserTreeConstants;
030:
031: /**
032: * This class implements a debugger for Pnuts interpreter.
033: * It is used through <tt>pnuts -d</tt> command.
034: * <pre>
035: * Commands:
036: * stop at [FILE:]LINENO
037: * Stop execution at the LINENO
038: * stop in FUNC[:NARGS]
039: * Stop execution when FUNC is called.
040: * When NARGS is specified, stop when FUNC with NARGS is called.
041: * clear
042: * Clear all breakpoints
043: * cont
044: * Continue execution
045: * trace
046: * Toggle trace mode
047: * trace function [FUNC]
048: * Toggle function call trace mode
049: * step [NUM]
050: * Single step NUM lines. The default number is 1.
051: * step up
052: * Step out of the current function
053: * next [NUM]
054: * Step NUM line (step OVER calls). The default number is 1.
055: * help
056: * Print a summary of commands
057: * ?
058: * Same as help.
059: * </pre>
060: */
061: public class TerminalDebugger implements Debugger, ContextFactory {
062: private static final boolean DEBUG = false;
063:
064: private Hashtable bpt_functions;
065: private Hashtable bpt_files;
066: private BufferedReader reader;
067: private boolean initialized = false;
068:
069: private boolean step = false;
070: private boolean step_up = false;
071: private boolean next = false;
072: private int nsteps;
073: private int nnexts;
074: private int c_depth;
075: private int initialEvalDepth;
076: private int initialCallDepth;
077: private int e_depth;
078: private Object file;
079: private int line;
080: private boolean trace_lines;
081: private boolean trace_all_functions;
082: private Hashtable trace_functions;
083: private boolean interactive;
084:
085: private boolean session = false;
086:
087: private static String indent = " >>> ";
088:
089: public TerminalDebugger() {
090: this (new InputStreamReader(System.in));
091: interactive = true;
092: }
093:
094: /**
095: * @param reader debug script to read in
096: */
097: public TerminalDebugger(Reader reader) {
098: initialized = false;
099: interactive = false;
100: if (reader instanceof BufferedReader) {
101: this .reader = (BufferedReader) reader;
102: } else {
103: this .reader = new BufferedReader(reader);
104: }
105: }
106:
107: public Context createContext() {
108: DebugContext dc = new DebugContext();
109: dc.addCommandListener(this );
110: return dc;
111: }
112:
113: void init(DebugContext dc) {
114: dc.setDebugger(this );
115: initialEvalDepth = Pnuts.evalDepth(dc);
116: nsteps = 1;
117: nnexts = 1;
118: c_depth = 0;
119: e_depth = 0;
120: file = null;
121: line = 0;
122: trace_lines = false;
123: trace_all_functions = false;
124: trace_functions = new Hashtable();
125: bpt_functions = new Hashtable();
126: bpt_files = new Hashtable();
127: }
128:
129: /**
130: * @param reader The target script to be tested
131: */
132: void setInput(BufferedReader reader) {
133: this .reader = reader;
134: }
135:
136: /**
137: * @see pnuts.tools.CommandListener
138: */
139: void exit(CommandEvent event) {
140: if (session) {
141: return;
142: }
143: DebugContext dc = (DebugContext) event.getSource();
144: if (DEBUG) {
145: System.out.println("depth = " + dc.getEvalDepth()
146: + ", initial depth = " + initialEvalDepth);
147: }
148: if (dc.getEvalDepth() <= initialEvalDepth
149: && dc.getCallDepth() <= initialCallDepth) {
150: if (DEBUG) {
151: System.out.println("initialCallDepth = "
152: + initialCallDepth);
153: System.out.println("callDepth = " + dc.getCallDepth());
154: }
155: PrintWriter term = dc.getTerminalWriter();
156: term.println("# Returns " + Pnuts.format(event.getArg()));
157: term.flush();
158: initialized = false;
159: }
160: }
161:
162: /**
163: * Sets a breakpoint at the specified position
164: *
165: * @param file the script file
166: * @param lineno the line number
167: */
168: public void setBreakPoint(Object file, int lineno) {
169: if (DEBUG) {
170: System.out.println("setBreakPoint(" + file + ", " + lineno
171: + ")");
172: }
173: if (file == null) {
174: return;
175: }
176: Vector lines = (Vector) bpt_files.get(file);
177: if (lines == null) {
178: lines = new Vector();
179: bpt_files.put(file, lines);
180: }
181: Integer i = new Integer(lineno);
182: if (!lines.contains(i)) {
183: lines.addElement(i);
184: }
185: }
186:
187: Vector getBreakPoints(Object file) {
188: if (file instanceof URL) {
189: URL url = (URL) file;
190: String f = url.getFile();
191: for (Enumeration e = bpt_files.keys(); e.hasMoreElements();) {
192: String key = (String) e.nextElement();
193: if (f.endsWith(key)) {
194: return (Vector) bpt_files.get(key);
195: }
196: }
197: } else if (file instanceof File) {
198: String f = ((File) file).getPath();
199: for (Enumeration e = bpt_files.keys(); e.hasMoreElements();) {
200: String key = (String) e.nextElement();
201: if (f.endsWith(key)) {
202: return (Vector) bpt_files.get(key);
203: }
204: }
205: }
206: return null;
207: }
208:
209: public void setBreakPointInFunction(String func_name) {
210: if (bpt_functions.get(func_name) == null) {
211: bpt_functions.put(func_name, func_name);
212: }
213: }
214:
215: public void setBreakPointInFunction(String func_name, int nargs) {
216: String key = func_name + ":" + nargs;
217: if (bpt_functions.get(key) == null) {
218: bpt_functions.put(key, key);
219: }
220: }
221:
222: public void removeBreakPoint(Object source, int lineno) {
223: if (source == null) {
224: return;
225: }
226: Vector lines = (Vector) bpt_files.get(source);
227: if (lines == null) {
228: return;
229: }
230: lines.removeElement(new Integer(lineno + 1));
231: }
232:
233: public void clearBreakPoints() {
234: bpt_functions.clear();
235: bpt_files.clear();
236: }
237:
238: SimpleNode getTopNode(SimpleNode node) {
239: while (node != null
240: && node.id != PnutsParserTreeConstants.JJTEXPRESSIONLIST) {
241: SimpleNode parent = node.jjtGetParent();
242: if (parent == null
243: || parent.id == PnutsParserTreeConstants.JJTBLOCK) {
244: break;
245: }
246: node = parent;
247: }
248: return node;
249: }
250:
251: public void signal(CommandEvent event) {
252: DebugContext dc = (DebugContext) event.getSource();
253: int eventType = event.getType();
254: if (eventType == CommandEvent.EXITED) {
255: exit(event);
256: return;
257: } else if (eventType == CommandEvent.EXCEPTION) {
258: initialized = false;
259: return;
260: } else if (eventType == CommandEvent.OPEN_FRAME) {
261: Object[] a = (Object[]) event.getArg();
262: Function f = (Function) a[0];
263: String fname = f.getName();
264: Object[] args = (Object[]) a[1];
265: if (trace_all_functions
266: || (fname != null && trace_functions.get(fname) != null)) {
267: int depth = dc.getCallDepth();
268: for (int i = 0; i < depth; i++) {
269: System.err.print(' ');
270: }
271: String param = Pnuts.format(args);
272: param = param.substring(1, param.length() - 1);
273: System.err.println(fname + "(" + param + ") =>");
274: }
275: } else if (eventType == CommandEvent.CLOSE_FRAME) {
276: Object[] a = (Object[]) event.getArg();
277: Function f = (Function) a[0];
278: String fname = f.getName();
279: Object[] args = (Object[]) a[1];
280: if (trace_all_functions
281: || (fname != null && trace_functions.get(fname) != null)) {
282: int depth = dc.getCallDepth();
283: for (int i = 0; i < depth; i++) {
284: System.err.print(' ');
285: }
286: String param = Pnuts.format(args);
287: param = param.substring(1, param.length() - 1);
288: System.err.println(fname + "(" + param + ") <=");
289: }
290: } else {
291: SimpleNode node = (SimpleNode) event.getArg();
292: if (node != null) {
293: lineUpdated(dc, node);
294: }
295: }
296: }
297:
298: void lineUpdated(DebugContext dc, SimpleNode node) {
299: if (session) {
300: return;
301: }
302:
303: PrintWriter term = dc.getTerminalWriter();
304: int beginLine = dc.getBeginLine();
305: int endLine = dc.getEndLine();
306:
307: if (!initialized) {
308: init(dc);
309: initialCallDepth = dc.getCallDepth();
310:
311: Object f = dc.getScriptSource();
312: if (f == null) {
313: f = "?";
314: }
315: term.println("# Stopped at " + f + ":" + beginLine);
316: SimpleNode n = getTopNode(node);
317: if (n != null) {
318: term.print(indent);
319: term.println(Runtime.unparse(n, dc));
320: }
321: c_depth = dc.getCallDepth();
322: e_depth = dc.getEvalDepth();
323: this .file = dc.getScriptSource();
324: this .line = dc.getBeginLine();
325: initialized = true;
326: session(dc);
327: return;
328: }
329: if (step) {
330: if (this .file != dc.getScriptSource()
331: || this .line != beginLine
332: || c_depth != dc.getCallDepth()) {
333:
334: this .line = beginLine;
335: this .file = dc.getScriptSource();
336: c_depth = dc.getCallDepth();
337:
338: if (--nsteps < 1) {
339: Object f = file;
340: if (f == null) {
341: f = "?";
342: }
343: term.println("# Stopped at " + f + ":" + beginLine);
344: SimpleNode n = getTopNode(node);
345: if (n != null) {
346: term.print(indent);
347: term.println(Runtime.unparse(n, dc));
348: }
349: session(dc);
350: } else if (trace_lines) {
351: term.print(file + ":" + line + indent);
352: term.println(Runtime.unparse(node, dc));
353: term.flush();
354: }
355: }
356: } else if (step_up) {
357: if (e_depth > dc.getEvalDepth()
358: || e_depth >= dc.getEvalDepth()
359: && c_depth > dc.getCallDepth()) {
360:
361: this .line = beginLine;
362: this .file = dc.getScriptSource();
363: this .c_depth = dc.getCallDepth();
364:
365: Object f = this .file;
366: if (f == null) {
367: f = "?";
368: }
369: term.println("# Stopped at " + f + ":" + beginLine);
370:
371: SimpleNode n = getTopNode(node);
372: if (n != null) {
373: term.print(indent);
374: term.println(Runtime.unparse(n, dc));
375: }
376: session(dc);
377: } else if (trace_lines) {
378: term.print(file + ":" + line + indent);
379: term.println(Runtime.unparse(node, dc));
380: term.flush();
381: }
382: } else if (next && e_depth >= dc.getEvalDepth()
383: && c_depth >= dc.getCallDepth()) {
384: if (this .file != dc.getScriptSource()
385: || this .line != beginLine
386: || c_depth != dc.getCallDepth()) {
387:
388: this .line = beginLine;
389: this .file = dc.getScriptSource();
390: this .c_depth = dc.getCallDepth();
391:
392: if (--nnexts < 1) {
393: Object f = this .file;
394: if (f == null) {
395: f = "?";
396: }
397: term.println("# Stopped at " + f + ":" + beginLine);
398: SimpleNode n = getTopNode(node);
399: if (n != null) {
400: term.print(indent);
401: term.println(Runtime.unparse(n, dc));
402: }
403: session(dc);
404: }
405: } else if (trace_lines) {
406: term.print(file + ":" + line + indent);
407: term.println(Runtime.unparse(node, dc));
408: term.flush();
409: }
410: } else {
411: Object file = dc.getScriptSource();
412: if (trace_lines) {
413: term.print(file + ":" + beginLine + indent);
414: term.println(Runtime.unparse(node, dc));
415: term.flush();
416: }
417: if (file != null) {
418: Vector lines = getBreakPoints(file);
419:
420: if (lines != null
421: && checkBreakPoint(lines, node.id, beginLine,
422: endLine)
423: && (line != beginLine || c_depth != dc
424: .getCallDepth())) {
425: this .line = beginLine;
426: this .file = dc.getScriptSource();
427: this .c_depth = dc.getCallDepth();
428: Object f = this .file;
429: if (f == null) {
430: f = "?";
431: }
432: term.println("# Stopped at " + f + ":" + beginLine);
433: SimpleNode n = getTopNode(node);
434: if (n != null) {
435: term.print(indent);
436: term.println(Runtime.unparse(n, dc));
437: }
438: session(dc);
439: return;
440: }
441: }
442: if (node.id == PnutsParserTreeConstants.JJTAPPLICATIONNODE) {
443: String name = node.jjtGetChild(0).str;
444: if (name != null && bpt_functions.get(name) != null) {
445: Object f = this .file;
446: if (f == null) {
447: f = "?";
448: }
449: term.println("# Stopped at " + f + ":" + beginLine);
450: SimpleNode n = getTopNode(node);
451: if (n != null) {
452: term.print(indent);
453: term.println(Runtime.unparse(n, dc));
454: }
455: session(dc);
456: }
457: }
458: }
459: }
460:
461: boolean checkBreakPoint(Vector lines, int nodeID, int begin, int end) {
462: if (DEBUG) {
463: System.out.println("checkBreakPoint(" + begin + ", " + end
464: + ")");
465: }
466: if (nodeID == PnutsParserTreeConstants.JJTBLOCK) {
467: return false;
468: }
469: for (Enumeration e = lines.elements(); e.hasMoreElements();) {
470: int bp = ((Integer) e.nextElement()).intValue();
471: if (bp >= begin && bp <= end) {
472: return true;
473: }
474: }
475: return false;
476: }
477:
478: String getCommand() {
479: String cmd = null;
480: try {
481: cmd = reader.readLine();
482: } catch (IOException e) {
483: }
484: return cmd;
485: }
486:
487: void session(DebugContext context) {
488: step = false;
489: step_up = false;
490: next = false;
491:
492: while (true) {
493:
494: file = context.getScriptSource();
495: c_depth = context.getCallDepth();
496: e_depth = context.getEvalDepth();
497:
498: PrintWriter term = context.getTerminalWriter();
499:
500: if (interactive) {
501: term.print("debug> ");
502: term.flush();
503: }
504:
505: String cmd = getCommand();
506: if (!interactive) {
507: term.println("debug> " + cmd);
508: }
509:
510: if (cmd == null) {
511: break;
512: }
513: int offset = 0;
514: ;
515: if ((offset = cmd.indexOf("stop at ")) >= 0) {
516: String arg = cmd
517: .substring(offset + "stop at ".length()).trim();
518: int idx = arg.indexOf(':');
519: if (idx < 0) {
520: Object f = context.getScriptSource();
521: String s = null;
522: if (f instanceof URL) {
523: s = ((URL) f).getFile();
524: } else if (f instanceof File) {
525: s = ((File) f).getPath();
526: } else if (f instanceof Runtime) {
527: s = f.getClass().getName();
528: }
529: setBreakPoint(s, Integer.parseInt(arg));
530: } else {
531: setBreakPoint(arg.substring(0, idx), Integer
532: .parseInt(arg.substring(idx + 1)));
533: }
534: } else if ((offset = cmd.indexOf("stop in ")) >= 0) {
535: String arg = cmd
536: .substring(offset + "stop at ".length()).trim();
537: int idx = arg.indexOf(':');
538: if (idx < 0) {
539: setBreakPointInFunction(arg);
540: } else {
541: setBreakPointInFunction(arg.substring(0, idx),
542: Integer.parseInt(arg.substring(idx + 1)));
543: }
544: } else if ("help".equals(cmd) || "?".equals(cmd)) {
545: try {
546: ResourceBundle rb = ResourceBundle
547: .getBundle("pnuts.tools.debug");
548: String help = rb.getString("pnuts.debug.help");
549: term.println(help);
550: } catch (MissingResourceException mis) {
551: mis.printStackTrace(term);
552: }
553: } else if ((offset = cmd.indexOf("step")) >= 0) {
554: String arg = cmd.substring(offset + "step".length())
555: .trim();
556: if (arg.indexOf("up") >= 0) {
557: file = context.getScriptSource();
558: c_depth = context.getCallDepth();
559: e_depth = context.getEvalDepth();
560: step_up = true;
561: } else if (arg.length() > 0
562: && Character.isDigit(arg.charAt(0))) {
563: step = true;
564: nsteps = Integer.parseInt(arg);
565: } else {
566: step = true;
567: nsteps = 1;
568: }
569: break;
570: } else if ((offset = cmd.indexOf("next")) >= 0) {
571: String arg = cmd.substring(offset + "step".length())
572: .trim();
573: if (arg.length() > 0
574: && Character.isDigit(arg.charAt(0))) {
575: nnexts = Integer.parseInt(arg);
576: } else {
577: nnexts = 1;
578: }
579: next = true;
580: c_depth = context.getCallDepth();
581: e_depth = context.getEvalDepth();
582: file = context.getScriptSource();
583: break;
584: } else if ((offset = cmd.indexOf("trace function")) >= 0) {
585: String arg = cmd.substring(
586: offset + "trace_function".length()).trim();
587: if (arg.length() > 0) {
588: if (trace_functions.get(arg) == null) {
589: trace_functions.put(arg, arg);
590: } else {
591: trace_functions.remove(arg);
592: }
593: } else {
594: trace_all_functions = !trace_all_functions;
595: if (trace_all_functions) {
596: term.println("on");
597: } else {
598: term.println("off");
599: }
600: }
601:
602: } else if ("trace".equals(cmd)) {
603: trace_lines = !trace_lines;
604: if (trace_lines) {
605: term.println("on");
606: } else {
607: term.println("off");
608: }
609: } else if ("cont".equals(cmd)) {
610: break;
611: } else if ("clear".equals(cmd)) {
612: clearBreakPoints();
613: } else {
614: try {
615: session = true;
616: Context c = (Context) context.clone(false, false,
617: true);
618: term.println(Pnuts.format(Pnuts.eval(cmd, c)));
619: } catch (Throwable t) {
620: term.println(t);
621: } finally {
622: session = false;
623: }
624: }
625: }
626: }
627: }
|