diff --git a/crates/mist-api/src/transpiler.mist b/crates/mist-api/src/transpiler.mist index f2975fe..f7001ea 100644 --- a/crates/mist-api/src/transpiler.mist +++ b/crates/mist-api/src/transpiler.mist @@ -184,8 +184,13 @@ fn is_source_newer(source *Path, output *Path) std::io::Result { Ok(source_time > output_time) } +pub enum TranspileError<'a> { + Parse[error::ParseError<'a, Vec>], + Semantic[Vec] +} + pub fn transpile_code<'a>(path *PathBuf, source *'a str, mut output_file PathBuf, mod_decl *str, curr_mod_decl *Option<[mist_parser::ast::Visibility, mist_parser::ast::Identifier]>) - Result<[PathBuf, mist_codegen::RustCodegen, String], error::ParseError<'a, Vec>> { + Result<[PathBuf, mist_codegen::RustCodegen, String], TranspileError<'a>> { if let Some[[_, name]] = &curr_mod_decl { if output_file.file_name().map(fn(v) -> v.to_str()).unwrap_or_default() != Some("mod.rs") { @@ -204,7 +209,11 @@ pub fn transpile_code<'a>(path *PathBuf, source *'a str, mut output_file PathBuf let mut gc = mist_codegen::RustCodegen::new(path.clone()); - let parsed = mist_parser::parse(source)?; + let parsed = mist_parser::parse(source).map_err(TranspileError::Parse)?; + + for i in &parsed { + let _ = mist_parser::semantics::check_class_semantics(i).map_err(TranspileError::Semantic)?; + } let output = gc.generate(parsed); @@ -217,32 +226,8 @@ pub fn transpile_file(path *PathBuf, source *str, output_file PathBuf, mod_decl let [output_file, gc, output] = match transpile_code(path, source, output_file, mod_decl, curr_mod_decl) { Ok[output] => output; Err[e] => { - match e { - error::ParseError::Ast[e] => { - let start_pos = e.span.start_pos().line_col(); - - let span = e.span.as_str(); - - eprintln!( - "\n{}:{}:{}\n \x1b[31mError\x1b[0m: {}\n\t{}{}\t{}", - path.as_os_str().display(), - start_pos.0, - start_pos.1, - e.error_message, - span, - if span.ends_with("\n") { "" } else { "\n" }, - "^".repeat(span.trim().len()), - ); - - process::exit(1); - } - - error::ParseError::PreAst[e] => { - eprintln!("error: parse failed in {}\n{}", path.display(), e); - - process::exit(1); - } - } + decode_err(&e, path); + process::exit(1); } }; @@ -272,3 +257,46 @@ pub fn transpile_file(path *PathBuf, source *str, output_file PathBuf, mod_decl process::exit(1); } } + +pub fn decode_err<'a>(err *TranspileError<'a>, path *PathBuf) { + let curr_dir = std::env::current_dir().unwrap(); + + match err { + TranspileError::Semantic[e_list] => { + for e in e_list { + eprintln!( + "\n{}:{}:{}\n \x1b[31mError\x1b[0m: {}", + path.strip_prefix(&curr_dir).unwrap_or_else(|_| path).as_os_str().display(), + e.line, + e.column, + e.error_message, + ); + } + } + + TranspileError::Parse[e] => { + match e { + error::ParseError::Ast[e] => { + let start_pos = e.span.start_pos().line_col(); + + let span = e.span.as_str(); + + eprintln!( + "\n{}:{}:{}\n \x1b[31mError\x1b[0m: {}\n\t{}{}\t{}", + path.strip_prefix(curr_dir).unwrap_or_else(|_| path).as_os_str().display(), + start_pos.0, + start_pos.1, + e.error_message, + span, + if span.ends_with("\n") { "" } else { "\n" }, + "^".repeat(span.trim().len()), + ); + } + + error::ParseError::PreAst[e] => { + eprintln!("error: parse failed in {}\n{}", path.strip_prefix(curr_dir).unwrap_or_else(|_| path).display(), e); + } + } + } + } +} diff --git a/crates/parser/src/grammar.pest b/crates/parser/src/grammar.pest index 5af7c30..561c979 100644 --- a/crates/parser/src/grammar.pest +++ b/crates/parser/src/grammar.pest @@ -333,7 +333,7 @@ trait_decl = { override_kw = { "override" ~ ("(" ~ expr_path ~ ")")? } function_decl = { - visibility? ~ override_kw? ~ "fn" ~ ("void" | type_expr) ~ identifier ~ generics_decl? ~ "(" ~ method_args ~ ")" ~ (block | semicolon) + visibility? ~ "fn" ~ ("void" | type_expr) ~ identifier ~ generics_decl? ~ "(" ~ method_args ~ ")" ~ override_kw? ~ (block | semicolon) } // ====================================================== diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index ea39710..cf905f0 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -5,6 +5,7 @@ pub mod ast; pub mod error; pub mod parser; pub mod rev_mapper; +pub mod semantics; use ast::*; diff --git a/crates/parser/src/parser/items/function.rs b/crates/parser/src/parser/items/function.rs index ef37078..10b519e 100644 --- a/crates/parser/src/parser/items/function.rs +++ b/crates/parser/src/parser/items/function.rs @@ -13,7 +13,6 @@ impl<'a> TryFrom> for FunctionDecl { ast_ensure!(pair, Rule::function_decl => { let mut inner = pair.into_inner(); let visibility = Visibility::try_from(&mut inner); - let is_override = consume_rule(&mut inner, Rule::override_kw).map(Override::try_from).transpose(); let return_type = consume_rule(&mut inner, Rule::type_expr) .map(TypeExpr::try_from) @@ -64,6 +63,8 @@ impl<'a> TryFrom> for FunctionDecl { }) .unwrap_or_else(|| Ok(ParamList(self_param.into_iter().collect()))); + let is_override = consume_rule(&mut inner, Rule::override_kw).map(Override::try_from).transpose(); + let body = inner.next().map(Block::try_from).transpose(); ast_expr!(Self { diff --git a/crates/parser/src/semantics.rs b/crates/parser/src/semantics.rs new file mode 100644 index 0000000..0b1a9aa --- /dev/null +++ b/crates/parser/src/semantics.rs @@ -0,0 +1,367 @@ +use crate::ast::{ + Block, ClassItem, Expression, FunctionDecl, Identifier, MatchItem, Postfix, Prefix, Spanned, + Statement, StatementBranch, TopLevel, TopLevelKind, VarDeclStmt, +}; + +pub trait GetMutability { + fn get_mutability(&self) -> Vec; +} + +#[derive(Debug, Clone)] +pub struct SemanticError { + pub line: usize, + pub column: usize, + pub error_message: String, +} + +/// Class semantics - Ensuring fields are initialized + +pub fn find_function_call<'a>( + items: &'a Vec, + name: &Identifier, +) -> Option<&'a Spanned> { + items.iter().find_map(|v| { + if let ClassItem::Method(m) = v { + if m.item.is_using_self() && &m.item.name == name { + Some(m) + } else { + None + } + } else { + None + } + }) +} + +pub fn check_class_semantics<'a>(top_level: &TopLevel) -> Result<(), Vec> { + if let TopLevelKind::ClassDecl { + fields, + constructor, + items, + name, + inherits, + .. + } = &top_level.0.item + { + if let Some(constructor) = constructor { + let mut fields = fields + .iter() + .map(|field| Spanned { + item: field.item.decl.name.clone(), + line: field.line, + column: field.column, + }) + .collect::>(); + + if inherits.is_some() { + fields.push(Spanned { + line: top_level.0.line, + column: top_level.0.column, + item: Identifier("_super".to_string()), + }); + } + + let mut mutability = constructor.item.body.get_mutability(); + + let mut i = 0; + while i < mutability.len() { + let name = mutability[i].clone(); + if let Some(method) = find_function_call(items, &name) { + if let Some(body) = &method.item.body { + for entry in body.get_mutability() { + if !mutability.contains(&entry) { + mutability.push(entry); + } + } + } + } + i += 1; + } + + let mut errs = Vec::new(); + + for field in &fields { + if !mutability.contains(&field.item) { + errs.push(SemanticError { + line: field.line, + column: field.column, + error_message: format!( + "class field `{}.{}` is uninitialized", + name.0, field.item.0 + ), + }); + } + } + + if errs.len() > 0 { + return Err(errs); + } + } + + Ok(()) + } else { + Ok(()) + } +} + +impl GetMutability for Vec { + fn get_mutability(&self) -> Vec { + self.iter().flat_map(T::get_mutability).collect() + } +} + +impl GetMutability for Block { + fn get_mutability(&self) -> Vec { + let mut items: Vec = self + .statements + .iter() + .flat_map(|v| v.item.get_mutability()) + .collect(); + + if let Some(v) = &self.soft_return { + items.append(&mut v.item.get_mutability()); + } + + items + } +} + +impl GetMutability for Statement { + fn get_mutability(&self) -> Vec { + match self { + Statement::Block(body) | Statement::UnsafeBlock(body) => body.get_mutability(), + Statement::If { + initial, + else_if, + else_branch, + } => { + let mut all_sets: Vec> = Vec::new(); + all_sets.push(initial.body.get_mutability()); + + for branch in else_if { + all_sets.push(branch.body.get_mutability()); + } + + if let Some(else_block) = else_branch { + all_sets.push(else_block.get_mutability()); + } else { + return Vec::new(); + } + + let mut result = all_sets.remove(0); + for set in all_sets { + result.retain(|id| set.contains(id)); + } + result + } + Statement::Loop(body) => body.get_mutability(), + Statement::While(branch) => branch.body.get_mutability(), + Statement::CStyleFor { + body, + init, + condition, + update, + .. + } => { + let mut a = init.get_mutability(); + a.append(&mut condition.get_mutability()); + a.append(&mut update.get_mutability()); + a.append(&mut body.get_mutability()); + a + } + Statement::For { body, iterator, .. } => { + let mut a = iterator.get_mutability(); + a.append(&mut body.get_mutability()); + a + } + Statement::Match(_, items) => { + if items.is_empty() { + return Vec::new(); + } + let mut all_sets: Vec> = items + .iter() + .map(|item| item.item.get_mutability()) + .collect(); + let mut result = all_sets.remove(0); + for set in all_sets { + result.retain(|id| set.contains(id)); + } + result + } + Statement::VarDecl(stmt) => stmt + .init + .as_ref() + .map(|e| e.get_mutability()) + .unwrap_or_default(), + Statement::Return(expr) => expr + .as_ref() + .map(|e| e.get_mutability()) + .unwrap_or_default(), + Statement::Break | Statement::Continue => Vec::new(), + } + } +} + +impl GetMutability for StatementBranch { + fn get_mutability(&self) -> Vec { + let mut a = self.condition.get_mutability(); + a.append(&mut self.body.get_mutability()); + a + } +} + +impl GetMutability for MatchItem { + fn get_mutability(&self) -> Vec { + self.1.get_mutability() + } +} + +impl GetMutability for VarDeclStmt { + fn get_mutability(&self) -> Vec { + self.init + .as_ref() + .map(|e| e.get_mutability()) + .unwrap_or_default() + } +} + +fn get_self_ref(initial: &Expression, postfixes: &[Postfix]) -> Option { + match initial { + Expression::Path(v) => { + let ident = v.0.first()?.ident.0.as_str(); + if ident == "self" || ident == "super" { + if let Some(Postfix::FieldAccess(field, _)) = postfixes.first() { + if !matches!(postfixes.get(1), Some(Postfix::Call(_))) { + return Some(field.clone()); + } + } + } + } + _ => {} + } + None +} + +fn get_self_method_call(initial: &Expression, postfixes: &[Postfix]) -> Option { + match initial { + Expression::Path(v) => { + let ident = v.0.first()?.ident.0.as_str(); + if ident == "self" || ident == "super" { + if let Some(Postfix::FieldAccess(method, _)) = postfixes.first() { + if matches!(postfixes.get(1), Some(Postfix::Call(_))) { + return Some(method.clone()); + } + } + } + } + _ => {} + } + None +} + +impl Expression { + fn get_self_ref(&self) -> Option { + match self { + Self::Fix { + initial, postfixes, .. + } => get_self_ref(initial, postfixes), + _ => None, + } + } +} + +impl GetMutability for Expression { + fn get_mutability(&self) -> Vec { + match self { + Self::Array(v) => v.get_mutability(), + Self::ArrayRepeat(v, i) => { + let mut a = v.get_mutability(); + a.append(&mut i.get_mutability()); + a + } + Self::Binary { lhs, op, rhs } => { + if mutates_lhs(op) { + let mut a = if let Some(self_ref) = lhs.get_self_ref() { + vec![self_ref] + } else { + lhs.get_mutability() + }; + + a.append(&mut rhs.get_mutability()); + a + } else { + let mut a = lhs.get_mutability(); + a.append(&mut rhs.get_mutability()); + a + } + } + Self::Statement(stmt) => stmt.get_mutability(), + Self::Fix { + initial, + prefixes, + postfixes, + } => { + let mut result = Vec::new(); + + // &mut self.field / &mut super.field / &mut super pattern + if prefixes.iter().any(|p| matches!(p, Prefix::RefMut)) { + if let Some(self_ref) = get_self_ref(initial, postfixes) { + result.push(self_ref); + } else if let Expression::Path(v) = &**initial { + if v.0.first().map_or(false, |s| s.ident.0 == "super") { + result.push(Identifier("_super".to_string())); + } + } + } + + // Recurse into all postfix sub-expressions + for postfix in postfixes { + match postfix { + Postfix::Assign(_, rhs) => { + if let Some(self_ref) = get_self_ref(initial, postfixes) { + result.push(self_ref); + } + result.append(&mut rhs.get_mutability()); + } + Postfix::Call(args) => { + for arg in args { + result.append(&mut arg.get_mutability()); + } + } + Postfix::StructCall(fields) => { + for (_, expr) in fields { + if let Some(expr) = expr { + result.append(&mut expr.get_mutability()); + } + } + } + Postfix::Index(expr) => { + result.append(&mut expr.get_mutability()); + } + _ => {} + } + } + + // Method calls on self: self.method_name(args) + if let Some(method) = get_self_method_call(initial, postfixes) { + result.push(method); + } + + result + } + Self::Closure { body, .. } => body.get_mutability(), + Self::Literal(_) => Vec::new(), + Self::Path(v) => { + if v.0.first().map_or(false, |s| s.ident.0 == "super") { + vec![Identifier("_super".to_string())] + } else { + Vec::new() + } + } + } + } +} + +pub fn mutates_lhs(op: &str) -> bool { + matches!(op, "=" | "->") +} diff --git a/mist-test/src/items/class_test.mist b/mist-test/src/items/class_test.mist index a95c15a..3d7ab57 100644 --- a/mist-test/src/items/class_test.mist +++ b/mist-test/src/items/class_test.mist @@ -1,8 +1,7 @@ pub module class_test; -pub class Animal { +pub class Animal { pub name String; - pub val T; constructor() { self.name = "Rex".to_string(); @@ -19,7 +18,7 @@ pub class Animal { pub fn void static_method() {} } -pub class Mammal : Animal { +pub class Mammal : Animal { constructor() { super = Super::new(); } @@ -30,12 +29,12 @@ pub class Mammal : Animal { } } -pub class Dog : Mammal { +pub class Dog : Mammal { constructor() { super = Super::new(); } - pub override(Animal) fn String speak(*self) { + pub fn String speak(*self) override(Animal) { "Woof!".to_string() }