Commit a00fd6e254

Vexu <git@vexu.eu>
2020-05-15 13:30:49
properly handle extra closing braces at top level
1 parent 440189a
Changed files (2)
lib/std/zig/parse.zig
@@ -57,16 +57,10 @@ pub fn parse(allocator: *Allocator, source: []const u8) Allocator.Error!*Tree {
 fn parseRoot(arena: *Allocator, it: *TokenIterator, tree: *Tree) Allocator.Error!*Node.Root {
     const node = try arena.create(Node.Root);
     node.* = .{
-        .decls = try parseContainerMembers(arena, it, tree),
-        .eof_token = eatToken(it, .Eof) orelse blk: {
-            // parseContainerMembers will try to skip as much
-            // invalid tokens as it can so this can only be a '}'
-            const tok = eatToken(it, .RBrace).?;
-            try tree.errors.push(.{
-                .ExpectedContainerMembers = .{ .token = tok },
-            });
-            break :blk tok;
-        },
+        .decls = try parseContainerMembers(arena, it, tree, true),
+        // parseContainerMembers will try to skip as much
+        // invalid tokens as it can so this can only be the EOF
+        .eof_token = eatToken(it, .Eof).?,
     };
     return node;
 }
@@ -78,7 +72,7 @@ fn parseRoot(arena: *Allocator, it: *TokenIterator, tree: *Tree) Allocator.Error
 ///      / KEYWORD_pub? ContainerField COMMA ContainerMembers
 ///      / KEYWORD_pub? ContainerField
 ///      /
-fn parseContainerMembers(arena: *Allocator, it: *TokenIterator, tree: *Tree) !Node.Root.DeclList {
+fn parseContainerMembers(arena: *Allocator, it: *TokenIterator, tree: *Tree, top_level: bool) !Node.Root.DeclList {
     var list = Node.Root.DeclList.init(arena);
 
     var field_state: union(enum) {
@@ -205,9 +199,15 @@ fn parseContainerMembers(arena: *Allocator, it: *TokenIterator, tree: *Tree) !No
                 // try to continue parsing
                 const index = it.index;
                 findNextContainerMember(it);
-                switch (it.peek().?.id) {
-                    .Eof, .RBrace => break,
+                const next = it.peek().?.id;
+                switch (next) {
+                    .Eof => break,
                     else => {
+                        if (next == .RBrace) {
+                            if (!top_level) break;
+                            _ = nextToken(it);
+                        }
+
                         // add error and continue
                         try tree.errors.push(.{
                             .ExpectedToken = .{ .token = index, .expected_id = .Comma },
@@ -228,9 +228,15 @@ fn parseContainerMembers(arena: *Allocator, it: *TokenIterator, tree: *Tree) !No
             });
         }
 
-        switch (it.peek().?.id) {
-            .Eof, .RBrace => break,
+        const next = it.peek().?.id;
+        switch (next) {
+            .Eof => break,
             else => {
+                if (next == .RBrace) {
+                    if (!top_level) break;
+                    _ = nextToken(it);
+                }
+
                 // this was likely not supposed to end yet,
                 // try to find the next declaration
                 const index = it.index;
@@ -2778,7 +2784,7 @@ fn parsePtrTypeStart(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node
 fn parseContainerDeclAuto(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
     const node = (try parseContainerDeclType(arena, it, tree)) orelse return null;
     const lbrace = try expectToken(it, tree, .LBrace);
-    const members = try parseContainerMembers(arena, it, tree);
+    const members = try parseContainerMembers(arena, it, tree, false);
     const rbrace = try expectToken(it, tree, .RBrace);
 
     const decl_type = node.cast(Node.ContainerDecl).?;
lib/std/zig/parser_test.zig
@@ -148,6 +148,20 @@ test "recovery: invalid parameter" {
     });
 }
 
+test "recovery: extra '}' at top level" {
+    try testError(
+        \\}}}
+        \\test "" {
+        \\    a && b;
+        \\}
+    , &[_]Error{
+        .ExpectedContainerMembers,
+        .ExpectedContainerMembers,
+        .ExpectedContainerMembers,
+        .InvalidAnd,
+    });
+}
+
 test "zig fmt: top-level fields" {
     try testCanonical(
         \\a: did_you_know,