From 2b797c19f79a8560dbcdf1fb0ec8724c65b4b4fe Mon Sep 17 00:00:00 2001 From: Jeff Date: Sun, 24 Mar 2024 09:10:49 -0400 Subject: [PATCH] Pass all tests --- Cargo.lock | 104 -------- dust-lang/Cargo.toml | 2 +- dust-lang/src/abstract_tree/type.rs | 2 + dust-lang/src/abstract_tree/value_node.rs | 21 +- dust-lang/src/context.rs | 12 +- dust-lang/src/lexer.rs | 18 +- dust-lang/src/lib.rs | 281 +++++++++++++++++++++- dust-lang/src/parser.rs | 191 +++++++++------ dust-lang/src/value.rs | 18 ++ dust-lang/tests/expressions.rs | 25 +- dust-lang/tests/functions.rs | 42 ++-- dust-lang/tests/statements.rs | 12 +- dust-lang/tests/structs.rs | 22 +- dust-lang/tests/values.rs | 80 +++--- dust-lang/tests/variables.rs | 26 +- dust-shell/src/cli.rs | 20 +- dust-shell/src/error.rs | 263 -------------------- dust-shell/src/main.rs | 36 +-- std/io.ds | 4 +- std/thread.ds | 2 +- 20 files changed, 623 insertions(+), 558 deletions(-) delete mode 100644 dust-shell/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index 078a39b..e322baa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,7 +115,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd002a6223f12c7a95cdd4b1cb3a0149d22d37f7a9ecdb2cb691a071fe236c29" dependencies = [ - "concolor", "unicode-width", "yansi", ] @@ -262,26 +261,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "concolor" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b946244a988c390a94667ae0e3958411fa40cc46ea496a929b263d883f5f9c3" -dependencies = [ - "bitflags 1.3.2", - "concolor-query", - "is-terminal", -] - -[[package]] -name = "concolor-query" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" -dependencies = [ - "windows-sys 0.45.0", -] - [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -537,12 +516,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "home" version = "0.5.9" @@ -591,17 +564,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "is-terminal" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "itertools" version = "0.12.1" @@ -1456,15 +1418,6 @@ dependencies = [ "windows-targets 0.52.4", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -1483,21 +1436,6 @@ dependencies = [ "windows-targets 0.52.4", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -1528,12 +1466,6 @@ dependencies = [ "windows_x86_64_msvc 0.52.4", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -1546,12 +1478,6 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -1564,12 +1490,6 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -1582,12 +1502,6 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -1600,12 +1514,6 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -1618,12 +1526,6 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -1636,12 +1538,6 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/dust-lang/Cargo.toml b/dust-lang/Cargo.toml index a2e6e0a..322489b 100644 --- a/dust-lang/Cargo.toml +++ b/dust-lang/Cargo.toml @@ -9,7 +9,7 @@ readme.workspace = true repository.workspace = true [dependencies] -ariadne = { version = "0.4.0", features = ["auto-color"] } +ariadne = "0.4.0" chumsky = { version = "1.0.0-alpha.6", features = ["pratt", "label"] } clap = { version = "4.5.2", features = ["derive"] } colored = "2.1.0" diff --git a/dust-lang/src/abstract_tree/type.rs b/dust-lang/src/abstract_tree/type.rs index bdc91ad..3dceec9 100644 --- a/dust-lang/src/abstract_tree/type.rs +++ b/dust-lang/src/abstract_tree/type.rs @@ -13,6 +13,7 @@ use super::{AbstractNode, Action, WithPosition}; #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum Type { Any, + Argument(Identifier), Boolean, Float, Function { @@ -179,6 +180,7 @@ impl Display for Type { write!(f, ") : {return_type}") } Type::Structure { name, .. } => write!(f, "{name}"), + Type::Argument(_) => todo!(), } } } diff --git a/dust-lang/src/abstract_tree/value_node.rs b/dust-lang/src/abstract_tree/value_node.rs index 407f54c..c14bf70 100644 --- a/dust-lang/src/abstract_tree/value_node.rs +++ b/dust-lang/src/abstract_tree/value_node.rs @@ -30,6 +30,7 @@ pub enum ValueNode { fields: Vec<(Identifier, WithPosition)>, }, ParsedFunction { + type_arguments: Vec>, parameters: Vec<(Identifier, WithPosition)>, return_type: WithPosition, body: WithPosition, @@ -112,6 +113,7 @@ impl AbstractNode for ValueNode { } if let ValueNode::ParsedFunction { + type_arguments, parameters, return_type, body, @@ -121,6 +123,12 @@ impl AbstractNode for ValueNode { function_context.inherit_types_from(context)?; + for r#type in type_arguments { + if let Type::Argument(identifier) = &r#type.node { + function_context.set_type(identifier.clone(), r#type.node.clone())?; + } + } + for (identifier, r#type) in parameters { function_context.set_type(identifier.clone(), r#type.node.clone())?; } @@ -210,10 +218,11 @@ impl AbstractNode for ValueNode { ValueNode::Range(range) => Value::range(range), ValueNode::String(string) => Value::string(string), ValueNode::ParsedFunction { + type_arguments, parameters, return_type, body, - } => Value::function(parameters, return_type, body), + } => Value::function(type_arguments, parameters, return_type, body), ValueNode::Structure { name, fields: expressions, @@ -281,11 +290,13 @@ impl Ord for ValueNode { (String(_), _) => Ordering::Greater, ( ParsedFunction { + type_arguments: left_type_arguments, parameters: left_parameters, return_type: left_return, body: left_body, }, ParsedFunction { + type_arguments: right_type_arguments, parameters: right_parameters, return_type: right_return, body: right_body, @@ -297,7 +308,13 @@ impl Ord for ValueNode { let return_cmp = left_return.cmp(right_return); if return_cmp.is_eq() { - left_body.cmp(right_body) + let type_argument_cmp = left_type_arguments.cmp(right_type_arguments); + + if type_argument_cmp.is_eq() { + left_body.cmp(right_body) + } else { + type_argument_cmp + } } else { return_cmp } diff --git a/dust-lang/src/context.rs b/dust-lang/src/context.rs index fbcffb1..6da6d85 100644 --- a/dust-lang/src/context.rs +++ b/dust-lang/src/context.rs @@ -56,11 +56,7 @@ impl Context { } pub fn contains(&self, identifier: &Identifier) -> Result { - if self.inner.read()?.contains_key(identifier) { - Ok(true) - } else { - Ok(false) - } + Ok(self.inner.read()?.contains_key(identifier)) } pub fn get_type(&self, identifier: &Identifier) -> Result, ValidationError> { @@ -93,9 +89,9 @@ impl Context { } pub fn set_value(&self, identifier: Identifier, value: Value) -> Result<(), RwLockPoisonError> { - let mut inner = self.inner.write()?; - - inner.insert(identifier, ValueData::Value(value)); + self.inner + .write()? + .insert(identifier, ValueData::Value(value)); Ok(()) } diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index 0731d77..9cd1581 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -61,6 +61,7 @@ pub enum Keyword { Break, Else, Float, + Fn, Int, If, List, @@ -69,6 +70,7 @@ pub enum Keyword { Range, Struct, Str, + Type, Loop, While, } @@ -82,6 +84,7 @@ impl Display for Keyword { Keyword::Break => write!(f, "break"), Keyword::Else => write!(f, "else"), Keyword::Float => write!(f, "float"), + Keyword::Fn => write!(f, "fn"), Keyword::Int => write!(f, "int"), Keyword::If => write!(f, "if"), Keyword::List => write!(f, "list"), @@ -92,6 +95,7 @@ impl Display for Keyword { Keyword::Str => write!(f, "str"), Keyword::Loop => write!(f, "loop"), Keyword::While => write!(f, "while"), + Keyword::Type => write!(f, "type"), } } } @@ -143,13 +147,13 @@ impl Display for Operator { #[derive(Copy, Clone, Debug, PartialEq)] pub enum Control { - Arrow, CurlyOpen, CurlyClose, SquareOpen, SquareClose, ParenOpen, ParenClose, + Pipe, Comma, DoubleColon, Colon, @@ -157,12 +161,13 @@ pub enum Control { Dot, DoubleDot, Semicolon, + SkinnyArrow, + FatArrow, } impl Display for Control { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - Control::Arrow => write!(f, "->"), Control::CurlyOpen => write!(f, "{{"), Control::CurlyClose => write!(f, "}}"), Control::Dollar => write!(f, "$"), @@ -170,12 +175,15 @@ impl Display for Control { Control::SquareClose => write!(f, "]"), Control::ParenOpen => write!(f, "("), Control::ParenClose => write!(f, ")"), + Control::Pipe => write!(f, "|"), Control::Comma => write!(f, ","), Control::DoubleColon => write!(f, "::"), Control::Colon => write!(f, ":"), Control::Dot => write!(f, "."), Control::Semicolon => write!(f, ";"), Control::DoubleDot => write!(f, ".."), + Control::SkinnyArrow => write!(f, "->"), + Control::FatArrow => write!(f, "=>"), } } } @@ -265,13 +273,15 @@ pub fn lexer<'src>() -> impl Parser< .map(Token::Operator); let control = choice(( - just("->").to(Control::Arrow), + just("->").to(Control::SkinnyArrow), + just("=>").to(Control::FatArrow), just("{").to(Control::CurlyOpen), just("}").to(Control::CurlyClose), just("[").to(Control::SquareOpen), just("]").to(Control::SquareClose), just("(").to(Control::ParenOpen), just(")").to(Control::ParenClose), + just("|").to(Control::Pipe), just(",").to(Control::Comma), just(";").to(Control::Semicolon), just("::").to(Control::DoubleColon), @@ -289,6 +299,7 @@ pub fn lexer<'src>() -> impl Parser< just("break").to(Keyword::Break), just("else").to(Keyword::Else), just("float").to(Keyword::Float), + just("fn").to(Keyword::Fn), just("int").to(Keyword::Int), just("if").to(Keyword::If), just("list").to(Keyword::List), @@ -297,6 +308,7 @@ pub fn lexer<'src>() -> impl Parser< just("range").to(Keyword::Range), just("struct").to(Keyword::Struct), just("str").to(Keyword::Str), + just("type").to(Keyword::Type), just("loop").to(Keyword::Loop), just("while").to(Keyword::While), )) diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 239c958..55fe397 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -6,18 +6,256 @@ pub mod lexer; pub mod parser; pub mod value; +use std::{ops::Range, rc::Rc}; + +use abstract_tree::Type; +use ariadne::{Color, Fmt, Label, Report, ReportKind}; use context::Context; -use error::Error; +use error::{Error, RuntimeError, TypeConflict, ValidationError}; use lexer::lex; use parser::parse; pub use value::Value; -pub fn interpret(source: &str) -> Result, Vec> { +pub fn interpret(source_id: Rc, source: &str) -> Result, InterpreterError> { let mut interpreter = Interpreter::new(Context::new()); - interpreter.run(include_str!("../../std/io.ds"))?; - interpreter.run(include_str!("../../std/thread.ds"))?; - interpreter.run(source) + interpreter.load_std()?; + interpreter.run(source_id, source) +} + +pub fn interpret_without_std( + source_id: Rc, + source: &str, +) -> Result, InterpreterError> { + let mut interpreter = Interpreter::new(Context::new()); + + interpreter.run(source_id, source) +} + +#[derive(Debug, PartialEq)] +pub struct InterpreterError { + source_id: Rc, + errors: Vec, +} + +impl InterpreterError { + pub fn errors(&self) -> &Vec { + &self.errors + } +} + +impl InterpreterError { + pub fn build_reports<'id>(self) -> Vec, Range)>> { + let mut reports = Vec::new(); + + for error in self.errors { + let (mut builder, validation_error, error_position) = match error { + Error::Lex { + expected, + span, + reason, + } => { + let description = if expected.is_empty() { + "Invalid character.".to_string() + } else { + format!("Expected {expected}.") + }; + + ( + Report::build( + ReportKind::Custom("Lexing Error", Color::Yellow), + self.source_id.clone(), + span.1, + ) + .with_message(description) + .with_label( + Label::new((self.source_id.clone(), span.0..span.1)) + .with_message(reason) + .with_color(Color::Red), + ), + None, + span.into(), + ) + } + Error::Parse { + expected, + span, + reason, + } => { + let description = if expected.is_empty() { + "Invalid token.".to_string() + } else { + format!("Expected {expected}.") + }; + + ( + Report::build( + ReportKind::Custom("Parsing Error", Color::Yellow), + self.source_id.clone(), + span.1, + ) + .with_message(description) + .with_label( + Label::new((self.source_id.clone(), span.0..span.1)) + .with_message(reason) + .with_color(Color::Red), + ), + None, + span.into(), + ) + } + Error::Runtime { error, position } => ( + Report::build( + ReportKind::Custom("Runtime Error", Color::Red), + self.source_id.clone(), + position.1, + ) + .with_message("An error occured that forced the program to exit.") + .with_note( + "There may be unexpected side-effects because the program could not finish.", + ) + .with_help( + "This is the interpreter's fault. Please submit a bug with this error message.", + ), + if let RuntimeError::ValidationFailure(validation_error) = error { + Some(validation_error) + } else { + None + }, + position, + ), + Error::Validation { error, position } => ( + Report::build( + ReportKind::Custom("Validation Error", Color::Magenta), + self.source_id.clone(), + position.1, + ) + .with_message("The syntax is valid but this code is not sound.") + .with_note("This error was detected by the interpreter before running the code."), + Some(error), + position, + ), + }; + + let type_color = Color::Green; + let identifier_color = Color::Blue; + + if let Some(validation_error) = validation_error { + match validation_error { + ValidationError::ExpectedBoolean { actual, position } => { + builder.add_label( + Label::new((self.source_id.clone(), position.0..position.1)) + .with_message(format!( + "Expected {} but got {}.", + "boolean".fg(type_color), + actual.fg(type_color) + )), + ); + } + ValidationError::ExpectedIntegerOrFloat(position) => { + builder.add_label( + Label::new((self.source_id.clone(), position.0..position.1)) + .with_message(format!( + "Expected {} or {}.", + "integer".fg(type_color), + "float".fg(type_color) + )), + ); + } + ValidationError::RwLockPoison(_) => todo!(), + ValidationError::TypeCheck { + conflict, + actual_position, + expected_position: expected_postion, + } => { + let TypeConflict { actual, expected } = conflict; + + builder = builder.with_message("A type conflict was found."); + + builder.add_labels([ + Label::new(( + self.source_id.clone(), + expected_postion.0..expected_postion.1, + )) + .with_message(format!( + "Type {} established here.", + expected.fg(type_color) + )), + Label::new(( + self.source_id.clone(), + actual_position.0..actual_position.1, + )) + .with_message(format!("Got type {} here.", actual.fg(type_color))), + ]); + } + ValidationError::VariableNotFound(identifier) => builder.add_label( + Label::new((self.source_id.clone(), error_position.0..error_position.1)) + .with_message(format!( + "Variable {} does not exist in this context.", + identifier.fg(identifier_color) + )), + ), + ValidationError::CannotIndex { r#type, position } => builder.add_label( + Label::new((self.source_id.clone(), position.0..position.1)).with_message( + format!("Cannot index into a {}.", r#type.fg(type_color)), + ), + ), + ValidationError::CannotIndexWith { + collection_type, + collection_position, + index_type, + index_position, + } => { + builder = builder.with_message(format!( + "Cannot index into {} with {}.", + collection_type.clone().fg(type_color), + index_type.clone().fg(type_color) + )); + + builder.add_labels([ + Label::new(( + self.source_id.clone(), + collection_position.0..collection_position.1, + )) + .with_message(format!( + "This has type {}.", + collection_type.fg(type_color), + )), + Label::new(( + self.source_id.clone(), + index_position.0..index_position.1, + )) + .with_message(format!("This has type {}.", index_type.fg(type_color),)), + ]) + } + ValidationError::InterpreterExpectedReturn(_) => todo!(), + ValidationError::ExpectedFunction { .. } => todo!(), + ValidationError::ExpectedValue(_) => todo!(), + ValidationError::PropertyNotFound { .. } => todo!(), + ValidationError::WrongArguments { .. } => todo!(), + ValidationError::ExpectedIntegerFloatOrString { actual, position } => { + builder = builder.with_message(format!( + "Expected an {}, {} or {}.", + Type::Integer.fg(type_color), + Type::Float.fg(type_color), + Type::String.fg(type_color) + )); + + builder.add_labels([Label::new(( + self.source_id.clone(), + position.0..position.1, + )) + .with_message(format!("This has type {}.", actual.fg(type_color),))]) + } + } + } + let report = builder.finish(); + + reports.push(report); + } + + reports + } } pub struct Interpreter { @@ -29,11 +267,36 @@ impl Interpreter { Interpreter { context } } - pub fn run(&mut self, source: &str) -> Result, Vec> { - let tokens = lex(source)?; - let abstract_tree = parse(&tokens)?; - let value_option = abstract_tree.run(&self.context)?; + pub fn run( + &mut self, + source_id: Rc, + source: &str, + ) -> Result, InterpreterError> { + let tokens = lex(source).map_err(|errors| InterpreterError { + source_id: source_id.clone(), + errors, + })?; + let abstract_tree = parse(&tokens).map_err(|errors| InterpreterError { + source_id: source_id.clone(), + errors, + })?; + let value_option = abstract_tree + .run(&self.context) + .map_err(|errors| InterpreterError { source_id, errors })?; Ok(value_option) } + + pub fn load_std(&mut self) -> Result<(), InterpreterError> { + self.run( + Rc::new("std/io.ds".to_string()), + include_str!("../../std/io.ds"), + )?; + self.run( + Rc::new("std/io.ds".to_string()), + include_str!("../../std/thread.ds"), + )?; + + Ok(()) + } } diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 86eb7b3..8351be5 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -66,15 +66,18 @@ pub fn parser<'src>() -> impl Parser< .map_with(|value, state| Expression::Value(value).with_position(state.span())); let r#type = recursive(|r#type| { - let function_type = r#type - .clone() - .separated_by(just(Token::Control(Control::Comma))) - .collect() - .delimited_by( - just(Token::Control(Control::ParenOpen)), - just(Token::Control(Control::ParenClose)), + let function_type = just(Token::Keyword(Keyword::Fn)) + .ignore_then( + r#type + .clone() + .separated_by(just(Token::Control(Control::Comma))) + .collect() + .delimited_by( + just(Token::Control(Control::ParenOpen)), + just(Token::Control(Control::ParenClose)), + ), ) - .then_ignore(just(Token::Control(Control::Arrow))) + .then_ignore(just(Token::Control(Control::SkinnyArrow))) .then(r#type.clone()) .map(|(parameter_types, return_type)| Type::Function { parameter_types, @@ -110,20 +113,21 @@ pub fn parser<'src>() -> impl Parser< just(Token::Keyword(Keyword::Range)).to(Type::Range), just(Token::Keyword(Keyword::Str)).to(Type::String), just(Token::Keyword(Keyword::List)).to(Type::List), - identifier.clone().try_map(move |identifier, span| { - custom_types - .0 - .borrow() - .get(&identifier) - .cloned() - .ok_or_else(|| { - Rich::custom(span, format!("There is no type named {identifier}.")) - }) + identifier.clone().map(move |identifier| { + if let Some(r#type) = custom_types.0.borrow().get(&identifier) { + r#type.clone() + } else { + Type::Argument(identifier) + } }), )) }) .map_with(|r#type, state| r#type.with_position(state.span())); + let type_argument = identifier + .clone() + .map_with(|identifier, state| Type::Argument(identifier).with_position(state.span())); + let type_specification = just(Token::Control(Control::Colon)).ignore_then(r#type.clone()); let structure_field_definition = identifier.clone().then(type_specification.clone()); @@ -213,25 +217,42 @@ pub fn parser<'src>() -> impl Parser< .with_position(state.span()) }); - let parsed_function = identifier + let type_arguments = type_argument .clone() - .then(type_specification.clone()) .separated_by(just(Token::Control(Control::Comma))) + .at_least(1) .collect() .delimited_by( just(Token::Control(Control::ParenOpen)), just(Token::Control(Control::ParenClose)), + ); + + let parsed_function = type_arguments + .or_not() + .then( + identifier + .clone() + .then(type_specification.clone()) + .separated_by(just(Token::Control(Control::Comma))) + .collect() + .delimited_by( + just(Token::Control(Control::ParenOpen)), + just(Token::Control(Control::ParenClose)), + ) + .then(r#type.clone()) + .then(block.clone()), ) - .then(type_specification.clone()) - .then(block.clone()) - .map_with(|((parameters, return_type), body), state| { - Expression::Value(ValueNode::ParsedFunction { - parameters, - return_type, - body: body.with_position(state.span()), - }) - .with_position(state.span()) - }); + .map_with( + |(type_arguments, ((parameters, return_type), body)), state| { + Expression::Value(ValueNode::ParsedFunction { + type_arguments: type_arguments.unwrap_or_else(|| Vec::with_capacity(0)), + parameters, + return_type, + body: body.with_position(state.span()), + }) + .with_position(state.span()) + }, + ); let built_in_function = { select! { @@ -246,21 +267,45 @@ pub fn parser<'src>() -> impl Parser< .with_position(state.span()) }); + use Operator::*; + + let structure_field = identifier + .clone() + .then_ignore(just(Token::Operator(Operator::Assign))) + .then(positioned_expression.clone()); + + let structure_instance = identifier + .clone() + .then( + structure_field + .separated_by(just(Token::Control(Control::Comma))) + .allow_trailing() + .collect() + .delimited_by( + just(Token::Control(Control::CurlyOpen)), + just(Token::Control(Control::CurlyClose)), + ), + ) + .map_with(|(name, fields), state| { + Expression::Value(ValueNode::Structure { name, fields }) + .with_position(state.span()) + }); + let atom = choice(( - built_in_function.clone(), + range.clone(), + structure_instance.clone(), parsed_function.clone(), - identifier_expression.clone(), - basic_value.clone(), + built_in_function.clone(), list.clone(), map.clone(), + basic_value.clone(), + identifier_expression.clone(), positioned_expression.clone().delimited_by( just(Token::Control(Control::ParenOpen)), just(Token::Control(Control::ParenClose)), ), )); - use Operator::*; - let logic_math_indexes_and_function_calls = atom.pratt(( prefix(2, just(Token::Operator(Not)), |_, expression, span| { Expression::Logic(Box::new(Logic::Not(expression))).with_position(span) @@ -395,32 +440,10 @@ pub fn parser<'src>() -> impl Parser< ), )); - let structure_field = identifier - .clone() - .then_ignore(just(Token::Operator(Operator::Assign))) - .then(positioned_expression.clone()); - - let structure_instance = identifier - .clone() - .then( - structure_field - .separated_by(just(Token::Control(Control::Comma))) - .allow_trailing() - .collect() - .delimited_by( - just(Token::Control(Control::CurlyOpen)), - just(Token::Control(Control::CurlyClose)), - ), - ) - .map_with(|(name, fields), state| { - Expression::Value(ValueNode::Structure { name, fields }) - .with_position(state.span()) - }); - choice(( - structure_instance, - range, logic_math_indexes_and_function_calls, + range, + structure_instance, parsed_function, built_in_function, list, @@ -742,7 +765,7 @@ mod tests { #[test] fn function_type() { assert_eq!( - parse(&lex("foobar : () -> any = some_function").unwrap()).unwrap()[0].node, + parse(&lex("foobar : fn() -> any = some_function").unwrap()).unwrap()[0].node, Statement::Assignment(Assignment::new( Identifier::new("foobar").with_position((0, 6)), Some( @@ -750,11 +773,11 @@ mod tests { parameter_types: vec![], return_type: Box::new(Type::Any) } - .with_position((9, 18)) + .with_position((9, 20)) ), AssignmentOperator::Assign, Statement::Expression(Expression::Identifier(Identifier::new("some_function"))) - .with_position((21, 34)) + .with_position((23, 36)) ),) ); } @@ -762,9 +785,13 @@ mod tests { #[test] fn function_call() { assert_eq!( - parse(&lex("output()").unwrap()).unwrap()[0].node, + parse(&lex("io.write_line()").unwrap()).unwrap()[0].node, Statement::Expression(Expression::FunctionCall(FunctionCall::new( - Expression::Identifier(Identifier::new("output")).with_position((0, 6)), + Expression::MapIndex(Box::new(MapIndex::new( + Expression::Identifier(Identifier::new("io")).with_position((0, 2)), + Expression::Identifier(Identifier::new("write_line")).with_position((3, 13)) + ))) + .with_position((0, 13)), Vec::with_capacity(0), ))) ) @@ -781,15 +808,45 @@ mod tests { #[test] fn function() { assert_eq!( - parse(&lex("(x: int) : int { x }").unwrap()).unwrap()[0].node, + parse(&lex("(x: int) int { x }").unwrap()).unwrap()[0].node, Statement::Expression(Expression::Value(ValueNode::ParsedFunction { + type_arguments: Vec::with_capacity(0), parameters: vec![(Identifier::new("x"), Type::Integer.with_position((4, 7)))], - return_type: Type::Integer.with_position((11, 14)), + return_type: Type::Integer.with_position((9, 12)), body: Block::new(vec![Statement::Expression(Expression::Identifier( Identifier::new("x") ),) - .with_position((17, 18))]) - .with_position((0, 20)) + .with_position((15, 16))]) + .with_position((0, 18)), + }),) + ) + } + + #[test] + fn function_with_type_arguments() { + assert_eq!( + parse(&lex("(T, U)(x: T, y: U) T { x }").unwrap()).unwrap()[0].node, + Statement::Expression(Expression::Value(ValueNode::ParsedFunction { + type_arguments: vec![ + Type::Argument(Identifier::new("T")).with_position((1, 2)), + Type::Argument(Identifier::new("U")).with_position((4, 5)), + ], + parameters: vec![ + ( + Identifier::new("x"), + Type::Argument(Identifier::new("T")).with_position((10, 11)) + ), + ( + Identifier::new("y"), + Type::Argument(Identifier::new("U")).with_position((16, 17)) + ) + ], + return_type: Type::Argument(Identifier::new("T")).with_position((19, 20)), + body: Block::new(vec![Statement::Expression(Expression::Identifier( + Identifier::new("x") + ),) + .with_position((23, 24))]) + .with_position((0, 26)), }),) ) } diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index 1c3bd54..4f3a9f5 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -56,12 +56,14 @@ impl Value { } pub fn function( + type_arguments: Vec>, parameters: Vec<(Identifier, WithPosition)>, return_type: WithPosition, body: WithPosition, ) -> Self { Value(Arc::new(ValueInner::Function(Function::Parsed( ParsedFunction { + type_arguments, parameters, return_type, body, @@ -137,10 +139,25 @@ impl Display for Value { ValueInner::Range(_) => todo!(), ValueInner::String(string) => write!(f, "{string}"), ValueInner::Function(Function::Parsed(ParsedFunction { + type_arguments, parameters, return_type, body, })) => { + if !type_arguments.is_empty() { + write!(f, "(")?; + + for (index, r#type) in type_arguments.into_iter().enumerate() { + if index == type_arguments.len() - 1 { + write!(f, "{}", r#type.node)?; + } else { + write!(f, "{} ", r#type.node)?; + } + } + + write!(f, ")")?; + } + write!(f, "(")?; for (identifier, r#type) in parameters { @@ -326,6 +343,7 @@ impl Function { #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub struct ParsedFunction { + type_arguments: Vec>, parameters: Vec<(Identifier, WithPosition)>, return_type: WithPosition, body: WithPosition, diff --git a/dust-lang/tests/expressions.rs b/dust-lang/tests/expressions.rs index 3c87047..ff27ac8 100644 --- a/dust-lang/tests/expressions.rs +++ b/dust-lang/tests/expressions.rs @@ -1,19 +1,27 @@ +use std::rc::Rc; + use dust_lang::*; #[test] fn logic() { - assert_eq!(interpret("1 == 1").unwrap(), Some(Value::boolean(true))); assert_eq!( - interpret("('42' == '42') && (42 != 0)").unwrap(), + interpret(Rc::new("test".to_string()), "1 == 1").unwrap(), + Some(Value::boolean(true)) + ); + assert_eq!( + interpret(Rc::new("test".to_string()), "('42' == '42') && (42 != 0)").unwrap(), Some(Value::boolean(true)) ); } #[test] fn math() { - assert_eq!(interpret("1 + 1").unwrap(), Some(Value::integer(2))); assert_eq!( - interpret("2 * (21 + 19 + 1 * 2) / 2").unwrap(), + interpret(Rc::new("test".to_string()), "1 + 1").unwrap(), + Some(Value::integer(2)) + ); + assert_eq!( + interpret(Rc::new("test".to_string()), "2 * (21 + 19 + 1 * 2) / 2").unwrap(), Some(Value::integer(42)) ); } @@ -21,16 +29,19 @@ fn math() { #[test] fn list_index() { assert_eq!( - interpret("foo = [1, 2, 3]; foo[2]").unwrap(), + interpret(Rc::new("test".to_string()), "foo = [1, 2, 3]; foo[2]").unwrap(), Some(Value::integer(3)) ); } #[test] fn map_index() { - assert_eq!(interpret("{ x = 3 }.x").unwrap(), Some(Value::integer(3))); assert_eq!( - interpret("foo = { x = 3 }; foo.x").unwrap(), + interpret(Rc::new("test".to_string()), "{ x = 3 }.x").unwrap(), + Some(Value::integer(3)) + ); + assert_eq!( + interpret(Rc::new("test".to_string()), "foo = { x = 3 }; foo.x").unwrap(), Some(Value::integer(3)) ); } diff --git a/dust-lang/tests/functions.rs b/dust-lang/tests/functions.rs index c8498e6..b66a481 100644 --- a/dust-lang/tests/functions.rs +++ b/dust-lang/tests/functions.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use dust_lang::{ abstract_tree::Identifier, error::{Error, ValidationError}, @@ -8,8 +10,9 @@ use dust_lang::{ fn function_call() { assert_eq!( interpret( + Rc::new("test".to_string()), " - foobar = (message : str) : str { message } + foobar = (message : str) str { message } foobar('Hiya') ", ), @@ -21,8 +24,9 @@ fn function_call() { fn call_empty_function() { assert_eq!( interpret( + Rc::new("test".to_string()), " - foobar = (message : str) : none {} + foobar = (message : str) none {} foobar('Hiya') ", ), @@ -34,11 +38,12 @@ fn call_empty_function() { fn callback() { assert_eq!( interpret( + Rc::new("test".to_string()), " - foobar = (cb : () -> str) : str { + foobar = (cb: fn() -> str) str { cb() } - foobar(() : str { 'Hiya' }) + foobar(() str { 'Hiya' }) ", ), Ok(Some(Value::string("Hiya".to_string()))) @@ -47,30 +52,37 @@ fn callback() { #[test] fn built_in_function_call() { - assert_eq!(interpret("io.write_line('Hiya')"), Ok(None)); + assert_eq!( + interpret(Rc::new("test".to_string()), "io.write_line('Hiya')"), + Ok(None) + ); } #[test] fn function_context_does_not_capture_values() { assert_eq!( interpret( + Rc::new("test".to_string()), " x = 1 - foo = () : any { x } + foo = () any { x } " - ), - Err(vec![Error::Validation { + ) + .unwrap_err() + .errors(), + &vec![Error::Validation { error: ValidationError::VariableNotFound(Identifier::new("x")), - position: (32, 52).into() - }]) + position: (32, 50).into() + }] ); assert_eq!( interpret( + Rc::new("test".to_string()), " x = 1 - foo = (x : int) : int { x } + foo = (x: int) int { x } foo(2) " ), @@ -82,9 +94,10 @@ fn function_context_does_not_capture_values() { fn function_context_captures_functions() { assert_eq!( interpret( + Rc::new("test".to_string()), " - bar = () : int { 2 } - foo = () : int { bar() } + bar = () int { 2 } + foo = () int { bar() } foo() " ), @@ -96,8 +109,9 @@ fn function_context_captures_functions() { fn recursion() { assert_eq!( interpret( + Rc::new("test".to_string()), " - fib = (i : int) : int { + fib = (i: int) int { if i <= 1 { 1 } else { diff --git a/dust-lang/tests/statements.rs b/dust-lang/tests/statements.rs index f5f7a21..5b8e898 100644 --- a/dust-lang/tests/statements.rs +++ b/dust-lang/tests/statements.rs @@ -1,9 +1,12 @@ +use std::rc::Rc; + use dust_lang::*; #[test] fn async_block() { assert_eq!( interpret( + Rc::new("test".to_string()), " x = 41 async { @@ -21,6 +24,7 @@ fn async_block() { fn loops_and_breaks() { assert_eq!( interpret( + Rc::new("test".to_string()), " i = 0 loop { @@ -37,6 +41,7 @@ fn loops_and_breaks() { ); assert_eq!( interpret( + Rc::new("test".to_string()), " foobar = { while true { @@ -55,7 +60,7 @@ fn loops_and_breaks() { #[test] fn r#if() { assert_eq!( - interpret("if true { 'foobar' }"), + interpret(Rc::new("test".to_string()), "if true { 'foobar' }"), Ok(Some(Value::string("foobar".to_string()))) ) } @@ -63,7 +68,10 @@ fn r#if() { #[test] fn if_else() { assert_eq!( - interpret("if false { 'foo' } else { 'bar' }"), + interpret( + Rc::new("test".to_string()), + "if false { 'foo' } else { 'bar' }" + ), Ok(Some(Value::string("bar".to_string()))) ) } diff --git a/dust-lang/tests/structs.rs b/dust-lang/tests/structs.rs index 49b0cec..ba2f348 100644 --- a/dust-lang/tests/structs.rs +++ b/dust-lang/tests/structs.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use dust_lang::{ abstract_tree::{Identifier, Type}, error::{Error, TypeConflict, ValidationError}, @@ -7,6 +9,7 @@ use dust_lang::{ fn simple_structure() { assert_eq!( interpret( + Rc::new("test".to_string()), " struct Foo { bar : int, @@ -33,6 +36,7 @@ fn simple_structure() { fn field_type_error() { assert_eq!( interpret( + Rc::new("test".to_string()), " struct Foo { bar : int, @@ -42,8 +46,10 @@ fn field_type_error() { bar = 'hiya', } " - ), - Err(vec![Error::Validation { + ) + .unwrap_err() + .errors(), + &vec![Error::Validation { error: ValidationError::TypeCheck { conflict: TypeConflict { actual: Type::String, @@ -53,7 +59,7 @@ fn field_type_error() { expected_position: (56, 59).into() }, position: (96, 153).into() - }]) + }] ) } @@ -61,6 +67,7 @@ fn field_type_error() { fn nested_structure() { assert_eq!( interpret( + Rc::new("test".to_string()), " struct Bar { baz : int @@ -93,15 +100,18 @@ fn nested_structure() { fn undefined_struct() { assert_eq!( interpret( + Rc::new("test".to_string()), " Foo { bar = 42 } " - ), - Err(vec![Error::Validation { + ) + .unwrap_err() + .errors(), + &vec![Error::Validation { error: error::ValidationError::VariableNotFound(Identifier::new("Foo")), position: (17, 69).into() - }]) + }] ) } diff --git a/dust-lang/tests/values.rs b/dust-lang/tests/values.rs index 773e16c..6f0563d 100644 --- a/dust-lang/tests/values.rs +++ b/dust-lang/tests/values.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, rc::Rc}; use dust_lang::{ abstract_tree::{Identifier, Type}, @@ -8,25 +8,37 @@ use dust_lang::{ #[test] fn none() { - assert_eq!(interpret("x = 9"), Ok(None)); - assert_eq!(interpret("x = 1 + 1"), Ok(None)); + assert_eq!(interpret(Rc::new("test".to_string()), "x = 9"), Ok(None)); + assert_eq!( + interpret(Rc::new("test".to_string()), "x = 1 + 1"), + Ok(None) + ); } #[test] fn integer() { - assert_eq!(interpret("1"), Ok(Some(Value::integer(1)))); - assert_eq!(interpret("123"), Ok(Some(Value::integer(123)))); - assert_eq!(interpret("-666"), Ok(Some(Value::integer(-666)))); + assert_eq!( + interpret(Rc::new("test".to_string()), "1"), + Ok(Some(Value::integer(1))) + ); + assert_eq!( + interpret(Rc::new("test".to_string()), "123"), + Ok(Some(Value::integer(123))) + ); + assert_eq!( + interpret(Rc::new("test".to_string()), "-666"), + Ok(Some(Value::integer(-666))) + ); } #[test] fn integer_saturation() { assert_eq!( - interpret("9223372036854775807 + 1"), + interpret(Rc::new("test".to_string()), "9223372036854775807 + 1"), Ok(Some(Value::integer(i64::MAX))) ); assert_eq!( - interpret("-9223372036854775808 - 1"), + interpret(Rc::new("test".to_string()), "-9223372036854775808 - 1"), Ok(Some(Value::integer(i64::MIN))) ); } @@ -34,11 +46,11 @@ fn integer_saturation() { #[test] fn float() { assert_eq!( - interpret("1.7976931348623157e308"), + interpret(Rc::new("test".to_string()), "1.7976931348623157e308"), Ok(Some(Value::float(f64::MAX))) ); assert_eq!( - interpret("-1.7976931348623157e308"), + interpret(Rc::new("test".to_string()), "-1.7976931348623157e308"), Ok(Some(Value::float(f64::MIN))) ); } @@ -46,11 +58,11 @@ fn float() { #[test] fn float_saturation() { assert_eq!( - interpret("1.7976931348623157e308 + 1"), + interpret(Rc::new("test".to_string()), "1.7976931348623157e308 + 1"), Ok(Some(Value::float(f64::MAX))) ); assert_eq!( - interpret("-1.7976931348623157e308 - 1"), + interpret(Rc::new("test".to_string()), "-1.7976931348623157e308 - 1"), Ok(Some(Value::float(f64::MIN))) ); } @@ -58,27 +70,27 @@ fn float_saturation() { #[test] fn string() { assert_eq!( - interpret("\"one\""), + interpret(Rc::new("test".to_string()), "\"one\""), Ok(Some(Value::string("one".to_string()))) ); assert_eq!( - interpret("'one'"), + interpret(Rc::new("test".to_string()), "'one'"), Ok(Some(Value::string("one".to_string()))) ); assert_eq!( - interpret("`one`"), + interpret(Rc::new("test".to_string()), "`one`"), Ok(Some(Value::string("one".to_string()))) ); assert_eq!( - interpret("`'one'`"), + interpret(Rc::new("test".to_string()), "`'one'`"), Ok(Some(Value::string("'one'".to_string()))) ); assert_eq!( - interpret("'`one`'"), + interpret(Rc::new("test".to_string()), "'`one`'"), Ok(Some(Value::string("`one`".to_string()))) ); assert_eq!( - interpret("\"'one'\""), + interpret(Rc::new("test".to_string()), "\"'one'\""), Ok(Some(Value::string("'one'".to_string()))) ); } @@ -86,7 +98,7 @@ fn string() { #[test] fn list() { assert_eq!( - interpret("[1, 2, 'foobar']"), + interpret(Rc::new("test".to_string()), "[1, 2, 'foobar']"), Ok(Some(Value::list(vec![ Value::integer(1), Value::integer(2), @@ -97,7 +109,10 @@ fn list() { #[test] fn empty_list() { - assert_eq!(interpret("[]"), Ok(Some(Value::list(Vec::new())))); + assert_eq!( + interpret(Rc::new("test".to_string()), "[]"), + Ok(Some(Value::list(Vec::new()))) + ); } #[test] @@ -108,14 +123,17 @@ fn map() { map.insert(Identifier::new("foo"), Value::string("bar".to_string())); assert_eq!( - interpret("{ x = 1, foo = 'bar' }"), + interpret(Rc::new("test".to_string()), "{ x = 1, foo = 'bar' }"), Ok(Some(Value::map(map))) ); } #[test] fn empty_map() { - assert_eq!(interpret("{}"), Ok(Some(Value::map(BTreeMap::new())))); + assert_eq!( + interpret(Rc::new("test".to_string()), "{}"), + Ok(Some(Value::map(BTreeMap::new()))) + ); } #[test] @@ -126,7 +144,10 @@ fn map_types() { map.insert(Identifier::new("foo"), Value::string("bar".to_string())); assert_eq!( - interpret("{ x : int = 1, foo : str = 'bar' }"), + interpret( + Rc::new("test".to_string()), + "{ x : int = 1, foo : str = 'bar' }" + ), Ok(Some(Value::map(map))) ); } @@ -134,8 +155,10 @@ fn map_types() { #[test] fn map_type_errors() { assert_eq!( - interpret("{ foo : bool = 'bar' }"), - Err(vec![Error::Validation { + interpret(Rc::new("test".to_string()), "{ foo : bool = 'bar' }") + .unwrap_err() + .errors(), + &vec![Error::Validation { error: ValidationError::TypeCheck { conflict: TypeConflict { actual: Type::String, @@ -145,11 +168,14 @@ fn map_type_errors() { expected_position: (8, 12).into(), }, position: (0, 22).into() - }]) + }] ); } #[test] fn range() { - assert_eq!(interpret("0..100"), Ok(Some(Value::range(0..100)))); + assert_eq!( + interpret(Rc::new("test".to_string()), "0..100"), + Ok(Some(Value::range(0..100))) + ); } diff --git a/dust-lang/tests/variables.rs b/dust-lang/tests/variables.rs index 7574717..4ef1547 100644 --- a/dust-lang/tests/variables.rs +++ b/dust-lang/tests/variables.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use dust_lang::{ abstract_tree::{AbstractNode, Block, Expression, Identifier, Statement, Type}, error::{Error, TypeConflict, ValidationError}, @@ -7,7 +9,7 @@ use dust_lang::{ #[test] fn set_and_get_variable() { assert_eq!( - interpret("foobar = true; foobar"), + interpret(Rc::new("test".to_string()), "foobar = true; foobar"), Ok(Some(Value::boolean(true))) ); } @@ -15,7 +17,7 @@ fn set_and_get_variable() { #[test] fn set_variable_with_type() { assert_eq!( - interpret("foobar: bool = true; foobar"), + interpret(Rc::new("test".to_string()), "foobar: bool = true; foobar"), Ok(Some(Value::boolean(true))) ); } @@ -23,8 +25,10 @@ fn set_variable_with_type() { #[test] fn set_variable_with_type_error() { assert_eq!( - interpret("foobar: str = true"), - Err(vec![Error::Validation { + interpret(Rc::new("test".to_string()), "foobar: str = true") + .unwrap_err() + .errors(), + &vec![Error::Validation { error: ValidationError::TypeCheck { conflict: TypeConflict { actual: Type::Boolean, @@ -34,22 +38,26 @@ fn set_variable_with_type_error() { expected_position: (8, 11).into() }, position: (0, 18).into() - }]) + }] ); } #[test] fn function_variable() { assert_eq!( - interpret("foobar = (x: int): int { x }; foobar"), + interpret( + Rc::new("test".to_string()), + "foobar = (x: int) int { x }; foobar" + ), Ok(Some(Value::function( + Vec::with_capacity(0), vec![(Identifier::new("x"), Type::Integer.with_position((13, 16)))], - Type::Integer.with_position((19, 22)), + Type::Integer.with_position((18, 21)), Block::new(vec![Statement::Expression(Expression::Identifier( Identifier::new("x") )) - .with_position((25, 26))]) - .with_position((9, 28)) + .with_position((24, 25))]) + .with_position((9, 27)) ))) ); } diff --git a/dust-shell/src/cli.rs b/dust-shell/src/cli.rs index 450c3a0..d1569e0 100644 --- a/dust-shell/src/cli.rs +++ b/dust-shell/src/cli.rs @@ -1,4 +1,10 @@ -use std::{borrow::Cow, io::stderr, path::PathBuf, process::Command, rc::Rc}; +use std::{ + borrow::Cow, + io::{self, stderr}, + path::PathBuf, + process::Command, + rc::Rc, +}; use ariadne::sources; use dust_lang::{ @@ -12,9 +18,7 @@ use reedline::{ SqliteBackedHistory, Suggestion, }; -use crate::error::Error; - -pub fn run_shell(context: Context) -> Result<(), Error> { +pub fn run_shell(context: Context) -> Result<(), io::Error> { let mut interpreter = Interpreter::new(context.clone()); let mut keybindings = default_emacs_keybindings(); @@ -76,18 +80,16 @@ pub fn run_shell(context: Context) -> Result<(), Error> { continue; } - let run_result = interpreter.run(&buffer); + let run_result = interpreter.run(Rc::new("input".to_string()), &buffer); match run_result { Ok(Some(value)) => { println!("{value}") } Ok(None) => {} - Err(errors) => { + Err(error) => { let source_id = Rc::new("input".to_string()); - let reports = Error::Dust { errors } - .build_reports(source_id.clone()) - .unwrap(); + let reports = error.build_reports(); for report in reports { report diff --git a/dust-shell/src/error.rs b/dust-shell/src/error.rs deleted file mode 100644 index 4d74f17..0000000 --- a/dust-shell/src/error.rs +++ /dev/null @@ -1,263 +0,0 @@ -use ariadne::{Color, Fmt, Label, Report, ReportKind}; -use dust_lang::{ - abstract_tree::Type, - error::{Error as DustError, RuntimeError, TypeConflict, ValidationError}, -}; -use std::{ - fmt::{self, Display, Formatter}, - io, - ops::Range, - rc::Rc, -}; - -#[derive(Debug)] -pub enum Error { - Dust { - errors: Vec, - }, - Io(io::Error), -} - -impl From for Error { - fn from(error: io::Error) -> Self { - Error::Io(error) - } -} - -impl Error { - pub fn build_reports<'id>( - self, - source_id: Rc, - ) -> Result, Range)>>, io::Error> { - if let Error::Dust { errors } = self { - let mut reports = Vec::new(); - - for error in errors { - let (mut builder, validation_error, error_position) = match error { - DustError::Parse { - expected, - span, - reason, - } => { - let description = if expected.is_empty() { - "Invalid token.".to_string() - } else { - format!("Expected {expected}.") - }; - - ( - Report::build( - ReportKind::Custom("Parsing Error", Color::Yellow), - source_id.clone(), - span.1, - ) - .with_message(description) - .with_label( - Label::new((source_id.clone(), span.0..span.1)) - .with_message(reason) - .with_color(Color::Red), - ), - None, - span.into(), - ) - } - DustError::Lex { - expected, - span, - reason, - } => { - let description = if expected.is_empty() { - "Invalid character.".to_string() - } else { - format!("Expected {expected}.") - }; - - ( - Report::build( - ReportKind::Custom("Lexing Error", Color::Yellow), - source_id.clone(), - span.1, - ) - .with_message(description) - .with_label( - Label::new((source_id.clone(), span.0..span.1)) - .with_message(reason) - .with_color(Color::Red), - ), - None, - span.into(), - ) - } - DustError::Runtime { error, position } => ( - Report::build( - ReportKind::Custom("Runtime Error", Color::Red), - source_id.clone(), - position.1, - ) - .with_message("An error occured that forced the program to exit.") - .with_note( - "There may be unexpected side-effects because the program could not finish.", - ) - .with_help( - "This is the interpreter's fault. Please submit a bug with this error message.", - ), - if let RuntimeError::ValidationFailure(validation_error) = error { - Some(validation_error) - } else { - None - }, - position, - ), - DustError::Validation { error, position } => ( - Report::build( - ReportKind::Custom("Validation Error", Color::Magenta), - source_id.clone(), - position.1, - ) - .with_message("The syntax is valid but this code is not sound.") - .with_note("This error was detected by the interpreter before running the code."), - Some(error), - position, - ), - }; - - let type_color = Color::Green; - let identifier_color = Color::Blue; - - if let Some(validation_error) = validation_error { - match validation_error { - ValidationError::ExpectedBoolean { actual, position } => { - builder.add_label( - Label::new((source_id.clone(), position.0..position.1)) - .with_message(format!( - "Expected {} but got {}.", - "boolean".fg(type_color), - actual.fg(type_color) - )), - ); - } - ValidationError::ExpectedIntegerOrFloat(position) => { - builder.add_label( - Label::new((source_id.clone(), position.0..position.1)) - .with_message(format!( - "Expected {} or {}.", - "integer".fg(type_color), - "float".fg(type_color) - )), - ); - } - ValidationError::RwLockPoison(_) => todo!(), - ValidationError::TypeCheck { - conflict, - actual_position, - expected_position: expected_postion, - } => { - let TypeConflict { actual, expected } = conflict; - - builder = builder.with_message("A type conflict was found."); - - builder.add_labels([ - Label::new(( - source_id.clone(), - expected_postion.0..expected_postion.1, - )) - .with_message(format!( - "Type {} established here.", - expected.fg(type_color) - )), - Label::new(( - source_id.clone(), - actual_position.0..actual_position.1, - )) - .with_message(format!("Got type {} here.", actual.fg(type_color))), - ]); - } - ValidationError::VariableNotFound(identifier) => builder.add_label( - Label::new((source_id.clone(), error_position.0..error_position.1)) - .with_message(format!( - "Variable {} does not exist in this context.", - identifier.fg(identifier_color) - )), - ), - ValidationError::CannotIndex { r#type, position } => builder.add_label( - Label::new((source_id.clone(), position.0..position.1)).with_message( - format!("Cannot index into a {}.", r#type.fg(type_color)), - ), - ), - ValidationError::CannotIndexWith { - collection_type, - collection_position, - index_type, - index_position, - } => { - builder = builder.with_message(format!( - "Cannot index into {} with {}.", - collection_type.clone().fg(type_color), - index_type.clone().fg(type_color) - )); - - builder.add_labels([ - Label::new(( - source_id.clone(), - collection_position.0..collection_position.1, - )) - .with_message(format!( - "This has type {}.", - collection_type.fg(type_color), - )), - Label::new((source_id.clone(), index_position.0..index_position.1)) - .with_message(format!( - "This has type {}.", - index_type.fg(type_color), - )), - ]) - } - ValidationError::InterpreterExpectedReturn(_) => todo!(), - ValidationError::ExpectedFunction { .. } => todo!(), - ValidationError::ExpectedValue(_) => todo!(), - ValidationError::PropertyNotFound { .. } => todo!(), - ValidationError::WrongArguments { .. } => todo!(), - ValidationError::ExpectedIntegerFloatOrString { actual, position } => { - builder = builder.with_message(format!( - "Expected an {}, {} or {}.", - Type::Integer.fg(type_color), - Type::Float.fg(type_color), - Type::String.fg(type_color) - )); - - builder.add_labels([Label::new(( - source_id.clone(), - position.0..position.1, - )) - .with_message(format!("This has type {}.", actual.fg(type_color),))]) - } - } - } - let report = builder.finish(); - - reports.push(report); - } - - return Ok(reports); - } else { - return Ok(Vec::with_capacity(0)); - }; - } -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Error::Dust { errors } => { - for error in errors { - writeln!(f, "{error:?}")?; - } - - Ok(()) - } - Error::Io(io_error) => { - write!(f, "{io_error}") - } - } - } -} diff --git a/dust-shell/src/main.rs b/dust-shell/src/main.rs index 33f7a73..241e3d1 100644 --- a/dust-shell/src/main.rs +++ b/dust-shell/src/main.rs @@ -1,12 +1,10 @@ //! Command line interface for the dust programming language. mod cli; -mod error; use ariadne::sources; use clap::Parser; use cli::run_shell; use colored::Colorize; -use error::Error; use std::{ fs::read_to_string, @@ -14,7 +12,7 @@ use std::{ rc::Rc, }; -use dust_lang::{context::Context, interpret}; +use dust_lang::{context::Context, interpret, interpret_without_std}; /// Command-line arguments to be parsed. #[derive(Parser, Debug)] @@ -24,6 +22,9 @@ struct Args { #[arg(short, long)] command: Option, + #[arg(long)] + no_std: bool, + /// Location of the file to run. path: Option, } @@ -54,7 +55,11 @@ fn main() { return; }; - let eval_result = interpret(&source); + let eval_result = if args.no_std { + interpret_without_std(source_id.clone(), &source) + } else { + interpret(source_id.clone(), &source) + }; match eval_result { Ok(value) => { @@ -62,27 +67,10 @@ fn main() { println!("{value}") } } - Err(errors) => { - let reports = Error::Dust { errors } - .build_reports(source_id.clone()) - .unwrap(); - - for report in reports { + Err(error) => { + for report in error.build_reports() { report - .write_for_stdout( - sources([ - (source_id.clone(), source.as_str()), - ( - Rc::new("std/io.ds".to_string()), - include_str!("../../std/io.ds"), - ), - ( - Rc::new("std/thread.ds".to_string()), - include_str!("../../std/thread.ds"), - ), - ]), - stderr(), - ) + .write_for_stdout(sources([(source_id.clone(), source.as_str())]), stderr()) .unwrap(); } } diff --git a/std/io.ds b/std/io.ds index 74983fb..3659f1e 100644 --- a/std/io.ds +++ b/std/io.ds @@ -1,9 +1,9 @@ io = { - read_line = () : str { + read_line = () str { __READ_LINE__() } - write_line = (output: str) : none { + write_line = (T)(output: T) none { __WRITE_LINE__(output) } } diff --git a/std/thread.ds b/std/thread.ds index 1b1f80a..8e5a6e2 100644 --- a/std/thread.ds +++ b/std/thread.ds @@ -1,5 +1,5 @@ thread = { - sleep = (milliseconds: int) : none { + sleep = (milliseconds: int) none { __SLEEP__(milliseconds) } }