1
0

Clean up chunk

This commit is contained in:
Jeff 2024-11-29 15:48:50 -05:00
parent dcb590fe07
commit 12ae935f50
7 changed files with 232 additions and 321 deletions

View File

@ -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<ConcreteValue> {
&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<const COUNT: usize>(&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<Local> {
&self.locals
}
pub fn locals_mut(&mut self) -> &mut Vec<Local> {
&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<String> {
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<Type, ChunkError> {
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<H: Hasher>(&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)
}
}
}
}

View File

@ -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<Chunk, DustError> {
.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<String>,
instructions: Vec<(Instruction, Type, Span)>,
constants: Vec<ConcreteValue>,
locals: Vec<Local>,
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<Self, CompileError> {
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<Vec<u16>>,
value_parameters: Option<Vec<(u16, Type)>>,
) -> 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<u16, CompileError> {
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<String> {
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<bool, CompileError> {
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<const COUNT: usize>(&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<Type, CompileError> {
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<Type, CompileError> {
for (instruction, r#type, _) in self.chunk.instructions() {
fn get_register_type(&self, register_index: u16) -> Result<Type, CompileError> {
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,

View File

@ -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.
//!

View File

@ -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};

View File

@ -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<u16> 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<NativeFunction> for u8 {
impl From<NativeFunction> for u16 {
fn from(native_function: NativeFunction) -> Self {
match native_function {
$(
NativeFunction::$name => $byte,
NativeFunction::$name => $bytes,
)*
}
}

View File

@ -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<const COUNT: usize>(
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

View File

@ -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<Option<ConcreteValue>, 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<Instruction, VmError> {
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<String> {
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,