From 7e613b1b90d2a64886a25c330a338615c547f12c Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Sat, 14 Mar 2026 10:36:53 -0500 Subject: [PATCH] Parsing/analyzing assign statements. --- dm/src/run.rs | 2 +- dmc-lib/src/ast/assign_statement.rs | 250 +++++++++++++++++++++++ dmc-lib/src/ast/field.rs | 7 +- dmc-lib/src/ast/let_statement.rs | 4 + dmc-lib/src/ast/mod.rs | 1 + dmc-lib/src/ast/statement.rs | 10 + dmc-lib/src/error_codes.rs | 5 + dmc-lib/src/lib.rs | 1 + dmc-lib/src/parser.rs | 39 +++- dmc-lib/src/symbol/expressible_symbol.rs | 32 +++ dmc-lib/src/symbol/field_symbol.rs | 12 +- dmc-lib/src/symbol/variable_symbol.rs | 8 +- examples/simple_assign.dm | 7 + 13 files changed, 370 insertions(+), 8 deletions(-) create mode 100644 dmc-lib/src/ast/assign_statement.rs create mode 100644 dmc-lib/src/error_codes.rs create mode 100644 examples/simple_assign.dm diff --git a/dm/src/run.rs b/dm/src/run.rs index 98d80e1..4c7ab93 100644 --- a/dm/src/run.rs +++ b/dm/src/run.rs @@ -164,7 +164,7 @@ fn report_and_exit( .with_labels(secondary_labels); if let Some(error_code) = diagnostic.error_code() { - csr_diagnostic = csr_diagnostic.with_code(format!("E{}", error_code)); + csr_diagnostic = csr_diagnostic.with_code(format!("E{:04}", error_code)); } if let Some((reporter_file, reporter_line)) = diagnostic.reporter() { diff --git a/dmc-lib/src/ast/assign_statement.rs b/dmc-lib/src/ast/assign_statement.rs new file mode 100644 index 0000000..5a2a2e2 --- /dev/null +++ b/dmc-lib/src/ast/assign_statement.rs @@ -0,0 +1,250 @@ +use crate::ast::expression::Expression; +use crate::diagnostic::{Diagnostic, SecondaryLabel}; +use crate::error_codes::{ASSIGN_LHS_IMMUTABLE, ASSIGN_MISMATCHED_TYPES, ASSIGN_NO_L_VALUE}; +use crate::symbol_table::SymbolTable; + +pub struct AssignStatement { + destination: Box, + value: Box, +} + +impl AssignStatement { + pub fn new(destination: Expression, value: Expression) -> Self { + Self { + destination: destination.into(), + value: value.into(), + } + } + + pub fn gather_declared_names( + &mut self, + symbol_table: &mut SymbolTable, + ) -> Result<(), Vec> { + let mut diagnostics: Vec = vec![]; + + match self.value.gather_declared_names(symbol_table) { + Ok(_) => {} + Err(mut value_diagnostics) => { + diagnostics.append(&mut value_diagnostics); + } + } + + match self.destination.gather_declared_names(symbol_table) { + Ok(_) => {} + Err(mut destination_diagnostics) => { + diagnostics.append(&mut destination_diagnostics); + } + } + + if diagnostics.is_empty() { + Ok(()) + } else { + Err(diagnostics) + } + } + + pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + let mut diagnostics: Vec = vec![]; + + match self.value.check_name_usages(symbol_table) { + Ok(_) => {} + Err(mut value_diagnostics) => { + diagnostics.append(&mut value_diagnostics); + } + } + + match self.destination.check_name_usages(symbol_table) { + Ok(_) => {} + Err(mut destination_diagnostics) => { + diagnostics.append(&mut destination_diagnostics); + } + } + + if diagnostics.is_empty() { + Ok(()) + } else { + Err(diagnostics) + } + } + + pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + let mut diagnostics: Vec = vec![]; + + match self.value.type_check(symbol_table) { + Ok(_) => {} + Err(mut value_diagnostics) => { + diagnostics.append(&mut value_diagnostics); + } + } + + match self.destination.type_check(symbol_table) { + Ok(_) => {} + Err(mut destination_diagnostics) => { + diagnostics.append(&mut destination_diagnostics); + } + } + + // check destination is l value + match &*self.destination { + Expression::Identifier(identifier) => { + let expressible_symbol = identifier.expressible_symbol(); + + // check mutable + if !expressible_symbol.is_mut() { + let secondary_label = SecondaryLabel::new( + expressible_symbol.source_range().start(), + expressible_symbol.source_range().end(), + Some("Destination (declared here) is immutable.".to_string()), + ); + let diagnostic = Diagnostic::new( + "Destination is immutable and not re-assignable.", + self.destination.source_range().start(), + self.destination.source_range().end(), + ) + .with_primary_label_message("Attempt to mutate immutable destination.") + .with_reporter(file!(), line!()) + .with_error_code(ASSIGN_LHS_IMMUTABLE) + .with_secondary_labels(&[secondary_label]); + diagnostics.push(diagnostic); + } + + // check assignable + let lhs_type = expressible_symbol.type_info(); + let rhs_type = self.value.type_info(); + if !lhs_type.is_assignable_from(rhs_type) { + let secondary_label = SecondaryLabel::new( + expressible_symbol.source_range().start(), + expressible_symbol.source_range().end(), + Some(format!( + "Destination declared here is of type {}.", + lhs_type + )), + ); + let diagnostic = Diagnostic::new( + &format!( + "Mismatched types: right-hand side {} is not assignable to left {}.", + rhs_type, lhs_type + ), + self.destination.source_range().start(), + self.value.source_range().end(), + ) + .with_primary_label_message(&format!( + "Attempt to assign {} to {}.", + rhs_type, lhs_type + )) + .with_error_code(ASSIGN_MISMATCHED_TYPES) + .with_reporter(file!(), line!()) + .with_secondary_labels(&[secondary_label]); + diagnostics.push(diagnostic); + } + } + _ => { + let diagnostic = Diagnostic::new( + "Left-hand side of assign must be an L value.", + self.destination.source_range().start(), + self.destination.source_range().end(), + ) + .with_primary_label_message("Must be L value.") + .with_reporter(file!(), line!()) + .with_error_code(ASSIGN_NO_L_VALUE); + diagnostics.push(diagnostic); + } + } + + if diagnostics.is_empty() { + Ok(()) + } else { + Err(diagnostics) + } + } +} + +#[cfg(test)] +mod tests { + use crate::error_codes::{ASSIGN_LHS_IMMUTABLE, ASSIGN_MISMATCHED_TYPES, ASSIGN_NO_L_VALUE}; + use crate::parser::parse_compilation_unit; + use crate::symbol_table::SymbolTable; + + #[test] + fn finds_mismatched_types() { + let mut compilation_unit = parse_compilation_unit( + " + fn main() + let mut x = 4 + x = \"Hello\" + end + ", + ) + .unwrap(); + let mut symbol_table = SymbolTable::new(); + compilation_unit + .gather_declared_names(&mut symbol_table) + .unwrap(); + compilation_unit.check_name_usages(&symbol_table).unwrap(); + match compilation_unit.type_check(&symbol_table) { + Ok(_) => { + panic!("Type check missed diagnostic."); + } + Err(diagnostics) => { + assert_eq!(diagnostics.len(), 1); + assert_eq!( + diagnostics[0].error_code().unwrap(), + ASSIGN_MISMATCHED_TYPES + ); + } + } + } + + #[test] + fn finds_no_l_value() { + let mut compilation_unit = parse_compilation_unit( + " + fn main() + 42 = 42 + end + ", + ) + .unwrap(); + let mut symbol_table = SymbolTable::new(); + compilation_unit + .gather_declared_names(&mut symbol_table) + .unwrap(); + compilation_unit.check_name_usages(&symbol_table).unwrap(); + match compilation_unit.type_check(&symbol_table) { + Ok(_) => { + panic!("Type check missed diagnostic."); + } + Err(diagnostics) => { + assert_eq!(diagnostics.len(), 1); + assert_eq!(diagnostics[0].error_code().unwrap(), ASSIGN_NO_L_VALUE); + } + } + } + + #[test] + fn finds_immutable_destination() { + let mut compilation_unit = parse_compilation_unit( + " + fn main() + let x = 42 + x = 43 + end + ", + ) + .unwrap(); + let mut symbol_table = SymbolTable::new(); + compilation_unit + .gather_declared_names(&mut symbol_table) + .unwrap(); + compilation_unit.check_name_usages(&symbol_table).unwrap(); + match compilation_unit.type_check(&symbol_table) { + Ok(_) => { + panic!("Type check missed diagnostic."); + } + Err(diagnostics) => { + assert_eq!(diagnostics.len(), 1); + assert_eq!(diagnostics[0].error_code().unwrap(), ASSIGN_LHS_IMMUTABLE); + } + } + } +} diff --git a/dmc-lib/src/ast/field.rs b/dmc-lib/src/ast/field.rs index 96f9825..49cc75b 100644 --- a/dmc-lib/src/ast/field.rs +++ b/dmc-lib/src/ast/field.rs @@ -56,8 +56,11 @@ impl Field { field_index: usize, ) -> Result<(), Vec> { // 1. insert field symbol - let to_insert = - FieldSymbol::new(&self.declared_name, self.declared_name_source_range.clone()); + let to_insert = FieldSymbol::new( + &self.declared_name, + self.declared_name_source_range.clone(), + self.is_mut, + ); let field_symbol = symbol_table .insert_field_symbol(to_insert) diff --git a/dmc-lib/src/ast/let_statement.rs b/dmc-lib/src/ast/let_statement.rs index fd8f56f..fb35f28 100644 --- a/dmc-lib/src/ast/let_statement.rs +++ b/dmc-lib/src/ast/let_statement.rs @@ -15,6 +15,7 @@ use std::rc::Rc; pub struct LetStatement { declared_name: Rc, declared_name_source_range: SourceRange, + is_mut: bool, initializer: Box, scope_id: Option, } @@ -23,11 +24,13 @@ impl LetStatement { pub fn new( declared_name: &str, declared_name_source_range: SourceRange, + is_mut: bool, initializer: Expression, ) -> Self { Self { declared_name: declared_name.into(), declared_name_source_range, + is_mut, initializer: initializer.into(), scope_id: None, } @@ -61,6 +64,7 @@ impl LetStatement { let insert_result = symbol_table.insert_variable_symbol(VariableSymbol::new( &self.declared_name, self.declared_name_source_range.clone(), + self.is_mut, )); if let Err(symbol_insert_error) = insert_result { match symbol_insert_error { diff --git a/dmc-lib/src/ast/mod.rs b/dmc-lib/src/ast/mod.rs index 7b417e8..ecdce2e 100644 --- a/dmc-lib/src/ast/mod.rs +++ b/dmc-lib/src/ast/mod.rs @@ -1,4 +1,5 @@ pub mod add_expression; +pub mod assign_statement; pub mod call; pub mod class; pub mod compilation_unit; diff --git a/dmc-lib/src/ast/statement.rs b/dmc-lib/src/ast/statement.rs index b6f7428..08d496c 100644 --- a/dmc-lib/src/ast/statement.rs +++ b/dmc-lib/src/ast/statement.rs @@ -1,3 +1,4 @@ +use crate::ast::assign_statement::AssignStatement; use crate::ast::expression_statement::ExpressionStatement; use crate::ast::ir_builder::IrBuilder; use crate::ast::let_statement::LetStatement; @@ -8,6 +9,7 @@ use crate::type_info::TypeInfo; pub enum Statement { Let(LetStatement), Expression(ExpressionStatement), + Assign(AssignStatement), } impl Statement { @@ -20,6 +22,9 @@ impl Statement { Statement::Expression(expression_statement) => { expression_statement.gather_declared_names(symbol_table) } + Statement::Assign(assign_statement) => { + assign_statement.gather_declared_names(symbol_table) + } } } @@ -29,6 +34,7 @@ impl Statement { Statement::Expression(expression_statement) => { expression_statement.check_name_usages(symbol_table) } + Statement::Assign(assign_statement) => assign_statement.check_name_usages(symbol_table), } } @@ -42,6 +48,7 @@ impl Statement { Statement::Expression(expression_statement) => { expression_statement.type_check(symbol_table, must_return_type_info) } + Statement::Assign(assign_statement) => assign_statement.type_check(symbol_table), } } @@ -58,6 +65,9 @@ impl Statement { Statement::Expression(expression_statement) => { expression_statement.to_ir(builder, symbol_table, should_return_value); } + Statement::Assign(assign_statement) => { + todo!() + } } } } diff --git a/dmc-lib/src/error_codes.rs b/dmc-lib/src/error_codes.rs new file mode 100644 index 0000000..743023a --- /dev/null +++ b/dmc-lib/src/error_codes.rs @@ -0,0 +1,5 @@ +pub type ErrorCode = usize; + +pub const ASSIGN_MISMATCHED_TYPES: ErrorCode = 16; +pub const ASSIGN_NO_L_VALUE: ErrorCode = 17; +pub const ASSIGN_LHS_IMMUTABLE: ErrorCode = 18; diff --git a/dmc-lib/src/lib.rs b/dmc-lib/src/lib.rs index 8902917..32662e0 100644 --- a/dmc-lib/src/lib.rs +++ b/dmc-lib/src/lib.rs @@ -1,6 +1,7 @@ pub mod ast; pub mod constants_table; pub mod diagnostic; +pub mod error_codes; pub mod ir; pub mod lexer; pub mod parser; diff --git a/dmc-lib/src/parser.rs b/dmc-lib/src/parser.rs index 03b6c7f..ae565ac 100644 --- a/dmc-lib/src/parser.rs +++ b/dmc-lib/src/parser.rs @@ -1,4 +1,5 @@ use crate::ast::add_expression::AddExpression; +use crate::ast::assign_statement::AssignStatement; use crate::ast::call::Call; use crate::ast::class::Class; use crate::ast::compilation_unit::CompilationUnit; @@ -621,24 +622,44 @@ impl<'a> Parser<'a> { let current = self.get_current(); match current.kind() { TokenKind::Let => Ok(Statement::Let(self.let_statement()?)), - _ => Ok(Statement::Expression(self.expression_statement()?)), + _ => self.expression_statement_or_assign_statement(), } } fn let_statement(&mut self) -> Result> { self.expect_advance(TokenKind::Let)?; + + let is_mut = if self.current.is_some() && self.peek_current(TokenKind::Mut) { + self.advance(); + true + } else { + false + }; + let identifier = self.expect_advance(TokenKind::Identifier)?; self.expect_advance(TokenKind::Equals)?; let expression = self.expression()?; Ok(LetStatement::new( self.token_text(&identifier), SourceRange::new(identifier.start(), identifier.end()), + is_mut, expression, )) } - fn expression_statement(&mut self) -> Result> { - Ok(ExpressionStatement::new(self.expression()?)) + fn expression_statement_or_assign_statement(&mut self) -> Result> { + let base = self.expression()?; + if self.current.is_some() && self.peek_current(TokenKind::Equals) { + Ok(Statement::Assign(self.assign_rhs(base)?)) + } else { + Ok(Statement::Expression(ExpressionStatement::new(base))) + } + } + + fn assign_rhs(&mut self, destination: Expression) -> Result> { + self.expect_advance(TokenKind::Equals)?; + let value = self.expression()?; + Ok(AssignStatement::new(destination, value)) } fn expression(&mut self) -> Result> { @@ -919,6 +940,18 @@ mod smoke_tests { ", ); } + + #[test] + fn simple_assign() { + smoke_test( + " + fn main() + let mut x = 4 + x = 42 + end + ", + ); + } } #[cfg(test)] diff --git a/dmc-lib/src/symbol/expressible_symbol.rs b/dmc-lib/src/symbol/expressible_symbol.rs index 6b76261..9231ed8 100644 --- a/dmc-lib/src/symbol/expressible_symbol.rs +++ b/dmc-lib/src/symbol/expressible_symbol.rs @@ -6,6 +6,7 @@ use crate::ir::ir_operation::IrOperation; use crate::ir::ir_read_field::IrReadField; use crate::ir::ir_statement::IrStatement; use crate::ir::ir_variable::IrVariable; +use crate::source_range::SourceRange; use crate::symbol::Symbol; use crate::symbol::class_symbol::ClassSymbol; use crate::symbol::field_symbol::FieldSymbol; @@ -41,6 +42,37 @@ impl ExpressibleSymbol { } } + pub fn is_mut(&self) -> bool { + match self { + ExpressibleSymbol::Field(field_symbol) => field_symbol.borrow().is_mut(), + ExpressibleSymbol::Variable(variable_symbol) => variable_symbol.borrow().is_mut(), + _ => false, + } + } + + pub fn source_range(&self) -> SourceRange { + match self { + ExpressibleSymbol::Class(class_symbol) => { + class_symbol.borrow().declared_name_source_range().clone() + } + ExpressibleSymbol::Field(field_symbol) => { + field_symbol.borrow().declared_name_source_range().clone() + } + ExpressibleSymbol::Function(function_symbol) => function_symbol + .borrow() + .declared_name_source_range() + .clone(), + ExpressibleSymbol::Parameter(parameter_symbol) => parameter_symbol + .borrow() + .declared_name_source_range() + .clone(), + ExpressibleSymbol::Variable(variable_symbol) => variable_symbol + .borrow() + .declared_name_source_range() + .clone(), + } + } + pub fn ir_expression(&self, builder: &mut IrBuilder) -> IrExpression { match self { ExpressibleSymbol::Class(class_symbol) => { diff --git a/dmc-lib/src/symbol/field_symbol.rs b/dmc-lib/src/symbol/field_symbol.rs index 05d4b6d..cca7080 100644 --- a/dmc-lib/src/symbol/field_symbol.rs +++ b/dmc-lib/src/symbol/field_symbol.rs @@ -6,20 +6,30 @@ use std::rc::Rc; pub struct FieldSymbol { declared_name: Rc, declared_name_source_range: SourceRange, + is_mut: bool, type_info: Option, field_index: Option, } impl FieldSymbol { - pub fn new(declared_name: &Rc, declared_name_source_range: SourceRange) -> Self { + pub fn new( + declared_name: &Rc, + declared_name_source_range: SourceRange, + is_mut: bool, + ) -> Self { Self { declared_name: declared_name.clone(), declared_name_source_range, + is_mut, type_info: None, field_index: None, } } + pub fn is_mut(&self) -> bool { + self.is_mut + } + pub fn set_type_info(&mut self, type_info: TypeInfo) { self.type_info = Some(type_info); } diff --git a/dmc-lib/src/symbol/variable_symbol.rs b/dmc-lib/src/symbol/variable_symbol.rs index f1e2348..b7eaf08 100644 --- a/dmc-lib/src/symbol/variable_symbol.rs +++ b/dmc-lib/src/symbol/variable_symbol.rs @@ -8,20 +8,26 @@ use std::rc::Rc; pub struct VariableSymbol { declared_name: Rc, declared_name_source_range: SourceRange, + is_mut: bool, type_info: Option, vr_variable: Option>>, } impl VariableSymbol { - pub fn new(name: &Rc, declared_name_source_range: SourceRange) -> Self { + pub fn new(name: &Rc, declared_name_source_range: SourceRange, is_mut: bool) -> Self { Self { declared_name: name.clone(), declared_name_source_range, + is_mut, type_info: None, vr_variable: None, } } + pub fn is_mut(&self) -> bool { + self.is_mut + } + pub fn set_type_info(&mut self, type_info: TypeInfo) { self.type_info = Some(type_info); } diff --git a/examples/simple_assign.dm b/examples/simple_assign.dm new file mode 100644 index 0000000..35f9256 --- /dev/null +++ b/examples/simple_assign.dm @@ -0,0 +1,7 @@ +extern fn println(message: Any) -> Void + +fn main() + let mut x = 4 + x = 7 + println(x) +end