Parsing/analyzing assign statements.

This commit is contained in:
Jesse Brault 2026-03-14 10:36:53 -05:00
parent f7e8cef380
commit 7e613b1b90
13 changed files with 370 additions and 8 deletions

View File

@ -164,7 +164,7 @@ fn report_and_exit(
.with_labels(secondary_labels); .with_labels(secondary_labels);
if let Some(error_code) = diagnostic.error_code() { 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() { if let Some((reporter_file, reporter_line)) = diagnostic.reporter() {

View File

@ -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<Expression>,
value: Box<Expression>,
}
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<Diagnostic>> {
let mut diagnostics: Vec<Diagnostic> = 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<Diagnostic>> {
let mut diagnostics: Vec<Diagnostic> = 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<Diagnostic>> {
let mut diagnostics: Vec<Diagnostic> = 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);
}
}
}
}

View File

@ -56,8 +56,11 @@ impl Field {
field_index: usize, field_index: usize,
) -> Result<(), Vec<Diagnostic>> { ) -> Result<(), Vec<Diagnostic>> {
// 1. insert field symbol // 1. insert field symbol
let to_insert = let to_insert = FieldSymbol::new(
FieldSymbol::new(&self.declared_name, self.declared_name_source_range.clone()); &self.declared_name,
self.declared_name_source_range.clone(),
self.is_mut,
);
let field_symbol = symbol_table let field_symbol = symbol_table
.insert_field_symbol(to_insert) .insert_field_symbol(to_insert)

View File

@ -15,6 +15,7 @@ use std::rc::Rc;
pub struct LetStatement { pub struct LetStatement {
declared_name: Rc<str>, declared_name: Rc<str>,
declared_name_source_range: SourceRange, declared_name_source_range: SourceRange,
is_mut: bool,
initializer: Box<Expression>, initializer: Box<Expression>,
scope_id: Option<usize>, scope_id: Option<usize>,
} }
@ -23,11 +24,13 @@ impl LetStatement {
pub fn new( pub fn new(
declared_name: &str, declared_name: &str,
declared_name_source_range: SourceRange, declared_name_source_range: SourceRange,
is_mut: bool,
initializer: Expression, initializer: Expression,
) -> Self { ) -> Self {
Self { Self {
declared_name: declared_name.into(), declared_name: declared_name.into(),
declared_name_source_range, declared_name_source_range,
is_mut,
initializer: initializer.into(), initializer: initializer.into(),
scope_id: None, scope_id: None,
} }
@ -61,6 +64,7 @@ impl LetStatement {
let insert_result = symbol_table.insert_variable_symbol(VariableSymbol::new( let insert_result = symbol_table.insert_variable_symbol(VariableSymbol::new(
&self.declared_name, &self.declared_name,
self.declared_name_source_range.clone(), self.declared_name_source_range.clone(),
self.is_mut,
)); ));
if let Err(symbol_insert_error) = insert_result { if let Err(symbol_insert_error) = insert_result {
match symbol_insert_error { match symbol_insert_error {

View File

@ -1,4 +1,5 @@
pub mod add_expression; pub mod add_expression;
pub mod assign_statement;
pub mod call; pub mod call;
pub mod class; pub mod class;
pub mod compilation_unit; pub mod compilation_unit;

View File

@ -1,3 +1,4 @@
use crate::ast::assign_statement::AssignStatement;
use crate::ast::expression_statement::ExpressionStatement; use crate::ast::expression_statement::ExpressionStatement;
use crate::ast::ir_builder::IrBuilder; use crate::ast::ir_builder::IrBuilder;
use crate::ast::let_statement::LetStatement; use crate::ast::let_statement::LetStatement;
@ -8,6 +9,7 @@ use crate::type_info::TypeInfo;
pub enum Statement { pub enum Statement {
Let(LetStatement), Let(LetStatement),
Expression(ExpressionStatement), Expression(ExpressionStatement),
Assign(AssignStatement),
} }
impl Statement { impl Statement {
@ -20,6 +22,9 @@ impl Statement {
Statement::Expression(expression_statement) => { Statement::Expression(expression_statement) => {
expression_statement.gather_declared_names(symbol_table) 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) => { Statement::Expression(expression_statement) => {
expression_statement.check_name_usages(symbol_table) 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) => { Statement::Expression(expression_statement) => {
expression_statement.type_check(symbol_table, must_return_type_info) 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) => { Statement::Expression(expression_statement) => {
expression_statement.to_ir(builder, symbol_table, should_return_value); expression_statement.to_ir(builder, symbol_table, should_return_value);
} }
Statement::Assign(assign_statement) => {
todo!()
}
} }
} }
} }

View File

@ -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;

View File

@ -1,6 +1,7 @@
pub mod ast; pub mod ast;
pub mod constants_table; pub mod constants_table;
pub mod diagnostic; pub mod diagnostic;
pub mod error_codes;
pub mod ir; pub mod ir;
pub mod lexer; pub mod lexer;
pub mod parser; pub mod parser;

View File

@ -1,4 +1,5 @@
use crate::ast::add_expression::AddExpression; use crate::ast::add_expression::AddExpression;
use crate::ast::assign_statement::AssignStatement;
use crate::ast::call::Call; use crate::ast::call::Call;
use crate::ast::class::Class; use crate::ast::class::Class;
use crate::ast::compilation_unit::CompilationUnit; use crate::ast::compilation_unit::CompilationUnit;
@ -621,24 +622,44 @@ impl<'a> Parser<'a> {
let current = self.get_current(); let current = self.get_current();
match current.kind() { match current.kind() {
TokenKind::Let => Ok(Statement::Let(self.let_statement()?)), 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<LetStatement, Vec<Diagnostic>> { fn let_statement(&mut self) -> Result<LetStatement, Vec<Diagnostic>> {
self.expect_advance(TokenKind::Let)?; 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)?; let identifier = self.expect_advance(TokenKind::Identifier)?;
self.expect_advance(TokenKind::Equals)?; self.expect_advance(TokenKind::Equals)?;
let expression = self.expression()?; let expression = self.expression()?;
Ok(LetStatement::new( Ok(LetStatement::new(
self.token_text(&identifier), self.token_text(&identifier),
SourceRange::new(identifier.start(), identifier.end()), SourceRange::new(identifier.start(), identifier.end()),
is_mut,
expression, expression,
)) ))
} }
fn expression_statement(&mut self) -> Result<ExpressionStatement, Vec<Diagnostic>> { fn expression_statement_or_assign_statement(&mut self) -> Result<Statement, Vec<Diagnostic>> {
Ok(ExpressionStatement::new(self.expression()?)) 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<AssignStatement, Vec<Diagnostic>> {
self.expect_advance(TokenKind::Equals)?;
let value = self.expression()?;
Ok(AssignStatement::new(destination, value))
} }
fn expression(&mut self) -> Result<Expression, Vec<Diagnostic>> { fn expression(&mut self) -> Result<Expression, Vec<Diagnostic>> {
@ -919,6 +940,18 @@ mod smoke_tests {
", ",
); );
} }
#[test]
fn simple_assign() {
smoke_test(
"
fn main()
let mut x = 4
x = 42
end
",
);
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -6,6 +6,7 @@ use crate::ir::ir_operation::IrOperation;
use crate::ir::ir_read_field::IrReadField; use crate::ir::ir_read_field::IrReadField;
use crate::ir::ir_statement::IrStatement; use crate::ir::ir_statement::IrStatement;
use crate::ir::ir_variable::IrVariable; use crate::ir::ir_variable::IrVariable;
use crate::source_range::SourceRange;
use crate::symbol::Symbol; use crate::symbol::Symbol;
use crate::symbol::class_symbol::ClassSymbol; use crate::symbol::class_symbol::ClassSymbol;
use crate::symbol::field_symbol::FieldSymbol; 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 { pub fn ir_expression(&self, builder: &mut IrBuilder) -> IrExpression {
match self { match self {
ExpressibleSymbol::Class(class_symbol) => { ExpressibleSymbol::Class(class_symbol) => {

View File

@ -6,20 +6,30 @@ use std::rc::Rc;
pub struct FieldSymbol { pub struct FieldSymbol {
declared_name: Rc<str>, declared_name: Rc<str>,
declared_name_source_range: SourceRange, declared_name_source_range: SourceRange,
is_mut: bool,
type_info: Option<TypeInfo>, type_info: Option<TypeInfo>,
field_index: Option<usize>, field_index: Option<usize>,
} }
impl FieldSymbol { impl FieldSymbol {
pub fn new(declared_name: &Rc<str>, declared_name_source_range: SourceRange) -> Self { pub fn new(
declared_name: &Rc<str>,
declared_name_source_range: SourceRange,
is_mut: bool,
) -> Self {
Self { Self {
declared_name: declared_name.clone(), declared_name: declared_name.clone(),
declared_name_source_range, declared_name_source_range,
is_mut,
type_info: None, type_info: None,
field_index: None, field_index: None,
} }
} }
pub fn is_mut(&self) -> bool {
self.is_mut
}
pub fn set_type_info(&mut self, type_info: TypeInfo) { pub fn set_type_info(&mut self, type_info: TypeInfo) {
self.type_info = Some(type_info); self.type_info = Some(type_info);
} }

View File

@ -8,20 +8,26 @@ use std::rc::Rc;
pub struct VariableSymbol { pub struct VariableSymbol {
declared_name: Rc<str>, declared_name: Rc<str>,
declared_name_source_range: SourceRange, declared_name_source_range: SourceRange,
is_mut: bool,
type_info: Option<TypeInfo>, type_info: Option<TypeInfo>,
vr_variable: Option<Rc<RefCell<IrVariable>>>, vr_variable: Option<Rc<RefCell<IrVariable>>>,
} }
impl VariableSymbol { impl VariableSymbol {
pub fn new(name: &Rc<str>, declared_name_source_range: SourceRange) -> Self { pub fn new(name: &Rc<str>, declared_name_source_range: SourceRange, is_mut: bool) -> Self {
Self { Self {
declared_name: name.clone(), declared_name: name.clone(),
declared_name_source_range, declared_name_source_range,
is_mut,
type_info: None, type_info: None,
vr_variable: None, vr_variable: None,
} }
} }
pub fn is_mut(&self) -> bool {
self.is_mut
}
pub fn set_type_info(&mut self, type_info: TypeInfo) { pub fn set_type_info(&mut self, type_info: TypeInfo) {
self.type_info = Some(type_info); self.type_info = Some(type_info);
} }

View File

@ -0,0 +1,7 @@
extern fn println(message: Any) -> Void
fn main()
let mut x = 4
x = 7
println(x)
end