Commit 08a2311efd

Andrew Kelley <superjoe30@gmail.com>
2015-12-02 05:19:38
support if conditionals
1 parent 1ed926c
doc/vim/syntax/zig.vim
@@ -7,8 +7,8 @@ if exists("b:current_syntax")
   finish
 endif
 
-syn keyword zigKeyword fn return mut const extern unreachable export pub as use
-syn keyword zigType bool i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64 f128 void
+syn keyword zigKeyword fn return mut const extern unreachable export pub as use if else let void
+syn keyword zigType bool i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64 f128
 
 syn region zigCommentLine start="//" end="$" contains=zigTodo,@Spell
 syn region zigCommentLineDoc start="//\%(//\@!\|!\)" end="$" contains=zigTodo,@Spell
src/analyze.cpp
@@ -274,6 +274,7 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import,
         case NodeTypeSymbol:
         case NodeTypeCastExpr:
         case NodeTypePrefixOpExpr:
+        case NodeTypeIfExpr:
             zig_unreachable();
     }
 }
@@ -302,7 +303,9 @@ static void check_type_compatibility(CodeGen *g, AstNode *node, TypeTableEntry *
     add_node_error(g, node, buf_sprintf("type mismatch. expected %s. got %s", buf_ptr(&expected_type->name), buf_ptr(&actual_type->name)));
 }
 
-static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context, TypeTableEntry *expected_type, AstNode *node) {
+static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+        TypeTableEntry *expected_type, AstNode *node)
+{
     TypeTableEntry *return_type = nullptr;
     switch (node->type) {
         case NodeTypeBlock:
@@ -348,10 +351,64 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
 
         case NodeTypeBinOpExpr:
             {
-                // TODO: think about expected types
-                analyze_expression(g, import, context, expected_type, node->data.bin_op_expr.op1);
-                analyze_expression(g, import, context, expected_type, node->data.bin_op_expr.op2);
-                return_type = expected_type;
+                switch (node->data.bin_op_expr.bin_op) {
+                    case BinOpTypeBoolOr:
+                    case BinOpTypeBoolAnd:
+                        analyze_expression(g, import, context, g->builtin_types.entry_bool,
+                                node->data.bin_op_expr.op1);
+                        analyze_expression(g, import, context, g->builtin_types.entry_bool,
+                                node->data.bin_op_expr.op2);
+                        return_type = g->builtin_types.entry_bool;
+                        break;
+                    case BinOpTypeCmpEq:
+                    case BinOpTypeCmpNotEq:
+                    case BinOpTypeCmpLessThan:
+                    case BinOpTypeCmpGreaterThan:
+                    case BinOpTypeCmpLessOrEq:
+                    case BinOpTypeCmpGreaterOrEq:
+                        // TODO think how should type checking for these work?
+                        analyze_expression(g, import, context, g->builtin_types.entry_i32,
+                                node->data.bin_op_expr.op1);
+                        analyze_expression(g, import, context, g->builtin_types.entry_i32,
+                                node->data.bin_op_expr.op2);
+                        return_type = g->builtin_types.entry_bool;
+                        break;
+                    case BinOpTypeBinOr:
+                        zig_panic("TODO bin or type");
+                        break;
+                    case BinOpTypeBinXor:
+                        zig_panic("TODO bin xor type");
+                        break;
+                    case BinOpTypeBinAnd:
+                        zig_panic("TODO bin and type");
+                        break;
+                    case BinOpTypeBitShiftLeft:
+                        zig_panic("TODO bit shift left type");
+                        break;
+                    case BinOpTypeBitShiftRight:
+                        zig_panic("TODO bit shift right type");
+                        break;
+                    case BinOpTypeAdd:
+                    case BinOpTypeSub:
+                        // TODO think how should type checking for these work?
+                        analyze_expression(g, import, context, g->builtin_types.entry_i32,
+                                node->data.bin_op_expr.op1);
+                        analyze_expression(g, import, context, g->builtin_types.entry_i32,
+                                node->data.bin_op_expr.op2);
+                        return_type = g->builtin_types.entry_i32;
+                        break;
+                    case BinOpTypeMult:
+                        zig_panic("TODO mult type");
+                        break;
+                    case BinOpTypeDiv:
+                        zig_panic("TODO div type");
+                        break;
+                    case BinOpTypeMod:
+                        zig_panic("TODO modulus type");
+                        break;
+                    case BinOpTypeInvalid:
+                        zig_unreachable();
+                }
                 break;
             }
 
@@ -426,11 +483,46 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
 
         case NodeTypeSymbol:
             // look up symbol in symbol table
-            zig_panic("TODO");
+            zig_panic("TODO analyze_expression symbol");
 
         case NodeTypeCastExpr:
+            zig_panic("TODO analyze_expression cast expr");
+            break;
+
         case NodeTypePrefixOpExpr:
-            zig_panic("TODO");
+            switch (node->data.prefix_op_expr.prefix_op) {
+                case PrefixOpBoolNot:
+                    analyze_expression(g, import, context, g->builtin_types.entry_bool,
+                            node->data.prefix_op_expr.primary_expr);
+                    return_type = g->builtin_types.entry_bool;
+                    break;
+                case PrefixOpBinNot:
+                    zig_panic("TODO type check bin not");
+                    break;
+                case PrefixOpNegation:
+                    zig_panic("TODO type check negation");
+                    break;
+                case PrefixOpInvalid:
+                    zig_unreachable();
+            }
+            break;
+        case NodeTypeIfExpr:
+            {
+                analyze_expression(g, import, context, g->builtin_types.entry_bool, node->data.if_expr.condition);
+
+                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;
+                }
+                TypeTableEntry *then_type = analyze_expression(g, import, context, expected_type,
+                        node->data.if_expr.then_block);
+
+                check_type_compatibility(g, node, expected_type, else_type);
+                return_type = then_type;
+                break;
+            }
         case NodeTypeDirective:
         case NodeTypeFnDecl:
         case NodeTypeFnProto:
@@ -445,6 +537,11 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import,
     }
     assert(return_type);
     check_type_compatibility(g, node, expected_type, return_type);
+
+    assert(!node->codegen_node);
+    node->codegen_node = allocate<CodeGenNode>(1);
+    node->codegen_node->data.expr_node.type_entry = return_type;
+
     return return_type;
 }
 
@@ -509,6 +606,7 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import,
         case NodeTypeSymbol:
         case NodeTypeCastExpr:
         case NodeTypePrefixOpExpr:
+        case NodeTypeIfExpr:
             zig_unreachable();
     }
 }
src/codegen.cpp
@@ -120,6 +120,10 @@ static LLVMValueRef get_variable_value(CodeGen *g, Buf *name) {
     zig_unreachable();
 }
 
+static TypeTableEntry *get_expr_type(AstNode *node) {
+    return node->codegen_node->data.expr_node.type_entry;
+}
+
 static LLVMValueRef gen_fn_call_expr(CodeGen *g, AstNode *node) {
     assert(node->type == NodeTypeFnCallExpr);
 
@@ -283,6 +287,7 @@ static LLVMValueRef gen_bool_and_expr(CodeGen *g, AstNode *node) {
 
     LLVMValueRef val1 = gen_expr(g, node->data.bin_op_expr.op1);
 
+    LLVMBasicBlockRef orig_block = LLVMGetInsertBlock(g->builder);
     // block for when val1 == true
     LLVMBasicBlockRef true_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "BoolAndTrue");
     // block for when val1 == false (don't even evaluate the second part)
@@ -297,13 +302,14 @@ static LLVMValueRef gen_bool_and_expr(CodeGen *g, AstNode *node) {
     LLVMValueRef val2 = gen_expr(g, node->data.bin_op_expr.op2);
     add_debug_source_node(g, node);
     LLVMValueRef val2_i1 = LLVMBuildICmp(g->builder, LLVMIntEQ, val2, zero, "");
+    LLVMBuildBr(g->builder, false_block);
 
     LLVMPositionBuilderAtEnd(g->builder, false_block);
     add_debug_source_node(g, node);
     LLVMValueRef phi = LLVMBuildPhi(g->builder, LLVMInt1Type(), "");
     LLVMValueRef one_i1 = LLVMConstAllOnes(LLVMInt1Type());
     LLVMValueRef incoming_values[2] = {one_i1, val2_i1};
-    LLVMBasicBlockRef incoming_blocks[2] = {LLVMGetInsertBlock(g->builder), true_block};
+    LLVMBasicBlockRef incoming_blocks[2] = {orig_block, true_block};
     LLVMAddIncoming(phi, incoming_values, incoming_blocks, 2);
 
     return phi;
@@ -314,6 +320,8 @@ static LLVMValueRef gen_bool_or_expr(CodeGen *g, AstNode *expr_node) {
 
     LLVMValueRef val1 = gen_expr(g, expr_node->data.bin_op_expr.op1);
 
+    LLVMBasicBlockRef orig_block = LLVMGetInsertBlock(g->builder);
+
     // block for when val1 == false
     LLVMBasicBlockRef false_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "BoolOrFalse");
     // block for when val1 == true (don't even evaluate the second part)
@@ -328,13 +336,14 @@ static LLVMValueRef gen_bool_or_expr(CodeGen *g, AstNode *expr_node) {
     LLVMValueRef val2 = gen_expr(g, expr_node->data.bin_op_expr.op2);
     add_debug_source_node(g, expr_node);
     LLVMValueRef val2_i1 = LLVMBuildICmp(g->builder, LLVMIntEQ, val2, zero, "");
+    LLVMBuildBr(g->builder, true_block);
 
     LLVMPositionBuilderAtEnd(g->builder, true_block);
     add_debug_source_node(g, expr_node);
     LLVMValueRef phi = LLVMBuildPhi(g->builder, LLVMInt1Type(), "");
     LLVMValueRef one_i1 = LLVMConstAllOnes(LLVMInt1Type());
     LLVMValueRef incoming_values[2] = {one_i1, val2_i1};
-    LLVMBasicBlockRef incoming_blocks[2] = {LLVMGetInsertBlock(g->builder), false_block};
+    LLVMBasicBlockRef incoming_blocks[2] = {orig_block, false_block};
     LLVMAddIncoming(phi, incoming_values, incoming_blocks, 2);
 
     return phi;
@@ -383,9 +392,91 @@ static LLVMValueRef gen_return_expr(CodeGen *g, AstNode *node) {
         return LLVMBuildRetVoid(g->builder);
     }
 }
-/*
-Expression : BoolOrExpression | ReturnExpression
-*/
+
+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);
+
+    LLVMValueRef cond_value = gen_expr(g, node->data.if_expr.condition);
+
+    TypeTableEntry *then_type = get_expr_type(node->data.if_expr.then_block);
+    bool use_expr_value = (then_type != g->builtin_types.entry_unreachable &&
+                           then_type != g->builtin_types.entry_void);
+
+    if (node->data.if_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");
+
+        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);
+        LLVMBuildBr(g->builder, endif_block);
+
+        LLVMPositionBuilderAtEnd(g->builder, else_block);
+        LLVMValueRef else_expr_result = gen_expr(g, node->data.if_expr.else_node);
+        LLVMBuildBr(g->builder, endif_block);
+
+        LLVMPositionBuilderAtEnd(g->builder, endif_block);
+        if (use_expr_value) {
+            LLVMValueRef phi = LLVMBuildPhi(g->builder, LLVMTypeOf(then_expr_result), "");
+            LLVMValueRef incoming_values[2] = {then_expr_result, else_expr_result};
+            LLVMBasicBlockRef incoming_blocks[2] = {then_block, else_block};
+            LLVMAddIncoming(phi, incoming_values, incoming_blocks, 2);
+
+            return phi;
+        }
+
+        return nullptr;
+    }
+
+    assert(!use_expr_value);
+
+    LLVMBasicBlockRef then_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "Then");
+    LLVMBasicBlockRef endif_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "EndIf");
+
+    LLVMBuildCondBr(g->builder, cond_value, then_block, endif_block);
+
+    LLVMPositionBuilderAtEnd(g->builder, then_block);
+    gen_expr(g, node->data.if_expr.then_block);
+    LLVMBuildBr(g->builder, endif_block);
+
+    LLVMPositionBuilderAtEnd(g->builder, endif_block);
+    return nullptr;
+}
+
+static LLVMValueRef gen_block(CodeGen *g, AstNode *block_node, TypeTableEntry *implicit_return_type) {
+    assert(block_node->type == NodeTypeBlock);
+
+    ImportTableEntry *import = g->cur_fn->import_entry;
+
+    LLVMZigDILexicalBlock *di_block = LLVMZigCreateLexicalBlock(g->dbuilder, g->block_scopes.last(),
+            import->di_file, block_node->line + 1, block_node->column + 1);
+    g->block_scopes.append(LLVMZigLexicalBlockToScope(di_block));
+
+    add_debug_source_node(g, block_node);
+
+    LLVMValueRef return_value;
+    for (int i = 0; i < block_node->data.block.statements.length; i += 1) {
+        AstNode *statement_node = block_node->data.block.statements.at(i);
+        return_value = gen_expr(g, statement_node);
+    }
+
+    if (implicit_return_type) {
+        if (implicit_return_type == g->builtin_types.entry_void) {
+            LLVMBuildRetVoid(g->builder);
+        } else if (implicit_return_type != g->builtin_types.entry_unreachable) {
+            LLVMBuildRet(g->builder, return_value);
+        }
+    }
+
+    g->block_scopes.pop();
+
+    return return_value;
+}
+
 static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
     switch (node->type) {
         case NodeTypeBinOpExpr:
@@ -403,6 +494,8 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
             return LLVMBuildUnreachable(g->builder);
         case NodeTypeVoid:
             return nullptr;
+        case NodeTypeIfExpr:
+            return gen_if_expr(g, node);
         case NodeTypeNumberLiteral:
             {
                 Buf *number_str = &node->data.number;
@@ -427,6 +520,8 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
                 Buf *name = &node->data.symbol;
                 return get_variable_value(g, name);
             }
+        case NodeTypeBlock:
+            return gen_block(g, node, nullptr);
         case NodeTypeRoot:
         case NodeTypeRootExportDecl:
         case NodeTypeFnProto:
@@ -434,7 +529,6 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
         case NodeTypeFnDecl:
         case NodeTypeParamDecl:
         case NodeTypeType:
-        case NodeTypeBlock:
         case NodeTypeExternBlock:
         case NodeTypeDirective:
         case NodeTypeUse:
@@ -443,30 +537,6 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
     zig_unreachable();
 }
 
-static void gen_block(CodeGen *g, ImportTableEntry *import, AstNode *block_node, TypeTableEntry *implicit_return_type) {
-    assert(block_node->type == NodeTypeBlock);
-
-    LLVMZigDILexicalBlock *di_block = LLVMZigCreateLexicalBlock(g->dbuilder, g->block_scopes.last(),
-            import->di_file, block_node->line + 1, block_node->column + 1);
-    g->block_scopes.append(LLVMZigLexicalBlockToScope(di_block));
-
-    add_debug_source_node(g, block_node);
-
-    LLVMValueRef return_value;
-    for (int i = 0; i < block_node->data.block.statements.length; i += 1) {
-        AstNode *statement_node = block_node->data.block.statements.at(i);
-        return_value = gen_expr(g, statement_node);
-    }
-
-    if (implicit_return_type == g->builtin_types.entry_void) {
-        LLVMBuildRetVoid(g->builder);
-    } else if (implicit_return_type != g->builtin_types.entry_unreachable) {
-        LLVMBuildRet(g->builder, return_value);
-    }
-
-    g->block_scopes.pop();
-}
-
 static LLVMZigDISubroutineType *create_di_function_type(CodeGen *g, AstNodeFnProto *fn_proto,
         LLVMZigDIFile *di_file)
 {
@@ -558,7 +628,7 @@ static void do_code_gen(CodeGen *g) {
         LLVMGetParams(fn, codegen_fn_def->params);
 
         TypeTableEntry *implicit_return_type = codegen_fn_def->implicit_return_type;
-        gen_block(g, import, fn_def_node->data.fn_def.body, implicit_return_type);
+        gen_block(g, fn_def_node->data.fn_def.body, implicit_return_type);
 
         g->block_scopes.pop();
     }
@@ -585,6 +655,15 @@ static void define_primitive_types(CodeGen *g) {
         buf_init_from_str(&entry->name, "(invalid)");
         g->builtin_types.entry_invalid = entry;
     }
+    {
+        TypeTableEntry *entry = allocate<TypeTableEntry>(1);
+        entry->type_ref = LLVMInt1Type();
+        buf_init_from_str(&entry->name, "bool");
+        entry->di_type = LLVMZigCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name), 1, 8,
+                LLVMZigEncoding_DW_ATE_unsigned());
+        g->type_table.put(&entry->name, entry);
+        g->builtin_types.entry_bool = entry;
+    }
     {
         TypeTableEntry *entry = allocate<TypeTableEntry>(1);
         entry->type_ref = LLVMInt8Type();
@@ -803,7 +882,7 @@ static Buf *to_c_type(CodeGen *g, AstNode *type_node) {
         g->c_stdint_used = true;
         return buf_create_from_str("int32_t");
     } else {
-        zig_panic("TODO");
+        zig_panic("TODO to_c_type");
     }
 }
 
src/parser.cpp
@@ -91,6 +91,8 @@ const char *node_type_str(NodeType node_type) {
             return "Use";
         case NodeTypeVoid:
             return "Void";
+        case NodeTypeIfExpr:
+            return "IfExpr";
     }
     zig_unreachable();
 }
@@ -236,7 +238,15 @@ void ast_print(AstNode *node, int indent) {
             fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.use.path));
             break;
         case NodeTypeVoid:
-            fprintf(stderr, "Void\n");
+            fprintf(stderr, "%s\n", node_type_str(node->type));
+            break;
+        case NodeTypeIfExpr:
+            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);
             break;
     }
 }
@@ -353,6 +363,7 @@ static void ast_invalid_token_error(ParseContext *pc, Token *token) {
 
 static AstNode *ast_parse_expression(ParseContext *pc, int *token_index, bool mandatory);
 static AstNode *ast_parse_block(ParseContext *pc, int *token_index, bool mandatory);
+static AstNode *ast_parse_if_expr(ParseContext *pc, int *token_index, bool mandatory);
 
 
 static void ast_expect_token(ParseContext *pc, Token *token, TokenId token_id) {
@@ -558,7 +569,7 @@ static AstNode *ast_parse_grouped_expr(ParseContext *pc, int *token_index, bool
 }
 
 /*
-PrimaryExpression : token(Number) | token(String) | token(Unreachable) | GroupedExpression | Block | token(Symbol)
+PrimaryExpression : token(Number) | token(String) | token(Unreachable) | GroupedExpression | token(Symbol)
 */
 static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool mandatory) {
     Token *token = &pc->tokens->at(*token_index);
@@ -588,11 +599,6 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool
         return node;
     }
 
-    AstNode *block_node = ast_parse_block(pc, token_index, false);
-    if (block_node) {
-        return block_node;
-    }
-
     AstNode *grouped_expr_node = ast_parse_grouped_expr(pc, token_index, false);
     if (grouped_expr_node) {
         return grouped_expr_node;
@@ -975,6 +981,50 @@ static AstNode *ast_parse_bool_and_expr(ParseContext *pc, int *token_index, bool
     return node;
 }
 
+/*
+ElseIf : token(Else) IfExpression
+Else : token(Else) Block
+*/
+static AstNode *ast_parse_else_or_else_if(ParseContext *pc, int *token_index, bool mandatory) {
+    Token *else_token = &pc->tokens->at(*token_index);
+
+    if (else_token->id != TokenIdKeywordElse) {
+        if (mandatory) {
+            ast_invalid_token_error(pc, else_token);
+        } else {
+            return nullptr;
+        }
+    }
+    *token_index += 1;
+
+    AstNode *if_expr = ast_parse_if_expr(pc, token_index, false);
+    if (if_expr)
+        return if_expr;
+
+    return ast_parse_block(pc, token_index, true);
+}
+
+/*
+IfExpression : token(If) 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);
+    if (if_tok->id != TokenIdKeywordIf) {
+        if (mandatory) {
+            ast_invalid_token_error(pc, if_tok);
+        } else {
+            return nullptr;
+        }
+    }
+    *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;
+}
+
 /*
 ReturnExpression : token(Return) option(Expression)
 */
@@ -1016,27 +1066,68 @@ static AstNode *ast_parse_bool_or_expr(ParseContext *pc, int *token_index, bool
 }
 
 /*
-Expression : BoolOrExpression | ReturnExpression
+BlockExpression : IfExpression | Block
 */
-static AstNode *ast_parse_expression(ParseContext *pc, int *token_index, bool mandatory) {
+static AstNode *ast_parse_block_expr(ParseContext *pc, int *token_index, bool mandatory) {
     Token *token = &pc->tokens->at(*token_index);
 
-    AstNode *return_expr = ast_parse_return_expr(pc, token_index, false);
-    if (return_expr)
-        return return_expr;
+    AstNode *if_expr = ast_parse_if_expr(pc, token_index, false);
+    if (if_expr)
+        return if_expr;
+
+    AstNode *block = ast_parse_block(pc, token_index, false);
+    if (block)
+        return block;
+
+    if (mandatory)
+        ast_invalid_token_error(pc, token);
+
+    return nullptr;
+}
+
+/*
+NonBlockExpression : BoolOrExpression | ReturnExpression
+*/
+static AstNode *ast_parse_non_block_expr(ParseContext *pc, int *token_index, bool mandatory) {
+    Token *token = &pc->tokens->at(*token_index);
 
     AstNode *bool_or_expr = ast_parse_bool_or_expr(pc, token_index, false);
     if (bool_or_expr)
         return bool_or_expr;
 
-    if (!mandatory)
-        return nullptr;
+    AstNode *return_expr = ast_parse_return_expr(pc, token_index, false);
+    if (return_expr)
+        return return_expr;
 
-    ast_invalid_token_error(pc, token);
+    if (mandatory)
+        ast_invalid_token_error(pc, token);
+
+    return nullptr;
+}
+
+/*
+Expression : BlockExpression | NonBlockExpression
+*/
+static AstNode *ast_parse_expression(ParseContext *pc, int *token_index, bool mandatory) {
+    Token *token = &pc->tokens->at(*token_index);
+
+    AstNode *block_expr = ast_parse_block_expr(pc, token_index, false);
+    if (block_expr)
+        return block_expr;
+
+    AstNode *non_block_expr = ast_parse_non_block_expr(pc, token_index, false);
+    if (non_block_expr)
+        return non_block_expr;
+
+    if (mandatory)
+        ast_invalid_token_error(pc, token);
+
+    return nullptr;
 }
 
 /*
-Block : token(LBrace) list(option(Expression), token(Semicolon)) token(RBrace)
+Statement : NonBlockExpression token(Semicolon) | BlockExpression
+Block : token(LBrace) list(option(Statement), token(Semicolon)) token(RBrace)
 */
 static AstNode *ast_parse_block(ParseContext *pc, int *token_index, bool mandatory) {
     Token *last_token = &pc->tokens->at(*token_index);
@@ -1058,16 +1149,22 @@ static AstNode *ast_parse_block(ParseContext *pc, int *token_index, bool mandato
     // {2;} -> {2;void}
     // {;2} -> {void;2}
     for (;;) {
-        AstNode *expression_node = ast_parse_expression(pc, token_index, false);
-        if (!expression_node) {
-            expression_node = ast_create_node(pc, NodeTypeVoid, last_token);
+        AstNode *statement_node = ast_parse_block_expr(pc, token_index, false);
+        bool semicolon_expected = !statement_node;
+        if (!statement_node) {
+            statement_node = ast_parse_non_block_expr(pc, token_index, false);
+            if (!statement_node) {
+                statement_node = ast_create_node(pc, NodeTypeVoid, last_token);
+            }
         }
-        node->data.block.statements.append(expression_node);
+        node->data.block.statements.append(statement_node);
 
         last_token = &pc->tokens->at(*token_index);
         if (last_token->id == TokenIdRBrace) {
             *token_index += 1;
             return node;
+        } else if (!semicolon_expected) {
+            continue;
         } else if (last_token->id == TokenIdSemicolon) {
             *token_index += 1;
         } else {
src/parser.hpp
@@ -39,6 +39,7 @@ enum NodeType {
     NodeTypeFnCallExpr,
     NodeTypeUse,
     NodeTypeVoid,
+    NodeTypeIfExpr,
 };
 
 struct AstNodeRoot {
@@ -167,6 +168,12 @@ struct AstNodeUse {
     ZigList<AstNode *> *directives;
 };
 
+struct AstNodeIfExpr {
+    AstNode *condition;
+    AstNode *then_block;
+    AstNode *else_node; // null, block node, or other if expr node
+};
+
 struct AstNode {
     enum NodeType type;
     int line;
@@ -190,6 +197,7 @@ struct AstNode {
         AstNodePrefixOpExpr prefix_op_expr;
         AstNodeFnCallExpr fn_call_expr;
         AstNodeUse use;
+        AstNodeIfExpr if_expr;
         Buf number;
         Buf string;
         Buf symbol;
src/semantic_info.hpp
@@ -63,6 +63,7 @@ struct CodeGen {
     HashMap<Buf *, ImportTableEntry *, buf_hash, buf_eql_buf> import_table;
 
     struct {
+        TypeTableEntry *entry_bool;
         TypeTableEntry *entry_u8;
         TypeTableEntry *entry_i32;
         TypeTableEntry *entry_string_literal;
@@ -111,10 +112,15 @@ struct FnDefNode {
     LLVMValueRef *params;
 };
 
+struct ExprNode {
+    TypeTableEntry *type_entry;
+};
+
 struct CodeGenNode {
     union {
         TypeNode type_node; // for NodeTypeType
         FnDefNode fn_def_node; // for NodeTypeFnDef
+        ExprNode expr_node; // for all the expression nodes
     } data;
 };
 
src/tokenizer.cpp
@@ -183,6 +183,10 @@ static void end_token(Tokenize *t) {
         t->cur_tok->id = TokenIdKeywordUse;
     } else if (mem_eql_str(token_mem, token_len, "void")) {
         t->cur_tok->id = TokenIdKeywordVoid;
+    } else if (mem_eql_str(token_mem, token_len, "if")) {
+        t->cur_tok->id = TokenIdKeywordIf;
+    } else if (mem_eql_str(token_mem, token_len, "else")) {
+        t->cur_tok->id = TokenIdKeywordElse;
     }
 
     t->cur_tok = nullptr;
@@ -577,6 +581,8 @@ static const char * token_name(Token *token) {
         case TokenIdKeywordAs: return "As";
         case TokenIdKeywordUse: return "Use";
         case TokenIdKeywordVoid: return "Void";
+        case TokenIdKeywordIf: return "If";
+        case TokenIdKeywordElse: return "Else";
         case TokenIdLParen: return "LParen";
         case TokenIdRParen: return "RParen";
         case TokenIdComma: return "Comma";
src/tokenizer.hpp
@@ -24,6 +24,8 @@ enum TokenId {
     TokenIdKeywordAs,
     TokenIdKeywordUse,
     TokenIdKeywordVoid,
+    TokenIdKeywordIf,
+    TokenIdKeywordElse,
     TokenIdLParen,
     TokenIdRParen,
     TokenIdComma,
test/run_tests.cpp
@@ -189,6 +189,30 @@ static void add_compiling_test_cases(void) {
         )SOURCE");
     }
 
+    add_simple_case("if statements", R"SOURCE(
+        #link("c")
+        extern {
+            fn puts(s: *const u8) -> i32;
+            fn exit(code: i32) -> unreachable;
+        }
+
+        export fn _start() -> unreachable {
+            if 1 != 0 {
+                puts("1 is true");
+            } else {
+                puts("1 is false");
+            }
+            if 0 != 0 {
+                puts("0 is true");
+            } else if 1 - 1 != 0 {
+                puts("1 - 1 is true");
+            }
+            if !(0 != 0) {
+                puts("!0 is true");
+            }
+            exit(0);
+        }
+    )SOURCE", "1 is true\n!0 is true\n");
 }
 
 static void add_compile_failure_test_cases(void) {
README.md
@@ -44,7 +44,11 @@ make
 
  * variable declarations and assignment expressions
  * Type checking
+ * loops
+ * labels and goto
  * inline assembly and syscalls
+ * conditional compilation and ability to check target platform and architecture
+ * main function with command line arguments
  * running code at compile time
  * print! macro that takes var args
  * panic! macro that prints a stack trace to stderr in debug mode and calls
@@ -104,14 +108,26 @@ Type : token(Symbol) | PointerType | token(Unreachable)
 
 PointerType : token(Star) token(Const) Type | token(Star) token(Mut) Type
 
-Block : token(LBrace) list(option(Expression), token(Semicolon)) token(RBrace)
+Block : token(LBrace) list(option(Statement), token(Semicolon)) token(RBrace)
 
-Expression : BoolOrExpression | ReturnExpression
+Statement : NonBlockExpression token(Semicolon) | BlockExpression
+
+Expression : BlockExpression | NonBlockExpression
+
+NonBlockExpression : BoolOrExpression | ReturnExpression
+
+BlockExpression : IfExpression | Block
 
 BoolOrExpression : BoolAndExpression token(BoolOr) BoolAndExpression | BoolAndExpression
 
 ReturnExpression : token(Return) option(Expression)
 
+IfExpression : token(If) Expression Block option(Else | ElseIf)
+
+ElseIf : token(Else) IfExpression
+
+Else : token(Else) Block
+
 BoolAndExpression : ComparisonExpression token(BoolAnd) ComparisonExpression | ComparisonExpression
 
 ComparisonExpression : BinaryOrExpression ComparisonOperator BinaryOrExpression | BinaryOrExpression
@@ -144,7 +160,7 @@ FnCallExpression : PrimaryExpression token(LParen) list(Expression, token(Comma)
 
 PrefixOp : token(Not) | token(Dash) | token(Tilde)
 
-PrimaryExpression : token(Number) | token(String) | token(Unreachable) | GroupedExpression | Block | token(Symbol)
+PrimaryExpression : token(Number) | token(String) | token(Unreachable) | GroupedExpression | token(Symbol)
 
 GroupedExpression : token(LParen) Expression token(RParen)
 ```