diff --git a/crates/mist-api/src/transpiler.mist b/crates/mist-api/src/transpiler.mist index f2975fe..6ce29aa 100644 --- a/crates/mist-api/src/transpiler.mist +++ b/crates/mist-api/src/transpiler.mist @@ -206,6 +206,10 @@ pub fn transpile_code<'a>(path *PathBuf, source *'a str, mut output_file PathBuf let parsed = mist_parser::parse(source)?; + for i in &parsed { + let _ = mist_parser::semantics::check_class_semantics(i); + } + let output = gc.generate(parsed); gc.mapping.shift_rust(mod_decl.lines().count() as isize, 0); 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/semantics.rs b/crates/parser/src/semantics.rs new file mode 100644 index 0000000..6a2c664 --- /dev/null +++ b/crates/parser/src/semantics.rs @@ -0,0 +1,157 @@ +use crate::{ + ast::{ + Block, ClassItem, Expression, FunctionDecl, Identifier, Postfix, Spanned, TopLevel, + TopLevelKind, + }, + error::AstError, +}; + +pub trait GetMutability { + fn get_mutability(&self) -> Vec; +} + +/// 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<(), AstError<'a, ()>> { + if let TopLevelKind::ClassDecl { + fields, + constructor, + items, + .. + } = &top_level.0.item + { + if let Some(constructor) = constructor { + let fields = fields + .iter() + .map(|field| Spanned { + item: field.item.decl.name.clone(), + line: field.line, + column: field.column, + }) + .collect::>(); + + let mut mutability = constructor.item.body.get_mutability(); + + for name in mutability.clone() { + if let Some(method) = find_function_call(items, &name) { + if let Some(body) = &method.item.body { + mutability.append(&mut body.get_mutability()); + } + } + } + + let mut mutability_iter = mutability.into_iter(); + + for field in &fields { + if mutability_iter.find(|v| v == &field.item).is_none() { + panic!("Unable to find set for field {}", field.item.0); + } + } + } + + 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 Expression { + fn get_self_ref(&self) -> Option { + match self { + Self::Fix { + initial, postfixes, .. + } => match &**initial { + Self::Path(v) => { + if v.0[0].ident.0 == "self" { + if let Some(field) = postfixes.get(0) { + match field { + Postfix::FieldAccess(field, _) => return Some(field.clone()), + _ => {} + } + } + } + } + _ => {} + }, + _ => {} + } + + 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 + } + } + _ => Vec::new(), + } + } +} + +pub fn mutates_lhs(op: &str) -> bool { + matches!( + op, + "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "&=" | "|=" | "^=" | "<<=" | ">>=" + ) +}