Compare commits

...

8 Commits

Author SHA1 Message Date
Jesse Brault
320cdcf805 Compiling fields getters/setters. 2026-03-12 21:52:52 -05:00
Jesse Brault
51a80bb5ed Add records to manual. 2026-03-12 18:41:41 -05:00
Jesse Brault
940671822c Add basic classes example. 2026-03-12 16:24:20 -05:00
Jesse Brault
75802e6ee4 Expand diagnostic API. 2026-03-12 16:15:03 -05:00
Jesse Brault
93eb5eb204 Add ability to call constructors. 2026-03-12 15:57:32 -05:00
Jesse Brault
8082f4c2e6 Add type-checking to classes/fields/constructors. 2026-03-12 15:39:13 -05:00
Jesse Brault
3c0bf948ac Start writing documentation on compiler errors. 2026-03-12 15:17:34 -05:00
Jesse Brault
ad821ce6a7 Add ctors, most name-analysis for classes/fields/constructors. 2026-03-12 12:35:36 -05:00
41 changed files with 1437 additions and 112 deletions

View File

@ -6,18 +6,14 @@ use dmc_lib::ir::ir_return::IrReturn;
use dmc_lib::ir::ir_statement::IrStatement;
use dmc_lib::lexer::Lexer;
use dmc_lib::parser::parse_expression;
use dmc_lib::source_range::SourceRange;
use dmc_lib::symbol::function_symbol::FunctionSymbol;
use dmc_lib::symbol_table::SymbolTable;
use dmc_lib::token::TokenKind;
use dvm_lib::vm::constant::{Constant, StringConstant};
use dvm_lib::vm::function::Function;
use dvm_lib::vm::value::Value;
use dvm_lib::vm::{CallStack, DvmContext, call};
use std::cell::RefCell;
use std::io;
use std::io::Write;
use std::rc::Rc;
pub fn repl(register_count: usize) {
let mut buffer = String::new();
@ -128,19 +124,8 @@ fn compile_expression(
ir_builder.finish_block();
let entry_block = ir_builder.get_block(entry_block_id);
// synthesize symbol
let fake_function_symbol = Rc::new(RefCell::new(FunctionSymbol::new(
"__repl",
SourceRange::new(0, 0),
false,
)));
fake_function_symbol
.borrow_mut()
.set_return_type_info(expression.type_info().clone());
let mut ir_function = IrFunction::new(
fake_function_symbol,
"__repl".into(),
&[],
expression.type_info(),
entry_block.clone(),

View File

@ -125,12 +125,42 @@ fn report_and_exit(
let writer = StandardStream::stderr(ColorChoice::Always);
let config = term::Config::default();
for diagnostic in diagnostics {
let csr_diagnostic = codespan_reporting::diagnostic::Diagnostic::error()
let mut primary_label =
Label::primary(script_file_id, diagnostic.start()..diagnostic.end());
if let Some(primary_label_message) = diagnostic.primary_label_message() {
primary_label = primary_label.with_message(primary_label_message);
}
let secondary_labels: Vec<Label<usize>> = diagnostic
.secondary_labels()
.iter()
.map(|secondary_label| {
let mut label = Label::secondary(
script_file_id,
secondary_label.start()..secondary_label.end(),
);
if let Some(message) = secondary_label.message() {
label = label.with_message(message);
}
label
})
.collect();
let mut csr_diagnostic = codespan_reporting::diagnostic::Diagnostic::error()
.with_message(diagnostic.message())
.with_label(Label::primary(
script_file_id,
diagnostic.start()..diagnostic.end(),
.with_label(primary_label)
.with_labels(secondary_labels);
if let Some(error_code) = diagnostic.error_code() {
csr_diagnostic = csr_diagnostic.with_code(format!("E{}", error_code));
}
if let Some((reporter_file, reporter_line)) = diagnostic.reporter() {
csr_diagnostic = csr_diagnostic.with_note(format!(
"Reported by (Rust) source: {}, line {}",
reporter_file, reporter_line
));
}
term::emit_to_write_style(&mut writer.lock(), &config, files, &csr_diagnostic).unwrap();
}

View File

@ -5,6 +5,7 @@ use crate::ir::ir_call::IrCall;
use crate::ir::ir_expression::IrExpression;
use crate::source_range::SourceRange;
use crate::symbol::Symbol;
use crate::symbol::callable_symbol::CallableSymbol;
use crate::symbol::expressible_symbol::ExpressibleSymbol;
use crate::symbol::function_symbol::FunctionSymbol;
use crate::symbol_table::SymbolTable;
@ -87,8 +88,11 @@ impl Call {
.collect();
// check that callee is callable
let function_symbol = match self.callee.type_info() {
TypeInfo::Function(function_symbol) => function_symbol,
let callable_symbol = match self.callee.type_info() {
TypeInfo::Function(function_symbol) => {
CallableSymbol::Function(function_symbol.clone())
}
TypeInfo::ClassInstance(class_symbol) => CallableSymbol::Class(class_symbol.clone()),
_ => {
diagnostics.push(Diagnostic::new(
&format!(
@ -103,11 +107,10 @@ impl Call {
};
// set return type
self.return_type_info = Some(function_symbol.borrow().return_type_info().clone());
self.return_type_info = Some(callable_symbol.return_type_info());
// check arguments length
let function_symbol_ref = function_symbol.borrow();
let parameters = function_symbol_ref.parameters();
let parameters = callable_symbol.parameters();
if parameters.len() != self.arguments.len() {
diagnostics.push(Diagnostic::new(
&format!(

View File

@ -1,30 +1,39 @@
use crate::ast::constructor::Constructor;
use crate::ast::field::Field;
use crate::ast::function::Function;
use crate::diagnostic::Diagnostic;
use crate::diagnostic::{Diagnostic, SecondaryLabel};
use crate::ir::ir_function::IrFunction;
use crate::source_range::SourceRange;
use crate::symbol::class_symbol::ClassSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
use std::cell::RefCell;
use std::collections::HashSet;
use std::rc::Rc;
pub struct Class {
declared_name: Rc<str>,
declared_name_source_range: SourceRange,
constructor: Option<Constructor>,
fields: Vec<Field>,
functions: Vec<Function>,
class_symbol: Option<Rc<RefCell<ClassSymbol>>>,
}
impl Class {
pub fn new(
declared_name: &str,
declared_name_source_range: SourceRange,
constructor: Option<Constructor>,
fields: Vec<Field>,
functions: Vec<Function>,
) -> Self {
Class {
declared_name: declared_name.into(),
declared_name_source_range,
constructor,
fields,
functions,
class_symbol: None,
}
}
@ -39,10 +48,11 @@ impl Class {
false,
);
symbol_table
let class_symbol = symbol_table
.insert_class_symbol(to_insert)
.map_err(|e| match e {
SymbolInsertError::AlreadyDeclared(already_declared) => {
let symbol = already_declared.symbol().borrow();
vec![
Diagnostic::new(
&format!(
@ -52,11 +62,19 @@ impl Class {
self.declared_name_source_range.start(),
self.declared_name_source_range.end(),
)
.with_secondary_labels(&[SecondaryLabel::new(
symbol.declared_name_source_range().start(),
symbol.declared_name_source_range().end(),
Some("Symbol declared here.".to_string()),
)])
.with_reporter(file!(), line!()),
]
}
})?;
// save symbol for later
self.class_symbol = Some(class_symbol);
// 2. push scope
symbol_table.push_class_scope(&format!("class_scope({})", self.declared_name));
@ -64,7 +82,8 @@ impl Class {
let fields_diagnostics: Vec<Diagnostic> = self
.fields
.iter_mut()
.map(|field| field.gather_declared_names(symbol_table))
.enumerate()
.map(|(field_index, field)| field.gather_declared_names(symbol_table, field_index))
.filter_map(Result::err)
.flatten()
.collect();
@ -73,7 +92,17 @@ impl Class {
return Err(fields_diagnostics);
}
// 4. gather functions
// 4. gather constructor
if let Some(constructor) = &mut self.constructor {
let constructor_symbol = constructor.gather_declared_names(symbol_table)?;
self.class_symbol
.as_mut()
.unwrap()
.borrow_mut()
.set_constructor_symbol(Some(constructor_symbol));
}
// 5. gather functions
let functions_diagnostics: Vec<Diagnostic> = self
.functions
.iter_mut()
@ -86,13 +115,18 @@ impl Class {
return Err(functions_diagnostics);
}
// 5. pop scope
// 6. pop scope
symbol_table.pop_scope();
Ok(())
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
self.constructor
.as_mut()
.map(|constructor| constructor.check_name_usages(symbol_table))
.transpose()?;
let fields_diagnostics: Vec<Diagnostic> = self
.fields
.iter_mut()
@ -104,6 +138,10 @@ impl Class {
return Err(fields_diagnostics);
}
if let Some(constructor) = &mut self.constructor {
constructor.check_name_usages(symbol_table)?;
}
let functions_diagnostics: Vec<Diagnostic> = self
.functions
.iter_mut()
@ -120,21 +158,123 @@ impl Class {
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
todo!()
let mut diagnostics: Vec<Diagnostic> = vec![];
self.fields
.iter_mut()
.map(|field| field.type_check(symbol_table))
.filter_map(Result::err)
.flatten()
.for_each(|diagnostic| diagnostics.push(diagnostic));
if !diagnostics.is_empty() {
return Err(diagnostics);
}
if let Some(constructor) = &mut self.constructor {
match constructor.type_check(symbol_table) {
Ok(_) => {}
Err(mut constructor_diagnostics) => {
diagnostics.append(&mut constructor_diagnostics);
}
}
}
if !diagnostics.is_empty() {
return Err(diagnostics);
}
self.functions
.iter_mut()
.map(|function| function.type_check(symbol_table))
.filter_map(Result::err)
.flatten()
.for_each(|diagnostic| diagnostics.push(diagnostic));
if !diagnostics.is_empty() {
return Err(diagnostics);
}
// We need to determine if fields are initialized or not (the latter is an error).
// First phase: check all fields, then check constructor, leaving pending those things that
// are fields <- initialized by constructor. Then circle back to fields and check all are
// initialized
let mut initialized_field_names: HashSet<&str> = HashSet::new();
for field in &self.fields {
if field.initializer().is_some() {
initialized_field_names.insert(field.declared_name());
}
}
// check constructor
if let Some(constructor) = &self.constructor {
for statement in constructor.statements() {
match statement {
_ => {
// no-op for now, because we don't yet have assign statements
}
}
}
}
// check that all fields are present in the hash set
for field in &self.fields {
if !initialized_field_names.contains(field.declared_name()) {
diagnostics.push(
Diagnostic::new(
&format!("Field {} is not initialized.", field.declared_name()),
field.declared_name_source_range().start(),
field.declared_name_source_range().end(),
)
.with_primary_label_message(
"Must be initialized in declaration or constructor.",
)
.with_reporter(file!(), line!()),
)
}
}
if diagnostics.is_empty() {
Ok(())
} else {
Err(diagnostics)
}
}
pub fn to_ir(&self, symbol_table: &SymbolTable) -> Vec<IrFunction> {
let mut ir_functions: Vec<IrFunction> = vec![];
if let Some(constructor) = &self.constructor {
ir_functions.push(constructor.to_ir(
self.class_symbol.as_ref().unwrap(),
&self.fields,
symbol_table,
))
}
for function in &self.functions {
ir_functions.push(function.to_ir(symbol_table));
}
ir_functions
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants_table::ConstantsTable;
use crate::parser::parse_compilation_unit;
#[test]
fn name_analysis_no_diagnostics() {
fn complete_example_no_diagnostics() {
let parse_result = parse_compilation_unit(
"
class Foo
mut bar = 42
mut bar: Int = 42
ctor(_bar: Int)
end
fn baz() -> Int
bar
@ -143,7 +283,7 @@ mod tests {
class Qux
fn foo() -> Foo
Foo()
Foo(42)
end
end
",
@ -170,5 +310,20 @@ mod tests {
panic!("{:?}", diagnostics);
}
}
match compilation_unit.type_check(&symbol_table) {
Ok(_) => {}
Err(diagnostics) => {
panic!("{:?}", diagnostics);
}
}
let mut ir_functions = compilation_unit.to_ir(&symbol_table);
let mut constants_table = ConstantsTable::new();
for ir_function in &mut ir_functions {
let (_, stack_size) = ir_function.assign_registers(8);
let vm_function = ir_function.assemble(stack_size, &mut constants_table);
println!("{}", vm_function)
}
}
}

View File

@ -136,9 +136,15 @@ impl CompilationUnit {
}
pub fn to_ir(&self, symbol_table: &SymbolTable) -> Vec<IrFunction> {
let mut functions: Vec<IrFunction> = vec![];
self.functions
.iter()
.map(|f| f.to_ir(symbol_table))
.collect()
.for_each(|f| functions.push(f));
self.classes
.iter()
.flat_map(|c| c.to_ir(symbol_table))
.for_each(|f| functions.push(f));
functions
}
}

View File

@ -0,0 +1,241 @@
use crate::ast::field::Field;
use crate::ast::ir_builder::IrBuilder;
use crate::ast::parameter::Parameter;
use crate::ast::statement::Statement;
use crate::diagnostic::Diagnostic;
use crate::ir::ir_allocate::IrAllocate;
use crate::ir::ir_assign::IrAssign;
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_function::IrFunction;
use crate::ir::ir_operation::IrOperation;
use crate::ir::ir_parameter::IrParameter;
use crate::ir::ir_set_field::IrSetField;
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::constructor_symbol::ConstructorSymbol;
use crate::symbol::parameter_symbol::ParameterSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
use crate::type_info::TypeInfo;
use std::cell::RefCell;
use std::ops::Neg;
use std::rc::Rc;
pub struct Constructor {
is_public: bool,
ctor_keyword_source_range: SourceRange,
parameters: Vec<Parameter>,
statements: Vec<Statement>,
}
impl Constructor {
pub fn new(
is_public: bool,
ctor_keyword_source_range: SourceRange,
parameters: Vec<Parameter>,
statements: Vec<Statement>,
) -> Self {
Self {
is_public,
ctor_keyword_source_range,
parameters,
statements,
}
}
pub fn statements(&self) -> &[Statement] {
&self.statements
}
pub fn gather_declared_names(
&mut self,
symbol_table: &mut SymbolTable,
) -> Result<Rc<RefCell<ConstructorSymbol>>, Vec<Diagnostic>> {
// insert constructor symbol
let to_insert = ConstructorSymbol::new(self.ctor_keyword_source_range.clone(), false);
let constructor_symbol =
symbol_table
.insert_constructor_symbol(to_insert)
.map_err(|symbol_insert_error| match symbol_insert_error {
SymbolInsertError::AlreadyDeclared(_) => {
vec![
Diagnostic::new(
"Cannot declare more than one constructor.",
self.ctor_keyword_source_range.start(),
self.ctor_keyword_source_range.end(),
)
.with_reporter(file!(), line!()),
]
}
})?;
symbol_table.push_function_scope("ctor_scope");
let mut parameter_symbols: Vec<Rc<RefCell<ParameterSymbol>>> = vec![];
let mut parameters_diagnostics = vec![];
for parameter in &mut self.parameters {
match parameter.gather_declared_names(symbol_table) {
Ok(parameter_symbol) => {
parameter_symbols.push(parameter_symbol);
}
Err(mut ds) => {
parameters_diagnostics.append(&mut ds);
}
}
}
if !parameters_diagnostics.is_empty() {
symbol_table.pop_scope();
return Err(parameters_diagnostics);
} else {
constructor_symbol
.borrow_mut()
.set_parameters(parameter_symbols);
}
symbol_table.push_block_scope("ctor_main_block");
let statements_diagnostics = self
.statements
.iter_mut()
.map(|stmt| stmt.gather_declared_names(symbol_table))
.filter_map(Result::err)
.flatten()
.collect::<Vec<_>>();
symbol_table.pop_scope(); // block
symbol_table.pop_scope(); // function
if statements_diagnostics.is_empty() {
Ok(constructor_symbol)
} else {
Err(statements_diagnostics)
}
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
let parameters_diagnostics: Vec<Diagnostic> = self
.parameters
.iter_mut()
.map(|param| param.check_name_usages(symbol_table))
.filter_map(Result::err)
.flatten()
.collect();
if !parameters_diagnostics.is_empty() {
return Err(parameters_diagnostics);
}
let statements_diagnostics: Vec<Diagnostic> = self
.statements
.iter_mut()
.map(|statement| statement.check_name_usages(symbol_table))
.filter_map(Result::err)
.flatten()
.collect();
if statements_diagnostics.is_empty() {
Ok(())
} else {
Err(statements_diagnostics)
}
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
let parameters_diagnostics: Vec<Diagnostic> = self
.parameters
.iter_mut()
.map(|param| param.type_check(symbol_table))
.filter_map(Result::err)
.flatten()
.collect();
if !parameters_diagnostics.is_empty() {
return Err(parameters_diagnostics);
}
let statements_diagnostics: Vec<Diagnostic> = self
.statements
.iter_mut()
.map(|statement| statement.type_check(symbol_table, None))
.filter_map(Result::err)
.flatten()
.collect();
if statements_diagnostics.is_empty() {
Ok(())
} else {
Err(statements_diagnostics)
}
}
pub fn to_ir(
&self,
class_symbol: &Rc<RefCell<ClassSymbol>>,
fields: &[Field],
symbol_table: &SymbolTable,
) -> IrFunction {
let mut ir_builder = IrBuilder::new();
let parameters_count = self.parameters.len();
let ir_parameters = self
.parameters
.iter()
.enumerate()
.map(|(i, parameter)| {
let parameter_symbol = parameter.parameter_symbol().borrow();
let offset = (parameters_count as isize).neg() + i as isize;
Rc::new(IrParameter::new(
parameter_symbol.declared_name(),
parameter_symbol.type_info().clone(),
offset,
))
})
.collect::<Vec<_>>();
let entry_block_id = ir_builder.new_block();
// first, allocate the object into a t var
let alloc_assign_destination = IrVariable::new_vr(
ir_builder.new_t_var().into(),
ir_builder.current_block().id(),
&TypeInfo::ClassInstance(class_symbol.clone()),
);
let self_variable = Rc::new(RefCell::new(alloc_assign_destination));
let alloc_assign = IrAssign::new(
self_variable.clone(),
IrOperation::Allocate(IrAllocate::new(class_symbol.borrow().declared_name_owned())),
);
ir_builder
.current_block_mut()
.add_statement(IrStatement::Assign(alloc_assign));
// next, initialize fields that have an initializer in their declaration
for field in fields {
if let Some(initializer) = field.initializer() {
let ir_expression = initializer.to_ir(&mut ir_builder, symbol_table).unwrap();
let ir_set_field = IrSetField::new(
&self_variable.clone(),
field.field_symbol().borrow().field_index(),
ir_expression,
);
ir_builder
.current_block_mut()
.add_statement(IrStatement::SetField(ir_set_field));
}
}
ir_builder.finish_block();
let entry_block = ir_builder.get_block(entry_block_id);
IrFunction::new(
format!("{}::ctor", class_symbol.borrow().declared_name()).into(), // fake function symbol
&ir_parameters, // make params
&TypeInfo::ClassInstance(class_symbol.clone()),
entry_block.clone(),
)
}
}

View File

@ -3,10 +3,8 @@ use crate::ast::ir_builder::IrBuilder;
use crate::diagnostic::Diagnostic;
use crate::ir::ir_return::IrReturn;
use crate::ir::ir_statement::IrStatement;
use crate::symbol::function_symbol::FunctionSymbol;
use crate::symbol_table::SymbolTable;
use std::cell::RefCell;
use std::rc::Rc;
use crate::type_info::TypeInfo;
pub struct ExpressionStatement {
expression: Box<Expression>,
@ -37,15 +35,13 @@ impl ExpressionStatement {
pub fn type_check(
&mut self,
symbol_table: &SymbolTable,
is_last: bool,
function_symbol: &Rc<RefCell<FunctionSymbol>>,
must_return_type_info: Option<&TypeInfo>,
) -> Result<(), Vec<Diagnostic>> {
self.expression.type_check(symbol_table)?;
if is_last {
if must_return_type_info.is_some() {
let expression_type = self.expression.type_info();
let borrowed_symbol = function_symbol.borrow();
let return_type = borrowed_symbol.return_type_info();
let return_type = must_return_type_info.unwrap();
if !return_type.is_assignable_from(expression_type) {
return Err(vec![Diagnostic::new(
&format!(

View File

@ -4,6 +4,7 @@ use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol::field_symbol::FieldSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
use std::cell::RefCell;
use std::rc::Rc;
pub struct Field {
@ -13,6 +14,7 @@ pub struct Field {
is_mut: bool,
declared_type: Option<Box<TypeUse>>,
initializer: Option<Box<Expression>>,
field_symbol: Option<Rc<RefCell<FieldSymbol>>>,
}
impl Field {
@ -31,18 +33,32 @@ impl Field {
is_mut,
declared_type: declared_type.map(Box::new),
initializer: initializer.map(Box::new),
field_symbol: None,
}
}
pub fn declared_name(&self) -> &str {
&self.declared_name
}
pub fn declared_name_source_range(&self) -> &SourceRange {
&self.declared_name_source_range
}
pub fn initializer(&self) -> Option<&Expression> {
self.initializer.as_ref().map(Box::as_ref)
}
pub fn gather_declared_names(
&mut self,
symbol_table: &mut SymbolTable,
field_index: usize,
) -> Result<(), Vec<Diagnostic>> {
// 1. insert field symbol
let to_insert =
FieldSymbol::new(&self.declared_name, self.declared_name_source_range.clone());
symbol_table
let field_symbol = symbol_table
.insert_field_symbol(to_insert)
.map_err(|e| match e {
SymbolInsertError::AlreadyDeclared(already_declared) => {
@ -57,7 +73,21 @@ impl Field {
}
})?;
// 2. gather initializer, if present
// save for later
self.field_symbol = Some(field_symbol);
// set field index on symbol
self.field_symbol
.as_ref()
.unwrap()
.borrow_mut()
.set_field_index(field_index);
// 2. gather type_use and initializer, if present
if let Some(type_use) = &mut self.declared_type {
type_use.gather_declared_names(symbol_table)?;
}
if let Some(initializer) = &mut self.initializer {
initializer.gather_declared_names(symbol_table)?;
}
@ -80,4 +110,87 @@ impl Field {
}
Ok(())
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
let mut diagnostics: Vec<Diagnostic> = vec![];
if let Some(type_use) = &mut self.declared_type {
if let Some(mut type_use_diagnostics) = type_use.type_check(symbol_table).err() {
diagnostics.append(&mut type_use_diagnostics);
}
}
if let Some(initializer) = &mut self.initializer {
if let Some(mut initializer_diagnostics) = initializer.type_check(symbol_table).err() {
diagnostics.append(&mut initializer_diagnostics);
}
}
if !diagnostics.is_empty() {
return Err(diagnostics);
}
// Now check that types are assignable, and update field symbol's type info
let field_type_info =
match self.declared_type.as_ref() {
Some(type_use) => {
match self.initializer.as_ref() {
Some(initializer) => {
let initializer_type_info = initializer.type_info();
let declared_type_info = type_use.to_type_info();
if declared_type_info.is_assignable_from(initializer_type_info) {
declared_type_info
} else {
return Err(vec![
Diagnostic::new(
&format!(
"Mismatched types: {} is not assignable to {}",
initializer_type_info, declared_type_info
),
initializer.source_range().start(),
initializer.source_range().end(),
)
.with_reporter(file!(), line!()),
]);
}
}
None => {
// easy: the declared type
type_use.to_type_info()
}
}
}
None => {
// type is the initializer
match self.initializer.as_ref() {
Some(initializer) => initializer.type_info().clone(),
None => {
// this is an error
return Err(vec![Diagnostic::new(
"Field must have either a declared type or initializer, or both.",
self.declared_name_source_range.start(),
self.declared_name_source_range.end(),
).with_reporter(file!(), line!())]);
}
}
}
};
// update field symbol's type info
self.field_symbol
.as_mut()
.unwrap()
.borrow_mut()
.set_type_info(field_type_info);
if diagnostics.is_empty() {
Ok(())
} else {
Err(diagnostics)
}
}
pub fn field_symbol(&self) -> &Rc<RefCell<FieldSymbol>> {
self.field_symbol.as_ref().unwrap()
}
}

View File

@ -169,9 +169,11 @@ impl Function {
}
} else {
// we don't have a given return type, so it's void
self.function_symbol.as_mut().unwrap().borrow_mut().set_return_type_info(
TypeInfo::Void
);
self.function_symbol
.as_mut()
.unwrap()
.borrow_mut()
.set_return_type_info(TypeInfo::Void);
}
// statements
@ -206,6 +208,7 @@ impl Function {
);
let function_symbol = self.function_symbol.as_ref().unwrap();
let return_type_info = function_symbol.borrow().return_type_info().clone();
let statements_len = self.statements.len();
// statements
@ -216,7 +219,11 @@ impl Function {
.enumerate()
.map(|(i, statement)| {
let is_last = i == statements_len - 1;
statement.type_check(symbol_table, is_last, function_symbol)
if is_last {
statement.type_check(symbol_table, Some(&return_type_info))
} else {
statement.type_check(symbol_table, None)
}
})
.filter_map(Result::err)
.flatten()
@ -262,7 +269,11 @@ impl Function {
let entry_block = builder.get_block(entry_block_id).clone();
IrFunction::new(
self.function_symbol.as_ref().unwrap().clone(),
self.function_symbol
.as_ref()
.unwrap()
.borrow()
.declared_name_owned(), // ok for now... but we need to start using the fqn
builder.parameters(),
return_type_info,
entry_block,

View File

@ -2,6 +2,7 @@ pub mod add_expression;
pub mod call;
pub mod class;
pub mod compilation_unit;
pub mod constructor;
pub mod double_literal;
pub mod expression;
pub mod expression_statement;

View File

@ -74,6 +74,9 @@ impl NegativeExpression {
.expect("Attempt to negate non-value expression");
match operand_as_ir {
IrExpression::Field(self_variable, _) => {
todo!()
}
IrExpression::Parameter(parameter) => {
let destination = Rc::new(RefCell::new(IrVariable::new_vr(
builder.new_t_var().into(),

View File

@ -36,7 +36,7 @@ impl Parameter {
let insert_result = symbol_table.insert_parameter_symbol(ParameterSymbol::new(
&self.declared_name,
self.declared_name_source_range.clone(),
TypeInfo::from_declared_name(self.type_use.declared_name()),
TypeInfo::from_declared_name(self.type_use.declared_name()), // todo: this will blow up if type is a Class
));
match insert_result {
Ok(parameter_symbol) => {

View File

@ -2,10 +2,8 @@ use crate::ast::expression_statement::ExpressionStatement;
use crate::ast::ir_builder::IrBuilder;
use crate::ast::let_statement::LetStatement;
use crate::diagnostic::Diagnostic;
use crate::symbol::function_symbol::FunctionSymbol;
use crate::symbol_table::SymbolTable;
use std::cell::RefCell;
use std::rc::Rc;
use crate::type_info::TypeInfo;
pub enum Statement {
Let(LetStatement),
@ -37,13 +35,12 @@ impl Statement {
pub fn type_check(
&mut self,
symbol_table: &SymbolTable,
is_last: bool,
function_symbol: &Rc<RefCell<FunctionSymbol>>,
must_return_type_info: Option<&TypeInfo>,
) -> Result<(), Vec<Diagnostic>> {
match self {
Statement::Let(let_statement) => let_statement.type_check(symbol_table),
Statement::Expression(expression_statement) => {
expression_statement.type_check(symbol_table, is_last, function_symbol)
expression_statement.type_check(symbol_table, must_return_type_info)
}
}
}

View File

@ -56,9 +56,13 @@ impl TypeUse {
}
}
pub fn type_check(&mut self, _symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
Ok(()) // no-op, for now
}
pub fn to_type_info(&self) -> TypeInfo {
match self.type_symbol.as_ref().unwrap() {
TypeSymbol::Class(class_symbol) => TypeInfo::Class(class_symbol.clone()),
TypeSymbol::Class(class_symbol) => TypeInfo::ClassInstance(class_symbol.clone()),
TypeSymbol::Primitive(primitive_type) => match primitive_type {
PrimitiveTypeSymbol::Any => TypeInfo::Any,
PrimitiveTypeSymbol::Int => TypeInfo::Integer,

View File

@ -1,20 +1,26 @@
#[derive(Debug)]
pub struct Diagnostic {
message: String,
primary_label_message: Option<String>,
error_code: Option<usize>,
start: usize,
end: usize,
reporter_file: Option<&'static str>,
reporter_line: Option<u32>,
secondary_labels: Vec<SecondaryLabel>,
}
impl Diagnostic {
pub fn new(message: &str, start: usize, end: usize) -> Self {
Self {
message: message.into(),
primary_label_message: None,
error_code: None,
start,
end,
reporter_line: None,
reporter_file: None,
secondary_labels: vec![],
}
}
@ -22,6 +28,14 @@ impl Diagnostic {
&self.message
}
pub fn primary_label_message(&self) -> Option<&str> {
self.primary_label_message.as_deref()
}
pub fn error_code(&self) -> Option<usize> {
self.error_code
}
pub fn start(&self) -> usize {
self.start
}
@ -30,13 +44,65 @@ impl Diagnostic {
self.end
}
pub fn with_reporter(&self, file: &'static str, line: u32) -> Self {
Self {
message: self.message.clone(),
start: self.start,
end: self.end,
reporter_file: Some(file),
reporter_line: Some(line),
pub fn reporter(&self) -> Option<(&'static str, u32)> {
if let Some(file) = self.reporter_file {
Some((file, self.reporter_line.unwrap()))
} else {
None
}
}
pub fn secondary_labels(&self) -> &[SecondaryLabel] {
&self.secondary_labels
}
pub fn with_error_code(mut self, code: usize) -> Self {
self.error_code = Some(code);
self
}
pub fn with_primary_label_message(mut self, message: &str) -> Self {
self.primary_label_message = Some(message.into());
self
}
pub fn with_reporter(mut self, file: &'static str, line: u32) -> Self {
self.reporter_file = Some(file);
self.reporter_line = Some(line);
self
}
pub fn with_secondary_labels(mut self, labels: &[SecondaryLabel]) -> Self {
self.secondary_labels.extend_from_slice(labels);
self
}
}
#[derive(Debug, Clone)]
pub struct SecondaryLabel {
start: usize,
end: usize,
message: Option<String>,
}
impl SecondaryLabel {
pub fn new(start: usize, end: usize, message: Option<String>) -> Self {
Self {
start,
end,
message,
}
}
pub fn start(&self) -> usize {
self.start
}
pub fn end(&self) -> usize {
self.end
}
pub fn message(&self) -> Option<&str> {
self.message.as_deref()
}
}

View File

@ -0,0 +1,26 @@
use std::fmt::{Display, Formatter};
use std::rc::Rc;
pub struct IrAllocate {
class_fqn: Rc<str>,
}
impl IrAllocate {
pub fn new(class_fqn: Rc<str>) -> Self {
Self { class_fqn }
}
pub fn class_fqn(&self) -> &str {
&self.class_fqn
}
pub fn class_fqn_owned(&self) -> Rc<str> {
self.class_fqn.clone()
}
}
impl Display for IrAllocate {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "alloc {}", self.class_fqn)
}
}

View File

@ -109,6 +109,10 @@ impl Assemble for IrAssign {
ir_call.assemble(builder, constants_table);
builder.push(Instruction::Pop(Some(destination)));
}
IrOperation::Allocate(ir_allocate) => builder.push(Instruction::Allocate(
ir_allocate.class_fqn_owned(),
destination,
)),
}
}
}

View File

@ -6,7 +6,8 @@ use crate::ir::ir_variable::{
use crate::ir::register_allocation::{OffsetCounter, VrUser};
use crate::type_info::TypeInfo;
use dvm_lib::instruction::{
AddOperand, Location, MoveOperand, MultiplyOperand, PushOperand, ReturnOperand, SubtractOperand,
AddOperand, Location, MoveOperand, MultiplyOperand, PushOperand, ReturnOperand,
SetFieldOperand, SubtractOperand,
};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
@ -14,6 +15,7 @@ use std::fmt::{Display, Formatter};
use std::rc::Rc;
pub enum IrExpression {
Field(Rc<RefCell<IrVariable>>, usize),
Parameter(Rc<IrParameter>),
Variable(Rc<RefCell<IrVariable>>),
Int(i32),
@ -22,18 +24,11 @@ pub enum IrExpression {
}
impl IrExpression {
pub fn type_info(&self) -> TypeInfo {
match self {
IrExpression::Parameter(ir_parameter) => ir_parameter.type_info().clone(),
IrExpression::Variable(ir_variable) => ir_variable.borrow().type_info().clone(),
IrExpression::Int(_) => TypeInfo::Integer,
IrExpression::Double(_) => TypeInfo::Double,
IrExpression::String(_) => TypeInfo::String,
}
}
pub fn move_operand(&self, constants_table: &mut ConstantsTable) -> MoveOperand {
match self {
IrExpression::Field(self_variable, _) => {
todo!()
}
IrExpression::Parameter(ir_parameter) => {
MoveOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
@ -56,6 +51,9 @@ impl IrExpression {
pub fn push_operand(&self, constants_table: &mut ConstantsTable) -> PushOperand {
match self {
IrExpression::Field(self_variable, _) => {
todo!()
}
IrExpression::Parameter(ir_parameter) => {
PushOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
@ -78,6 +76,9 @@ impl IrExpression {
pub fn add_operand(&self, constants_table: &mut ConstantsTable) -> AddOperand {
match self {
IrExpression::Field(self_variable, _) => {
todo!()
}
IrExpression::Parameter(ir_parameter) => match ir_parameter.type_info() {
TypeInfo::Integer | TypeInfo::Double | TypeInfo::String => {
AddOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
@ -116,6 +117,9 @@ impl IrExpression {
pub fn subtract_operand(&self) -> SubtractOperand {
match self {
IrExpression::Field(self_variable, _) => {
todo!()
}
IrExpression::Parameter(ir_parameter) => match ir_parameter.type_info() {
TypeInfo::Integer | TypeInfo::Double => SubtractOperand::Location(
Location::StackFrameOffset(ir_parameter.stack_offset()),
@ -151,6 +155,9 @@ impl IrExpression {
pub fn multiply_operand(&self) -> MultiplyOperand {
match self {
IrExpression::Field(self_variable, _) => {
todo!()
}
IrExpression::Parameter(ir_parameter) => {
MultiplyOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
@ -172,6 +179,9 @@ impl IrExpression {
pub fn return_operand(&self, constants_table: &mut ConstantsTable) -> ReturnOperand {
match self {
IrExpression::Field(self_variable, _) => {
todo!()
}
IrExpression::Parameter(ir_parameter) => {
ReturnOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
@ -193,11 +203,39 @@ impl IrExpression {
}
}
}
pub fn set_field_operand(&self, constants_table: &mut ConstantsTable) -> SetFieldOperand {
match self {
IrExpression::Field(self_variable, _) => {
todo!()
}
IrExpression::Parameter(ir_parameter) => {
SetFieldOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
IrExpression::Variable(ir_variable) => match ir_variable.borrow().descriptor() {
IrVariableDescriptor::VirtualRegister(vr_variable) => {
SetFieldOperand::Location(Location::Register(vr_variable.assigned_register()))
}
IrVariableDescriptor::Stack(stack_variable) => {
SetFieldOperand::Location(Location::StackFrameOffset(stack_variable.offset()))
}
},
IrExpression::Int(i) => SetFieldOperand::Int(*i),
IrExpression::Double(d) => SetFieldOperand::Double(*d),
IrExpression::String(s) => {
let constant_name = constants_table.get_or_insert(s);
SetFieldOperand::String(constant_name)
}
}
}
}
impl Display for IrExpression {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
IrExpression::Field(self_variable, field_index) => {
write!(f, "{}.{}", self_variable.borrow(), field_index)
}
IrExpression::Parameter(ir_parameter) => {
write!(f, "{}", ir_parameter)
}
@ -219,11 +257,21 @@ impl Display for IrExpression {
impl VrUser for IrExpression {
fn vr_definitions(&self) -> HashSet<IrVrVariableDescriptor> {
// no defs for an expression
HashSet::new()
}
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
match self {
IrExpression::Field(self_variable, field_index) => {
if let IrVariableDescriptor::VirtualRegister(vr_variable) =
self_variable.borrow().descriptor()
{
HashSet::from([vr_variable.clone()])
} else {
HashSet::new()
}
}
IrExpression::Parameter(_) => HashSet::new(),
IrExpression::Variable(ir_variable) => {
if let IrVariableDescriptor::VirtualRegister(vr_variable) =
@ -242,6 +290,9 @@ impl VrUser for IrExpression {
fn propagate_spills(&mut self, spills: &HashSet<IrVrVariableDescriptor>) {
match self {
IrExpression::Field(self_variable, field_index) => {
// no-op, no defs
}
IrExpression::Parameter(_) => {
// no-op
}
@ -278,6 +329,9 @@ impl VrUser for IrExpression {
assignments: &HashMap<IrVrVariableDescriptor, usize>,
) {
match self {
IrExpression::Field(self_variable, field_index) => {
// no-op
}
IrExpression::Parameter(_) => {
// no-op
}

View File

@ -4,8 +4,6 @@ use crate::ir::ir_block::IrBlock;
use crate::ir::ir_parameter::IrParameter;
use crate::ir::ir_variable::IrVrVariableDescriptor;
use crate::ir::register_allocation::HasVrUsers;
use crate::symbol::Symbol;
use crate::symbol::function_symbol::FunctionSymbol;
use crate::type_info::TypeInfo;
use dvm_lib::vm::function::Function;
use std::cell::RefCell;
@ -14,7 +12,7 @@ use std::fmt::Display;
use std::rc::Rc;
pub struct IrFunction {
function_symbol: Rc<RefCell<FunctionSymbol>>,
fqn: Rc<str>,
parameters: Vec<Rc<IrParameter>>,
return_type_info: TypeInfo,
entry: Rc<RefCell<IrBlock>>,
@ -22,13 +20,13 @@ pub struct IrFunction {
impl IrFunction {
pub fn new(
function_symbol: Rc<RefCell<FunctionSymbol>>,
fqn: Rc<str>,
parameters: &[Rc<IrParameter>],
return_type_info: &TypeInfo,
entry: Rc<RefCell<IrBlock>>,
) -> Self {
Self {
function_symbol,
fqn,
parameters: parameters.to_vec(),
return_type_info: return_type_info.clone(),
entry,
@ -47,7 +45,7 @@ impl IrFunction {
self.entry.borrow().assemble(&mut builder, constants_table);
let instructions = builder.take_instructions();
Function::new(
self.function_symbol.borrow().declared_name_owned(),
self.fqn.clone(),
self.parameters.len(),
stack_size,
instructions,
@ -57,7 +55,7 @@ impl IrFunction {
impl Display for IrFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "fn {}(", self.function_symbol.borrow().declared_name())?;
write!(f, "fn {}(", self.fqn)?;
for (i, parameter) in self.parameters.iter().enumerate() {
write!(f, "{}: {}", parameter, parameter.type_info())?;
if i < self.parameters.len() - 1 {

View File

@ -1,4 +1,5 @@
use crate::ir::ir_add::IrAdd;
use crate::ir::ir_allocate::IrAllocate;
use crate::ir::ir_call::IrCall;
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_multiply::IrMultiply;
@ -14,6 +15,7 @@ pub enum IrOperation {
Subtract(IrSubtract),
Multiply(IrMultiply),
Call(IrCall),
Allocate(IrAllocate),
}
impl Display for IrOperation {
@ -34,6 +36,9 @@ impl Display for IrOperation {
IrOperation::Call(ir_call) => {
write!(f, "{}", ir_call)
}
IrOperation::Allocate(ir_allocate) => {
write!(f, "{}", ir_allocate)
}
}
}
}
@ -46,6 +51,7 @@ impl VrUser for IrOperation {
IrOperation::Subtract(ir_subtract) => ir_subtract.vr_definitions(),
IrOperation::Multiply(ir_multiply) => ir_multiply.vr_definitions(),
IrOperation::Call(ir_call) => ir_call.vr_definitions(),
IrOperation::Allocate(_) => HashSet::new(),
}
}
@ -56,6 +62,7 @@ impl VrUser for IrOperation {
IrOperation::Subtract(ir_subtract) => ir_subtract.vr_uses(),
IrOperation::Multiply(ir_multiply) => ir_multiply.vr_uses(),
IrOperation::Call(ir_call) => ir_call.vr_uses(),
IrOperation::Allocate(_) => HashSet::new(),
}
}
@ -76,6 +83,7 @@ impl VrUser for IrOperation {
IrOperation::Call(ir_call) => {
ir_call.propagate_spills(spills);
}
IrOperation::Allocate(_) => (),
}
}
@ -99,6 +107,7 @@ impl VrUser for IrOperation {
IrOperation::Call(ir_call) => {
ir_call.propagate_register_assignments(assignments);
}
IrOperation::Allocate(_) => (),
}
}

View File

@ -0,0 +1,98 @@
use crate::constants_table::ConstantsTable;
use crate::ir::assemble::{Assemble, InstructionsBuilder};
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_variable::{IrVariable, IrVariableDescriptor, IrVrVariableDescriptor};
use crate::ir::register_allocation::{OffsetCounter, VrUser};
use crate::ir::util::propagate_spills;
use dvm_lib::instruction::{Instruction, Location};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::fmt::{Display, Formatter};
use std::rc::Rc;
pub struct IrSetField {
target_object: Rc<RefCell<IrVariable>>,
field_index: usize,
initializer: Box<IrExpression>,
}
impl IrSetField {
pub fn new(
target_object: &Rc<RefCell<IrVariable>>,
field_index: usize,
initializer: IrExpression,
) -> Self {
Self {
target_object: target_object.clone(),
field_index,
initializer: initializer.into(),
}
}
}
impl VrUser for IrSetField {
fn vr_definitions(&self) -> HashSet<IrVrVariableDescriptor> {
// only this, because the target_object is technically not a definition
self.initializer.vr_definitions()
}
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
let mut set = HashSet::new();
match self.target_object.borrow().descriptor() {
IrVariableDescriptor::VirtualRegister(vr_variable) => {
set.insert(vr_variable.clone());
}
IrVariableDescriptor::Stack(_) => {}
}
set.extend(self.initializer.vr_uses());
set
}
fn propagate_spills(&mut self, spills: &HashSet<IrVrVariableDescriptor>) {
propagate_spills(&mut self.target_object, spills);
self.initializer.propagate_spills(spills);
}
fn propagate_register_assignments(
&mut self,
_assignments: &HashMap<IrVrVariableDescriptor, usize>,
) {
// no definitions
}
fn propagate_stack_offsets(&mut self, _counter: &mut OffsetCounter) {
// no definitions
}
}
impl Assemble for IrSetField {
fn assemble(&self, builder: &mut InstructionsBuilder, constants_table: &mut ConstantsTable) {
let destination = match self.target_object.borrow().descriptor() {
IrVariableDescriptor::VirtualRegister(vr_variable) => {
Location::Register(vr_variable.assigned_register())
}
IrVariableDescriptor::Stack(stack_variable) => {
Location::StackFrameOffset(stack_variable.offset())
}
};
let set_field_operand = self.initializer.set_field_operand(constants_table);
builder.push(Instruction::SetField(
destination,
self.field_index,
set_field_operand,
));
}
}
impl Display for IrSetField {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}.{} = {}",
self.target_object.borrow(),
self.field_index,
self.initializer
)
}
}

View File

@ -3,6 +3,7 @@ use crate::ir::assemble::{Assemble, InstructionsBuilder};
use crate::ir::ir_assign::IrAssign;
use crate::ir::ir_call::IrCall;
use crate::ir::ir_return::IrReturn;
use crate::ir::ir_set_field::IrSetField;
use crate::ir::ir_variable::IrVrVariableDescriptor;
use crate::ir::register_allocation::{OffsetCounter, VrUser};
use std::collections::{HashMap, HashSet};
@ -12,6 +13,7 @@ pub enum IrStatement {
Assign(IrAssign),
Call(IrCall),
Return(IrReturn),
SetField(IrSetField),
}
impl VrUser for IrStatement {
@ -20,6 +22,7 @@ impl VrUser for IrStatement {
IrStatement::Assign(ir_assign) => ir_assign.vr_definitions(),
IrStatement::Call(ir_call) => ir_call.vr_definitions(),
IrStatement::Return(ir_return) => ir_return.vr_definitions(),
IrStatement::SetField(ir_set_field) => ir_set_field.vr_definitions(),
}
}
@ -28,6 +31,7 @@ impl VrUser for IrStatement {
IrStatement::Assign(ir_assign) => ir_assign.vr_uses(),
IrStatement::Call(ir_call) => ir_call.vr_uses(),
IrStatement::Return(ir_return) => ir_return.vr_uses(),
IrStatement::SetField(ir_set_field) => ir_set_field.vr_uses(),
}
}
@ -42,6 +46,9 @@ impl VrUser for IrStatement {
IrStatement::Return(ir_return) => {
ir_return.propagate_spills(spills);
}
IrStatement::SetField(ir_set_field) => {
ir_set_field.propagate_spills(spills);
}
}
}
@ -59,6 +66,9 @@ impl VrUser for IrStatement {
IrStatement::Return(ir_return) => {
ir_return.propagate_register_assignments(assignments);
}
IrStatement::SetField(ir_set_field) => {
ir_set_field.propagate_register_assignments(assignments);
}
}
}
@ -73,6 +83,9 @@ impl VrUser for IrStatement {
IrStatement::Return(ir_return) => {
ir_return.propagate_stack_offsets(counter);
}
IrStatement::SetField(ir_set_field) => {
ir_set_field.propagate_stack_offsets(counter);
}
}
}
}
@ -89,6 +102,9 @@ impl Assemble for IrStatement {
IrStatement::Return(ir_return) => {
ir_return.assemble(builder, constants_table);
}
IrStatement::SetField(ir_set_field) => {
ir_set_field.assemble(builder, constants_table);
}
}
}
}
@ -105,6 +121,9 @@ impl Display for IrStatement {
IrStatement::Return(ir_return) => {
write!(f, "{}", ir_return)
}
IrStatement::SetField(ir_set_field) => {
write!(f, "{}", ir_set_field)
}
}
}
}

View File

@ -73,6 +73,13 @@ impl IrVariableDescriptor {
IrVariableDescriptor::Stack(stack_variable) => stack_variable.name(),
}
}
pub fn name_owned(&self) -> Rc<str> {
match self {
IrVariableDescriptor::VirtualRegister(vr_variable) => vr_variable.name_owned(),
IrVariableDescriptor::Stack(stack_variable) => stack_variable.name_owned(),
}
}
}
#[derive(Clone, Hash, PartialEq, Eq)]
@ -95,6 +102,10 @@ impl IrVrVariableDescriptor {
&self.name
}
pub fn name_owned(&self) -> Rc<str> {
self.name.clone()
}
pub fn block_id(&self) -> usize {
self.block_id
}
@ -139,6 +150,10 @@ impl IrStackVariableDescriptor {
&self.name
}
pub fn name_owned(&self) -> Rc<str> {
self.name.clone()
}
pub fn set_offset(&mut self, offset: isize) {
self.offset = Some(offset);
}

View File

@ -1,5 +1,6 @@
mod assemble;
pub mod ir_add;
pub mod ir_allocate;
pub mod ir_assign;
pub mod ir_block;
pub mod ir_call;
@ -9,7 +10,9 @@ pub mod ir_multiply;
pub mod ir_operation;
pub mod ir_parameter;
pub mod ir_return;
pub mod ir_set_field;
pub mod ir_statement;
pub mod ir_subtract;
pub mod ir_variable;
mod register_allocation;
mod util;

22
dmc-lib/src/ir/util.rs Normal file
View File

@ -0,0 +1,22 @@
use crate::ir::ir_variable::{
IrStackVariableDescriptor, IrVariable, IrVariableDescriptor, IrVrVariableDescriptor,
};
use std::cell::RefCell;
use std::collections::HashSet;
use std::rc::Rc;
pub fn propagate_spills(
ir_variable: &mut Rc<RefCell<IrVariable>>,
spills: &HashSet<IrVrVariableDescriptor>,
) {
let mut borrowed_ir_variable = ir_variable.borrow_mut();
if let IrVariableDescriptor::VirtualRegister(vr_variable) = borrowed_ir_variable.descriptor() {
if spills.contains(vr_variable) {
let name = vr_variable.name_owned();
let block_id = vr_variable.block_id();
borrowed_ir_variable.set_descriptor(IrVariableDescriptor::Stack(
IrStackVariableDescriptor::new(name, block_id),
));
}
}
}

View File

@ -136,6 +136,7 @@ impl<'a> Lexer<'a> {
"self" => TokenKind::SelfKw,
"pub" => TokenKind::Public,
"mut" => TokenKind::Mut,
"ctor" => TokenKind::Ctor,
_ => TokenKind::Identifier,
};
Token::new(self.position, self.position + prefix.len(), token_kind)

View File

@ -2,6 +2,7 @@ use crate::ast::add_expression::AddExpression;
use crate::ast::call::Call;
use crate::ast::class::Class;
use crate::ast::compilation_unit::CompilationUnit;
use crate::ast::constructor::Constructor;
use crate::ast::double_literal::DoubleLiteral;
use crate::ast::expression::Expression;
use crate::ast::expression_statement::ExpressionStatement;
@ -49,6 +50,12 @@ macro_rules! matches_expression_first {
};
}
macro_rules! matches_statement_first {
( $token_kind : expr ) => {
matches!($token_kind, TokenKind::Let) || matches_expression_first!($token_kind)
};
}
struct Parser<'a> {
input: &'a str,
lexer: Lexer<'a>,
@ -382,12 +389,17 @@ impl<'a> Parser<'a> {
let identifier_token = self.expect_advance(TokenKind::Identifier)?;
let mut fields = vec![];
let mut functions = vec![];
let mut maybe_constructor: Option<Constructor> = None;
let mut diagnostics = vec![];
while self.current.is_some() && !self.peek_current(TokenKind::End) {
match self.get_current().kind() {
TokenKind::Public => match self.public_class_member(&mut fields, &mut functions) {
TokenKind::Public => match self.public_class_member(
&mut fields,
&mut functions,
&mut maybe_constructor,
) {
Ok(_) => {}
Err(mut member_diagnostics) => diagnostics.append(&mut member_diagnostics),
},
@ -399,6 +411,12 @@ impl<'a> Parser<'a> {
Ok(function) => functions.push(function),
Err(mut function_diagnostics) => diagnostics.append(&mut function_diagnostics),
},
TokenKind::Ctor => match self.constructor() {
Ok(constructor) => {
maybe_constructor = Some(constructor);
}
Err(mut ctor_diagnostics) => diagnostics.append(&mut ctor_diagnostics),
},
_ => unreachable!(),
}
}
@ -409,6 +427,7 @@ impl<'a> Parser<'a> {
Ok(Class::new(
self.token_text(&identifier_token),
SourceRange::new(identifier_token.start(), identifier_token.end()),
maybe_constructor,
fields,
functions,
))
@ -469,26 +488,29 @@ impl<'a> Parser<'a> {
&mut self,
fields: &mut Vec<Field>,
functions: &mut Vec<Function>,
maybe_ctor: &mut Option<Constructor>,
) -> Result<(), Vec<Diagnostic>> {
if self.lookahead.is_some() {
if matches!(
self.lookahead.as_ref().unwrap().kind(),
TokenKind::Mut | TokenKind::Identifier
) {
fields.push(self.field()?);
} else if matches!(self.lookahead.as_ref().unwrap().kind(), TokenKind::Fn) {
functions.push(self.function()?);
} else {
let lookahead = self.lookahead.as_ref().unwrap();
return Err(vec![Diagnostic::new(
&format!(
"Expected any of {:?}; found {:?}",
[TokenKind::Mut, TokenKind::Identifier, TokenKind::Fn],
lookahead.kind()
),
lookahead.start(),
lookahead.end(),
)]);
match self.lookahead.as_ref().unwrap().kind() {
TokenKind::Mut | TokenKind::Identifier => {
fields.push(self.field()?);
}
TokenKind::Fn => functions.push(self.function()?),
TokenKind::Ctor => {
maybe_ctor.replace(self.constructor()?);
}
_ => {
let lookahead = self.lookahead.as_ref().unwrap();
return Err(vec![Diagnostic::new(
&format!(
"Expected any of {:?}; found {:?}",
[TokenKind::Mut, TokenKind::Identifier, TokenKind::Fn],
lookahead.kind()
),
lookahead.start(),
lookahead.end(),
)]);
}
}
Ok(())
} else {
@ -504,6 +526,56 @@ impl<'a> Parser<'a> {
}
}
fn constructor(&mut self) -> Result<Constructor, Vec<Diagnostic>> {
let is_public = if self.current.is_some() && self.peek_current(TokenKind::Public) {
self.advance();
true
} else {
false
};
let ctor_keyword = self.expect_advance(TokenKind::Ctor)?;
self.expect_advance(TokenKind::LeftParentheses)?;
let parameters = if self.current.is_some() && self.peek_current(TokenKind::Identifier) {
self.parameter_list()?
} else {
vec![]
};
self.expect_advance(TokenKind::RightParentheses)?;
// statements
let mut statements: Vec<Statement> = vec![];
let mut diagnostics: Vec<Diagnostic> = vec![];
while self.current.is_some()
&& matches_statement_first!(self.current.as_ref().unwrap().kind())
{
match self.statement() {
Ok(statement) => {
statements.push(statement);
}
Err(mut statement_diagnostics) => {
diagnostics.append(&mut statement_diagnostics);
}
}
}
self.expect_advance(TokenKind::End)?;
if diagnostics.is_empty() {
Ok(Constructor::new(
is_public,
SourceRange::new(ctor_keyword.start(), ctor_keyword.end()),
parameters,
statements,
))
} else {
Err(diagnostics)
}
}
fn field(&mut self) -> Result<Field, Vec<Diagnostic>> {
let is_public = if self.current.is_some() && self.peek_current(TokenKind::Public) {
self.advance();

View File

@ -1,4 +1,5 @@
use crate::symbol::class_symbol::ClassSymbol;
use crate::symbol::constructor_symbol::ConstructorSymbol;
use crate::symbol::field_symbol::FieldSymbol;
use crate::symbol::function_symbol::FunctionSymbol;
use std::cell::RefCell;
@ -11,6 +12,7 @@ pub struct ClassScope {
class_symbols: HashMap<Rc<str>, Rc<RefCell<ClassSymbol>>>,
field_symbols: HashMap<Rc<str>, Rc<RefCell<FieldSymbol>>>,
function_symbols: HashMap<Rc<str>, Rc<RefCell<FunctionSymbol>>>,
constructor_symbol: Option<Rc<RefCell<ConstructorSymbol>>>,
}
impl ClassScope {
@ -21,6 +23,7 @@ impl ClassScope {
class_symbols: HashMap::new(),
field_symbols: HashMap::new(),
function_symbols: HashMap::new(),
constructor_symbol: None,
}
}
@ -48,6 +51,14 @@ impl ClassScope {
&mut self.function_symbols
}
pub fn constructor_symbol(&self) -> Option<&Rc<RefCell<ConstructorSymbol>>> {
self.constructor_symbol.as_ref()
}
pub fn constructor_symbol_mut(&mut self) -> &mut Option<Rc<RefCell<ConstructorSymbol>>> {
&mut self.constructor_symbol
}
pub fn parent_id(&self) -> usize {
self.parent_id
}

View File

@ -0,0 +1,38 @@
use crate::symbol::class_symbol::ClassSymbol;
use crate::symbol::function_symbol::FunctionSymbol;
use crate::symbol::parameter_symbol::ParameterSymbol;
use crate::type_info::TypeInfo;
use std::cell::RefCell;
use std::rc::Rc;
pub enum CallableSymbol {
Function(Rc<RefCell<FunctionSymbol>>),
Class(Rc<RefCell<ClassSymbol>>),
}
impl CallableSymbol {
pub fn return_type_info(&self) -> TypeInfo {
match self {
CallableSymbol::Function(function) => function.borrow().return_type_info().clone(),
CallableSymbol::Class(class_symbol) => {
// At the language level, constructors "return" an instance of their type
TypeInfo::ClassInstance(class_symbol.clone())
}
}
}
pub fn parameters(&self) -> Vec<Rc<RefCell<ParameterSymbol>>> {
match self {
CallableSymbol::Function(function_symbol) => {
function_symbol.borrow().parameters().to_vec()
}
CallableSymbol::Class(class_symbol) => {
if let Some(constructor_symbol) = class_symbol.borrow().constructor_symbol() {
constructor_symbol.borrow().parameters().to_vec()
} else {
vec![]
}
}
}
}
}

View File

@ -1,5 +1,6 @@
use crate::source_range::SourceRange;
use crate::symbol::Symbol;
use crate::symbol::constructor_symbol::ConstructorSymbol;
use crate::symbol::field_symbol::FieldSymbol;
use crate::symbol::function_symbol::FunctionSymbol;
use std::cell::RefCell;
@ -10,6 +11,7 @@ pub struct ClassSymbol {
declared_name: Rc<str>,
declared_name_source_range: SourceRange,
is_extern: bool,
constructor_symbol: Option<Rc<RefCell<ConstructorSymbol>>>,
fields: HashMap<Rc<str>, Rc<RefCell<FieldSymbol>>>,
functions: HashMap<Rc<str>, Rc<RefCell<FunctionSymbol>>>,
}
@ -24,10 +26,27 @@ impl ClassSymbol {
declared_name: declared_name.clone(),
declared_name_source_range,
is_extern,
constructor_symbol: None,
fields: HashMap::new(),
functions: HashMap::new(),
}
}
pub fn set_constructor_symbol(
&mut self,
constructor_symbol: Option<Rc<RefCell<ConstructorSymbol>>>,
) {
self.constructor_symbol = constructor_symbol;
}
pub fn fields(&self) -> &HashMap<Rc<str>, Rc<RefCell<FieldSymbol>>> {
&self.fields
}
/// A value of `None` indicates that there is no declared constructor for this class.
pub fn constructor_symbol(&self) -> Option<&Rc<RefCell<ConstructorSymbol>>> {
self.constructor_symbol.as_ref()
}
}
impl Symbol for ClassSymbol {

View File

@ -0,0 +1,47 @@
use crate::source_range::SourceRange;
use crate::symbol::Symbol;
use crate::symbol::parameter_symbol::ParameterSymbol;
use std::cell::RefCell;
use std::rc::Rc;
pub struct ConstructorSymbol {
ctor_keyword_source_range: SourceRange,
is_extern: bool,
parameters: Option<Vec<Rc<RefCell<ParameterSymbol>>>>,
}
impl ConstructorSymbol {
pub fn new(ctor_keyword_source_range: SourceRange, is_extern: bool) -> Self {
Self {
ctor_keyword_source_range,
is_extern,
parameters: None,
}
}
pub fn set_parameters(&mut self, parameters: Vec<Rc<RefCell<ParameterSymbol>>>) {
self.parameters = Some(parameters);
}
pub fn parameters(&self) -> &[Rc<RefCell<ParameterSymbol>>] {
self.parameters.as_ref().unwrap()
}
pub fn is_extern(&self) -> bool {
self.is_extern
}
}
impl Symbol for ConstructorSymbol {
fn declared_name(&self) -> &str {
"ctor"
}
fn declared_name_owned(&self) -> Rc<str> {
Rc::from(self.declared_name())
}
fn declared_name_source_range(&self) -> &SourceRange {
&self.ctor_keyword_source_range
}
}

View File

@ -19,12 +19,8 @@ pub enum ExpressibleSymbol {
impl ExpressibleSymbol {
pub fn type_info(&self) -> TypeInfo {
match self {
ExpressibleSymbol::Class(class_symbol) => {
todo!()
}
ExpressibleSymbol::Field(field_symbol) => {
todo!()
}
ExpressibleSymbol::Class(class_symbol) => TypeInfo::ClassInstance(class_symbol.clone()),
ExpressibleSymbol::Field(field_symbol) => field_symbol.borrow().type_info().clone(),
ExpressibleSymbol::Function(function_symbol) => {
TypeInfo::Function(function_symbol.clone()) // n.b. not the return type!
}
@ -43,6 +39,8 @@ impl ExpressibleSymbol {
todo!()
}
ExpressibleSymbol::Field(field_symbol) => {
// this, unfortunately, is going to need more context, because fields live longer
// than function calls
todo!()
}
ExpressibleSymbol::Function(_) => {

View File

@ -7,6 +7,7 @@ pub struct FieldSymbol {
declared_name: Rc<str>,
declared_name_source_range: SourceRange,
type_info: Option<TypeInfo>,
field_index: Option<usize>,
}
impl FieldSymbol {
@ -15,8 +16,25 @@ impl FieldSymbol {
declared_name: declared_name.clone(),
declared_name_source_range,
type_info: None,
field_index: None,
}
}
pub fn set_type_info(&mut self, type_info: TypeInfo) {
self.type_info = Some(type_info);
}
pub fn type_info(&self) -> &TypeInfo {
self.type_info.as_ref().unwrap()
}
pub fn set_field_index(&mut self, field_index: usize) {
self.field_index = Some(field_index);
}
pub fn field_index(&self) -> usize {
self.field_index.unwrap()
}
}
impl Symbol for FieldSymbol {

View File

@ -1,7 +1,9 @@
use crate::source_range::SourceRange;
use std::rc::Rc;
pub mod callable_symbol;
pub mod class_symbol;
pub mod constructor_symbol;
pub mod expressible_symbol;
pub mod field_symbol;
pub mod function_symbol;

View File

@ -7,6 +7,7 @@ use crate::scope::function_scope::FunctionScope;
use crate::scope::module_scope::ModuleScope;
use crate::symbol::Symbol;
use crate::symbol::class_symbol::ClassSymbol;
use crate::symbol::constructor_symbol::ConstructorSymbol;
use crate::symbol::expressible_symbol::ExpressibleSymbol;
use crate::symbol::field_symbol::FieldSymbol;
use crate::symbol::function_symbol::FunctionSymbol;
@ -123,6 +124,32 @@ impl SymbolTable {
Ok(to_return)
}
pub fn insert_constructor_symbol(
&mut self,
constructor_symbol: ConstructorSymbol,
) -> Result<Rc<RefCell<ConstructorSymbol>>, SymbolInsertError> {
let maybe_already_inserted = match self.current_scope() {
Scope::Class(class_scope) => class_scope.constructor_symbol(),
_ => panic!("Attempt to insert ConstructorSymbol in incompatible scope"),
};
if let Some(already_inserted) = maybe_already_inserted {
return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new(
already_inserted.clone() as Rc<RefCell<dyn Symbol>>,
)));
}
let as_rc = Rc::new(RefCell::new(constructor_symbol));
let to_return = as_rc.clone();
match self.current_scope_mut() {
Scope::Class(class_scope) => {
class_scope.constructor_symbol_mut().replace(as_rc);
}
_ => unreachable!(),
}
Ok(to_return)
}
pub fn insert_field_symbol(
&mut self,
field_symbol: FieldSymbol,

View File

@ -47,4 +47,5 @@ pub enum TokenKind {
SelfKw,
Public,
Mut,
Ctor,
}

View File

@ -12,7 +12,7 @@ pub enum TypeInfo {
Double,
String,
Function(Rc<RefCell<FunctionSymbol>>),
Class(Rc<RefCell<ClassSymbol>>),
ClassInstance(Rc<RefCell<ClassSymbol>>),
Void,
}
@ -30,7 +30,7 @@ impl Display for TypeInfo {
}
write!(f, ")")
}
TypeInfo::Class(class_symbol) => {
TypeInfo::ClassInstance(class_symbol) => {
write!(f, "{}", class_symbol.borrow().declared_name())
}
TypeInfo::Void => write!(f, "Void"),
@ -67,9 +67,9 @@ impl TypeInfo {
TypeInfo::Function(_) => {
unimplemented!("Type matching on Functions not yet supported.")
}
TypeInfo::Class(class_symbol) => {
TypeInfo::ClassInstance(class_symbol) => {
match other {
TypeInfo::Class(other_class_symbol) => {
TypeInfo::ClassInstance(other_class_symbol) => {
class_symbol.borrow().declared_name()
== other_class_symbol.borrow().declared_name() // good enough for now
}

148
doc/deimos.asciidoc Normal file
View File

@ -0,0 +1,148 @@
= Deimos
Jesse Brault <jesse@jessebrault.com>
0.1.0
:toc:
== Language
=== Records
[source]
----
pub record Foo(bar: Int, baz: String) end
fn main()
let foo = Foo(1, "Hello")
println(foo.bar()) // 1
println(foo.baz()) // Hello
let foo2 = Foo(1, "Hello")
println(foo == foo2) // true
end
----
Records are essentially classes whose data members are all shallowly immutable. The data members of the constructor are
always inherently public; it is a syntax error to include the `pub` keyword.
Records inherently have methods named after the data fields to get the fields' values. It is an error to redeclare a
data field's method. Additionally, records inherently implement `Eq` and `Hash`, but each only if the constituent
members also implement those traits. *Records that do _not_ automatically implement `Eq` _and_ `Hash` must manually
implement the missing traits; it is an error to not do so.*
==== DVM/Performance Considerations
Records are interned and compared based on their data content, much like Strings (however, while Strings are primitive,
records are not). References to the record's shared data are cloned in the VM for all same-valued instances. To
illustrate:
[source]
----
class Cat(pub sound: String) end
record Dog(sound: String) end
fn main()
let cats: List<Cat> = []
for i in 0..100_000 do
cats << Cat("meow") // creates a new Cat object in memory each iteration
end
let dogs: List<Dog> = []
for i in 0..100_000 do
dogs << Dog("meow") // creates one Dog object in memory on first iteration,
// with 100_000 references stored in dogs list
end
end
----
==== Engineering Considerations
Records should be preferred against classes in most cases where the data of the class is all immutable, as this saves
memory and should result in faster execution times for data-heavy workloads.
==== Extern Records
Extern records signify native (Rust or otherwise) objects. They are declared with the `extern` keyword. However, unlike
plain Deimos records, all data-access methods, as well as the `Eq` and `Hash` traits' methods, must be implemented
natively, as the Deimos compiler has no method of knowing how to actually access the inner data.
[source]
----
extern record GameCharacter(name: String) end
----
== Compile Error Explanations
=== Not more than one constructor
Classes may have up to only one constructor. The following does not compile:
[source]
----
class Foo
ctor(bar: Int) end
ctor(baz: String) end // Error
end
----
=== Field must be initialized
All class fields must be initialized in their declaration, or in the constructor. It
is an error to leave a field uninitialized.
[source]
----
class Foo
bar: Int // Error: no initializer
end
class Bar
baz: Int // Error: no initializer
ctor()
// some other stuff
end
end
----
=== Use of field before initialization
A field _without_ a declared initializer (i.e., one present at its declaration, not the constructor) may not be read in
the initializer of any other field or constructor variable, as the language does not guarantee a specific ordering of
field initialization and cannot statically analyze field/variable dependencies.
[source]
----
class Foo
bar: Int
baz = bar // Error
end
class Bar
foo: Int
baz = fnWithArg(foo) // Error
end
class Baz
foo: Int
qux: String
ctor(baz: String)
qux = foo + baz // Error: attempt to read uninitialized foo
end
end
----
The exception to this is reading the field inside another field's initializer *only* when that expression is itself a
closure.
[source]
----
class Foo
bar: Int
baz = { bar } // OK
ctor(bar: Int)
self.bar = bar
end
end
----

View File

@ -7,6 +7,8 @@ pub type ConstantName = Rc<str>;
pub type FunctionName = Rc<str>;
pub type ArgCount = usize;
pub type CallerPopCount = usize;
pub type ClassFqn = Rc<str>;
pub type FieldIndex = usize;
pub enum Instruction {
Move(MoveOperand, Location),
@ -15,6 +17,10 @@ pub enum Instruction {
InvokeStatic(FunctionName, ArgCount),
InvokePlatformStatic(FunctionName, ArgCount),
Allocate(ClassFqn, Location),
GetField(Location, FieldIndex, Location),
SetField(Location, FieldIndex, SetFieldOperand),
Add(AddOperand, AddOperand, Location),
Subtract(SubtractOperand, SubtractOperand, Location),
Multiply(MultiplyOperand, MultiplyOperand, Location),
@ -64,6 +70,15 @@ impl Display for Instruction {
Instruction::Return => {
write!(f, "ret")
}
Instruction::Allocate(class_fqn, location) => {
write!(f, "alloc {}, {}", class_fqn, location)
}
Instruction::GetField(source, field_index, destination) => {
write!(f, "getf {}.{}, {}", source, field_index, destination)
}
Instruction::SetField(target_location, field_index, operand) => {
write!(f, "setf {}.{}, {}", target_location, field_index, operand)
}
}
}
}
@ -237,3 +252,29 @@ impl Display for ReturnOperand {
}
}
}
pub enum SetFieldOperand {
Location(Location),
Int(i32),
Double(f64),
String(Rc<str>),
}
impl Display for SetFieldOperand {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SetFieldOperand::Location(location) => {
write!(f, "{}", location)
}
SetFieldOperand::Int(i) => {
write!(f, "{}", i)
}
SetFieldOperand::Double(d) => {
write!(f, "{}", d)
}
SetFieldOperand::String(s) => {
write!(f, "{}", s)
}
}
}
}

View File

@ -318,6 +318,29 @@ pub fn call<'a>(
call_stack.top_mut().increment_ip();
}
/* Object instructions */
Instruction::Allocate(class_fqn, destination) => {
// let class = context.classes().get(class_fqn).expect("No such class loaded: {}")
// let object: Gc<GcCell<Object>> = Object::new(class, heap)
// let value = Value::Object(object)
// put_value(registers, call_stack.top_mut(), destination, result)
todo!()
}
Instruction::GetField(source, field_index, destination) => {
// let object = load_value(...).unwrap_object();
// let field_value = object.fields()[field_index];
// put_value(..., destination, field_value.clone());
todo!()
}
Instruction::SetField(target, field_index, operand) => {
// let new_value = to_value(operand);
// let object = load_value(... target).unwrap_object();
// object.fields_mut()[field_index] = new_value;
todo!()
}
/* Add instructions */
Instruction::Add(left_operand, right_operand, destination) => {
let left_value = add_operand_to_value(

20
examples/basic_classes.dm Normal file
View File

@ -0,0 +1,20 @@
class Foo
mut bar: Int = 42
ctor(_bar: Int)
end
fn baz() -> Int
bar
end
end
class Qux
fn foo() -> Foo
Foo(42)
end
end
fn main()
let crash = Qux()
end