Index: test/DebuggerStackTraceParserTest.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- test/DebuggerStackTraceParserTest.js	(revision 14+:ee80168855c6+)
+++ test/DebuggerStackTraceParserTest.js	(revision 14+:ee80168855c6+)
@@ -0,0 +1,76 @@
+/*jshint expr: true*/
+
+"use strict";
+
+var chai = require("chai"),
+    expect = chai.expect,
+    should = require("mocha-should");
+
+var StringStream = require("./StringStream");
+
+var DebuggerStackTraceParser = require("../src/DebuggerStackTraceParser");
+
+describe("DebuggerStackTraceParser Tests", function() {
+  var parser;
+
+  beforeEach(function() {
+    parser = new DebuggerStackTraceParser({
+      log: process.stdout
+    });
+  });
+
+  function eventReader(callback) {
+    return function() {
+      callback(parser.read());
+    };
+  }
+
+  should("parse stack trace", function(done) {
+    var stacktrace = ". = Baz::baz() called from file 'Bar.pm' line 12\n. = Bar::bar() called from file 'Foo.pm' line 12\n. = Foo::foo() called from file 'main.pl' line 3",
+        expected = [
+          {
+            sub: "Baz::baz()",
+            location: null
+          },
+          {
+            sub: "Bar::bar()",
+            location: {
+              file: "Bar.pm",
+              line: 12
+            }
+          },
+          {
+            sub: "Foo::foo()",
+            location: {
+              file: "Foo.pm",
+              line: 12
+            }
+          },
+          {
+            sub: null,
+            location: {
+              file: "main.pl",
+              line: 3
+            }
+          }
+        ];
+
+    parser.once("readable", eventReader(function(event) {
+      expect(event.name).to.equal("stacktrace");
+      expect(event.args[0][0], "Got no tracke").to.not.be.undefined;
+
+      var trace = event.args[0][0];
+      expect(trace.length).to.equal(expected.length);
+
+      for (var i = 0; i < expected.length; i++) {
+        expect(trace[i].sub).to.equal(expected[i].sub);
+        expect(trace[i].location.file).to.equal(expected[i].location.file);
+        expect(trace[i].location.line).to.equal(expected[i].location.line);
+      }
+
+      done();
+    }));
+
+    StringStream.createStream(stacktrace + "\n  DB<18> ").pipe(parser);
+  });
+});
\ No newline at end of file
Index: src/DebuggerStackTraceParser.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- src/DebuggerStackTraceParser.js	(revision 14+:ee80168855c6+)
+++ src/DebuggerStackTraceParser.js	(revision 14+:ee80168855c6+)
@@ -0,0 +1,115 @@
+"use strict";
+
+var TransformStream = require("stream").Transform,
+    util = require("util");
+
+var StreamEmitter = require("./StreamEmitter");
+
+util.inherits(DebuggerStackTaceParser, TransformStream);
+StreamEmitter.mixin(DebuggerStackTaceParser);
+
+module.exports = DebuggerStackTaceParser;
+
+DebuggerStackTaceParser.LINE = "line";
+DebuggerStackTaceParser.PROMPT = "prompt";
+
+/**
+ * @typedef {Object} DebuggerStackTraceParserConfiguration
+ * @property {string|Writable} log - If a string, path to a log file, else a Writable stream.
+ */
+/**
+ * Parses a stack trace from the Perl debugger
+ * <p>
+ * This should not be used directly
+ *
+ * @param {DebuggerStackTraceParserConfiguration} [config]
+ * @constructor
+ */
+function DebuggerStackTaceParser(config) {
+  TransformStream.call(this, { objectMode: true });
+
+  this.reset();
+}
+
+DebuggerStackTaceParser.prototype.reset = function() {
+  this._next = [];
+  this._tokenType = null;
+  this._trace = [];
+  this._traceElement = {};
+};
+
+//noinspection JSUnusedGlobalSymbols
+DebuggerStackTaceParser.prototype._transform = function(chunk, encoding, done) {
+  chunk.split("").forEach(this._character.bind(this));
+
+  done();
+};
+
+DebuggerStackTaceParser.prototype._character = function(char) {
+  this._next.push(char);
+  this._tokenise();
+};
+
+DebuggerStackTaceParser.prototype._tokenise = function() {
+  switch(this._tokenType) {
+    case DebuggerStackTaceParser.LINE:
+      return this._continueLine();
+
+    case DebuggerStackTaceParser.PROMPT:
+      return this._continuePrompt();
+
+    default:
+      return this._beginToken();
+  }
+};
+
+DebuggerStackTaceParser.prototype._beginToken = function() {
+  switch (this._next[0]) {
+    case " ":
+      this._tokenType = DebuggerStackTaceParser.PROMPT;
+      break;
+
+    default:
+      this._tokenType = DebuggerStackTaceParser.LINE;
+  }
+};
+
+DebuggerStackTaceParser.prototype._endToken = function() {
+  this._parse(this._next.join("").trim());
+  this._tokenType = null;
+};
+
+DebuggerStackTaceParser.prototype._continueLine = function() {
+  if (this._next.top() === "\n") {
+    this._endToken();
+    this._consume();
+  }
+};
+
+DebuggerStackTaceParser.prototype._consume = function() {
+  this._next = [];
+};
+
+DebuggerStackTaceParser.prototype._parse = function(line) {
+  var matches;
+
+  matches = /\s*DB<(\d+)>\s*$/.exec(line);
+  if (matches && matches.length > 0) {
+    this._event("stacktrace", this._trace);
+    return this._event("prompt", matches[1]);
+  }
+
+  matches = /^.*? = (.*?\(\)) called from file '(.+?)' line (\d+).*$/.exec(line);
+  if (matches && matches.length > 0) {
+    this._trace.push({
+      sub: matches[1],
+      location: this._traceElement.location
+    });
+
+    this._traceElement.lastsub = matches[1];
+    this._traceElement.location = {
+      file: matches[2],
+      line: matches[3]
+    };
+  }
+};
\ No newline at end of file
