| author | |
| committer | |
| log | a5c2de5fee67e35c8173b7051675d49648086cbb |
| tree | e6ee63d6daca6d6456e58f866d47e2c426cd9315 |
| parent | 2bb2e61ee288a02e184e5b8422859a4afcbb4813 |
closes #145 files changed, 170 insertions(+), 70 deletions(-)
doc/langref.md+1-1| ... | @@ -141,7 +141,7 @@ StructLiteralField = "." "Symbol" "=" Expression | ... | @@ -141,7 +141,7 @@ StructLiteralField = "." "Symbol" "=" Expression |
| 141 | 141 | ||
| 142 | PrefixOp = "!" | "-" | "~" | "*" | ("&" option("const")) | "?" | "%" | "%%" | 142 | PrefixOp = "!" | "-" | "~" | "*" | ("&" option("const")) | "?" | "%" | "%%" |
| 143 | 143 | ||
| 144 | PrimaryExpression = "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." "Symbol") | 144 | PrimaryExpression = "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | (option("extern") FnProto) | AsmExpression | ("error" "." "Symbol") |
| 145 | 145 | ||
| 146 | ArrayType = "[" option(Expression) "]" option("const") PrefixOpExpression | 146 | ArrayType = "[" option(Expression) "]" option("const") PrefixOpExpression |
| 147 | 147 |
src/all_types.hpp+2| ... | @@ -189,6 +189,7 @@ struct AstNodeFnProto { | ... | @@ -189,6 +189,7 @@ struct AstNodeFnProto { |
| 189 | FnTableEntry *fn_table_entry; | 189 | FnTableEntry *fn_table_entry; |
| 190 | bool skip; | 190 | bool skip; |
| 191 | TopLevelDecl top_level_decl; | 191 | TopLevelDecl top_level_decl; |
| 192 | Expr resolved_expr; | ||
| 192 | }; | 193 | }; |
| 193 | 194 | ||
| 194 | struct AstNodeFnDef { | 195 | struct AstNodeFnDef { |
| ... | @@ -828,6 +829,7 @@ struct TypeTableEntryFn { | ... | @@ -828,6 +829,7 @@ struct TypeTableEntryFn { |
| 828 | bool is_var_args; | 829 | bool is_var_args; |
| 829 | int gen_param_count; | 830 | int gen_param_count; |
| 830 | LLVMCallConv calling_convention; | 831 | LLVMCallConv calling_convention; |
| 832 | bool is_extern; | ||
| 831 | bool is_naked; | 833 | bool is_naked; |
| 832 | }; | 834 | }; |
| 833 | 835 |
src/analyze.cpp+114-50| ... | @@ -451,44 +451,20 @@ static TypeTableEntry *analyze_type_expr(CodeGen *g, ImportTableEntry *import, B | ... | @@ -451,44 +451,20 @@ static TypeTableEntry *analyze_type_expr(CodeGen *g, ImportTableEntry *import, B |
| 451 | return resolve_type(g, *node_ptr); | 451 | return resolve_type(g, *node_ptr); |
| 452 | } | 452 | } |
| 453 | 453 | ||
| 454 | static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_table_entry, | 454 | static TypeTableEntry *analyze_fn_proto_type(CodeGen *g, ImportTableEntry *import, BlockContext *context, |
| 455 | ImportTableEntry *import) | 455 | TypeTableEntry *expected_type, AstNode *node, bool is_naked) |
| 456 | { | 456 | { |
| 457 | assert(node->type == NodeTypeFnProto); | 457 | assert(node->type == NodeTypeFnProto); |
| 458 | AstNodeFnProto *fn_proto = &node->data.fn_proto; | 458 | AstNodeFnProto *fn_proto = &node->data.fn_proto; |
| 459 | 459 | ||
| 460 | if (fn_proto->skip) { | 460 | if (fn_proto->skip) { |
| 461 | return; | 461 | return g->builtin_types.entry_invalid; |
| 462 | } | 462 | } |
| 463 | 463 | ||
| 464 | TypeTableEntry *fn_type = new_type_table_entry(TypeTableEntryIdFn); | 464 | TypeTableEntry *fn_type = new_type_table_entry(TypeTableEntryIdFn); |
| 465 | fn_table_entry->type_entry = fn_type; | 465 | fn_type->data.fn.is_extern = fn_proto->is_extern || (fn_proto->visib_mod == VisibModExport); |
| 466 | fn_type->data.fn.calling_convention = fn_table_entry->internal_linkage ? LLVMFastCallConv : LLVMCCallConv; | 466 | fn_type->data.fn.is_naked = is_naked; |
| 467 | 467 | fn_type->data.fn.calling_convention = fn_proto->is_extern ? LLVMCCallConv : LLVMFastCallConv; | |
| 468 | for (int i = 0; i < fn_proto->directives->length; i += 1) { | ||
| 469 | AstNode *directive_node = fn_proto->directives->at(i); | ||
| 470 | Buf *name = &directive_node->data.directive.name; | ||
| 471 | |||
| 472 | if (buf_eql_str(name, "attribute")) { | ||
| 473 | Buf *attr_name = &directive_node->data.directive.param; | ||
| 474 | if (fn_table_entry->fn_def_node) { | ||
| 475 | if (buf_eql_str(attr_name, "naked")) { | ||
| 476 | fn_type->data.fn.is_naked = true; | ||
| 477 | } else if (buf_eql_str(attr_name, "inline")) { | ||
| 478 | fn_table_entry->is_inline = true; | ||
| 479 | } else { | ||
| 480 | add_node_error(g, directive_node, | ||
| 481 | buf_sprintf("invalid function attribute: '%s'", buf_ptr(name))); | ||
| 482 | } | ||
| 483 | } else { | ||
| 484 | add_node_error(g, directive_node, | ||
| 485 | buf_sprintf("invalid function attribute: '%s'", buf_ptr(name))); | ||
| 486 | } | ||
| 487 | } else { | ||
| 488 | add_node_error(g, directive_node, | ||
| 489 | buf_sprintf("invalid directive: '%s'", buf_ptr(name))); | ||
| 490 | } | ||
| 491 | } | ||
| 492 | 468 | ||
| 493 | int src_param_count = node->data.fn_proto.params.length; | 469 | int src_param_count = node->data.fn_proto.params.length; |
| 494 | fn_type->size_in_bits = g->pointer_size_bytes * 8; | 470 | fn_type->size_in_bits = g->pointer_size_bytes * 8; |
| ... | @@ -499,10 +475,9 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t | ... | @@ -499,10 +475,9 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t |
| 499 | // first, analyze the parameters and return type in order they appear in | 475 | // first, analyze the parameters and return type in order they appear in |
| 500 | // source code in order for error messages to be in the best order. | 476 | // source code in order for error messages to be in the best order. |
| 501 | buf_resize(&fn_type->name, 0); | 477 | buf_resize(&fn_type->name, 0); |
| 502 | const char *export_str = fn_table_entry->internal_linkage ? "" : "export "; | 478 | const char *extern_str = fn_type->data.fn.is_extern ? "extern " : ""; |
| 503 | const char *inline_str = fn_table_entry->is_inline ? "inline " : ""; | ||
| 504 | const char *naked_str = fn_type->data.fn.is_naked ? "naked " : ""; | 479 | const char *naked_str = fn_type->data.fn.is_naked ? "naked " : ""; |
| 505 | buf_appendf(&fn_type->name, "%s%s%sfn(", export_str, inline_str, naked_str); | 480 | buf_appendf(&fn_type->name, "%s%sfn(", extern_str, naked_str); |
| 506 | for (int i = 0; i < src_param_count; i += 1) { | 481 | for (int i = 0; i < src_param_count; i += 1) { |
| 507 | AstNode *child = node->data.fn_proto.params.at(i); | 482 | AstNode *child = node->data.fn_proto.params.at(i); |
| 508 | assert(child->type == NodeTypeParamDecl); | 483 | assert(child->type == NodeTypeParamDecl); |
| ... | @@ -525,10 +500,9 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t | ... | @@ -525,10 +500,9 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t |
| 525 | const char *comma = (src_param_count == 0) ? "" : ", "; | 500 | const char *comma = (src_param_count == 0) ? "" : ", "; |
| 526 | buf_appendf(&fn_type->name, "%s...", comma); | 501 | buf_appendf(&fn_type->name, "%s...", comma); |
| 527 | } | 502 | } |
| 528 | |||
| 529 | buf_appendf(&fn_type->name, ")"); | 503 | buf_appendf(&fn_type->name, ")"); |
| 530 | if (return_type->id != TypeTableEntryIdVoid) { | 504 | if (return_type->id != TypeTableEntryIdVoid) { |
| 531 | buf_appendf(&fn_type->name, " %s", buf_ptr(&return_type->name)); | 505 | buf_appendf(&fn_type->name, " -> %s", buf_ptr(&return_type->name)); |
| 532 | } | 506 | } |
| 533 | 507 | ||
| 534 | 508 | ||
| ... | @@ -593,13 +567,12 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t | ... | @@ -593,13 +567,12 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t |
| 593 | fn_type->data.fn.gen_param_count = gen_param_index; | 567 | fn_type->data.fn.gen_param_count = gen_param_index; |
| 594 | 568 | ||
| 595 | if (fn_proto->skip) { | 569 | if (fn_proto->skip) { |
| 596 | return; | 570 | return g->builtin_types.entry_invalid; |
| 597 | } | 571 | } |
| 598 | 572 | ||
| 599 | auto table_entry = import->fn_type_table.maybe_get(&fn_type->name); | 573 | auto table_entry = import->fn_type_table.maybe_get(&fn_type->name); |
| 600 | if (table_entry) { | 574 | if (table_entry) { |
| 601 | fn_type = table_entry->value; | 575 | return table_entry->value; |
| 602 | fn_table_entry->type_entry = fn_type; | ||
| 603 | } else { | 576 | } else { |
| 604 | fn_type->data.fn.raw_type_ref = LLVMFunctionType(gen_return_type->type_ref, | 577 | fn_type->data.fn.raw_type_ref = LLVMFunctionType(gen_return_type->type_ref, |
| 605 | gen_param_types, gen_param_index, fn_type->data.fn.is_var_args); | 578 | gen_param_types, gen_param_index, fn_type->data.fn.is_var_args); |
| ... | @@ -608,8 +581,56 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t | ... | @@ -608,8 +581,56 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t |
| 608 | param_di_types, gen_param_index + 1, 0); | 581 | param_di_types, gen_param_index + 1, 0); |
| 609 | 582 | ||
| 610 | import->fn_type_table.put(&fn_type->name, fn_type); | 583 | import->fn_type_table.put(&fn_type->name, fn_type); |
| 584 | |||
| 585 | return fn_type; | ||
| 586 | } | ||
| 587 | } | ||
| 588 | |||
| 589 | |||
| 590 | static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_table_entry, | ||
| 591 | ImportTableEntry *import) | ||
| 592 | { | ||
| 593 | assert(node->type == NodeTypeFnProto); | ||
| 594 | AstNodeFnProto *fn_proto = &node->data.fn_proto; | ||
| 595 | |||
| 596 | if (fn_proto->skip) { | ||
| 597 | return; | ||
| 611 | } | 598 | } |
| 612 | 599 | ||
| 600 | bool is_naked = false; | ||
| 601 | for (int i = 0; i < fn_proto->directives->length; i += 1) { | ||
| 602 | AstNode *directive_node = fn_proto->directives->at(i); | ||
| 603 | Buf *name = &directive_node->data.directive.name; | ||
| 604 | |||
| 605 | if (buf_eql_str(name, "attribute")) { | ||
| 606 | Buf *attr_name = &directive_node->data.directive.param; | ||
| 607 | if (fn_table_entry->fn_def_node) { | ||
| 608 | if (buf_eql_str(attr_name, "naked")) { | ||
| 609 | is_naked = true; | ||
| 610 | } else if (buf_eql_str(attr_name, "inline")) { | ||
| 611 | fn_table_entry->is_inline = true; | ||
| 612 | } else { | ||
| 613 | add_node_error(g, directive_node, | ||
| 614 | buf_sprintf("invalid function attribute: '%s'", buf_ptr(name))); | ||
| 615 | } | ||
| 616 | } else { | ||
| 617 | add_node_error(g, directive_node, | ||
| 618 | buf_sprintf("invalid function attribute: '%s'", buf_ptr(name))); | ||
| 619 | } | ||
| 620 | } else { | ||
| 621 | add_node_error(g, directive_node, | ||
| 622 | buf_sprintf("invalid directive: '%s'", buf_ptr(name))); | ||
| 623 | } | ||
| 624 | } | ||
| 625 | |||
| 626 | TypeTableEntry *fn_type = analyze_fn_proto_type(g, import, import->block_context, nullptr, node, is_naked); | ||
| 627 | |||
| 628 | if (fn_type->id == TypeTableEntryIdInvalid) { | ||
| 629 | fn_proto->skip = true; | ||
| 630 | return; | ||
| 631 | } | ||
| 632 | |||
| 633 | fn_table_entry->type_entry = fn_type; | ||
| 613 | 634 | ||
| 614 | fn_table_entry->fn_value = LLVMAddFunction(g->module, buf_ptr(&fn_table_entry->symbol_name), | 635 | fn_table_entry->fn_value = LLVMAddFunction(g->module, buf_ptr(&fn_table_entry->symbol_name), |
| 615 | fn_type->data.fn.raw_type_ref); | 636 | fn_type->data.fn.raw_type_ref); |
| ... | @@ -624,7 +645,7 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t | ... | @@ -624,7 +645,7 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t |
| 624 | LLVMSetLinkage(fn_table_entry->fn_value, fn_table_entry->internal_linkage ? | 645 | LLVMSetLinkage(fn_table_entry->fn_value, fn_table_entry->internal_linkage ? |
| 625 | LLVMInternalLinkage : LLVMExternalLinkage); | 646 | LLVMInternalLinkage : LLVMExternalLinkage); |
| 626 | 647 | ||
| 627 | if (return_type->id == TypeTableEntryIdUnreachable) { | 648 | if (fn_type->data.fn.src_return_type->id == TypeTableEntryIdUnreachable) { |
| 628 | LLVMAddFunctionAttr(fn_table_entry->fn_value, LLVMNoReturnAttribute); | 649 | LLVMAddFunctionAttr(fn_table_entry->fn_value, LLVMNoReturnAttribute); |
| 629 | } | 650 | } |
| 630 | LLVMSetFunctionCallConv(fn_table_entry->fn_value, fn_type->data.fn.calling_convention); | 651 | LLVMSetFunctionCallConv(fn_table_entry->fn_value, fn_type->data.fn.calling_convention); |
| ... | @@ -1353,7 +1374,29 @@ static bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTable | ... | @@ -1353,7 +1374,29 @@ static bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTable |
| 1353 | if (expected_type->id == TypeTableEntryIdFn && | 1374 | if (expected_type->id == TypeTableEntryIdFn && |
| 1354 | actual_type->id == TypeTableEntryIdFn) | 1375 | actual_type->id == TypeTableEntryIdFn) |
| 1355 | { | 1376 | { |
| 1356 | zig_panic("TODO types_match_const_cast_only for fns"); | 1377 | if (expected_type->data.fn.is_extern != actual_type->data.fn.is_extern) { |
| 1378 | return false; | ||
| 1379 | } | ||
| 1380 | if (expected_type->data.fn.is_naked != actual_type->data.fn.is_naked) { | ||
| 1381 | return false; | ||
| 1382 | } | ||
| 1383 | if (!types_match_const_cast_only(expected_type->data.fn.src_return_type, | ||
| 1384 | actual_type->data.fn.src_return_type)) | ||
| 1385 | { | ||
| 1386 | return false; | ||
| 1387 | } | ||
| 1388 | if (expected_type->data.fn.src_param_count != actual_type->data.fn.src_param_count) { | ||
| 1389 | return false; | ||
| 1390 | } | ||
| 1391 | for (int i = 0; i < expected_type->data.fn.src_param_count; i += 1) { | ||
| 1392 | // note it's reversed for parameters | ||
| 1393 | if (types_match_const_cast_only(actual_type->data.fn.param_types[i], | ||
| 1394 | expected_type->data.fn.param_types[i])) | ||
| 1395 | { | ||
| 1396 | return false; | ||
| 1397 | } | ||
| 1398 | } | ||
| 1399 | return true; | ||
| 1357 | } | 1400 | } |
| 1358 | 1401 | ||
| 1359 | 1402 | ||
| ... | @@ -2902,6 +2945,18 @@ static TypeTableEntry *analyze_array_type(CodeGen *g, ImportTableEntry *import, | ... | @@ -2902,6 +2945,18 @@ static TypeTableEntry *analyze_array_type(CodeGen *g, ImportTableEntry *import, |
| 2902 | } | 2945 | } |
| 2903 | } | 2946 | } |
| 2904 | 2947 | ||
| 2948 | static TypeTableEntry *analyze_fn_proto_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context, | ||
| 2949 | TypeTableEntry *expected_type, AstNode *node) | ||
| 2950 | { | ||
| 2951 | TypeTableEntry *type_entry = analyze_fn_proto_type(g, import, context, expected_type, node, false); | ||
| 2952 | |||
| 2953 | if (type_entry->id == TypeTableEntryIdInvalid) { | ||
| 2954 | return type_entry; | ||
| 2955 | } | ||
| 2956 | |||
| 2957 | return resolve_expr_const_val_as_type(g, node, type_entry); | ||
| 2958 | } | ||
| 2959 | |||
| 2905 | static TypeTableEntry *analyze_while_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context, | 2960 | static TypeTableEntry *analyze_while_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context, |
| 2906 | TypeTableEntry *expected_type, AstNode *node) | 2961 | TypeTableEntry *expected_type, AstNode *node) |
| 2907 | { | 2962 | { |
| ... | @@ -4240,6 +4295,9 @@ static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import, | ... | @@ -4240,6 +4295,9 @@ static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import, |
| 4240 | case NodeTypeArrayType: | 4295 | case NodeTypeArrayType: |
| 4241 | return_type = analyze_array_type(g, import, context, expected_type, node); | 4296 | return_type = analyze_array_type(g, import, context, expected_type, node); |
| 4242 | break; | 4297 | break; |
| 4298 | case NodeTypeFnProto: | ||
| 4299 | return_type = analyze_fn_proto_expr(g, import, context, expected_type, node); | ||
| 4300 | break; | ||
| 4243 | case NodeTypeErrorType: | 4301 | case NodeTypeErrorType: |
| 4244 | return_type = resolve_expr_const_val_as_type(g, node, g->builtin_types.entry_pure_error); | 4302 | return_type = resolve_expr_const_val_as_type(g, node, g->builtin_types.entry_pure_error); |
| 4245 | break; | 4303 | break; |
| ... | @@ -4250,7 +4308,6 @@ static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import, | ... | @@ -4250,7 +4308,6 @@ static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import, |
| 4250 | case NodeTypeSwitchRange: | 4308 | case NodeTypeSwitchRange: |
| 4251 | case NodeTypeDirective: | 4309 | case NodeTypeDirective: |
| 4252 | case NodeTypeFnDecl: | 4310 | case NodeTypeFnDecl: |
| 4253 | case NodeTypeFnProto: | ||
| 4254 | case NodeTypeParamDecl: | 4311 | case NodeTypeParamDecl: |
| 4255 | case NodeTypeRoot: | 4312 | case NodeTypeRoot: |
| 4256 | case NodeTypeRootExportDecl: | 4313 | case NodeTypeRootExportDecl: |
| ... | @@ -4555,13 +4612,23 @@ static void collect_expr_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode | ... | @@ -4555,13 +4612,23 @@ static void collect_expr_decl_deps(CodeGen *g, ImportTableEntry *import, AstNode |
| 4555 | collect_expr_decl_deps(g, import, node->data.switch_range.start, decl_node); | 4612 | collect_expr_decl_deps(g, import, node->data.switch_range.start, decl_node); |
| 4556 | collect_expr_decl_deps(g, import, node->data.switch_range.end, decl_node); | 4613 | collect_expr_decl_deps(g, import, node->data.switch_range.end, decl_node); |
| 4557 | break; | 4614 | break; |
| 4558 | case NodeTypeVariableDeclaration: | ||
| 4559 | case NodeTypeFnProto: | 4615 | case NodeTypeFnProto: |
| 4616 | // remember that fn proto node is used for function definitions as well | ||
| 4617 | // as types | ||
| 4618 | for (int i = 0; i < node->data.fn_proto.params.length; i += 1) { | ||
| 4619 | AstNode *param = node->data.fn_proto.params.at(i); | ||
| 4620 | collect_expr_decl_deps(g, import, param, decl_node); | ||
| 4621 | } | ||
| 4622 | collect_expr_decl_deps(g, import, node->data.fn_proto.return_type, decl_node); | ||
| 4623 | break; | ||
| 4624 | case NodeTypeParamDecl: | ||
| 4625 | collect_expr_decl_deps(g, import, node->data.param_decl.type, decl_node); | ||
| 4626 | break; | ||
| 4627 | case NodeTypeVariableDeclaration: | ||
| 4560 | case NodeTypeRootExportDecl: | 4628 | case NodeTypeRootExportDecl: |
| 4561 | case NodeTypeFnDef: | 4629 | case NodeTypeFnDef: |
| 4562 | case NodeTypeRoot: | 4630 | case NodeTypeRoot: |
| 4563 | case NodeTypeFnDecl: | 4631 | case NodeTypeFnDecl: |
| 4564 | case NodeTypeParamDecl: | ||
| 4565 | case NodeTypeDirective: | 4632 | case NodeTypeDirective: |
| 4566 | case NodeTypeImport: | 4633 | case NodeTypeImport: |
| 4567 | case NodeTypeCImport: | 4634 | case NodeTypeCImport: |
| ... | @@ -4705,12 +4772,8 @@ static void detect_top_level_decl_deps(CodeGen *g, ImportTableEntry *import, Ast | ... | @@ -4705,12 +4772,8 @@ static void detect_top_level_decl_deps(CodeGen *g, ImportTableEntry *import, Ast |
| 4705 | // determine which other top level declarations this function prototype depends on. | 4772 | // determine which other top level declarations this function prototype depends on. |
| 4706 | TopLevelDecl *decl_node = &node->data.fn_proto.top_level_decl; | 4773 | TopLevelDecl *decl_node = &node->data.fn_proto.top_level_decl; |
| 4707 | decl_node->deps.init(1); | 4774 | decl_node->deps.init(1); |
| 4708 | for (int i = 0; i < node->data.fn_proto.params.length; i += 1) { | 4775 | |
| 4709 | AstNode *param_node = node->data.fn_proto.params.at(i); | 4776 | collect_expr_decl_deps(g, import, node, decl_node); |
| 4710 | assert(param_node->type == NodeTypeParamDecl); | ||
| 4711 | collect_expr_decl_deps(g, import, param_node->data.param_decl.type, decl_node); | ||
| 4712 | } | ||
| 4713 | collect_expr_decl_deps(g, import, node->data.fn_proto.return_type, decl_node); | ||
| 4714 | 4777 | ||
| 4715 | decl_node->name = name; | 4778 | decl_node->name = name; |
| 4716 | decl_node->import = import; | 4779 | decl_node->import = import; |
| ... | @@ -4999,11 +5062,12 @@ Expr *get_resolved_expr(AstNode *node) { | ... | @@ -4999,11 +5062,12 @@ Expr *get_resolved_expr(AstNode *node) { |
| 4999 | return &node->data.error_type.resolved_expr; | 5062 | return &node->data.error_type.resolved_expr; |
| 5000 | case NodeTypeSwitchExpr: | 5063 | case NodeTypeSwitchExpr: |
| 5001 | return &node->data.switch_expr.resolved_expr; | 5064 | return &node->data.switch_expr.resolved_expr; |
| 5065 | case NodeTypeFnProto: | ||
| 5066 | return &node->data.fn_proto.resolved_expr; | ||
| 5002 | case NodeTypeSwitchProng: | 5067 | case NodeTypeSwitchProng: |
| 5003 | case NodeTypeSwitchRange: | 5068 | case NodeTypeSwitchRange: |
| 5004 | case NodeTypeRoot: | 5069 | case NodeTypeRoot: |
| 5005 | case NodeTypeRootExportDecl: | 5070 | case NodeTypeRootExportDecl: |
| 5006 | case NodeTypeFnProto: | ||
| 5007 | case NodeTypeFnDef: | 5071 | case NodeTypeFnDef: |
| 5008 | case NodeTypeFnDecl: | 5072 | case NodeTypeFnDecl: |
| 5009 | case NodeTypeParamDecl: | 5073 | case NodeTypeParamDecl: |
src/parser.cpp+36-19| ... | @@ -503,6 +503,8 @@ static AstNode *ast_parse_if_expr(ParseContext *pc, int *token_index, bool manda | ... | @@ -503,6 +503,8 @@ static AstNode *ast_parse_if_expr(ParseContext *pc, int *token_index, bool manda |
| 503 | static AstNode *ast_parse_block_expr(ParseContext *pc, int *token_index, bool mandatory); | 503 | static AstNode *ast_parse_block_expr(ParseContext *pc, int *token_index, bool mandatory); |
| 504 | static AstNode *ast_parse_unwrap_expr(ParseContext *pc, int *token_index, bool mandatory); | 504 | static AstNode *ast_parse_unwrap_expr(ParseContext *pc, int *token_index, bool mandatory); |
| 505 | static AstNode *ast_parse_prefix_op_expr(ParseContext *pc, int *token_index, bool mandatory); | 505 | static AstNode *ast_parse_prefix_op_expr(ParseContext *pc, int *token_index, bool mandatory); |
| 506 | static AstNode *ast_parse_fn_proto(ParseContext *pc, int *token_index, bool mandatory, | ||
| 507 | ZigList<AstNode*> *directives, VisibMod visib_mod); | ||
| 506 | 508 | ||
| 507 | static void ast_expect_token(ParseContext *pc, Token *token, TokenId token_id) { | 509 | static void ast_expect_token(ParseContext *pc, Token *token, TokenId token_id) { |
| 508 | if (token->id == token_id) { | 510 | if (token->id == token_id) { |
| ... | @@ -671,7 +673,7 @@ static AstNode *ast_parse_grouped_expr(ParseContext *pc, int *token_index, bool | ... | @@ -671,7 +673,7 @@ static AstNode *ast_parse_grouped_expr(ParseContext *pc, int *token_index, bool |
| 671 | Token *l_paren = &pc->tokens->at(*token_index); | 673 | Token *l_paren = &pc->tokens->at(*token_index); |
| 672 | if (l_paren->id != TokenIdLParen) { | 674 | if (l_paren->id != TokenIdLParen) { |
| 673 | if (mandatory) { | 675 | if (mandatory) { |
| 674 | ast_invalid_token_error(pc, l_paren); | 676 | ast_expect_token(pc, l_paren, TokenIdLParen); |
| 675 | } else { | 677 | } else { |
| 676 | return nullptr; | 678 | return nullptr; |
| 677 | } | 679 | } |
| ... | @@ -695,7 +697,7 @@ static AstNode *ast_parse_array_type_expr(ParseContext *pc, int *token_index, bo | ... | @@ -695,7 +697,7 @@ static AstNode *ast_parse_array_type_expr(ParseContext *pc, int *token_index, bo |
| 695 | Token *l_bracket = &pc->tokens->at(*token_index); | 697 | Token *l_bracket = &pc->tokens->at(*token_index); |
| 696 | if (l_bracket->id != TokenIdLBracket) { | 698 | if (l_bracket->id != TokenIdLBracket) { |
| 697 | if (mandatory) { | 699 | if (mandatory) { |
| 698 | ast_invalid_token_error(pc, l_bracket); | 700 | ast_expect_token(pc, l_bracket, TokenIdLBracket); |
| 699 | } else { | 701 | } else { |
| 700 | return nullptr; | 702 | return nullptr; |
| 701 | } | 703 | } |
| ... | @@ -865,7 +867,7 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, int *token_index, bool mand | ... | @@ -865,7 +867,7 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, int *token_index, bool mand |
| 865 | 867 | ||
| 866 | if (asm_token->id != TokenIdKeywordAsm) { | 868 | if (asm_token->id != TokenIdKeywordAsm) { |
| 867 | if (mandatory) { | 869 | if (mandatory) { |
| 868 | ast_invalid_token_error(pc, asm_token); | 870 | ast_expect_token(pc, asm_token, TokenIdKeywordAsm); |
| 869 | } else { | 871 | } else { |
| 870 | return nullptr; | 872 | return nullptr; |
| 871 | } | 873 | } |
| ... | @@ -905,7 +907,7 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, int *token_index, bool mand | ... | @@ -905,7 +907,7 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, int *token_index, bool mand |
| 905 | } | 907 | } |
| 906 | 908 | ||
| 907 | /* | 909 | /* |
| 908 | PrimaryExpression : "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | AsmExpression | ("error" "." "Symbol") | 910 | PrimaryExpression = "Number" | "String" | "CharLiteral" | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | "Symbol" | ("@" "Symbol" FnCallExpression) | ArrayType | FnProto | AsmExpression | ("error" "." "Symbol") |
| 909 | KeywordLiteral : "true" | "false" | "null" | "break" | "continue" | "undefined" | "error" | 911 | KeywordLiteral : "true" | "false" | "null" | "break" | "continue" | "undefined" | "error" |
| 910 | */ | 912 | */ |
| 911 | static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool mandatory) { | 913 | static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool mandatory) { |
| ... | @@ -956,6 +958,11 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool | ... | @@ -956,6 +958,11 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool |
| 956 | AstNode *node = ast_create_node(pc, NodeTypeErrorType, token); | 958 | AstNode *node = ast_create_node(pc, NodeTypeErrorType, token); |
| 957 | *token_index += 1; | 959 | *token_index += 1; |
| 958 | return node; | 960 | return node; |
| 961 | } else if (token->id == TokenIdKeywordExtern) { | ||
| 962 | *token_index += 1; | ||
| 963 | AstNode *node = ast_parse_fn_proto(pc, token_index, true, nullptr, VisibModPrivate); | ||
| 964 | node->data.fn_proto.is_extern = true; | ||
| 965 | return node; | ||
| 959 | } else if (token->id == TokenIdAtSign) { | 966 | } else if (token->id == TokenIdAtSign) { |
| 960 | *token_index += 1; | 967 | *token_index += 1; |
| 961 | Token *name_tok = ast_eat_token(pc, token_index, TokenIdSymbol); | 968 | Token *name_tok = ast_eat_token(pc, token_index, TokenIdSymbol); |
| ... | @@ -1002,6 +1009,11 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool | ... | @@ -1002,6 +1009,11 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool |
| 1002 | return array_type_node; | 1009 | return array_type_node; |
| 1003 | } | 1010 | } |
| 1004 | 1011 | ||
| 1012 | AstNode *fn_proto_node = ast_parse_fn_proto(pc, token_index, false, nullptr, VisibModPrivate); | ||
| 1013 | if (fn_proto_node) { | ||
| 1014 | return fn_proto_node; | ||
| 1015 | } | ||
| 1016 | |||
| 1005 | AstNode *asm_expr = ast_parse_asm_expr(pc, token_index, false); | 1017 | AstNode *asm_expr = ast_parse_asm_expr(pc, token_index, false); |
| 1006 | if (asm_expr) { | 1018 | if (asm_expr) { |
| 1007 | return asm_expr; | 1019 | return asm_expr; |
| ... | @@ -1055,7 +1067,7 @@ static AstNode *ast_parse_curly_suffix_expr(ParseContext *pc, int *token_index, | ... | @@ -1055,7 +1067,7 @@ static AstNode *ast_parse_curly_suffix_expr(ParseContext *pc, int *token_index, |
| 1055 | token = &pc->tokens->at(*token_index); | 1067 | token = &pc->tokens->at(*token_index); |
| 1056 | continue; | 1068 | continue; |
| 1057 | } else if (comma_tok->id != TokenIdRBrace) { | 1069 | } else if (comma_tok->id != TokenIdRBrace) { |
| 1058 | ast_invalid_token_error(pc, comma_tok); | 1070 | ast_expect_token(pc, comma_tok, TokenIdRBrace); |
| 1059 | } else { | 1071 | } else { |
| 1060 | *token_index += 1; | 1072 | *token_index += 1; |
| 1061 | break; | 1073 | break; |
| ... | @@ -1084,7 +1096,7 @@ static AstNode *ast_parse_curly_suffix_expr(ParseContext *pc, int *token_index, | ... | @@ -1084,7 +1096,7 @@ static AstNode *ast_parse_curly_suffix_expr(ParseContext *pc, int *token_index, |
| 1084 | token = &pc->tokens->at(*token_index); | 1096 | token = &pc->tokens->at(*token_index); |
| 1085 | continue; | 1097 | continue; |
| 1086 | } else if (comma_tok->id != TokenIdRBrace) { | 1098 | } else if (comma_tok->id != TokenIdRBrace) { |
| 1087 | ast_invalid_token_error(pc, comma_tok); | 1099 | ast_expect_token(pc, comma_tok, TokenIdRBrace); |
| 1088 | } else { | 1100 | } else { |
| 1089 | *token_index += 1; | 1101 | *token_index += 1; |
| 1090 | break; | 1102 | break; |
| ... | @@ -1555,7 +1567,7 @@ static AstNode *ast_parse_else(ParseContext *pc, int *token_index, bool mandator | ... | @@ -1555,7 +1567,7 @@ static AstNode *ast_parse_else(ParseContext *pc, int *token_index, bool mandator |
| 1555 | 1567 | ||
| 1556 | if (else_token->id != TokenIdKeywordElse) { | 1568 | if (else_token->id != TokenIdKeywordElse) { |
| 1557 | if (mandatory) { | 1569 | if (mandatory) { |
| 1558 | ast_invalid_token_error(pc, else_token); | 1570 | ast_expect_token(pc, else_token, TokenIdKeywordElse); |
| 1559 | } else { | 1571 | } else { |
| 1560 | return nullptr; | 1572 | return nullptr; |
| 1561 | } | 1573 | } |
| ... | @@ -1574,7 +1586,7 @@ static AstNode *ast_parse_if_expr(ParseContext *pc, int *token_index, bool manda | ... | @@ -1574,7 +1586,7 @@ static AstNode *ast_parse_if_expr(ParseContext *pc, int *token_index, bool manda |
| 1574 | Token *if_tok = &pc->tokens->at(*token_index); | 1586 | Token *if_tok = &pc->tokens->at(*token_index); |
| 1575 | if (if_tok->id != TokenIdKeywordIf) { | 1587 | if (if_tok->id != TokenIdKeywordIf) { |
| 1576 | if (mandatory) { | 1588 | if (mandatory) { |
| 1577 | ast_invalid_token_error(pc, if_tok); | 1589 | ast_expect_token(pc, if_tok, TokenIdKeywordIf); |
| 1578 | } else { | 1590 | } else { |
| 1579 | return nullptr; | 1591 | return nullptr; |
| 1580 | } | 1592 | } |
| ... | @@ -1637,7 +1649,8 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, int *token_index, bool m | ... | @@ -1637,7 +1649,8 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, int *token_index, bool m |
| 1637 | kind = ReturnKindError; | 1649 | kind = ReturnKindError; |
| 1638 | *token_index += 2; | 1650 | *token_index += 2; |
| 1639 | } else if (mandatory) { | 1651 | } else if (mandatory) { |
| 1640 | ast_invalid_token_error(pc, token); | 1652 | ast_expect_token(pc, next_token, TokenIdKeywordReturn); |
| 1653 | zig_unreachable(); | ||
| 1641 | } else { | 1654 | } else { |
| 1642 | return nullptr; | 1655 | return nullptr; |
| 1643 | } | 1656 | } |
| ... | @@ -1647,7 +1660,8 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, int *token_index, bool m | ... | @@ -1647,7 +1660,8 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, int *token_index, bool m |
| 1647 | kind = ReturnKindMaybe; | 1660 | kind = ReturnKindMaybe; |
| 1648 | *token_index += 2; | 1661 | *token_index += 2; |
| 1649 | } else if (mandatory) { | 1662 | } else if (mandatory) { |
| 1650 | ast_invalid_token_error(pc, token); | 1663 | ast_expect_token(pc, next_token, TokenIdKeywordReturn); |
| 1664 | zig_unreachable(); | ||
| 1651 | } else { | 1665 | } else { |
| 1652 | return nullptr; | 1666 | return nullptr; |
| 1653 | } | 1667 | } |
| ... | @@ -1655,7 +1669,8 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, int *token_index, bool m | ... | @@ -1655,7 +1669,8 @@ static AstNode *ast_parse_return_expr(ParseContext *pc, int *token_index, bool m |
| 1655 | kind = ReturnKindUnconditional; | 1669 | kind = ReturnKindUnconditional; |
| 1656 | *token_index += 1; | 1670 | *token_index += 1; |
| 1657 | } else if (mandatory) { | 1671 | } else if (mandatory) { |
| 1658 | ast_invalid_token_error(pc, token); | 1672 | ast_expect_token(pc, token, TokenIdKeywordReturn); |
| 1673 | zig_unreachable(); | ||
| 1659 | } else { | 1674 | } else { |
| 1660 | return nullptr; | 1675 | return nullptr; |
| 1661 | } | 1676 | } |
| ... | @@ -1756,7 +1771,7 @@ static AstNode *ast_parse_while_expr(ParseContext *pc, int *token_index, bool ma | ... | @@ -1756,7 +1771,7 @@ static AstNode *ast_parse_while_expr(ParseContext *pc, int *token_index, bool ma |
| 1756 | 1771 | ||
| 1757 | if (token->id != TokenIdKeywordWhile) { | 1772 | if (token->id != TokenIdKeywordWhile) { |
| 1758 | if (mandatory) { | 1773 | if (mandatory) { |
| 1759 | ast_invalid_token_error(pc, token); | 1774 | ast_expect_token(pc, token, TokenIdKeywordWhile); |
| 1760 | } else { | 1775 | } else { |
| 1761 | return nullptr; | 1776 | return nullptr; |
| 1762 | } | 1777 | } |
| ... | @@ -1791,7 +1806,7 @@ static AstNode *ast_parse_for_expr(ParseContext *pc, int *token_index, bool mand | ... | @@ -1791,7 +1806,7 @@ static AstNode *ast_parse_for_expr(ParseContext *pc, int *token_index, bool mand |
| 1791 | 1806 | ||
| 1792 | if (token->id != TokenIdKeywordFor) { | 1807 | if (token->id != TokenIdKeywordFor) { |
| 1793 | if (mandatory) { | 1808 | if (mandatory) { |
| 1794 | ast_invalid_token_error(pc, token); | 1809 | ast_expect_token(pc, token, TokenIdKeywordFor); |
| 1795 | } else { | 1810 | } else { |
| 1796 | return nullptr; | 1811 | return nullptr; |
| 1797 | } | 1812 | } |
| ... | @@ -1829,7 +1844,7 @@ static AstNode *ast_parse_switch_expr(ParseContext *pc, int *token_index, bool m | ... | @@ -1829,7 +1844,7 @@ static AstNode *ast_parse_switch_expr(ParseContext *pc, int *token_index, bool m |
| 1829 | 1844 | ||
| 1830 | if (token->id != TokenIdKeywordSwitch) { | 1845 | if (token->id != TokenIdKeywordSwitch) { |
| 1831 | if (mandatory) { | 1846 | if (mandatory) { |
| 1832 | ast_invalid_token_error(pc, token); | 1847 | ast_expect_token(pc, token, TokenIdKeywordSwitch); |
| 1833 | } else { | 1848 | } else { |
| 1834 | return nullptr; | 1849 | return nullptr; |
| 1835 | } | 1850 | } |
| ... | @@ -2082,7 +2097,7 @@ static AstNode *ast_parse_label(ParseContext *pc, int *token_index, bool mandato | ... | @@ -2082,7 +2097,7 @@ static AstNode *ast_parse_label(ParseContext *pc, int *token_index, bool mandato |
| 2082 | Token *symbol_token = &pc->tokens->at(*token_index); | 2097 | Token *symbol_token = &pc->tokens->at(*token_index); |
| 2083 | if (symbol_token->id != TokenIdSymbol) { | 2098 | if (symbol_token->id != TokenIdSymbol) { |
| 2084 | if (mandatory) { | 2099 | if (mandatory) { |
| 2085 | ast_invalid_token_error(pc, symbol_token); | 2100 | ast_expect_token(pc, symbol_token, TokenIdSymbol); |
| 2086 | } else { | 2101 | } else { |
| 2087 | return nullptr; | 2102 | return nullptr; |
| 2088 | } | 2103 | } |
| ... | @@ -2091,7 +2106,7 @@ static AstNode *ast_parse_label(ParseContext *pc, int *token_index, bool mandato | ... | @@ -2091,7 +2106,7 @@ static AstNode *ast_parse_label(ParseContext *pc, int *token_index, bool mandato |
| 2091 | Token *colon_token = &pc->tokens->at(*token_index + 1); | 2106 | Token *colon_token = &pc->tokens->at(*token_index + 1); |
| 2092 | if (colon_token->id != TokenIdColon) { | 2107 | if (colon_token->id != TokenIdColon) { |
| 2093 | if (mandatory) { | 2108 | if (mandatory) { |
| 2094 | ast_invalid_token_error(pc, colon_token); | 2109 | ast_expect_token(pc, colon_token, TokenIdColon); |
| 2095 | } else { | 2110 | } else { |
| 2096 | return nullptr; | 2111 | return nullptr; |
| 2097 | } | 2112 | } |
| ... | @@ -2122,7 +2137,7 @@ static AstNode *ast_parse_block(ParseContext *pc, int *token_index, bool mandato | ... | @@ -2122,7 +2137,7 @@ static AstNode *ast_parse_block(ParseContext *pc, int *token_index, bool mandato |
| 2122 | 2137 | ||
| 2123 | if (last_token->id != TokenIdLBrace) { | 2138 | if (last_token->id != TokenIdLBrace) { |
| 2124 | if (mandatory) { | 2139 | if (mandatory) { |
| 2125 | ast_invalid_token_error(pc, last_token); | 2140 | ast_expect_token(pc, last_token, TokenIdLBrace); |
| 2126 | } else { | 2141 | } else { |
| 2127 | return nullptr; | 2142 | return nullptr; |
| 2128 | } | 2143 | } |
| ... | @@ -2245,7 +2260,7 @@ static AstNode *ast_parse_extern_decl(ParseContext *pc, int *token_index, bool m | ... | @@ -2245,7 +2260,7 @@ static AstNode *ast_parse_extern_decl(ParseContext *pc, int *token_index, bool m |
| 2245 | Token *extern_kw = &pc->tokens->at(*token_index); | 2260 | Token *extern_kw = &pc->tokens->at(*token_index); |
| 2246 | if (extern_kw->id != TokenIdKeywordExtern) { | 2261 | if (extern_kw->id != TokenIdKeywordExtern) { |
| 2247 | if (mandatory) { | 2262 | if (mandatory) { |
| 2248 | ast_invalid_token_error(pc, extern_kw); | 2263 | ast_expect_token(pc, extern_kw, TokenIdKeywordExtern); |
| 2249 | } else { | 2264 | } else { |
| 2250 | return nullptr; | 2265 | return nullptr; |
| 2251 | } | 2266 | } |
| ... | @@ -2591,7 +2606,9 @@ void normalize_parent_ptrs(AstNode *node) { | ... | @@ -2591,7 +2606,9 @@ void normalize_parent_ptrs(AstNode *node) { |
| 2591 | break; | 2606 | break; |
| 2592 | case NodeTypeFnProto: | 2607 | case NodeTypeFnProto: |
| 2593 | set_field(&node->data.fn_proto.return_type); | 2608 | set_field(&node->data.fn_proto.return_type); |
| 2594 | set_list_fields(node->data.fn_proto.directives); | 2609 | if (node->data.fn_proto.directives) { |
| 2610 | set_list_fields(node->data.fn_proto.directives); | ||
| 2611 | } | ||
| 2595 | set_list_fields(&node->data.fn_proto.params); | 2612 | set_list_fields(&node->data.fn_proto.params); |
| 2596 | break; | 2613 | break; |
| 2597 | case NodeTypeFnDef: | 2614 | case NodeTypeFnDef: |
test/run_tests.cpp+17| ... | @@ -1866,6 +1866,23 @@ fn f(i32) {} | ... | @@ -1866,6 +1866,23 @@ fn f(i32) {} |
| 1866 | )SOURCE", 2, | 1866 | )SOURCE", 2, |
| 1867 | ".tmp_source.zig:2:1: error: missing function name", | 1867 | ".tmp_source.zig:2:1: error: missing function name", |
| 1868 | ".tmp_source.zig:3:6: error: missing parameter name"); | 1868 | ".tmp_source.zig:3:6: error: missing parameter name"); |
| 1869 | |||
| 1870 | add_compile_fail_case("wrong function type", R"SOURCE( | ||
| 1871 | const fns = []fn(){ a, b, c }; | ||
| 1872 | fn a() -> i32 {0} | ||
| 1873 | fn b() -> i32 {1} | ||
| 1874 | fn c() -> i32 {2} | ||
| 1875 | )SOURCE", 3, | ||
| 1876 | ".tmp_source.zig:2:21: error: expected type 'fn()', got 'fn() -> i32'", | ||
| 1877 | ".tmp_source.zig:2:24: error: expected type 'fn()', got 'fn() -> i32'", | ||
| 1878 | ".tmp_source.zig:2:27: error: expected type 'fn()', got 'fn() -> i32'"); | ||
| 1879 | |||
| 1880 | add_compile_fail_case("extern function pointer mismatch", R"SOURCE( | ||
| 1881 | const fns = [](fn(i32)->i32){ a, b, c }; | ||
| 1882 | pub fn a(x: i32) -> i32 {x + 0} | ||
| 1883 | pub fn b(x: i32) -> i32 {x + 1} | ||
| 1884 | export fn c(x: i32) -> i32 {x + 2} | ||
| 1885 | )SOURCE", 1, ".tmp_source.zig:2:37: error: expected type 'fn(i32) -> i32', got 'extern fn(i32) -> i32'"); | ||
| 1869 | } | 1886 | } |
| 1870 | 1887 | ||
| 1871 | ////////////////////////////////////////////////////////////////////////////// | 1888 | ////////////////////////////////////////////////////////////////////////////// |