Write docs; Update logging and error messages
This commit is contained in:
parent
fb3cd6e6da
commit
b7d152be91
37
README.md
37
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
|
||||
|
BIN
docs/assets/debugging.png
Normal file
BIN
docs/assets/debugging.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
BIN
docs/assets/syntax_error.png
Normal file
BIN
docs/assets/syntax_error.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
docs/assets/type_error.png
Normal file
BIN
docs/assets/type_error.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -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())
|
||||
|
@ -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()) {
|
||||
|
@ -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, "<="),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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()));
|
||||
|
@ -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!(
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"Unexpected syntax node. Expected {expected} but got {actual} at {position:?}."
|
||||
),
|
||||
"Interpreter Error. Tried to parse {actual} as {expected} from ({}, {}) to ({}, {}).",
|
||||
position.start_row,
|
||||
position.start_column,
|
||||
position.end_row,
|
||||
position.end_column,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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!(),
|
||||
|
Loading…
Reference in New Issue
Block a user