Monday, November 11, 2013

Debugger 1: A fictional interpreter

Recently I was playing around with the debug framework to implement a custom debugger. While there exists some documentation, it still is somewhat tricky to fit all parts together. So I am going to start a new series on debugging support.

Debug Framework Tutorials

For a list of all debug related tutorials see Debug Framework Tutorials Overview.

In this series we will handle launching, debugging with all its flavors (stepping, suspend/resume, breakpoints), source lookup to trace the current code location and finally variables and memory support.

But before we can even think of debugging, we need a language definition and an interpreter with debugging support.

Displayed source code will only show relevant parts, please refer to the provided links to see the full implementation.

Source code for this tutorial is available on github as a single zip archive, as a Team Project Set or you can browse the files online. 

Language definition

Lets keep things simple and use a fictional interpreter that simply processes lines of code by printing them to the standard output. Therefore any text file can be used as source input. This way, we may use the Eclipse default Text Editor as our source editor.

Our simple language shall support variable definitions in the form "my variable name = some content". To also make use of variables we will allow for placeholders ${variableName} that will get replaced by the actual variable content.

Finally we support memory dumps. For simplicity each processed line content will add to our memory dump.

A simple script example looks as follows:
hello world
first name = Christian
${first name}, you just defined a variable

counter = 23

we are running
our interpreter

Interpreter implementation

Lets have a look at the TextInterpreter implementation (just the relevant parts):
package com.codeandme.debugger.textinterpreter;

public class TextInterpreter extends Job {

 public TextInterpreter() {
  super("Text interpreter");
 }


 /**
  * Set the source code to be executed. As our interpreter is line oriented, we immediately split the text into separate lines.
  * 
  * @param code
  *            source code
  */
 public void setCode(final String code) {
  String[] lines = code.replaceAll("\r\n", "\n").split("\n");
  fLines = new LinkedList<String>(Arrays.asList(lines));
 }

 @Override
 protected synchronized IStatus run(final IProgressMonitor monitor) {
  fTerminated = false;
  fLineNumber = 1;

  fVariables.clear();
  fMemory = new StringWriter();

  debug(DebugAction.LOADED);

  while ((!fTerminated) && (!monitor.isCanceled()) && (!fLines.isEmpty())) {

   // read line to execute
   String line = fLines.remove(0);
   line = evaluate(line);

   // "execute" line of code
   System.out.println(line);

   // alter our simulated memory
   fMemory.append(line);

   // advance by one line
   fLineNumber++;

   debug(DebugAction.SUSPEND);
  }

  debug(DebugAction.TERMINATE);

  return Status.OK_STATUS;
 }

 public String evaluate(String lineOfCode) {
  // do variable replacement
  Matcher matcher = VARIABLE_MATCHER.matcher(lineOfCode);
  while (matcher.matches()) {
   lineOfCode = matcher.replaceFirst(fVariables.get(matcher.group(1)));
   matcher = VARIABLE_MATCHER.matcher(lineOfCode);
  }

  // try to register variable
  String[] tokens = lineOfCode.split("=");
  if (tokens.length == 2) {
   // variable found
   fVariables.put(tokens[0].trim(), tokens[1].trim());
  }

  return lineOfCode;
 }
}

As interpreters typically run in their own process or thread our interpreter is implemented as a Job. The two important methods so far are setCode() and run(). Everything else is for debugging support and will be explained in the following tutorials.
The main loop (line 31) processes line by line by printing, altering the memory and extracting variables. This is done until all lines are processed or the Job got terminated via the monitor or an external call.

All we have to do to start our interpreter is:
TextInterpreter interpreter = new TextInterpreter();
interpreter.setCode("hello world");
interpreter.schedule();
Launching is a topic of its own and will be briefly handled in the next tutorial.

5 comments:

  1. Dear Christian,
    thank you for the great tutorial. Is it possible to upload the whole source code again?

    ReplyDelete
    Replies
    1. I am moving my old tutorials step by step to github. Get in touch with me directly an I'll send you a zipped version of the debugger tutorials

      Delete
    2. Hello Christian, could you please send me the zip ViewGragon@gmail.com.
      Thank you!

      Delete
  2. Hello Christian

    This is very interesting... could you please also send me the zip touti.airbnb@gmail.com

    Many Thanks

    ReplyDelete