Commit dfda85e870

Andrew Kelley <superjoe30@gmail.com>
2015-12-09 09:03:04
ability to call external variadic functions
1 parent 4eff5f1
example/hello_world/hello.zig
@@ -2,11 +2,11 @@ export executable "hello";
 
 #link("c")
 extern {
-    fn puts(s: *const u8) -> i32;
-    fn exit(code: i32) -> unreachable;
+    fn printf(__format: *const u8, ...) -> i32;
+    fn exit(__status: i32) -> unreachable;
 }
 
 export fn _start() -> unreachable {
-    puts("Hello, world!");
+    printf("Hello, world!\n");
     exit(0);
 }
src/analyze.cpp
@@ -163,9 +163,12 @@ static TypeTableEntry *resolve_type(CodeGen *g, AstNode *node) {
             {
                 resolve_type(g, node->data.type.child_type);
                 TypeTableEntry *child_type = node->data.type.child_type->codegen_node->data.type_node.entry;
+                assert(child_type);
                 if (child_type == g->builtin_types.entry_unreachable) {
                     add_node_error(g, node,
                             buf_create_from_str("pointer to unreachable not allowed"));
+                } else if (child_type->id == TypeTableEntryIdInvalid) {
+                    return child_type;
                 }
                 type_node->entry = get_pointer_to_type(g, child_type, node->data.type.is_const);
                 return type_node->entry;
@@ -312,6 +315,10 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import,
                         skip = true;
                     }
                 }
+                if (proto_node->data.fn_proto.is_var_args) {
+                    add_node_error(g, node,
+                            buf_sprintf("variadic arguments only allowed in extern functions"));
+                }
                 if (!skip) {
                     FnTableEntry *fn_table_entry = allocate<FnTableEntry>(1);
                     fn_table_entry->import_entry = import;
@@ -743,7 +750,13 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
                     // count parameters
                     int expected_param_count = fn_proto->params.length;
                     int actual_param_count = node->data.fn_call_expr.params.length;
-                    if (expected_param_count != actual_param_count) {
+                    if (fn_proto->is_var_args) {
+                        if (actual_param_count < expected_param_count) {
+                            add_node_error(g, node,
+                                    buf_sprintf("wrong number of arguments. Expected at least %d, got %d.",
+                                        expected_param_count, actual_param_count));
+                        }
+                    } else if (expected_param_count != actual_param_count) {
                         add_node_error(g, node,
                                 buf_sprintf("wrong number of arguments. Expected %d, got %d.",
                                     expected_param_count, actual_param_count));
src/codegen.cpp
@@ -138,19 +138,32 @@ static LLVMValueRef gen_fn_call_expr(CodeGen *g, AstNode *node) {
         fn_table_entry = g->fn_table.get(name);
 
     assert(fn_table_entry->proto_node->type == NodeTypeFnProto);
-    int expected_param_count = fn_table_entry->proto_node->data.fn_proto.params.length;
+    AstNodeFnProto *fn_proto_data = &fn_table_entry->proto_node->data.fn_proto;
+
+    int expected_param_count = fn_proto_data->params.length;
     int actual_param_count = node->data.fn_call_expr.params.length;
-    assert(expected_param_count == actual_param_count);
+    bool is_var_args = fn_proto_data->is_var_args;
+    assert((is_var_args && actual_param_count >= expected_param_count) ||
+            actual_param_count == expected_param_count);
 
     // don't really include void values
-    int gen_param_count = count_non_void_params(g, &fn_table_entry->proto_node->data.fn_proto.params);
+    int gen_param_count;
+    if (is_var_args) {
+        gen_param_count = actual_param_count;
+    } else {
+        gen_param_count = count_non_void_params(g, &fn_table_entry->proto_node->data.fn_proto.params);
+    }
     LLVMValueRef *gen_param_values = allocate<LLVMValueRef>(gen_param_count);
 
+    int loop_end = max(gen_param_count, actual_param_count);
+
     int gen_param_index = 0;
-    for (int i = 0; i < actual_param_count; i += 1) {
+    for (int i = 0; i < loop_end; i += 1) {
         AstNode *expr_node = node->data.fn_call_expr.params.at(i);
         LLVMValueRef param_value = gen_expr(g, expr_node);
-        if (!is_param_decl_type_void(g, fn_table_entry->proto_node->data.fn_proto.params.at(i))) {
+        if (is_var_args ||
+            !is_param_decl_type_void(g, fn_table_entry->proto_node->data.fn_proto.params.at(i)))
+        {
             gen_param_values[gen_param_index] = param_value;
             gen_param_index += 1;
         }
@@ -773,7 +786,7 @@ static void do_code_gen(CodeGen *g) {
             param_types[gen_param_index] = to_llvm_type(type_node);
             gen_param_index += 1;
         }
-        LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, param_count, 0);
+        LLVMTypeRef function_type = LLVMFunctionType(ret_type, param_types, param_count, fn_proto->is_var_args);
         LLVMValueRef fn = LLVMAddFunction(g->module, buf_ptr(&fn_proto->name), function_type);
 
         LLVMSetLinkage(fn, fn_table_entry->internal_linkage ? LLVMInternalLinkage : LLVMExternalLinkage);
src/parseh.cpp
@@ -132,10 +132,10 @@ static bool resolves_to_void(ParseH *p, CXType raw_type) {
 static Buf *to_zig_type(ParseH *p, CXType raw_type) {
     if (raw_type.kind == CXType_Unexposed) {
         CXType canonical = clang_getCanonicalType(raw_type);
-        if (canonical.kind != CXType_Unexposed)
-            return to_zig_type(p, canonical);
-        else
+        if (canonical.kind == CXType_Unexposed)
             zig_panic("clang C api insufficient");
+        else
+            return to_zig_type(p, canonical);
     }
     switch (raw_type.kind) {
         case CXType_Invalid:
@@ -453,6 +453,10 @@ static enum CXChildVisitResult fn_visitor(CXCursor cursor, CXCursor parent, CXCl
             } else if (underlying_type.kind == CXType_Record) {
                 CXCursor decl_cursor = clang_getTypeDeclaration(underlying_type);
                 skip_typedef = handle_struct_cursor(p, decl_cursor, clang_getCString(name), false);
+            } else if (underlying_type.kind == CXType_Invalid) {
+                fprintf(stderr, "warning: invalid type\n");
+                print_location(p);
+                skip_typedef = true;
             } else {
                 skip_typedef = false;
             }
src/parser.cpp
@@ -517,32 +517,39 @@ static AstNode *ast_parse_type(ParseContext *pc, int token_index, int *new_token
 }
 
 /*
-ParamDecl : token(Symbol) token(Colon) Type
+ParamDecl : token(Symbol) token(Colon) Type | token(Ellipse)
 */
 static AstNode *ast_parse_param_decl(ParseContext *pc, int token_index, int *new_token_index) {
     Token *param_name = &pc->tokens->at(token_index);
     token_index += 1;
-    ast_expect_token(pc, param_name, TokenIdSymbol);
 
-    AstNode *node = ast_create_node(pc, NodeTypeParamDecl, param_name);
+    if (param_name->id == TokenIdSymbol) {
+        AstNode *node = ast_create_node(pc, NodeTypeParamDecl, param_name);
 
+        ast_buf_from_token(pc, param_name, &node->data.param_decl.name);
 
-    ast_buf_from_token(pc, param_name, &node->data.param_decl.name);
-
-    Token *colon = &pc->tokens->at(token_index);
-    token_index += 1;
-    ast_expect_token(pc, colon, TokenIdColon);
+        Token *colon = &pc->tokens->at(token_index);
+        token_index += 1;
+        ast_expect_token(pc, colon, TokenIdColon);
 
-    node->data.param_decl.type = ast_parse_type(pc, token_index, &token_index);
+        node->data.param_decl.type = ast_parse_type(pc, token_index, &token_index);
 
-    *new_token_index = token_index;
-    return node;
+        *new_token_index = token_index;
+        return node;
+    } else if (param_name->id == TokenIdEllipse) {
+        *new_token_index = token_index;
+        return nullptr;
+    } else {
+        ast_invalid_token_error(pc, param_name);
+    }
 }
 
 
 static void ast_parse_param_decl_list(ParseContext *pc, int token_index, int *new_token_index,
-        ZigList<AstNode *> *params)
+        ZigList<AstNode *> *params, bool *is_var_args)
 {
+    *is_var_args = false;
+
     Token *l_paren = &pc->tokens->at(token_index);
     token_index += 1;
     ast_expect_token(pc, l_paren, TokenIdLParen);
@@ -556,13 +563,21 @@ static void ast_parse_param_decl_list(ParseContext *pc, int token_index, int *ne
 
     for (;;) {
         AstNode *param_decl_node = ast_parse_param_decl(pc, token_index, &token_index);
-        params->append(param_decl_node);
+        bool expect_end = false;
+        if (param_decl_node) {
+            params->append(param_decl_node);
+        } else {
+            *is_var_args = true;
+            expect_end = true;
+        }
 
         Token *token = &pc->tokens->at(token_index);
         token_index += 1;
         if (token->id == TokenIdRParen) {
             *new_token_index = token_index;
             return;
+        } else if (expect_end) {
+            ast_invalid_token_error(pc, token);
         } else {
             ast_expect_token(pc, token, TokenIdComma);
         }
@@ -1421,7 +1436,8 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc, int *token_index, bool mand
     ast_buf_from_token(pc, fn_name, &node->data.fn_proto.name);
 
 
-    ast_parse_param_decl_list(pc, *token_index, token_index, &node->data.fn_proto.params);
+    ast_parse_param_decl_list(pc, *token_index, token_index,
+            &node->data.fn_proto.params, &node->data.fn_proto.is_var_args);
 
     Token *arrow = &pc->tokens->at(*token_index);
     if (arrow->id == TokenIdArrow) {
src/parser.hpp
@@ -63,6 +63,7 @@ struct AstNodeFnProto {
     Buf name;
     ZigList<AstNode *> params;
     AstNode *return_type;
+    bool is_var_args;
 };
 
 struct AstNodeFnDef {
src/tokenizer.cpp
@@ -104,6 +104,8 @@ enum TokenizeState {
     TokenizeStateBang,
     TokenizeStateLessThan,
     TokenizeStateGreaterThan,
+    TokenizeStateDot,
+    TokenizeStateDotDot,
     TokenizeStateError,
 };
 
@@ -323,10 +325,40 @@ void tokenize(Buf *buf, Tokenization *out) {
                         begin_token(&t, TokenIdCmpGreaterThan);
                         t.state = TokenizeStateGreaterThan;
                         break;
+                    case '.':
+                        begin_token(&t, TokenIdDot);
+                        t.state = TokenizeStateDot;
+                        break;
                     default:
                         tokenize_error(&t, "invalid character: '%c'", c);
                 }
                 break;
+            case TokenizeStateDot:
+                switch (c) {
+                    case '.':
+                        t.state = TokenizeStateDotDot;
+                        t.cur_tok->id = TokenIdEllipse;
+                        break;
+                    default:
+                        t.pos -= 1;
+                        end_token(&t);
+                        t.state = TokenizeStateStart;
+                        continue;
+                }
+                break;
+            case TokenizeStateDotDot:
+                switch (c) {
+                    case '.':
+                        t.state = TokenizeStateStart;
+                        end_token(&t);
+                        break;
+                    default:
+                        t.pos -= 1;
+                        end_token(&t);
+                        t.state = TokenizeStateStart;
+                        continue;
+                }
+                break;
             case TokenizeStateGreaterThan:
                 switch (c) {
                     case '=':
@@ -561,9 +593,11 @@ void tokenize(Buf *buf, Tokenization *out) {
         case TokenizeStateBang:
         case TokenizeStateLessThan:
         case TokenizeStateGreaterThan:
+        case TokenizeStateDot:
             end_token(&t);
             break;
         case TokenizeStateSawSlash:
+        case TokenizeStateDotDot:
             tokenize_error(&t, "unexpected EOF");
             break;
         case TokenizeStateLineComment:
@@ -637,6 +671,8 @@ static const char * token_name(Token *token) {
         case TokenIdBitShiftRight: return "BitShiftRight";
         case TokenIdSlash: return "Slash";
         case TokenIdPercent: return "Percent";
+        case TokenIdDot: return "Dot";
+        case TokenIdEllipse: return "Ellipse";
     }
     return "(invalid token)";
 }
src/tokenizer.hpp
@@ -64,6 +64,8 @@ enum TokenId {
     TokenIdBitShiftRight,
     TokenIdSlash,
     TokenIdPercent,
+    TokenIdDot,
+    TokenIdEllipse,
 };
 
 struct Token {
test/run_tests.cpp
@@ -577,6 +577,11 @@ fn f() {
                  ".tmp_source.zig:5:8: error: array subscripts must be integers",
                  ".tmp_source.zig:5:19: error: array access of non-array",
                  ".tmp_source.zig:5:19: error: array subscripts must be integers");
+
+    add_compile_fail_case("variadic functions only allowed in extern", R"SOURCE(
+fn f(...) {}
+    )SOURCE", 1, ".tmp_source.zig:2:1: error: variadic arguments only allowed in extern functions");
+
 }
 
 static void print_compiler_invocation(TestCase *test_case, Buf *zig_stderr) {
README.md
@@ -58,7 +58,6 @@ compromises backward compatibility.
  * structs
  * loops
  * enums
- * calling external variadic functions and exporting variadic functions
  * inline assembly and syscalls
  * conditional compilation and ability to check target platform and architecture
  * main function with command line arguments
@@ -69,6 +68,9 @@ compromises backward compatibility.
  * static initializers
  * assert
  * function pointers
+ * hex literal, binary literal, float literal, hex float literal
+ * += and -= operators
+ * fix a + b + c
  * running code at compile time
  * standard library print functions
  * panic! macro or statement that prints a stack trace to stderr in debug mode
@@ -144,7 +146,7 @@ FnDef : FnProto Block
 
 ParamDeclList : token(LParen) list(ParamDecl, token(Comma)) token(RParen)
 
-ParamDecl : token(Symbol) token(Colon) Type
+ParamDecl : token(Symbol) token(Colon) Type | token(Ellipse)
 
 Type : token(Symbol) | token(Unreachable) | token(Void) | PointerType | ArrayType