Parsing/analyzing assign statements.
This commit is contained in:
parent
f7e8cef380
commit
7e613b1b90
@ -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() {
|
||||
|
||||
250
dmc-lib/src/ast/assign_statement.rs
Normal file
250
dmc-lib/src/ast/assign_statement.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
pub mod add_expression;
|
||||
pub mod assign_statement;
|
||||
pub mod call;
|
||||
pub mod class;
|
||||
pub mod compilation_unit;
|
||||
|
||||
@ -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!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
dmc-lib/src/error_codes.rs
Normal file
5
dmc-lib/src/error_codes.rs
Normal 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;
|
||||
@ -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;
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
7
examples/simple_assign.dm
Normal file
7
examples/simple_assign.dm
Normal file
@ -0,0 +1,7 @@
|
||||
extern fn println(message: Any) -> Void
|
||||
|
||||
fn main()
|
||||
let mut x = 4
|
||||
x = 7
|
||||
println(x)
|
||||
end
|
||||
Loading…
Reference in New Issue
Block a user