From 12ae935f5076733a1d4633fea1b31d8e8d950359 Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 29 Nov 2024 15:48:50 -0500 Subject: [PATCH] Clean up chunk --- dust-lang/src/chunk.rs | 146 +------------ dust-lang/src/compiler.rs | 304 +++++++++++++++------------ dust-lang/src/instruction/mod.rs | 4 +- dust-lang/src/lib.rs | 2 +- dust-lang/src/native_function/mod.rs | 20 +- dust-lang/src/optimize.rs | 29 ++- dust-lang/src/vm.rs | 48 +++-- 7 files changed, 232 insertions(+), 321 deletions(-) diff --git a/dust-lang/src/chunk.rs b/dust-lang/src/chunk.rs index d8217fb..2b643d1 100644 --- a/dust-lang/src/chunk.rs +++ b/dust-lang/src/chunk.rs @@ -4,12 +4,11 @@ //! list of locals that can be executed by the Dust virtual machine. Chunks have a name when they //! belong to a named function. -use std::fmt::{self, Debug, Display, Formatter, Write}; -use std::hash::{Hash, Hasher}; +use std::fmt::{self, Debug, Display, Write}; use serde::{Deserialize, Serialize}; -use crate::{ConcreteValue, Disassembler, FunctionType, Instruction, Operation, Scope, Span, Type}; +use crate::{ConcreteValue, Disassembler, FunctionType, Instruction, Scope, Span, Type}; /// In-memory representation of a Dust program or function. /// @@ -59,18 +58,10 @@ impl Chunk { self.name.as_ref() } - pub fn set_name(&mut self, name: String) { - self.name = Some(name); - } - pub fn r#type(&self) -> &FunctionType { &self.r#type } - pub fn set_type(&mut self, r#type: FunctionType) { - self.r#type = r#type; - } - pub fn len(&self) -> usize { self.instructions.len() } @@ -83,113 +74,14 @@ impl Chunk { &self.constants } - pub fn constants_mut(&mut self) -> &mut Vec { - &mut self.constants - } - - pub fn get_constant(&self, index: u16) -> Result<&ConcreteValue, ChunkError> { - self.constants - .get(index as usize) - .ok_or(ChunkError::ConstantIndexOutOfBounds { - index: index as usize, - }) - } - - pub fn push_or_get_constant(&mut self, value: ConcreteValue) -> u16 { - if let Some(index) = self - .constants - .iter() - .position(|constant| *constant == value) - { - index as u16 - } else { - let index = self.constants.len() as u16; - - self.constants.push(value); - - index - } - } - pub fn instructions(&self) -> &Vec<(Instruction, Type, Span)> { &self.instructions } - pub fn instructions_mut(&mut self) -> &mut Vec<(Instruction, Type, Span)> { - &mut self.instructions - } - - pub fn get_instruction(&self, index: usize) -> Result<&(Instruction, Type, Span), ChunkError> { - self.instructions - .get(index) - .ok_or(ChunkError::InstructionIndexOutOfBounds { index }) - } - - pub fn get_last_operations(&self) -> Option<[Operation; COUNT]> { - let mut n_operations = [Operation::Return; COUNT]; - - for (nth, operation) in n_operations.iter_mut().rev().zip( - self.instructions - .iter() - .rev() - .map(|(instruction, _, _)| instruction.operation()), - ) { - *nth = operation; - } - - Some(n_operations) - } - pub fn locals(&self) -> &Vec { &self.locals } - pub fn locals_mut(&mut self) -> &mut Vec { - &mut self.locals - } - - pub fn get_local(&self, index: u16) -> Result<&Local, ChunkError> { - self.locals - .get(index as usize) - .ok_or(ChunkError::LocalIndexOutOfBounds { - index: index as usize, - }) - } - - pub fn get_local_mut(&mut self, index: u16) -> Result<&mut Local, ChunkError> { - self.locals - .get_mut(index as usize) - .ok_or(ChunkError::LocalIndexOutOfBounds { - index: index as usize, - }) - } - - pub fn get_identifier(&self, local_index: u16) -> Option { - self.locals.get(local_index as usize).and_then(|local| { - self.constants - .get(local.identifier_index as usize) - .map(|value| value.to_string()) - }) - } - - pub fn get_constant_type(&self, constant_index: u16) -> Result { - self.constants - .get(constant_index as usize) - .map(|value| value.r#type()) - .ok_or(ChunkError::ConstantIndexOutOfBounds { - index: constant_index as usize, - }) - } - - pub fn get_local_type(&self, local_index: u16) -> Result<&Type, ChunkError> { - self.locals - .get(local_index as usize) - .map(|local| &local.r#type) - .ok_or(ChunkError::LocalIndexOutOfBounds { - index: local_index as usize, - }) - } - pub fn disassembler(&self) -> Disassembler { Disassembler::new(self) } @@ -252,37 +144,3 @@ impl Local { } } } - -impl Hash for Local { - fn hash(&self, state: &mut H) { - self.identifier_index.hash(state); - } -} - -/// Errors that can occur when using a [`Chunk`]. -#[derive(Clone, Debug, PartialEq)] -pub enum ChunkError { - ConstantIndexOutOfBounds { index: usize }, - FunctionIndexOutOfBounds { index: usize }, - InstructionIndexOutOfBounds { index: usize }, - LocalIndexOutOfBounds { index: usize }, -} - -impl Display for ChunkError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - ChunkError::ConstantIndexOutOfBounds { index } => { - write!(f, "Constant index {} out of bounds", index) - } - ChunkError::FunctionIndexOutOfBounds { index } => { - write!(f, "Function index {} out of bounds", index) - } - ChunkError::InstructionIndexOutOfBounds { index } => { - write!(f, "Instruction index {} out of bounds", index) - } - ChunkError::LocalIndexOutOfBounds { index } => { - write!(f, "Local index {} out of bounds", index) - } - } - } -} diff --git a/dust-lang/src/compiler.rs b/dust-lang/src/compiler.rs index 81863d0..e6dadd9 100644 --- a/dust-lang/src/compiler.rs +++ b/dust-lang/src/compiler.rs @@ -19,9 +19,9 @@ use crate::{ }, optimize_control_flow, optimize_set_local, value::ConcreteValue, - AnnotatedError, Argument, Chunk, ChunkError, Destination, DustError, FunctionType, Instruction, - LexError, Lexer, Local, NativeFunction, Operation, Scope, Span, Token, TokenKind, TokenOwned, - Type, TypeConflict, + AnnotatedError, Argument, Chunk, Destination, DustError, FunctionType, Instruction, LexError, + Lexer, Local, NativeFunction, Operation, Scope, Span, Token, TokenKind, TokenOwned, Type, + TypeConflict, }; /// Compiles the input and returns a chunk. @@ -44,15 +44,19 @@ pub fn compile(source: &str) -> Result { .parse_top_level() .map_err(|error| DustError::Compile { error, source })?; - Ok(compiler.finish()) + Ok(compiler.finish(None, None)) } /// Low-level tool for compiling the input a token at a time while assembling a chunk. /// /// See the [`compile`] function an example of how to create and use a Compiler. -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug)] pub struct Compiler<'src> { - chunk: Chunk, + self_name: Option, + instructions: Vec<(Instruction, Type, Span)>, + constants: Vec, + locals: Vec, + lexer: Lexer<'src>, current_token: Token<'src>, @@ -69,7 +73,6 @@ pub struct Compiler<'src> { impl<'src> Compiler<'src> { pub fn new(mut lexer: Lexer<'src>) -> Result { let (current_token, current_position) = lexer.next_token()?; - let chunk = Chunk::new(None); log::info!( "Begin chunk with {} at {}", @@ -78,7 +81,10 @@ impl<'src> Compiler<'src> { ); Ok(Compiler { - chunk, + self_name: None, + instructions: Vec::new(), + constants: Vec::new(), + locals: Vec::new(), lexer, current_token, current_position, @@ -91,20 +97,26 @@ impl<'src> Compiler<'src> { }) } - pub fn finish(mut self) -> Chunk { + pub fn finish( + self, + type_parameters: Option>, + value_parameters: Option>, + ) -> Chunk { log::info!("End chunk"); - if let Type::None = *self.chunk.r#type().return_type { - self.chunk.set_type(FunctionType { - type_parameters: None, - value_parameters: None, - return_type: self - .return_type - .map_or_else(|| Box::new(Type::None), Box::new), - }); - } + let r#type = FunctionType { + type_parameters, + value_parameters, + return_type: Box::new(self.return_type.unwrap_or(Type::None)), + }; - self.chunk + Chunk::with_data( + self.self_name, + r#type, + self.instructions, + self.constants, + self.locals, + ) } fn is_eof(&self) -> bool { @@ -112,8 +124,7 @@ impl<'src> Compiler<'src> { } fn next_register(&self) -> u16 { - self.chunk - .instructions() + self.instructions .iter() .rev() .find_map(|(instruction, _, _)| { @@ -146,25 +157,21 @@ impl<'src> Compiler<'src> { } fn get_local(&self, index: u16) -> Result<&Local, CompileError> { - self.chunk - .get_local(index) - .map_err(|error| CompileError::Chunk { - error, + self.locals + .get(index as usize) + .ok_or(CompileError::UndeclaredVariable { + identifier: format!("#{}", index), position: self.current_position, }) } fn get_local_index(&self, identifier_text: &str) -> Result { - self.chunk - .locals() + self.locals .iter() .enumerate() .rev() .find_map(|(index, local)| { - let constant = self - .chunk - .constants() - .get(local.identifier_index as usize)?; + let constant = self.constants.get(local.identifier_index as usize)?; let identifier = if let ConcreteValue::String(identifier) = constant { identifier } else { @@ -193,16 +200,39 @@ impl<'src> Compiler<'src> { log::info!("Declaring local {identifier}"); let identifier = ConcreteValue::string(identifier); - let identifier_index = self.chunk.push_or_get_constant(identifier); - let local_index = self.chunk.locals().len() as u16; + let identifier_index = self.push_or_get_constant(identifier); + let local_index = self.locals.len() as u16; - self.chunk - .locals_mut() + self.locals .push(Local::new(identifier_index, r#type, is_mutable, scope)); (local_index, identifier_index) } + fn get_identifier(&self, local_index: u16) -> Option { + self.locals.get(local_index as usize).and_then(|local| { + self.constants + .get(local.identifier_index as usize) + .map(|value| value.to_string()) + }) + } + + fn push_or_get_constant(&mut self, value: ConcreteValue) -> u16 { + if let Some(index) = self + .constants + .iter() + .position(|constant| constant == &value) + { + index as u16 + } else { + let index = self.constants.len() as u16; + + self.constants.push(value); + + index + } + } + fn allow(&mut self, allowed: Token) -> Result { if self.current_token == allowed { self.advance()?; @@ -226,8 +256,7 @@ impl<'src> Compiler<'src> { } fn pop_last_instruction(&mut self) -> Result<(Instruction, Type, Span), CompileError> { - self.chunk - .instructions_mut() + self.instructions .pop() .ok_or_else(|| CompileError::ExpectedExpression { found: self.previous_token.to_owned(), @@ -235,13 +264,27 @@ impl<'src> Compiler<'src> { }) } + fn get_last_operations(&self) -> Option<[Operation; COUNT]> { + let mut n_operations = [Operation::Return; COUNT]; + + for (nth, operation) in n_operations.iter_mut().rev().zip( + self.instructions + .iter() + .rev() + .map(|(instruction, _, _)| instruction.operation()), + ) { + *nth = operation; + } + + Some(n_operations) + } + fn get_last_jumpable_mut_between( &mut self, minimum: usize, maximum: usize, ) -> Option<&mut Instruction> { - self.chunk - .instructions_mut() + self.instructions .iter_mut() .rev() .skip(minimum) @@ -256,32 +299,14 @@ impl<'src> Compiler<'src> { } fn get_last_instruction_type(&self) -> Type { - self.chunk - .instructions() + self.instructions .last() .map(|(_, r#type, _)| r#type.clone()) .unwrap_or(Type::None) } - pub fn get_argument_type(&self, argument: &Argument) -> Result { - let r#type = match argument { - Argument::Register(register_index) => self.get_register_type(*register_index)?, - Argument::Constant(constant_index) => self - .chunk - .get_constant(*constant_index) - .map_err(|error| CompileError::Chunk { - error, - position: self.current_position, - })? - .r#type(), - Argument::Local(local_index) => self.get_local(*local_index)?.r#type.clone(), - }; - - Ok(r#type) - } - - pub fn get_register_type(&self, register_index: u16) -> Result { - for (instruction, r#type, _) in self.chunk.instructions() { + fn get_register_type(&self, register_index: u16) -> Result { + for (instruction, r#type, _) in &self.instructions { if instruction.a() == register_index { if let Operation::LoadList = instruction.operation() { let LoadList { start_register, .. } = LoadList::from(instruction); @@ -336,9 +361,7 @@ impl<'src> Compiler<'src> { position.to_string() ); - self.chunk - .instructions_mut() - .push((instruction, r#type, position)); + self.instructions.push((instruction, r#type, position)); } fn emit_constant( @@ -347,7 +370,7 @@ impl<'src> Compiler<'src> { position: Span, ) -> Result<(), CompileError> { let r#type = constant.r#type(); - let constant_index = self.chunk.push_or_get_constant(constant); + let constant_index = self.push_or_get_constant(constant); let destination = Destination::Register(self.next_register()); let instruction = Instruction::from(LoadConstant { destination, @@ -520,7 +543,7 @@ impl<'src> Compiler<'src> { let (argument, push_back) = self.handle_binary_argument(&previous_instruction)?; if push_back { - self.chunk.instructions_mut().push(( + self.instructions.push(( previous_instruction, previous_type.clone(), previous_position, @@ -568,12 +591,12 @@ impl<'src> Compiler<'src> { fn parse_math_binary(&mut self) -> Result<(), CompileError> { let (left_instruction, left_type, left_position) = - self.chunk.instructions_mut().pop().ok_or_else(|| { - CompileError::ExpectedExpression { + self.instructions + .pop() + .ok_or_else(|| CompileError::ExpectedExpression { found: self.previous_token.to_owned(), position: self.previous_position, - } - })?; + })?; let (left, push_back_left) = self.handle_binary_argument(&left_instruction)?; let left_is_mutable_local = if let Argument::Local(local_index) = left { self.get_local(local_index)?.is_mutable @@ -601,8 +624,7 @@ impl<'src> Compiler<'src> { } if push_back_left { - self.chunk - .instructions_mut() + self.instructions .push((left_instruction, left_type, left_position)); } @@ -613,8 +635,7 @@ impl<'src> Compiler<'src> { let (right, push_back_right) = self.handle_binary_argument(&right_instruction)?; if push_back_right { - self.chunk - .instructions_mut() + self.instructions .push((right_instruction, right_type, right_position)); } @@ -660,7 +681,7 @@ impl<'src> Compiler<'src> { fn parse_comparison_binary(&mut self) -> Result<(), CompileError> { if let Some([Operation::Equal | Operation::Less | Operation::LessEqual, _, _]) = - self.chunk.get_last_operations() + self.get_last_operations() { return Err(CompileError::CannotChainComparison { position: self.current_position, @@ -668,20 +689,19 @@ impl<'src> Compiler<'src> { } let (left_instruction, left_type, left_position) = - self.chunk.instructions_mut().pop().ok_or_else(|| { - CompileError::ExpectedExpression { + self.instructions + .pop() + .ok_or_else(|| CompileError::ExpectedExpression { found: self.previous_token.to_owned(), position: self.previous_position, - } - })?; + })?; let (left, push_back_left) = self.handle_binary_argument(&left_instruction)?; let operator = self.current_token; let operator_position = self.current_position; let rule = ParseRule::from(&operator); if push_back_left { - self.chunk - .instructions_mut() + self.instructions .push((left_instruction, left_type, left_position)); } @@ -689,17 +709,16 @@ impl<'src> Compiler<'src> { self.parse_sub_expression(&rule.precedence)?; let (right_instruction, right_type, right_position) = - self.chunk.instructions_mut().pop().ok_or_else(|| { - CompileError::ExpectedExpression { + self.instructions + .pop() + .ok_or_else(|| CompileError::ExpectedExpression { found: self.previous_token.to_owned(), position: self.previous_position, - } - })?; + })?; let (right, push_back_right) = self.handle_binary_argument(&right_instruction)?; if push_back_right { - self.chunk - .instructions_mut() + self.instructions .push((right_instruction, right_type, right_position)); } @@ -762,11 +781,8 @@ impl<'src> Compiler<'src> { let (argument, push_back) = self.handle_binary_argument(&left_instruction)?; if push_back { - self.chunk.instructions_mut().push(( - left_instruction, - left_type.clone(), - left_position, - )); + self.instructions + .push((left_instruction, left_type.clone(), left_position)); } let operator = self.current_token; @@ -817,7 +833,7 @@ impl<'src> Compiler<'src> { local_index } else if let Some(native_function) = NativeFunction::from_str(identifier) { return self.parse_native_call(native_function); - } else if Some(identifier) == self.chunk.name().map(|string| string.as_str()) { + } else if self.self_name.as_deref() == Some(identifier) { let destination = Destination::Register(self.next_register()); let load_self = Instruction::from(LoadSelf { destination }); @@ -836,7 +852,7 @@ impl<'src> Compiler<'src> { if !self.current_scope.contains(&local.scope) { return Err(CompileError::VariableOutOfScope { - identifier: self.chunk.get_identifier(local_index).unwrap(), + identifier: self.get_identifier(local_index).unwrap(), position: start_position, variable_scope: local.scope, access_scope: self.current_scope, @@ -846,7 +862,7 @@ impl<'src> Compiler<'src> { if self.allow(Token::Equal)? { if !is_mutable { return Err(CompileError::CannotMutateImmutableVariable { - identifier: self.chunk.get_identifier(local_index).unwrap(), + identifier: self.get_identifier(local_index).unwrap(), position: start_position, }); } @@ -860,7 +876,7 @@ impl<'src> Compiler<'src> { }); self.emit_instruction(set_local, Type::None, start_position); - optimize_set_local(&mut self.chunk); + optimize_set_local(&mut self.instructions); return Ok(()); } @@ -961,7 +977,7 @@ impl<'src> Compiler<'src> { self.parse_expression()?; if matches!( - self.chunk.get_last_operations(), + self.get_last_operations(), Some([ Operation::Equal | Operation::Less | Operation::LessEqual, Operation::Jump, @@ -969,10 +985,10 @@ impl<'src> Compiler<'src> { Operation::LoadBoolean, ]) ) { - self.chunk.instructions_mut().pop(); - self.chunk.instructions_mut().pop(); - self.chunk.instructions_mut().pop(); - } else if let Some((instruction, _, _)) = self.chunk.instructions().last() { + self.instructions.pop(); + self.instructions.pop(); + self.instructions.pop(); + } else if let Some((instruction, _, _)) = self.instructions.last() { let test_register = instruction.a(); let test = Instruction::from(Test { argument: Argument::Register(test_register), @@ -982,7 +998,7 @@ impl<'src> Compiler<'src> { self.emit_instruction(test, Type::None, self.current_position) } - let if_block_start = self.chunk.len(); + let if_block_start = self.instructions.len(); let if_block_start_position = self.current_position; if let Token::LeftBrace = self.current_token { @@ -995,7 +1011,7 @@ impl<'src> Compiler<'src> { }); } - let if_block_end = self.chunk.len(); + let if_block_end = self.instructions.len(); let mut if_block_distance = (if_block_end - if_block_start) as u16; let if_block_type = self.get_last_instruction_type(); let if_last_register = self.next_register().saturating_sub(1); @@ -1022,7 +1038,7 @@ impl<'src> Compiler<'src> { false }; - let else_block_end = self.chunk.len(); + let else_block_end = self.instructions.len(); let else_block_distance = (else_block_end - if_block_end) as u16; let else_block_type = self.get_last_instruction_type(); @@ -1047,8 +1063,7 @@ impl<'src> Compiler<'src> { is_positive: true, }); - self.chunk - .instructions_mut() + self.instructions .insert(if_block_end, (jump, Type::None, self.current_position)); } } @@ -1059,8 +1074,7 @@ impl<'src> Compiler<'src> { is_positive: true, }); - self.chunk - .instructions_mut() + self.instructions .insert(if_block_end, (jump, Type::None, self.current_position)); } } @@ -1070,12 +1084,11 @@ impl<'src> Compiler<'src> { is_positive: true, }); - self.chunk - .instructions_mut() + self.instructions .insert(if_block_start, (jump, Type::None, if_block_start_position)); - if self.chunk.len() >= 4 { - optimize_control_flow(&mut self.chunk); + if self.instructions.len() >= 4 { + optimize_control_flow(&mut self.instructions); } let else_last_register = self.next_register().saturating_sub(1); @@ -1094,12 +1107,12 @@ impl<'src> Compiler<'src> { fn parse_while(&mut self) -> Result<(), CompileError> { self.advance()?; - let expression_start = self.chunk.len() as u16; + let expression_start = self.instructions.len() as u16; self.parse_expression()?; if matches!( - self.chunk.get_last_operations(), + self.get_last_operations(), Some([ Operation::Equal | Operation::Less | Operation::LessEqual, Operation::Jump, @@ -1107,24 +1120,23 @@ impl<'src> Compiler<'src> { Operation::LoadBoolean, ],) ) { - self.chunk.instructions_mut().pop(); - self.chunk.instructions_mut().pop(); - self.chunk.instructions_mut().pop(); + self.instructions.pop(); + self.instructions.pop(); + self.instructions.pop(); } - let block_start = self.chunk.len(); + let block_start = self.instructions.len(); self.parse_block()?; - let block_end = self.chunk.len() as u16; + let block_end = self.instructions.len() as u16; let jump_distance = block_end - block_start as u16 + 1; let jump = Instruction::from(Jump { offset: jump_distance, is_positive: true, }); - self.chunk - .instructions_mut() + self.instructions .insert(block_start, (jump, Type::None, self.current_position)); let jump_back_distance = block_end - expression_start + 1; @@ -1203,7 +1215,7 @@ impl<'src> Compiler<'src> { let expression_type = self.get_last_instruction_type(); - if expression_type == Type::None || self.chunk.is_empty() { + if expression_type == Type::None || self.instructions.is_empty() { return Err(CompileError::ExpectedExpression { found: self.previous_token.to_owned(), position: self.current_position, @@ -1322,7 +1334,8 @@ impl<'src> Compiler<'src> { let position = function_compiler.current_position; function_compiler.advance()?; - function_compiler.chunk.set_name(text.to_string()); + + function_compiler.self_name = Some(text.to_string()); Some((text, position)) } else { @@ -1388,13 +1401,8 @@ impl<'src> Compiler<'src> { } else { Box::new(Type::None) }; - let function_type = FunctionType { - type_parameters: None, - value_parameters, - return_type, - }; - function_compiler.chunk.set_type(function_type.clone()); + function_compiler.return_type = Some((*return_type).clone()); function_compiler.expect(Token::LeftBrace)?; function_compiler.parse_top_level()?; @@ -1404,10 +1412,16 @@ impl<'src> Compiler<'src> { self.current_token = function_compiler.current_token; self.current_position = function_compiler.current_position; - let function = ConcreteValue::Function(function_compiler.finish()); - let constant_index = self.chunk.push_or_get_constant(function); + let function = + ConcreteValue::Function(function_compiler.finish(None, value_parameters.clone())); + let constant_index = self.push_or_get_constant(function); let function_end = self.current_position.1; let register = self.next_register(); + let function_type = FunctionType { + type_parameters: None, + value_parameters, + return_type, + }; self.lexer.skip_to(function_end); @@ -1454,8 +1468,7 @@ impl<'src> Compiler<'src> { fn parse_call(&mut self) -> Result<(), CompileError> { let (last_instruction, _, _) = - self.chunk - .instructions() + self.instructions .last() .ok_or_else(|| CompileError::ExpectedExpression { found: self.previous_token.to_owned(), @@ -1478,7 +1491,7 @@ impl<'src> Compiler<'src> { let register_type = self.get_register_type(function.index())?; let function_return_type = match register_type { Type::Function(function_type) => *function_type.return_type, - Type::SelfChunk => (*self.chunk.r#type().return_type).clone(), + Type::SelfChunk => self.return_type.clone().unwrap_or(Type::None), _ => { return Err(CompileError::ExpectedFunction { found: self.previous_token.to_owned(), @@ -1977,11 +1990,21 @@ pub enum CompileError { position: Span, }, - // Wrappers around foreign errors - Chunk { - error: ChunkError, + // Chunk errors + ConstantIndexOutOfBounds { + index: usize, position: Span, }, + InstructionIndexOutOfBounds { + index: usize, + position: Span, + }, + LocalIndexOutOfBounds { + index: usize, + position: Span, + }, + + // Wrappers around foreign errors Lex(LexError), ParseFloatError { error: ParseFloatError, @@ -2004,7 +2027,7 @@ impl AnnotatedError for CompileError { Self::CannotMutateImmutableVariable { .. } => "Cannot mutate immutable variable", Self::CannotResolveRegisterType { .. } => "Cannot resolve register type", Self::CannotResolveVariableType { .. } => "Cannot resolve type", - Self::Chunk { .. } => "Chunk error", + Self::ConstantIndexOutOfBounds { .. } => "Constant index out of bounds", Self::ExpectedExpression { .. } => "Expected an expression", Self::ExpectedFunction { .. } => "Expected a function", Self::ExpectedFunctionType { .. } => "Expected a function type", @@ -2013,9 +2036,11 @@ impl AnnotatedError for CompileError { Self::ExpectedTokenMultiple { .. } => "Expected one of multiple tokens", Self::IfElseBranchMismatch { .. } => "Type mismatch in if/else branches", Self::IfMissingElse { .. } => "If statement missing else branch", + Self::InstructionIndexOutOfBounds { .. } => "Instruction index out of bounds", Self::InvalidAssignmentTarget { .. } => "Invalid assignment target", Self::Lex(error) => error.description(), Self::ListItemTypeConflict { .. } => "List item type conflict", + Self::LocalIndexOutOfBounds { .. } => "Local index out of bounds", Self::ParseFloatError { .. } => "Failed to parse float", Self::ParseIntError { .. } => "Failed to parse integer", Self::ReturnTypeConflict { .. } => "Return type conflict", @@ -2030,7 +2055,6 @@ impl AnnotatedError for CompileError { Self::CannotMutateImmutableVariable { identifier, .. } => { Some(format!("{identifier} is immutable")) } - Self::Chunk { error, .. } => Some(error.to_string()), Self::ExpectedExpression { found, .. } => Some(format!("Found {found}")), Self::ExpectedFunction { found, actual_type, .. } => { Some(format!("Expected \"{found}\" to be a function but it has type {actual_type}")) @@ -2102,7 +2126,7 @@ impl AnnotatedError for CompileError { Self::CannotMutateImmutableVariable { position, .. } => *position, Self::CannotResolveRegisterType { position, .. } => *position, Self::CannotResolveVariableType { position, .. } => *position, - Self::Chunk { position, .. } => *position, + Self::ConstantIndexOutOfBounds { position, .. } => *position, Self::ExpectedExpression { position, .. } => *position, Self::ExpectedFunction { position, .. } => *position, Self::ExpectedFunctionType { position, .. } => *position, @@ -2111,9 +2135,11 @@ impl AnnotatedError for CompileError { Self::ExpectedTokenMultiple { position, .. } => *position, Self::IfElseBranchMismatch { position, .. } => *position, Self::IfMissingElse { position } => *position, + Self::InstructionIndexOutOfBounds { position, .. } => *position, Self::InvalidAssignmentTarget { position, .. } => *position, Self::Lex(error) => error.position(), Self::ListItemTypeConflict { position, .. } => *position, + Self::LocalIndexOutOfBounds { position, .. } => *position, Self::ParseFloatError { position, .. } => *position, Self::ParseIntError { position, .. } => *position, Self::ReturnTypeConflict { position, .. } => *position, diff --git a/dust-lang/src/instruction/mod.rs b/dust-lang/src/instruction/mod.rs index d55c13b..e7c03f6 100644 --- a/dust-lang/src/instruction/mod.rs +++ b/dust-lang/src/instruction/mod.rs @@ -20,7 +20,7 @@ //! - Use the associated function on `Instruction` //! - Use the corresponding struct and call `Instruction::from` //! -//! # Example +//! # Examples //! //! ``` //! # use dust_lang::instruction::{Instruction, Move}; @@ -30,8 +30,6 @@ //! assert_eq!(move_1, move_2); //! ``` //! -//! # Example -//! //! Use the `Destination` and `Argument` enums to create instructions. This is easier to read and //! enforces consistency in how the `Instruction` methods are called. //! diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 64377bd..55a2cb6 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -15,7 +15,7 @@ pub mod r#type; pub mod value; pub mod vm; -pub use crate::chunk::{Chunk, ChunkError, Local}; +pub use crate::chunk::{Chunk, Local}; pub use crate::compiler::{compile, CompileError, Compiler}; pub use crate::disassembler::Disassembler; pub use crate::dust_error::{AnnotatedError, DustError}; diff --git a/dust-lang/src/native_function/mod.rs b/dust-lang/src/native_function/mod.rs index 5386996..2ba0aef 100644 --- a/dust-lang/src/native_function/mod.rs +++ b/dust-lang/src/native_function/mod.rs @@ -1,7 +1,7 @@ //! Built-in functions that implement extended functionality. //! -//! Native functions are used either to implement features that are not possible to implement in -//! Dust itself or that are more efficient to implement in Rust. +//! Native functions are used to implement features that are not possible to implement in Dust +//! itself or that are more efficient to implement in Rust. mod logic; use std::{ @@ -15,14 +15,14 @@ use serde::{Deserialize, Serialize}; use crate::{AnnotatedError, FunctionType, Instruction, Span, Type, Value, Vm, VmError}; macro_rules! define_native_function { - ($(($name:ident, $byte:literal, $str:expr, $type:expr, $function:expr)),*) => { + ($(($name:ident, $bytes:literal, $str:expr, $type:expr, $function:expr)),*) => { /// A dust-native function. /// /// See the [module-level documentation](index.html) for more information. #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub enum NativeFunction { $( - $name = $byte as isize, + $name = $bytes as isize, )* } @@ -75,14 +75,14 @@ macro_rules! define_native_function { } impl From for NativeFunction { - fn from(byte: u16) -> Self { - match byte { + fn from(bytes: u16) -> Self { + match bytes { $( - $byte => NativeFunction::$name, + $bytes => NativeFunction::$name, )* _ => { if cfg!(test) { - panic!("Invalid native function byte: {}", byte) + panic!("Invalid native function byte: {}", bytes) } else { NativeFunction::Panic } @@ -91,11 +91,11 @@ macro_rules! define_native_function { } } - impl From for u8 { + impl From for u16 { fn from(native_function: NativeFunction) -> Self { match native_function { $( - NativeFunction::$name => $byte, + NativeFunction::$name => $bytes, )* } } diff --git a/dust-lang/src/optimize.rs b/dust-lang/src/optimize.rs index fecc510..ddfc609 100644 --- a/dust-lang/src/optimize.rs +++ b/dust-lang/src/optimize.rs @@ -1,6 +1,23 @@ //! Tools used by the compiler to optimize a chunk's bytecode. -use crate::{instruction::SetLocal, Chunk, Operation}; +use crate::{instruction::SetLocal, Instruction, Operation, Span, Type}; + +fn get_last_operations( + instructions: &[(Instruction, Type, Span)], +) -> Option<[Operation; COUNT]> { + let mut n_operations = [Operation::Return; COUNT]; + + for (nth, operation) in n_operations.iter_mut().rev().zip( + instructions + .iter() + .rev() + .map(|(instruction, _, _)| instruction.operation()), + ) { + *nth = operation; + } + + Some(n_operations) +} /// Optimizes a short control flow pattern. /// @@ -22,9 +39,9 @@ use crate::{instruction::SetLocal, Chunk, Operation}; /// - `Jump` /// - `LoadBoolean` or `LoadConstant` /// - `LoadBoolean` or `LoadConstant` -pub fn optimize_control_flow(chunk: &mut Chunk) { +pub fn optimize_control_flow(instructions: &mut [(Instruction, Type, Span)]) { if !matches!( - chunk.get_last_operations(), + get_last_operations(instructions), Some([ Operation::Equal | Operation::Less | Operation::LessEqual | Operation::Test, Operation::Jump, @@ -37,7 +54,6 @@ pub fn optimize_control_flow(chunk: &mut Chunk) { log::debug!("Consolidating registers for control flow optimization"); - let instructions = chunk.instructions_mut(); let first_loader = &mut instructions.iter_mut().nth_back(1).unwrap().0; first_loader.set_c_to_boolean(true); @@ -67,9 +83,9 @@ pub fn optimize_control_flow(chunk: &mut Chunk) { /// The instructions must be in the following order: /// - `Add`, `Subtract`, `Multiply`, `Divide` or `Modulo` /// - `SetLocal` -pub fn optimize_set_local(chunk: &mut Chunk) { +pub fn optimize_set_local(instructions: &mut Vec<(Instruction, Type, Span)>) { if !matches!( - chunk.get_last_operations(), + get_last_operations(instructions), Some([ Operation::Add | Operation::Subtract @@ -84,7 +100,6 @@ pub fn optimize_set_local(chunk: &mut Chunk) { log::debug!("Condensing math and SetLocal to math instruction"); - let instructions = chunk.instructions_mut(); let set_local = SetLocal::from(&instructions.pop().unwrap().0); let math_instruction = instructions.last_mut().unwrap().0; let math_instruction_new = *math_instruction diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 3930a00..934f1b6 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -6,9 +6,9 @@ use std::{ }; use crate::{ - compile, instruction::*, AbstractValue, AnnotatedError, Argument, Chunk, ChunkError, - ConcreteValue, Destination, DustError, Instruction, NativeFunctionError, Operation, Span, - Value, ValueError, ValueRef, + compile, instruction::*, AbstractValue, AnnotatedError, Argument, Chunk, ConcreteValue, + Destination, DustError, Instruction, NativeFunctionError, Operation, Span, Value, ValueError, + ValueRef, }; pub fn run(source: &str) -> Result, DustError> { @@ -690,9 +690,10 @@ impl<'a> Vm<'a> { fn get_constant(&self, constant_index: u16) -> Result<&ConcreteValue, VmError> { self.chunk - .get_constant(constant_index) - .map_err(|error| VmError::Chunk { - error, + .constants() + .get(constant_index as usize) + .ok_or_else(|| VmError::ConstantIndexOutOfBounds { + index: constant_index as usize, position: self.current_position, }) } @@ -715,12 +716,12 @@ impl<'a> Vm<'a> { fn read(&mut self) -> Result { let (instruction, _type, position) = - self.chunk - .get_instruction(self.ip) - .map_err(|error| VmError::Chunk { - error, + self.chunk.instructions().get(self.ip).ok_or_else(|| { + VmError::InstructionIndexOutOfBounds { + index: self.ip, position: self.current_position, - })?; + } + })?; self.ip += 1; self.current_position = *position; @@ -816,11 +817,21 @@ pub enum VmError { position: Span, }, - // Wrappers for foreign errors - Chunk { - error: ChunkError, + // Chunk errors + ConstantIndexOutOfBounds { + index: usize, position: Span, }, + InstructionIndexOutOfBounds { + index: usize, + position: Span, + }, + LocalIndexOutOfBounds { + index: usize, + position: Span, + }, + + // Wrappers for foreign errors NativeFunction(NativeFunctionError), Value { error: ValueError, @@ -835,13 +846,15 @@ impl AnnotatedError for VmError { fn description(&self) -> &'static str { match self { - Self::Chunk { .. } => "Chunk error", + Self::ConstantIndexOutOfBounds { .. } => "Constant index out of bounds", Self::EmptyRegister { .. } => "Empty register", Self::ExpectedBoolean { .. } => "Expected boolean", Self::ExpectedConcreteValue { .. } => "Expected concrete value", Self::ExpectedFunction { .. } => "Expected function", Self::ExpectedParent { .. } => "Expected parent", Self::ExpectedValue { .. } => "Expected value", + Self::InstructionIndexOutOfBounds { .. } => "Instruction index out of bounds", + Self::LocalIndexOutOfBounds { .. } => "Local index out of bounds", Self::NativeFunction(error) => error.description(), Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds", Self::StackOverflow { .. } => "Stack overflow", @@ -854,7 +867,6 @@ impl AnnotatedError for VmError { fn details(&self) -> Option { match self { - Self::Chunk { error, .. } => Some(error.to_string()), Self::EmptyRegister { index, .. } => Some(format!("Register R{index} is empty")), Self::ExpectedFunction { found, .. } => Some(format!("{found} is not a function")), @@ -870,13 +882,15 @@ impl AnnotatedError for VmError { fn position(&self) -> Span { match self { - Self::Chunk { position, .. } => *position, + Self::ConstantIndexOutOfBounds { position, .. } => *position, Self::EmptyRegister { position, .. } => *position, Self::ExpectedBoolean { position, .. } => *position, Self::ExpectedConcreteValue { position, .. } => *position, Self::ExpectedFunction { position, .. } => *position, Self::ExpectedParent { position } => *position, Self::ExpectedValue { position, .. } => *position, + Self::InstructionIndexOutOfBounds { position, .. } => *position, + Self::LocalIndexOutOfBounds { position, .. } => *position, Self::NativeFunction(error) => error.position(), Self::RegisterIndexOutOfBounds { position, .. } => *position, Self::StackOverflow { position } => *position,