Add a couple functions to package:stack_trace.

This adds a function to convert a Trace to a VM-style StackTrace
object, and to parse a Trace-style string into a Trace.

R=jmesserly@google.com

Review URL: https://codereview.chromium.org//18029023

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/stack_trace@24611 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/stack_trace/lib/src/frame.dart b/pkgs/stack_trace/lib/src/frame.dart
index 6daca46..6ba3079 100644
--- a/pkgs/stack_trace/lib/src/frame.dart
+++ b/pkgs/stack_trace/lib/src/frame.dart
@@ -24,6 +24,11 @@
 final _firefoxFrame = new RegExp(
     r'^([^@(/]*)(?:\(.*\))?(/[^<]*<?)?(?:\(.*\))?@(.*):(\d+)$');
 
+// foo/bar.dart 10:11 in Foo._bar
+// http://dartlang.org/foo/bar.dart in Foo._bar
+final _friendlyFrame = new RegExp(
+    r'^([^\s]+)(?: (\d+):(\d+))?\s+([^\d][^\s]*)$');
+
 final _initialDot = new RegExp(r"^\.");
 
 /// A single stack frame. Each frame points to a precise location in Dart code.
@@ -144,6 +149,26 @@
     return new Frame(uri, int.parse(match[4]), null, member);
   }
 
+  /// Parses this package's string representation of a stack frame.
+  factory Frame.parseFriendly(String frame) {
+    var match = _friendlyFrame.firstMatch(frame);
+    if (match == null) {
+      throw new FormatException(
+          "Couldn't parse package:stack_trace stack trace line '$frame'.");
+    }
+
+    var uri = Uri.parse(match[1]);
+    // If there's no scheme, this is a relative URI. We should interpret it as
+    // relative to the current working directory.
+    if (uri.scheme == '') {
+      uri = path.toUri(path.absolute(path.fromUri(uri)));
+    }
+
+    var line = match[2] == null ? null : int.parse(match[2]);
+    var column = match[3] == null ? null : int.parse(match[3]);
+    return new Frame(uri, line, column, match[4]);
+  }
+
   Frame(this.uri, this.line, this.column, this.member);
 
   String toString() => '$location in $member';
diff --git a/pkgs/stack_trace/lib/src/lazy_trace.dart b/pkgs/stack_trace/lib/src/lazy_trace.dart
index 8c44829..ae56b5f 100644
--- a/pkgs/stack_trace/lib/src/lazy_trace.dart
+++ b/pkgs/stack_trace/lib/src/lazy_trace.dart
@@ -25,8 +25,7 @@
   }
 
   List<Frame> get frames => _trace.frames;
-  String get stackTrace => _trace.stackTrace;
-  String get fullStackTrace => _trace.fullStackTrace;
+  StackTrace get vmTrace => _trace.vmTrace;
   Trace get terse => new LazyTrace(() => _trace.terse);
   Trace foldFrames(bool predicate(frame)) =>
     new LazyTrace(() => _trace.foldFrames(predicate));
diff --git a/pkgs/stack_trace/lib/src/trace.dart b/pkgs/stack_trace/lib/src/trace.dart
index db7a404..2b9d22a 100644
--- a/pkgs/stack_trace/lib/src/trace.dart
+++ b/pkgs/stack_trace/lib/src/trace.dart
@@ -9,6 +9,8 @@
 
 import 'frame.dart';
 import 'lazy_trace.dart';
+import 'utils.dart';
+import 'vm_trace.dart';
 
 final _terseRegExp = new RegExp(r"(-patch)?(/.*)?$");
 
@@ -19,6 +21,9 @@
 /// `.VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560`.
 final _firefoxTrace = new RegExp(r"^([.0-9A-Za-z_$/<]*|\(.*\))*@");
 
+/// A RegExp to match this package's stack traces.
+final _friendlyTrace = new RegExp(r"^[^\s]+( \d+:\d+)?\s+[^\s]+($|\n)");
+
 /// A stack trace, comprised of a list of stack frames.
 class Trace implements StackTrace {
   /// The stack frames that comprise this stack trace.
@@ -70,6 +75,7 @@
     if (trace.isEmpty) return new Trace(<Frame>[]);
     if (trace.startsWith("Error\n")) return new Trace.parseV8(trace);
     if (trace.contains(_firefoxTrace)) return new Trace.parseFirefox(trace);
+    if (trace.contains(_friendlyTrace)) return new Trace.parseFriendly(trace);
 
     // Default to parsing the stack trace as a VM trace. This is also hit on IE
     // and Safari, where the stack trace is just an empty string (issue 11257).
@@ -89,23 +95,21 @@
       : this(trace.trim().split("\n")
           .map((line) => new Frame.parseFirefox(line)));
 
+  /// Parses this package's a string representation of a stack trace.
+  Trace.parseFriendly(String trace)
+      : this(trace.trim().split("\n")
+          .map((line) => new Frame.parseFriendly(line)));
+
   /// Returns a new [Trace] comprised of [frames].
   Trace(Iterable<Frame> frames)
       : frames = new UnmodifiableListView<Frame>(frames.toList());
 
-  // TODO(nweiz): Keep track of which [Frame]s are part of the partial stack
-  // trace and only print them.
-  /// Returns a string representation of this stack trace.
+  /// Returns a VM-style [StackTrace] object.
   ///
-  /// This is identical to [toString]. It will not be formatted in the manner of
-  /// native stack traces.
-  String get stackTrace => toString();
-
-  /// Returns a string representation of this stack trace.
-  ///
-  /// This is identical to [toString]. It will not be formatted in the manner of
-  /// native stack traces.
-  String get fullStackTrace => toString();
+  /// The return value's [toString] method will always return a string
+  /// representation in the Dart VM's stack trace format, regardless of what
+  /// platform is being used.
+  StackTrace get vmTrace => new VMTrace(frames);
 
   /// Returns a terser version of [this].
   ///
@@ -149,21 +153,7 @@
 
     // Print out the stack trace nicely formatted.
     return frames.map((frame) {
-      return '${_padRight(frame.location, longest)}  ${frame.member}\n';
+      return '${padRight(frame.location, longest)}  ${frame.member}\n';
     }).join();
   }
 }
-
-/// Returns [string] with enough spaces added to the end to make it [length]
-/// characters long.
-String _padRight(String string, int length) {
-  if (string.length >= length) return string;
-
-  var result = new StringBuffer();
-  result.write(string);
-  for (var i = 0; i < length - string.length; i++) {
-    result.write(' ');
-  }
-
-  return result.toString();
-}
diff --git a/pkgs/stack_trace/lib/src/utils.dart b/pkgs/stack_trace/lib/src/utils.dart
new file mode 100644
index 0000000..08b3b96
--- /dev/null
+++ b/pkgs/stack_trace/lib/src/utils.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library stack_trace.src.utils;
+
+/// Returns [string] with enough spaces added to the end to make it [length]
+/// characters long.
+String padRight(String string, int length) {
+  if (string.length >= length) return string;
+
+  var result = new StringBuffer();
+  result.write(string);
+  for (var i = 0; i < length - string.length; i++) {
+    result.write(' ');
+  }
+
+  return result.toString();
+}
+
diff --git a/pkgs/stack_trace/lib/src/vm_trace.dart b/pkgs/stack_trace/lib/src/vm_trace.dart
new file mode 100644
index 0000000..8116015
--- /dev/null
+++ b/pkgs/stack_trace/lib/src/vm_trace.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library vm_trace;
+
+import 'frame.dart';
+import 'utils.dart';
+
+/// An implementation of [StackTrace] that emulates the behavior of the VM's
+/// implementation.
+///
+/// In particular, when [toString] is called, this returns a string in the VM's
+/// stack trace format.
+class VMTrace implements StackTrace {
+  /// The stack frames that comprise this stack trace.
+  final List<Frame> frames;
+
+  VMTrace(this.frames);
+
+  String toString() {
+    var i = 1;
+    return frames.map((frame) {
+      var number = padRight("#${i++}", 8);
+      var member = frame.member.replaceAll("<fn>", "<anonymous closure>");
+      var line = frame.line == null ? 0 : frame.line;
+      var column = frame.column == null ? 0 : frame.column;
+      return "$number$member (${frame.uri}:$line:$column)\n";
+    }).join();
+  }
+}
diff --git a/pkgs/stack_trace/test/frame_test.dart b/pkgs/stack_trace/test/frame_test.dart
index 2ab310c..f38278d 100644
--- a/pkgs/stack_trace/test/frame_test.dart
+++ b/pkgs/stack_trace/test/frame_test.dart
@@ -185,6 +185,43 @@
     });
   });
 
+  group('.parseFriendly', () {
+    test('parses a simple stack frame correctly', () {
+      var frame = new Frame.parseFriendly(
+          "http://dartlang.org/foo/bar.dart 10:11  Foo.<fn>.bar");
+      expect(frame.uri, equals(Uri.parse("http://dartlang.org/foo/bar.dart")));
+      expect(frame.line, equals(10));
+      expect(frame.column, equals(11));
+      expect(frame.member, equals('Foo.<fn>.bar'));
+    });
+
+    test('parses a stack frame with no line or column correctly', () {
+      var frame = new Frame.parseFriendly(
+          "http://dartlang.org/foo/bar.dart  Foo.<fn>.bar");
+      expect(frame.uri, equals(Uri.parse("http://dartlang.org/foo/bar.dart")));
+      expect(frame.line, isNull);
+      expect(frame.column, isNull);
+      expect(frame.member, equals('Foo.<fn>.bar'));
+    });
+
+    test('parses a stack frame with a relative path correctly', () {
+      var frame = new Frame.parseFriendly("foo/bar.dart 10:11    Foo.<fn>.bar");
+      expect(frame.uri, equals(
+          path.toUri(path.absolute(path.join('foo', 'bar.dart')))));
+      expect(frame.line, equals(10));
+      expect(frame.column, equals(11));
+      expect(frame.member, equals('Foo.<fn>.bar'));
+    });
+
+    test('throws a FormatException for malformed frames', () {
+      expect(() => new Frame.parseFriendly(''), throwsFormatException);
+      expect(() => new Frame.parseFriendly('foo/bar.dart'),
+          throwsFormatException);
+      expect(() => new Frame.parseFriendly('foo/bar.dart 10:11'),
+          throwsFormatException);
+    });
+  });
+
   test('only considers dart URIs to be core', () {
     bool isCore(String library) =>
       new Frame.parseVM('#0 Foo ($library:0:0)').isCore;
diff --git a/pkgs/stack_trace/test/trace_test.dart b/pkgs/stack_trace/test/trace_test.dart
index 95753a0..fbe11a3 100644
--- a/pkgs/stack_trace/test/trace_test.dart
+++ b/pkgs/stack_trace/test/trace_test.dart
@@ -101,6 +101,22 @@
           equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
     });
 
+    test('parses a package:stack_trace stack trace correctly', () {
+      var trace = new Trace.parse(
+          'http://dartlang.org/foo/bar.dart 10:11  Foo.<fn>.bar\n'
+          'http://dartlang.org/foo/baz.dart        Foo.<fn>.bar');
+
+      expect(trace.frames[0].uri,
+          equals(Uri.parse("http://dartlang.org/foo/bar.dart")));
+      expect(trace.frames[1].uri,
+          equals(Uri.parse("http://dartlang.org/foo/baz.dart")));
+    });
+
+    test('parses a real package:stack_trace stack trace correctly', () {
+      var traceString = new Trace.current().toString();
+      expect(new Trace.parse(traceString).toString(), equals(traceString));
+    });
+
     test('parses an empty string correctly', () {
       var trace = new Trace.parse('');
       expect(trace.frames, isEmpty);
@@ -122,14 +138,18 @@
 '''));
   });
 
-  test('.stackTrace forwards to .toString()', () {
-    var trace = new Trace.current();
-    expect(trace.stackTrace, equals(trace.toString()));
-  });
+  test('.vmTrace returns a native-style trace', () {
+    var uri = path.toUri(path.absolute('foo'));
+    var trace = new Trace([
+      new Frame(uri, 10, 20, 'Foo.<fn>'),
+      new Frame(Uri.parse('http://dartlang.org/foo.dart'), null, null, 'bar'),
+      new Frame(Uri.parse('dart:async'), 15, null, 'baz'),
+    ]);
 
-  test('.fullStackTrace forwards to .toString()', () {
-    var trace = new Trace.current();
-    expect(trace.fullStackTrace, equals(trace.toString()));
+    expect(trace.vmTrace.toString(), equals(
+        '#1      Foo.<anonymous closure> ($uri:10:20)\n'
+        '#2      bar (http://dartlang.org/foo.dart:0:0)\n'
+        '#3      baz (dart:async:15:0)\n'));
   });
 
   test('.terse folds core frames together bottom-up', () {