diff --git a/CHANGELOG.md b/CHANGELOG.md index d3b0c67..c29f36d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ - [ ] Test semantic analysis - [ ] Generate php code from current AST +- [x] Typecheck and semantic check simple assignment - [x] Test correct operator precedence - [x] Parse assignments - [x] Parse dot `.` operator diff --git a/src/error_handling/error_messages.rs b/src/error_handling/error_messages.rs index 93d1fa7..07d403c 100644 --- a/src/error_handling/error_messages.rs +++ b/src/error_handling/error_messages.rs @@ -25,6 +25,7 @@ pub const SEMANTIC_MISMATCHED_TYPES: u32 = 21; pub const SEMANTIC_DUPLICATED_REFERENCE: u32 = 22; pub const SEMANTIC_MISMATCHED_ARGUMENT_COUNT: u32 = 23; pub const SYNTAX_INVALID_ARRAY_ACCESS: u32 = 24; +pub const SEMANTIC_IMMUTABLE_VARIABLE: u32 = 25; /// Reads the error codes from the error code list pub fn error_code_to_string() -> String { diff --git a/src/file/mod.rs b/src/file/mod.rs index 8b81ad5..6e2a0ce 100644 --- a/src/file/mod.rs +++ b/src/file/mod.rs @@ -38,8 +38,7 @@ pub fn compile_file(input: &String) -> Result<(), ()> { let out_code = match compile(&contents) { Ok(out_code) => out_code, - Err(error) => { - eprintln!("{}", error); + Err(_) => { return Err(()); } }; @@ -60,15 +59,15 @@ pub fn compile_file(input: &String) -> Result<(), ()> { } /// Full pipeline from THP source code to PHP output -fn compile(input: &String) -> Result { +fn compile(input: &String) -> Result { // // Lexical analysis // let tokens = match lexic::get_tokens(input) { Ok(t) => t, Err(error) => { - let chars: Vec = input.chars().collect(); - return Err(error.get_error_str(&chars)); + error.print_ariadne(input); + return Err(()); } }; @@ -77,9 +76,9 @@ fn compile(input: &String) -> Result { // let ast = match syntax::build_ast(&tokens) { Ok(ast) => ast, - Err(reason) => { - let chars: Vec = input.chars().collect(); - return Err(reason.get_error_str(&chars)); + Err(error) => { + error.print_ariadne(input); + return Err(()); } }; @@ -89,10 +88,9 @@ fn compile(input: &String) -> Result { let res1 = crate::semantic::check_semantics(&ast); match res1 { Ok(_) => {} - Err(reason) => { - let chars: Vec = input.chars().collect(); - let error = format!("{}: {}", "error".on_red(), reason.get_error_str(&chars)); - return Err(error); + Err(error) => { + error.print_ariadne(input); + return Err(()); } } diff --git a/src/semantic/checks/assignment.rs b/src/semantic/checks/assignment.rs new file mode 100644 index 0000000..cf6ca16 --- /dev/null +++ b/src/semantic/checks/assignment.rs @@ -0,0 +1,86 @@ +use crate::{ + error_handling::{ + self, + error_messages::{SEMANTIC_IMMUTABLE_VARIABLE, SEMANTIC_INVALID_REFERENCE}, + ErrorContainer, ErrorLabel, + }, + semantic::{self, impls::SemanticCheck, types::Typed}, + syntax::ast::{Assignment, Positionable}, +}; + +impl SemanticCheck for Assignment<'_> { + fn check_semantics( + &self, + scope: &semantic::symbol_table::SymbolTable, + ) -> Result<(), error_handling::MistiError> { + // for now the assignment can only be to a variable + + // get the datatype and mutability status + let datatype = match scope.get_type_and_mut(&self.identifier.value) { + Some((datatype, true)) => datatype, + Some((_, false)) => { + // throw error: variable is immutable + let label = ErrorLabel { + message: String::from( + "This variable is immutable, therefore it cannot be assigned a new value", + ), + start: self.identifier.position, + end: self.identifier.get_end_position(), + }; + let econtainer = ErrorContainer { + error_code: SEMANTIC_IMMUTABLE_VARIABLE, + error_offset: self.identifier.position, + labels: vec![label], + note: None, + help: None, + }; + return Err(econtainer); + } + None => { + // throw error: variable does not exist + let label = ErrorLabel { + message: String::from("This variable does not exist in this scope"), + start: self.identifier.position, + end: self.identifier.get_end_position(), + }; + let econtainer = ErrorContainer { + error_code: SEMANTIC_INVALID_REFERENCE, + error_offset: self.identifier.position, + labels: vec![label], + note: None, + help: None, + }; + return Err(econtainer); + } + }; + + // assert the datatype is the same + let expression_type = self.expression.get_type(scope)?; + + if !datatype.equals(&expression_type) { + // throw error: variable and expression have different types + let label = ErrorLabel { + message: format!("This variable has type {:?}", datatype), + start: self.identifier.position, + end: self.identifier.get_end_position(), + }; + let (expr_start, expr_end) = self.expression.get_position(); + let label2 = ErrorLabel { + message: format!("But this expression has type {:?}", expression_type), + start: expr_start, + end: expr_end, + }; + let econtainer = ErrorContainer { + error_code: SEMANTIC_INVALID_REFERENCE, + error_offset: self.identifier.position, + labels: vec![label, label2], + note: None, + help: None, + }; + return Err(econtainer); + } + + // ok + Ok(()) + } +} diff --git a/src/semantic/checks/binding.rs b/src/semantic/checks/binding.rs index fd6c7c3..8269d6b 100644 --- a/src/semantic/checks/binding.rs +++ b/src/semantic/checks/binding.rs @@ -64,7 +64,7 @@ impl SemanticCheck for VariableBinding<'_> { return Err(econtainer); } - scope.insert(binding_name.clone(), datatype); + scope.insert_custom(binding_name.clone(), datatype, self.is_mutable); Ok(()) } diff --git a/src/semantic/checks/mod.rs b/src/semantic/checks/mod.rs index bd623e5..204df70 100644 --- a/src/semantic/checks/mod.rs +++ b/src/semantic/checks/mod.rs @@ -1,3 +1,4 @@ +pub mod assignment; pub mod binding; pub mod block; pub mod conditional; diff --git a/src/semantic/checks/top_level_declaration.rs b/src/semantic/checks/top_level_declaration.rs index 14fd2c4..931223b 100644 --- a/src/semantic/checks/top_level_declaration.rs +++ b/src/semantic/checks/top_level_declaration.rs @@ -25,9 +25,7 @@ impl SemanticCheck for Statement<'_> { Statement::Conditional(c) => c.check_semantics(scope), Statement::ForLoop(f) => f.check_semantics(scope), Statement::WhileLoop(w) => w.check_semantics(scope), - Statement::Assignment(_assignment) => { - unimplemented!("Semantic check for an assignment") - } + Statement::Assignment(a) => a.check_semantics(scope), } } } diff --git a/src/semantic/symbol_table.rs b/src/semantic/symbol_table.rs index d5f23b1..12ac96b 100644 --- a/src/semantic/symbol_table.rs +++ b/src/semantic/symbol_table.rs @@ -12,7 +12,7 @@ struct SymbolTableNode { // the parent scope parent: Option>>, // the current scope - scope: HashMap, + scope: HashMap, } impl SymbolTable { @@ -33,7 +33,17 @@ impl SymbolTable { /// Inserts a new symbol into the current table scope pub fn insert(&self, key: String, value: Type) { - self.node.borrow_mut().insert(key, value); + self.node.borrow_mut().insert(key, value, false); + } + + /// Inserts a new symbol into the current table scope + pub fn insert_mutable(&self, key: String, value: Type) { + self.node.borrow_mut().insert(key, value, true); + } + + /// Inserts a new symbol into the current table scope + pub fn insert_custom(&self, key: String, value: Type, is_mutable: bool) { + self.node.borrow_mut().insert(key, value, is_mutable); } /// Tests if a symbol is declared in the current or parent scopes @@ -45,6 +55,11 @@ impl SymbolTable { pub fn get_type<'a>(&'a self, key: &String) -> Option { self.node.borrow_mut().get_type(key) } + + /// Gets the datatype of a symbol, if it exists, and if its mutable + pub fn get_type_and_mut<'a>(&'a self, key: &String) -> Option<(Type, bool)> { + self.node.borrow_mut().get_type_and_mut(key) + } } impl SymbolTableNode { @@ -65,8 +80,8 @@ impl SymbolTableNode { } /// Inserts a new symbol into the current scope - pub fn insert(&mut self, key: String, value: Type) { - self.scope.insert(key, value); + pub fn insert(&mut self, key: String, value: Type, is_mutable: bool) { + self.scope.insert(key, (value, is_mutable)); } /// Tests if a symbol is declared in the current or parent scopes @@ -87,7 +102,7 @@ impl SymbolTableNode { /// Returns the symbol's datatype pub fn get_type<'a>(&'a mut self, key: &String) -> Option { // Try to get the type in the current scope - if let Some(entry) = self.scope.get(key) { + if let Some((entry, _)) = self.scope.get(key) { // TODO: Change to allow other types of datatypes: functions, classes, maps return Some(entry.clone()); } @@ -101,4 +116,22 @@ impl SymbolTableNode { None => None, } } + + /// Returns the symbol's datatype and mutability + pub fn get_type_and_mut<'a>(&'a mut self, key: &String) -> Option<(Type, bool)> { + // Try to get the type in the current scope + if let Some((entry, mutable)) = self.scope.get(key) { + // TODO: Change to allow other types of datatypes: functions, classes, maps + return Some((entry.clone(), *mutable)); + } + + // Try to get the type in the parent scope + match &self.parent { + Some(parent) => { + parent.as_ref().borrow_mut().get_type_and_mut(key) + // parent.get_type(key) + } + None => None, + } + } }