1
0

Optimize; Remove non-working optimizations; Improve CLI

This commit is contained in:
Jeff 2025-01-09 01:44:07 -05:00
parent f667716336
commit 2365979561
19 changed files with 211 additions and 259 deletions

4
Cargo.lock generated
View File

@ -339,7 +339,6 @@ dependencies = [
"rand",
"serde",
"serde_json",
"smallvec",
"smartstring",
"tracing",
]
@ -796,9 +795,6 @@ name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
dependencies = [
"serde",
]
[[package]]
name = "smartstring"

View File

@ -6,70 +6,74 @@ use std::{
};
use clap::{
builder::{styling::AnsiColor, StyledStr, Styles},
builder::{
styling::{AnsiColor, Color},
StyledStr, Styles,
},
crate_authors, crate_description, crate_version,
error::ErrorKind,
Args, ColorChoice, Error, Parser, Subcommand, ValueHint,
};
use color_print::cstr;
use color_print::{cformat, cstr};
use dust_lang::{CompileError, Compiler, DustError, DustString, Lexer, Span, Token, Vm};
use tracing::{subscriber::set_global_default, Level};
use tracing_subscriber::FmtSubscriber;
const CLI_HELP_TEMPLATE: &str = cstr!(
r#"
<bright-magenta><bold>Dust CLI
</bold></bright-magenta>
{about}
<bold>Version:</bold> {version}
<bold>Author:</bold> {author}
<bold>License:</bold> GPL-3.0
<bold>Repository:</bold> git.jeffa.io/jeff/dust
<bright-magenta,bold>Usage
</bright-magenta,bold>
{tab}{usage}
<bright-magenta,bold>Modes
</bright-magenta,bold>
{subcommands}
<bright-magenta,bold>Options
</bright-magenta,bold>
{options}
"#
);
const MODE_HELP_TEMPLATE: &str = cstr!(
const ABOUT: &str = cstr!(
r#"
<bright-magenta,bold>Dust CLI
</bright-magenta,bold>
</>
{about}
<bold>Version:</bold> {version}
<bold>Author:</bold> {author}
<bold>License:</bold> GPL-3.0
<bold>Repository:</bold> git.jeffa.io/jeff/dust
<bold> Version:</> {version}
<bold>🦀 Author:</> {author}
<bold> License:</> GPL-3.0
<bold>🔬 Repository:</> https://git.jeffa.io/jeff/dust
"#
);
<bright-magenta,bold>Usage
</bright-magenta,bold>
{usage}
const PLAIN_ABOUT: &str = r#"
{about}
"#;
<bright-magenta,bold>Options
</bright-magenta,bold>
const USAGE: &str = cstr!(
r#"
<bright-magenta,bold>Usage:</> {usage}
"#
);
const SUBCOMMANDS: &str = cstr!(
r#"
<bright-magenta,bold>Modes:</>
{subcommands}
"#
);
const OPTIONS: &str = cstr!(
r#"
<bright-magenta,bold>Options:</>
{options}
"#
);
const CREATE_MAIN_HELP_TEMPLATE: fn() -> String =
|| cformat!("{ABOUT}{USAGE}{SUBCOMMANDS}{OPTIONS}");
const CREATE_MODE_HELP_TEMPLATE: fn(&str) -> String = |title| {
cformat!(
"\
<bright-magenta,bold>{title}\n</>\
{PLAIN_ABOUT}{USAGE}{OPTIONS}
"
)
};
const STYLES: Styles = Styles::styled()
.header(AnsiColor::BrightMagenta.on_default().bold())
.usage(AnsiColor::BrightCyan.on_default().bold())
.literal(AnsiColor::BrightCyan.on_default())
.placeholder(AnsiColor::BrightMagenta.on_default())
.error(AnsiColor::BrightRed.on_default().bold())
.valid(AnsiColor::Blue.on_default())
.invalid(AnsiColor::BrightRed.on_default());
.literal(AnsiColor::Cyan.on_default())
.placeholder(AnsiColor::Cyan.on_default())
.valid(AnsiColor::BrightCyan.on_default())
.invalid(AnsiColor::BrightYellow.on_default())
.error(AnsiColor::BrightRed.on_default());
#[derive(Parser)]
#[clap(
@ -77,7 +81,7 @@ const STYLES: Styles = Styles::styled()
author = crate_authors!(),
about = crate_description!(),
color = ColorChoice::Auto,
help_template = StyledStr::from(CLI_HELP_TEMPLATE),
help_template = CREATE_MAIN_HELP_TEMPLATE(),
styles = STYLES,
)]
struct Cli {
@ -122,7 +126,7 @@ struct Input {
#[derive(Args)]
#[command(
short_flag = 'r',
help_template = MODE_HELP_TEMPLATE
help_template = CREATE_MODE_HELP_TEMPLATE("Run Mode")
)]
struct Run {
/// Print the time taken for compilation and execution
@ -149,7 +153,7 @@ enum Mode {
/// Compile and print the bytecode disassembly
#[command(
short_flag = 'd',
help_template = MODE_HELP_TEMPLATE
help_template = CREATE_MODE_HELP_TEMPLATE("Disassemble Mode")
)]
Disassemble {
/// Style disassembly output
@ -167,7 +171,7 @@ enum Mode {
/// Lex the source code and print the tokens
#[command(
short_flag = 't',
help_template = MODE_HELP_TEMPLATE
help_template = CREATE_MODE_HELP_TEMPLATE("Tokenize Mode")
)]
Tokenize {
/// Style token output
@ -224,7 +228,7 @@ fn main() {
let (source, file_name) = get_source_and_file_name(input);
let lexer = Lexer::new(&source);
let program_name = name.or(file_name);
let mut compiler = match Compiler::new(lexer, program_name) {
let mut compiler = match Compiler::new(lexer, program_name, true) {
Ok(compiler) => compiler,
Err(error) => {
handle_compile_error(error, &source);
@ -312,7 +316,7 @@ fn main() {
let (source, file_name) = get_source_and_file_name(input);
let lexer = Lexer::new(&source);
let program_name = name.or(file_name);
let mut compiler = match Compiler::new(lexer, program_name) {
let mut compiler = match Compiler::new(lexer, program_name, true) {
Ok(compiler) => compiler,
Err(error) => {
handle_compile_error(error, &source);

View File

@ -1,6 +1,6 @@
[package]
name = "dust-lang"
description = "Interpreter library for the Dust programming language"
description = "Dust programming language library"
authors.workspace = true
edition.workspace = true
license.workspace = true
@ -17,7 +17,6 @@ serde_json = "1.0.117"
getrandom = { version = "0.2", features = [
"js",
] } # Indirect dependency, for wasm builds
smallvec = { version = "1.13.2", features = ["const_generics", "serde"] }
smartstring = { version = "1.0.1", features = [
"serde",
], default-features = false }
@ -25,7 +24,6 @@ tracing = "0.1.41"
[dev-dependencies]
criterion = { version = "0.3.4", features = ["html_reports"] }
smallvec = { version = "1.13.2", features = ["const_generics"] }
[[bench]]
name = "addictive_addition"

View File

@ -24,7 +24,6 @@ use std::fmt::{self, Debug, Display, Formatter, Write as FmtWrite};
use std::io::Write;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use crate::{DustString, Function, FunctionType, Instruction, Span, Value};
@ -36,10 +35,10 @@ pub struct Chunk {
pub(crate) name: Option<DustString>,
pub(crate) r#type: FunctionType,
pub(crate) instructions: SmallVec<[Instruction; 32]>,
pub(crate) positions: SmallVec<[Span; 32]>,
pub(crate) constants: SmallVec<[Value; 16]>,
pub(crate) locals: SmallVec<[Local; 8]>,
pub(crate) instructions: Vec<Instruction>,
pub(crate) positions: Vec<Span>,
pub(crate) constants: Vec<Value>,
pub(crate) locals: Vec<Local>,
pub(crate) prototypes: Vec<Chunk>,
pub(crate) register_count: usize,
@ -51,10 +50,10 @@ impl Chunk {
pub fn with_data(
name: Option<DustString>,
r#type: FunctionType,
instructions: impl Into<SmallVec<[Instruction; 32]>>,
positions: impl Into<SmallVec<[Span; 32]>>,
constants: impl Into<SmallVec<[Value; 16]>>,
locals: impl Into<SmallVec<[Local; 8]>>,
instructions: impl Into<Vec<Instruction>>,
positions: impl Into<Vec<Span>>,
constants: impl Into<Vec<Value>>,
locals: impl Into<Vec<Local>>,
prototypes: Vec<Chunk>,
) -> Self {
Self {

View File

@ -1,7 +1,5 @@
use std::num::{ParseFloatError, ParseIntError};
use smallvec::{smallvec, SmallVec};
use crate::{AnnotatedError, LexError, Scope, Span, TokenKind, TokenOwned, Type, TypeConflict};
/// Compilation errors
@ -212,7 +210,7 @@ impl AnnotatedError for CompileError {
}
}
fn detail_snippets(&self) -> SmallVec<[(String, Span); 2]> {
fn detail_snippets(&self) -> Vec<(String, Span)> {
match self {
Self::CannotAddArguments {
left_type,
@ -220,31 +218,31 @@ impl AnnotatedError for CompileError {
right_type,
right_position,
} => {
smallvec![
vec![
(
format!("A value of type \"{left_type}\" was used here."),
*left_position
*left_position,
),
(
format!("A value of type \"{right_type}\" was used here."),
*right_position
)
*right_position,
),
]
}
Self::ReturnTypeConflict { conflict, position } => {
smallvec![(
vec![(
format!(
"Expected type {} but found type {}",
conflict.expected, conflict.actual
),
*position
*position,
)]
}
_ => SmallVec::new(),
_ => Vec::with_capacity(0),
}
}
fn help_snippets(&self) -> SmallVec<[(String, Span); 2]> {
fn help_snippets(&self) -> Vec<(String, Span)> {
match self {
Self::CannotAddArguments {
left_type,
@ -252,12 +250,12 @@ impl AnnotatedError for CompileError {
right_type,
right_position,
} => {
smallvec![(
vec![(
format!("Type \"{left_type}\" cannot be added to type \"{right_type}\". Try converting one of the values to the other type."),
Span(left_position.0, right_position.1)
)]
}
_ => SmallVec::new(),
_ => Vec::with_capacity(0),
}
}
}

View File

@ -31,7 +31,6 @@ use type_checks::{check_math_type, check_math_types};
use std::mem::replace;
use optimize::control_flow_register_consolidation;
use smallvec::{smallvec, SmallVec};
use crate::{
instruction::{CallNative, Close, GetLocal, Jump, LoadList, Negate, Not, Return, SetLocal},
@ -52,8 +51,8 @@ use crate::{
/// ```
pub fn compile(program_name: Option<DustString>, source: &str) -> Result<Chunk, DustError> {
let lexer = Lexer::new(source);
let mut compiler =
Compiler::new(lexer, program_name).map_err(|error| DustError::compile(error, source))?;
let mut compiler = Compiler::new(lexer, program_name, true)
.map_err(|error| DustError::compile(error, source))?;
compiler
.compile()
@ -85,15 +84,15 @@ pub struct Compiler<'src> {
/// Instructions, along with their types and positions, that have been compiled. The
/// instructions and positions are assigned to the chunk when [`Compiler::finish`] is called.
/// The types are discarded after compilation.
instructions: SmallVec<[(Instruction, Type, Span); 32]>,
instructions: Vec<(Instruction, Type, Span)>,
/// Constants that have been compiled. These are assigned to the chunk when [`Compiler::finish`]
/// is called.
constants: SmallVec<[Value; 16]>,
constants: Vec<Value>,
/// Block-local variables and their types. The locals are assigned to the chunk when
/// [`Compiler::finish`] is called. The types are discarded after compilation.
locals: SmallVec<[(Local, Type); 8]>,
locals: Vec<(Local, Type)>,
/// Prototypes that have been compiled. These are assigned to the chunk when
/// [`Compiler::finish`] is called.
@ -104,12 +103,11 @@ pub struct Compiler<'src> {
stack_size: usize,
/// The first register index that the compiler should use. This is used to avoid reusing the
/// registers that are used for the function's arguments, thus it is zero in the program's main
/// chunk.
/// registers that are used for the function's arguments.
minimum_register: u8,
/// Index of the current block. This is used to determine the scope of the locals and is
/// incremented when a new block is entered.
/// Index of the current block. This is used to determine the scope of locals and is incremented
/// when a new block is entered.
block_index: u8,
/// The current block scope of the compiler. This is used to test if a variable is in scope.
@ -119,6 +117,10 @@ pub struct Compiler<'src> {
/// that value is never read because the main chunk is not a callable function.
prototype_index: u8,
/// Whether the chunk is the program's main chunk. This is used to prevent recursive calls to
/// the main chunk.
is_main: bool,
current_token: Token<'src>,
current_position: Span,
previous_token: Token<'src>,
@ -126,23 +128,24 @@ pub struct Compiler<'src> {
}
impl<'src> Compiler<'src> {
/// Creates a new top-level compiler with the given lexer.
/// Creates a new compiler.
pub fn new(
mut lexer: Lexer<'src>,
function_name: Option<DustString>,
is_main: bool,
) -> Result<Self, CompileError> {
let (current_token, current_position) = lexer.next_token()?;
Ok(Compiler {
function_name,
r#type: FunctionType {
type_parameters: None,
value_parameters: None,
type_parameters: Vec::with_capacity(0),
value_parameters: Vec::with_capacity(0),
return_type: Type::None,
},
instructions: SmallVec::new(),
constants: SmallVec::new(),
locals: SmallVec::new(),
instructions: Vec::new(),
constants: Vec::new(),
locals: Vec::new(),
prototypes: Vec::new(),
stack_size: 0,
lexer,
@ -150,6 +153,7 @@ impl<'src> Compiler<'src> {
block_index: 0,
current_scope: Scope::default(),
prototype_index: 0,
is_main,
current_token,
current_position,
previous_token: Token::Eof,
@ -195,7 +199,7 @@ impl<'src> Compiler<'src> {
/// will allow [`Compiler::function_name`] to be both the name used for recursive calls and the
/// name of the function when it is compiled. The name can later be seen in the VM's call stack.
pub fn finish(self) -> Chunk {
let (instructions, positions): (SmallVec<[Instruction; 32]>, SmallVec<[Span; 32]>) = self
let (instructions, positions): (Vec<Instruction>, Vec<Span>) = self
.instructions
.into_iter()
.map(|(instruction, _, position)| (instruction, position))
@ -204,14 +208,14 @@ impl<'src> Compiler<'src> {
.locals
.into_iter()
.map(|(local, _)| local)
.collect::<SmallVec<[Local; 8]>>();
.collect::<Vec<Local>>();
Chunk {
name: self.function_name,
r#type: self.r#type,
instructions,
positions,
constants: self.constants,
constants: self.constants.to_vec(),
locals,
prototypes: self.prototypes,
register_count: self.stack_size,
@ -998,7 +1002,7 @@ impl<'src> Compiler<'src> {
local_index
} else if let Some(native_function) = NativeFunction::from_str(identifier) {
return self.parse_call_native(native_function);
} else if self.function_name.as_deref() == Some(identifier) {
} else if self.function_name.as_deref() == Some(identifier) && !self.is_main {
let destination = self.next_register();
let load_self = Instruction::load_self(destination);
@ -1549,7 +1553,7 @@ impl<'src> Compiler<'src> {
let mut function_compiler = if self.current_token == Token::LeftParenthesis {
let function_name = identifier.map(DustString::from);
Compiler::new(self.lexer, function_name)? // This will consume the left parenthesis
Compiler::new(self.lexer, function_name, false)? // This will consume the parenthesis
} else {
return Err(CompileError::ExpectedToken {
expected: TokenKind::LeftParenthesis,
@ -1560,7 +1564,7 @@ impl<'src> Compiler<'src> {
function_compiler.prototype_index = self.prototypes.len() as u8;
let mut value_parameters: Option<SmallVec<[(u8, Type); 4]>> = None;
let mut value_parameters: Vec<(u8, Type)> = Vec::with_capacity(3);
while !function_compiler.allow(Token::RightParenthesis)? {
let is_mutable = function_compiler.allow(Token::Mut)?;
@ -1594,15 +1598,10 @@ impl<'src> Compiler<'src> {
function_compiler.current_scope,
);
if let Some(value_parameters) = value_parameters.as_mut() {
value_parameters.push((identifier_index, r#type));
} else {
value_parameters = Some(smallvec![(identifier_index, r#type)]);
};
function_compiler.allow(Token::Comma)?;
function_compiler.minimum_register += 1;
function_compiler.allow(Token::Comma)?;
}
let return_type = if function_compiler.allow(Token::ArrowThin)? {
@ -1618,7 +1617,7 @@ impl<'src> Compiler<'src> {
Type::None
};
let function_type = FunctionType {
type_parameters: None,
type_parameters: Vec::with_capacity(0),
value_parameters,
return_type,
};

View File

@ -3,7 +3,6 @@
use std::fmt::{self, Display, Formatter};
use annotate_snippets::{Level, Renderer, Snippet};
use smallvec::SmallVec;
use crate::{CompileError, NativeFunctionError, Span};
@ -76,6 +75,6 @@ impl Display for DustError<'_> {
pub trait AnnotatedError {
fn title() -> &'static str;
fn description(&self) -> &'static str;
fn detail_snippets(&self) -> SmallVec<[(String, Span); 2]>;
fn help_snippets(&self) -> SmallVec<[(String, Span); 2]>;
fn detail_snippets(&self) -> Vec<(String, Span)>;
fn help_snippets(&self) -> Vec<(String, Span)>;
}

View File

@ -744,12 +744,12 @@ impl AnnotatedError for LexError {
}
}
fn detail_snippets(&self) -> smallvec::SmallVec<[(String, Span); 2]> {
todo!()
fn detail_snippets(&self) -> Vec<(String, Span)> {
Vec::with_capacity(0)
}
fn help_snippets(&self) -> smallvec::SmallVec<[(String, Span); 2]> {
todo!()
fn help_snippets(&self) -> Vec<(String, Span)> {
Vec::with_capacity(0)
}
}

View File

@ -28,8 +28,6 @@
//! println!("{}", report);
//! ```
#![feature(array_repeat)]
pub mod chunk;
pub mod compiler;
pub mod dust_error;

View File

@ -3,7 +3,7 @@ use std::{ops::Range, panic};
use crate::vm::ThreadData;
pub fn panic(data: &mut ThreadData, _: Option<u8>, argument_range: Range<u8>) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let position = record.current_position();
let mut message = format!("Dust panic at {position}!");

View File

@ -11,7 +11,7 @@ pub fn read_line(
destination: Option<u8>,
_argument_range: Range<u8>,
) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let destination = destination.unwrap();
let mut buffer = String::new();
@ -31,7 +31,7 @@ pub fn read_line(
}
pub fn write(data: &mut ThreadData, _destination: Option<u8>, argument_range: Range<u8>) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let mut stdout = stdout();
for register_index in argument_range {
@ -52,7 +52,7 @@ pub fn write_line(
_destination: Option<u8>,
argument_range: Range<u8>,
) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let mut stdout = stdout().lock();
for register_index in argument_range {

View File

@ -2,7 +2,7 @@
//!
//! 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 assertion;
mod assert;
mod io;
mod string;
@ -14,7 +14,6 @@ use std::{
};
use serde::{Deserialize, Serialize};
use smallvec::{smallvec, SmallVec};
use crate::{vm::ThreadData, AnnotatedError, FunctionType, Span, Type};
@ -134,11 +133,11 @@ define_native_function! {
3,
"panic",
FunctionType {
type_parameters: None,
value_parameters: None,
type_parameters: Vec::with_capacity(0),
value_parameters: Vec::with_capacity(0),
return_type: Type::None
},
assertion::panic
assert::panic
),
// // Type conversion
@ -151,8 +150,8 @@ define_native_function! {
8,
"to_string",
FunctionType {
type_parameters: None,
value_parameters: Some(smallvec![(0, Type::Any)]),
type_parameters: Vec::with_capacity(0),
value_parameters: vec![(0, Type::Any)],
return_type: Type::String
},
string::to_string
@ -212,8 +211,8 @@ define_native_function! {
50,
"read_line",
FunctionType {
type_parameters: None,
value_parameters: None,
type_parameters: Vec::with_capacity(0),
value_parameters: Vec::with_capacity(0),
return_type: Type::String
},
io::read_line
@ -228,8 +227,8 @@ define_native_function! {
55,
"write",
FunctionType {
type_parameters: None,
value_parameters: Some(smallvec![(0, Type::String)]),
type_parameters: Vec::with_capacity(0),
value_parameters: vec![(0, Type::String)],
return_type: Type::None
},
io::write
@ -240,8 +239,8 @@ define_native_function! {
57,
"write_line",
FunctionType {
type_parameters: None,
value_parameters: Some(smallvec![(0, Type::String)]),
type_parameters: Vec::with_capacity(0),
value_parameters: vec![(0, Type::String)],
return_type: Type::None
},
io::write_line
@ -289,29 +288,29 @@ impl AnnotatedError for NativeFunctionError {
}
}
fn detail_snippets(&self) -> SmallVec<[(String, Span); 2]> {
fn detail_snippets(&self) -> Vec<(String, Span)> {
match self {
NativeFunctionError::ExpectedArgumentCount {
expected,
found,
position,
} => smallvec![(
} => vec![(
format!("Expected {expected} arguments, found {found}"),
*position
*position,
)],
NativeFunctionError::Panic { message, position } => {
smallvec![(format!("Dust panic!\n{message}"), *position)]
vec![(format!("Dust panic!\n{message}"), *position)]
}
NativeFunctionError::Parse { error, position } => {
smallvec![(format!("{error}"), *position)]
vec![(format!("{error}"), *position)]
}
NativeFunctionError::Io { error, position } => {
smallvec![(format!("{error}"), *position)]
vec![(format!("{error}"), *position)]
}
}
}
fn help_snippets(&self) -> SmallVec<[(String, Span); 2]> {
SmallVec::new()
fn help_snippets(&self) -> Vec<(String, Span)> {
Vec::with_capacity(0)
}
}

View File

@ -10,7 +10,7 @@ pub fn to_string(
destination: Option<u8>,
argument_range: Range<u8>,
) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let argument_value = record.open_register_unchecked(argument_range.start);
let argument_string = argument_value.display(record);
let destination = destination.unwrap();

View File

@ -6,7 +6,6 @@ use std::{
};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
/// Description of a kind of value.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
@ -25,7 +24,7 @@ pub enum Type {
Integer,
List(Box<Type>),
Map {
pairs: Box<SmallVec<[(u8, Type); 8]>>,
pairs: Vec<(u8, Type)>,
},
None,
Range {
@ -35,7 +34,7 @@ pub enum Type {
String,
Struct(StructType),
Tuple {
fields: Box<SmallVec<[Type; 4]>>,
fields: Vec<Type>,
},
}
@ -274,20 +273,20 @@ impl Ord for Type {
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct FunctionType {
pub type_parameters: Option<SmallVec<[u8; 4]>>,
pub value_parameters: Option<SmallVec<[(u8, Type); 4]>>,
pub type_parameters: Vec<u8>,
pub value_parameters: Vec<(u8, Type)>,
pub return_type: Type,
}
impl FunctionType {
pub fn new<T: Into<SmallVec<[u8; 4]>>, U: Into<SmallVec<[(u8, Type); 4]>>>(
type_parameters: Option<T>,
value_parameters: Option<U>,
pub fn new<T: Into<Vec<u8>>, U: Into<Vec<(u8, Type)>>>(
type_parameters: T,
value_parameters: U,
return_type: Type,
) -> Self {
FunctionType {
type_parameters: type_parameters.map(|into_types| into_types.into()),
value_parameters: value_parameters.map(|into_values| into_values.into()),
type_parameters: type_parameters.into(),
value_parameters: value_parameters.into(),
return_type,
}
}
@ -297,10 +296,10 @@ impl Display for FunctionType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "fn ")?;
if let Some(type_parameters) = &self.type_parameters {
if !self.type_parameters.is_empty() {
write!(f, "<")?;
for (index, type_parameter) in type_parameters.iter().enumerate() {
for (index, type_parameter) in self.type_parameters.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
@ -313,8 +312,8 @@ impl Display for FunctionType {
write!(f, "(")?;
if let Some(value_parameters) = &self.value_parameters {
for (index, (_, r#type)) in value_parameters.iter().enumerate() {
if !self.value_parameters.is_empty() {
for (index, (_, r#type)) in self.value_parameters.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}

View File

@ -1,39 +0,0 @@
use std::fmt::{self, Display, Formatter};
use crate::{InstructionData, Value};
use super::{stack::Stack, FunctionCall};
#[derive(Clone, Debug, PartialEq)]
pub enum VmError {
StackUnderflow,
ExpectedFunction {
value: Value,
},
InstructionIndexOutOfBounds {
call_stack: Stack<FunctionCall>,
ip: usize,
},
MalformedInstruction {
instruction: InstructionData,
},
}
impl Display for VmError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::StackUnderflow => {
write!(f, "Call stack underflow")
}
Self::ExpectedFunction { value } => {
write!(f, "Expected function, found {value}")
}
Self::InstructionIndexOutOfBounds { call_stack, ip } => {
write!(f, "Instruction index {} out of bounds\n{call_stack}", ip)
}
Self::MalformedInstruction { instruction } => {
write!(f, "Malformed instruction {instruction}")
}
}
}
}

View File

@ -1,5 +1,4 @@
//! Virtual machine and errors
mod error;
mod record;
mod run_action;
mod stack;
@ -11,7 +10,6 @@ use std::{
thread::spawn,
};
pub use error::VmError;
pub use record::Record;
pub(crate) use run_action::get_next_action;
pub use run_action::RunAction;

View File

@ -68,7 +68,7 @@ pub(crate) fn get_next_action(record: &mut Record) -> RunAction {
}
pub fn point(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let Point { from, to } = instruction.into();
let from_register = record.get_register_unchecked(from);
let from_register_is_empty = matches!(from_register, Register::Empty);
@ -85,7 +85,7 @@ pub fn point(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn close(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let Close { from, to } = instruction.into();
for register_index in from..to {
@ -98,7 +98,7 @@ pub fn close(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn load_boolean(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let LoadBoolean {
destination,
value,
@ -119,7 +119,7 @@ pub fn load_boolean(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn load_constant(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let LoadConstant {
destination,
constant_index,
@ -141,7 +141,7 @@ pub fn load_constant(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn load_list(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let LoadList {
destination,
start_register,
@ -183,7 +183,7 @@ pub fn load_list(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn load_function(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let LoadFunction {
destination,
prototype_index,
@ -200,7 +200,7 @@ pub fn load_function(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn load_self(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let LoadSelf { destination } = instruction.into();
let function = record.as_function();
let register = Register::Value(Value::Function(function));
@ -213,7 +213,7 @@ pub fn load_self(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn get_local(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let GetLocal {
destination,
local_index,
@ -229,7 +229,7 @@ pub fn get_local(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn set_local(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let SetLocal {
register_index,
local_index,
@ -245,7 +245,7 @@ pub fn set_local(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn add(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let Add {
destination,
left,
@ -264,7 +264,7 @@ pub fn add(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn subtract(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let Subtract {
destination,
left,
@ -283,7 +283,7 @@ pub fn subtract(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn multiply(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let Multiply {
destination,
left,
@ -310,7 +310,7 @@ pub fn multiply(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn divide(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let Divide {
destination,
left,
@ -337,7 +337,7 @@ pub fn divide(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn modulo(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let Modulo {
destination,
left,
@ -364,7 +364,7 @@ pub fn modulo(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn test(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let Test {
operand_register,
test_value,
@ -386,7 +386,7 @@ pub fn test(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn test_set(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let TestSet {
destination,
argument,
@ -416,7 +416,7 @@ pub fn test_set(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn equal(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let Equal { value, left, right } = instruction.into();
let left = record.get_argument_unchecked(left);
let right = record.get_argument_unchecked(right);
@ -432,7 +432,7 @@ pub fn equal(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn less(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let Less { value, left, right } = instruction.into();
let left = record.get_argument_unchecked(left);
let right = record.get_argument_unchecked(right);
@ -448,7 +448,7 @@ pub fn less(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn less_equal(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let LessEqual { value, left, right } = instruction.into();
let left = record.get_argument_unchecked(left);
let right = record.get_argument_unchecked(right);
@ -464,7 +464,7 @@ pub fn less_equal(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn negate(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let Negate {
destination,
argument,
@ -481,7 +481,7 @@ pub fn negate(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn not(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let Not {
destination,
argument,
@ -501,7 +501,7 @@ pub fn not(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn jump(instruction: Instruction, data: &mut ThreadData) -> bool {
let record = data.records.last_mut_unchecked();
let record = &mut data.call_stack.last_mut_unchecked().record;
let Jump {
offset,
is_positive,
@ -520,32 +520,35 @@ pub fn jump(instruction: Instruction, data: &mut ThreadData) -> bool {
}
pub fn call(instruction: Instruction, data: &mut ThreadData) -> bool {
let current_record = data.records.pop_unchecked();
let current_call = data.call_stack.pop_unchecked();
let Call {
destination: return_register,
function_register,
argument_count,
is_recursive,
} = instruction.into();
let function = current_record
let function = current_call
.record
.open_register_unchecked(function_register)
.as_function()
.unwrap();
let first_argument_register = return_register - argument_count;
let prototype = if is_recursive {
current_record.chunk
current_call.record.chunk
} else {
&current_record.chunk.prototypes[function.prototype_index as usize]
&current_call.record.chunk.prototypes[function.prototype_index as usize]
};
let mut next_record = Record::new(prototype);
let next_call = FunctionCall {
name: next_record.name().cloned(),
let mut next_call = FunctionCall {
name: prototype.name.clone(),
return_register,
ip: current_record.ip,
ip: current_call.ip,
record: Record::new(prototype),
};
for (argument_index, register_index) in (first_argument_register..return_register).enumerate() {
let argument = current_record.clone_register_value_or_constant_unchecked(register_index);
let argument = current_call
.record
.clone_register_value_or_constant_unchecked(register_index);
trace!(
"Passing argument \"{argument}\" to {}",
@ -555,14 +558,15 @@ pub fn call(instruction: Instruction, data: &mut ThreadData) -> bool {
.unwrap_or_else(|| DustString::from("anonymous"))
);
next_record.set_register(argument_index as u8, Register::Value(argument));
next_call
.record
.set_register(argument_index as u8, Register::Value(argument));
}
data.next_action = get_next_action(&mut next_record);
data.next_action = get_next_action(&mut next_call.record);
data.call_stack.push(current_call);
data.call_stack.push(next_call);
data.records.push(current_record);
data.records.push(next_record);
false
}
@ -586,28 +590,30 @@ pub fn r#return(instruction: Instruction, data: &mut ThreadData) -> bool {
should_return_value,
return_register,
} = instruction.into();
let current_call = data.call_stack.pop_unchecked();
let mut current_record = if data.call_stack.is_empty() {
let mut current_call = if data.call_stack.len() == 1 {
if should_return_value {
data.return_value_index = Some(return_register);
};
return true;
} else {
data.records.pop_unchecked()
data.call_stack.pop_unchecked()
};
let outer_record = data.records.last_mut_unchecked();
let outer_call = data.call_stack.last_mut_unchecked();
let destination = current_call.return_register;
if should_return_value {
let return_value =
current_record.empty_register_or_clone_constant_unchecked(return_register);
let return_value = current_call
.record
.empty_register_or_clone_constant_unchecked(return_register);
outer_record.set_register(destination, Register::Value(return_value));
outer_call
.record
.set_register(destination, Register::Value(return_value));
}
data.next_action = get_next_action(outer_record);
data.next_action = get_next_action(&mut outer_call.record);
false
}

View File

@ -5,7 +5,7 @@ use std::{
use crate::DustString;
use super::VmError;
use super::Record;
#[derive(Clone, PartialEq)]
pub struct Stack<T> {
@ -35,7 +35,7 @@ impl<T> Stack<T> {
pub fn get_unchecked(&self, index: usize) -> &T {
if cfg!(debug_assertions) {
assert!(index < self.len(), "{}", VmError::StackUnderflow);
assert!(index < self.len(), "Stack underflow");
&self.items[index]
} else {
@ -61,7 +61,7 @@ impl<T> Stack<T> {
pub fn pop_unchecked(&mut self) -> T {
if cfg!(debug_assertions) {
assert!(!self.is_empty(), "{}", VmError::StackUnderflow);
assert!(!self.is_empty(), "Stack underflow");
self.items.pop().unwrap()
} else {
@ -71,7 +71,7 @@ impl<T> Stack<T> {
pub fn last_unchecked(&self) -> &T {
if cfg!(debug_assertions) {
assert!(!self.is_empty(), "{}", VmError::StackUnderflow);
assert!(!self.is_empty(), "Stack underflow");
self.items.last().unwrap()
} else {
@ -81,7 +81,7 @@ impl<T> Stack<T> {
pub fn last_mut_unchecked(&mut self) -> &mut T {
if cfg!(debug_assertions) {
assert!(!self.is_empty(), "{}", VmError::StackUnderflow);
assert!(!self.is_empty(), "Stack underflow");
self.items.last_mut().unwrap()
} else {
@ -116,7 +116,7 @@ impl<T: Debug> Debug for Stack<T> {
}
}
impl Display for Stack<FunctionCall> {
impl Display for Stack<FunctionCall<'_>> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "-- DUST CALL STACK --")?;
@ -128,14 +128,15 @@ impl Display for Stack<FunctionCall> {
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FunctionCall {
#[derive(Debug)]
pub struct FunctionCall<'a> {
pub name: Option<DustString>,
pub return_register: u8,
pub ip: usize,
pub record: Record<'a>,
}
impl Display for FunctionCall {
impl Display for FunctionCall<'_> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let FunctionCall {
name,

View File

@ -23,21 +23,18 @@ impl Thread {
);
let mut call_stack = Stack::with_capacity(self.chunk.prototypes.len() + 1);
let mut records = Stack::with_capacity(self.chunk.prototypes.len() + 1);
let main_call = FunctionCall {
name: self.chunk.name.clone(),
return_register: 0, // Never used, the main function's return is the thread's return
ip: 0,
record: Record::new(&self.chunk),
};
let main_record = Record::new(&self.chunk);
call_stack.push(main_call);
records.push(main_record);
let first_action = RunAction::from(*self.chunk.instructions.first().unwrap());
let mut thread_data = ThreadData {
call_stack,
records,
next_action: first_action,
return_value_index: None,
};
@ -53,8 +50,9 @@ impl Thread {
if should_end {
let return_value = if let Some(register_index) = thread_data.return_value_index {
let value = thread_data
.records
.call_stack
.last_mut_unchecked()
.record
.empty_register_or_clone_constant_unchecked(register_index);
Some(value)
@ -70,8 +68,7 @@ impl Thread {
#[derive(Debug)]
pub struct ThreadData<'a> {
pub call_stack: Stack<FunctionCall>,
pub records: Stack<Record<'a>>,
pub call_stack: Stack<FunctionCall<'a>>,
pub next_action: RunAction,
pub return_value_index: Option<u8>,
}