Properly parse friendly stack traces without columns.

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

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/stack_trace@28892 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkgs/stack_trace/lib/src/frame.dart b/pkgs/stack_trace/lib/src/frame.dart
index b6b5962..0b4f577 100644
--- a/pkgs/stack_trace/lib/src/frame.dart
+++ b/pkgs/stack_trace/lib/src/frame.dart
@@ -23,7 +23,7 @@
 // .VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560
 // .VW.call$0/name<@http://pub.dartlang.org/stuff.dart.js:560
 final _firefoxFrame = new RegExp(
-    r'^([^@(/]*)(?:\(.*\))?(/[^<]*<?)?(?:\(.*\))?@(.*):(\d+)$');
+    r'^([^@(/]*)(?:\(.*\))?((?:/[^/]*)*)(?:\(.*\))?@(.*):(\d+)$');
 
 // foo/bar.dart 10:11 in Foo._bar
 // http://dartlang.org/foo/bar.dart in Foo._bar
@@ -160,11 +160,9 @@
 
     var uri = Uri.parse(match[3]);
     var member = match[1];
-    if (member == "") {
-      member = "<fn>";
-    } else if (match[2] != null) {
-      member = "$member.<fn>";
-    }
+    member += new List.filled('/'.allMatches(match[2]).length, ".<fn>").join();
+    if (member == '') member = '<fn>';
+
     // Some Firefox members have initial dots. We remove them for consistency
     // with other platforms.
     member = member.replaceFirst(_initialDot, '');
diff --git a/pkgs/stack_trace/lib/src/trace.dart b/pkgs/stack_trace/lib/src/trace.dart
index bf14ee2..95d41ad 100644
--- a/pkgs/stack_trace/lib/src/trace.dart
+++ b/pkgs/stack_trace/lib/src/trace.dart
@@ -36,7 +36,7 @@
 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)");
+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 {
diff --git a/pkgs/stack_trace/test/frame_test.dart b/pkgs/stack_trace/test/frame_test.dart
index 75b92a4..ab7c401 100644
--- a/pkgs/stack_trace/test/frame_test.dart
+++ b/pkgs/stack_trace/test/frame_test.dart
@@ -202,6 +202,18 @@
       expect(frame.member, equals("foo.<fn>"));
     });
 
+    test('parses a deeply-nested anonymous stack frame with parameters '
+        'correctly', () {
+      var frame = new Frame.parseFirefox(
+          '.convertDartClosureToJS/\$function</<@'
+          'http://pub.dartlang.org/stuff.dart.js:560');
+      expect(frame.uri,
+          equals(Uri.parse("http://pub.dartlang.org/stuff.dart.js")));
+      expect(frame.line, equals(560));
+      expect(frame.column, isNull);
+      expect(frame.member, equals("convertDartClosureToJS.<fn>.<fn>"));
+    });
+
     test('throws a FormatException for malformed frames', () {
       expect(() => new Frame.parseFirefox(''), throwsFormatException);
       expect(() => new Frame.parseFirefox('.foo'), throwsFormatException);