diff --git a/README.md b/README.md index a3abdbe..ec517cf 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ This language is under active development, progress will be marked here as the l - [ ] Higher kinded types - [ ] Variadic generic types - [ ] Control Flow - - [ ] If + - [x] If - [ ] While - [ ] For - [ ] Enums diff --git a/examples/main.bl b/examples/main.bl index 6a417ed..bf01425 100644 --- a/examples/main.bl +++ b/examples/main.bl @@ -65,8 +65,20 @@ impl User { } } +fn if_expression(): i64 { + if (true) { + return 6; + } else { + return 9; + } +} + fn main(): i64 { - add(4, 4) + if (false) { + add(4, 4) + } else { + 61 + } } // type TestTrait trait { diff --git a/src/ast.rs b/src/ast.rs index 3913a25..f1e5611 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -128,6 +128,12 @@ pub struct LiteralFloat { pub type_: TypeUsage, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LiteralBool { + pub value: Spanned, + pub type_: TypeUsage, +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct LiteralStruct { pub name: Identifier, @@ -167,13 +173,23 @@ pub struct VariableUsage { pub type_: TypeUsage, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct IfExpression { + pub condition: Expression, + pub block: Block, + pub else_: Option, + pub type_: TypeUsage, +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Subexpression { LiteralInt(LiteralInt), LiteralFloat(LiteralFloat), + LiteralBool(LiteralBool), LiteralStruct(LiteralStruct), FunctionCall(FunctionCall), VariableUsage(VariableUsage), + If(IfExpression), StructGetter(StructGetter), Block(Block), Op(Operation), diff --git a/src/errors.rs b/src/errors.rs index 0a15041..de15350 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -31,6 +31,10 @@ pub enum TypingError { FunctionCallNotAFunction { // TODO: add position }, + #[error("`if` condition must be bool")] + IfConditionMustBeBool { + // TODO: add position + }, #[error("multiple errors")] MultipleErrors { errors: Vec }, } diff --git a/src/grammar.lalrpop b/src/grammar.lalrpop index 6a29845..0ae60e6 100644 --- a/src/grammar.lalrpop +++ b/src/grammar.lalrpop @@ -20,6 +20,10 @@ match { "fn", "return", "let", + "true", + "false", + "if", + "else", "=", "type", "struct", @@ -47,6 +51,15 @@ pub SpannedLiteralFloat: ast::LiteralFloat = { > => ast::LiteralFloat{value: literal_float, type_: ast::TypeUsage::new_builtin("f64".to_string())} }; +pub LiteralBool: String = { + "true" => "true".to_string(), + "false" => "false".to_string(), +}; + +pub SpannedLiteralBool: ast::LiteralBool = { + > => ast::LiteralBool{value: literal_bool, type_: ast::TypeUsage::new_builtin("bool".to_string())} +}; + pub Identifier: String = { => i.to_string() }; @@ -79,6 +92,11 @@ pub VariableUsage: ast::VariableUsage = { => ast::VariableUsage{name: identifier, type_: ast::TypeUsage::new_unknown(&id_generator)} }; +pub IfExpression: ast::IfExpression = { + "if" "("")" => ast::IfExpression{condition: c, block: b, else_: None, type_: ast::TypeUsage::new_unknown(&id_generator)}, + "if" "("")" "else" => ast::IfExpression{condition: c, block: b, else_: Some(e), type_: ast::TypeUsage::new_unknown(&id_generator)}, +}; + pub Expression: ast::Expression = { "+" => { ast::Expression{ @@ -114,10 +132,12 @@ pub Factor: ast::Expression = { pub Term: ast::Expression = { SpannedLiteralInt => ast::Expression{subexpression: Box::new(ast::Subexpression::LiteralInt(<>)), type_: ast::TypeUsage::new_unknown(&id_generator)}, SpannedLiteralFloat => ast::Expression{subexpression: Box::new(ast::Subexpression::LiteralFloat(<>)), type_: ast::TypeUsage::new_unknown(&id_generator)}, + SpannedLiteralBool => ast::Expression{subexpression: Box::new(ast::Subexpression::LiteralBool(<>)), type_: ast::TypeUsage::new_unknown(&id_generator)}, LiteralStruct => ast::Expression{subexpression: Box::new(ast::Subexpression::LiteralStruct(<>)), type_: ast::TypeUsage::new_unknown(&id_generator)}, FunctionCall => ast::Expression{subexpression: Box::new(ast::Subexpression::FunctionCall(<>)), type_: ast::TypeUsage::new_unknown(&id_generator)}, StructGetter => ast::Expression{subexpression: Box::new(ast::Subexpression::StructGetter(<>)), type_: ast::TypeUsage::new_unknown(&id_generator)}, VariableUsage => ast::Expression{subexpression: Box::new(ast::Subexpression::VariableUsage(<>)), type_: ast::TypeUsage::new_unknown(&id_generator)}, + IfExpression => ast::Expression{subexpression: Box::new(ast::Subexpression::If(<>)), type_: ast::TypeUsage::new_unknown(&id_generator)}, Block => ast::Expression{subexpression: Box::new(ast::Subexpression::Block(<>)), type_: ast::TypeUsage::new_unknown(&id_generator)}, "(" ")" => e, }; diff --git a/src/interpreter.rs b/src/interpreter.rs index 155b57f..da90165 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -37,6 +37,7 @@ pub enum Function { #[derive(Debug, Clone)] pub enum Value { Numeric(NumericValue), + Bool(bool), Function(Function), Struct(Arc>), Unit, @@ -204,6 +205,15 @@ fn create_builtins() -> HashMap { )), ); + result.insert( + "bool".to_string(), + NamedEntity::TypeDeclaration(ast::TypeDeclaration::Primitive( + ast::PrimitiveTypeDeclaration { + name: "bool".to_string(), + }, + )), + ); + result.insert( "!".to_string(), NamedEntity::TypeDeclaration(ast::TypeDeclaration::Primitive( @@ -351,6 +361,14 @@ impl TreeWalkInterpreter { let value: f64 = literal_float.value.value.parse().unwrap(); return ExpressionResult::Value(Value::Numeric(NumericValue::F64(value))); } + ast::Subexpression::LiteralBool(literal_bool) => { + let value: bool = if &literal_bool.value.value == "true" { + true + } else { + false + }; + return ExpressionResult::Value(Value::Bool(value)); + } ast::Subexpression::LiteralStruct(literal_struct) => { let declaration = match &ctx.environment[&literal_struct.name.name.value] { NamedEntity::TypeDeclaration(ast::TypeDeclaration::Struct(declaration)) => { @@ -423,6 +441,28 @@ impl TreeWalkInterpreter { }; return ExpressionResult::Value(variable_value); } + ast::Subexpression::If(if_expression) => { + let condition = match self.with_expression(ctx, &if_expression.condition) { + ExpressionResult::Value(r) => r, + ExpressionResult::Return(r) => { + return ExpressionResult::Return(r); + } + }; + + match &condition { + Value::Bool(cond) => { + if cond.clone() { + return self.with_block(ctx, &if_expression.block); + } else { + return match &if_expression.else_ { + Some(else_) => self.with_block(ctx, else_), + None => ExpressionResult::Value(Value::Unit), + }; + } + } + _ => panic!("TypeError: condition must be bool"), + } + } ast::Subexpression::StructGetter(struct_getter) => { let source = match self.with_expression(ctx, &struct_getter.source) { ExpressionResult::Value(r) => r, diff --git a/src/main.rs b/src/main.rs index 9092c7e..7071e73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,10 +55,10 @@ fn main() { let module_ast = grammar::ModuleParser::new() .parse(&unknown_id_gen, &contents) .unwrap(); //TODO: convert to error - println!("ast: {:#?}", &module_ast); + // println!("ast: {:#?}", &module_ast); let alias_resolver = type_alias_resolution::TypeAliasResolver {}; let resolved_ast = alias_resolver.with_module(&module_ast); - println!("resolved ast: {:#?}", &resolved_ast); + // println!("resolved ast: {:#?}", &resolved_ast); let type_checker = type_checking::TypeChecker {}; let type_checking_result = type_checker.with_module(&resolved_ast); match &type_checking_result { diff --git a/src/type_alias_resolution.rs b/src/type_alias_resolution.rs index ecb3588..c2cc489 100644 --- a/src/type_alias_resolution.rs +++ b/src/type_alias_resolution.rs @@ -258,6 +258,12 @@ impl TypeAliasResolver { type_: process_type(ctx, &literal_float.type_), }) } + ast::Subexpression::LiteralBool(literal_bool) => { + ast::Subexpression::LiteralBool(ast::LiteralBool { + value: literal_bool.value.clone(), + type_: process_type(ctx, &literal_bool.type_), + }) + } ast::Subexpression::LiteralStruct(literal_struct) => { let result = resolve_type( ctx, @@ -296,6 +302,17 @@ impl TypeAliasResolver { type_: process_type(ctx, &variable_usage.type_), }) } + ast::Subexpression::If(if_expression) => { + ast::Subexpression::If(ast::IfExpression { + condition: self.with_expression(ctx, &if_expression.condition), + block: self.with_block(ctx, &if_expression.block), + else_: match &if_expression.else_ { + Some(else_) => Some(self.with_block(ctx, else_)), + None => None, + }, + type_: process_type(ctx, &if_expression.type_), + }) + } ast::Subexpression::StructGetter(struct_getter) => { ast::Subexpression::StructGetter(ast::StructGetter { source: self.with_expression(ctx, &struct_getter.source), diff --git a/src/type_checking.rs b/src/type_checking.rs index 4c80219..f79b86d 100644 --- a/src/type_checking.rs +++ b/src/type_checking.rs @@ -120,6 +120,15 @@ fn create_builtins() -> HashMap { )), ); + result.insert( + "bool".to_string(), + NamedEntity::TypeDeclaration(ast::TypeDeclaration::Primitive( + ast::PrimitiveTypeDeclaration { + name: "bool".to_string(), + }, + )), + ); + result.insert( "!".to_string(), NamedEntity::TypeDeclaration(ast::TypeDeclaration::Primitive( @@ -132,7 +141,7 @@ fn create_builtins() -> HashMap { "unit".to_string(), NamedEntity::TypeDeclaration(ast::TypeDeclaration::Primitive( ast::PrimitiveTypeDeclaration { - name: "!".to_string(), + name: "unit".to_string(), }, )), ); @@ -798,6 +807,17 @@ impl TypeChecker { type_: apply_substitution(ctx, &substitution, &literal_float.type_)?, }) } + ast::Subexpression::LiteralBool(literal_bool) => { + substitution = compose_substitutions( + ctx, + &substitution, + &unify(ctx, &expression.type_, &literal_bool.type_)?, + )?; + ast::Subexpression::LiteralBool(ast::LiteralBool { + value: literal_bool.value.clone(), + type_: apply_substitution(ctx, &substitution, &literal_bool.type_)?, + }) + } ast::Subexpression::LiteralStruct(literal_struct) => { substitution = compose_substitutions( ctx, @@ -926,6 +946,109 @@ impl TypeChecker { type_: apply_substitution(ctx, &substitution, &variable_usage.type_)?, }) } + ast::Subexpression::If(if_expression) => { + let (condition, subst) = + self.with_expression(ctx, &substitution, &if_expression.condition)?; + substitution = compose_substitutions(ctx, &substitution, &subst)?; + + let (block_result, subst) = + self.with_block(ctx, &substitution, &if_expression.block)?; + substitution = compose_substitutions(ctx, &substitution, &subst)?; + + let else_ = match &if_expression.else_ { + Some(else_) => { + let (result, subst) = self.with_block(ctx, &substitution, else_)?; + substitution = compose_substitutions(ctx, &substitution, &subst)?; + Some(result) + } + None => None, + }; + + match &condition.type_ { + ast::TypeUsage::Named(named) => { + if named.name.name.value != "bool" { + return Err(errors::TypingError::IfConditionMustBeBool {}); + } + } + ast::TypeUsage::Function(_) => { + return Err(errors::TypingError::IfConditionMustBeBool {}); + } + _ => {} + }; + + let mut never_count = 0; + match &block_result.type_ { + ast::TypeUsage::Named(named) => { + if named.name.name.value != "!" { + substitution = compose_substitutions( + ctx, + &substitution, + &unify(ctx, &if_expression.type_, &block_result.type_)?, + )?; + } else { + never_count += 1; + } + } + _ => { + substitution = compose_substitutions( + ctx, + &substitution, + &unify(ctx, &if_expression.type_, &block_result.type_)?, + )?; + } + }; + + match &else_ { + Some(else_block) => { + match &else_block.type_ { + ast::TypeUsage::Named(named) => { + if named.name.name.value != "!" { + substitution = compose_substitutions( + ctx, + &substitution, + &unify(ctx, &if_expression.type_, &else_block.type_)?, + )?; + } else { + never_count += 1; + } + } + _ => { + substitution = compose_substitutions( + ctx, + &substitution, + &unify(ctx, &if_expression.type_, &else_block.type_)?, + )?; + } + }; + } + None => { + substitution = compose_substitutions( + ctx, + &substitution, + &unify(ctx, &if_expression.type_, &ast::new_unit())?, + )?; + } + } + + let result_type = if never_count == 2 { + ast::new_never() + } else { + apply_substitution(ctx, &substitution, &if_expression.type_)? + }; + + substitution = compose_substitutions( + ctx, + &substitution, + &unify(ctx, &expression.type_, &result_type)?, + )?; + + ast::Subexpression::If(ast::IfExpression { + condition: condition, + block: block_result, + else_: else_, + type_: result_type, + }) + } ast::Subexpression::StructGetter(struct_getter) => { let (source, subst) = self.with_expression(ctx, &substitution, &struct_getter.source)?;