Commit 5afe473a86

Andrew Kelley <superjoe30@gmail.com>
2016-01-26 21:08:21
different extern syntax and simplify parsing top level decls
1 parent bc89614
Changed files (8)
doc/vim/syntax/zig.vim
@@ -14,7 +14,7 @@ syn keyword zigConditional if else switch
 syn keyword zigRepeat while for
 
 syn keyword zigConstant null undefined
-syn keyword zigKeyword fn import
+syn keyword zigKeyword fn import c_import
 syn keyword zigType bool i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64 void unreachable type error
 syn keyword zigType c_short c_ushort c_int c_uint c_long c_ulong c_longlong c_ulonglong
 
doc/langref.md
@@ -5,31 +5,33 @@
 ```
 Root : many(TopLevelDecl) "EOF"
 
-TopLevelDecl : FnDef | ExternBlock | RootExportDecl | Import | ContainerDecl | VariableDeclaration | ErrorValueDecl
+TopLevelDecl : many(Directive) option(VisibleMod) (FnDef | ExternDecl | RootExportDecl | Import | ContainerDecl | GlobalVarDecl | ErrorValueDecl | CImportDecl)
 
-ErrorValueDecl : option(FnVisibleMod) "error" "Symbol"
+CImportDecl : "c_import" Block
 
-VariableDeclaration : option(FnVisibleMod) ("var" | "const") "Symbol" option(":" PrefixOpExpression) "=" Expression
+ErrorValueDecl : "error" "Symbol" ";"
 
-ContainerDecl : many(Directive) option(FnVisibleMod) ("struct" | "enum") "Symbol" "{" many(StructMember) "}"
+GlobalVarDecl : VariableDeclaration ";"
 
-StructMember: StructField | FnDecl
+VariableDeclaration : ("var" | "const") "Symbol" option(":" PrefixOpExpression) "=" Expression
+
+ContainerDecl : ("struct" | "enum") "Symbol" "{" many(StructMember) "}"
+
+StructMember: many(Directive) option(VisibleMod) (StructField | FnDef)
 
 StructField : "Symbol" option(":" Expression) ",")
 
-Import : many(Directive) "import" "String" ";"
+Import : "import" "String" ";"
 
-RootExportDecl : many(Directive) "export" "Symbol" "String" ";"
+RootExportDecl : "export" "Symbol" "String" ";"
 
-ExternBlock : many(Directive) "extern" "{" many(FnDecl) "}"
+ExternDecl : "extern" FnProto ";"
 
-FnProto : many(Directive) option(FnVisibleMod) "fn" "Symbol" ParamDeclList option("->" PrefixOpExpression)
+FnProto : "fn" "Symbol" ParamDeclList option("->" PrefixOpExpression)
 
 Directive : "#" "Symbol" "(" "String" ")"
 
-FnVisibleMod : "pub" | "export"
-
-FnDecl : FnProto ";"
+VisibleMod : "pub" | "export"
 
 FnDef : FnProto Block
 
example/hello_world/hello_libc.zig
@@ -1,9 +1,7 @@
+#link("c")
 export executable "hello";
 
-#link("c")
-extern {
-    fn printf(__format: &const u8, ...) -> c_int;
-}
+extern fn printf(__format: &const u8, ...) -> c_int;
 
 export fn main(argc: c_int, argv: &&u8) -> c_int {
     printf(c"Hello, world!\n");
src/all_types.hpp
@@ -121,7 +121,6 @@ enum NodeType {
     NodeTypeFnDecl,
     NodeTypeParamDecl,
     NodeTypeBlock,
-    NodeTypeExternBlock,
     NodeTypeDirective,
     NodeTypeReturnExpr,
     NodeTypeVariableDeclaration,
@@ -178,11 +177,10 @@ struct AstNodeFnProto {
     ZigList<AstNode *> params;
     AstNode *return_type;
     bool is_var_args;
+    bool is_extern;
 
     // populated by semantic analyzer:
 
-    // the extern block this fn proto is inside. can be null.
-    AstNode *extern_node;
     // the struct decl node this fn proto is inside. can be null.
     AstNode *struct_node;
     // the function definition this fn proto is inside. can be null.
@@ -247,6 +245,7 @@ struct AstNodeVariableDeclaration {
     // one or both of type and expr will be non null
     AstNode *type;
     AstNode *expr;
+    ZigList<AstNode *> *directives;
 
     // populated by semantic analyzer
     TopLevelDecl top_level_decl;
@@ -255,8 +254,9 @@ struct AstNodeVariableDeclaration {
 };
 
 struct AstNodeErrorValueDecl {
-    VisibMod visib_mod;
     Buf name;
+    VisibMod visib_mod;
+    ZigList<AstNode *> *directives;
 
     // populated by semantic analyzer
     TopLevelDecl top_level_decl;
@@ -378,11 +378,6 @@ struct AstNodeFieldAccessExpr {
     StructValExprCodeGen resolved_struct_val_expr; // for enum values
 };
 
-struct AstNodeExternBlock {
-    ZigList<AstNode *> *directives;
-    ZigList<AstNode *> fn_decls;
-};
-
 struct AstNodeDirective {
     Buf name;
     Buf param;
@@ -418,6 +413,7 @@ struct AstNodePrefixOpExpr {
 struct AstNodeUse {
     Buf path;
     ZigList<AstNode *> *directives;
+    VisibMod visib_mod;
 
     // populated by semantic analyzer
     ImportTableEntry *import;
@@ -559,6 +555,7 @@ struct AstNodeStructField {
     Buf name;
     AstNode *type;
     ZigList<AstNode *> *directives;
+    VisibMod visib_mod;
 };
 
 struct AstNodeStringLiteral {
@@ -697,7 +694,6 @@ struct AstNode {
         AstNodeErrorValueDecl error_value_decl;
         AstNodeBinOpExpr bin_op_expr;
         AstNodeUnwrapErrorExpr unwrap_err_expr;
-        AstNodeExternBlock extern_block;
         AstNodeDirective directive;
         AstNodePrefixOpExpr prefix_op_expr;
         AstNodeFnCallExpr fn_call_expr;
src/analyze.cpp
@@ -45,7 +45,6 @@ static AstNode *first_executing_node(AstNode *node) {
         case NodeTypeFnDecl:
         case NodeTypeParamDecl:
         case NodeTypeBlock:
-        case NodeTypeExternBlock:
         case NodeTypeDirective:
         case NodeTypeReturnExpr:
         case NodeTypeVariableDeclaration:
@@ -897,8 +896,8 @@ static void preview_fn_proto(CodeGen *g, ImportTableEntry *import,
         AstNode *proto_node)
 {
     AstNode *fn_def_node = proto_node->data.fn_proto.fn_def_node;
-    AstNode *extern_node = proto_node->data.fn_proto.extern_node;
     AstNode *struct_node = proto_node->data.fn_proto.struct_node;
+    bool is_extern = proto_node->data.fn_proto.is_extern;
     TypeTableEntry *struct_type;
     if (struct_node) {
         assert(struct_node->type == NodeTypeStructDecl);
@@ -914,7 +913,7 @@ static void preview_fn_proto(CodeGen *g, ImportTableEntry *import,
     auto entry = fn_table->maybe_get(proto_name);
     bool skip = false;
     bool is_internal = (proto_node->data.fn_proto.visib_mod != VisibModExport);
-    bool is_c_compat = !is_internal || extern_node;
+    bool is_c_compat = !is_internal || is_extern;
     bool is_pub = (proto_node->data.fn_proto.visib_mod != VisibModPrivate);
     if (entry) {
         add_node_error(g, proto_node,
@@ -922,7 +921,7 @@ static void preview_fn_proto(CodeGen *g, ImportTableEntry *import,
         proto_node->data.fn_proto.skip = true;
         skip = true;
     }
-    if (!extern_node && proto_node->data.fn_proto.is_var_args) {
+    if (!is_extern && proto_node->data.fn_proto.is_var_args) {
         add_node_error(g, proto_node,
                 buf_sprintf("variadic arguments only allowed in extern functions"));
     }
@@ -935,7 +934,7 @@ static void preview_fn_proto(CodeGen *g, ImportTableEntry *import,
     fn_table_entry->proto_node = proto_node;
     fn_table_entry->fn_def_node = fn_def_node;
     fn_table_entry->internal_linkage = !is_c_compat;
-    fn_table_entry->is_extern = extern_node;
+    fn_table_entry->is_extern = is_extern;
     fn_table_entry->label_table.init(8);
     fn_table_entry->member_of_struct = struct_type;
 
@@ -950,7 +949,7 @@ static void preview_fn_proto(CodeGen *g, ImportTableEntry *import,
 
     g->fn_protos.append(fn_table_entry);
 
-    if (!extern_node) {
+    if (!is_extern) {
         g->fn_defs.append(fn_table_entry);
     }
 
@@ -1021,19 +1020,6 @@ static void resolve_error_value_decl(CodeGen *g, ImportTableEntry *import, AstNo
 
 static void resolve_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode *node) {
     switch (node->type) {
-        case NodeTypeExternBlock:
-            for (int i = 0; i < node->data.extern_block.directives->length; i += 1) {
-                AstNode *directive_node = node->data.extern_block.directives->at(i);
-                Buf *name = &directive_node->data.directive.name;
-                Buf *param = &directive_node->data.directive.param;
-                if (buf_eql_str(name, "link")) {
-                    g->link_table.put(param, true);
-                } else {
-                    add_node_error(g, directive_node,
-                            buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
-                }
-            }
-            break;
         case NodeTypeFnProto:
             preview_fn_proto(g, import, node);
             break;
@@ -4051,7 +4037,6 @@ static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import,
         case NodeTypeParamDecl:
         case NodeTypeRoot:
         case NodeTypeRootExportDecl:
-        case NodeTypeExternBlock:
         case NodeTypeFnDef:
         case NodeTypeUse:
         case NodeTypeLabel:
@@ -4155,15 +4140,14 @@ static void analyze_top_level_decl(CodeGen *g, ImportTableEntry *import, AstNode
                 break;
             }
         case NodeTypeRootExportDecl:
-        case NodeTypeExternBlock:
         case NodeTypeUse:
         case NodeTypeVariableDeclaration:
         case NodeTypeErrorValueDecl:
+        case NodeTypeFnProto:
             // already took care of these
             break;
         case NodeTypeDirective:
         case NodeTypeParamDecl:
-        case NodeTypeFnProto:
         case NodeTypeFnDecl:
         case NodeTypeReturnExpr:
         case NodeTypeRoot:
@@ -4350,7 +4334,6 @@ static void collect_expr_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode
             break;
         case NodeTypeVariableDeclaration:
         case NodeTypeFnProto:
-        case NodeTypeExternBlock:
         case NodeTypeRootExportDecl:
         case NodeTypeFnDef:
         case NodeTypeRoot:
@@ -4453,16 +4436,6 @@ static void detect_top_level_decl_deps(CodeGen *g, ImportTableEntry *import, Ast
 
                 break;
             }
-        case NodeTypeExternBlock:
-            for (int fn_decl_i = 0; fn_decl_i < node->data.extern_block.fn_decls.length; fn_decl_i += 1) {
-                AstNode *fn_decl = node->data.extern_block.fn_decls.at(fn_decl_i);
-                assert(fn_decl->type == NodeTypeFnDecl);
-                AstNode *fn_proto = fn_decl->data.fn_decl.fn_proto;
-                fn_proto->data.fn_proto.extern_node = node;
-                detect_top_level_decl_deps(g, import, fn_proto);
-            }
-            resolve_top_level_decl(g, import, node);
-            break;
         case NodeTypeFnDef:
             node->data.fn_def.fn_proto->data.fn_proto.fn_def_node = node;
             detect_top_level_decl_deps(g, import, node->data.fn_def.fn_proto);
@@ -4786,7 +4759,6 @@ Expr *get_resolved_expr(AstNode *node) {
         case NodeTypeFnDef:
         case NodeTypeFnDecl:
         case NodeTypeParamDecl:
-        case NodeTypeExternBlock:
         case NodeTypeDirective:
         case NodeTypeUse:
         case NodeTypeStructDecl:
@@ -4832,7 +4804,6 @@ TopLevelDecl *get_resolved_top_level_decl(AstNode *node) {
         case NodeTypeFnDecl:
         case NodeTypeParamDecl:
         case NodeTypeBlock:
-        case NodeTypeExternBlock:
         case NodeTypeDirective:
         case NodeTypeStringLiteral:
         case NodeTypeCharLiteral:
src/codegen.cpp
@@ -2249,7 +2249,6 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
         case NodeTypeFnDef:
         case NodeTypeFnDecl:
         case NodeTypeParamDecl:
-        case NodeTypeExternBlock:
         case NodeTypeDirective:
         case NodeTypeUse:
         case NodeTypeStructDecl:
@@ -3001,18 +3000,6 @@ static void init(CodeGen *g, Buf *source_path) {
 
 }
 
-static bool directives_contains_link_libc(ZigList<AstNode*> *directives) {
-    for (int i = 0; i < directives->length; i += 1) {
-        AstNode *directive_node = directives->at(i);
-        if (buf_eql_str(&directive_node->data.directive.name, "link") &&
-            buf_eql_str(&directive_node->data.directive.param, "c"))
-        {
-            return true;
-        }
-    }
-    return false;
-}
-
 static int parse_version_string(Buf *buf, int *major, int *minor, int *patch) {
     char *dot1 = strstr(buf_ptr(buf), ".");
     if (!dot1)
@@ -3114,6 +3101,11 @@ static ImportTableEntry *codegen_add_code(CodeGen *g, Buf *abs_full_path,
                     Buf *param = &directive_node->data.directive.param;
                     if (buf_eql_str(name, "version")) {
                         set_root_export_version(g, param, directive_node);
+                    } else if (buf_eql_str(name, "link")) {
+                        g->link_table.put(param, true);
+                        if (buf_eql_str(param, "c")) {
+                            g->link_libc = true;
+                        }
                     } else {
                         add_node_error(g, directive_node,
                                 buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
@@ -3204,8 +3196,6 @@ static ImportTableEntry *codegen_add_code(CodeGen *g, Buf *abs_full_path,
             if (buf_eql_str(proto_name, "main") && !is_private) {
                 g->have_exported_main = true;
             }
-        } else if (top_level_decl->type == NodeTypeExternBlock) {
-            g->link_libc = directives_contains_link_libc(top_level_decl->data.extern_block.directives);
         }
     }
 
src/parser.cpp
@@ -105,8 +105,6 @@ const char *node_type_str(NodeType node_type) {
             return "ArrayAccessExpr";
         case NodeTypeSliceExpr:
             return "SliceExpr";
-        case NodeTypeExternBlock:
-            return "ExternBlock";
         case NodeTypeDirective:
             return "Directive";
         case NodeTypeReturnExpr:
@@ -258,15 +256,6 @@ void ast_print(AstNode *node, int indent) {
                 fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf));
                 break;
             }
-        case NodeTypeExternBlock:
-            {
-                fprintf(stderr, "%s\n", node_type_str(node->type));
-                for (int i = 0; i < node->data.extern_block.fn_decls.length; i += 1) {
-                    AstNode *child = node->data.extern_block.fn_decls.at(i);
-                    ast_print(child, indent + 2);
-                }
-                break;
-            }
         case NodeTypeFnDecl:
             fprintf(stderr, "%s\n", node_type_str(node->type));
             ast_print(node->data.fn_decl.fn_proto, indent + 2);
@@ -482,9 +471,9 @@ struct ParseContext {
     Buf *buf;
     AstNode *root;
     ZigList<Token> *tokens;
-    ZigList<AstNode *> *directive_list;
     ImportTableEntry *owner;
     ErrColor err_color;
+    bool parsed_root_export;
     uint32_t *next_node_index;
 };
 
@@ -2146,56 +2135,32 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, int *token_index, bool m
 }
 
 /*
-VariableDeclaration : option(FnVisibleMod) ("var" | "const") "Symbol" ("=" Expression | ":" PrefixOpExpression option("=" Expression))
+VariableDeclaration : ("var" | "const") "Symbol" ("=" Expression | ":" PrefixOpExpression option("=" Expression))
 */
-static AstNode *ast_parse_variable_declaration_expr(ParseContext *pc, int *token_index, bool mandatory) {
+static AstNode *ast_parse_variable_declaration_expr(ParseContext *pc, int *token_index, bool mandatory,
+        ZigList<AstNode*> *directives, VisibMod visib_mod)
+{
     Token *first_token = &pc->tokens->at(*token_index);
 
-    VisibMod visib_mod;
     bool is_const;
 
-    if (first_token->id == TokenIdKeywordPub) {
-        Token *next_token = &pc->tokens->at(*token_index + 1);
-        if (next_token->id == TokenIdKeywordVar ||
-            next_token->id == TokenIdKeywordConst)
-        {
-            visib_mod = VisibModPub;
-            is_const = (next_token->id == TokenIdKeywordConst);
-            *token_index += 2;
-        } else if (mandatory) {
-            ast_invalid_token_error(pc, next_token);
-        } else {
-            return nullptr;
-        }
-    } else if (first_token->id == TokenIdKeywordExport) {
-        Token *next_token = &pc->tokens->at(*token_index + 1);
-        if (next_token->id == TokenIdKeywordVar ||
-            next_token->id == TokenIdKeywordConst)
-        {
-            visib_mod = VisibModExport;
-            is_const = (next_token->id == TokenIdKeywordConst);
-            *token_index += 2;
-        } else if (mandatory) {
-            ast_invalid_token_error(pc, next_token);
-        } else {
-            return nullptr;
-        }
-    } else if (first_token->id == TokenIdKeywordVar ||
-               first_token->id == TokenIdKeywordConst)
-    {
-        visib_mod = VisibModPrivate;
-        is_const = (first_token->id == TokenIdKeywordConst);
-        *token_index += 1;
+    if (first_token->id == TokenIdKeywordVar) {
+        is_const = false;
+    } else if (first_token->id == TokenIdKeywordConst) {
+        is_const = true;
     } else if (mandatory) {
         ast_invalid_token_error(pc, first_token);
     } else {
         return nullptr;
     }
 
+    *token_index += 1;
+
     AstNode *node = ast_create_node(pc, NodeTypeVariableDeclaration, first_token);
 
     node->data.variable_declaration.is_const = is_const;
     node->data.variable_declaration.visib_mod = visib_mod;
+    node->data.variable_declaration.directives = directives;
 
     Token *name_token = ast_eat_token(pc, token_index, TokenIdSymbol);
     ast_buf_from_token(pc, name_token, &node->data.variable_declaration.symbol);
@@ -2643,7 +2608,8 @@ static AstNode *ast_parse_block(ParseContext *pc, int *token_index, bool mandato
         if (statement_node) {
             semicolon_expected = false;
         } else {
-            statement_node = ast_parse_variable_declaration_expr(pc, token_index, false);
+            statement_node = ast_parse_variable_declaration_expr(pc, token_index, false,
+                    nullptr, VisibModPrivate);
             if (statement_node) {
                 semicolon_expected = true;
             } else {
@@ -2677,47 +2643,25 @@ static AstNode *ast_parse_block(ParseContext *pc, int *token_index, bool mandato
 }
 
 /*
-FnProto : many(Directive) option(FnVisibleMod) "fn" "Symbol" ParamDeclList option("->" PrefixOpExpression)
+FnProto : "fn" "Symbol" ParamDeclList option("->" PrefixOpExpression)
 */
-static AstNode *ast_parse_fn_proto(ParseContext *pc, int *token_index, bool mandatory) {
+static AstNode *ast_parse_fn_proto(ParseContext *pc, int *token_index, bool mandatory,
+        ZigList<AstNode*> *directives, VisibMod visib_mod)
+{
     Token *first_token = &pc->tokens->at(*token_index);
 
-    VisibMod visib_mod;
-
-    if (first_token->id == TokenIdKeywordPub) {
-        Token *next_token = &pc->tokens->at(*token_index + 1);
-        if (next_token->id == TokenIdKeywordFn) {
-            visib_mod = VisibModPub;
-            *token_index += 2;
-        } else if (mandatory) {
-            ast_invalid_token_error(pc, first_token);
-        } else {
-            return nullptr;
-        }
-    } else if (first_token->id == TokenIdKeywordExport) {
-        Token *next_token = &pc->tokens->at(*token_index + 1);
-        if (next_token->id == TokenIdKeywordFn) {
-            visib_mod = VisibModExport;
-            *token_index += 2;
-        } else if (mandatory) {
-            ast_invalid_token_error(pc, first_token);
+    if (first_token->id != TokenIdKeywordFn) {
+        if (mandatory) {
+            ast_expect_token(pc, first_token, TokenIdKeywordFn);
         } else {
             return nullptr;
         }
-    } else if (first_token->id == TokenIdKeywordFn) {
-        visib_mod = VisibModPrivate;
-        *token_index += 1;
-    } else if (mandatory) {
-        ast_invalid_token_error(pc, first_token);
-    } else {
-        return nullptr;
     }
+    *token_index += 1;
 
     AstNode *node = ast_create_node(pc, NodeTypeFnProto, first_token);
     node->data.fn_proto.visib_mod = visib_mod;
-    node->data.fn_proto.directives = pc->directive_list;
-    pc->directive_list = nullptr;
-
+    node->data.fn_proto.directives = directives;
 
     Token *fn_name = &pc->tokens->at(*token_index);
     *token_index += 1;
@@ -2743,107 +2687,59 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc, int *token_index, bool mand
 /*
 FnDef : FnProto Block
 */
-static AstNode *ast_parse_fn_def(ParseContext *pc, int *token_index, bool mandatory) {
-    AstNode *fn_proto = ast_parse_fn_proto(pc, token_index, mandatory);
+static AstNode *ast_parse_fn_def(ParseContext *pc, int *token_index, bool mandatory,
+        ZigList<AstNode*> *directives, VisibMod visib_mod)
+{
+    AstNode *fn_proto = ast_parse_fn_proto(pc, token_index, mandatory, directives, visib_mod);
     if (!fn_proto)
         return nullptr;
     AstNode *node = ast_create_node_with_node(pc, NodeTypeFnDef, fn_proto);
 
     node->data.fn_def.fn_proto = fn_proto;
     node->data.fn_def.body = ast_parse_block(pc, token_index, true);
-
-    normalize_parent_ptrs(node);
-    return node;
-}
-
-/*
-FnDecl : FnProto token(Semicolon)
-*/
-static AstNode *ast_parse_fn_decl(ParseContext *pc, int *token_index) {
-    AstNode *fn_proto = ast_parse_fn_proto(pc, token_index, true);
-    AstNode *node = ast_create_node_with_node(pc, NodeTypeFnDecl, fn_proto);
-
-    node->data.fn_decl.fn_proto = fn_proto;
-
-    Token *semicolon = &pc->tokens->at(*token_index);
-    *token_index += 1;
-    ast_expect_token(pc, semicolon, TokenIdSemicolon);
-
     normalize_parent_ptrs(node);
     return node;
 }
 
 /*
-Directive : token(NumberSign) token(Symbol) token(LParen) token(String) token(RParen)
-*/
-/*
-ExternBlock : many(Directive) token(Extern) token(LBrace) many(FnProtoDecl) token(RBrace)
+ExternDecl : "extern" FnProto ";"
 */
-static AstNode *ast_parse_extern_block(ParseContext *pc, int *token_index, bool mandatory) {
+static AstNode *ast_parse_extern_decl(ParseContext *pc, int *token_index, bool mandatory,
+        ZigList<AstNode *> *directives, VisibMod visib_mod)
+{
     Token *extern_kw = &pc->tokens->at(*token_index);
     if (extern_kw->id != TokenIdKeywordExtern) {
-        if (mandatory)
+        if (mandatory) {
             ast_invalid_token_error(pc, extern_kw);
-        else
+        } else {
             return nullptr;
+        }
     }
     *token_index += 1;
 
-    AstNode *node = ast_create_node(pc, NodeTypeExternBlock, extern_kw);
-
-    node->data.extern_block.directives = pc->directive_list;
-    pc->directive_list = nullptr;
-
-    Token *l_brace = &pc->tokens->at(*token_index);
-    *token_index += 1;
-    ast_expect_token(pc, l_brace, TokenIdLBrace);
-
-    for (;;) {
-        Token *directive_token = &pc->tokens->at(*token_index);
-        assert(!pc->directive_list);
-        pc->directive_list = allocate<ZigList<AstNode*>>(1);
-        ast_parse_directives(pc, token_index, pc->directive_list);
-
-        Token *token = &pc->tokens->at(*token_index);
-        if (token->id == TokenIdRBrace) {
-            if (pc->directive_list->length > 0) {
-                ast_error(pc, directive_token, "invalid directive");
-            }
-            pc->directive_list = nullptr;
-
-            *token_index += 1;
-
-            normalize_parent_ptrs(node);
-            return node;
-        } else {
-            AstNode *child = ast_parse_fn_decl(pc, token_index);
-            node->data.extern_block.fn_decls.append(child);
-        }
-    }
+    AstNode *node = ast_parse_fn_proto(pc, token_index, true, directives, visib_mod);
 
+    ast_eat_token(pc, token_index, TokenIdSemicolon);
 
-    zig_unreachable();
+    node->data.fn_proto.is_extern = true;
+    normalize_parent_ptrs(node);
+    return node;
 }
 
 /*
-RootExportDecl : many(Directive) token(Export) token(Symbol) token(String) token(Semicolon)
+RootExportDecl : "export" "Symbol" "String" ";"
 */
-static AstNode *ast_parse_root_export_decl(ParseContext *pc, int *token_index, bool mandatory) {
-    assert(mandatory == false);
-
-    Token *export_kw = &pc->tokens->at(*token_index);
-    if (export_kw->id != TokenIdKeywordExport)
-        return nullptr;
-
-    Token *export_type = &pc->tokens->at(*token_index + 1);
+static AstNode *ast_parse_root_export_decl(ParseContext *pc, int *token_index,
+        ZigList<AstNode*> *directives)
+{
+    Token *export_type = &pc->tokens->at(*token_index);
     if (export_type->id != TokenIdSymbol)
         return nullptr;
 
-    *token_index += 2;
+    *token_index += 1;
 
-    AstNode *node = ast_create_node(pc, NodeTypeRootExportDecl, export_kw);
-    node->data.root_export_decl.directives = pc->directive_list;
-    pc->directive_list = nullptr;
+    AstNode *node = ast_create_node(pc, NodeTypeRootExportDecl, export_type);
+    node->data.root_export_decl.directives = directives;
 
     ast_buf_from_token(pc, export_type, &node->data.root_export_decl.type);
 
@@ -2862,11 +2758,13 @@ static AstNode *ast_parse_root_export_decl(ParseContext *pc, int *token_index, b
 }
 
 /*
-Use : many(Directive) token(Use) token(String) token(Semicolon)
+Import : "import" "String" ";"
 */
-static AstNode *ast_parse_use(ParseContext *pc, int *token_index) {
-    Token *use_kw = &pc->tokens->at(*token_index);
-    if (use_kw->id != TokenIdKeywordImport)
+static AstNode *ast_parse_import(ParseContext *pc, int *token_index,
+        ZigList<AstNode*> *directives, VisibMod visib_mod)
+{
+    Token *import_kw = &pc->tokens->at(*token_index);
+    if (import_kw->id != TokenIdKeywordImport)
         return nullptr;
     *token_index += 1;
 
@@ -2878,59 +2776,35 @@ static AstNode *ast_parse_use(ParseContext *pc, int *token_index) {
     *token_index += 1;
     ast_expect_token(pc, semicolon, TokenIdSemicolon);
 
-    AstNode *node = ast_create_node(pc, NodeTypeUse, use_kw);
+    AstNode *node = ast_create_node(pc, NodeTypeUse, import_kw);
+    node->data.use.visib_mod = visib_mod;
+    node->data.use.directives = directives;
 
     parse_string_literal(pc, use_name, &node->data.use.path, nullptr, nullptr);
-
-    node->data.use.directives = pc->directive_list;
-    pc->directive_list = nullptr;
-
     normalize_parent_ptrs(node);
     return node;
 }
 
 /*
-ContainerDecl : many(Directive) option(FnVisibleMod) (token(Struct) | token(Enum)) token(Symbol) token(LBrace) many(StructMember) token(RBrace)
-StructMember: StructField | FnDecl
-StructField : token(Symbol) option(token(Colon) Expression) token(Comma))
+ContainerDecl : ("struct" | "enum") "Symbol" "{" many(StructMember) "}"
+StructMember: many(Directive) option(VisibleMod) (StructField | FnDef)
+StructField : "Symbol" option(":" Expression) ",")
 */
-static AstNode *ast_parse_struct_decl(ParseContext *pc, int *token_index) {
+static AstNode *ast_parse_struct_decl(ParseContext *pc, int *token_index,
+        ZigList<AstNode*> *directives, VisibMod visib_mod)
+{
     Token *first_token = &pc->tokens->at(*token_index);
 
-    VisibMod visib_mod;
     ContainerKind kind;
 
-    if (first_token->id == TokenIdKeywordPub) {
-        Token *next_token = &pc->tokens->at(*token_index + 1);
-        if (next_token->id == TokenIdKeywordStruct ||
-            next_token->id == TokenIdKeywordEnum)
-        {
-            visib_mod = VisibModPub;
-            kind = (next_token->id == TokenIdKeywordStruct) ? ContainerKindStruct : ContainerKindEnum;
-            *token_index += 2;
-        } else {
-            return nullptr;
-        }
-    } else if (first_token->id == TokenIdKeywordExport) {
-        Token *next_token = &pc->tokens->at(*token_index + 1);
-        if (next_token->id == TokenIdKeywordStruct ||
-            next_token->id == TokenIdKeywordEnum)
-        {
-            visib_mod = VisibModExport;
-            kind = (next_token->id == TokenIdKeywordStruct) ? ContainerKindStruct : ContainerKindEnum;
-            *token_index += 2;
-        } else {
-            return nullptr;
-        }
-    } else if (first_token->id == TokenIdKeywordStruct ||
-               first_token->id == TokenIdKeywordEnum)
-    {
-        visib_mod = VisibModPrivate;
-        kind = (first_token->id == TokenIdKeywordStruct) ? ContainerKindStruct : ContainerKindEnum;
-        *token_index += 1;
+    if (first_token->id == TokenIdKeywordStruct) {
+        kind = ContainerKindStruct;
+    } else if (first_token->id == TokenIdKeywordEnum) {
+        kind = ContainerKindEnum;
     } else {
         return nullptr;
     }
+    *token_index += 1;
 
     Token *struct_name = ast_eat_token(pc, token_index, TokenIdSymbol);
 
@@ -2938,19 +2812,28 @@ static AstNode *ast_parse_struct_decl(ParseContext *pc, int *token_index) {
     node->data.struct_decl.kind = kind;
     ast_buf_from_token(pc, struct_name, &node->data.struct_decl.name);
     node->data.struct_decl.visib_mod = visib_mod;
+    node->data.struct_decl.directives = directives;
 
     ast_eat_token(pc, token_index, TokenIdLBrace);
 
-    node->data.struct_decl.directives = pc->directive_list;
-    pc->directive_list = nullptr;
-
     for (;;) {
-        assert(!pc->directive_list);
-        pc->directive_list = allocate<ZigList<AstNode*>>(1);
         Token *directive_token = &pc->tokens->at(*token_index);
-        ast_parse_directives(pc, token_index, pc->directive_list);
+        ZigList<AstNode *> *directive_list = allocate<ZigList<AstNode*>>(1);
+        ast_parse_directives(pc, token_index, directive_list);
+
+        Token *visib_tok = &pc->tokens->at(*token_index);
+        VisibMod visib_mod;
+        if (visib_tok->id == TokenIdKeywordPub) {
+            *token_index += 1;
+            visib_mod = VisibModPub;
+        } else if (visib_tok->id == TokenIdKeywordExport) {
+            *token_index += 1;
+            visib_mod = VisibModExport;
+        } else {
+            visib_mod = VisibModPrivate;
+        }
 
-        AstNode *fn_def_node = ast_parse_fn_def(pc, token_index, false);
+        AstNode *fn_def_node = ast_parse_fn_def(pc, token_index, false, directive_list, visib_mod);
         if (fn_def_node) {
             node->data.struct_decl.fns.append(fn_def_node);
             continue;
@@ -2959,10 +2842,9 @@ static AstNode *ast_parse_struct_decl(ParseContext *pc, int *token_index) {
         Token *token = &pc->tokens->at(*token_index);
 
         if (token->id == TokenIdRBrace) {
-            if (pc->directive_list->length > 0) {
+            if (directive_list->length > 0) {
                 ast_error(pc, directive_token, "invalid directive");
             }
-            pc->directive_list = nullptr;
 
             *token_index += 1;
             break;
@@ -2970,8 +2852,8 @@ static AstNode *ast_parse_struct_decl(ParseContext *pc, int *token_index) {
             AstNode *field_node = ast_create_node(pc, NodeTypeStructField, token);
             *token_index += 1;
 
-            field_node->data.struct_field.directives = pc->directive_list;
-            pc->directive_list = nullptr;
+            field_node->data.struct_field.visib_mod = visib_mod;
+            field_node->data.struct_field.directives = directive_list;
 
             ast_buf_from_token(pc, token, &field_node->data.struct_field.name);
 
@@ -2997,47 +2879,24 @@ static AstNode *ast_parse_struct_decl(ParseContext *pc, int *token_index) {
 }
 
 /*
-ErrorValueDecl : option(FnVisibleMod) "error" "Symbol"
+ErrorValueDecl : "error" "Symbol" ";"
 */
-static AstNode *ast_parse_error_value_decl(ParseContext *pc, int *token_index, bool mandatory) {
+static AstNode *ast_parse_error_value_decl(ParseContext *pc, int *token_index,
+        ZigList<AstNode*> *directives, VisibMod visib_mod)
+{
     Token *first_token = &pc->tokens->at(*token_index);
 
-    VisibMod visib_mod;
-
-    if (first_token->id == TokenIdKeywordPub) {
-        Token *next_token = &pc->tokens->at(*token_index + 1);
-        if (next_token->id == TokenIdKeywordError) {
-            visib_mod = VisibModPub;
-            *token_index += 2;
-        } else if (mandatory) {
-            ast_invalid_token_error(pc, next_token);
-        } else {
-            return nullptr;
-        }
-    } else if (first_token->id == TokenIdKeywordExport) {
-        Token *next_token = &pc->tokens->at(*token_index + 1);
-        if (next_token->id == TokenIdKeywordError) {
-            visib_mod = VisibModExport;
-            *token_index += 2;
-        } else if (mandatory) {
-            ast_invalid_token_error(pc, next_token);
-        } else {
-            return nullptr;
-        }
-    } else if (first_token->id == TokenIdKeywordError) {
-        visib_mod = VisibModPrivate;
-        *token_index += 1;
-    } else if (mandatory) {
-        ast_invalid_token_error(pc, first_token);
-    } else {
+    if (first_token->id != TokenIdKeywordError) {
         return nullptr;
     }
+    *token_index += 1;
 
     Token *name_tok = ast_eat_token(pc, token_index, TokenIdSymbol);
     ast_eat_token(pc, token_index, TokenIdSemicolon);
 
     AstNode *node = ast_create_node(pc, NodeTypeErrorValueDecl, first_token);
     node->data.error_value_decl.visib_mod = visib_mod;
+    node->data.error_value_decl.directives = directives;
     ast_buf_from_token(pc, name_tok, &node->data.error_value_decl.name);
 
     normalize_parent_ptrs(node);
@@ -3045,63 +2904,79 @@ static AstNode *ast_parse_error_value_decl(ParseContext *pc, int *token_index, b
 }
 
 /*
-TopLevelDecl : FnDef | ExternBlock | RootExportDecl | Import | ContainerDecl | VariableDeclaration | ErrorValueDecl
+TopLevelDecl : many(Directive) option(FnVisibleMod) (FnDef | ExternFnProto | RootExportDecl | Import | ContainerDecl | VariableDeclaration | ErrorValueDecl | CImportDecl)
 */
 static void ast_parse_top_level_decls(ParseContext *pc, int *token_index, ZigList<AstNode *> *top_level_decls) {
     for (;;) {
         Token *directive_token = &pc->tokens->at(*token_index);
-        assert(!pc->directive_list);
-        pc->directive_list = allocate<ZigList<AstNode*>>(1);
-        ast_parse_directives(pc, token_index, pc->directive_list);
+        ZigList<AstNode *> *directives = allocate<ZigList<AstNode*>>(1);
+        ast_parse_directives(pc, token_index, directives);
 
-        AstNode *root_export_decl_node = ast_parse_root_export_decl(pc, token_index, false);
-        if (root_export_decl_node) {
-            top_level_decls->append(root_export_decl_node);
-            continue;
+        Token *visib_tok = &pc->tokens->at(*token_index);
+        VisibMod visib_mod;
+        if (visib_tok->id == TokenIdKeywordPub) {
+            *token_index += 1;
+            visib_mod = VisibModPub;
+        } else if (visib_tok->id == TokenIdKeywordExport) {
+            *token_index += 1;
+            visib_mod = VisibModExport;
+        } else {
+            visib_mod = VisibModPrivate;
         }
 
-        AstNode *fn_def_node = ast_parse_fn_def(pc, token_index, false);
+        bool try_to_parse_root_export = (visib_mod == VisibModExport && !pc->parsed_root_export);
+        pc->parsed_root_export = true;
+
+        if (try_to_parse_root_export) {
+            AstNode *root_export_decl_node = ast_parse_root_export_decl(pc, token_index, directives);
+            if (root_export_decl_node) {
+                top_level_decls->append(root_export_decl_node);
+                continue;
+            }
+        }
+
+        AstNode *fn_def_node = ast_parse_fn_def(pc, token_index, false, directives, visib_mod);
         if (fn_def_node) {
             top_level_decls->append(fn_def_node);
             continue;
         }
 
-        AstNode *extern_node = ast_parse_extern_block(pc, token_index, false);
-        if (extern_node) {
-            top_level_decls->append(extern_node);
+        AstNode *fn_proto_node = ast_parse_extern_decl(pc, token_index, false, directives, visib_mod);
+        if (fn_proto_node) {
+            top_level_decls->append(fn_proto_node);
             continue;
         }
 
-        AstNode *use_node = ast_parse_use(pc, token_index);
-        if (use_node) {
-            top_level_decls->append(use_node);
+        AstNode *import_node = ast_parse_import(pc, token_index, directives, visib_mod);
+        if (import_node) {
+            top_level_decls->append(import_node);
             continue;
         }
 
-        AstNode *struct_node = ast_parse_struct_decl(pc, token_index);
+        AstNode *struct_node = ast_parse_struct_decl(pc, token_index, directives, visib_mod);
         if (struct_node) {
             top_level_decls->append(struct_node);
             continue;
         }
 
-        if (pc->directive_list->length > 0) {
-            ast_error(pc, directive_token, "invalid directive");
-        }
-        pc->directive_list = nullptr;
-
-        AstNode *var_decl_node = ast_parse_variable_declaration_expr(pc, token_index, false);
+        AstNode *var_decl_node = ast_parse_variable_declaration_expr(pc, token_index, false,
+                directives, visib_mod);
         if (var_decl_node) {
             ast_eat_token(pc, token_index, TokenIdSemicolon);
             top_level_decls->append(var_decl_node);
             continue;
         }
 
-        AstNode *error_value_node = ast_parse_error_value_decl(pc, token_index, false);
+        AstNode *error_value_node = ast_parse_error_value_decl(pc, token_index, directives, visib_mod);
         if (error_value_node) {
             top_level_decls->append(error_value_node);
             continue;
         }
 
+        if (directives->length > 0) {
+            ast_error(pc, directive_token, "invalid directive");
+        }
+
         return;
     }
     zig_unreachable();
@@ -3175,10 +3050,6 @@ void normalize_parent_ptrs(AstNode *node) {
         case NodeTypeBlock:
             set_list_fields(&node->data.block.statements);
             break;
-        case NodeTypeExternBlock:
-            set_list_fields(node->data.extern_block.directives);
-            set_list_fields(&node->data.extern_block.fn_decls);
-            break;
         case NodeTypeDirective:
             // none
             break;
test/run_tests.cpp
@@ -97,10 +97,8 @@ static TestCase *add_compile_fail_case(const char *case_name, const char *source
 static void add_compiling_test_cases(void) {
     add_simple_case("hello world with libc", R"SOURCE(
 #link("c")
-extern {
-    fn puts(s: &const u8) -> c_int;
-}
-
+export executable "test";
+extern fn puts(s: &const u8) -> c_int;
 export fn main(argc: c_int, argv: &&u8) -> c_int {
     puts(c"Hello, world!");
     return 0;
@@ -482,9 +480,8 @@ pub fn main(args: [][]u8) -> %void {
 
     add_simple_case("number literals", R"SOURCE(
 #link("c")
-extern {
-    fn printf(__format: &const u8, ...) -> c_int;
-}
+export executable "test";
+extern fn printf(__format: &const u8, ...) -> c_int;
 
 export fn main(argc: c_int, argv: &&u8) -> c_int {
     printf(c"\n");
@@ -1321,13 +1318,11 @@ fn a() {}
 
     add_compile_fail_case("bad directive", R"SOURCE(
 #bogus1("")
-extern {
-    fn b();
-}
+extern fn b();
 #bogus2("")
 fn a() {}
     )SOURCE", 2, ".tmp_source.zig:2:1: error: invalid directive: 'bogus1'",
-                 ".tmp_source.zig:6:1: error: invalid directive: 'bogus2'");
+                 ".tmp_source.zig:4:1: error: invalid directive: 'bogus2'");
 
     add_compile_fail_case("unreachable with return", R"SOURCE(
 fn a() -> unreachable {return;}
@@ -1668,11 +1663,9 @@ fn f() {
     )SOURCE", 1, ".tmp_source.zig:6:9: error: multiple else prongs in switch expression");
 
     add_compile_fail_case("global variable initializer must be constant expression", R"SOURCE(
-extern {
-    fn foo() -> i32;
-}
+extern fn foo() -> i32;
 const x = foo();
-    )SOURCE", 1, ".tmp_source.zig:5:11: error: global variable initializer requires constant expression");
+    )SOURCE", 1, ".tmp_source.zig:3:11: error: global variable initializer requires constant expression");
 
     add_compile_fail_case("non compile time string concatenation", R"SOURCE(
 fn f(s: []u8) -> []u8 {