1
0

Refactor library and CLI

This commit is contained in:
Jeff 2024-11-17 20:32:53 -05:00
parent 0cb96519e2
commit e04ead3848
33 changed files with 979 additions and 650 deletions

135
Cargo.lock generated
View File

@ -70,6 +70,21 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bumpalo"
version = "3.16.0"
@ -128,6 +143,12 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
[[package]]
name = "cobs"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
[[package]]
name = "colorchoice"
version = "1.0.3"
@ -144,6 +165,25 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "dust-cli"
version = "0.1.0"
dependencies = [
"clap",
"colored",
"dust-lang",
"env_logger",
"log",
"postcard",
"serde_json",
]
[[package]]
name = "dust-lang"
version = "0.5.0"
@ -158,17 +198,6 @@ dependencies = [
"serde_json",
]
[[package]]
name = "dust-shell"
version = "0.1.0"
dependencies = [
"clap",
"colored",
"dust-lang",
"env_logger",
"log",
]
[[package]]
name = "env_filter"
version = "0.1.2"
@ -205,6 +234,29 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32",
"rustc_version",
"serde",
"spin",
"stable_deref_trait",
]
[[package]]
name = "heck"
version = "0.5.0"
@ -250,6 +302,16 @@ version = "0.2.161"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.22"
@ -268,6 +330,17 @@ version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "postcard"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f7f0a8d620d71c457dd1d47df76bb18960378da56af4527aaa10f515eee732e"
dependencies = [
"cobs",
"heapless",
"serde",
]
[[package]]
name = "ppv-lite86"
version = "0.2.20"
@ -354,12 +427,33 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.214"
@ -382,9 +476,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.132"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"itoa",
"memchr",
@ -392,6 +486,21 @@ dependencies = [
"serde",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "strsim"
version = "0.11.1"

View File

@ -1,6 +1,6 @@
[workspace]
members = ["dust-lang", "dust-shell"]
default-members = ["dust-lang"]
members = ["dust-lang", "dust-cli"]
default-members = ["dust-lang", "dust-cli"]
resolver = "2"
[workspace.package]

View File

@ -1,15 +1,22 @@
[package]
name = "dust-shell"
name = "dust-cli"
description = "The Dust Programming Language CLI"
version = "0.1.0"
authors.workspace = true
authors = ["Jeff Anderson"]
edition.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
[[bin]]
name = "dust"
path = "src/main.rs"
[dependencies]
clap = { version = "4.5.14", features = ["derive"] }
colored = "2.1.0"
dust-lang = { path = "../dust-lang" }
env_logger = "0.11.5"
log = "0.4.22"
postcard = "1.0.10"
serde_json = "1.0.133"

344
dust-cli/src/main.rs Normal file
View File

@ -0,0 +1,344 @@
use std::{
fmt::{self, Display, Formatter},
fs::{read_to_string, File, OpenOptions},
io::{stdin, stdout, Read, Write},
time::Instant,
};
use clap::{Args, Parser, Subcommand, ValueEnum};
use colored::Colorize;
use dust_lang::{compile, display_token_list, format, lex, run_source, vm::run_chunk, Chunk};
use env_logger::Target;
use log::{Level, LevelFilter};
#[derive(Parser)]
#[command(version, author, about)]
struct Cli {
#[command(flatten)]
input: Input,
#[command(flatten)]
output: Output,
/// Log level: INFO, DEBUG, TRACE, WARN or ERROR. This overrides the DUST_LOG environment variable.
#[arg(short, long, value_enum, value_name = "LEVEL", group = "log")]
log_level: Option<LevelFilter>,
/// Path to a file for log output
#[arg(long, group = "log")]
log_file: Option<String>,
/// Which CLI feature to use, defaults to "run"
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Args, Clone)]
#[group(required = true, multiple = true)]
struct Input {
/// Path to a Dust source file. If this and `input` are not provided, input will be read from
/// stdin.
source_file: Option<String>,
/// Dust source code to compile. If this and `source_file` are not provided, input will be read
/// from stdin.
#[arg(short, long, conflicts_with = "source_file")]
input: Option<Vec<u8>>,
/// The format of the input
#[arg(short, long, value_enum, default_value = "source")]
read_format: IoFormat,
}
#[derive(Args, Clone)]
#[group(required = false, multiple = false)]
struct Output {
/// Path to a file for output. If not provided, output will be written to stdout.
#[arg(short, long)]
output_file: Option<String>,
/// The format of the output
#[arg(short, long, value_enum, default_value = "postcard")]
write_format: IoFormat,
}
#[derive(Subcommand, Clone, Copy)]
enum Command {
/// Compile Dust to an intermediate format
Compile,
/// Compile and display the disassembled chunk
Disassemble {
/// Whether to style the output
#[arg(short, long, default_value = "true")]
style: bool,
},
/// Format and display the source code
Format {
/// Whether to color the output
#[arg(short, long, default_value = "true")]
color: bool,
/// Number of spaces per indent level
#[arg(short, long, default_value = "4")]
indent: usize,
/// Whether to include line numbers in the output
#[arg(short, long, default_value = "true")]
line_numbers: bool,
},
/// Compile and run the Dust code
Run,
/// Lex and display the token list
Tokenize {
/// Whether to style the output
#[arg(short, long, default_value = "true")]
style: bool,
},
}
#[derive(ValueEnum, Clone, Copy)]
enum IoFormat {
Json,
Postcard,
Source,
}
impl Display for IoFormat {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
IoFormat::Json => write!(f, "json"),
IoFormat::Postcard => write!(f, "postcard"),
IoFormat::Source => write!(f, "source"),
}
}
}
fn main() -> Result<(), String> {
let Cli {
input,
output,
log_level,
log_file,
command,
} = Cli::parse();
let command = command.unwrap_or(Command::Run);
let start = Instant::now();
let mut logger = env_logger::builder();
logger.format(move |buf, record| {
let level_display = match record.level() {
Level::Info => "INFO".bold().white(),
Level::Debug => "DEBUG".bold().blue(),
Level::Warn => "WARN".bold().yellow(),
Level::Error => "ERROR".bold().red(),
Level::Trace => "TRACE".bold().purple(),
};
let module = record
.module_path()
.map(|path| path.split("::").last().unwrap_or(path))
.unwrap_or("unknown")
.dimmed();
let elapsed = start.elapsed().as_secs_f32();
let elapsed_display = format!("T+{elapsed:0.09}").dimmed();
let display = format!(
"[{elapsed_display}] {level_display:5} {module:^6} {args}",
args = record.args()
);
writeln!(buf, "{display}")
});
if let Some(path) = log_file {
let log_file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)
.expect("Failed to open log file");
logger.target(Target::Pipe(Box::new(log_file)));
}
if let Some(level) = log_level {
logger.filter_level(level).init();
} else {
logger.parse_env("DUST_LOG").init();
}
let input_bytes = if let Some(source_input) = input.input {
source_input
} else if let Some(path) = &input.source_file {
let mut source_file = File::open(path).expect("Failed to open source file");
let file_length = source_file
.metadata()
.expect("Failed to read file metadata")
.len();
let mut buffer = Vec::with_capacity(file_length as usize);
source_file
.read_to_end(&mut buffer)
.expect("Failed to read source file");
buffer
} else {
let mut buffer = Vec::new();
stdin()
.read_to_end(&mut buffer)
.expect("Failed to read from stdin");
buffer
};
match command {
Command::Format {
color,
indent,
line_numbers,
} => {
let source = String::from_utf8(input_bytes).expect("Failed to parse input as UTF-8");
let formatted = match format(&source, color, indent, line_numbers) {
Ok(formatted) => formatted,
Err(dust_error) => {
let report = dust_error.report();
return Err(report);
}
};
if let Some(path) = output.output_file {
let mut file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)
.expect("Failed to open output file");
file.write_all(formatted.as_bytes())
.expect("Failed to write to output file");
} else {
println!("{}", formatted);
}
return Ok(());
}
Command::Tokenize { style } => {
let source = String::from_utf8(input_bytes).expect("Failed to parse input as UTF-8");
let tokens = match lex(&source) {
Ok(tokens) => tokens,
Err(dust_error) => {
let report = dust_error.report();
return Err(report);
}
};
if let Some(path) = output.output_file {
let mut file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)
.expect("Failed to open output file");
display_token_list(&tokens, style, &mut file)
} else {
display_token_list(&tokens, style, &mut stdout())
}
return Ok(());
}
_ => {}
}
let chunk = match input.read_format {
IoFormat::Source => {
let source = String::from_utf8(input_bytes).expect("Failed to parse input as UTF-8");
match compile(&source) {
Ok(chunk) => chunk,
Err(dust_error) => {
let report = dust_error.report();
return Err(report);
}
}
}
IoFormat::Json => {
serde_json::from_slice(&input_bytes).expect("Failed to deserialize chunk from JSON")
}
IoFormat::Postcard => {
postcard::from_bytes(&input_bytes).expect("Failed to deserialize chunk from Postcard")
}
};
match command {
Command::Run => match run_chunk(&chunk) {
Ok(Some(value)) => {
println!("{}", value);
return Ok(());
}
Ok(None) => return Ok(()),
Err(dust_error) => {
let report = dust_error.report();
return Err(report);
}
},
Command::Disassemble { style } => {
let disassembly = chunk.disassembler().style(style).disassemble();
println!("{disassembly}");
return Ok(());
}
_ => {}
}
let output_bytes = match output.write_format {
IoFormat::Source => {
return Err("Invalid options, cannot compile chunk as source code.".to_string())
}
IoFormat::Json => serde_json::to_vec(&chunk).expect("Failed to serialize chunk as JSON"),
IoFormat::Postcard => {
let length = postcard::experimental::serialized_size(&chunk)
.expect("Failed to calculate Postcard size");
let mut buffer = vec![0_u8; length as usize];
postcard::to_slice(&chunk, &mut buffer).expect("Failed to serialize chunk as Postcard");
buffer
}
};
if let Some(path) = output.output_file {
let mut file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)
.expect("Failed to open output file");
file.write_all(&output_bytes)
.expect("Failed to write to output file");
} else {
stdout()
.write_all(&output_bytes)
.expect("Failed to write to stdout");
}
Ok(())
}
#[cfg(test)]
mod tests {
use clap::CommandFactory;
use super::*;
#[test]
fn verify_cli() {
Cli::command().debug_assert();
}
}

View File

@ -5,6 +5,7 @@
//! belong to a named function.
use std::fmt::{self, Debug, Display, Formatter};
use std::hash::{Hash, Hasher};
use serde::{Deserialize, Serialize};
@ -181,21 +182,21 @@ impl Chunk {
impl Display for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disassembler = self.disassembler().styled(false);
let disassembly = self.disassembler().style(true).disassemble();
write!(f, "{}", disassembler.disassemble())
write!(f, "{disassembly}")
}
}
impl Debug for Chunk {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let disassembly = self.disassembler().styled(false).disassemble();
let disassembly = self.disassembler().style(false).disassemble();
if cfg!(debug_assertions) {
write!(f, "\n{}", disassembly)
} else {
write!(f, "{}", disassembly)
write!(f, "\n",)?;
}
write!(f, "{}", disassembly)
}
}
@ -237,6 +238,12 @@ 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 {

View File

@ -4,6 +4,7 @@
//! - [`compile`], which compiles the entire input and returns a chunk
//! - [`Compiler`], which compiles the input a token at a time while assembling a chunk
use std::{
collections::HashMap,
fmt::{self, Display, Formatter},
mem::replace,
num::{ParseFloatError, ParseIntError},
@ -44,19 +45,18 @@ pub fn compile(source: &str) -> Result<Chunk, DustError> {
/// 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, PartialOrd)]
#[derive(Debug, Eq, PartialEq)]
pub struct Compiler<'src> {
chunk: Chunk,
lexer: Lexer<'src>,
local_declarations: HashMap<u8, u8>,
current_token: Token<'src>,
current_position: Span,
previous_token: Token<'src>,
previous_position: Span,
return_type: Option<Type>,
local_definitions: Vec<u8>,
optimization_count: usize,
previous_expression_type: Type,
minimum_register: u8,
@ -79,12 +79,12 @@ impl<'src> Compiler<'src> {
Ok(Compiler {
chunk,
lexer,
local_declarations: HashMap::new(),
current_token,
current_position,
previous_token: Token::Eof,
previous_position: Span(0, 0),
return_type: None,
local_definitions: Vec::new(),
optimization_count: 0,
previous_expression_type: Type::None,
minimum_register: 0,
@ -113,21 +113,20 @@ impl<'src> Compiler<'src> {
matches!(self.current_token, Token::Eof)
}
fn next_register(&mut self) -> u8 {
fn next_register(&self) -> u8 {
self.chunk
.instructions()
.iter()
.rev()
.find_map(|(instruction, _)| {
.filter_map(|(instruction, _)| {
if instruction.yields_value() {
let previous = instruction.a();
let next = previous.overflowing_add(1).0;
let to_register = instruction.a();
Some(next)
Some(to_register + 1)
} else {
None
}
})
.max()
.unwrap_or(self.minimum_register)
}
@ -196,7 +195,7 @@ impl<'src> Compiler<'src> {
scope: Scope,
register_index: u8,
) -> (u8, u8) {
log::debug!("Declare local {identifier}");
log::info!("Declaring local {identifier}");
let identifier = ConcreteValue::string(identifier);
let identifier_index = self.chunk.push_or_get_constant(identifier);
@ -205,11 +204,35 @@ impl<'src> Compiler<'src> {
self.chunk
.locals_mut()
.push(Local::new(identifier_index, r#type, is_mutable, scope));
self.local_definitions.push(register_index);
self.local_declarations.insert(local_index, register_index);
(local_index, identifier_index)
}
fn redeclare_local(&mut self, local_index: u8, register_index: u8) -> Result<(), CompileError> {
let local = self.get_local(local_index)?;
if !self.current_scope.contains(&local.scope) {
let identifier = self
.chunk
.constants()
.get(local.identifier_index as usize)
.map(|constant| constant.to_string())
.unwrap_or("unknown".to_string());
return Err(CompileError::VariableOutOfScope {
identifier,
variable_scope: local.scope,
access_scope: self.current_scope,
position: self.current_position,
});
}
self.local_declarations.insert(local_index, register_index);
Ok(())
}
fn allow(&mut self, allowed: Token) -> Result<bool, CompileError> {
if self.current_token == allowed {
self.advance()?;
@ -673,10 +696,7 @@ impl<'src> Compiler<'src> {
let local = self.get_local(local_index)?;
is_mutable_local = local.is_mutable;
*self
.local_definitions
.get(local_index as usize)
.ok_or_else(|| {
*self.local_declarations.get(&local_index).ok_or_else(|| {
let identifier = self
.chunk
.constants()
@ -733,7 +753,7 @@ impl<'src> Compiler<'src> {
let operator = self.current_token;
let operator_position = self.current_position;
let rule = ParseRule::from(&operator);
let is_assignment =
if let Token::PlusEqual | Token::MinusEqual | Token::StarEqual | Token::SlashEqual =
operator
{
@ -743,7 +763,11 @@ impl<'src> Compiler<'src> {
position: left_position,
});
}
}
true
} else {
false
};
self.advance()?;
self.parse_sub_expression(&rule.precedence)?;
@ -756,7 +780,7 @@ impl<'src> Compiler<'src> {
self.emit_instruction(right_instruction, right_position);
}
let register = if left_is_mutable_local {
let register = if is_assignment {
left
} else {
self.next_register()
@ -818,7 +842,7 @@ impl<'src> Compiler<'src> {
}
fn parse_comparison_binary(&mut self) -> Result<(), CompileError> {
if let Some([Operation::Equal | Operation::Less | Operation::LessEqual, _, _, _]) =
if let Some([Operation::Equal | Operation::Less | Operation::LessEqual, _, _]) =
self.get_last_operations()
{
return Err(CompileError::CannotChainComparison {
@ -911,15 +935,23 @@ impl<'src> Compiler<'src> {
}
fn parse_logical_binary(&mut self) -> Result<(), CompileError> {
let start_length = self.chunk.len();
let (left_instruction, left_position) = self.pop_last_instruction()?;
if !left_instruction.yields_value() {
return Err(CompileError::ExpectedExpression {
found: self.previous_token.to_owned(),
position: self.previous_position,
});
}
let (_, is_constant, _, _) = self.handle_binary_argument(&left_instruction)?;
let operator = self.current_token;
let operator_position = self.current_position;
let rule = ParseRule::from(&operator);
let test_instruction = match operator {
Token::DoubleAmpersand => Instruction::test(left_instruction.a(), false),
Token::DoublePipe => Instruction::test(left_instruction.a(), true),
let test_register = left_instruction.a();
let mut test_instruction = match operator {
Token::DoubleAmpersand => Instruction::test(test_register, true),
Token::DoublePipe => Instruction::test(test_register, false),
_ => {
return Err(CompileError::ExpectedTokenMultiple {
expected: &[TokenKind::DoubleAmpersand, TokenKind::DoublePipe],
@ -929,13 +961,14 @@ impl<'src> Compiler<'src> {
}
};
if is_constant {
test_instruction.set_b_is_constant();
}
self.advance()?;
self.emit_instruction(left_instruction, left_position);
self.emit_instruction(test_instruction, operator_position);
let jump_distance = (self.chunk.len() - start_length) as u8;
self.emit_instruction(Instruction::jump(jump_distance, true), operator_position);
self.emit_instruction(Instruction::jump(1, true), operator_position);
self.parse_sub_expression(&rule.precedence)?;
self.previous_expression_type = Type::Boolean;
@ -975,17 +1008,14 @@ impl<'src> Compiler<'src> {
});
};
let (is_mutable, local_scope) = {
let local = self.get_local(local_index)?;
let is_mutable = local.is_mutable;
(local.is_mutable, local.scope)
};
if !self.current_scope.contains(&local_scope) {
if !self.current_scope.contains(&local.scope) {
return Err(CompileError::VariableOutOfScope {
identifier: self.chunk.get_identifier(local_index).unwrap(),
position: start_position,
variable_scope: local_scope,
variable_scope: local.scope,
access_scope: self.current_scope,
});
}
@ -998,10 +1028,10 @@ impl<'src> Compiler<'src> {
});
}
self.parse_expression()?;
let register = self.next_register() - 1;
self.parse_expression()?;
self.redeclare_local(local_index, register)?;
self.emit_instruction(
Instruction::set_local(register, local_index),
start_position,
@ -1055,15 +1085,16 @@ impl<'src> Compiler<'src> {
fn parse_block(&mut self) -> Result<(), CompileError> {
self.advance()?;
self.block_index += 1;
let starting_block = self.current_scope.block_index;
self.block_index += 1;
self.current_scope.begin(self.block_index);
while !self.allow(Token::RightBrace)? && !self.is_eof() {
self.parse(Precedence::None)?;
}
self.current_scope.end();
self.current_scope.end(starting_block);
Ok(())
}
@ -1135,6 +1166,13 @@ impl<'src> Compiler<'src> {
self.chunk.instructions_mut().pop();
self.chunk.instructions_mut().pop();
self.chunk.instructions_mut().pop();
} else if let Some((instruction, _)) = self.chunk.instructions().last() {
let test_register = instruction.a();
self.emit_instruction(
Instruction::test(test_register, true),
self.current_position,
)
}
let if_block_start = self.chunk.len();
@ -1229,9 +1267,8 @@ impl<'src> Compiler<'src> {
if self.chunk.len() >= 4 {
let mut optimizer = Optimizer::new(&mut self.chunk);
let optimized = optimizer.optimize_comparison();
if optimized {
if optimizer.optimize_control_flow() {
self.optimization_count += 1
}
}
@ -1449,7 +1486,7 @@ impl<'src> Compiler<'src> {
fn parse_function(&mut self) -> Result<(), CompileError> {
let function_start = self.current_position.0;
let mut function_compiler = Compiler::new(self.lexer)?;
let identifier = if let Token::Identifier(text) = function_compiler.current_token {
let identifier_info = if let Token::Identifier(text) = function_compiler.current_token {
let position = function_compiler.current_position;
function_compiler.advance()?;
@ -1544,7 +1581,7 @@ impl<'src> Compiler<'src> {
self.lexer.skip_to(function_end);
if let Some((identifier, identifier_position)) = identifier {
if let Some((identifier, position)) = identifier_info {
let (local_index, _) = self.declare_local(
identifier,
Type::Function(function_type),
@ -1559,7 +1596,7 @@ impl<'src> Compiler<'src> {
);
self.emit_instruction(
Instruction::define_local(register, local_index, false),
identifier_position,
position,
);
self.previous_expression_type = Type::None;

View File

@ -50,22 +50,22 @@ use crate::{value::ConcreteValue, Chunk, Local};
const INSTRUCTION_HEADER: [&str; 4] = [
"Instructions",
"------------",
" i BYTECODE OPERATION INFO POSITION ",
"--- -------- ------------- ------------------------- ----------",
" i POSITION BYTECODE OPERATION INFO ",
"--- ---------- -------- ------------- ------------------------------------",
];
const CONSTANT_HEADER: [&str; 4] = [
"Constants",
"---------",
" i VALUE ",
"--- ---------------",
" i TYPE VALUE ",
"--- ---------------- -----------------",
];
const LOCAL_HEADER: [&str; 4] = [
"Locals",
"------",
" i IDENTIFIER TYPE MUTABLE SCOPE ",
"--- ---------- ---------------- ------- -------",
" i SCOPE MUTABLE TYPE IDENTIFIER ",
"--- ----- ------- ---------------- ----------------",
];
/// Builder that constructs a human-readable representation of a chunk.
@ -77,7 +77,7 @@ pub struct Disassembler<'a> {
source: Option<&'a str>,
// Options
styled: bool,
style: bool,
indent: usize,
}
@ -87,33 +87,26 @@ impl<'a> Disassembler<'a> {
output: String::new(),
chunk,
source: None,
styled: false,
style: false,
indent: 0,
}
}
/// The default width of the disassembly output. To correctly align the output, this should
/// return the width of the longest line that the disassembler is guaranteed to produce.
/// The default width of the disassembly output, including borders.
pub fn default_width() -> usize {
let longest_line = INSTRUCTION_HEADER[3];
longest_line.chars().count().max(80)
(longest_line.chars().count() + 2).max(80)
}
pub fn source(mut self, source: &'a str) -> Self {
pub fn set_source(&mut self, source: &'a str) -> &mut Self {
self.source = Some(source);
self
}
pub fn styled(mut self, styled: bool) -> Self {
self.styled = styled;
self
}
pub fn indent(mut self, indent: usize) -> Self {
self.indent = indent;
pub fn style(mut self, styled: bool) -> Self {
self.style = styled;
self
}
@ -141,21 +134,16 @@ impl<'a> Disassembler<'a> {
(0, extra_space)
}
};
let content = if style_bold {
line_characters
.iter()
.collect::<String>()
.bold()
.to_string()
} else if style_dim {
line_characters
.iter()
.collect::<String>()
.dimmed()
.to_string()
} else {
line_characters.iter().collect::<String>()
};
let mut content = line_characters.iter().collect::<String>();
if style_bold {
content = content.bold().to_string();
}
if style_dim {
content = content.dimmed().to_string();
}
let length_before_content = self.output.chars().count();
for _ in 0..self.indent {
@ -195,8 +183,16 @@ impl<'a> Disassembler<'a> {
}
}
fn push_source(&mut self, source: &str) {
self.push(source, true, false, false, true);
}
fn push_chunk_info(&mut self, info: &str) {
self.push(info, true, false, true, true);
}
fn push_header(&mut self, header: &str) {
self.push(header, true, self.styled, false, true);
self.push(header, true, self.style, false, true);
}
fn push_details(&mut self, details: &str) {
@ -240,12 +236,7 @@ impl<'a> Disassembler<'a> {
if let Some(source) = self.source {
self.push_empty();
self.push_details(
&source
.replace(" ", "")
.replace("\n\n", " ")
.replace('\n', " "),
);
self.push_source(&source.split_whitespace().collect::<Vec<&str>>().join(" "));
self.push_empty();
}
@ -257,7 +248,7 @@ impl<'a> Disassembler<'a> {
self.chunk.r#type().return_type
);
self.push(&info_line, true, false, true, true);
self.push_chunk_info(&info_line);
self.push_empty();
for line in INSTRUCTION_HEADER {
@ -265,13 +256,13 @@ impl<'a> Disassembler<'a> {
}
for (index, (instruction, position)) in self.chunk.instructions().iter().enumerate() {
let position = position.to_string();
let bytecode = format!("{:02X}", u32::from(instruction));
let operation = instruction.operation().to_string();
let info = instruction.disassembly_info(self.chunk);
let position = position.to_string();
let instruction_display =
format!("{index:^3} {bytecode:>8} {operation:13} {info:<25} {position:10}");
format!("{index:^3} {position:^10} {bytecode:>8} {operation:13} {info:^36}");
self.push_details(&instruction_display);
}
@ -300,7 +291,7 @@ impl<'a> Disassembler<'a> {
.unwrap_or_else(|| "unknown".to_string());
let type_display = r#type.to_string();
let local_display = format!(
"{index:^3} {identifier_display:10} {type_display:16} {mutable:7} {scope:7}"
"{index:^3} {scope:5} {mutable:^7} {type_display:^16} {identifier_display:^16}"
);
self.push_details(&local_display);
@ -314,35 +305,34 @@ impl<'a> Disassembler<'a> {
for (index, value) in self.chunk.constants().iter().enumerate() {
if let ConcreteValue::Function(chunk) = value {
let function_disassembly = chunk
.disassembler()
.styled(self.styled)
.indent(self.indent + 1)
.disassemble();
let mut function_disassembler = chunk.disassembler().style(self.style);
function_disassembler.indent = self.indent + 1;
let function_disassembly = function_disassembler.disassemble();
self.output.push_str(&function_disassembly);
continue;
}
let type_display = value.r#type().to_string();
let value_display = {
let value_string = value.to_string();
let mut value_string = value.to_string();
if value_string.len() > 15 {
format!("{value_string:.12}...")
} else {
value_string
value_string = format!("{value_string:.12}...");
}
value_string
};
let constant_display = format!("{index:^3} {value_display:^15}");
let constant_display = format!("{index:^3} {type_display:^16} {value_display:^17}");
self.push_details(&constant_display);
}
self.push_border(&bottom_border);
let _ = self.output.trim_end_matches('\n');
self.output
self.output.to_string()
}
}

View File

@ -5,7 +5,12 @@ use colored::{ColoredString, Colorize, CustomColor};
use crate::{CompileError, DustError, LexError, Lexer, Token};
pub fn format(source: &str, line_numbers: bool, colored: bool) -> Result<String, DustError> {
pub fn format(
source: &str,
colored: bool,
indent: usize,
line_numbers: bool,
) -> Result<String, DustError> {
let lexer = Lexer::new(source);
let formatted = Formatter::new(lexer)
.line_numbers(line_numbers)

View File

@ -14,7 +14,7 @@
use serde::{Deserialize, Serialize};
use crate::{Chunk, NativeFunction, Operation, Type};
use crate::{Chunk, NativeFunction, Operation};
/// An operation and its arguments for the Dust virtual machine.
///
@ -160,10 +160,10 @@ impl Instruction {
instruction
}
pub fn test(to_register: u8, test_value: bool) -> Instruction {
pub fn test(test_register: u8, test_value: bool) -> Instruction {
let mut instruction = Instruction(Operation::Test as u32);
instruction.set_a(to_register);
instruction.set_b(test_register);
instruction.set_c_to_boolean(test_value);
instruction
@ -375,9 +375,11 @@ impl Instruction {
}
pub fn yields_value(&self) -> bool {
match self.operation() {
matches!(
self.operation(),
Operation::Add
| Operation::Call
| Operation::CallNative
| Operation::Divide
| Operation::GetLocal
| Operation::LoadBoolean
@ -388,14 +390,9 @@ impl Instruction {
| Operation::Multiply
| Operation::Negate
| Operation::Not
| Operation::Subtract => true,
Operation::CallNative => {
let native_function = NativeFunction::from(self.b());
*native_function.r#type().return_type != Type::None
}
_ => false,
}
| Operation::Subtract
| Operation::TestSet
)
}
pub fn disassembly_info(&self, chunk: &Chunk) -> String {
@ -428,7 +425,7 @@ impl Instruction {
let jump = self.c_as_boolean();
if jump {
format!("R{to_register} = {boolean} && SKIP")
format!("R{to_register} = {boolean} && JUMP +1")
} else {
format!("R{to_register} = {boolean}")
}
@ -439,7 +436,7 @@ impl Instruction {
let jump = self.c_as_boolean();
if jump {
format!("R{register_index} = C{constant_index} && SKIP")
format!("R{register_index} = C{constant_index} JUMP +1")
} else {
format!("R{register_index} = C{constant_index}")
}
@ -463,13 +460,8 @@ impl Instruction {
Operation::DefineLocal => {
let to_register = self.a();
let local_index = self.b();
let mutable_display = if self.c_as_boolean() { "mut" } else { "" };
let identifier_display = match chunk.get_identifier(local_index) {
Some(identifier) => identifier.to_string(),
None => "???".to_string(),
};
format!("R{to_register} = L{local_index} {mutable_display} {identifier_display}")
format!("L{local_index} = R{to_register}")
}
Operation::GetLocal => {
let local_index = self.b();
@ -478,12 +470,9 @@ impl Instruction {
}
Operation::SetLocal => {
let local_index = self.b();
let identifier_display = match chunk.get_identifier(local_index) {
Some(identifier) => identifier.to_string(),
None => "???".to_string(),
};
let register = self.a();
format!("L{} = R{} {}", local_index, self.a(), identifier_display)
format!("L{local_index} = R{register}")
}
Operation::Add => {
let to_register = self.a();
@ -516,37 +505,42 @@ impl Instruction {
format!("R{to_register} = {first_argument} % {second_argument}",)
}
Operation::Test => {
let to_register = self.a();
let test_value = self.c_as_boolean();
format!("if R{to_register} != {test_value} {{ SKIP }}")
}
Operation::TestSet => {
let to_register = self.a();
let argument = format!("R{}", self.b());
let test_register = if self.b_is_constant() {
format!("C{}", self.b())
} else {
format!("R{}", self.b())
};
let test_value = self.c_as_boolean();
let bang = if test_value { "" } else { "!" };
format!("if {bang}R{to_register} {{ R{to_register} = R{argument} }}",)
format!("if {bang}{test_register} {{ JUMP +1 }}",)
}
Operation::TestSet => {
let to_register = self.a();
let test_register = self.b();
let test_value = self.c_as_boolean();
let bang = if test_value { "" } else { "!" };
format!("if {bang}R{test_register} {{ JUMP +1 }} else {{ R{to_register} = R{test_register} }}")
}
Operation::Equal => {
let comparison_symbol = if self.a_as_boolean() { "==" } else { "!=" };
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
format!("if {first_argument} {comparison_symbol} {second_argument} {{ JUMP +1 }}")
}
Operation::Less => {
let comparison_symbol = if self.a_as_boolean() { "<" } else { ">=" };
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
format!("if {first_argument} {comparison_symbol} {second_argument} {{ JUMP +1 }}")
}
Operation::LessEqual => {
let comparison_symbol = if self.a_as_boolean() { "<=" } else { ">" };
let (first_argument, second_argument) = format_arguments();
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
format!("if {first_argument} {comparison_symbol} {second_argument} {{ JUMP +1 }}")
}
Operation::Negate => {
let to_register = self.a();
@ -615,8 +609,8 @@ impl Instruction {
if argument_count != 0 {
let first_argument = to_register.saturating_sub(argument_count);
for (index, register) in (first_argument..to_register).enumerate() {
if index > 0 {
for register in first_argument..to_register {
if register != first_argument {
output.push_str(", ");
}
@ -788,16 +782,16 @@ mod tests {
}
#[test]
fn and() {
let instruction = Instruction::test(4, true);
fn test() {
let instruction = Instruction::test(42, true);
assert_eq!(instruction.operation(), Operation::Test);
assert_eq!(instruction.a(), 4);
assert_eq!(instruction.b(), 42);
assert!(instruction.c_as_boolean());
}
#[test]
fn or() {
fn test_set() {
let instruction = Instruction::test_set(4, 1, true);
assert_eq!(instruction.operation(), Operation::TestSet);

View File

@ -28,11 +28,9 @@ pub use crate::operation::Operation;
pub use crate::optimizer::Optimizer;
pub use crate::r#type::{EnumType, FunctionType, StructType, Type, TypeConflict};
pub use crate::scope::Scope;
pub use crate::token::{output_token_list, Token, TokenKind, TokenOwned};
pub use crate::value::{
AbstractValue, ConcreteValue, RangeValue, ValueError, ValueOwned, ValueRef,
};
pub use crate::vm::{run, Vm, VmError};
pub use crate::token::{display_token_list, Token, TokenKind, TokenOwned};
pub use crate::value::{AbstractValue, ConcreteValue, RangeValue, Value, ValueError, ValueRef};
pub use crate::vm::{run_source, Vm, VmError};
use std::fmt::Display;

View File

@ -1,8 +1,8 @@
use std::io::{self, stdout, Write};
use crate::{ConcreteValue, Instruction, NativeFunctionError, ValueOwned, Vm, VmError};
use crate::{ConcreteValue, Instruction, NativeFunctionError, Value, Vm, VmError};
pub fn panic<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<ValueOwned>, VmError> {
pub fn panic<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let argument_count = instruction.c();
let message = if argument_count == 0 {
None
@ -14,7 +14,7 @@ pub fn panic<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Valu
message.push(' ');
}
let argument = if let Some(value) = vm.open_register_ignore_empty(argument_index)? {
let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? {
value
} else {
continue;
@ -33,10 +33,7 @@ pub fn panic<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Valu
}))
}
pub fn to_string<'a>(
vm: &'a Vm<'a>,
instruction: Instruction,
) -> Result<Option<ValueOwned>, VmError> {
pub fn to_string<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let argument_count = instruction.c();
if argument_count != 1 {
@ -52,7 +49,7 @@ pub fn to_string<'a>(
let mut string = String::new();
for argument_index in 0..argument_count {
let argument = if let Some(value) = vm.open_register_ignore_empty(argument_index)? {
let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? {
value
} else {
continue;
@ -62,13 +59,10 @@ pub fn to_string<'a>(
string.push_str(&argument_string);
}
Ok(Some(ValueOwned::Concrete(ConcreteValue::String(string))))
Ok(Some(Value::Concrete(ConcreteValue::String(string))))
}
pub fn read_line<'a>(
vm: &'a Vm<'a>,
instruction: Instruction,
) -> Result<Option<ValueOwned>, VmError> {
pub fn read_line<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let argument_count = instruction.c();
if argument_count != 0 {
@ -87,7 +81,7 @@ pub fn read_line<'a>(
Ok(_) => {
buffer = buffer.trim_end_matches('\n').to_string();
Ok(Some(ValueOwned::Concrete(ConcreteValue::String(buffer))))
Ok(Some(Value::Concrete(ConcreteValue::String(buffer))))
}
Err(error) => Err(VmError::NativeFunction(NativeFunctionError::Io {
error: error.kind(),
@ -96,7 +90,7 @@ pub fn read_line<'a>(
}
}
pub fn write<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<ValueOwned>, VmError> {
pub fn write<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let to_register = instruction.a();
let argument_count = instruction.c();
let mut stdout = stdout();
@ -114,7 +108,7 @@ pub fn write<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Valu
stdout.write(b" ").map_err(map_err)?;
}
let argument = if let Some(value) = vm.open_register_ignore_empty(argument_index)? {
let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? {
value
} else {
continue;
@ -129,10 +123,7 @@ pub fn write<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Valu
Ok(None)
}
pub fn write_line<'a>(
vm: &'a Vm<'a>,
instruction: Instruction,
) -> Result<Option<ValueOwned>, VmError> {
pub fn write_line<'a>(vm: &'a Vm<'a>, instruction: Instruction) -> Result<Option<Value>, VmError> {
let to_register = instruction.a();
let argument_count = instruction.c();
let mut stdout = stdout();
@ -150,7 +141,7 @@ pub fn write_line<'a>(
stdout.write(b" ").map_err(map_err)?;
}
let argument = if let Some(value) = vm.open_register_ignore_empty(argument_index)? {
let argument = if let Some(value) = vm.open_register_allow_empty(argument_index)? {
value
} else {
continue;

View File

@ -12,7 +12,7 @@ use std::{
use serde::{Deserialize, Serialize};
use crate::{AnnotatedError, FunctionType, Instruction, Span, Type, ValueOwned, Vm, VmError};
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)),*) => {
@ -31,7 +31,7 @@ macro_rules! define_native_function {
&self,
vm: &mut Vm,
instruction: Instruction,
) -> Result<Option<ValueOwned>, VmError> {
) -> Result<Option<Value>, VmError> {
match self {
$(
NativeFunction::$name => $function(vm, instruction),

View File

@ -16,11 +16,11 @@ impl<'a> Optimizer<'a> {
}
}
/// Optimizes a comparison operation.
/// Optimizes a short control flow pattern.
///
/// Comparison instructions (which are always followed by a JUMP) can be optimized when the
/// next instructions are two constant or boolean loaders. The first loader is set to skip an
/// instruction if it is run while the second loader is modified to use the first's register.
/// Comparison and test instructions (which are always followed by a JUMP) can be optimized when
/// the next instructions are two constant or boolean loaders. The first loader is set to skip
/// an instruction if it is run while the second loader is modified to use the first's register.
/// This makes the following two code snippets compile to the same bytecode:
///
/// ```dust
@ -32,15 +32,15 @@ impl<'a> Optimizer<'a> {
/// ```
///
/// The instructions must be in the following order:
/// - `Operation::Equal | Operation::Less | Operation::LessEqual`
/// - `Operation::Equal` | `Operation::Less` | `Operation::LessEqual` | `Operation::Test`
/// - `Operation::Jump`
/// - `Operation::LoadBoolean | Operation::LoadConstant`
/// - `Operation::LoadBoolean | Operation::LoadConstant`
pub fn optimize_comparison(&mut self) -> bool {
/// - `Operation::LoadBoolean` | `Operation::LoadConstant`
/// - `Operation::LoadBoolean` | `Operation::LoadConstant`
pub fn optimize_control_flow(&mut self) -> bool {
if !matches!(
self.get_operations(),
Some([
Operation::Equal | Operation::Less | Operation::LessEqual,
Operation::Equal | Operation::Less | Operation::LessEqual | Operation::Test,
Operation::Jump,
Operation::LoadBoolean | Operation::LoadConstant,
Operation::LoadBoolean | Operation::LoadConstant,
@ -49,24 +49,20 @@ impl<'a> Optimizer<'a> {
return false;
}
log::debug!("Optimizing comparison");
log::debug!("Consolidating registers for control flow optimization");
let instructions = self.instructions_mut();
let first_loader_register = {
let first_loader = &mut instructions[2].0;
let first_loader = &mut instructions.iter_mut().nth_back(1).unwrap().0;
first_loader.set_c_to_boolean(true);
first_loader.a()
};
let second_loader = &mut instructions[3].0;
let first_loader_register = first_loader.a();
let second_loader = &mut instructions.last_mut().unwrap().0;
let mut second_loader_new = Instruction::with_operation(second_loader.operation());
second_loader_new.set_a(first_loader_register);
second_loader_new.set_b(second_loader.b());
second_loader_new.set_c(second_loader.c());
second_loader_new.set_b_to_boolean(second_loader.b_is_constant());
second_loader_new.set_c_to_boolean(second_loader.c_is_constant());
*second_loader = second_loader_new;
@ -88,9 +84,27 @@ impl<'a> Optimizer<'a> {
return false;
}
self.instructions_mut().pop();
log::debug!("Condensing math and SetLocal to math instruction");
log::debug!("Optimizing by removing redundant SetLocal");
let instructions = self.instructions_mut();
let set_local = instructions.pop().unwrap().0;
let set_local_register = set_local.a();
let math_instruction = &mut instructions.last_mut().unwrap().0;
let mut math_instruction_new = Instruction::with_operation(math_instruction.operation());
math_instruction_new.set_a(set_local_register);
math_instruction_new.set_b(math_instruction.b());
math_instruction_new.set_c(math_instruction.c());
if math_instruction.b_is_constant() {
math_instruction_new.set_b_is_constant();
}
if math_instruction.c_is_constant() {
math_instruction_new.set_c_is_constant();
}
*math_instruction = math_instruction_new;
true
}

View File

@ -36,19 +36,14 @@ impl Scope {
#[inline]
pub fn begin(&mut self, block_index: u8) {
self.block_index = block_index;
self.depth += 1;
self.block_index = block_index;
}
#[inline]
pub fn end(&mut self) {
pub fn end(&mut self, block_index: u8) {
self.depth -= 1;
if self.depth == 0 {
self.block_index = 0;
} else {
self.block_index -= 1;
}
self.block_index = block_index;
}
}

View File

@ -4,24 +4,25 @@ use std::{
io::Write,
};
use colored::Colorize;
use serde::{Deserialize, Serialize};
use crate::Span;
pub fn output_token_list<W: Write>(tokens: &[(Token, Span)], writer: &mut W) {
const HEADER: [&str; 2] = [
"TOKEN KIND POSITION ",
"------------ ---------- ----------",
];
pub fn display_token_list<W: Write>(tokens: &[(Token, Span)], styled: bool, writer: &mut W) {
const HEADER: [&str; 2] = [" TOKEN POSITION ", "------------- ----------"];
writeln!(writer, "{}", HEADER[0]).unwrap();
writeln!(writer, "{}", HEADER[1]).unwrap();
for (token, position) in tokens {
let kind = token.kind().to_string();
let token = token.to_string();
let token = if styled {
format!("{:^13}", token.to_string().bold())
} else {
format!("{token:^13}")
};
writeln!(writer, "{token:<12} {kind:<10} {position}").unwrap();
writeln!(writer, "{token} {position:<10}").unwrap();
}
}

View File

@ -12,23 +12,23 @@ use std::fmt::{self, Debug, Display, Formatter};
use crate::{Vm, VmError};
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum ValueOwned {
pub enum Value {
Abstract(AbstractValue),
Concrete(ConcreteValue),
}
impl ValueOwned {
impl Value {
pub fn to_concrete_owned(&self, vm: &Vm) -> Result<ConcreteValue, VmError> {
match self {
ValueOwned::Abstract(abstract_value) => abstract_value.to_concrete_owned(vm),
ValueOwned::Concrete(concrete_value) => Ok(concrete_value.clone()),
Value::Abstract(abstract_value) => abstract_value.to_concrete_owned(vm),
Value::Concrete(concrete_value) => Ok(concrete_value.clone()),
}
}
pub fn display(&self, vm: &Vm) -> Result<String, VmError> {
match self {
ValueOwned::Abstract(abstract_value) => abstract_value.display(vm),
ValueOwned::Concrete(concrete_value) => Ok(concrete_value.to_string()),
Value::Abstract(abstract_value) => abstract_value.display(vm),
Value::Concrete(concrete_value) => Ok(concrete_value.to_string()),
}
}
}

View File

@ -8,11 +8,10 @@ use std::{
use crate::{
compile, AbstractValue, AnnotatedError, Chunk, ChunkError, ConcreteValue, DustError,
Instruction, NativeFunction, NativeFunctionError, Operation, Span, ValueError, ValueOwned,
ValueRef,
Instruction, NativeFunction, NativeFunctionError, Operation, Span, Value, ValueError, ValueRef,
};
pub fn run(source: &str) -> Result<Option<ConcreteValue>, DustError> {
pub fn run_source(source: &str) -> Result<Option<ConcreteValue>, DustError> {
let chunk = compile(source)?;
let mut vm = Vm::new(&chunk, None);
@ -20,25 +19,24 @@ pub fn run(source: &str) -> Result<Option<ConcreteValue>, DustError> {
.map_err(|error| DustError::Runtime { error, source })
}
pub fn run_and_display_output(source: &str) {
match run(source) {
Ok(Some(value)) => println!("{}", value),
Ok(None) => {}
Err(error) => eprintln!("{}", error.report()),
}
pub fn run_chunk(chunk: &Chunk) -> Result<Option<ConcreteValue>, DustError> {
let mut vm = Vm::new(chunk, None);
vm.run()
.map_err(|error| DustError::Runtime { error, source: "" })
}
/// Dust virtual machine.
///
/// See the [module-level documentation](index.html) for more information.
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct Vm<'a> {
chunk: &'a Chunk,
stack: Vec<Register>,
parent: Option<&'a Vm<'a>>,
local_definitions: HashMap<u8, u8>,
ip: usize,
local_definitions: HashMap<u8, u8>,
last_assigned_register: Option<u8>,
current_position: Span,
}
@ -51,8 +49,8 @@ impl<'a> Vm<'a> {
chunk,
stack: Vec::new(),
parent,
ip: 0,
local_definitions: HashMap::new(),
ip: 0,
last_assigned_register: None,
current_position: Span(0, 0),
}
@ -186,6 +184,7 @@ impl<'a> Vm<'a> {
)?;
let register = Register::Pointer(Pointer::Stack(from_register));
self.define_local(to_local, from_register)?;
self.set_register(local_register, register)?;
}
Operation::Add => {
@ -239,9 +238,9 @@ impl<'a> Vm<'a> {
self.set_register(to_register, Register::ConcreteValue(remainder))?;
}
Operation::Test => {
let register = instruction.a();
let test_register = instruction.b();
let test_value = instruction.c_as_boolean();
let value = self.open_register(register)?;
let value = self.open_register(test_register)?;
let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value
{
*boolean
@ -252,11 +251,33 @@ impl<'a> Vm<'a> {
});
};
if boolean != test_value {
if boolean == test_value {
self.ip += 1;
}
}
Operation::TestSet => todo!(),
Operation::TestSet => {
let to_register = instruction.a();
let test_register = instruction.b();
let test_value = instruction.c_as_boolean();
let value = self.open_register(test_register)?;
let boolean = if let ValueRef::Concrete(ConcreteValue::Boolean(boolean)) = value
{
*boolean
} else {
return Err(VmError::ExpectedBoolean {
found: value.to_concrete_owned(self)?,
position: self.current_position,
});
};
if boolean == test_value {
self.ip += 1;
} else {
let register = Register::Pointer(Pointer::Stack(test_register));
self.set_register(to_register, register)?;
}
}
Operation::Equal => {
let compare_to = instruction.a_as_boolean();
let (left, right) = self.get_arguments(instruction)?;
@ -394,10 +415,10 @@ impl<'a> Vm<'a> {
let to_register = instruction.a();
let register = match value {
ValueOwned::Abstract(abstract_value) => {
Value::Abstract(abstract_value) => {
Register::AbstractValue(abstract_value)
}
ValueOwned::Concrete(concrete_value) => {
Value::Concrete(concrete_value) => {
Register::ConcreteValue(concrete_value)
}
};
@ -485,7 +506,7 @@ impl<'a> Vm<'a> {
}
}
pub(crate) fn open_register_ignore_empty(
pub(crate) fn open_register_allow_empty(
&self,
register_index: u8,
) -> Result<Option<ValueRef>, VmError> {

View File

@ -22,7 +22,7 @@ fn constant() {
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(42))));
}
#[test]
@ -43,7 +43,7 @@ fn empty() {
vec![]
))
);
assert_eq!(run(source), Ok(None));
assert_eq!(run_source(source), Ok(None));
}
#[test]
@ -81,5 +81,5 @@ fn parentheses_precedence() {
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(9))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(9))));
}

View File

@ -30,7 +30,7 @@ fn equal() {
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(false))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Boolean(false))));
}
#[test]
@ -63,7 +63,7 @@ fn greater() {
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(false))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Boolean(false))));
}
#[test]
@ -96,7 +96,7 @@ fn greater_than_or_equal() {
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(false))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Boolean(false))));
}
#[test]
@ -129,7 +129,7 @@ fn less_than() {
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Boolean(true))));
}
#[test]
@ -162,7 +162,7 @@ fn less_than_or_equal() {
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Boolean(true))));
}
#[test]
@ -195,5 +195,5 @@ fn not_equal() {
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Boolean(true))));
}

View File

@ -243,76 +243,6 @@ fn if_else_complex() {
assert_eq!(run(source), Ok(None));
}
// #[test]
// fn if_else_nested() {
// let source = r#"
// if 0 == 1 {
// if 0 == 2 {
// 1;
// } else {
// 2;
// }
// } else {
// if 0 == 3 {
// 3;
// } else {
// 4;
// }
// }"#;
// assert_eq!(
// parse(source),
// Ok(Chunk::with_data(
// None,
// vec![
// (
// *Instruction::equal(true, 0, 1)
// .set_b_is_constant()
// .set_c_is_constant(),
// Span(14, 16)
// ),
// (Instruction::jump(7, true), Span(14, 16)),
// (
// *Instruction::equal(true, 0, 2)
// .set_b_is_constant()
// .set_c_is_constant(),
// Span(38, 41)
// ),
// (Instruction::jump(3, true), Span(38, 41)),
// (Instruction::load_constant(0, 1, false), Span(61, 62)),
// (Instruction::jump(1, true1), Span(95, 95)),
// (
// *Instruction::equal(true, 0, 3)
// .set_b_is_constant()
// .set_c_is_constant(),
// Span(77, 79)
// ),
// (Instruction::jump(3, true), Span(77, 79)),
// (Instruction::load_constant(0, 2, false), Span(94, 95)),
// (Instruction::jump(1, true1), Span(95, 95)),
// (Instruction::load_constant(0, 3, false), Span(114, 115)),
// (Instruction::jump(1, true1), Span(95, 95)),
// (Instruction::load_constant(0, 4, false), Span(134, 135)),
// (Instruction::r#return(true), Span(146, 146)),
// ],
// vec![
// ConcreteValue::integer(0),
// ConcreteValue::integer(1),
// ConcreteValue::integer(0),
// ConcreteValue::integer(2),
// ConcreteValue::integer(1),
// ConcreteValue::integer(0),
// ConcreteValue::integer(3),
// ConcreteValue::integer(3),
// ConcreteValue::integer(4)
// ],
// vec![]
// ))
// );
// assert_eq!(run(source), Ok(Some(ConcreteValue::integer(4))));
// }
#[test]
fn if_else_false() {
let source = "if 1 == 2 { panic(); 0 } else { 42 }";

View File

@ -0,0 +1,34 @@
#[test]
fn if_true() {
let source = "if true && true { 42 } else { 0 }";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Box::new(Type::None)
},
vec![
(
*Instruction::equal(true, 0, 0)
.set_b_is_constant()
.set_c_is_constant(),
Span(5, 7)
),
(Instruction::jump(1, true), Span(10, 11)),
(
Instruction::call_native(0, NativeFunction::Panic, 0),
Span(12, 19)
),
(Instruction::r#return(false), Span(21, 21))
],
vec![ConcreteValue::Integer(1)],
vec![]
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42))),);
}

View File

@ -5,7 +5,7 @@ fn function() {
let source = "fn(a: int, b: int) -> int { a + b }";
assert_eq!(
run(source),
run_source(source),
Ok(Some(ConcreteValue::Function(Chunk::with_data(
None,
FunctionType {
@ -23,8 +23,8 @@ fn function() {
],
vec![ConcreteValue::string("a"), ConcreteValue::string("b"),],
vec![
Local::new(0, Type::Integer, false, Scope::default()),
Local::new(1, Type::Integer, false, Scope::default())
Local::new(0, Type::Integer, false, Scope::default(), 0),
Local::new(1, Type::Integer, false, Scope::default(), 1)
]
))))
);
@ -64,8 +64,8 @@ fn function_call() {
],
vec![ConcreteValue::string("a"), ConcreteValue::string("b"),],
vec![
Local::new(0, Type::Integer, false, Scope::default()),
Local::new(1, Type::Integer, false, Scope::default())
Local::new(0, Type::Integer, false, Scope::default(), 0),
Local::new(1, Type::Integer, false, Scope::default(), 1)
]
)),
ConcreteValue::Integer(1),
@ -75,7 +75,7 @@ fn function_call() {
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(3))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(3))));
}
#[test]
@ -93,7 +93,6 @@ fn function_declaration() {
},
vec![
(Instruction::load_constant(0, 0, false), Span(0, 40)),
(Instruction::define_local(0, 0, false), Span(3, 6)),
(Instruction::r#return(false), Span(40, 40))
],
vec![
@ -110,8 +109,8 @@ fn function_declaration() {
],
vec![ConcreteValue::string("a"), ConcreteValue::string("b")],
vec![
Local::new(0, Type::Integer, false, Scope::default()),
Local::new(1, Type::Integer, false, Scope::default())
Local::new(0, Type::Integer, false, Scope::default(), 0),
Local::new(1, Type::Integer, false, Scope::default(), 1)
]
)),
ConcreteValue::string("add"),
@ -125,9 +124,10 @@ fn function_declaration() {
}),
false,
Scope::default(),
)],
0
),],
)),
);
assert_eq!(run(source), Ok(None));
assert_eq!(run_source(source), Ok(None));
}

View File

@ -25,7 +25,7 @@ fn empty_list() {
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::list([]))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::list([]))));
}
#[test]
@ -61,7 +61,7 @@ fn list() {
);
assert_eq!(
run(source),
run_source(source),
Ok(Some(ConcreteValue::list([
ConcreteValue::Integer(1),
ConcreteValue::Integer(2),
@ -117,7 +117,7 @@ fn list_with_complex_expression() {
);
assert_eq!(
run(source),
run_source(source),
Ok(Some(ConcreteValue::list([
ConcreteValue::Integer(1),
ConcreteValue::Integer(-15)
@ -164,7 +164,7 @@ fn list_with_simple_expression() {
);
assert_eq!(
run(source),
run_source(source),
Ok(Some(ConcreteValue::list([
ConcreteValue::Integer(1),
ConcreteValue::Integer(5),

View File

@ -4,34 +4,6 @@ use dust_lang::*;
fn and() {
let source = "true && false";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Box::new(Type::Boolean),
},
vec![
(Instruction::load_boolean(0, true, false), Span(0, 4)),
(Instruction::test(0, false), Span(5, 7)),
(Instruction::jump(1, true), Span(5, 7)),
(Instruction::load_boolean(1, false, false), Span(8, 13)),
(Instruction::r#return(true), Span(13, 13)),
],
vec![],
vec![]
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(false))));
}
#[test]
fn or() {
let source = "true || false";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
@ -53,7 +25,35 @@ fn or() {
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Boolean(false))));
}
#[test]
fn or() {
let source = "true || false";
assert_eq!(
compile(source),
Ok(Chunk::with_data(
None,
FunctionType {
type_parameters: None,
value_parameters: None,
return_type: Box::new(Type::Boolean),
},
vec![
(Instruction::load_boolean(0, true, false), Span(0, 4)),
(Instruction::test(0, false), Span(5, 7)),
(Instruction::jump(1, true), Span(5, 7)),
(Instruction::load_boolean(1, false, false), Span(8, 13)),
(Instruction::r#return(true), Span(13, 13)),
],
vec![],
vec![]
))
);
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Boolean(true))));
}
#[test]
@ -71,22 +71,20 @@ fn variable_and() {
},
vec![
(Instruction::load_boolean(0, true, false), Span(8, 12)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::load_boolean(1, false, false), Span(22, 27)),
(Instruction::define_local(1, 1, false), Span(18, 19)),
(Instruction::get_local(2, 0), Span(29, 30)),
(Instruction::test(2, false), Span(31, 33)),
(Instruction::test(2, true), Span(31, 33)),
(Instruction::jump(1, true), Span(31, 33)),
(Instruction::get_local(3, 1), Span(34, 35)),
(Instruction::r#return(true), Span(35, 35)),
],
vec![ConcreteValue::string("a"), ConcreteValue::string("b"),],
vec![
Local::new(0, Type::Boolean, false, Scope::default()),
Local::new(1, Type::Boolean, false, Scope::default()),
Local::new(0, Type::Boolean, false, Scope::default(), 0),
Local::new(1, Type::Boolean, false, Scope::default(), 1),
]
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(false))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Boolean(false))));
}

View File

@ -15,7 +15,6 @@ fn r#while() {
},
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::less(true, 0, 2).set_c_is_constant(),
Span(23, 24)
@ -32,9 +31,9 @@ fn r#while() {
ConcreteValue::Integer(5),
ConcreteValue::Integer(1),
],
vec![Local::new(1, Type::Integer, true, Scope::default())]
vec![Local::new(1, Type::Integer, true, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(5))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(5))));
}

View File

@ -27,7 +27,7 @@ fn add() {
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(3))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(3))));
}
#[test]
@ -45,7 +45,6 @@ fn add_assign() {
},
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(*Instruction::add(0, 0, 2).set_c_is_constant(), Span(17, 19)),
(Instruction::get_local(1, 0), Span(23, 24)),
(Instruction::r#return(true), Span(24, 24))
@ -55,11 +54,11 @@ fn add_assign() {
ConcreteValue::string("a"),
ConcreteValue::Integer(2)
],
vec![Local::new(1, Type::Integer, true, Scope::default())]
vec![Local::new(1, Type::Integer, true, Scope::default(), 0)]
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(3))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(3))));
}
#[test]
@ -121,7 +120,7 @@ fn divide() {
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(1))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(1))));
}
#[test]
@ -139,7 +138,6 @@ fn divide_assign() {
},
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::divide(0, 0, 0).set_c_is_constant(),
Span(17, 19)
@ -148,11 +146,11 @@ fn divide_assign() {
(Instruction::r#return(true), Span(24, 24))
],
vec![ConcreteValue::Integer(2), ConcreteValue::string("a")],
vec![Local::new(1, Type::Integer, true, Scope::default())]
vec![Local::new(1, Type::Integer, true, Scope::default(), 0)]
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(1))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(1))));
}
#[test]
@ -215,7 +213,7 @@ fn math_operator_precedence() {
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(1))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(1))));
}
#[test]
@ -245,7 +243,7 @@ fn multiply() {
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(2))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(2))));
}
#[test]
@ -263,7 +261,6 @@ fn multiply_assign() {
},
vec![
(Instruction::load_constant(0, 0, false), Span(12, 13)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::multiply(0, 0, 2).set_c_is_constant(),
Span(17, 19)
@ -276,11 +273,11 @@ fn multiply_assign() {
ConcreteValue::string("a"),
ConcreteValue::Integer(3)
],
vec![Local::new(1, Type::Integer, true, Scope::default())]
vec![Local::new(1, Type::Integer, true, Scope::default(), 0)]
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(6))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(6))));
}
#[test]
@ -326,7 +323,7 @@ fn subtract() {
))
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(-1))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(-1))));
}
#[test]
@ -344,7 +341,6 @@ fn subtract_assign() {
},
vec![
(Instruction::load_constant(0, 0, false), Span(12, 14)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(
*Instruction::subtract(0, 0, 2).set_c_is_constant(),
Span(18, 20)
@ -357,11 +353,11 @@ fn subtract_assign() {
ConcreteValue::string("x"),
ConcreteValue::Integer(2)
],
vec![Local::new(1, Type::Integer, true, Scope::default())]
vec![Local::new(1, Type::Integer, true, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(40))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(40))));
}
#[test]

View File

@ -31,7 +31,7 @@ fn panic() {
);
assert_eq!(
run(source),
run_source(source),
Err(DustError::Runtime {
error: VmError::NativeFunction(NativeFunctionError::Panic {
message: Some("Goodbye world! 42".to_string()),
@ -68,5 +68,5 @@ fn to_string() {
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::string("42"))))
assert_eq!(run_source(source), Ok(Some(ConcreteValue::string("42"))))
}

View File

@ -9,7 +9,7 @@ fn allow_access_to_parent_scope() {
}
"#;
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(1))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(1))));
}
#[test]
@ -60,16 +60,16 @@ fn block_scope() {
ConcreteValue::string("e"),
],
vec![
Local::new(1, Type::Integer, false, Scope::new(0, 0)),
Local::new(3, Type::Integer, false, Scope::new(1, 1)),
Local::new(5, Type::Integer, false, Scope::new(2, 2)),
Local::new(7, Type::Integer, false, Scope::new(1, 1)),
Local::new(8, Type::Integer, false, Scope::new(0, 0)),
Local::new(1, Type::Integer, false, Scope::new(0, 0), 0),
Local::new(3, Type::Integer, false, Scope::new(1, 1), 0),
Local::new(5, Type::Integer, false, Scope::new(2, 2), 0),
Local::new(7, Type::Integer, false, Scope::new(1, 1), 0),
Local::new(8, Type::Integer, false, Scope::new(0, 0), 0),
]
)),
);
assert_eq!(run(source), Ok(None));
assert_eq!(run_source(source), Ok(None));
}
#[test]
@ -81,17 +81,17 @@ fn multiple_block_scopes() {
{
let c = 1;
}
let d = 2;
let d = b;
}
let q = 42;
let q = a;
{
let b = 42;
{
let c = 1;
}
let d = 2;
let d = b;
}
let e = 1;
let e = a;
";
assert_eq!(
@ -110,19 +110,19 @@ fn multiple_block_scopes() {
(Instruction::define_local(1, 1, false), Span(46, 47)),
(Instruction::load_constant(2, 4, false), Span(92, 93)),
(Instruction::define_local(2, 2, false), Span(88, 89)),
(Instruction::load_constant(3, 6, false), Span(129, 130)),
(Instruction::get_local(3, 1), Span(129, 130)),
(Instruction::define_local(3, 3, false), Span(125, 126)),
(Instruction::load_constant(4, 2, false), Span(158, 160)),
(Instruction::get_local(4, 0), Span(158, 159)),
(Instruction::define_local(4, 4, false), Span(154, 155)),
(Instruction::load_constant(5, 2, false), Span(192, 194)),
(Instruction::define_local(5, 5, false), Span(188, 189)),
(Instruction::load_constant(6, 4, false), Span(234, 235)),
(Instruction::define_local(6, 6, false), Span(230, 231)),
(Instruction::load_constant(7, 6, false), Span(271, 272)),
(Instruction::define_local(7, 7, false), Span(267, 268)),
(Instruction::load_constant(8, 4, false), Span(300, 301)),
(Instruction::define_local(8, 8, false), Span(296, 297)),
(Instruction::r#return(false), Span(307, 307))
(Instruction::load_constant(5, 2, false), Span(191, 193)),
(Instruction::define_local(5, 5, false), Span(187, 188)),
(Instruction::load_constant(6, 4, false), Span(233, 234)),
(Instruction::define_local(6, 6, false), Span(229, 230)),
(Instruction::get_local(7, 5), Span(270, 271)),
(Instruction::define_local(7, 7, false), Span(266, 267)),
(Instruction::get_local(8, 0), Span(299, 300)),
(Instruction::define_local(8, 8, false), Span(295, 296)),
(Instruction::r#return(false), Span(306, 306))
],
vec![
ConcreteValue::Integer(0),
@ -131,26 +131,25 @@ fn multiple_block_scopes() {
ConcreteValue::string("b"),
ConcreteValue::Integer(1),
ConcreteValue::string("c"),
ConcreteValue::Integer(2),
ConcreteValue::string("d"),
ConcreteValue::string("q"),
ConcreteValue::string("e"),
],
vec![
Local::new(1, Type::Integer, false, Scope::new(0, 0)),
Local::new(3, Type::Integer, false, Scope::new(1, 1)),
Local::new(5, Type::Integer, false, Scope::new(2, 2)),
Local::new(7, Type::Integer, false, Scope::new(1, 1)),
Local::new(8, Type::Integer, false, Scope::new(0, 0)),
Local::new(3, Type::Integer, false, Scope::new(1, 3)),
Local::new(5, Type::Integer, false, Scope::new(2, 4)),
Local::new(7, Type::Integer, false, Scope::new(1, 3)),
Local::new(9, Type::Integer, false, Scope::new(0, 0)),
Local::new(1, Type::Integer, false, Scope::new(0, 0), 0),
Local::new(3, Type::Integer, false, Scope::new(1, 1), 0),
Local::new(5, Type::Integer, false, Scope::new(2, 2), 0),
Local::new(6, Type::Integer, false, Scope::new(1, 1), 0),
Local::new(7, Type::Integer, false, Scope::new(0, 0), 0),
Local::new(3, Type::Integer, false, Scope::new(1, 3), 0),
Local::new(5, Type::Integer, false, Scope::new(2, 4), 0),
Local::new(6, Type::Integer, false, Scope::new(1, 3), 0),
Local::new(8, Type::Integer, false, Scope::new(0, 0), 0),
]
)),
);
assert_eq!(run(source), Ok(None));
assert_eq!(run_source(source), Ok(None));
}
#[test]
@ -163,7 +162,7 @@ fn disallow_access_to_child_scope() {
"#;
assert_eq!(
run(source),
run_source(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),
@ -188,7 +187,7 @@ fn disallow_access_to_child_scope_nested() {
"#;
assert_eq!(
run(source),
run_source(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),
@ -213,7 +212,7 @@ fn disallow_access_to_sibling_scope() {
"#;
assert_eq!(
run(source),
run_source(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),
@ -240,7 +239,7 @@ fn disallow_access_to_sibling_scope_nested() {
"#;
assert_eq!(
run(source),
run_source(source),
Err(DustError::Compile {
error: CompileError::VariableOutOfScope {
identifier: "x".to_string(),

View File

@ -22,7 +22,7 @@ fn negate() {
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(-42))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(-42))));
}
#[test]
@ -48,5 +48,5 @@ fn not() {
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(false))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Boolean(false))));
}

View File

@ -15,15 +15,14 @@ fn define_local() {
},
vec![
(Instruction::load_constant(0, 0, false), Span(8, 10)),
(Instruction::define_local(0, 0, false), Span(4, 5)),
(Instruction::r#return(false), Span(11, 11))
],
vec![ConcreteValue::Integer(42), ConcreteValue::string("x")],
vec![Local::new(1, Type::Integer, false, Scope::default())]
vec![Local::new(1, Type::Integer, false, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(None));
assert_eq!(run_source(source), Ok(None));
}
#[test]
@ -58,9 +57,8 @@ fn set_local() {
},
vec![
(Instruction::load_constant(0, 0, false), Span(12, 14)),
(Instruction::define_local(0, 0, true), Span(8, 9)),
(Instruction::load_constant(1, 2, false), Span(20, 22)),
(Instruction::set_local(1, 0), Span(16, 17)),
(Instruction::set_local(0, 0), Span(16, 17)),
(Instruction::get_local(2, 0), Span(24, 25)),
(Instruction::r#return(true), Span(25, 25)),
],
@ -69,9 +67,9 @@ fn set_local() {
ConcreteValue::string("x"),
ConcreteValue::Integer(42)
],
vec![Local::new(1, Type::Integer, true, Scope::default())]
vec![Local::new(1, Type::Integer, true, Scope::default(), 0)]
)),
);
assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42))));
assert_eq!(run_source(source), Ok(Some(ConcreteValue::Integer(42))));
}

View File

@ -1,154 +0,0 @@
use std::{
fs::read_to_string,
io::{stdout, Write},
};
use clap::Parser;
use colored::Colorize;
use dust_lang::{compile, format, lex, output_token_list, run};
use log::{Level, LevelFilter};
#[derive(Parser)]
struct Cli {
/// Source code sent via command line
#[arg(short, long)]
command: Option<String>,
/// Whether to output formatted source code instead of running the program
#[arg(short, long)]
format: bool,
/// Whether to output line numbers in formatted source code
#[arg(long)]
format_line_numbers: Option<bool>,
/// Whether to output colors in formatted source code
#[arg(long)]
format_colored: Option<bool>,
/// Whether to output the disassembled chunk instead of running the program
#[arg(short, long)]
parse: bool,
/// Whether to style the disassembled chunk
#[arg(long)]
style_disassembly: Option<bool>,
/// Whether to tokenize the source code instead of running the program
#[arg(short, long)]
tokenize: bool,
/// Log level
#[arg(short, long)]
log: Option<LevelFilter>,
/// Path to a source code file
path: Option<String>,
}
fn main() {
let args = Cli::parse();
let mut logger = env_logger::builder();
logger.format(|buf, record| {
let level_display = match record.level() {
Level::Info => "INFO".bold().white(),
Level::Debug => "DEBUG".bold().blue(),
Level::Warn => "WARN".bold().yellow(),
Level::Error => "ERROR".bold().red(),
Level::Trace => "TRACE".bold().purple(),
};
let module = record
.module_path()
.map(|path| path.split("::").last().unwrap_or(path))
.unwrap_or("unknown")
.dimmed();
let display = format!("{level_display:5} {module:^6} {args}", args = record.args());
writeln!(buf, "{display}")
});
if let Some(level) = args.log {
logger.filter_level(level).init();
} else {
logger.parse_env("DUST_LOG").init();
}
let source = if let Some(path) = &args.path {
&read_to_string(path).expect("Failed to read file")
} else if let Some(command) = &args.command {
command
} else {
eprintln!("No input provided");
return;
};
if args.format {
log::info!("Formatting source");
let line_numbers = args.format_line_numbers.unwrap_or(true);
let colored = args.format_colored.unwrap_or(true);
match format(source, line_numbers, colored) {
Ok(formatted) => println!("{}", formatted),
Err(error) => {
eprintln!("{}", error.report());
}
}
}
if args.tokenize {
log::info!("Tokenizing source");
match lex(source) {
Ok(tokens) => output_token_list(&tokens, &mut stdout()),
Err(error) => eprintln!("{}", error.report()),
}
}
if args.parse {
log::info!("Parsing source");
let styled = args.style_disassembly.unwrap_or(true);
match compile(source) {
Ok(chunk) => {
let disassembly = chunk
.disassembler()
.source(source)
.styled(styled)
.disassemble();
println!("{}", disassembly);
}
Err(error) => {
eprintln!("{}", error.report());
}
}
}
if args.format || args.tokenize || args.parse {
return;
}
match run(source) {
Ok(Some(value)) => println!("{}", value),
Ok(None) => {}
Err(error) => {
eprintln!("{}", error.report());
}
}
}
#[cfg(test)]
mod tests {
use clap::CommandFactory;
use super::*;
#[test]
fn verify_cli() {
Cli::command().debug_assert();
}
}

View File

@ -0,0 +1,11 @@
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
# Driver Program
print(fib(25))

View File

@ -4,17 +4,22 @@ while count <= 15 {
let divides_by_3 = count % 3 == 0
let divides_by_5 = count % 5 == 0
let output = if divides_by_3 && divides_by_5 {
"fizzbuzz"
} else if divides_by_3 {
"fizz"
} else if divides_by_5 {
"buzz"
} else {
to_string(count)
if divides_by_3 && divides_by_5 {
write_line("fizzbuzz")
return
}
write_line(output)
if divides_by_3 {
write_line("fizz")
return
}
if divides_by_5 {
write_line("buzz")
return
}
write_line(count)
count += 1
}