diff --git a/fine/src/compiler.rs b/fine/src/compiler.rs index e262508b..b3989d12 100644 --- a/fine/src/compiler.rs +++ b/fine/src/compiler.rs @@ -425,7 +425,7 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR { compile_expression(c, tr.nth_tree(2)?); - if arg_type.compatible_with(&Type::Nothing) { + if c.semantics.type_compat(&arg_type, &Type::Nothing) { c.push(Instruction::Discard); c.push(Instruction::Discard); c.push(Instruction::PushTrue); diff --git a/fine/src/parser.rs b/fine/src/parser.rs index c00dd7f3..9973cf44 100644 --- a/fine/src/parser.rs +++ b/fine/src/parser.rs @@ -139,6 +139,10 @@ pub enum TreeKind { IfStatement, Identifier, ReturnType, + TypeParameterList, + TypeParameter, + ListConstructor, + ListConstructorElement, } pub struct Tree<'a> { @@ -566,11 +570,46 @@ fn return_type(p: &mut CParser) { fn type_expr(p: &mut CParser) { let m = p.start(); + // TODO: Other kinds of type expressions probably! p.expect(TokenKind::Identifier, "expected the identifier of a type"); + + if p.at(TokenKind::Less) { + type_parameter_list(p); + } + p.end(m, TreeKind::TypeExpression); } +fn type_parameter_list(p: &mut CParser) { + assert!(p.at(TokenKind::Less)); + let m = p.start(); + + p.expect(TokenKind::Less, "expected < to start type parameter list"); + while !p.at(TokenKind::Greater) && !p.eof() { + if p.at(TokenKind::Identifier) { + type_parameter(p); + } else { + break; + } + } + p.expect(TokenKind::Greater, "expected > to end type parameter list"); + + p.end(m, TreeKind::TypeParameterList); +} + +fn type_parameter(p: &mut CParser) { + assert!(p.at(TokenKind::Identifier)); + let m = p.start(); + + type_expr(p); + if !p.at(TokenKind::Greater) { + p.expect(TokenKind::Comma, "expect a comma between type parameters"); + } + + p.end(m, TreeKind::TypeParameter); +} + fn block(p: &mut CParser) { assert!(p.at(TokenKind::LeftBrace)); let m = p.start(); @@ -758,6 +797,8 @@ fn prefix_expression(p: &mut CParser) -> MarkClosed { TokenKind::Identifier => identifier(p), + TokenKind::LeftBracket => list_constructor(p), + _ => p.advance_with_error("expected an expression"), } } @@ -816,6 +857,39 @@ fn identifier(p: &mut CParser) -> MarkClosed { p.end(m, TreeKind::Identifier) } +fn list_constructor(p: &mut CParser) -> MarkClosed { + assert!(p.at(TokenKind::LeftBracket)); + let m = p.start(); + + p.expect( + TokenKind::LeftBracket, + "expect a list constructor to start with [", + ); + while !p.at(TokenKind::RightBracket) && !p.eof() { + list_constructor_element(p); + } + p.expect( + TokenKind::RightBracket, + "expected a ] to end the list constructor", + ); + + p.end(m, TreeKind::ListConstructor) +} + +fn list_constructor_element(p: &mut CParser) { + let m = p.start(); + + expression(p); + if !p.at(TokenKind::RightBracket) { + p.expect( + TokenKind::Comma, + "expected a comma between list constructor elements", + ); + } + + p.end(m, TreeKind::ListConstructorElement); +} + #[cfg(test)] mod tests { use super::*; diff --git a/fine/src/semantics.rs b/fine/src/semantics.rs index 455ab09d..0b92e35f 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -72,6 +72,10 @@ pub enum Type { // This is until generics are working MagicPrintGarbage, + // An potentially-bound type variable. + // We need to ... like ... unify these things if possible. + TypeVariable(TreeRef), + Nothing, // TODO: Numeric literals should be implicitly convertable, unlike other // types. Maybe just "numeric literal" type? @@ -80,6 +84,7 @@ pub enum Type { Bool, Function(Vec>, Box), + List(Box), } impl Type { @@ -89,23 +94,6 @@ impl Type { _ => false, } } - - pub fn compatible_with(&self, other: &Type) -> bool { - // TODO: This is wrong; we because of numeric literals etc. - match (self, other) { - (Type::F64, Type::F64) => true, - (Type::String, Type::String) => true, - (Type::Bool, Type::Bool) => true, - (Type::Unreachable, Type::Unreachable) => true, - (Type::Nothing, Type::Nothing) => true, - - // Avoid introducing more errors - (Type::Error, _) => true, - (_, Type::Error) => true, - - (_, _) => false, - } - } } impl fmt::Debug for Type { @@ -137,6 +125,10 @@ impl fmt::Display for Type { } write!(f, ") -> {ret}") } + + // TODO: Better names + TypeVariable(_) => write!(f, "$_"), + List(t) => write!(f, "list<{t}>"), } } } @@ -614,6 +606,26 @@ impl<'a> Semantics<'a> { EnvironmentRef::new(environment) } + pub fn type_compat(&self, a: &Type, b: &Type) -> bool { + // TODO: This is wrong; we because of numeric literals etc. + match (a, b) { + (Type::F64, Type::F64) => true, + (Type::String, Type::String) => true, + (Type::Bool, Type::Bool) => true, + (Type::Unreachable, Type::Unreachable) => true, + (Type::Nothing, Type::Nothing) => true, + + // Avoid introducing more errors + (Type::Error, _) => true, + (_, Type::Error) => true, + + (Type::List(a), Type::List(b)) => self.type_compat(a, b), + + // TODO: Unification on type variables! :D + (_, _) => false, + } + } + pub fn type_of(&self, t: TreeRef) -> Type { { let state = &mut self.types.borrow_mut()[t.index()]; @@ -638,6 +650,7 @@ impl<'a> Semantics<'a> { TreeKind::UnaryExpression => self.type_of_unary(tree), TreeKind::BinaryExpression => self.type_of_binary(tree), TreeKind::TypeExpression => self.type_of_type_expr(tree), + TreeKind::TypeParameter => self.type_of_type_parameter(tree), TreeKind::Block => self.type_of_block(tree), TreeKind::LiteralExpression => self.type_of_literal(tree), TreeKind::GroupingExpression => self.type_of_grouping(tree), @@ -655,6 +668,9 @@ impl<'a> Semantics<'a> { TreeKind::ReturnType => self.type_of_return_type(tree), TreeKind::Parameter => self.type_of_parameter(tree), + TreeKind::ListConstructorElement => self.type_of_list_constructor_element(tree), + TreeKind::ListConstructor => self.type_of_list_constructor(t, tree), + _ => self.internal_compiler_error(Some(t), "asking for a nonsense type"), }; @@ -765,13 +781,30 @@ impl<'a> Semantics<'a> { "string" => Some(Type::String), "bool" => Some(Type::Bool), "()" => Some(Type::Nothing), + "list" => { + let args = + tree.child_tree_of_kind(self.syntax_tree, TreeKind::TypeParameterList)?; + let mut arg_types: Vec<_> = args.child_trees().map(|t| self.type_of(t)).collect(); + + if arg_types.len() != 1 { + self.report_error_tree(tree, "list takes a single type argument"); + Some(Type::Error) + } else { + Some(Type::List(Box::new(arg_types.pop().unwrap()))) + } + } _ => { - self.report_error_tree(tree, "Unrecognized type"); + self.report_error_tree(tree, format!("Unrecognized type: '{token}'")); Some(Type::Error) } } } + fn type_of_type_parameter(&self, tree: &Tree) -> Option { + assert_eq!(tree.kind, TreeKind::TypeParameter); + Some(self.type_of(tree.nth_tree(0)?)) + } + fn type_of_block(&self, tree: &Tree) -> Option { assert_eq!(tree.kind, TreeKind::Block); @@ -849,7 +882,7 @@ impl<'a> Semantics<'a> { None }; - if !cond_type.compatible_with(&Type::Bool) { + if !self.type_compat(&cond_type, &Type::Bool) { if !cond_type.is_error() { self.report_error_tree_ref(cond_tree, "conditions must yield a boolean"); } @@ -865,7 +898,7 @@ impl<'a> Semantics<'a> { (then_type, else_type) => { let else_type = else_type.unwrap_or(Type::Nothing); - if !then_type.compatible_with(&else_type) { + if !self.type_compat(&then_type, &else_type) { self.report_error_tree( tree, format!("the type of the `then` branch ({then_type}) must match the type of the `else` branch ({else_type})"), @@ -909,7 +942,7 @@ impl<'a> Semantics<'a> { } for (i, ((t, a), p)) in arg_types.iter().zip(params.iter()).enumerate() { - if !a.compatible_with(p) { + if !self.type_compat(&a, p) { self.report_error_tree_ref( *t, format!( @@ -1025,6 +1058,38 @@ impl<'a> Semantics<'a> { } } + fn type_of_list_constructor(&self, t: TreeRef, tree: &Tree) -> Option { + assert_eq!(tree.kind, TreeKind::ListConstructor); + let mut element_type = None; + for ct in tree.child_trees() { + let child_type = self.type_of(ct); + element_type = match element_type { + None => Some(child_type), + Some(list_type) => { + if list_type.is_error() { + Some(child_type) + } else if child_type.is_error() { + Some(list_type) + } else { + if !self.type_compat(&child_type, &list_type) { + self.report_error_tree_ref(ct, format!("list element of type {child_type} is not compatible with the list type {list_type}")); + } + Some(list_type) + } + } + } + } + + let element_type = element_type.unwrap_or_else(|| Type::TypeVariable(t)); + + Some(Type::List(Box::new(element_type))) + } + + fn type_of_list_constructor_element(&self, tree: &Tree) -> Option { + assert_eq!(tree.kind, TreeKind::ListConstructorElement); + Some(self.type_of(tree.nth_tree(0)?)) + } + fn type_of_return_type(&self, tree: &Tree) -> Option { assert_eq!(tree.kind, TreeKind::ReturnType); Some(self.type_of(tree.nth_tree(1)?)) // type expression @@ -1112,12 +1177,14 @@ pub fn check(s: &Semantics) { TreeKind::File => {} TreeKind::FunctionDecl => check_function_decl(s, t, tree), TreeKind::ParamList => {} - TreeKind::Parameter => check_parameter(s, t), + TreeKind::Parameter => { + let _ = s.type_of(t); + } TreeKind::TypeExpression => { - let _ = s.type_of_type_expr(tree); + let _ = s.type_of(t); } TreeKind::Block => { - let _ = s.type_of_block(tree); + let _ = s.type_of(t); } TreeKind::LetStatement => { let _ = s.environment_of(t); @@ -1141,6 +1208,14 @@ pub fn check(s: &Semantics) { let _ = s.type_of(t); } TreeKind::ReturnType => {} + TreeKind::TypeParameter => {} + TreeKind::TypeParameterList => {} + TreeKind::ListConstructor => { + let _ = s.type_of(t); + } + TreeKind::ListConstructorElement => { + let _ = s.type_of(t); + } } } } @@ -1156,7 +1231,7 @@ fn check_function_decl(s: &Semantics, t: TreeRef, tree: &Tree) { if let Some(body) = tree.child_of_kind(s.syntax_tree, TreeKind::Block) { let body_type = s.type_of(body); - if !body_type.compatible_with(&return_type) { + if !s.type_compat(&body_type, &return_type) { // Just work very hard to get an appropriate error span. let (start, end) = return_type_tree .map(|t| { @@ -1206,7 +1281,7 @@ fn check_return_statement(s: &Semantics, tree: &Tree) { Type::Error }; - if !expected_type.compatible_with(&actual_type) { + if !s.type_compat(&expected_type, &actual_type) { s.report_error_tree(tree, format!("callers of this function expect a value of type `{expected_type}` but this statement returns a value of type `{actual_type}`")); } } @@ -1220,10 +1295,6 @@ fn check_return_statement(s: &Semantics, tree: &Tree) { // OK this one is a little bit messed up because it reaches *up*, sorry. } -fn check_parameter(s: &Semantics, t: TreeRef) { - let _ = s.type_of(t); -} - #[cfg(test)] mod tests { use super::*; diff --git a/fine/src/tokens.rs b/fine/src/tokens.rs index 3c7409bb..db211457 100644 --- a/fine/src/tokens.rs +++ b/fine/src/tokens.rs @@ -6,10 +6,10 @@ pub enum TokenKind { Whitespace, Comment, - LeftBrace, - RightBrace, - LeftBracket, - RightBracket, + LeftBrace, // TODO: LeftCurly + RightBrace, // TODO: RightCurly + LeftBracket, // TODO: LeftSquare + RightBracket, // TODO: RightSquare LeftParen, RightParen, Comma, diff --git a/fine/tests/expression/lists.fine b/fine/tests/expression/lists.fine new file mode 100644 index 00000000..0e667255 --- /dev/null +++ b/fine/tests/expression/lists.fine @@ -0,0 +1,10 @@ +fun sum(x: list) -> f64 { + 75 // lol +} + +fun test() { + let val = [1, 2, 3]; + sum(val); +} + +// @no-errors \ No newline at end of file