Refactor library and CLI
This commit is contained in:
parent
0cb96519e2
commit
e04ead3848
135
Cargo.lock
generated
135
Cargo.lock
generated
@ -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"
|
||||
|
@ -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]
|
||||
|
@ -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
344
dust-cli/src/main.rs
Normal 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();
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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,22 +696,19 @@ 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(|| {
|
||||
let identifier = self
|
||||
.chunk
|
||||
.constants()
|
||||
.get(local.identifier_index as usize)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
*self.local_declarations.get(&local_index).ok_or_else(|| {
|
||||
let identifier = self
|
||||
.chunk
|
||||
.constants()
|
||||
.get(local.identifier_index as usize)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
CompileError::UndeclaredVariable {
|
||||
identifier,
|
||||
position: self.current_position,
|
||||
}
|
||||
})?
|
||||
CompileError::UndeclaredVariable {
|
||||
identifier,
|
||||
position: self.current_position,
|
||||
}
|
||||
})?
|
||||
}
|
||||
Operation::LoadConstant => {
|
||||
is_constant = true;
|
||||
@ -733,17 +753,21 @@ 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
|
||||
{
|
||||
if !left_is_mutable_local {
|
||||
return Err(CompileError::ExpectedMutableVariable {
|
||||
found: self.previous_token.to_owned(),
|
||||
position: left_position,
|
||||
});
|
||||
}
|
||||
|
||||
if let Token::PlusEqual | Token::MinusEqual | Token::StarEqual | Token::SlashEqual =
|
||||
operator
|
||||
{
|
||||
if !left_is_mutable_local {
|
||||
return Err(CompileError::ExpectedMutableVariable {
|
||||
found: self.previous_token.to_owned(),
|
||||
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 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;
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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,27 +375,24 @@ impl Instruction {
|
||||
}
|
||||
|
||||
pub fn yields_value(&self) -> bool {
|
||||
match self.operation() {
|
||||
matches!(
|
||||
self.operation(),
|
||||
Operation::Add
|
||||
| Operation::Call
|
||||
| Operation::Divide
|
||||
| Operation::GetLocal
|
||||
| Operation::LoadBoolean
|
||||
| Operation::LoadConstant
|
||||
| Operation::LoadList
|
||||
| Operation::LoadSelf
|
||||
| Operation::Modulo
|
||||
| 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::Call
|
||||
| Operation::CallNative
|
||||
| Operation::Divide
|
||||
| Operation::GetLocal
|
||||
| Operation::LoadBoolean
|
||||
| Operation::LoadConstant
|
||||
| Operation::LoadList
|
||||
| Operation::LoadSelf
|
||||
| Operation::Modulo
|
||||
| Operation::Multiply
|
||||
| Operation::Negate
|
||||
| Operation::Not
|
||||
| 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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
|
@ -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()
|
||||
};
|
||||
first_loader.set_c_to_boolean(true);
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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))));
|
||||
}
|
||||
|
@ -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))));
|
||||
}
|
||||
|
@ -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 }";
|
34
dust-lang/tests/control_flow/logic_expressions.rs
Normal file
34
dust-lang/tests/control_flow/logic_expressions.rs
Normal 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))),);
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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))));
|
||||
}
|
||||
|
@ -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))));
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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"))))
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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))));
|
||||
}
|
||||
|
@ -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))));
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
11
examples/assets/fibonacci.py
Normal file
11
examples/assets/fibonacci.py
Normal 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))
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user