Making semantic analysis more flexible so we can have a repl again soon.

This commit is contained in:
Jesse Brault 2026-07-10 22:15:51 -05:00
parent 9b2e952bf8
commit ed6f15a8a9
16 changed files with 512 additions and 230 deletions

View File

@ -1,17 +1,18 @@
use dmc_lib::ast::ir_builder::IrBuilder;
use dmc_lib::ast::expression_statement::ExpressionStatement;
use dmc_lib::ast::statement::Statement;
use dmc_lib::compile_statement_to_synthetic_function;
use dmc_lib::constants_table::ConstantsTable;
use dmc_lib::diagnostic::Diagnostic;
use dmc_lib::ir::ir_function::IrFunction;
use dmc_lib::ir::ir_return::IrReturn;
use dmc_lib::ir::ir_statement::IrStatement;
use dmc_lib::diagnostic::{Diagnostic, Diagnostics};
use dmc_lib::ir::ir_variable::IrVariable;
use dmc_lib::ir::variable_locations::VariableLocations;
use dmc_lib::lexer::Lexer;
use dmc_lib::offset_counter::OffsetCounter;
use dmc_lib::parser::{parse_expression, parse_let_statement};
use dmc_lib::parser::parse_expression;
use dmc_lib::semantic_analysis::AnalysisContext;
use dmc_lib::semantic_analysis::scope::ScopeId;
use dmc_lib::symbol::variable_symbol::VariableSymbol;
use dmc_lib::symbol_table::SymbolTable;
use dmc_lib::token::TokenKind;
use dmc_lib::type_info::TypeInfo;
use dmc_lib::types_table::TypesTable;
use dvm_lib::vm::constant::{Constant, StringConstant};
use dvm_lib::vm::function::Function;
@ -304,3 +305,69 @@ fn compile_let_statement(
// Ok(ir_function.assemble(offset_counter.get_count(), constants_table))
todo!()
}
pub fn repl_2(read: &mut impl BufRead, register_count: usize) {
let mut buffer = String::new();
let mut analysis_context = AnalysisContext::new();
let mut function_scope_id: Option<ScopeId> = None;
let fqn: Rc<str> = Rc::from("repl");
let mut variable_locations = VariableLocations::new();
let mut constants_table = ConstantsTable::new();
let mut dvm_context = DvmContext::new();
let mut repl_fn_stack_locals: Vec<Operand> = Vec::new();
'repl: loop {
print!("> ");
io::stdout().flush().unwrap();
read.read_line(&mut buffer).unwrap();
let input = buffer.trim();
if input.is_empty() {
buffer.clear();
continue;
}
let mut lexer = Lexer::new(input);
let first_token = match lexer.next() {
None => {
continue;
}
Some(result) => match result {
Ok(first_token) => first_token,
Err(lexer_error) => {
eprintln!("{:?}", lexer_error);
buffer.clear();
continue;
}
},
};
}
}
fn compile_expression_2(
input: &str,
analysis_context: &mut AnalysisContext,
function_scope_id: ScopeId,
fqn: &Rc<str>,
variable_locations: &mut VariableLocations,
register_count: usize,
constants_table: &mut ConstantsTable,
) -> Result<Function, Diagnostics> {
let (expression, parse_diagnostics) = parse_expression(input);
if !parse_diagnostics.is_empty() {
return Err(parse_diagnostics);
}
let statement = Statement::Expression(ExpressionStatement::new(0, expression));
compile_statement_to_synthetic_function(
&statement,
analysis_context,
function_scope_id,
fqn.clone(),
variable_locations,
register_count,
constants_table,
)
}

View File

@ -6,6 +6,7 @@ use dm_std_lib::add_all_std_core;
use dmc_lib::compile_compilation_unit;
use dmc_lib::constants_table::ConstantsTable;
use dmc_lib::diagnostic::{Diagnostic, Diagnostics};
use dmc_lib::semantic_analysis::AnalysisContext;
use dvm_lib::vm::constant::{Constant, StringConstant};
use dvm_lib::vm::{DvmContext, call};
use std::path::PathBuf;
@ -35,9 +36,14 @@ fn run(
show_asm: bool,
register_count: usize,
) -> Result<(), Diagnostics> {
let mut analysis_context = AnalysisContext::new();
let mut constants_table = ConstantsTable::new();
let compile_compilation_unit_result =
compile_compilation_unit(input, register_count, &mut constants_table)?;
let compile_compilation_unit_result = compile_compilation_unit(
input,
&mut analysis_context,
register_count,
&mut constants_table,
)?;
let mut dvm_context = DvmContext::new();

View File

@ -57,7 +57,7 @@ impl IrFunction {
#[deprecated]
pub fn assign_registers(&self, register_count: usize) -> VariableLocations {
if self.blocks.is_empty() {
return VariableLocations::new(HashMap::new(), HashMap::new());
return VariableLocations::new();
}
if self.blocks.len() > 1 {
unimplemented!("having more than one block in a function is not yet implemented.")

View File

@ -1,7 +1,8 @@
use crate::constants_table::ConstantsTable;
use crate::ir::assemble::assemble_ir_function;
use crate::ir::ir_function::IrFunction;
use crate::ir::register_allocation::assign_registers;
use crate::ir::register_allocation::{AssignRegistersResult, assign_registers};
use crate::ir::variable_locations::VariableLocations;
use dvm_lib::vm::function::Function;
mod assemble;
@ -27,14 +28,24 @@ pub mod ir_type_info;
pub mod ir_variable;
mod register_allocation;
mod util;
mod variable_locations;
pub mod variable_locations;
pub fn compile_dvm_function(
ir_function: &IrFunction,
register_count: usize,
variable_locations: &mut VariableLocations,
constants_table: &mut ConstantsTable,
) -> Function {
let variable_locations = assign_registers(ir_function, register_count);
let AssignRegistersResult {
register_variables,
spilled_variables,
} = assign_registers(ir_function, register_count);
variable_locations.push_all_register_variables(&register_variables);
for spilled_variable in spilled_variables {
variable_locations.push_stack_variable(spilled_variable);
}
let instructions = assemble_ir_function(ir_function, &variable_locations, constants_table);
Function::new(
ir_function.fqn().into(),

View File

@ -3,17 +3,24 @@
use crate::ir::ir_block::IrBlock;
use crate::ir::ir_function::IrFunction;
use crate::ir::ir_variable::IrVariableId;
use crate::ir::variable_locations::VariableLocations;
use dvm_lib::instruction::{Register, StackFrameOffset};
use dvm_lib::instruction::Register;
use std::collections::{HashMap, HashSet};
pub type RegisterAssignment = Register;
pub type InterferenceGraph = HashMap<IrVariableId, HashSet<IrVariableId>>;
pub type LivenessSets = Vec<HashSet<IrVariableId>>;
pub fn assign_registers(ir_function: &IrFunction, register_count: usize) -> VariableLocations {
pub struct AssignRegistersResult {
pub register_variables: HashMap<IrVariableId, RegisterAssignment>,
pub spilled_variables: HashSet<IrVariableId>,
}
pub fn assign_registers(ir_function: &IrFunction, register_count: usize) -> AssignRegistersResult {
if ir_function.blocks().is_empty() {
return VariableLocations::new(HashMap::new(), HashMap::new());
return AssignRegistersResult {
register_variables: HashMap::new(),
spilled_variables: HashSet::new(),
};
}
if ir_function.blocks().len() > 1 {
unimplemented!("having more than one block in a function is not yet implemented.")
@ -32,7 +39,7 @@ fn block_live_in_live_out(ir_block: &IrBlock) -> (LivenessSets, LivenessSets) {
let mut did_work = false;
// Go backwards for efficiency
for (statement_index, ir_statement) in ir_block.statements().iter().enumerate().rev() {
// out (union of successors ins)
// out (union of successors' ins)
// for now, a statement can only have one successor
// this will need to be updated when we add jumps
let statement_live_out = &mut live_out[statement_index];
@ -120,7 +127,7 @@ fn block_interference_graph(
graph
}
fn block_assign_registers(ir_block: &IrBlock, register_count: usize) -> VariableLocations {
fn block_assign_registers(ir_block: &IrBlock, register_count: usize) -> AssignRegistersResult {
let mut spilled: HashSet<IrVariableId> = HashSet::new();
loop {
let mut interference_graph = block_interference_graph(ir_block, &spilled);
@ -130,14 +137,10 @@ fn block_assign_registers(ir_block: &IrBlock, register_count: usize) -> Variable
spilled.extend(new_spills);
// check if added zero spills to get to fixed point
if old_n_spilled == spilled.len() {
let mut spills_to_stack_offsets: HashMap<IrVariableId, StackFrameOffset> =
HashMap::new();
let mut offset_counter = 0isize;
for spill in spilled {
spills_to_stack_offsets.insert(spill, offset_counter);
offset_counter += 1;
}
return VariableLocations::new(registers, spills_to_stack_offsets);
return AssignRegistersResult {
register_variables: registers,
spilled_variables: spilled,
};
}
}
}
@ -182,7 +185,7 @@ fn registers_and_spills(
assign_register(&work_item, &mut rebuilt_graph, k, &mut register_assignments);
} else {
// spill
spills.insert(work_item.vr.clone());
spills.insert(work_item.vr);
}
}
@ -320,8 +323,9 @@ fn can_optimistically_color(
mod tests {
use super::*;
use crate::diagnostic::Diagnostics;
use crate::lowering::lower_to_ir;
use crate::lowering::lower_to_ir_compilation_unit;
use crate::parser::get_compilation_unit;
use crate::semantic_analysis::AnalysisContext;
use crate::semantic_analysis::analyze_compilation_unit;
fn line_graph() -> InterferenceGraph {
@ -452,23 +456,20 @@ mod tests {
None,
)?;
let (analysis_result, diagnostics) = analyze_compilation_unit(&compilation_unit);
let mut analysis_context = AnalysisContext::new();
let diagnostics = analyze_compilation_unit(&compilation_unit, &mut analysis_context);
assert!(diagnostics.is_empty());
let lower_result = lower_to_ir(
&compilation_unit,
&analysis_result.symbols,
&analysis_result.nodes_to_symbols,
&analysis_result.type_infos,
&analysis_result.symbols_to_type_infos,
&analysis_result.nodes_to_type_infos,
); // todo: make this API friendlier
let lower_result = lower_to_ir_compilation_unit(&compilation_unit, &analysis_context);
assert_eq!(lower_result.functions.len(), 1);
let main_fn = &lower_result.functions[0];
let variable_locations = assign_registers(main_fn, 2);
assert_eq!(variable_locations.register_variables_count(), 4);
let AssignRegistersResult {
register_variables,
spilled_variables: _,
} = assign_registers(main_fn, 2);
assert_eq!(register_variables.len(), 4);
Ok(())
}

View File

@ -12,19 +12,25 @@ pub enum VariableLocation {
pub struct VariableLocations {
register_variables: HashMap<IrVariableId, RegisterAssignment>,
stack_variables: HashMap<IrVariableId, StackVariableOffset>,
next_stack_variable_offset: StackVariableOffset,
}
impl VariableLocations {
pub fn new(
register_variables: HashMap<IrVariableId, RegisterAssignment>,
stack_variables: HashMap<IrVariableId, StackVariableOffset>,
) -> Self {
pub fn new() -> Self {
Self {
register_variables,
stack_variables,
register_variables: HashMap::new(),
stack_variables: HashMap::new(),
next_stack_variable_offset: 0,
}
}
pub fn push_all_register_variables(
&mut self,
register_variables: &HashMap<IrVariableId, RegisterAssignment>,
) {
self.register_variables.extend(register_variables);
}
pub fn get_variable_location(&self, id: &IrVariableId) -> VariableLocation {
match self.register_variables.get(id) {
Some(register_assignment) => VariableLocation::Register(*register_assignment),
@ -39,4 +45,18 @@ impl VariableLocations {
pub fn stack_variables_count(&self) -> usize {
self.stack_variables.len()
}
pub fn extend(&mut self, other: &VariableLocations) {
// todo: add logic to transpose the `other`'s stack assignments on top of the current ones.
self.register_variables
.extend(other.register_variables.clone());
self.stack_variables.extend(other.stack_variables.clone());
}
pub fn push_stack_variable(&mut self, ir_variable_id: IrVariableId) {
self.stack_variables
.insert(ir_variable_id, self.next_stack_variable_offset);
self.next_stack_variable_offset += 1;
}
}

View File

@ -1,10 +1,14 @@
use crate::ast::statement::Statement;
use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostics;
use crate::ir::compile_dvm_function;
use crate::lowering::lower_to_ir;
use crate::ir::variable_locations::VariableLocations;
use crate::lowering::{lower_to_ir_compilation_unit, lower_to_ir_synthetic_function};
use crate::parser::parse_compilation_unit;
use crate::semantic_analysis::analyze_compilation_unit;
use crate::semantic_analysis::scope::ScopeId;
use crate::semantic_analysis::{analyze_compilation_unit, analyze_statement};
use dvm_lib::vm::function::Function;
use semantic_analysis::AnalysisContext;
use std::collections::HashMap;
use std::rc::Rc;
@ -39,6 +43,7 @@ pub struct CompileCompilationUnitResult {
pub fn compile_compilation_unit(
input: &str,
analysis_context: &mut AnalysisContext,
register_count: usize,
constants_table: &mut ConstantsTable,
) -> Result<CompileCompilationUnitResult, Diagnostics> {
@ -47,24 +52,22 @@ pub fn compile_compilation_unit(
return Err(diagnostics);
}
let (analysis_result, diagnostics) = analyze_compilation_unit(&compilation_unit);
let diagnostics = analyze_compilation_unit(&compilation_unit, analysis_context);
if !diagnostics.is_empty() {
return Err(diagnostics);
}
let lower_to_ir_result = lower_to_ir(
&compilation_unit,
&analysis_result.symbols,
&analysis_result.nodes_to_symbols,
&analysis_result.type_infos,
&analysis_result.symbols_to_type_infos,
&analysis_result.nodes_to_type_infos,
);
let lower_to_ir_result = lower_to_ir_compilation_unit(&compilation_unit, analysis_context);
let mut dvm_functions = HashMap::new();
for ir_function in &lower_to_ir_result.functions {
let dvm_function = compile_dvm_function(ir_function, register_count, constants_table);
let dvm_function = compile_dvm_function(
ir_function,
register_count,
&mut VariableLocations::new(),
constants_table,
);
dvm_functions.insert(dvm_function.name_owned(), dvm_function);
}
@ -72,3 +75,25 @@ pub fn compile_compilation_unit(
functions: dvm_functions,
})
}
pub fn compile_statement_to_synthetic_function(
statement: &Statement,
analysis_context: &mut AnalysisContext,
function_scope_id: ScopeId,
fqn: Rc<str>,
variable_locations: &mut VariableLocations,
register_count: usize,
constants_table: &mut ConstantsTable,
) -> Result<Function, Diagnostics> {
let diagnostics = analyze_statement(statement, analysis_context, function_scope_id);
if !diagnostics.is_empty() {
return Err(diagnostics);
}
let ir_function = lower_to_ir_synthetic_function(statement, analysis_context, fqn);
Ok(compile_dvm_function(
&ir_function,
register_count,
variable_locations,
constants_table,
))
}

View File

@ -23,10 +23,12 @@ use crate::ir::ir_statement::IrStatement;
use crate::ir::ir_type_info::IrTypeInfo;
use crate::ir::ir_variable::{IrVariable, IrVariableId};
use crate::lowering::util::{return_type_info_to_ir_type_info, to_ir_type_info};
use crate::semantic_analysis::AnalysisContext;
use crate::semantic_analysis::symbol::{Symbol, SymbolId};
use crate::semantic_analysis::type_info::{TypeInfo, TypeInfoId};
use std::collections::HashMap;
use std::ops::Neg;
use std::rc::Rc;
pub struct LowerToIrResult {
pub functions: Vec<IrFunction>,
@ -41,20 +43,16 @@ struct LowerToIrContext<'a> {
ir_functions: Vec<IrFunction>,
}
pub fn lower_to_ir(
pub fn lower_to_ir_compilation_unit(
compilation_unit: &CompilationUnit,
symbols: &[Symbol],
nodes_to_symbols: &HashMap<NodeId, SymbolId>,
type_infos: &[TypeInfo],
symbols_to_type_infos: &HashMap<SymbolId, TypeInfoId>,
nodes_to_type_infos: &HashMap<NodeId, TypeInfoId>,
analysis_context: &AnalysisContext,
) -> LowerToIrResult {
let mut ctx = LowerToIrContext {
symbols,
nodes_to_symbols,
type_infos,
symbols_to_type_infos,
nodes_to_type_infos,
symbols: analysis_context.symbols(),
nodes_to_symbols: analysis_context.nodes_to_symbols(),
type_infos: analysis_context.type_infos(),
symbols_to_type_infos: analysis_context.symbols_to_type_infos(),
nodes_to_type_infos: analysis_context.nodes_to_type_infos(),
ir_functions: Vec::new(),
};
@ -67,6 +65,45 @@ pub fn lower_to_ir(
}
}
pub fn lower_to_ir_synthetic_function(
statement: &Statement,
analysis_ctx: &AnalysisContext,
fqn: Rc<str>,
) -> IrFunction {
let mut ctx = LowerToIrContext {
symbols: analysis_ctx.symbols(),
nodes_to_symbols: analysis_ctx.nodes_to_symbols(),
type_infos: analysis_ctx.type_infos(),
symbols_to_type_infos: analysis_ctx.symbols_to_type_infos(),
nodes_to_type_infos: analysis_ctx.nodes_to_type_infos(),
ir_functions: Vec::new(),
};
let mut fn_ctx = LowerToIrFunctionContext::new();
lower_to_ir_statement(statement, &mut ctx, &mut fn_ctx, true);
fn_ctx.finish_block();
// infer return type from statement
let maybe_return_ir_type_info = match statement {
Statement::Let(_) | Statement::Assign(_) => None,
Statement::Expression(expression_statement) => {
let type_info_id =
ctx.nodes_to_type_infos[&expression_statement.expression().node_id()];
let type_info = &ctx.type_infos[type_info_id];
return_type_info_to_ir_type_info(type_info)
}
};
IrFunction::new(
fqn,
fn_ctx.ir_parameters,
fn_ctx.ir_variables,
maybe_return_ir_type_info,
fn_ctx.blocks,
)
}
struct LowerToIrFunctionContext {
blocks: Vec<IrBlock>,
ir_variables: Vec<IrVariable>,
@ -145,37 +182,30 @@ fn lower_to_ir_function(function: &Function, ctx: &mut LowerToIrContext) {
lower_to_ir_parameters(function, ctx, &mut fn_ctx);
let function_symbol_id = ctx.nodes_to_symbols[&function.node_id()];
let function_type_info_id = ctx.symbols_to_type_infos[&function_symbol_id];
let function_type_info = match &ctx.type_infos[function_type_info_id] {
TypeInfo::Function(function_type_info) => function_type_info,
_ => panic!("Expected FunctionTypeInfo"),
};
let is_void_function = match ctx.type_infos[function_type_info.return_type_id()] {
TypeInfo::Void => true,
_ => false,
};
let n_statements = function.statements().len();
for (i, statement) in function.statements().iter().enumerate() {
let can_return_value = i == n_statements - 1 && !is_void_function;
lower_to_ir_statement(statement, ctx, &mut fn_ctx, can_return_value);
}
fn_ctx.finish_block();
// get various function info
let function_symbol_id = ctx.nodes_to_symbols[&function.node_id()];
let function_symbol = match &ctx.symbols[function_symbol_id] {
Symbol::Function(function_symbol) => function_symbol,
_ => panic!("Expected function symbol"),
_ => panic!("Expected FunctionSymbol"),
};
let function_type_info_id = ctx.symbols_to_type_infos[&function_symbol_id];
let function_type_info = match &ctx.type_infos[function_type_info_id] {
TypeInfo::Function(function_type_info) => function_type_info,
_ => panic!("Expected FunctionTypeInfo"),
};
let return_type_info = &ctx.type_infos[function_type_info.return_type_id()];
let is_void_function = match return_type_info {
TypeInfo::Void => true,
_ => false,
};
// lower function body
let n_statements = function.statements().len();
for (i, statement) in function.statements().iter().enumerate() {
let can_return_value = i == n_statements - 1 && !is_void_function;
lower_to_ir_statement(statement, ctx, &mut fn_ctx, can_return_value);
}
fn_ctx.finish_block();
let ir_function = IrFunction::new(
function_symbol.fqn_owned(),

View File

@ -14,34 +14,37 @@ use crate::ast::statement::Statement;
use crate::semantic_analysis::scope::{Scope, ScopeId};
use std::collections::HashMap;
pub struct ScopeCollectionResult {
pub scopes: Vec<Scope>,
pub nodes_to_scopes: HashMap<NodeId, ScopeId>,
}
struct ScopeCollectionContext {
scopes: Vec<Scope>,
struct ScopeCollectionContext<'a> {
scopes: &'a mut Vec<Scope>,
nodes_to_scopes: &'a mut HashMap<NodeId, ScopeId>,
current_scope_id: Option<ScopeId>,
nodes_to_scopes: HashMap<NodeId, ScopeId>,
}
impl ScopeCollectionContext {
pub fn new() -> Self {
impl<'a> ScopeCollectionContext<'a> {
pub fn new(
scopes: &'a mut Vec<Scope>,
nodes_to_scopes: &'a mut HashMap<NodeId, ScopeId>,
current_scope_id: ScopeId,
) -> Self {
Self {
scopes: Vec::new(),
current_scope_id: None,
nodes_to_scopes: HashMap::new(),
scopes,
nodes_to_scopes,
current_scope_id: Some(current_scope_id),
}
}
pub fn push_scope(&mut self, scope: Scope) -> ScopeId {
pub fn down_scope(&mut self) -> ScopeId {
let scope = Scope::new(self.current_scope_id); // current is parent before push
self.scopes.push(scope);
self.current_scope_id = Some(self.scopes.len() - 1);
self.current_scope_id.unwrap() // guaranteed because we just set it
self.current_scope_id.unwrap()
}
pub fn pop_scope(&mut self) {
self.current_scope_id = self.scopes[self.current_scope_id.unwrap()].parent_id();
pub fn up_scope(&mut self) {
let current_scope_id = self
.current_scope_id
.expect("Cannot up_scope() when there is no current_scope");
self.current_scope_id = self.scopes[current_scope_id].parent_id();
}
pub fn current_scope_id(&self) -> Option<ScopeId> {
@ -53,20 +56,35 @@ impl ScopeCollectionContext {
}
}
pub fn collect_scopes(compilation_unit: &CompilationUnit) -> ScopeCollectionResult {
let mut ctx = ScopeCollectionContext::new();
ctx.push_scope(Scope::new(None));
/// N.b.: the parent_scope_id refers to the **parent** of the compilation unit scope, and must
/// be a valid (non-panicking) index of `scopes`.
pub fn collect_scopes_in_compilation_unit(
compilation_unit: &CompilationUnit,
scopes: &mut Vec<Scope>,
nodes_to_scopes: &mut HashMap<NodeId, ScopeId>,
parent_scope_id: ScopeId,
) {
let mut ctx = ScopeCollectionContext::new(scopes, nodes_to_scopes, parent_scope_id);
ctx.down_scope(); // compilation unit scope
for function in compilation_unit.functions() {
collect_scopes_function(function, &mut ctx);
}
for extern_function in compilation_unit.extern_functions() {
collect_scopes_extern_function(extern_function, &mut ctx);
}
ctx.pop_scope();
ScopeCollectionResult {
scopes: ctx.scopes,
nodes_to_scopes: ctx.nodes_to_scopes,
}
ctx.up_scope(); // compilation unit scope
}
/// N.b. the parent_scope_id refers to the scope of the statement and must be a valid
/// (non-panicking) index of `scopes`.
pub fn collect_scopes_in_statement(
statement: &Statement,
scopes: &mut Vec<Scope>,
nodes_to_scopes: &mut HashMap<NodeId, ScopeId>,
parent_scope_id: ScopeId,
) {
let mut ctx = ScopeCollectionContext::new(scopes, nodes_to_scopes, parent_scope_id);
collect_scopes_statement(statement, &mut ctx);
}
fn collect_scopes_function(function: &Function, ctx: &mut ScopeCollectionContext) {
@ -78,8 +96,7 @@ fn collect_scopes_function(function: &Function, ctx: &mut ScopeCollectionContext
.insert(function.node_id(), containing_scope_id);
// push function scope
let function_scope = Scope::new(Some(containing_scope_id));
let function_scope_id = ctx.push_scope(function_scope);
let function_scope_id = ctx.down_scope();
// save return-type and parameters' scope id
for parameter in function.parameters() {
@ -97,15 +114,14 @@ fn collect_scopes_function(function: &Function, ctx: &mut ScopeCollectionContext
}
// push block scope for body
let block_scope = Scope::new(Some(function_scope_id));
ctx.push_scope(block_scope);
ctx.down_scope();
for statement in function.statements() {
collect_scopes_statement(statement, ctx);
}
ctx.pop_scope(); // block
ctx.pop_scope(); // function
ctx.up_scope(); // block
ctx.up_scope(); // function
}
fn collect_scopes_extern_function(
@ -117,8 +133,7 @@ fn collect_scopes_extern_function(
ctx.nodes_to_scopes_mut()
.insert(extern_function.node_id(), containing_scope_id);
let function_scope = Scope::new(Some(containing_scope_id));
let function_scope_id = ctx.push_scope(function_scope);
let function_scope_id = ctx.down_scope();
for parameter in extern_function.parameters() {
ctx.nodes_to_scopes_mut()
@ -127,7 +142,7 @@ fn collect_scopes_extern_function(
ctx.nodes_to_scopes_mut()
.insert(extern_function.return_type().node_id(), function_scope_id);
ctx.pop_scope();
ctx.up_scope();
}
fn collect_scopes_statement(statement: &Statement, ctx: &mut ScopeCollectionContext) {

View File

@ -3,6 +3,7 @@ use crate::ast::compilation_unit::CompilationUnit;
use crate::ast::extern_function::ExternFunction;
use crate::ast::function::Function;
use crate::ast::parameter::Parameter;
use crate::ast::statement::Statement;
use crate::diagnostic::Diagnostics;
use crate::semantic_analysis::diagnostic_helpers::symbol_already_declared;
use crate::semantic_analysis::scope::{Scope, ScopeId};
@ -10,26 +11,27 @@ use crate::semantic_analysis::symbol::{FunctionSymbol, ParameterSymbol, Symbol};
use std::collections::HashMap;
use std::rc::Rc;
pub struct SymbolCollectionResult {
pub symbols: Vec<Symbol>,
pub diagnostics: Diagnostics,
}
pub struct SymbolCollectionResult(pub Diagnostics);
struct SymbolCollectionContext<'a> {
scopes: &'a mut Vec<Scope>,
nodes_to_scopes: &'a HashMap<NodeId, ScopeId>,
symbols: &'a mut Vec<Symbol>,
fqn_context: Vec<Rc<str>>,
symbols: Vec<Symbol>,
diagnostics: Diagnostics,
}
impl<'a> SymbolCollectionContext<'a> {
pub fn new(scopes: &'a mut Vec<Scope>, nodes_to_scopes: &'a HashMap<NodeId, ScopeId>) -> Self {
pub fn new(
scopes: &'a mut Vec<Scope>,
nodes_to_scopes: &'a HashMap<NodeId, ScopeId>,
symbols: &'a mut Vec<Symbol>,
) -> Self {
Self {
scopes,
nodes_to_scopes,
symbols,
fqn_context: Vec::new(),
symbols: Vec::new(),
diagnostics: Diagnostics::new(),
}
}
@ -79,12 +81,13 @@ impl<'a> SymbolCollectionContext<'a> {
}
}
pub fn collect_symbols(
pub fn collect_symbols_in_compilation_unit(
compilation_unit: &CompilationUnit,
scopes: &mut Vec<Scope>,
nodes_to_scopes: &HashMap<NodeId, ScopeId>,
symbols: &mut Vec<Symbol>,
) -> SymbolCollectionResult {
let mut ctx = SymbolCollectionContext::new(scopes, nodes_to_scopes);
let mut ctx = SymbolCollectionContext::new(scopes, nodes_to_scopes, symbols);
for function in compilation_unit.functions() {
collect_symbols_function(function, &mut ctx);
@ -94,10 +97,7 @@ pub fn collect_symbols(
collect_symbols_extern_function(extern_function, &mut ctx);
}
SymbolCollectionResult {
symbols: ctx.symbols,
diagnostics: ctx.diagnostics,
}
SymbolCollectionResult(ctx.diagnostics)
}
fn collect_symbols_function(function: &Function, ctx: &mut SymbolCollectionContext) {

View File

@ -7,23 +7,22 @@ use crate::semantic_analysis::symbol::SymbolId;
use crate::semantic_analysis::type_info::{FunctionTypeInfo, TypeInfo, TypeInfoId};
use std::collections::HashMap;
pub struct CollectTypesResult {
pub type_infos: Vec<TypeInfo>,
pub symbols_to_type_infos: HashMap<SymbolId, TypeInfoId>,
}
struct CollectTypesContext<'a> {
nodes_to_symbols: &'a HashMap<NodeId, SymbolId>,
type_infos: Vec<TypeInfo>,
symbols_to_type_infos: HashMap<SymbolId, TypeInfoId>,
type_infos: &'a mut Vec<TypeInfo>,
symbols_to_type_infos: &'a mut HashMap<SymbolId, TypeInfoId>,
}
impl<'a> CollectTypesContext<'a> {
pub fn new(nodes_to_symbols: &'a HashMap<NodeId, SymbolId>) -> Self {
pub fn new(
nodes_to_symbols: &'a HashMap<NodeId, SymbolId>,
type_infos: &'a mut Vec<TypeInfo>,
symbols_to_type_infos: &'a mut HashMap<SymbolId, TypeInfoId>,
) -> Self {
Self {
nodes_to_symbols,
type_infos: Vec::new(),
symbols_to_type_infos: HashMap::new(),
type_infos,
symbols_to_type_infos,
}
}
@ -36,8 +35,10 @@ impl<'a> CollectTypesContext<'a> {
pub fn collect_types(
compilation_unit: &CompilationUnit,
nodes_to_symbols: &HashMap<NodeId, SymbolId>,
) -> CollectTypesResult {
let mut ctx = CollectTypesContext::new(nodes_to_symbols);
type_infos: &mut Vec<TypeInfo>,
symbols_to_type_infos: &mut HashMap<SymbolId, TypeInfoId>,
) {
let mut ctx = CollectTypesContext::new(nodes_to_symbols, type_infos, symbols_to_type_infos);
for function in compilation_unit.functions() {
collect_types_function(function, &mut ctx);
}
@ -45,11 +46,6 @@ pub fn collect_types(
for extern_function in compilation_unit.extern_functions() {
collect_types_extern_function(extern_function, &mut ctx);
}
CollectTypesResult {
type_infos: ctx.type_infos,
symbols_to_type_infos: ctx.symbols_to_type_infos,
}
}
fn collect_types_function(function: &Function, ctx: &mut CollectTypesContext) {

View File

@ -1,11 +1,21 @@
use crate::ast::NodeId;
use crate::ast::compilation_unit::CompilationUnit;
use crate::ast::statement::Statement;
use crate::diagnostic::Diagnostics;
use crate::semantic_analysis::collect_scopes::collect_scopes;
use crate::semantic_analysis::collect_symbols::collect_symbols;
use crate::semantic_analysis::collect_scopes::{
collect_scopes_in_compilation_unit, collect_scopes_in_statement,
};
use crate::semantic_analysis::collect_symbols::{
SymbolCollectionResult, collect_symbols_in_compilation_unit,
};
use crate::semantic_analysis::collect_types::collect_types;
use crate::semantic_analysis::resolve_names::resolve_names;
use crate::semantic_analysis::resolve_types::resolve_types;
use crate::semantic_analysis::resolve_names::{
NameResolutionResult, resolve_names_in_compilation_unit, resolve_names_in_statement,
};
use crate::semantic_analysis::resolve_types::{
ResolveTypesResult, resolve_types_in_compilation_unit, resolve_types_in_statement,
};
use crate::semantic_analysis::scope::{Scope, ScopeId};
use crate::semantic_analysis::symbol::{Symbol, SymbolId};
use crate::semantic_analysis::type_info::{TypeInfo, TypeInfoId};
use std::collections::HashMap;
@ -16,59 +26,141 @@ mod collect_types;
mod diagnostic_helpers;
mod resolve_names;
mod resolve_types;
mod scope;
mod semantic_context;
pub mod scope;
pub mod symbol;
pub mod type_info;
pub struct AnalysisResult {
pub symbols: Vec<Symbol>,
pub nodes_to_symbols: HashMap<NodeId, SymbolId>,
pub type_infos: Vec<TypeInfo>,
pub symbols_to_type_infos: HashMap<SymbolId, TypeInfoId>,
pub nodes_to_type_infos: HashMap<NodeId, TypeInfoId>,
pub struct AnalysisContext {
root_scope_id: ScopeId,
scopes: Vec<Scope>,
nodes_to_scopes: HashMap<NodeId, ScopeId>,
symbols: Vec<Symbol>,
nodes_to_symbols: HashMap<NodeId, SymbolId>,
type_infos: Vec<TypeInfo>,
symbols_to_type_infos: HashMap<SymbolId, TypeInfoId>,
nodes_to_type_infos: HashMap<NodeId, TypeInfoId>,
}
impl AnalysisContext {
pub fn new() -> Self {
Self {
scopes: vec![Scope::new(None)],
nodes_to_scopes: HashMap::new(),
root_scope_id: 0, // already pushed in vec! above
symbols: Vec::new(),
nodes_to_symbols: HashMap::new(),
type_infos: Vec::new(),
symbols_to_type_infos: HashMap::new(),
nodes_to_type_infos: HashMap::new(),
}
}
pub fn symbols(&self) -> &[Symbol] {
&self.symbols
}
pub fn nodes_to_symbols(&self) -> &HashMap<NodeId, SymbolId> {
&self.nodes_to_symbols
}
pub fn type_infos(&self) -> &[TypeInfo] {
&self.type_infos
}
pub fn symbols_to_type_infos(&self) -> &HashMap<SymbolId, TypeInfoId> {
&self.symbols_to_type_infos
}
pub fn nodes_to_type_infos(&self) -> &HashMap<NodeId, TypeInfoId> {
&self.nodes_to_type_infos
}
}
pub fn analyze_compilation_unit(
compilation_unit: &CompilationUnit,
) -> (AnalysisResult, Diagnostics) {
ctx: &mut AnalysisContext,
) -> Diagnostics {
let mut diagnostics = Diagnostics::new();
let mut scopes_result = collect_scopes(compilation_unit);
let mut symbols_result = collect_symbols(
collect_scopes_in_compilation_unit(
compilation_unit,
&mut scopes_result.scopes,
&scopes_result.nodes_to_scopes,
&mut ctx.scopes,
&mut ctx.nodes_to_scopes,
ctx.root_scope_id,
);
diagnostics.append(&mut symbols_result.diagnostics);
let mut names_result = resolve_names(
let SymbolCollectionResult(mut collect_symbols_diagnostics) =
collect_symbols_in_compilation_unit(
compilation_unit,
&mut ctx.scopes,
&ctx.nodes_to_scopes,
&mut ctx.symbols,
);
diagnostics.append(&mut collect_symbols_diagnostics);
let NameResolutionResult(mut resolve_names_diagnostics) = resolve_names_in_compilation_unit(
compilation_unit,
&mut scopes_result.scopes,
&mut symbols_result.symbols,
&scopes_result.nodes_to_scopes,
&mut ctx.scopes,
&mut ctx.symbols,
&mut ctx.nodes_to_scopes,
&mut ctx.nodes_to_symbols,
);
diagnostics.append(&mut names_result.diagnostics);
diagnostics.append(&mut resolve_names_diagnostics);
let mut collect_types_result = collect_types(compilation_unit, &names_result.nodes_to_symbols);
let mut resolve_types_result = resolve_types(
collect_types(
compilation_unit,
&names_result.nodes_to_symbols,
&mut collect_types_result.type_infos,
&mut collect_types_result.symbols_to_type_infos,
&ctx.nodes_to_symbols,
&mut ctx.type_infos,
&mut ctx.symbols_to_type_infos,
);
diagnostics.append(&mut resolve_types_result.diagnostics);
(
AnalysisResult {
symbols: symbols_result.symbols,
nodes_to_symbols: names_result.nodes_to_symbols,
type_infos: collect_types_result.type_infos,
symbols_to_type_infos: collect_types_result.symbols_to_type_infos,
nodes_to_type_infos: resolve_types_result.nodes_to_type_infos,
},
diagnostics,
)
let ResolveTypesResult(mut resolve_types_diagnostics) = resolve_types_in_compilation_unit(
compilation_unit,
&ctx.nodes_to_symbols,
&mut ctx.type_infos,
&mut ctx.symbols_to_type_infos,
&mut ctx.nodes_to_type_infos,
);
diagnostics.append(&mut resolve_types_diagnostics);
diagnostics
}
pub fn analyze_statement(
statement: &Statement,
ctx: &mut AnalysisContext,
function_scope_id: ScopeId,
) -> Diagnostics {
let mut diagnostics = Diagnostics::new();
collect_scopes_in_statement(
statement,
&mut ctx.scopes,
&mut ctx.nodes_to_scopes,
function_scope_id,
);
// collect symbols for a statement is currently a no-op, so not needed here
let NameResolutionResult(mut resolve_names_diagnostics) = resolve_names_in_statement(
statement,
&mut ctx.scopes,
&mut ctx.symbols,
&mut ctx.nodes_to_scopes,
&mut ctx.nodes_to_symbols,
);
diagnostics.append(&mut resolve_names_diagnostics);
// collect types for a statement is currently a no-op, so not needed here
let ResolveTypesResult(mut resolve_types_diagnostics) = resolve_types_in_statement(
statement,
&ctx.nodes_to_symbols,
&mut ctx.type_infos,
&mut ctx.symbols_to_type_infos,
&mut ctx.nodes_to_type_infos,
);
diagnostics.append(&mut resolve_types_diagnostics);
diagnostics
}

View File

@ -18,16 +18,13 @@ use crate::semantic_analysis::scope::{Scope, ScopeId};
use crate::semantic_analysis::symbol::{Symbol, SymbolId, VariableSymbol};
use std::collections::HashMap;
pub struct NameResolutionResult {
pub nodes_to_symbols: HashMap<NodeId, SymbolId>,
pub diagnostics: Diagnostics,
}
pub struct NameResolutionResult(pub Diagnostics);
struct NameResolutionContext<'a> {
scopes: &'a mut Vec<Scope>,
symbols: &'a mut Vec<Symbol>,
nodes_to_scopes: &'a HashMap<NodeId, ScopeId>,
nodes_to_symbols: HashMap<NodeId, SymbolId>,
nodes_to_symbols: &'a mut HashMap<NodeId, SymbolId>,
diagnostics: Diagnostics,
}
@ -36,12 +33,13 @@ impl<'a> NameResolutionContext<'a> {
scopes: &'a mut Vec<Scope>,
symbols: &'a mut Vec<Symbol>,
nodes_to_scopes: &'a HashMap<NodeId, ScopeId>,
nodes_to_symbols: &'a mut HashMap<NodeId, SymbolId>,
) -> Self {
Self {
scopes,
symbols,
nodes_to_scopes,
nodes_to_symbols: HashMap::new(),
nodes_to_symbols,
diagnostics: Diagnostics::new(),
}
}
@ -88,13 +86,14 @@ enum ExpressionResolutionPhase {
Static,
}
pub fn resolve_names(
pub fn resolve_names_in_compilation_unit(
compilation_unit: &CompilationUnit,
scopes: &mut Vec<Scope>,
symbols: &mut Vec<Symbol>,
nodes_to_scopes: &HashMap<NodeId, ScopeId>,
nodes_to_symbols: &mut HashMap<NodeId, SymbolId>,
) -> NameResolutionResult {
let mut ctx = NameResolutionContext::new(scopes, symbols, nodes_to_scopes);
let mut ctx = NameResolutionContext::new(scopes, symbols, nodes_to_scopes, nodes_to_symbols);
for function in compilation_unit.functions() {
resolve_names_function(function, &mut ctx);
@ -104,10 +103,19 @@ pub fn resolve_names(
resolve_names_extern_function(extern_function, &mut ctx);
}
NameResolutionResult {
nodes_to_symbols: ctx.nodes_to_symbols,
diagnostics: ctx.diagnostics,
}
NameResolutionResult(ctx.diagnostics)
}
pub fn resolve_names_in_statement(
statement: &Statement,
scopes: &mut Vec<Scope>,
symbols: &mut Vec<Symbol>,
nodes_to_scopes: &HashMap<NodeId, ScopeId>,
nodes_to_symbols: &mut HashMap<NodeId, SymbolId>,
) -> NameResolutionResult {
let mut ctx = NameResolutionContext::new(scopes, symbols, nodes_to_scopes, nodes_to_symbols);
resolve_names_statement(statement, &mut ctx, StatementResolutionPhase::Static);
NameResolutionResult(ctx.diagnostics)
}
fn resolve_names_function(function: &Function, ctx: &mut NameResolutionContext) {

View File

@ -21,16 +21,13 @@ use crate::semantic_analysis::symbol::SymbolId;
use crate::semantic_analysis::type_info::{TypeInfo, TypeInfoId};
use std::collections::HashMap;
pub struct ResolveTypesResult {
pub nodes_to_type_infos: HashMap<NodeId, TypeInfoId>,
pub diagnostics: Diagnostics,
}
pub struct ResolveTypesResult(pub Diagnostics);
struct ResolveTypesContext<'a> {
nodes_to_symbols: &'a HashMap<NodeId, SymbolId>,
type_infos: &'a mut Vec<TypeInfo>,
symbols_to_type_infos: &'a mut HashMap<SymbolId, TypeInfoId>,
nodes_to_type_infos: HashMap<NodeId, TypeInfoId>,
nodes_to_type_infos: &'a mut HashMap<NodeId, TypeInfoId>,
diagnostics: Diagnostics,
}
@ -39,32 +36,54 @@ impl<'a> ResolveTypesContext<'a> {
nodes_to_symbols: &'a HashMap<NodeId, SymbolId>,
type_infos: &'a mut Vec<TypeInfo>,
symbols_to_type_infos: &'a mut HashMap<SymbolId, TypeInfoId>,
nodes_to_type_infos: &'a mut HashMap<NodeId, TypeInfoId>,
) -> Self {
Self {
nodes_to_symbols,
type_infos,
symbols_to_type_infos,
nodes_to_type_infos: HashMap::new(),
nodes_to_type_infos,
diagnostics: Diagnostics::new(),
}
}
}
pub fn resolve_types(
pub fn resolve_types_in_compilation_unit(
compilation_unit: &CompilationUnit,
nodes_to_symbols: &HashMap<NodeId, SymbolId>,
type_infos: &mut Vec<TypeInfo>,
symbols_to_type_infos: &mut HashMap<SymbolId, TypeInfoId>,
nodes_to_type_infos: &mut HashMap<NodeId, TypeInfoId>,
) -> ResolveTypesResult {
let mut ctx = ResolveTypesContext::new(nodes_to_symbols, type_infos, symbols_to_type_infos);
let mut ctx = ResolveTypesContext::new(
nodes_to_symbols,
type_infos,
symbols_to_type_infos,
nodes_to_type_infos,
);
for function in compilation_unit.functions() {
resolve_types_function(function, &mut ctx);
}
ResolveTypesResult {
nodes_to_type_infos: ctx.nodes_to_type_infos,
diagnostics: ctx.diagnostics,
}
ResolveTypesResult(ctx.diagnostics)
}
pub fn resolve_types_in_statement(
statement: &Statement,
nodes_to_symbols: &HashMap<NodeId, SymbolId>,
type_infos: &mut Vec<TypeInfo>,
symbols_to_type_infos: &mut HashMap<SymbolId, TypeInfoId>,
nodes_to_type_infos: &mut HashMap<NodeId, TypeInfoId>,
) -> ResolveTypesResult {
let mut ctx = ResolveTypesContext::new(
nodes_to_symbols,
type_infos,
symbols_to_type_infos,
nodes_to_type_infos,
);
resolve_types_statement(statement, &mut ctx);
ResolveTypesResult(ctx.diagnostics)
}
fn resolve_types_function(function: &Function, ctx: &mut ResolveTypesContext) {

View File

@ -1,10 +0,0 @@
use crate::ast::NodeId;
use crate::semantic_analysis::scope::{Scope, ScopeId};
use std::collections::HashMap;
pub struct SemanticContext {
scopes: Vec<Scope>,
node_scopes: HashMap<NodeId, ScopeId>,
}
impl SemanticContext {}

View File

@ -3,6 +3,7 @@ mod e2e_tests {
use dmc_lib::compile_compilation_unit;
use dmc_lib::constants_table::ConstantsTable;
use dmc_lib::diagnostic::{Diagnostic, Diagnostics};
use dmc_lib::semantic_analysis::AnalysisContext;
use dvm_lib::vm::constant::{Constant, StringConstant};
use dvm_lib::vm::value::Value;
use dvm_lib::vm::{DvmContext, call};
@ -23,10 +24,11 @@ mod e2e_tests {
}
fn prepare_context(input: &str) -> Result<DvmContext, Diagnostics> {
let mut ctx = AnalysisContext::new();
let mut constants_table = ConstantsTable::new();
let compile_compilation_unit_result =
compile_compilation_unit(input, REGISTER_COUNT, &mut constants_table);
compile_compilation_unit(input, &mut ctx, REGISTER_COUNT, &mut constants_table);
let mut dvm_context = DvmContext::new();