Write docs; Update logging and error messages

This commit is contained in:
Jeff 2024-02-19 17:00:33 -05:00
parent fb3cd6e6da
commit 1585145ff4
13 changed files with 132 additions and 27 deletions

View File

@ -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

BIN
docs/assets/debugging.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/assets/type_error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -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())

View File

@ -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<Value, RuntimeError> {
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()) {

View File

@ -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, "<="),
}
}
}

View File

@ -20,6 +20,8 @@ impl AbstractTree for MathOperator {
_source: &str,
_context: &Context,
) -> Result<Self, SyntaxError> {
SyntaxError::expect_syntax_node("math_operator", node)?;
let operator_node = node.child(0).unwrap();
let operator = match operator_node.kind() {
"+" => MathOperator::Add,

View File

@ -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 {

View File

@ -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<Value, RuntimeError> {
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())
}
}

View File

@ -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()));

View File

@ -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<RwLockError> 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,
)
}
}
}
}

View File

@ -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!(),