Write docs; Update logging and error messages
This commit is contained in:
parent
fb3cd6e6da
commit
1585145ff4
37
README.md
37
README.md
@ -1,6 +1,6 @@
|
|||||||
# Dust
|
# 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)
|
![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)
|
- [Dust](#dust)
|
||||||
- [Easy to Read and Write](#easy-to-read-and-write)
|
- [Easy to Read and Write](#easy-to-read-and-write)
|
||||||
- [Effortless Concurrency](#effortless-concurrency)
|
- [Effortless Concurrency](#effortless-concurrency)
|
||||||
|
- [Helpful Errors](#helpful-errors)
|
||||||
- [Debugging](#debugging)
|
- [Debugging](#debugging)
|
||||||
- [Automatic Memory Management](#automatic-memory-management)
|
- [Automatic Memory Management](#automatic-memory-management)
|
||||||
- [Installation and Usage](#installation-and-usage)
|
- [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
|
## Easy to Read and Write
|
||||||
|
|
||||||
|
Dust has simple, easy-to-learn syntax.
|
||||||
|
|
||||||
|
```js
|
||||||
|
output('Hello world!')
|
||||||
|
```
|
||||||
|
|
||||||
## Effortless Concurrency
|
## 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
|
## 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
|
## Automatic Memory Management
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
## Installation and Usage
|
## 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)?
|
self.statement.expected_type(context)?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
log::info!("Setting type: {} <{}>", self.identifier, r#type);
|
||||||
|
|
||||||
context.set_type(self.identifier.clone(), 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())?;
|
.set_value(self.identifier.clone(), new_value.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log::info!("RUN assignment: {} = {}", self.identifier, new_value);
|
||||||
|
|
||||||
context.set_value(self.identifier.clone(), new_value)?;
|
context.set_value(self.identifier.clone(), new_value)?;
|
||||||
|
|
||||||
Ok(Value::none())
|
Ok(Value::none())
|
||||||
|
@ -45,6 +45,8 @@ impl AbstractTree for Logic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
fn validate(&self, _source: &str, _context: &Context) -> Result<(), ValidationError> {
|
||||||
|
log::info!("VALIDATE logic expression");
|
||||||
|
|
||||||
self.left.validate(_source, _context)?;
|
self.left.validate(_source, _context)?;
|
||||||
self.right.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> {
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
let left = self.left.run(source, context)?;
|
let left = self.left.run(source, context)?;
|
||||||
let right = self.right.run(source, context)?;
|
let right = self.right.run(source, context)?;
|
||||||
|
|
||||||
|
log::info!("RUN logic expression: {left} {} {right}", self.operator);
|
||||||
|
|
||||||
let result = match self.operator {
|
let result = match self.operator {
|
||||||
LogicOperator::Equal => {
|
LogicOperator::Equal => {
|
||||||
if let (Ok(left_num), Ok(right_num)) = (left.as_number(), right.as_number()) {
|
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 serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
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,
|
_source: &str,
|
||||||
_context: &Context,
|
_context: &Context,
|
||||||
) -> Result<Self, SyntaxError> {
|
) -> Result<Self, SyntaxError> {
|
||||||
|
SyntaxError::expect_syntax_node("math_operator", node)?;
|
||||||
|
|
||||||
let operator_node = node.child(0).unwrap();
|
let operator_node = node.child(0).unwrap();
|
||||||
let operator = match operator_node.kind() {
|
let operator = match operator_node.kind() {
|
||||||
"+" => MathOperator::Add,
|
"+" => MathOperator::Add,
|
||||||
|
@ -179,7 +179,7 @@ impl AbstractTree for ValueNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ValueNode::Map(map_node) => map_node.validate(_source, context)?,
|
ValueNode::Map(map_node) => map_node.validate(_source, context)?,
|
||||||
ValueNode::Enum { name, variant, expression } => {
|
ValueNode::Enum { name, expression, .. } => {
|
||||||
name.validate(_source, context)?;
|
name.validate(_source, context)?;
|
||||||
|
|
||||||
if let Some(expression) = expression {
|
if let Some(expression) = expression {
|
||||||
|
@ -32,15 +32,21 @@ impl AbstractTree for While {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
|
fn validate(&self, _source: &str, context: &Context) -> Result<(), ValidationError> {
|
||||||
|
log::info!("VALIDATE while loop");
|
||||||
|
|
||||||
self.expression.validate(_source, context)?;
|
self.expression.validate(_source, context)?;
|
||||||
self.block.validate(_source, context)
|
self.block.validate(_source, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
fn run(&self, source: &str, context: &Context) -> Result<Value, RuntimeError> {
|
||||||
|
log::info!("RUN while loop start");
|
||||||
|
|
||||||
while self.expression.run(source, context)?.as_boolean()? {
|
while self.expression.run(source, context)?.as_boolean()? {
|
||||||
self.block.run(source, context)?;
|
self.block.run(source, context)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log::info!("RUN while loop end");
|
||||||
|
|
||||||
Ok(Value::none())
|
Ok(Value::none())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,8 +264,6 @@ impl Context {
|
|||||||
|
|
||||||
/// Set a value to a key.
|
/// Set a value to a key.
|
||||||
pub fn set_value(&self, key: Identifier, value: Value) -> Result<(), RwLockError> {
|
pub fn set_value(&self, key: Identifier, value: Value) -> Result<(), RwLockError> {
|
||||||
log::info!("Setting value: {key} = {value}");
|
|
||||||
|
|
||||||
let mut map = self.inner.write()?;
|
let mut map = self.inner.write()?;
|
||||||
let old_data = map.remove(&key);
|
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
|
/// This allows the interpreter to check a value's type before the value
|
||||||
/// actually exists by predicting what the abstract tree will produce.
|
/// actually exists by predicting what the abstract tree will produce.
|
||||||
pub fn set_type(&self, key: Identifier, r#type: Type) -> Result<(), RwLockError> {
|
pub fn set_type(&self, key: Identifier, r#type: Type) -> Result<(), RwLockError> {
|
||||||
log::info!("Setting type: {key} <{}>", r#type);
|
|
||||||
|
|
||||||
self.inner
|
self.inner
|
||||||
.write()?
|
.write()?
|
||||||
.insert(key, (ValueData::TypeHint(r#type), UsageCounter::new()));
|
.insert(key, (ValueData::TypeHint(r#type), UsageCounter::new()));
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
|
use colored::Colorize;
|
||||||
use lyneate::Report;
|
use lyneate::Report;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tree_sitter::Node as SyntaxNode;
|
use tree_sitter::Node as SyntaxNode;
|
||||||
@ -12,6 +13,8 @@ use super::rw_lock_error::RwLockError;
|
|||||||
pub enum SyntaxError {
|
pub enum SyntaxError {
|
||||||
/// Invalid user input.
|
/// Invalid user input.
|
||||||
InvalidSource {
|
InvalidSource {
|
||||||
|
expected: String,
|
||||||
|
actual: String,
|
||||||
position: SourcePosition,
|
position: SourcePosition,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -27,25 +30,25 @@ pub enum SyntaxError {
|
|||||||
impl SyntaxError {
|
impl SyntaxError {
|
||||||
pub fn create_report(&self, source: &str) -> String {
|
pub fn create_report(&self, source: &str) -> String {
|
||||||
let messages = match self {
|
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![(
|
vec![(
|
||||||
position.start_byte..position.end_byte,
|
position.start_byte..position.end_byte,
|
||||||
format!(
|
self.to_string(),
|
||||||
"Invalid syntax from ({}, {}) to ({}, {}).",
|
|
||||||
position.start_row,
|
|
||||||
position.start_column,
|
|
||||||
position.end_row,
|
|
||||||
position.end_column,
|
|
||||||
),
|
|
||||||
(255, 200, 100),
|
(255, 200, 100),
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
SyntaxError::RwLock(_) => todo!(),
|
|
||||||
SyntaxError::UnexpectedSyntaxNode {
|
|
||||||
expected: _,
|
|
||||||
actual: _,
|
|
||||||
position: _,
|
|
||||||
} => todo!(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Report::new_byte_spanned(source, messages).display_str()
|
Report::new_byte_spanned(source, messages).display_str()
|
||||||
@ -58,6 +61,8 @@ impl SyntaxError {
|
|||||||
Ok(())
|
Ok(())
|
||||||
} else if actual.is_error() {
|
} else if actual.is_error() {
|
||||||
Err(SyntaxError::InvalidSource {
|
Err(SyntaxError::InvalidSource {
|
||||||
|
expected: expected.to_owned(),
|
||||||
|
actual: actual.kind().to_string(),
|
||||||
position: SourcePosition::from(actual.range()),
|
position: SourcePosition::from(actual.range()),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -79,16 +84,43 @@ impl From<RwLockError> for SyntaxError {
|
|||||||
impl Display for SyntaxError {
|
impl Display for SyntaxError {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
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::RwLock(_) => todo!(),
|
||||||
SyntaxError::UnexpectedSyntaxNode {
|
SyntaxError::UnexpectedSyntaxNode {
|
||||||
expected,
|
expected,
|
||||||
actual,
|
actual,
|
||||||
position,
|
position,
|
||||||
} => write!(
|
} => {
|
||||||
f,
|
write!(
|
||||||
"Unexpected syntax node. Expected {expected} but got {actual} at {position:?}."
|
f,
|
||||||
),
|
"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 std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
|
use colored::Colorize;
|
||||||
use lyneate::Report;
|
use lyneate::Report;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -223,10 +224,17 @@ impl ValidationError {
|
|||||||
position,
|
position,
|
||||||
} => vec![(
|
} => vec![(
|
||||||
position.start_byte..position.end_byte,
|
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),
|
(200, 200, 200),
|
||||||
)],
|
)],
|
||||||
ValidationError::TypeCheckExpectedFunction { actual: _, position: _ } => todo!(),
|
ValidationError::TypeCheckExpectedFunction {
|
||||||
|
actual: _,
|
||||||
|
position: _,
|
||||||
|
} => todo!(),
|
||||||
ValidationError::VariableIdentifierNotFound(_) => todo!(),
|
ValidationError::VariableIdentifierNotFound(_) => todo!(),
|
||||||
ValidationError::TypeDefinitionNotFound(_) => todo!(),
|
ValidationError::TypeDefinitionNotFound(_) => todo!(),
|
||||||
ValidationError::ExpectedEnumDefintion { actual: _ } => todo!(),
|
ValidationError::ExpectedEnumDefintion { actual: _ } => todo!(),
|
||||||
|
Loading…
Reference in New Issue
Block a user