| author | |
| committer | |
| log | d697404f640d25be5fc1a02c5144362c2b329902 |
| tree | 73e26c0efea7b15335fe973142e6f91df63fdda8 |
| parent | 15ba5bc54e286fb64d67e38857ede4b0dac9c841 |
8 files changed, 442 insertions(+), 67 deletions(-)
doc/langref.md+4| ... | @@ -78,6 +78,10 @@ AsmOutput : token(Colon) list(AsmOutputItem, token(Comma)) option(AsmInput) | ... | @@ -78,6 +78,10 @@ AsmOutput : token(Colon) list(AsmOutputItem, token(Comma)) option(AsmInput) |
| 78 | 78 | ||
| 79 | AsmInput : token(Colon) list(AsmInputItem, token(Comma)) option(AsmClobbers) | 79 | AsmInput : token(Colon) list(AsmInputItem, token(Comma)) option(AsmClobbers) |
| 80 | 80 | ||
| 81 | AsmOutputItem : token(LBracket) token(Symbol) token(RBracket) token(String) token(LParen) token(Symbol) token(RParen) | ||
| 82 | |||
| 83 | AsmInputItem : token(LBracket) token(Symbol) token(RBracket) token(String) token(LParen) Expression token(RParen) | ||
| 84 | |||
| 81 | AsmClobbers: token(Colon) list(token(String), token(Comma)) | 85 | AsmClobbers: token(Colon) list(token(String), token(Comma)) |
| 82 | 86 | ||
| 83 | AssignmentExpression : BoolOrExpression token(Equal) BoolOrExpression | BoolOrExpression | 87 | AssignmentExpression : BoolOrExpression token(Equal) BoolOrExpression | BoolOrExpression |
example/hello_world/hello2.zig+1-1| ... | @@ -3,6 +3,6 @@ export executable "hello"; | ... | @@ -3,6 +3,6 @@ export executable "hello"; |
| 3 | use "std.zig"; | 3 | use "std.zig"; |
| 4 | 4 | ||
| 5 | export fn main(argc : isize, argv : *mut *mut u8, env : *mut *mut u8) -> i32 { | 5 | export fn main(argc : isize, argv : *mut *mut u8, env : *mut *mut u8) -> i32 { |
| 6 | print_str("Hello, world!", 13); | 6 | print_str("Hello, world!", 13 as isize); |
| 7 | return 0; | 7 | return 0; |
| 8 | } | 8 | } |
src/analyze.cpp+49-12| ... | @@ -536,6 +536,20 @@ static TypeTableEntry *analyze_array_access_expr(CodeGen *g, ImportTableEntry *i | ... | @@ -536,6 +536,20 @@ static TypeTableEntry *analyze_array_access_expr(CodeGen *g, ImportTableEntry *i |
| 536 | return return_type; | 536 | return return_type; |
| 537 | } | 537 | } |
| 538 | 538 | ||
| 539 | static TypeTableEntry *analyze_variable_name(CodeGen *g, BlockContext *context, | ||
| 540 | AstNode *node, Buf *variable_name) | ||
| 541 | { | ||
| 542 | LocalVariableTableEntry *local_variable = find_local_variable(context, variable_name); | ||
| 543 | if (local_variable) { | ||
| 544 | return local_variable->type; | ||
| 545 | } else { | ||
| 546 | // TODO: check global variables also | ||
| 547 | add_node_error(g, node, | ||
| 548 | buf_sprintf("use of undeclared identifier '%s'", buf_ptr(variable_name))); | ||
| 549 | return g->builtin_types.entry_invalid; | ||
| 550 | } | ||
| 551 | } | ||
| 552 | |||
| 539 | static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context, | 553 | static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context, |
| 540 | TypeTableEntry *expected_type, AstNode *node) | 554 | TypeTableEntry *expected_type, AstNode *node) |
| 541 | { | 555 | { |
| ... | @@ -649,6 +663,15 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, | ... | @@ -649,6 +663,15 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, |
| 649 | } | 663 | } |
| 650 | case NodeTypeAsmExpr: | 664 | case NodeTypeAsmExpr: |
| 651 | { | 665 | { |
| 666 | for (int i = 0; i < node->data.asm_expr.output_list.length; i += 1) { | ||
| 667 | AsmOutput *asm_output = node->data.asm_expr.output_list.at(i); | ||
| 668 | analyze_variable_name(g, context, node, &asm_output->variable_name); | ||
| 669 | } | ||
| 670 | for (int i = 0; i < node->data.asm_expr.input_list.length; i += 1) { | ||
| 671 | AsmInput *asm_input = node->data.asm_expr.input_list.at(i); | ||
| 672 | analyze_expression(g, import, context, nullptr, asm_input->expr); | ||
| 673 | } | ||
| 674 | |||
| 652 | return_type = g->builtin_types.entry_void; | 675 | return_type = g->builtin_types.entry_void; |
| 653 | break; | 676 | break; |
| 654 | } | 677 | } |
| ... | @@ -835,22 +858,36 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, | ... | @@ -835,22 +858,36 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, |
| 835 | 858 | ||
| 836 | case NodeTypeSymbol: | 859 | case NodeTypeSymbol: |
| 837 | { | 860 | { |
| 838 | Buf *symbol_name = &node->data.symbol; | 861 | return_type = analyze_variable_name(g, context, node, &node->data.symbol); |
| 839 | LocalVariableTableEntry *local_variable = find_local_variable(context, symbol_name); | ||
| 840 | if (local_variable) { | ||
| 841 | return_type = local_variable->type; | ||
| 842 | } else { | ||
| 843 | // TODO: check global variables also | ||
| 844 | add_node_error(g, node, | ||
| 845 | buf_sprintf("use of undeclared identifier '%s'", buf_ptr(symbol_name))); | ||
| 846 | return_type = g->builtin_types.entry_invalid; | ||
| 847 | } | ||
| 848 | break; | 862 | break; |
| 849 | } | 863 | } |
| 850 | case NodeTypeCastExpr: | 864 | case NodeTypeCastExpr: |
| 851 | zig_panic("TODO analyze_expression cast expr"); | 865 | { |
| 852 | break; | 866 | TypeTableEntry *wanted_type = resolve_type(g, node->data.cast_expr.type); |
| 867 | TypeTableEntry *actual_type = analyze_expression(g, import, context, nullptr, | ||
| 868 | node->data.cast_expr.expr); | ||
| 869 | |||
| 870 | if (wanted_type->id == TypeTableEntryIdInvalid || | ||
| 871 | actual_type->id == TypeTableEntryIdInvalid) | ||
| 872 | { | ||
| 873 | return_type = g->builtin_types.entry_invalid; | ||
| 874 | break; | ||
| 875 | } | ||
| 853 | 876 | ||
| 877 | // special casing this for now, TODO think about casting and do a general solution | ||
| 878 | if (wanted_type == g->builtin_types.entry_isize && | ||
| 879 | actual_type->id == TypeTableEntryIdPointer) | ||
| 880 | { | ||
| 881 | return_type = wanted_type; | ||
| 882 | } else if (wanted_type == g->builtin_types.entry_isize && | ||
| 883 | actual_type->id == TypeTableEntryIdInt) | ||
| 884 | { | ||
| 885 | return_type = wanted_type; | ||
| 886 | } else { | ||
| 887 | zig_panic("TODO analyze_expression cast expr"); | ||
| 888 | } | ||
| 889 | break; | ||
| 890 | } | ||
| 854 | case NodeTypePrefixOpExpr: | 891 | case NodeTypePrefixOpExpr: |
| 855 | switch (node->data.prefix_op_expr.prefix_op) { | 892 | switch (node->data.prefix_op_expr.prefix_op) { |
| 856 | case PrefixOpBoolNot: | 893 | case PrefixOpBoolNot: |
src/codegen.cpp+108-10| ... | @@ -232,12 +232,35 @@ static LLVMValueRef gen_prefix_op_expr(CodeGen *g, AstNode *node) { | ... | @@ -232,12 +232,35 @@ static LLVMValueRef gen_prefix_op_expr(CodeGen *g, AstNode *node) { |
| 232 | static LLVMValueRef gen_cast_expr(CodeGen *g, AstNode *node) { | 232 | static LLVMValueRef gen_cast_expr(CodeGen *g, AstNode *node) { |
| 233 | assert(node->type == NodeTypeCastExpr); | 233 | assert(node->type == NodeTypeCastExpr); |
| 234 | 234 | ||
| 235 | LLVMValueRef expr = gen_expr(g, node->data.cast_expr.prefix_op_expr); | 235 | LLVMValueRef expr_val = gen_expr(g, node->data.cast_expr.expr); |
| 236 | 236 | ||
| 237 | if (!node->data.cast_expr.type) | 237 | TypeTableEntry *actual_type = get_expr_type(node->data.cast_expr.expr); |
| 238 | return expr; | 238 | TypeTableEntry *wanted_type = get_expr_type(node); |
| 239 | 239 | ||
| 240 | zig_panic("TODO cast expression"); | 240 | // this asserts are here only because no other casting codegen is supported currently |
| 241 | assert(wanted_type == g->builtin_types.entry_isize); | ||
| 242 | |||
| 243 | if (wanted_type->id == TypeTableEntryIdPointer) { | ||
| 244 | return LLVMBuildIntToPtr(g->builder, expr_val, wanted_type->type_ref, ""); | ||
| 245 | } else if (wanted_type->id == TypeTableEntryIdInt) { | ||
| 246 | if (actual_type->size_in_bits == wanted_type->size_in_bits) { | ||
| 247 | if (actual_type->id == TypeTableEntryIdPointer) { | ||
| 248 | return LLVMBuildPtrToInt(g->builder, expr_val, wanted_type->type_ref, ""); | ||
| 249 | } else { | ||
| 250 | zig_panic("TODO gen_cast_expr"); | ||
| 251 | } | ||
| 252 | } else if (actual_type->size_in_bits < wanted_type->size_in_bits) { | ||
| 253 | if (actual_type->data.integral.is_signed && wanted_type->data.integral.is_signed) { | ||
| 254 | return LLVMBuildSExt(g->builder, expr_val, wanted_type->type_ref, ""); | ||
| 255 | } else { | ||
| 256 | zig_panic("TODO gen_cast_expr sign mismatch"); | ||
| 257 | } | ||
| 258 | } else { | ||
| 259 | zig_panic("TODO gen_cast_expr"); | ||
| 260 | } | ||
| 261 | } else { | ||
| 262 | zig_panic("TODO gen_cast_expr"); | ||
| 263 | } | ||
| 241 | } | 264 | } |
| 242 | 265 | ||
| 243 | static LLVMValueRef gen_arithmetic_bin_op_expr(CodeGen *g, AstNode *node) { | 266 | static LLVMValueRef gen_arithmetic_bin_op_expr(CodeGen *g, AstNode *node) { |
| ... | @@ -610,16 +633,37 @@ static LLVMValueRef gen_block(CodeGen *g, AstNode *block_node, TypeTableEntry *i | ... | @@ -610,16 +633,37 @@ static LLVMValueRef gen_block(CodeGen *g, AstNode *block_node, TypeTableEntry *i |
| 610 | return return_value; | 633 | return return_value; |
| 611 | } | 634 | } |
| 612 | 635 | ||
| 636 | static int find_asm_index(CodeGen *g, AstNode *node, AsmToken *tok) { | ||
| 637 | const char *ptr = buf_ptr(&node->data.asm_expr.asm_template) + tok->start + 2; | ||
| 638 | int len = tok->end - tok->start - 2; | ||
| 639 | int result = 0; | ||
| 640 | for (int i = 0; i < node->data.asm_expr.output_list.length; i += 1, result += 1) { | ||
| 641 | AsmOutput *asm_output = node->data.asm_expr.output_list.at(i); | ||
| 642 | if (buf_eql_mem(&asm_output->asm_symbolic_name, ptr, len)) { | ||
| 643 | return result; | ||
| 644 | } | ||
| 645 | } | ||
| 646 | for (int i = 0; i < node->data.asm_expr.input_list.length; i += 1, result += 1) { | ||
| 647 | AsmInput *asm_input = node->data.asm_expr.input_list.at(i); | ||
| 648 | if (buf_eql_mem(&asm_input->asm_symbolic_name, ptr, len)) { | ||
| 649 | return result; | ||
| 650 | } | ||
| 651 | } | ||
| 652 | return -1; | ||
| 653 | } | ||
| 654 | |||
| 613 | static LLVMValueRef gen_asm_expr(CodeGen *g, AstNode *node) { | 655 | static LLVMValueRef gen_asm_expr(CodeGen *g, AstNode *node) { |
| 614 | assert(node->type == NodeTypeAsmExpr); | 656 | assert(node->type == NodeTypeAsmExpr); |
| 615 | 657 | ||
| 616 | Buf *src_template = &node->data.asm_expr.asm_template; | 658 | AstNodeAsmExpr *asm_expr = &node->data.asm_expr; |
| 659 | |||
| 660 | Buf *src_template = &asm_expr->asm_template; | ||
| 617 | 661 | ||
| 618 | Buf llvm_template = BUF_INIT; | 662 | Buf llvm_template = BUF_INIT; |
| 619 | buf_resize(&llvm_template, 0); | 663 | buf_resize(&llvm_template, 0); |
| 620 | 664 | ||
| 621 | for (int token_i = 0; token_i < node->data.asm_expr.token_list.length; token_i += 1) { | 665 | for (int token_i = 0; token_i < asm_expr->token_list.length; token_i += 1) { |
| 622 | AsmToken *asm_token = &node->data.asm_expr.token_list.at(token_i); | 666 | AsmToken *asm_token = &asm_expr->token_list.at(token_i); |
| 623 | switch (asm_token->id) { | 667 | switch (asm_token->id) { |
| 624 | case AsmTokenIdTemplate: | 668 | case AsmTokenIdTemplate: |
| 625 | for (int offset = asm_token->start; offset < asm_token->end; offset += 1) { | 669 | for (int offset = asm_token->start; offset < asm_token->end; offset += 1) { |
| ... | @@ -634,15 +678,69 @@ static LLVMValueRef gen_asm_expr(CodeGen *g, AstNode *node) { | ... | @@ -634,15 +678,69 @@ static LLVMValueRef gen_asm_expr(CodeGen *g, AstNode *node) { |
| 634 | case AsmTokenIdPercent: | 678 | case AsmTokenIdPercent: |
| 635 | buf_append_char(&llvm_template, '%'); | 679 | buf_append_char(&llvm_template, '%'); |
| 636 | break; | 680 | break; |
| 681 | case AsmTokenIdVar: | ||
| 682 | int index = find_asm_index(g, node, asm_token); | ||
| 683 | assert(index >= 0); | ||
| 684 | buf_appendf(&llvm_template, "$%d", index); | ||
| 685 | break; | ||
| 686 | } | ||
| 687 | } | ||
| 688 | |||
| 689 | Buf constraint_buf = BUF_INIT; | ||
| 690 | buf_resize(&constraint_buf, 0); | ||
| 691 | int total_constraint_count = asm_expr->output_list.length + | ||
| 692 | asm_expr->input_list.length + | ||
| 693 | asm_expr->clobber_list.length; | ||
| 694 | int input_and_output_count = asm_expr->output_list.length + | ||
| 695 | asm_expr->input_list.length; | ||
| 696 | int total_index = 0; | ||
| 697 | LLVMTypeRef *param_types = allocate<LLVMTypeRef>(input_and_output_count); | ||
| 698 | LLVMValueRef *param_values = allocate<LLVMValueRef>(input_and_output_count); | ||
| 699 | for (int i = 0; i < asm_expr->output_list.length; i += 1, total_index += 1) { | ||
| 700 | AsmOutput *asm_output = asm_expr->output_list.at(i); | ||
| 701 | if (buf_eql_str(&asm_output->constraint, "=m")) { | ||
| 702 | buf_append_str(&constraint_buf, "=*m"); | ||
| 703 | } else { | ||
| 704 | zig_panic("TODO unable to handle anything other than '=m' for outputs"); | ||
| 705 | } | ||
| 706 | if (total_index + 1 < total_constraint_count) { | ||
| 707 | buf_append_char(&constraint_buf, ','); | ||
| 708 | } | ||
| 709 | |||
| 710 | LocalVariableTableEntry *variable = find_local_variable( | ||
| 711 | node->codegen_node->expr_node.block_context, | ||
| 712 | &asm_output->variable_name); | ||
| 713 | assert(variable); | ||
| 714 | param_types[total_index] = LLVMTypeOf(variable->value_ref); | ||
| 715 | param_values[total_index] = variable->value_ref; | ||
| 716 | } | ||
| 717 | for (int i = 0; i < asm_expr->input_list.length; i += 1, total_index += 1) { | ||
| 718 | AsmInput *asm_input = asm_expr->input_list.at(i); | ||
| 719 | buf_append_buf(&constraint_buf, &asm_input->constraint); | ||
| 720 | if (total_index + 1 < total_constraint_count) { | ||
| 721 | buf_append_char(&constraint_buf, ','); | ||
| 722 | } | ||
| 723 | |||
| 724 | TypeTableEntry *expr_type = get_expr_type(asm_input->expr); | ||
| 725 | param_types[total_index] = expr_type->type_ref; | ||
| 726 | param_values[total_index] = gen_expr(g, asm_input->expr); | ||
| 727 | } | ||
| 728 | for (int i = 0; i < asm_expr->clobber_list.length; i += 1, total_index += 1) { | ||
| 729 | Buf *clobber_buf = asm_expr->clobber_list.at(i); | ||
| 730 | buf_appendf(&constraint_buf, "~{%s}", buf_ptr(clobber_buf)); | ||
| 731 | if (total_index + 1 < total_constraint_count) { | ||
| 732 | buf_append_char(&constraint_buf, ','); | ||
| 637 | } | 733 | } |
| 638 | } | 734 | } |
| 639 | 735 | ||
| 640 | LLVMTypeRef function_type = LLVMFunctionType(LLVMVoidType(), nullptr, 0, false); | 736 | LLVMTypeRef function_type = LLVMFunctionType(LLVMVoidType(), param_types, input_and_output_count, false); |
| 641 | 737 | ||
| 642 | LLVMValueRef asm_fn = LLVMConstInlineAsm(function_type, buf_ptr(&llvm_template), "", true, false); | 738 | bool is_volatile = asm_expr->is_volatile || (asm_expr->output_list.length == 0); |
| 739 | LLVMValueRef asm_fn = LLVMConstInlineAsm(function_type, buf_ptr(&llvm_template), | ||
| 740 | buf_ptr(&constraint_buf), is_volatile, false); | ||
| 643 | 741 | ||
| 644 | add_debug_source_node(g, node); | 742 | add_debug_source_node(g, node); |
| 645 | return LLVMBuildCall(g->builder, asm_fn, nullptr, 0, ""); | 743 | return LLVMBuildCall(g->builder, asm_fn, param_values, input_and_output_count, ""); |
| 646 | } | 744 | } |
| 647 | 745 | ||
| 648 | static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { | 746 | static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { |
src/parser.cpp+234-32| ... | @@ -245,7 +245,7 @@ void ast_print(AstNode *node, int indent) { | ... | @@ -245,7 +245,7 @@ void ast_print(AstNode *node, int indent) { |
| 245 | break; | 245 | break; |
| 246 | case NodeTypeCastExpr: | 246 | case NodeTypeCastExpr: |
| 247 | fprintf(stderr, "%s\n", node_type_str(node->type)); | 247 | fprintf(stderr, "%s\n", node_type_str(node->type)); |
| 248 | ast_print(node->data.cast_expr.prefix_op_expr, indent + 2); | 248 | ast_print(node->data.cast_expr.expr, indent + 2); |
| 249 | if (node->data.cast_expr.type) | 249 | if (node->data.cast_expr.type) |
| 250 | ast_print(node->data.cast_expr.type, indent + 2); | 250 | ast_print(node->data.cast_expr.type, indent + 2); |
| 251 | break; | 251 | break; |
| ... | @@ -307,6 +307,33 @@ struct ParseContext { | ... | @@ -307,6 +307,33 @@ struct ParseContext { |
| 307 | ErrColor err_color; | 307 | ErrColor err_color; |
| 308 | }; | 308 | }; |
| 309 | 309 | ||
| 310 | __attribute__ ((format (printf, 4, 5))) | ||
| 311 | __attribute__ ((noreturn)) | ||
| 312 | static void ast_asm_error(ParseContext *pc, AstNode *node, int offset, const char *format, ...) { | ||
| 313 | assert(node->type == NodeTypeAsmExpr); | ||
| 314 | |||
| 315 | ErrorMsg *err = allocate<ErrorMsg>(1); | ||
| 316 | |||
| 317 | SrcPos pos = node->data.asm_expr.offset_map.at(offset); | ||
| 318 | |||
| 319 | err->line_start = pos.line; | ||
| 320 | err->column_start = pos.column; | ||
| 321 | err->line_end = -1; | ||
| 322 | err->column_end = -1; | ||
| 323 | |||
| 324 | va_list ap; | ||
| 325 | va_start(ap, format); | ||
| 326 | err->msg = buf_vprintf(format, ap); | ||
| 327 | va_end(ap); | ||
| 328 | |||
| 329 | err->path = pc->owner->path; | ||
| 330 | err->source = pc->owner->source_code; | ||
| 331 | err->line_offsets = pc->owner->line_offsets; | ||
| 332 | |||
| 333 | print_err_msg(err, pc->err_color); | ||
| 334 | exit(EXIT_FAILURE); | ||
| 335 | } | ||
| 336 | |||
| 310 | __attribute__ ((format (printf, 3, 4))) | 337 | __attribute__ ((format (printf, 3, 4))) |
| 311 | __attribute__ ((noreturn)) | 338 | __attribute__ ((noreturn)) |
| 312 | static void ast_error(ParseContext *pc, Token *token, const char *format, ...) { | 339 | static void ast_error(ParseContext *pc, Token *token, const char *format, ...) { |
| ... | @@ -372,6 +399,7 @@ static void parse_asm_template(ParseContext *pc, AstNode *node) { | ... | @@ -372,6 +399,7 @@ static void parse_asm_template(ParseContext *pc, AstNode *node) { |
| 372 | StateStart, | 399 | StateStart, |
| 373 | StatePercent, | 400 | StatePercent, |
| 374 | StateTemplate, | 401 | StateTemplate, |
| 402 | StateVar, | ||
| 375 | }; | 403 | }; |
| 376 | 404 | ||
| 377 | ZigList<AsmToken> *tok_list = &node->data.asm_expr.token_list; | 405 | ZigList<AsmToken> *tok_list = &node->data.asm_expr.token_list; |
| ... | @@ -403,8 +431,11 @@ static void parse_asm_template(ParseContext *pc, AstNode *node) { | ... | @@ -403,8 +431,11 @@ static void parse_asm_template(ParseContext *pc, AstNode *node) { |
| 403 | if (c == '%') { | 431 | if (c == '%') { |
| 404 | cur_tok->end = i; | 432 | cur_tok->end = i; |
| 405 | state = StateStart; | 433 | state = StateStart; |
| 434 | } else if (c == '[') { | ||
| 435 | cur_tok->id = AsmTokenIdVar; | ||
| 436 | state = StateVar; | ||
| 406 | } else { | 437 | } else { |
| 407 | zig_panic("TODO handle assembly tokenize error"); | 438 | ast_asm_error(pc, node, i, "expected a '%%' or '['"); |
| 408 | } | 439 | } |
| 409 | break; | 440 | break; |
| 410 | case StateTemplate: | 441 | case StateTemplate: |
| ... | @@ -415,6 +446,19 @@ static void parse_asm_template(ParseContext *pc, AstNode *node) { | ... | @@ -415,6 +446,19 @@ static void parse_asm_template(ParseContext *pc, AstNode *node) { |
| 415 | state = StateStart; | 446 | state = StateStart; |
| 416 | } | 447 | } |
| 417 | break; | 448 | break; |
| 449 | case StateVar: | ||
| 450 | if (c == ']') { | ||
| 451 | cur_tok->end = i; | ||
| 452 | state = StateStart; | ||
| 453 | } else if ((c >= 'a' && c <= 'z') || | ||
| 454 | (c >= '0' && c <= '9') || | ||
| 455 | (c == '_')) | ||
| 456 | { | ||
| 457 | // do nothing | ||
| 458 | } else { | ||
| 459 | ast_asm_error(pc, node, i, "invalid substitution character: '%c'", c); | ||
| 460 | } | ||
| 461 | break; | ||
| 418 | } | 462 | } |
| 419 | } | 463 | } |
| 420 | 464 | ||
| ... | @@ -422,7 +466,8 @@ static void parse_asm_template(ParseContext *pc, AstNode *node) { | ... | @@ -422,7 +466,8 @@ static void parse_asm_template(ParseContext *pc, AstNode *node) { |
| 422 | case StateStart: | 466 | case StateStart: |
| 423 | break; | 467 | break; |
| 424 | case StatePercent: | 468 | case StatePercent: |
| 425 | zig_panic("TODO handle assembly tokenize error eof"); | 469 | case StateVar: |
| 470 | ast_asm_error(pc, node, buf_len(asm_template), "unexpected end of assembly template"); | ||
| 426 | break; | 471 | break; |
| 427 | case StateTemplate: | 472 | case StateTemplate: |
| 428 | cur_tok->end = buf_len(asm_template); | 473 | cur_tok->end = buf_len(asm_template); |
| ... | @@ -430,40 +475,59 @@ static void parse_asm_template(ParseContext *pc, AstNode *node) { | ... | @@ -430,40 +475,59 @@ static void parse_asm_template(ParseContext *pc, AstNode *node) { |
| 430 | } | 475 | } |
| 431 | } | 476 | } |
| 432 | 477 | ||
| 433 | static void parse_string_literal(ParseContext *pc, Token *token, Buf *buf) { | 478 | static void parse_string_literal(ParseContext *pc, Token *token, Buf *buf, ZigList<SrcPos> *offset_map) { |
| 434 | // skip the double quotes at beginning and end | 479 | // skip the double quotes at beginning and end |
| 435 | // convert escape sequences | 480 | // convert escape sequences |
| 436 | 481 | ||
| 437 | buf_resize(buf, 0); | 482 | buf_resize(buf, 0); |
| 438 | bool escape = false; | 483 | bool escape = false; |
| 439 | for (int i = token->start_pos + 1; i < token->end_pos - 1; i += 1) { | 484 | bool first = true; |
| 485 | SrcPos pos = {token->start_line, token->start_column}; | ||
| 486 | for (int i = token->start_pos; i < token->end_pos - 1; i += 1) { | ||
| 440 | uint8_t c = *((uint8_t*)buf_ptr(pc->buf) + i); | 487 | uint8_t c = *((uint8_t*)buf_ptr(pc->buf) + i); |
| 441 | if (escape) { | 488 | if (first) { |
| 442 | switch (c) { | 489 | first = false; |
| 443 | case '\\': | 490 | } else { |
| 444 | buf_append_char(buf, '\\'); | 491 | if (escape) { |
| 445 | break; | 492 | switch (c) { |
| 446 | case 'r': | 493 | case '\\': |
| 447 | buf_append_char(buf, '\r'); | 494 | buf_append_char(buf, '\\'); |
| 448 | break; | 495 | if (offset_map) offset_map->append(pos); |
| 449 | case 'n': | 496 | break; |
| 450 | buf_append_char(buf, '\n'); | 497 | case 'r': |
| 451 | break; | 498 | buf_append_char(buf, '\r'); |
| 452 | case 't': | 499 | if (offset_map) offset_map->append(pos); |
| 453 | buf_append_char(buf, '\t'); | 500 | break; |
| 454 | break; | 501 | case 'n': |
| 455 | case '"': | 502 | buf_append_char(buf, '\n'); |
| 456 | buf_append_char(buf, '"'); | 503 | if (offset_map) offset_map->append(pos); |
| 457 | break; | 504 | break; |
| 505 | case 't': | ||
| 506 | buf_append_char(buf, '\t'); | ||
| 507 | if (offset_map) offset_map->append(pos); | ||
| 508 | break; | ||
| 509 | case '"': | ||
| 510 | buf_append_char(buf, '"'); | ||
| 511 | if (offset_map) offset_map->append(pos); | ||
| 512 | break; | ||
| 513 | } | ||
| 514 | escape = false; | ||
| 515 | } else if (c == '\\') { | ||
| 516 | escape = true; | ||
| 517 | } else { | ||
| 518 | buf_append_char(buf, c); | ||
| 519 | if (offset_map) offset_map->append(pos); | ||
| 458 | } | 520 | } |
| 459 | escape = false; | 521 | } |
| 460 | } else if (c == '\\') { | 522 | if (c == '\n') { |
| 461 | escape = true; | 523 | pos.line += 1; |
| 524 | pos.column = 0; | ||
| 462 | } else { | 525 | } else { |
| 463 | buf_append_char(buf, c); | 526 | pos.column += 1; |
| 464 | } | 527 | } |
| 465 | } | 528 | } |
| 466 | assert(!escape); | 529 | assert(!escape); |
| 530 | if (offset_map) offset_map->append(pos); | ||
| 467 | } | 531 | } |
| 468 | 532 | ||
| 469 | __attribute__ ((noreturn)) | 533 | __attribute__ ((noreturn)) |
| ... | @@ -505,7 +569,7 @@ static AstNode *ast_parse_directive(ParseContext *pc, int token_index, int *new_ | ... | @@ -505,7 +569,7 @@ static AstNode *ast_parse_directive(ParseContext *pc, int token_index, int *new_ |
| 505 | token_index += 1; | 569 | token_index += 1; |
| 506 | ast_expect_token(pc, param_str, TokenIdStringLiteral); | 570 | ast_expect_token(pc, param_str, TokenIdStringLiteral); |
| 507 | 571 | ||
| 508 | parse_string_literal(pc, param_str, &node->data.directive.param); | 572 | parse_string_literal(pc, param_str, &node->data.directive.param, nullptr); |
| 509 | 573 | ||
| 510 | Token *r_paren = &pc->tokens->at(token_index); | 574 | Token *r_paren = &pc->tokens->at(token_index); |
| 511 | token_index += 1; | 575 | token_index += 1; |
| ... | @@ -718,7 +782,7 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool | ... | @@ -718,7 +782,7 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool |
| 718 | return node; | 782 | return node; |
| 719 | } else if (token->id == TokenIdStringLiteral) { | 783 | } else if (token->id == TokenIdStringLiteral) { |
| 720 | AstNode *node = ast_create_node(pc, NodeTypeStringLiteral, token); | 784 | AstNode *node = ast_create_node(pc, NodeTypeStringLiteral, token); |
| 721 | parse_string_literal(pc, token, &node->data.string); | 785 | parse_string_literal(pc, token, &node->data.string, nullptr); |
| 722 | *token_index += 1; | 786 | *token_index += 1; |
| 723 | return node; | 787 | return node; |
| 724 | } else if (token->id == TokenIdKeywordUnreachable) { | 788 | } else if (token->id == TokenIdKeywordUnreachable) { |
| ... | @@ -861,7 +925,7 @@ static AstNode *ast_parse_cast_expression(ParseContext *pc, int *token_index, bo | ... | @@ -861,7 +925,7 @@ static AstNode *ast_parse_cast_expression(ParseContext *pc, int *token_index, bo |
| 861 | *token_index += 1; | 925 | *token_index += 1; |
| 862 | 926 | ||
| 863 | AstNode *node = ast_create_node(pc, NodeTypeCastExpr, as_kw); | 927 | AstNode *node = ast_create_node(pc, NodeTypeCastExpr, as_kw); |
| 864 | node->data.cast_expr.prefix_op_expr = prefix_op_expr; | 928 | node->data.cast_expr.expr = prefix_op_expr; |
| 865 | 929 | ||
| 866 | node->data.cast_expr.type = ast_parse_type(pc, *token_index, token_index); | 930 | node->data.cast_expr.type = ast_parse_type(pc, *token_index, token_index); |
| 867 | 931 | ||
| ... | @@ -1333,6 +1397,141 @@ static AstNode *ast_parse_ass_expr(ParseContext *pc, int *token_index, bool mand | ... | @@ -1333,6 +1397,141 @@ static AstNode *ast_parse_ass_expr(ParseContext *pc, int *token_index, bool mand |
| 1333 | return node; | 1397 | return node; |
| 1334 | } | 1398 | } |
| 1335 | 1399 | ||
| 1400 | static Token *ast_eat_token(ParseContext *pc, int *token_index, TokenId token_id) { | ||
| 1401 | Token *token = &pc->tokens->at(*token_index); | ||
| 1402 | ast_expect_token(pc, token, token_id); | ||
| 1403 | *token_index += 1; | ||
| 1404 | return token; | ||
| 1405 | } | ||
| 1406 | |||
| 1407 | |||
| 1408 | /* | ||
| 1409 | AsmInputItem : token(LBracket) token(Symbol) token(RBracket) token(String) token(LParen) Expression token(RParen) | ||
| 1410 | */ | ||
| 1411 | static void ast_parse_asm_input_item(ParseContext *pc, int *token_index, AstNode *node) { | ||
| 1412 | ast_eat_token(pc, token_index, TokenIdLBracket); | ||
| 1413 | Token *alias = ast_eat_token(pc, token_index, TokenIdSymbol); | ||
| 1414 | ast_eat_token(pc, token_index, TokenIdRBracket); | ||
| 1415 | |||
| 1416 | Token *constraint = ast_eat_token(pc, token_index, TokenIdStringLiteral); | ||
| 1417 | |||
| 1418 | ast_eat_token(pc, token_index, TokenIdLParen); | ||
| 1419 | AstNode *expr_node = ast_parse_expression(pc, token_index, true); | ||
| 1420 | ast_eat_token(pc, token_index, TokenIdRParen); | ||
| 1421 | |||
| 1422 | AsmInput *asm_input = allocate<AsmInput>(1); | ||
| 1423 | ast_buf_from_token(pc, alias, &asm_input->asm_symbolic_name); | ||
| 1424 | parse_string_literal(pc, constraint, &asm_input->constraint, nullptr); | ||
| 1425 | asm_input->expr = expr_node; | ||
| 1426 | node->data.asm_expr.input_list.append(asm_input); | ||
| 1427 | } | ||
| 1428 | |||
| 1429 | /* | ||
| 1430 | AsmOutputItem : token(LBracket) token(Symbol) token(RBracket) token(String) token(LParen) token(Symbol) token(RParen) | ||
| 1431 | */ | ||
| 1432 | static void ast_parse_asm_output_item(ParseContext *pc, int *token_index, AstNode *node) { | ||
| 1433 | ast_eat_token(pc, token_index, TokenIdLBracket); | ||
| 1434 | Token *alias = ast_eat_token(pc, token_index, TokenIdSymbol); | ||
| 1435 | ast_eat_token(pc, token_index, TokenIdRBracket); | ||
| 1436 | |||
| 1437 | Token *constraint = ast_eat_token(pc, token_index, TokenIdStringLiteral); | ||
| 1438 | |||
| 1439 | ast_eat_token(pc, token_index, TokenIdLParen); | ||
| 1440 | Token *out_symbol = ast_eat_token(pc, token_index, TokenIdSymbol); | ||
| 1441 | ast_eat_token(pc, token_index, TokenIdRParen); | ||
| 1442 | |||
| 1443 | AsmOutput *asm_output = allocate<AsmOutput>(1); | ||
| 1444 | ast_buf_from_token(pc, alias, &asm_output->asm_symbolic_name); | ||
| 1445 | parse_string_literal(pc, constraint, &asm_output->constraint, nullptr); | ||
| 1446 | ast_buf_from_token(pc, out_symbol, &asm_output->variable_name); | ||
| 1447 | node->data.asm_expr.output_list.append(asm_output); | ||
| 1448 | } | ||
| 1449 | |||
| 1450 | /* | ||
| 1451 | AsmClobbers: token(Colon) list(token(String), token(Comma)) | ||
| 1452 | */ | ||
| 1453 | static void ast_parse_asm_clobbers(ParseContext *pc, int *token_index, AstNode *node) { | ||
| 1454 | Token *colon_tok = &pc->tokens->at(*token_index); | ||
| 1455 | |||
| 1456 | if (colon_tok->id != TokenIdColon) | ||
| 1457 | return; | ||
| 1458 | |||
| 1459 | *token_index += 1; | ||
| 1460 | |||
| 1461 | for (;;) { | ||
| 1462 | Token *string_tok = &pc->tokens->at(*token_index); | ||
| 1463 | ast_expect_token(pc, string_tok, TokenIdStringLiteral); | ||
| 1464 | *token_index += 1; | ||
| 1465 | |||
| 1466 | Buf *clobber_buf = buf_alloc(); | ||
| 1467 | parse_string_literal(pc, string_tok, clobber_buf, nullptr); | ||
| 1468 | node->data.asm_expr.clobber_list.append(clobber_buf); | ||
| 1469 | |||
| 1470 | Token *comma = &pc->tokens->at(*token_index); | ||
| 1471 | |||
| 1472 | if (comma->id == TokenIdComma) { | ||
| 1473 | *token_index += 1; | ||
| 1474 | continue; | ||
| 1475 | } else { | ||
| 1476 | break; | ||
| 1477 | } | ||
| 1478 | } | ||
| 1479 | } | ||
| 1480 | |||
| 1481 | /* | ||
| 1482 | AsmInput : token(Colon) list(AsmInputItem, token(Comma)) option(AsmClobbers) | ||
| 1483 | */ | ||
| 1484 | static void ast_parse_asm_input(ParseContext *pc, int *token_index, AstNode *node) { | ||
| 1485 | Token *colon_tok = &pc->tokens->at(*token_index); | ||
| 1486 | |||
| 1487 | if (colon_tok->id != TokenIdColon) | ||
| 1488 | return; | ||
| 1489 | |||
| 1490 | *token_index += 1; | ||
| 1491 | |||
| 1492 | for (;;) { | ||
| 1493 | ast_parse_asm_input_item(pc, token_index, node); | ||
| 1494 | |||
| 1495 | Token *comma = &pc->tokens->at(*token_index); | ||
| 1496 | |||
| 1497 | if (comma->id == TokenIdComma) { | ||
| 1498 | *token_index += 1; | ||
| 1499 | continue; | ||
| 1500 | } else { | ||
| 1501 | break; | ||
| 1502 | } | ||
| 1503 | } | ||
| 1504 | |||
| 1505 | ast_parse_asm_clobbers(pc, token_index, node); | ||
| 1506 | } | ||
| 1507 | |||
| 1508 | /* | ||
| 1509 | AsmOutput : token(Colon) list(AsmOutputItem, token(Comma)) option(AsmInput) | ||
| 1510 | */ | ||
| 1511 | static void ast_parse_asm_output(ParseContext *pc, int *token_index, AstNode *node) { | ||
| 1512 | Token *colon_tok = &pc->tokens->at(*token_index); | ||
| 1513 | |||
| 1514 | if (colon_tok->id != TokenIdColon) | ||
| 1515 | return; | ||
| 1516 | |||
| 1517 | *token_index += 1; | ||
| 1518 | |||
| 1519 | for (;;) { | ||
| 1520 | ast_parse_asm_output_item(pc, token_index, node); | ||
| 1521 | |||
| 1522 | Token *comma = &pc->tokens->at(*token_index); | ||
| 1523 | |||
| 1524 | if (comma->id == TokenIdComma) { | ||
| 1525 | *token_index += 1; | ||
| 1526 | continue; | ||
| 1527 | } else { | ||
| 1528 | break; | ||
| 1529 | } | ||
| 1530 | } | ||
| 1531 | |||
| 1532 | ast_parse_asm_input(pc, token_index, node); | ||
| 1533 | } | ||
| 1534 | |||
| 1336 | /* | 1535 | /* |
| 1337 | AsmExpression : token(Asm) option(token(Volatile)) token(LParen) token(String) option(AsmOutput) token(RParen) | 1536 | AsmExpression : token(Asm) option(token(Volatile)) token(LParen) token(String) option(AsmOutput) token(RParen) |
| 1338 | */ | 1537 | */ |
| ... | @@ -1366,9 +1565,12 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, int *token_index, bool mand | ... | @@ -1366,9 +1565,12 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, int *token_index, bool mand |
| 1366 | ast_expect_token(pc, template_tok, TokenIdStringLiteral); | 1565 | ast_expect_token(pc, template_tok, TokenIdStringLiteral); |
| 1367 | *token_index += 1; | 1566 | *token_index += 1; |
| 1368 | 1567 | ||
| 1369 | parse_string_literal(pc, template_tok, &node->data.asm_expr.asm_template); | 1568 | parse_string_literal(pc, template_tok, &node->data.asm_expr.asm_template, |
| 1569 | &node->data.asm_expr.offset_map); | ||
| 1370 | parse_asm_template(pc, node); | 1570 | parse_asm_template(pc, node); |
| 1371 | 1571 | ||
| 1572 | ast_parse_asm_output(pc, token_index, node); | ||
| 1573 | |||
| 1372 | Token *rparen_tok = &pc->tokens->at(*token_index); | 1574 | Token *rparen_tok = &pc->tokens->at(*token_index); |
| 1373 | ast_expect_token(pc, rparen_tok, TokenIdRParen); | 1575 | ast_expect_token(pc, rparen_tok, TokenIdRParen); |
| 1374 | *token_index += 1; | 1576 | *token_index += 1; |
| ... | @@ -1675,7 +1877,7 @@ static AstNode *ast_parse_root_export_decl(ParseContext *pc, int *token_index, b | ... | @@ -1675,7 +1877,7 @@ static AstNode *ast_parse_root_export_decl(ParseContext *pc, int *token_index, b |
| 1675 | *token_index += 1; | 1877 | *token_index += 1; |
| 1676 | ast_expect_token(pc, export_name, TokenIdStringLiteral); | 1878 | ast_expect_token(pc, export_name, TokenIdStringLiteral); |
| 1677 | 1879 | ||
| 1678 | parse_string_literal(pc, export_name, &node->data.root_export_decl.name); | 1880 | parse_string_literal(pc, export_name, &node->data.root_export_decl.name, nullptr); |
| 1679 | 1881 | ||
| 1680 | Token *semicolon = &pc->tokens->at(*token_index); | 1882 | Token *semicolon = &pc->tokens->at(*token_index); |
| 1681 | *token_index += 1; | 1883 | *token_index += 1; |
| ... | @@ -1705,7 +1907,7 @@ static AstNode *ast_parse_use(ParseContext *pc, int *token_index, bool mandatory | ... | @@ -1705,7 +1907,7 @@ static AstNode *ast_parse_use(ParseContext *pc, int *token_index, bool mandatory |
| 1705 | 1907 | ||
| 1706 | AstNode *node = ast_create_node(pc, NodeTypeUse, use_kw); | 1908 | AstNode *node = ast_create_node(pc, NodeTypeUse, use_kw); |
| 1707 | 1909 | ||
| 1708 | parse_string_literal(pc, use_name, &node->data.use.path); | 1910 | parse_string_literal(pc, use_name, &node->data.use.path, nullptr); |
| 1709 | 1911 | ||
| 1710 | node->data.use.directives = pc->directive_list; | 1912 | node->data.use.directives = pc->directive_list; |
| 1711 | pc->directive_list = nullptr; | 1913 | pc->directive_list = nullptr; |
src/parser.hpp+23-2| ... | @@ -169,8 +169,7 @@ struct AstNodeRootExportDecl { | ... | @@ -169,8 +169,7 @@ struct AstNodeRootExportDecl { |
| 169 | }; | 169 | }; |
| 170 | 170 | ||
| 171 | struct AstNodeCastExpr { | 171 | struct AstNodeCastExpr { |
| 172 | AstNode *prefix_op_expr; | 172 | AstNode *expr; |
| 173 | // if type is non-null, do cast, otherwise nothing | ||
| 174 | AstNode *type; | 173 | AstNode *type; |
| 175 | }; | 174 | }; |
| 176 | 175 | ||
| ... | @@ -205,10 +204,31 @@ struct AstNodeGoto { | ... | @@ -205,10 +204,31 @@ struct AstNodeGoto { |
| 205 | Buf name; | 204 | Buf name; |
| 206 | }; | 205 | }; |
| 207 | 206 | ||
| 207 | struct AsmOutput { | ||
| 208 | Buf asm_symbolic_name; | ||
| 209 | Buf constraint; | ||
| 210 | Buf variable_name; | ||
| 211 | }; | ||
| 212 | |||
| 213 | struct AsmInput { | ||
| 214 | Buf asm_symbolic_name; | ||
| 215 | Buf constraint; | ||
| 216 | AstNode *expr; | ||
| 217 | }; | ||
| 218 | |||
| 219 | struct SrcPos { | ||
| 220 | int line; | ||
| 221 | int column; | ||
| 222 | }; | ||
| 223 | |||
| 208 | struct AstNodeAsmExpr { | 224 | struct AstNodeAsmExpr { |
| 209 | bool is_volatile; | 225 | bool is_volatile; |
| 210 | Buf asm_template; | 226 | Buf asm_template; |
| 227 | ZigList<SrcPos> offset_map; | ||
| 211 | ZigList<AsmToken> token_list; | 228 | ZigList<AsmToken> token_list; |
| 229 | ZigList<AsmOutput*> output_list; | ||
| 230 | ZigList<AsmInput*> input_list; | ||
| 231 | ZigList<Buf*> clobber_list; | ||
| 212 | }; | 232 | }; |
| 213 | 233 | ||
| 214 | struct AstNode { | 234 | struct AstNode { |
| ... | @@ -250,6 +270,7 @@ struct AstNode { | ... | @@ -250,6 +270,7 @@ struct AstNode { |
| 250 | enum AsmTokenId { | 270 | enum AsmTokenId { |
| 251 | AsmTokenIdTemplate, | 271 | AsmTokenIdTemplate, |
| 252 | AsmTokenIdPercent, | 272 | AsmTokenIdPercent, |
| 273 | AsmTokenIdVar, | ||
| 253 | }; | 274 | }; |
| 254 | 275 | ||
| 255 | struct AsmToken { | 276 | struct AsmToken { |
std/std.zig+11-10| ... | @@ -1,13 +1,13 @@ | ... | @@ -1,13 +1,13 @@ |
| 1 | fn syscall3(number: isize, arg1: isize, arg2: isize, arg3: isize) -> isize { | 1 | fn syscall3(number: isize, arg1: isize, arg2: isize, arg3: isize) -> isize { |
| 2 | let mut result : isize; | 2 | let mut result : isize; |
| 3 | asm volatile ( | 3 | asm volatile (" |
| 4 | "mov %[number], %%rax\n" | 4 | mov %[number], %%rax |
| 5 | "mov %[arg1], %%rdi\n" | 5 | mov %[arg1], %%rdi |
| 6 | "mov %[arg2], %%rsi\n" | 6 | mov %[arg2], %%rsi |
| 7 | "mov %[arg3], %%rdx\n" | 7 | mov %[arg3], %%rdx |
| 8 | "syscall\n" | 8 | syscall |
| 9 | "mov %%rax, %[ret]\n" | 9 | mov %%rax, %[ret]" |
| 10 | : [ret] "=r" (result) | 10 | : [ret] "=m" (result) |
| 11 | : [number] "r" (number), [arg1] "r" (arg1), [arg2] "r" (arg2), [arg3] "r" (arg3) | 11 | : [number] "r" (number), [arg1] "r" (arg1), [arg2] "r" (arg2), [arg3] "r" (arg3) |
| 12 | : "rcx", "r11", "rax", "rdi", "rsi", "rdx"); | 12 | : "rcx", "r11", "rax", "rdi", "rsi", "rdx"); |
| 13 | return result; | 13 | return result; |
| ... | @@ -16,8 +16,9 @@ fn syscall3(number: isize, arg1: isize, arg2: isize, arg3: isize) -> isize { | ... | @@ -16,8 +16,9 @@ fn syscall3(number: isize, arg1: isize, arg2: isize, arg3: isize) -> isize { |
| 16 | // TODO error handling | 16 | // TODO error handling |
| 17 | // TODO zig strings instead of C strings | 17 | // TODO zig strings instead of C strings |
| 18 | // TODO handle buffering and flushing | 18 | // TODO handle buffering and flushing |
| 19 | pub print_str(str : *const u8, len: isize) { | 19 | // TODO non-i32 integer literals so we can remove the casts |
| 20 | pub fn print_str(str : *const u8, len: isize) { | ||
| 20 | let SYS_write = 1; | 21 | let SYS_write = 1; |
| 21 | let stdout_fileno = 1; | 22 | let stdout_fileno = 1; |
| 22 | syscall3(SYS_write, stdout_fileno, str as isize, str_len); | 23 | syscall3(SYS_write as isize, stdout_fileno as isize, str as isize, len); |
| 23 | } | 24 | } |
test/run_tests.cpp+12| ... | @@ -398,6 +398,18 @@ loop_2_end: | ... | @@ -398,6 +398,18 @@ loop_2_end: |
| 398 | } | 398 | } |
| 399 | )SOURCE", "OK\n"); | 399 | )SOURCE", "OK\n"); |
| 400 | 400 | ||
| 401 | |||
| 402 | add_simple_case("hello world without libc", R"SOURCE( | ||
| 403 | use "std.zig"; | ||
| 404 | |||
| 405 | export fn main(argc : isize, argv : *mut *mut u8, env : *mut *mut u8) -> i32 { | ||
| 406 | print_str("Hello, world!\n", 14 as isize); | ||
| 407 | return 0; | ||
| 408 | } | ||
| 409 | )SOURCE", "Hello, world!\n"); | ||
| 410 | |||
| 411 | |||
| 412 | |||
| 401 | } | 413 | } |
| 402 | 414 | ||
| 403 | static void add_compile_failure_test_cases(void) { | 415 | static void add_compile_failure_test_cases(void) { |