diff --git a/Cargo.lock b/Cargo.lock index c68fe66..cb4f104 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,9 +27,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" @@ -65,6 +65,46 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c937d4061031a6d0c8da4b9a4f98a172fc2976dfb1c19213a9cf7d0d3c837e36" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85379ba512b21a328adf887e85f7742d12e96eb31f3ef077df4ffc26b506ffed" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + [[package]] name = "colorchoice" version = "1.0.0" @@ -110,6 +150,10 @@ dependencies = [ [[package]] name = "dust-shell" version = "0.1.0" +dependencies = [ + "clap", + "dust-lang", +] [[package]] name = "either" @@ -151,6 +195,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "humantime" version = "2.1.0" @@ -321,6 +371,12 @@ dependencies = [ "serde", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.53" diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index c066442..5c7d922 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -5,7 +5,11 @@ /// hash map of variables: /// - `analyze` convenience function /// - `Analyzer` struct -use std::collections::HashMap; +use std::{ + collections::HashMap, + error::Error, + fmt::{self, Display, Formatter}, +}; use crate::{AbstractSyntaxTree, BuiltInFunction, Identifier, Node, Span, Statement, Type, Value}; @@ -180,7 +184,7 @@ impl<'a> Analyzer<'a> { #[derive(Clone, Debug, PartialEq)] pub enum AnalyzerError { ExpectedBoolean { actual: Node }, - ExpectedFunction { position: Span }, + ExpectedFunction { actual: Node }, ExpectedIdentifier { actual: Node }, ExpectedIdentifierOrValue { actual: Node }, ExpectedIntegerOrFloat { actual: Node }, @@ -188,6 +192,36 @@ pub enum AnalyzerError { UnexpectedIdentifier { identifier: Node }, } +impl Error for AnalyzerError {} + +impl Display for AnalyzerError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + AnalyzerError::ExpectedBoolean { actual } => { + write!(f, "Expected boolean, found {}", actual) + } + AnalyzerError::ExpectedFunction { actual } => { + write!(f, "Expected function, found {}", actual) + } + AnalyzerError::ExpectedIdentifier { actual } => { + write!(f, "Expected identifier, found {}", actual) + } + AnalyzerError::ExpectedIdentifierOrValue { actual } => { + write!(f, "Expected identifier or value, found {}", actual) + } + AnalyzerError::ExpectedIntegerOrFloat { actual } => { + write!(f, "Expected integer or float, found {}", actual) + } + AnalyzerError::ExpectedIntegerFloatOrString { actual } => { + write!(f, "Expected integer, float, or string, found {}", actual) + } + AnalyzerError::UnexpectedIdentifier { identifier } => { + write!(f, "Unexpected identifier {}", identifier) + } + } + } +} + #[cfg(test)] mod tests { use crate::{BuiltInFunction, Identifier, Value}; diff --git a/dust-lang/src/built_in_function.rs b/dust-lang/src/built_in_function.rs index bcb703f..6c47a9f 100644 --- a/dust-lang/src/built_in_function.rs +++ b/dust-lang/src/built_in_function.rs @@ -1,4 +1,5 @@ use std::{ + error::Error, fmt::{self, Display, Formatter}, io::{self, stdin}, }; @@ -139,3 +140,17 @@ impl From for BuiltInFunctionError { Self::Io(error.kind()) } } + +impl Error for BuiltInFunctionError {} + +impl Display for BuiltInFunctionError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + BuiltInFunctionError::ExpectedInteger => write!(f, "Expected an integer"), + BuiltInFunctionError::Io(error_kind) => write!(f, "I/O error: {}", error_kind), + BuiltInFunctionError::WrongNumberOfValueArguments => { + write!(f, "Wrong number of value arguments") + } + } + } +} diff --git a/dust-lang/src/dust_error.rs b/dust-lang/src/dust_error.rs new file mode 100644 index 0000000..955f831 --- /dev/null +++ b/dust-lang/src/dust_error.rs @@ -0,0 +1,21 @@ +use std::{error::Error, fmt::Display}; + +use crate::VmError; + +#[derive(Debug, Clone, PartialEq)] +pub struct DustError<'src> { + vm_error: VmError, + source: &'src str, +} + +impl Error for DustError<'_> { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.vm_error) + } +} + +impl Display for DustError<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}\n{}", self.vm_error, self.source) + } +} diff --git a/dust-lang/src/lex.rs b/dust-lang/src/lex.rs index 530eb36..9a6de23 100644 --- a/dust-lang/src/lex.rs +++ b/dust-lang/src/lex.rs @@ -3,7 +3,11 @@ //! This module provides two lexing options: //! - [`lex`], which lexes the entire input and returns a vector of tokens and their positions //! - [`Lexer`], which lexes the input a token at a time -use std::num::{ParseFloatError, ParseIntError}; +use std::{ + error::Error, + fmt::{self, Display, Formatter}, + num::{ParseFloatError, ParseIntError}, +}; use crate::{Span, Token}; @@ -284,6 +288,28 @@ pub enum LexError { IntegerError(ParseIntError), } +impl Error for LexError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::FloatError(parse_float_error) => Some(parse_float_error), + Self::IntegerError(parse_int_error) => Some(parse_int_error), + } + } +} + +impl Display for LexError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::FloatError(parse_float_error) => { + write!(f, "Failed to parse float: {}", parse_float_error) + } + Self::IntegerError(parse_int_error) => { + write!(f, "Failed to parse integer: {}", parse_int_error) + } + } + } +} + impl From for LexError { fn from(error: std::num::ParseFloatError) -> Self { Self::FloatError(error) diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 71ad4b8..29cd981 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -8,6 +8,7 @@ pub mod abstract_tree; pub mod analyzer; pub mod built_in_function; +pub mod dust_error; pub mod identifier; pub mod lex; pub mod parse; diff --git a/dust-lang/src/parse.rs b/dust-lang/src/parse.rs index a8a061b..e520651 100644 --- a/dust-lang/src/parse.rs +++ b/dust-lang/src/parse.rs @@ -3,7 +3,11 @@ /// This module provides two parsing options: /// - `parse` convenience function /// - `Parser` struct, which parses the input a statement at a time -use std::collections::VecDeque; +use std::{ + collections::VecDeque, + error::Error, + fmt::{self, Display, Formatter}, +}; use crate::{ built_in_function::BuiltInFunction, token::TokenOwned, AbstractSyntaxTree, Identifier, @@ -351,6 +355,39 @@ pub enum ParseError { UnexpectedToken(TokenOwned), } +impl Error for ParseError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::LexError(error) => Some(error), + _ => None, + } + } +} + +impl Display for ParseError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::LexError(error) => write!(f, "{}", error), + Self::ExpectedClosingParenthesis { actual, span } => write!( + f, + "Expected closing parenthesis, found {} at {:?}", + actual, span + ), + Self::ExpectedClosingSquareBrace { actual, span } => write!( + f, + "Expected closing square brace, found {:?} at {:?}", + actual, span + ), + Self::ExpectedOpeningParenthesis { actual, span } => write!( + f, + "Expected opening parenthesis, found {:?} at {:?}", + actual, span + ), + Self::UnexpectedToken(actual) => write!(f, "Unexpected token {:?}", actual), + } + } +} + impl From for ParseError { fn from(v: LexError) -> Self { Self::LexError(v) diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index 55849a2..8632458 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -121,3 +121,30 @@ pub enum TokenOwned { RightSquareBrace, Star, } + +impl Display for TokenOwned { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + TokenOwned::Eof => write!(f, "EOF"), + TokenOwned::Identifier(text) => write!(f, "{text}"), + TokenOwned::Boolean(boolean) => write!(f, "{boolean}"), + TokenOwned::Float(float) => write!(f, "{float}"), + TokenOwned::Integer(integer) => write!(f, "{integer}"), + TokenOwned::String(string) => write!(f, "{string}"), + TokenOwned::IsEven => write!(f, "is_even"), + TokenOwned::IsOdd => write!(f, "is_odd"), + TokenOwned::Length => write!(f, "length"), + TokenOwned::ReadLine => write!(f, "read_line"), + TokenOwned::WriteLine => write!(f, "write_line"), + TokenOwned::Comma => write!(f, ","), + TokenOwned::Dot => write!(f, "."), + TokenOwned::Equal => write!(f, "="), + TokenOwned::Plus => write!(f, "+"), + TokenOwned::Star => write!(f, "*"), + TokenOwned::LeftParenthesis => write!(f, "("), + TokenOwned::RightParenthesis => write!(f, ")"), + TokenOwned::LeftSquareBrace => write!(f, "["), + TokenOwned::RightSquareBrace => write!(f, "]"), + } + } +} diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index c610a50..df21272 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -2,6 +2,7 @@ use std::{ cmp::Ordering, collections::{BTreeMap, HashMap}, + error::Error, fmt::{self, Display, Formatter}, ops::Range, sync::Arc, @@ -656,3 +657,20 @@ pub enum ValueError { IndexOutOfBounds { value: Value, index: i64 }, ExpectedList(Value), } + +impl Error for ValueError {} + +impl Display for ValueError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + ValueError::CannotAdd(left, right) => write!(f, "Cannot add {} and {}", left, right), + ValueError::PropertyNotFound { value, property } => { + write!(f, "{} does not have a property named {}", value, property) + } + ValueError::IndexOutOfBounds { value, index } => { + write!(f, "{} does not have an index of {}", value, index) + } + ValueError::ExpectedList(value) => write!(f, "{} is not a list", value), + } + } +} diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 07a2511..df7a724 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -1,4 +1,8 @@ -use std::collections::HashMap; +use std::{ + collections::HashMap, + error::Error, + fmt::{self, Display, Formatter}, +}; use crate::{ parse, AbstractSyntaxTree, Analyzer, AnalyzerError, BuiltInFunctionError, Identifier, Node, @@ -134,9 +138,10 @@ impl Vm { let function = if let Some(function) = function_value.as_function() { function } else { - return Err(VmError::AnaylyzerError(AnalyzerError::ExpectedFunction { + return Err(VmError::ExpectedFunction { + actual: function_value, position: function_position, - })); + }); }; let value_parameters = if let Some(value_nodes) = value_parameter_nodes { @@ -249,6 +254,7 @@ pub enum VmError { ExpectedIdentifier { position: Span }, ExpectedIdentifierOrInteger { position: Span }, ExpectedInteger { position: Span }, + ExpectedFunction { actual: Value, position: Span }, ExpectedList { position: Span }, ExpectedValue { position: Span }, } @@ -277,6 +283,59 @@ impl From for VmError { } } +impl Error for VmError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::AnaylyzerError(analyzer_error) => Some(analyzer_error), + Self::ParseError(parse_error) => Some(parse_error), + Self::ValueError(value_error) => Some(value_error), + Self::BuiltInFunctionCallFailed(built_in_function_error) => { + Some(built_in_function_error) + } + _ => None, + } + } +} + +impl Display for VmError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::AnaylyzerError(analyzer_error) => write!(f, "{}", analyzer_error), + Self::ParseError(parse_error) => write!(f, "{}", parse_error), + Self::ValueError(value_error) => write!(f, "{}", value_error), + Self::BuiltInFunctionCallFailed(built_in_function_error) => { + write!(f, "{}", built_in_function_error) + } + Self::ExpectedFunction { actual, position } => { + write!( + f, + "Expected a function, but got: {} at position: {:?}", + actual, position + ) + } + Self::ExpectedIdentifier { position } => { + write!(f, "Expected an identifier at position: {:?}", position) + } + Self::ExpectedIdentifierOrInteger { position } => { + write!( + f, + "Expected an identifier or integer at position: {:?}", + position + ) + } + Self::ExpectedInteger { position } => { + write!(f, "Expected an integer at position: {:?}", position) + } + Self::ExpectedList { position } => { + write!(f, "Expected a list at position: {:?}", position) + } + Self::ExpectedValue { position } => { + write!(f, "Expected a value at position: {:?}", position) + } + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/dust-shell/Cargo.toml b/dust-shell/Cargo.toml index 263888d..84bb94d 100644 --- a/dust-shell/Cargo.toml +++ b/dust-shell/Cargo.toml @@ -8,3 +8,5 @@ readme.workspace = true repository.workspace = true [dependencies] +clap = { version = "4.5.14", features = ["derive"] } +dust-lang = { path = "../dust-lang" } diff --git a/dust-shell/src/main.rs b/dust-shell/src/main.rs index e7a11a9..66db18f 100644 --- a/dust-shell/src/main.rs +++ b/dust-shell/src/main.rs @@ -1,3 +1,36 @@ -fn main() { - println!("Hello, world!"); +use std::{collections::HashMap, fs::read_to_string}; + +use clap::Parser; +use dust_lang::run; + +#[derive(Parser)] +struct Cli { + #[arg(short, long)] + command: Option, + + path: Option, +} + +fn main() { + let args = Cli::parse(); + let mut variables = HashMap::new(); + + let result = if let Some(command) = &args.command { + run(command, &mut variables) + } else if let Some(path) = &args.path { + let content = read_to_string(path).unwrap(); + + run(&content, &mut variables) + } else { + panic!("No command or path provided"); + }; + + match result { + Ok(return_value) => { + if let Some(value) = return_value { + println!("{}", value); + } + } + Err(error) => eprintln!("{}", error), + } }