diff --git a/README.md b/README.md index 215aad6..c4f46e8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Dust -High-level programming language with effortless concurrency, automatic memory management and first class functions. +High-level programming language with effortless concurrency, automatic memory management, type safety and strict error handling. ![Dust version of an example from The Rust Programming Language.](https://git.jeffa.io/jeff/dust/docs/assets/example_0.png) @@ -8,6 +8,7 @@ High-level programming language with effortless concurrency, automatic memory ma - [Dust](#dust) - [Easy to Read and Write](#easy-to-read-and-write) - [Effortless Concurrency](#effortless-concurrency) + - [Helpful Errors](#helpful-errors) - [Debugging](#debugging) - [Automatic Memory Management](#automatic-memory-management) - [Installation and Usage](#installation-and-usage) @@ -15,10 +16,44 @@ High-level programming language with effortless concurrency, automatic memory ma ## Easy to Read and Write +Dust has simple, easy-to-learn syntax. + +```js +output('Hello world!') +``` + ## Effortless Concurrency +Write multi-threaded code as easily as you would write code for a single thread. + +```js +async { + output('Will this one print first?') + output('Or will this one?') + output('Who knows! Each "output" will run in its own thread!') +} +``` + +## Helpful Errors + +Dust shows you exactly where your code went wrong and suggests changes. + +![Example of syntax error output.](https://git.jeffa.io/jeff/dust/docs/assets/syntax_error.png) + +## Static analysis + +Your code is always validated for safety before it is run. Other interpreted languages can fail halfway through, but Dust is able to avoid runtime errors by analyzing the program *before* it is run + +![Example of type error output.](https://git.jeffa.io/jeff/dust/docs/assets/type_error.png) + ## Debugging +Just set the environment variable `DUST_LOG=info` and Dust will tell you exactly what your code is doing while it's doing it. If you set `DUST_LOG=trace`, it will output detailed logs about parsing, abstraction, validation, memory management and runtime. + +![Example of debug output.](https://git.jeffa.io/jeff/dust/docs/assets/debugging.png) + ## Automatic Memory Management +## Error Handling + ## Installation and Usage diff --git a/docs/assets/debugging.png b/docs/assets/debugging.png new file mode 100644 index 0000000..b4bb53b Binary files /dev/null and b/docs/assets/debugging.png differ diff --git a/docs/assets/syntax_error.png b/docs/assets/syntax_error.png new file mode 100644 index 0000000..5e995c0 Binary files /dev/null and b/docs/assets/syntax_error.png differ diff --git a/docs/assets/type_error.png b/docs/assets/type_error.png new file mode 100644 index 0000000..01400cb Binary files /dev/null and b/docs/assets/type_error.png differ diff --git a/src/abstract_tree/assignment.rs b/src/abstract_tree/assignment.rs index 0a09c95..2edb2a3 100644 --- a/src/abstract_tree/assignment.rs +++ b/src/abstract_tree/assignment.rs @@ -60,6 +60,8 @@ impl AbstractTree for Assignment { self.statement.expected_type(context)? }; + log::info!("Setting type: {} <{}>", self.identifier, r#type); + context.set_type(self.identifier.clone(), r#type)?; } @@ -155,6 +157,8 @@ impl AbstractTree for Assignment { .set_value(self.identifier.clone(), new_value.clone())?; } + log::info!("RUN assignment: {} = {}", self.identifier, new_value); + context.set_value(self.identifier.clone(), new_value)?; Ok(Value::none()) diff --git a/src/abstract_tree/logic.rs b/src/abstract_tree/logic.rs index 5c7777c..5cf33ba 100644 --- a/src/abstract_tree/logic.rs +++ b/src/abstract_tree/logic.rs @@ -45,6 +45,8 @@ impl AbstractTree for Logic { } fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> { + log::info!("VALIDATE logic expression"); + self.left.validate(_source, _context)?; self.right.validate(_source, _context) } @@ -52,6 +54,9 @@ impl AbstractTree for Logic { fn run(&self, source: &str, context: &Context) -> Result { let left = self.left.run(source, context)?; let right = self.right.run(source, context)?; + + log::info!("RUN logic expression: {left} {} {right}", self.operator); + let result = match self.operator { LogicOperator::Equal => { if let (Ok(left_num), Ok(right_num)) = (left.as_number(), right.as_number()) { diff --git a/src/abstract_tree/logic_operator.rs b/src/abstract_tree/logic_operator.rs index b271142..3865f05 100644 --- a/src/abstract_tree/logic_operator.rs +++ b/src/abstract_tree/logic_operator.rs @@ -1,3 +1,5 @@ +use std::fmt::{self, Display, Formatter}; + use serde::{Deserialize, Serialize}; use crate::{ @@ -74,3 +76,18 @@ impl Format for LogicOperator { } } } + +impl Display for LogicOperator { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + LogicOperator::Equal => write!(f, "="), + LogicOperator::NotEqual => write!(f, "!="), + LogicOperator::And => write!(f, "&&"), + LogicOperator::Or => write!(f, "||"), + LogicOperator::Greater => write!(f, ">"), + LogicOperator::Less => write!(f, "<"), + LogicOperator::GreaterOrEqual => write!(f, ">="), + LogicOperator::LessOrEqual => write!(f, "<="), + } + } +} diff --git a/src/abstract_tree/math_operator.rs b/src/abstract_tree/math_operator.rs index 2d6f1d1..3b7ad3a 100644 --- a/src/abstract_tree/math_operator.rs +++ b/src/abstract_tree/math_operator.rs @@ -20,6 +20,8 @@ impl AbstractTree for MathOperator { _source: &str, _context: &Context, ) -> Result { + SyntaxError::expect_syntax_node("math_operator", node)?; + let operator_node = node.child(0).unwrap(); let operator = match operator_node.kind() { "+" => MathOperator::Add, diff --git a/src/abstract_tree/value_node.rs b/src/abstract_tree/value_node.rs index 843b334..6693b63 100644 --- a/src/abstract_tree/value_node.rs +++ b/src/abstract_tree/value_node.rs @@ -179,7 +179,7 @@ impl AbstractTree for ValueNode { } } ValueNode::Map(map_node) => map_node.validate(_source, context)?, - ValueNode::Enum { name, variant, expression } => { + ValueNode::Enum { name, expression, .. } => { name.validate(_source, context)?; if let Some(expression) = expression { diff --git a/src/abstract_tree/while.rs b/src/abstract_tree/while.rs index 07ba81c..a604a02 100644 --- a/src/abstract_tree/while.rs +++ b/src/abstract_tree/while.rs @@ -32,15 +32,21 @@ impl AbstractTree for While { } fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> { + log::info!("VALIDATE while loop"); + self.expression.validate(_source, context)?; self.block.validate(_source, context) } fn run(&self, source: &str, context: &Context) -> Result { + log::info!("RUN while loop start"); + while self.expression.run(source, context)?.as_boolean()? { self.block.run(source, context)?; } + log::info!("RUN while loop end"); + Ok(Value::none()) } } diff --git a/src/context/mod.rs b/src/context/mod.rs index 07fee6c..ef94cd7 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -264,8 +264,6 @@ impl Context { /// Set a value to a key. pub fn set_value(&self, key: Identifier, value: Value) -> Result<(), RwLockError> { - log::info!("Setting value: {key} = {value}"); - let mut map = self.inner.write()?; let old_data = map.remove(&key); @@ -283,8 +281,6 @@ impl Context { /// This allows the interpreter to check a value's type before the value /// actually exists by predicting what the abstract tree will produce. pub fn set_type(&self, key: Identifier, r#type: Type) -> Result<(), RwLockError> { - log::info!("Setting type: {key} <{}>", r#type); - self.inner .write()? .insert(key, (ValueData::TypeHint(r#type), UsageCounter::new())); diff --git a/src/error/syntax_error.rs b/src/error/syntax_error.rs index 27038cc..c2bed44 100644 --- a/src/error/syntax_error.rs +++ b/src/error/syntax_error.rs @@ -1,5 +1,6 @@ use std::fmt::{self, Display, Formatter}; +use colored::Colorize; use lyneate::Report; use serde::{Deserialize, Serialize}; use tree_sitter::Node as SyntaxNode; @@ -12,6 +13,8 @@ use super::rw_lock_error::RwLockError; pub enum SyntaxError { /// Invalid user input. InvalidSource { + expected: String, + actual: String, position: SourcePosition, }, @@ -27,25 +30,25 @@ pub enum SyntaxError { impl SyntaxError { pub fn create_report(&self, source: &str) -> String { let messages = match self { - SyntaxError::InvalidSource { position } => { + SyntaxError::InvalidSource { position, .. } => self + .to_string() + .split_inclusive(".") + .map(|message_part| { + ( + position.start_byte..position.end_byte, + message_part.to_string(), + (255, 200, 100), + ) + }) + .collect(), + SyntaxError::RwLock(_) => todo!(), + SyntaxError::UnexpectedSyntaxNode { position, .. } => { vec![( position.start_byte..position.end_byte, - format!( - "Invalid syntax from ({}, {}) to ({}, {}).", - position.start_row, - position.start_column, - position.end_row, - position.end_column, - ), + self.to_string(), (255, 200, 100), )] } - SyntaxError::RwLock(_) => todo!(), - SyntaxError::UnexpectedSyntaxNode { - expected: _, - actual: _, - position: _, - } => todo!(), }; Report::new_byte_spanned(source, messages).display_str() @@ -58,6 +61,8 @@ impl SyntaxError { Ok(()) } else if actual.is_error() { Err(SyntaxError::InvalidSource { + expected: expected.to_owned(), + actual: actual.kind().to_string(), position: SourcePosition::from(actual.range()), }) } else { @@ -79,16 +84,43 @@ impl From for SyntaxError { impl Display for SyntaxError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - SyntaxError::InvalidSource { position } => write!(f, "Invalid syntax at {position:?}."), + SyntaxError::InvalidSource { + expected, + actual, + position, + } => { + let actual = if actual == "ERROR" { + "unrecognized characters" + } else { + actual + }; + + write!( + f, + "Invalid syntax from ({}, {}) to ({}, {}). Exected {} but found {}.", + position.start_row, + position.start_column, + position.end_row, + position.end_column, + expected.bold().green(), + actual.bold().red(), + ) + } SyntaxError::RwLock(_) => todo!(), SyntaxError::UnexpectedSyntaxNode { expected, actual, position, - } => write!( - f, - "Unexpected syntax node. Expected {expected} but got {actual} at {position:?}." - ), + } => { + write!( + f, + "Interpreter Error. Tried to parse {actual} as {expected} from ({}, {}) to ({}, {}).", + position.start_row, + position.start_column, + position.end_row, + position.end_column, + ) + } } } } diff --git a/src/error/validation_error.rs b/src/error/validation_error.rs index 88e92aa..3b18a28 100644 --- a/src/error/validation_error.rs +++ b/src/error/validation_error.rs @@ -1,5 +1,6 @@ use std::fmt::{self, Display, Formatter}; +use colored::Colorize; use lyneate::Report; use serde::{Deserialize, Serialize}; @@ -223,10 +224,17 @@ impl ValidationError { position, } => vec![( position.start_byte..position.end_byte, - format!("Type {actual} is incompatible with {expected}."), + format!( + "Type {} is incompatible with {}.", + actual.to_string().bold().red(), + expected.to_string().bold().green() + ), (200, 200, 200), )], - ValidationError::TypeCheckExpectedFunction { actual: _, position: _ } => todo!(), + ValidationError::TypeCheckExpectedFunction { + actual: _, + position: _, + } => todo!(), ValidationError::VariableIdentifierNotFound(_) => todo!(), ValidationError::TypeDefinitionNotFound(_) => todo!(), ValidationError::ExpectedEnumDefintion { actual: _ } => todo!(),