Basic table support
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart
index efab2a4..162734b 100644
--- a/pkgs/markdown/lib/src/block_parser.dart
+++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -48,6 +48,9 @@
 final _olPattern =
     new RegExp(r'^([ ]{0,3})(\d{1,9})([\.)])(([ \t])([ \t]*)(.*))?$');
 
+/// A line of hyphens separated by at least one pipe.
+final _tablePattern = new RegExp(r'^[ ]{0,3}\|?(\-+\|)+\-*$');
+
 /// Maintains the internal state needed to parse a series of lines into blocks
 /// of Markdown suitable for further inline parsing.
 class BlockParser {
@@ -113,8 +116,8 @@
   ///
   /// `peek(1)` is equivalent to [next].
   String peek(int linesAhead) {
-    if (linesAhead < 0)
-      throw new ArgumentError('Invalid linesAhead: $linesAhead; must be >= 0.');
+    if (linesAhead < 0) throw new ArgumentError(
+        'Invalid linesAhead: $linesAhead; must be >= 0.');
     // Don't read past the end.
     if (_pos >= lines.length - linesAhead) return null;
     return lines[_pos + linesAhead];
@@ -706,6 +709,68 @@
   const OrderedListSyntax();
 }
 
+/// Parses tables.
+class TableSyntax extends BlockSyntax {
+  bool get canEndBlock => false;
+
+  const TableSyntax();
+
+  bool canParse(BlockParser parser) {
+    // Note: matches *next* line, not the current one. We're looking for the
+    // bar separating the head row from the body rows.
+    return parser.matchesNext(_tablePattern);
+  }
+
+  /// Parses a table into its three parts:
+  ///
+  /// * a head row of head cells (`<th>` cells)
+  /// * a divider of hyphens and pipes (not rendered)
+  /// * many body rows of body cells (`<td>` cells)
+  Node parse(BlockParser parser) {
+    var head = new Element('thead', [parseRow(parser, 'th')]);
+
+    // Advance past the divider of hyphens.
+    parser.advance();
+
+    var rows = <Element>[];
+    while (!parser.isDone && !parser.matches(_emptyPattern)) {
+      rows.add(parseRow(parser, 'td'));
+    }
+    var body = new Element('tbody', rows);
+
+    return new Element('table', [head, body]);
+  }
+
+  Node parseRow(BlockParser parser, String cellType) {
+    var line = parser.current
+        .replaceFirst(new RegExp(r'^\|\s*'), '')
+        .replaceFirst(new RegExp(r'\s*\|$'), '');
+    var contents = parser.document.parseInline(line);
+    parser.advance();
+    var row = <Node>[];
+    var cellContents = <Node>[];
+    var pipe = new RegExp(r'\s*\|\s*');
+
+    contents.forEach((Node node) {
+      if (node is Text) {
+        var cells = node.text.split(pipe);
+        cells.sublist(0, cells.length - 1).forEach((String cell) {
+          cellContents.add(new Text(cell));
+          row.add(new Element(cellType, cellContents));
+          cellContents = <Node>[];
+        });
+        cellContents.add(new Text(cells.last));
+      } else {
+        // An Element or something else.
+        cellContents.add(node);
+      }
+    });
+    row.add(new Element(cellType, cellContents));
+
+    return new Element('tr', row);
+  }
+}
+
 /// Parses paragraphs of regular text.
 class ParagraphSyntax extends BlockSyntax {
   static final _reflinkDefinitionStart = new RegExp(r'[ ]{0,3}\[');
diff --git a/pkgs/markdown/test/extensions/tables.unit b/pkgs/markdown/test/extensions/tables.unit
new file mode 100644
index 0000000..e28def5
--- /dev/null
+++ b/pkgs/markdown/test/extensions/tables.unit
@@ -0,0 +1,47 @@
+>>> basic table
+head | cells
+-----|------
+body | cells
+
+<<<
+<table><thead><tr><th>head</th><th>cells</th></tr></thead><tbody><tr><td>body</td><td>cells</td></tr></tbody></table>
+>>> multiple rows
+head | cells
+-----|------
+body | cells
+more | cells
+
+<<<
+<table><thead><tr><th>head</th><th>cells</th></tr></thead><tbody><tr><td>body</td><td>cells</td></tr><tr><td>more</td><td>cells</td></tr></tbody></table>
+>>> rows wrapped in pipes
+| head | cells |
+|------|-------|
+| body | cells |
+
+<<<
+<table><thead><tr><th>head</th><th>cells</th></tr></thead><tbody><tr><td>body</td><td>cells</td></tr></tbody></table>
+>>> cells with inline syntax
+head `code` | _cells_
+------------|--------
+*text*      | <span>text</span>
+<<<
+<table><thead><tr><th>head <code>code</code></th><th><em>cells</em></th></tr></thead><tbody><tr><td><em>text</em></td><td><span>text</span></td></tr></tbody></table>
+>>> cells with inline syntax with pipes
+header | _foo | bar_
+-------|------------
+text   | text
+<<<
+<table><thead><tr><th>header</th><th><em>foo | bar</em></th></tr></thead><tbody><tr><td>text</td><td>text</td></tr></tbody></table>
+>>> one column tables
+head
+-----|-----
+body
+<<<
+<table><thead><tr><th>head</th></tr></thead><tbody><tr><td>body</td></tr></tbody></table>
+>>> varying cells per row
+head | foo | bar
+-----|-----
+body
+row with | two cells
+<<<
+<table><thead><tr><th>head</th><th>foo</th><th>bar</th></tr></thead><tbody><tr><td>body</td></tr><tr><td>row with</td><td>two cells</td></tr></tbody></table>
diff --git a/pkgs/markdown/test/markdown_test.dart b/pkgs/markdown/test/markdown_test.dart
index 053e481..701ae63 100644
--- a/pkgs/markdown/test/markdown_test.dart
+++ b/pkgs/markdown/test/markdown_test.dart
@@ -148,4 +148,6 @@
 
   testFile('extensions/setext_headers_with_ids.unit',
       blockSyntaxes: [const SetextHeaderWithIdSyntax()]);
+
+  testFile('extensions/tables.unit', blockSyntaxes: [const TableSyntax()]);
 }