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);
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() {

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,
) -> Result<(), Vec<Diagnostic>> {
// 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)

View File

@ -15,6 +15,7 @@ use std::rc::Rc;
pub struct LetStatement {
declared_name: Rc<str>,
declared_name_source_range: SourceRange,
is_mut: bool,
initializer: Box<Expression>,
scope_id: Option<usize>,
}
@ -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 {

View File

@ -1,4 +1,5 @@
pub mod add_expression;
pub mod assign_statement;
pub mod call;
pub mod class;
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::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!()
}
}
}
}

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 constants_table;
pub mod diagnostic;
pub mod error_codes;
pub mod ir;
pub mod lexer;
pub mod parser;

View File

@ -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<LetStatement, Vec<Diagnostic>> {
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<ExpressionStatement, Vec<Diagnostic>> {
Ok(ExpressionStatement::new(self.expression()?))
fn expression_statement_or_assign_statement(&mut self) -> Result<Statement, Vec<Diagnostic>> {
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>> {
@ -919,6 +940,18 @@ mod smoke_tests {
",
);
}
#[test]
fn simple_assign() {
smoke_test(
"
fn main()
let mut x = 4
x = 42
end
",
);
}
}
#[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_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) => {

View File

@ -6,20 +6,30 @@ use std::rc::Rc;
pub struct FieldSymbol {
declared_name: Rc<str>,
declared_name_source_range: SourceRange,
is_mut: bool,
type_info: Option<TypeInfo>,
field_index: Option<usize>,
}
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 {
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);
}

View File

@ -8,20 +8,26 @@ use std::rc::Rc;
pub struct VariableSymbol {
declared_name: Rc<str>,
declared_name_source_range: SourceRange,
is_mut: bool,
type_info: Option<TypeInfo>,
vr_variable: Option<Rc<RefCell<IrVariable>>>,
}
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 {
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);
}

View File

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