diff --git a/dmc-lib/src/ast/class.rs b/dmc-lib/src/ast/class.rs index a7c1736..4532969 100644 --- a/dmc-lib/src/ast/class.rs +++ b/dmc-lib/src/ast/class.rs @@ -1,3 +1,4 @@ +use crate::ast::constructor::Constructor; use crate::ast::field::Field; use crate::ast::function::Function; use crate::diagnostic::Diagnostic; @@ -9,6 +10,7 @@ use std::rc::Rc; pub struct Class { declared_name: Rc, declared_name_source_range: SourceRange, + constructor: Option, fields: Vec, functions: Vec, } @@ -17,12 +19,14 @@ impl Class { pub fn new( declared_name: &str, declared_name_source_range: SourceRange, + constructor: Option, fields: Vec, functions: Vec, ) -> Self { Class { declared_name: declared_name.into(), declared_name_source_range, + constructor, fields, functions, } @@ -73,7 +77,12 @@ impl Class { return Err(fields_diagnostics); } - // 4. gather functions + // 4. gather constructor + if let Some(constructor) = &mut self.constructor { + constructor.gather_declared_names(symbol_table)?; + } + + // 5. gather functions let functions_diagnostics: Vec = self .functions .iter_mut() @@ -86,13 +95,18 @@ impl Class { return Err(functions_diagnostics); } - // 5. pop scope + // 6. pop scope symbol_table.pop_scope(); Ok(()) } pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + self.constructor + .as_mut() + .map(|constructor| constructor.check_name_usages(symbol_table)) + .transpose()?; + let fields_diagnostics: Vec = self .fields .iter_mut() @@ -104,6 +118,10 @@ impl Class { return Err(fields_diagnostics); } + if let Some(constructor) = &mut self.constructor { + constructor.check_name_usages(symbol_table)?; + } + let functions_diagnostics: Vec = self .functions .iter_mut() @@ -120,7 +138,26 @@ impl Class { } pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { - todo!() + // We need to determine if fields are initialized or not (the latter is an error). + // First phase: check all fields, then check constructor, leaving pending those things that + // are fields <- initialized by constructor. Then circle back to fields and check all are + // initialized + let mut diagnostics: Vec = vec![]; + + self.fields + .iter_mut() + .map(|field| field.type_check(symbol_table)) + .filter_map(Result::err) + .flatten() + .for_each(|diagnostic| diagnostics.push(diagnostic)); + + // todo check constructor and functions and re-check fields + + if diagnostics.is_empty() { + Ok(()) + } else { + Err(diagnostics) + } } } @@ -134,7 +171,10 @@ mod tests { let parse_result = parse_compilation_unit( " class Foo - mut bar = 42 + mut bar: Int + + ctor(_bar: Int) + end fn baz() -> Int bar @@ -143,7 +183,7 @@ mod tests { class Qux fn foo() -> Foo - Foo() + Foo(42) end end ", diff --git a/dmc-lib/src/ast/constructor.rs b/dmc-lib/src/ast/constructor.rs new file mode 100644 index 0000000..796f57b --- /dev/null +++ b/dmc-lib/src/ast/constructor.rs @@ -0,0 +1,127 @@ +use crate::ast::parameter::Parameter; +use crate::ast::statement::Statement; +use crate::diagnostic::Diagnostic; +use crate::source_range::SourceRange; +use crate::symbol::constructor_symbol::ConstructorSymbol; +use crate::symbol::parameter_symbol::ParameterSymbol; +use crate::symbol_table::{SymbolInsertError, SymbolTable}; +use std::cell::RefCell; +use std::rc::Rc; + +pub struct Constructor { + is_public: bool, + ctor_keyword_source_range: SourceRange, + parameters: Vec, + statements: Vec, +} + +impl Constructor { + pub fn new( + is_public: bool, + ctor_keyword_source_range: SourceRange, + parameters: Vec, + statements: Vec, + ) -> Self { + Self { + is_public, + ctor_keyword_source_range, + parameters, + statements, + } + } + + pub fn gather_declared_names( + &mut self, + symbol_table: &mut SymbolTable, + ) -> Result<(), Vec> { + // insert constructor symbol + let to_insert = ConstructorSymbol::new(self.ctor_keyword_source_range.clone(), false); + let constructor_symbol = + symbol_table + .insert_constructor_symbol(to_insert) + .map_err(|symbol_insert_error| match symbol_insert_error { + SymbolInsertError::AlreadyDeclared(_) => { + vec![ + Diagnostic::new( + "Cannot declare more than one constructor.", + self.ctor_keyword_source_range.start(), + self.ctor_keyword_source_range.end(), + ) + .with_reporter(file!(), line!()), + ] + } + })?; + + symbol_table.push_function_scope("ctor_scope"); + + let mut parameter_symbols: Vec>> = vec![]; + let mut parameters_diagnostics = vec![]; + + for parameter in &mut self.parameters { + match parameter.gather_declared_names(symbol_table) { + Ok(parameter_symbol) => { + parameter_symbols.push(parameter_symbol); + } + Err(mut ds) => { + parameters_diagnostics.append(&mut ds); + } + } + } + + if !parameters_diagnostics.is_empty() { + symbol_table.pop_scope(); + return Err(parameters_diagnostics); + } else { + constructor_symbol + .borrow_mut() + .set_parameters(parameter_symbols); + } + + symbol_table.push_block_scope("ctor_main_block"); + + let statements_diagnostics = self + .statements + .iter_mut() + .map(|stmt| stmt.gather_declared_names(symbol_table)) + .filter_map(Result::err) + .flatten() + .collect::>(); + + symbol_table.pop_scope(); // block + symbol_table.pop_scope(); // function + + if statements_diagnostics.is_empty() { + Ok(()) + } else { + Err(statements_diagnostics) + } + } + + pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + let parameters_diagnostics: Vec = self + .parameters + .iter_mut() + .map(|param| param.check_name_usages(symbol_table)) + .filter_map(Result::err) + .flatten() + .collect(); + + if !parameters_diagnostics.is_empty() { + return Err(parameters_diagnostics); + } + + let statements_diagnostics: Vec = self + .statements + .iter_mut() + .map(|statement| statement.check_name_usages(symbol_table)) + .filter_map(Result::err) + .flatten() + .collect(); + + if statements_diagnostics.is_empty() { + Ok(()) + } else { + Err(statements_diagnostics) + } + } +} diff --git a/dmc-lib/src/ast/field.rs b/dmc-lib/src/ast/field.rs index e864ad4..0878b65 100644 --- a/dmc-lib/src/ast/field.rs +++ b/dmc-lib/src/ast/field.rs @@ -4,6 +4,7 @@ use crate::diagnostic::Diagnostic; use crate::source_range::SourceRange; use crate::symbol::field_symbol::FieldSymbol; use crate::symbol_table::{SymbolInsertError, SymbolTable}; +use std::cell::RefCell; use std::rc::Rc; pub struct Field { @@ -13,6 +14,7 @@ pub struct Field { is_mut: bool, declared_type: Option>, initializer: Option>, + field_symbol: Option>>, } impl Field { @@ -31,6 +33,7 @@ impl Field { is_mut, declared_type: declared_type.map(Box::new), initializer: initializer.map(Box::new), + field_symbol: None, } } @@ -42,7 +45,7 @@ impl Field { let to_insert = FieldSymbol::new(&self.declared_name, self.declared_name_source_range.clone()); - symbol_table + let field_symbol = symbol_table .insert_field_symbol(to_insert) .map_err(|e| match e { SymbolInsertError::AlreadyDeclared(already_declared) => { @@ -57,7 +60,14 @@ impl Field { } })?; - // 2. gather initializer, if present + // save for later + self.field_symbol = Some(field_symbol); + + // 2. gather type_use and initializer, if present + if let Some(type_use) = &mut self.declared_type { + type_use.gather_declared_names(symbol_table)?; + } + if let Some(initializer) = &mut self.initializer { initializer.gather_declared_names(symbol_table)?; } @@ -80,4 +90,83 @@ impl Field { } Ok(()) } + + pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + let mut diagnostics: Vec = vec![]; + + if let Some(type_use) = &mut self.declared_type { + if let Some(mut type_use_diagnostics) = type_use.type_check(symbol_table).err() { + diagnostics.append(&mut type_use_diagnostics); + } + } + + if let Some(initializer) = &mut self.initializer { + if let Some(mut initializer_diagnostics) = initializer.type_check(symbol_table).err() { + diagnostics.append(&mut initializer_diagnostics); + } + } + + if !diagnostics.is_empty() { + return Err(diagnostics); + } + + // Now check that types are assignable, and update field symbol's type info + let field_type_info = + match self.declared_type.as_ref() { + Some(type_use) => { + match self.initializer.as_ref() { + Some(initializer) => { + let initializer_type_info = initializer.type_info(); + let declared_type_info = type_use.to_type_info(); + if declared_type_info.is_assignable_from(initializer_type_info) { + declared_type_info + } else { + return Err(vec![ + Diagnostic::new( + &format!( + "Mismatched types: {} is not assignable to {}", + initializer_type_info, declared_type_info + ), + initializer.source_range().start(), + initializer.source_range().end(), + ) + .with_reporter(file!(), line!()), + ]); + } + } + None => { + // easy: the declared type + type_use.to_type_info() + } + } + } + None => { + // type is the initializer + match self.initializer.as_ref() { + Some(initializer) => initializer.type_info().clone(), + None => { + // this is an error + return Err(vec![Diagnostic::new( + "Field must have either a declared type or initializer, or both.", + self.declared_name_source_range.start(), + self.declared_name_source_range.end(), + ).with_reporter(file!(), line!())]); + } + } + } + }; + + // update field symbol's type info + self.field_symbol + .as_mut() + .unwrap() + .borrow_mut() + .set_type_info(field_type_info); + + if diagnostics.is_empty() { + Ok(()) + } else { + Err(diagnostics) + } + } } diff --git a/dmc-lib/src/ast/function.rs b/dmc-lib/src/ast/function.rs index 4cc1f80..fd17249 100644 --- a/dmc-lib/src/ast/function.rs +++ b/dmc-lib/src/ast/function.rs @@ -169,9 +169,11 @@ impl Function { } } else { // we don't have a given return type, so it's void - self.function_symbol.as_mut().unwrap().borrow_mut().set_return_type_info( - TypeInfo::Void - ); + self.function_symbol + .as_mut() + .unwrap() + .borrow_mut() + .set_return_type_info(TypeInfo::Void); } // statements diff --git a/dmc-lib/src/ast/mod.rs b/dmc-lib/src/ast/mod.rs index 9f6e931..234768e 100644 --- a/dmc-lib/src/ast/mod.rs +++ b/dmc-lib/src/ast/mod.rs @@ -2,6 +2,7 @@ pub mod add_expression; pub mod call; pub mod class; pub mod compilation_unit; +pub mod constructor; pub mod double_literal; pub mod expression; pub mod expression_statement; diff --git a/dmc-lib/src/ast/type_use.rs b/dmc-lib/src/ast/type_use.rs index b9d9d49..b4e362a 100644 --- a/dmc-lib/src/ast/type_use.rs +++ b/dmc-lib/src/ast/type_use.rs @@ -56,6 +56,10 @@ impl TypeUse { } } + pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + Ok(()) // no-op, for now + } + pub fn to_type_info(&self) -> TypeInfo { match self.type_symbol.as_ref().unwrap() { TypeSymbol::Class(class_symbol) => TypeInfo::Class(class_symbol.clone()), diff --git a/dmc-lib/src/lexer.rs b/dmc-lib/src/lexer.rs index 462610a..70781b3 100644 --- a/dmc-lib/src/lexer.rs +++ b/dmc-lib/src/lexer.rs @@ -136,6 +136,7 @@ impl<'a> Lexer<'a> { "self" => TokenKind::SelfKw, "pub" => TokenKind::Public, "mut" => TokenKind::Mut, + "ctor" => TokenKind::Ctor, _ => TokenKind::Identifier, }; Token::new(self.position, self.position + prefix.len(), token_kind) diff --git a/dmc-lib/src/parser.rs b/dmc-lib/src/parser.rs index 8c1682e..03b6c7f 100644 --- a/dmc-lib/src/parser.rs +++ b/dmc-lib/src/parser.rs @@ -2,6 +2,7 @@ use crate::ast::add_expression::AddExpression; use crate::ast::call::Call; use crate::ast::class::Class; use crate::ast::compilation_unit::CompilationUnit; +use crate::ast::constructor::Constructor; use crate::ast::double_literal::DoubleLiteral; use crate::ast::expression::Expression; use crate::ast::expression_statement::ExpressionStatement; @@ -49,6 +50,12 @@ macro_rules! matches_expression_first { }; } +macro_rules! matches_statement_first { + ( $token_kind : expr ) => { + matches!($token_kind, TokenKind::Let) || matches_expression_first!($token_kind) + }; +} + struct Parser<'a> { input: &'a str, lexer: Lexer<'a>, @@ -382,12 +389,17 @@ impl<'a> Parser<'a> { let identifier_token = self.expect_advance(TokenKind::Identifier)?; let mut fields = vec![]; let mut functions = vec![]; + let mut maybe_constructor: Option = None; let mut diagnostics = vec![]; while self.current.is_some() && !self.peek_current(TokenKind::End) { match self.get_current().kind() { - TokenKind::Public => match self.public_class_member(&mut fields, &mut functions) { + TokenKind::Public => match self.public_class_member( + &mut fields, + &mut functions, + &mut maybe_constructor, + ) { Ok(_) => {} Err(mut member_diagnostics) => diagnostics.append(&mut member_diagnostics), }, @@ -399,6 +411,12 @@ impl<'a> Parser<'a> { Ok(function) => functions.push(function), Err(mut function_diagnostics) => diagnostics.append(&mut function_diagnostics), }, + TokenKind::Ctor => match self.constructor() { + Ok(constructor) => { + maybe_constructor = Some(constructor); + } + Err(mut ctor_diagnostics) => diagnostics.append(&mut ctor_diagnostics), + }, _ => unreachable!(), } } @@ -409,6 +427,7 @@ impl<'a> Parser<'a> { Ok(Class::new( self.token_text(&identifier_token), SourceRange::new(identifier_token.start(), identifier_token.end()), + maybe_constructor, fields, functions, )) @@ -469,26 +488,29 @@ impl<'a> Parser<'a> { &mut self, fields: &mut Vec, functions: &mut Vec, + maybe_ctor: &mut Option, ) -> Result<(), Vec> { if self.lookahead.is_some() { - if matches!( - self.lookahead.as_ref().unwrap().kind(), - TokenKind::Mut | TokenKind::Identifier - ) { - fields.push(self.field()?); - } else if matches!(self.lookahead.as_ref().unwrap().kind(), TokenKind::Fn) { - functions.push(self.function()?); - } else { - let lookahead = self.lookahead.as_ref().unwrap(); - return Err(vec![Diagnostic::new( - &format!( - "Expected any of {:?}; found {:?}", - [TokenKind::Mut, TokenKind::Identifier, TokenKind::Fn], - lookahead.kind() - ), - lookahead.start(), - lookahead.end(), - )]); + match self.lookahead.as_ref().unwrap().kind() { + TokenKind::Mut | TokenKind::Identifier => { + fields.push(self.field()?); + } + TokenKind::Fn => functions.push(self.function()?), + TokenKind::Ctor => { + maybe_ctor.replace(self.constructor()?); + } + _ => { + let lookahead = self.lookahead.as_ref().unwrap(); + return Err(vec![Diagnostic::new( + &format!( + "Expected any of {:?}; found {:?}", + [TokenKind::Mut, TokenKind::Identifier, TokenKind::Fn], + lookahead.kind() + ), + lookahead.start(), + lookahead.end(), + )]); + } } Ok(()) } else { @@ -504,6 +526,56 @@ impl<'a> Parser<'a> { } } + fn constructor(&mut self) -> Result> { + let is_public = if self.current.is_some() && self.peek_current(TokenKind::Public) { + self.advance(); + true + } else { + false + }; + + let ctor_keyword = self.expect_advance(TokenKind::Ctor)?; + self.expect_advance(TokenKind::LeftParentheses)?; + + let parameters = if self.current.is_some() && self.peek_current(TokenKind::Identifier) { + self.parameter_list()? + } else { + vec![] + }; + + self.expect_advance(TokenKind::RightParentheses)?; + + // statements + let mut statements: Vec = vec![]; + let mut diagnostics: Vec = vec![]; + + while self.current.is_some() + && matches_statement_first!(self.current.as_ref().unwrap().kind()) + { + match self.statement() { + Ok(statement) => { + statements.push(statement); + } + Err(mut statement_diagnostics) => { + diagnostics.append(&mut statement_diagnostics); + } + } + } + + self.expect_advance(TokenKind::End)?; + + if diagnostics.is_empty() { + Ok(Constructor::new( + is_public, + SourceRange::new(ctor_keyword.start(), ctor_keyword.end()), + parameters, + statements, + )) + } else { + Err(diagnostics) + } + } + fn field(&mut self) -> Result> { let is_public = if self.current.is_some() && self.peek_current(TokenKind::Public) { self.advance(); diff --git a/dmc-lib/src/scope/class_scope.rs b/dmc-lib/src/scope/class_scope.rs index b2cfd20..6052362 100644 --- a/dmc-lib/src/scope/class_scope.rs +++ b/dmc-lib/src/scope/class_scope.rs @@ -1,4 +1,5 @@ use crate::symbol::class_symbol::ClassSymbol; +use crate::symbol::constructor_symbol::ConstructorSymbol; use crate::symbol::field_symbol::FieldSymbol; use crate::symbol::function_symbol::FunctionSymbol; use std::cell::RefCell; @@ -11,6 +12,7 @@ pub struct ClassScope { class_symbols: HashMap, Rc>>, field_symbols: HashMap, Rc>>, function_symbols: HashMap, Rc>>, + constructor_symbol: Option>>, } impl ClassScope { @@ -21,6 +23,7 @@ impl ClassScope { class_symbols: HashMap::new(), field_symbols: HashMap::new(), function_symbols: HashMap::new(), + constructor_symbol: None, } } @@ -48,6 +51,14 @@ impl ClassScope { &mut self.function_symbols } + pub fn constructor_symbol(&self) -> Option<&Rc>> { + self.constructor_symbol.as_ref() + } + + pub fn constructor_symbol_mut(&mut self) -> &mut Option>> { + &mut self.constructor_symbol + } + pub fn parent_id(&self) -> usize { self.parent_id } diff --git a/dmc-lib/src/symbol/constructor_symbol.rs b/dmc-lib/src/symbol/constructor_symbol.rs new file mode 100644 index 0000000..057e719 --- /dev/null +++ b/dmc-lib/src/symbol/constructor_symbol.rs @@ -0,0 +1,47 @@ +use crate::source_range::SourceRange; +use crate::symbol::Symbol; +use crate::symbol::parameter_symbol::ParameterSymbol; +use std::cell::RefCell; +use std::rc::Rc; + +pub struct ConstructorSymbol { + ctor_keyword_source_range: SourceRange, + is_extern: bool, + parameters: Option>>>, +} + +impl ConstructorSymbol { + pub fn new(ctor_keyword_source_range: SourceRange, is_extern: bool) -> Self { + Self { + ctor_keyword_source_range, + is_extern, + parameters: None, + } + } + + pub fn set_parameters(&mut self, parameters: Vec>>) { + self.parameters = Some(parameters); + } + + pub fn parameters(&self) -> &[Rc>] { + self.parameters.as_ref().unwrap() + } + + pub fn is_extern(&self) -> bool { + self.is_extern + } +} + +impl Symbol for ConstructorSymbol { + fn declared_name(&self) -> &str { + "ctor" + } + + fn declared_name_owned(&self) -> Rc { + Rc::from(self.declared_name()) + } + + fn declared_name_source_range(&self) -> &SourceRange { + &self.ctor_keyword_source_range + } +} diff --git a/dmc-lib/src/symbol/expressible_symbol.rs b/dmc-lib/src/symbol/expressible_symbol.rs index 226cbe8..ef47076 100644 --- a/dmc-lib/src/symbol/expressible_symbol.rs +++ b/dmc-lib/src/symbol/expressible_symbol.rs @@ -19,12 +19,8 @@ pub enum ExpressibleSymbol { impl ExpressibleSymbol { pub fn type_info(&self) -> TypeInfo { match self { - ExpressibleSymbol::Class(class_symbol) => { - todo!() - } - ExpressibleSymbol::Field(field_symbol) => { - todo!() - } + ExpressibleSymbol::Class(class_symbol) => TypeInfo::Class(class_symbol.clone()), + ExpressibleSymbol::Field(field_symbol) => field_symbol.borrow().type_info().clone(), ExpressibleSymbol::Function(function_symbol) => { TypeInfo::Function(function_symbol.clone()) // n.b. not the return type! } diff --git a/dmc-lib/src/symbol/field_symbol.rs b/dmc-lib/src/symbol/field_symbol.rs index 6b50581..32b7809 100644 --- a/dmc-lib/src/symbol/field_symbol.rs +++ b/dmc-lib/src/symbol/field_symbol.rs @@ -17,6 +17,14 @@ impl FieldSymbol { type_info: None, } } + + pub fn set_type_info(&mut self, type_info: TypeInfo) { + self.type_info = Some(type_info); + } + + pub fn type_info(&self) -> &TypeInfo { + self.type_info.as_ref().unwrap() + } } impl Symbol for FieldSymbol { diff --git a/dmc-lib/src/symbol/mod.rs b/dmc-lib/src/symbol/mod.rs index 88c0143..83dcdf4 100644 --- a/dmc-lib/src/symbol/mod.rs +++ b/dmc-lib/src/symbol/mod.rs @@ -2,6 +2,7 @@ use crate::source_range::SourceRange; use std::rc::Rc; pub mod class_symbol; +pub mod constructor_symbol; pub mod expressible_symbol; pub mod field_symbol; pub mod function_symbol; diff --git a/dmc-lib/src/symbol_table/mod.rs b/dmc-lib/src/symbol_table/mod.rs index b69f00b..3ff6903 100644 --- a/dmc-lib/src/symbol_table/mod.rs +++ b/dmc-lib/src/symbol_table/mod.rs @@ -7,6 +7,7 @@ use crate::scope::function_scope::FunctionScope; use crate::scope::module_scope::ModuleScope; use crate::symbol::Symbol; use crate::symbol::class_symbol::ClassSymbol; +use crate::symbol::constructor_symbol::ConstructorSymbol; use crate::symbol::expressible_symbol::ExpressibleSymbol; use crate::symbol::field_symbol::FieldSymbol; use crate::symbol::function_symbol::FunctionSymbol; @@ -123,6 +124,32 @@ impl SymbolTable { Ok(to_return) } + pub fn insert_constructor_symbol( + &mut self, + constructor_symbol: ConstructorSymbol, + ) -> Result>, SymbolInsertError> { + let maybe_already_inserted = match self.current_scope() { + Scope::Class(class_scope) => class_scope.constructor_symbol(), + _ => panic!("Attempt to insert ConstructorSymbol in incompatible scope"), + }; + if let Some(already_inserted) = maybe_already_inserted { + return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new( + already_inserted.clone() as Rc>, + ))); + } + + let as_rc = Rc::new(RefCell::new(constructor_symbol)); + let to_return = as_rc.clone(); + + match self.current_scope_mut() { + Scope::Class(class_scope) => { + class_scope.constructor_symbol_mut().replace(as_rc); + } + _ => unreachable!(), + } + Ok(to_return) + } + pub fn insert_field_symbol( &mut self, field_symbol: FieldSymbol, diff --git a/dmc-lib/src/token.rs b/dmc-lib/src/token.rs index 515d70a..2c0117c 100644 --- a/dmc-lib/src/token.rs +++ b/dmc-lib/src/token.rs @@ -47,4 +47,5 @@ pub enum TokenKind { SelfKw, Public, Mut, + Ctor, }