diff --git a/README.md b/README.md index 8ecbb723aa..da4a14f35e 100644 --- a/README.md +++ b/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 diff --git a/example/hello_world/hello.zig b/example/hello_world/hello.zig index d950d9aa17..ea24b4d921 100644 --- a/example/hello_world/hello.zig +++ b/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); } diff --git a/src/analyze.cpp b/src/analyze.cpp index 4436457b01..e4c3b2ca7b 100644 --- a/src/analyze.cpp +++ b/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(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)); diff --git a/src/codegen.cpp b/src/codegen.cpp index d3e76bc8f4..bc8ece3fe8 100644 --- a/src/codegen.cpp +++ b/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(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); diff --git a/src/parseh.cpp b/src/parseh.cpp index b69f85b8ae..40c317d5d8 100644 --- a/src/parseh.cpp +++ b/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; } diff --git a/src/parser.cpp b/src/parser.cpp index 6aafa8781a..0ef3e6664b 100644 --- a/src/parser.cpp +++ b/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 *params) + ZigList *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) { diff --git a/src/parser.hpp b/src/parser.hpp index 0d3723712d..4dfac7472c 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -63,6 +63,7 @@ struct AstNodeFnProto { Buf name; ZigList params; AstNode *return_type; + bool is_var_args; }; struct AstNodeFnDef { diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 5244e4174b..e1249bbed1 100644 --- a/src/tokenizer.cpp +++ b/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)"; } diff --git a/src/tokenizer.hpp b/src/tokenizer.hpp index f0e4e5613e..6f15b5aed5 100644 --- a/src/tokenizer.hpp +++ b/src/tokenizer.hpp @@ -64,6 +64,8 @@ enum TokenId { TokenIdBitShiftRight, TokenIdSlash, TokenIdPercent, + TokenIdDot, + TokenIdEllipse, }; struct Token { diff --git a/test/run_tests.cpp b/test/run_tests.cpp index 7bbe6be97b..d3bc5f8bde 100644 --- a/test/run_tests.cpp +++ b/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) {