diff --git a/README.md b/README.md index 808a1dbb30..cfc24066aa 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,11 @@ compromises backward compatibility. ### Current Status - * Core language features are lacking such as structs, enums, loops. + * Have a look in the examples/ folder to see some code examples. + * Some language features available such as loops, inline assembly, expressions, + literals, functions. * Only Linux x86_64 is supported. * Only building for the native target is supported. - * Have a look in the examples/ folder to see some code examples. * Optimized machine code that Zig produces is indistinguishable from optimized machine code produced from equivalent C program. * Zig can generate dynamic libraries, executables, object files, and C diff --git a/doc/langref.md b/doc/langref.md index 32761f23e5..02ac26f7b9 100644 --- a/doc/langref.md +++ b/doc/langref.md @@ -102,7 +102,11 @@ BoolOrExpression : BoolAndExpression token(BoolOr) BoolOrExpression | BoolAndExp ReturnExpression : token(Return) option(Expression) -IfExpression : token(If) Expression Block option(Else | ElseIf) +IfExpression : IfVarExpression | IfBoolExpression + +IfBoolExpression : token(If) option((token) Expression Block option(Else | ElseIf) + +IfVarExpression : token(If) (token(Const) | token(Var)) token(Symbol) option(token(Colon) Type) Token(MaybeAssign) Expression Block Option(Else | ElseIf) ElseIf : token(Else) IfExpression diff --git a/example/guess_number/main.zig b/example/guess_number/main.zig index 07de57ee06..fffb9e6f89 100644 --- a/example/guess_number/main.zig +++ b/example/guess_number/main.zig @@ -12,17 +12,16 @@ fn main(argc: isize, argv: &&u8, env: &&u8) -> i32 { const answer = rand_int(&rand_state, 0, 100) + 1; while true { - Buffer line = readline("\nGuess a number between 1 and 100: "); + line = readline("\nGuess a number between 1 and 100: "); - (const err, const guess) = parse_number(line); - if err == Error.None { - if guess == answer { - print_str("You win!\n"); - return 0; - } else if guess < answer { + if const guess ?= parse_number(line) { + if (guess > answer) { + print_str("Guess lower.\n"); + } else if (guess < answer) { print_str("Guess higher.\n"); } else { - print_str("Guess lower.\n"); + print_str("You win!\n"); + return 0; } } else { print_str("Invalid number format.\n"); diff --git a/src/analyze.cpp b/src/analyze.cpp index aa747b5de1..27db0ce404 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -45,7 +45,8 @@ static AstNode *first_executing_node(AstNode *node) { case NodeTypeUse: case NodeTypeVoid: case NodeTypeBoolLiteral: - case NodeTypeIfExpr: + case NodeTypeIfBoolExpr: + case NodeTypeIfVarExpr: case NodeTypeLabel: case NodeTypeGoto: case NodeTypeBreak: @@ -528,7 +529,8 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import, case NodeTypeSymbol: case NodeTypeCastExpr: case NodeTypePrefixOpExpr: - case NodeTypeIfExpr: + case NodeTypeIfBoolExpr: + case NodeTypeIfVarExpr: case NodeTypeWhileExpr: case NodeTypeLabel: case NodeTypeGoto: @@ -598,7 +600,8 @@ static void preview_types(CodeGen *g, ImportTableEntry *import, AstNode *node) { case NodeTypeSymbol: case NodeTypeCastExpr: case NodeTypePrefixOpExpr: - case NodeTypeIfExpr: + case NodeTypeIfBoolExpr: + case NodeTypeIfVarExpr: case NodeTypeWhileExpr: case NodeTypeLabel: case NodeTypeGoto: @@ -1394,6 +1397,39 @@ static TypeTableEntry *analyze_continue_expr(CodeGen *g, ImportTableEntry *impor return g->builtin_types.entry_unreachable; } +static TypeTableEntry *analyze_if_bool_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context, + TypeTableEntry *expected_type, AstNode *node) +{ + analyze_expression(g, import, context, g->builtin_types.entry_bool, node->data.if_bool_expr.condition); + + TypeTableEntry *then_type = analyze_expression(g, import, context, expected_type, + node->data.if_bool_expr.then_block); + + TypeTableEntry *else_type; + if (node->data.if_bool_expr.else_node) { + else_type = analyze_expression(g, import, context, expected_type, node->data.if_bool_expr.else_node); + } else { + else_type = g->builtin_types.entry_void; + else_type = resolve_type_compatibility(g, context, node, expected_type, else_type); + } + + + if (expected_type) { + return (then_type->id == TypeTableEntryIdUnreachable) ? else_type : then_type; + } else { + return resolve_peer_type_compatibility(g, context, node, + node->data.if_bool_expr.then_block, node->data.if_bool_expr.else_node, + then_type, else_type); + } +} + +static TypeTableEntry *analyze_if_var_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context, + TypeTableEntry *expected_type, AstNode *node) +{ + assert(node->type == NodeTypeIfVarExpr); + zig_panic("TODO analyze_if_var_expr"); +} + static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context, TypeTableEntry *expected_type, AstNode *node) { @@ -1651,31 +1687,12 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, } } break; - case NodeTypeIfExpr: - { - analyze_expression(g, import, context, g->builtin_types.entry_bool, node->data.if_expr.condition); - - TypeTableEntry *then_type = analyze_expression(g, import, context, expected_type, - node->data.if_expr.then_block); - - TypeTableEntry *else_type; - if (node->data.if_expr.else_node) { - else_type = analyze_expression(g, import, context, expected_type, node->data.if_expr.else_node); - } else { - else_type = g->builtin_types.entry_void; - else_type = resolve_type_compatibility(g, context, node, expected_type, else_type); - } - - - if (expected_type) { - return_type = (then_type->id == TypeTableEntryIdUnreachable) ? else_type : then_type; - } else { - return_type = resolve_peer_type_compatibility(g, context, node, - node->data.if_expr.then_block, node->data.if_expr.else_node, - then_type, else_type); - } - break; - } + case NodeTypeIfBoolExpr: + return_type = analyze_if_bool_expr(g, import, context, expected_type, node); + break; + case NodeTypeIfVarExpr: + return_type = analyze_if_var_expr(g, import, context, expected_type, node); + break; case NodeTypeWhileExpr: return_type = analyze_while_expr(g, import, context, expected_type, node); break; @@ -1828,7 +1845,8 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import, case NodeTypeSymbol: case NodeTypeCastExpr: case NodeTypePrefixOpExpr: - case NodeTypeIfExpr: + case NodeTypeIfBoolExpr: + case NodeTypeIfVarExpr: case NodeTypeWhileExpr: case NodeTypeLabel: case NodeTypeGoto: diff --git a/src/codegen.cpp b/src/codegen.cpp index f7ebe6f9e1..0838b29aec 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -769,18 +769,18 @@ static LLVMValueRef gen_return_expr(CodeGen *g, AstNode *node) { } } -static LLVMValueRef gen_if_expr(CodeGen *g, AstNode *node) { - assert(node->type == NodeTypeIfExpr); - assert(node->data.if_expr.condition); - assert(node->data.if_expr.then_block); +static LLVMValueRef gen_if_bool_expr(CodeGen *g, AstNode *node) { + assert(node->type == NodeTypeIfBoolExpr); + assert(node->data.if_bool_expr.condition); + assert(node->data.if_bool_expr.then_block); - LLVMValueRef cond_value = gen_expr(g, node->data.if_expr.condition); + LLVMValueRef cond_value = gen_expr(g, node->data.if_bool_expr.condition); - TypeTableEntry *then_type = get_expr_type(node->data.if_expr.then_block); + TypeTableEntry *then_type = get_expr_type(node->data.if_bool_expr.then_block); bool use_expr_value = (then_type->id != TypeTableEntryIdUnreachable && then_type->id != TypeTableEntryIdVoid); - if (node->data.if_expr.else_node) { + if (node->data.if_bool_expr.else_node) { LLVMBasicBlockRef then_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "Then"); LLVMBasicBlockRef else_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "Else"); LLVMBasicBlockRef endif_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "EndIf"); @@ -788,13 +788,13 @@ static LLVMValueRef gen_if_expr(CodeGen *g, AstNode *node) { LLVMBuildCondBr(g->builder, cond_value, then_block, else_block); LLVMPositionBuilderAtEnd(g->builder, then_block); - LLVMValueRef then_expr_result = gen_expr(g, node->data.if_expr.then_block); - if (get_expr_type(node->data.if_expr.then_block)->id != TypeTableEntryIdUnreachable) + LLVMValueRef then_expr_result = gen_expr(g, node->data.if_bool_expr.then_block); + if (get_expr_type(node->data.if_bool_expr.then_block)->id != TypeTableEntryIdUnreachable) LLVMBuildBr(g->builder, endif_block); LLVMPositionBuilderAtEnd(g->builder, else_block); - LLVMValueRef else_expr_result = gen_expr(g, node->data.if_expr.else_node); - if (get_expr_type(node->data.if_expr.else_node)->id != TypeTableEntryIdUnreachable) + LLVMValueRef else_expr_result = gen_expr(g, node->data.if_bool_expr.else_node); + if (get_expr_type(node->data.if_bool_expr.else_node)->id != TypeTableEntryIdUnreachable) LLVMBuildBr(g->builder, endif_block); LLVMPositionBuilderAtEnd(g->builder, endif_block); @@ -818,14 +818,19 @@ static LLVMValueRef gen_if_expr(CodeGen *g, AstNode *node) { LLVMBuildCondBr(g->builder, cond_value, then_block, endif_block); LLVMPositionBuilderAtEnd(g->builder, then_block); - gen_expr(g, node->data.if_expr.then_block); - if (get_expr_type(node->data.if_expr.then_block)->id != TypeTableEntryIdUnreachable) + gen_expr(g, node->data.if_bool_expr.then_block); + if (get_expr_type(node->data.if_bool_expr.then_block)->id != TypeTableEntryIdUnreachable) LLVMBuildBr(g->builder, endif_block); LLVMPositionBuilderAtEnd(g->builder, endif_block); return nullptr; } +static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) { + assert(node->type == NodeTypeIfVarExpr); + zig_panic("TODO gen_if_var_expr"); +} + static LLVMValueRef gen_block(CodeGen *g, AstNode *block_node, TypeTableEntry *implicit_return_type) { assert(block_node->type == NodeTypeBlock); @@ -1112,8 +1117,10 @@ static LLVMValueRef gen_expr_no_cast(CodeGen *g, AstNode *node) { return LLVMConstAllOnes(LLVMInt1Type()); else return LLVMConstNull(LLVMInt1Type()); - case NodeTypeIfExpr: - return gen_if_expr(g, node); + case NodeTypeIfBoolExpr: + return gen_if_bool_expr(g, node); + case NodeTypeIfVarExpr: + return gen_if_var_expr(g, node); case NodeTypeWhileExpr: return gen_while_expr(g, node); case NodeTypeAsmExpr: diff --git a/src/parser.cpp b/src/parser.cpp index 994923c377..610ed061fa 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -114,8 +114,10 @@ const char *node_type_str(NodeType node_type) { return "Void"; case NodeTypeBoolLiteral: return "BoolLiteral"; - case NodeTypeIfExpr: - return "IfExpr"; + case NodeTypeIfBoolExpr: + return "IfBoolExpr"; + case NodeTypeIfVarExpr: + return "IfVarExpr"; case NodeTypeWhileExpr: return "WhileExpr"; case NodeTypeLabel: @@ -321,14 +323,27 @@ void ast_print(AstNode *node, int indent) { case NodeTypeBoolLiteral: fprintf(stderr, "%s '%s'\n", node_type_str(node->type), node->data.bool_literal ? "true" : "false"); break; - case NodeTypeIfExpr: + case NodeTypeIfBoolExpr: fprintf(stderr, "%s\n", node_type_str(node->type)); - if (node->data.if_expr.condition) - ast_print(node->data.if_expr.condition, indent + 2); - ast_print(node->data.if_expr.then_block, indent + 2); - if (node->data.if_expr.else_node) - ast_print(node->data.if_expr.else_node, indent + 2); + if (node->data.if_bool_expr.condition) + ast_print(node->data.if_bool_expr.condition, indent + 2); + ast_print(node->data.if_bool_expr.then_block, indent + 2); + if (node->data.if_bool_expr.else_node) + ast_print(node->data.if_bool_expr.else_node, indent + 2); break; + case NodeTypeIfVarExpr: + { + Buf *name_buf = &node->data.if_var_expr.var_decl.symbol; + fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(name_buf)); + if (node->data.if_var_expr.var_decl.type) + ast_print(node->data.if_var_expr.var_decl.type, indent + 2); + if (node->data.if_var_expr.var_decl.expr) + ast_print(node->data.if_var_expr.var_decl.expr, indent + 2); + ast_print(node->data.if_var_expr.then_block, indent + 2); + if (node->data.if_var_expr.else_node) + ast_print(node->data.if_var_expr.else_node, indent + 2); + break; + } case NodeTypeWhileExpr: fprintf(stderr, "%s\n", node_type_str(node->type)); ast_print(node->data.while_expr.condition, indent + 2); @@ -1644,7 +1659,9 @@ static AstNode *ast_parse_else_or_else_if(ParseContext *pc, int *token_index, bo } /* -IfExpression : token(If) Expression Block option(Else | ElseIf) +IfExpression : IfVarExpression | IfBoolExpression +IfBoolExpression : token(If) option((token) Expression Block option(Else | ElseIf) +IfVarExpression : token(If) (token(Const) | token(Var)) token(Symbol) option(token(Colon) Type) Token(Eq) Expression Block Option(Else | ElseIf) */ static AstNode *ast_parse_if_expr(ParseContext *pc, int *token_index, bool mandatory) { Token *if_tok = &pc->tokens->at(*token_index); @@ -1657,11 +1674,37 @@ static AstNode *ast_parse_if_expr(ParseContext *pc, int *token_index, bool manda } *token_index += 1; - AstNode *node = ast_create_node(pc, NodeTypeIfExpr, if_tok); - node->data.if_expr.condition = ast_parse_expression(pc, token_index, true); - node->data.if_expr.then_block = ast_parse_block(pc, token_index, true); - node->data.if_expr.else_node = ast_parse_else_or_else_if(pc, token_index, false); - return node; + Token *token = &pc->tokens->at(*token_index); + if (token->id == TokenIdKeywordConst || token->id == TokenIdKeywordVar) { + AstNode *node = ast_create_node(pc, NodeTypeIfVarExpr, if_tok); + node->data.if_var_expr.var_decl.is_const = (token->id == TokenIdKeywordConst); + *token_index += 1; + + Token *name_token = ast_eat_token(pc, token_index, TokenIdSymbol); + ast_buf_from_token(pc, name_token, &node->data.if_var_expr.var_decl.symbol); + + Token *eq_or_colon = &pc->tokens->at(*token_index); + *token_index += 1; + if (eq_or_colon->id == TokenIdMaybeAssign) { + node->data.if_var_expr.var_decl.expr = ast_parse_expression(pc, token_index, true); + } else if (eq_or_colon->id == TokenIdColon) { + node->data.if_var_expr.var_decl.type = ast_parse_type(pc, token_index); + + ast_eat_token(pc, token_index, TokenIdMaybeAssign); + node->data.if_var_expr.var_decl.expr = ast_parse_expression(pc, token_index, true); + } else { + ast_invalid_token_error(pc, eq_or_colon); + } + node->data.if_var_expr.then_block = ast_parse_block(pc, token_index, true); + node->data.if_var_expr.else_node = ast_parse_else_or_else_if(pc, token_index, false); + return node; + } else { + AstNode *node = ast_create_node(pc, NodeTypeIfBoolExpr, if_tok); + node->data.if_bool_expr.condition = ast_parse_expression(pc, token_index, true); + node->data.if_bool_expr.then_block = ast_parse_block(pc, token_index, true); + node->data.if_bool_expr.else_node = ast_parse_else_or_else_if(pc, token_index, false); + return node; + } } /* diff --git a/src/parser.hpp b/src/parser.hpp index a9ed03812d..2e4b62dc07 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -44,7 +44,8 @@ enum NodeType { NodeTypeUse, NodeTypeVoid, NodeTypeBoolLiteral, - NodeTypeIfExpr, + NodeTypeIfBoolExpr, + NodeTypeIfVarExpr, NodeTypeWhileExpr, NodeTypeLabel, NodeTypeGoto, @@ -217,12 +218,18 @@ struct AstNodeUse { ZigList *directives; }; -struct AstNodeIfExpr { +struct AstNodeIfBoolExpr { AstNode *condition; AstNode *then_block; AstNode *else_node; // null, block node, or other if expr node }; +struct AstNodeIfVarExpr { + AstNodeVariableDeclaration var_decl; + AstNode *then_block; + AstNode *else_node; // null, block node, or other if expr node +}; + struct AstNodeWhileExpr { AstNode *condition; AstNode *body; @@ -341,7 +348,8 @@ struct AstNode { AstNodeFnCallExpr fn_call_expr; AstNodeArrayAccessExpr array_access_expr; AstNodeUse use; - AstNodeIfExpr if_expr; + AstNodeIfBoolExpr if_bool_expr; + AstNodeIfVarExpr if_var_expr; AstNodeWhileExpr while_expr; AstNodeLabel label; AstNodeGoto go_to; diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 93301f4655..084e0ec28d 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -125,6 +125,7 @@ enum TokenizeState { TokenizeStateSawGreaterThanGreaterThan, TokenizeStateSawDot, TokenizeStateSawDotDot, + TokenizeStateSawQuestionMark, TokenizeStateError, }; @@ -402,10 +403,28 @@ void tokenize(Buf *buf, Tokenization *out) { begin_token(&t, TokenIdDot); t.state = TokenizeStateSawDot; break; + case '?': + begin_token(&t, TokenIdMaybe); + t.state = TokenizeStateSawQuestionMark; + break; default: tokenize_error(&t, "invalid character: '%c'", c); } break; + case TokenizeStateSawQuestionMark: + switch (c) { + case '=': + t.cur_tok->id = TokenIdMaybeAssign; + end_token(&t); + t.state = TokenizeStateStart; + break; + default: + t.pos -= 1; + end_token(&t); + t.state = TokenizeStateStart; + continue; + } + break; case TokenizeStateSawDot: switch (c) { case '.': @@ -917,6 +936,7 @@ void tokenize(Buf *buf, Tokenization *out) { case TokenizeStateSawGreaterThan: case TokenizeStateSawGreaterThanGreaterThan: case TokenizeStateSawDot: + case TokenizeStateSawQuestionMark: end_token(&t); break; case TokenizeStateSawDotDot: @@ -1012,6 +1032,8 @@ static const char * token_name(Token *token) { case TokenIdPercent: return "Percent"; case TokenIdDot: return "Dot"; case TokenIdEllipsis: return "Ellipsis"; + case TokenIdMaybe: return "Maybe"; + case TokenIdMaybeAssign: return "MaybeAssign"; } return "(invalid token)"; } diff --git a/src/tokenizer.hpp b/src/tokenizer.hpp index 2686cd9ce8..213b55b8c2 100644 --- a/src/tokenizer.hpp +++ b/src/tokenizer.hpp @@ -83,6 +83,8 @@ enum TokenId { TokenIdPercent, TokenIdDot, TokenIdEllipsis, + TokenIdMaybe, + TokenIdMaybeAssign, }; struct Token {