Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
25e3941315 | |||
900de8ca4b | |||
c4b51a1ef9 | |||
939b7464c6 | |||
64fb20c45e | |||
e3b55092b3 | |||
bd4983b821 | |||
a1500bf262 | |||
1585145ff4 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -369,7 +369,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dust-lang"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"clap",
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "dust-lang"
|
||||
description = "General purpose programming language"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
repository = "https://git.jeffa.io/jeff/dust.git"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
96
README.md
96
README.md
@ -1,24 +1,100 @@
|
||||
# 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/raw/branch/main/docs/assets/example_0.png)
|
||||
|
||||
<!--toc:start-->
|
||||
- [Dust](#dust)
|
||||
- [Easy to Read and Write](#easy-to-read-and-write)
|
||||
- [Effortless Concurrency](#effortless-concurrency)
|
||||
- [Debugging](#debugging)
|
||||
- [Automatic Memory Management](#automatic-memory-management)
|
||||
- [Features](#features)
|
||||
- [Easy to Read and Write](#easy-to-read-and-write)
|
||||
- [Effortless Concurrency](#effortless-concurrency)
|
||||
- [Helpful Errors](#helpful-errors)
|
||||
- [Static analysis](#static-analysis)
|
||||
- [Debugging](#debugging)
|
||||
- [Automatic Memory Management](#automatic-memory-management)
|
||||
- [Error Handling](#error-handling)
|
||||
- [Installation and Usage](#installation-and-usage)
|
||||
<!--toc:end-->
|
||||
|
||||
## Easy to Read and Write
|
||||
## Features
|
||||
|
||||
## Effortless Concurrency
|
||||
### Easy to Read and Write
|
||||
|
||||
## Debugging
|
||||
Dust has simple, easy-to-learn syntax.
|
||||
|
||||
## Automatic Memory Management
|
||||
```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/raw/branch/main/docs/assets/syntax_error.png)
|
||||
|
||||
### Static analysis
|
||||
|
||||
Your code is always validated for safety before it is run.
|
||||
|
||||
![Example of type error output.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/type_error.png)
|
||||
|
||||
Dust
|
||||
|
||||
### 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. Here are some of the logs from the end of a simple [fizzbuzz example](https://git.jeffa.io/jeff/dust/src/branch/main/examples/fizzbuzz.ds).
|
||||
|
||||
![Example of debug output.](https://git.jeffa.io/jeff/dust/raw/branch/main/docs/assets/debugging.png)
|
||||
|
||||
### Automatic Memory Management
|
||||
|
||||
Thanks to static analysis, Dust knows exactly how many times each variable is used. This allows Dust to free memory as soon as the variable will no longer be used, without any help from the user.
|
||||
|
||||
### Error Handling
|
||||
|
||||
Runtime errors are no problem with Dust. The `Result` type represents the output of an operation that might fail. The user must decide what to do in the case of an error.
|
||||
|
||||
```dust
|
||||
match io:stdin() {
|
||||
Result::Ok(input) -> output("We read this input: " + input)
|
||||
Result::Error(message) -> output("We got this error: " + message)
|
||||
}
|
||||
```
|
||||
|
||||
## Installation and Usage
|
||||
|
||||
There are two ways to compile Dust. **It is best to clone the repository and compile the latest code**, otherwise the program may be a different version than the one shown on GitHub. Either way, you must have `rustup`, `cmake` and a C compiler installed.
|
||||
|
||||
To install from the git repository:
|
||||
|
||||
```fish
|
||||
git clone https://git.jeffa.io/jeff/dust
|
||||
cd dust
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
To install with cargo:
|
||||
|
||||
```fish
|
||||
cargo install dust-lang
|
||||
dust
|
||||
```
|
||||
|
||||
## Benchmarks
|
||||
|
||||
## Development Status
|
||||
|
||||
Currently, Dust is being prepared for version 1.0. Until then, there may be breaking changes to the language and CLI.
|
||||
|
BIN
docs/assets/debugging.png
Normal file
BIN
docs/assets/debugging.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
Binary file not shown.
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 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: 32 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: 18 KiB |
@ -4,22 +4,22 @@
|
||||
|
||||
output("Guess the number.")
|
||||
|
||||
secret_number = int:random_range(0..=100);
|
||||
secret_number = int:random_range(0..=100)
|
||||
|
||||
loop {
|
||||
output("Please input your guess.")
|
||||
|
||||
input = io:stdin():expect("Failed to read line.")
|
||||
guess = int:parse(input);
|
||||
guess = int:parse(input)
|
||||
|
||||
output("You guessed: " + guess)
|
||||
|
||||
match cmp(guess, secret_number) {
|
||||
Ordering::Less -> output("Too small!"),
|
||||
Ordering::Greater -> output("Too big!"),
|
||||
Ordering::Less -> output("Too small!")
|
||||
Ordering::Greater -> output("Too big!")
|
||||
Ordering::Equal -> {
|
||||
output("You win!");
|
||||
break;
|
||||
output("You win!")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ hyperfine \
|
||||
--shell none \
|
||||
--parameter-list data_path examples/assets/seaCreatures.json \
|
||||
--warmup 3 \
|
||||
"dust -c 'length(json:parse(input))' -p {data_path}" \
|
||||
"target/release/dust -c 'length(json:parse(fs:read_file(\"{data_path}\")))'" \
|
||||
"jq 'length' {data_path}" \
|
||||
"node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \
|
||||
"nu -c 'open {data_path} | length'"
|
||||
@ -20,7 +20,7 @@ hyperfine \
|
||||
--shell none \
|
||||
--parameter-list data_path examples/assets/jq_data.json \
|
||||
--warmup 3 \
|
||||
"dust -c 'length(json:parse(input))' -p {data_path}" \
|
||||
"target/release/dust -c 'length(json:parse(fs:read_file(\"{data_path}\")))'" \
|
||||
"jq 'length' {data_path}" \
|
||||
"node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \
|
||||
"nu -c 'open {data_path} | length'"
|
||||
@ -29,7 +29,7 @@ hyperfine \
|
||||
--shell none \
|
||||
--parameter-list data_path dielectron.json \
|
||||
--warmup 3 \
|
||||
"dust -c 'length(json:parse(input))' -p {data_path}" \
|
||||
"target/release/dust -c 'length(json:parse(fs:read_file(\"{data_path}\")))'" \
|
||||
"jq 'length' {data_path}" \
|
||||
"node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \
|
||||
"nu -c 'open {data_path} | length'"
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::fs::read_to_string;
|
||||
use std::{fs::File, io::Read};
|
||||
|
||||
use enum_iterator::{all, Sequence};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -46,7 +46,11 @@ impl Callable for Fs {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let path = arguments.first().unwrap().as_string()?;
|
||||
let file_content = read_to_string(path.as_str())?;
|
||||
let mut file = File::open(path)?;
|
||||
let file_size = file.metadata()?.len() as usize;
|
||||
let mut file_content = String::with_capacity(file_size);
|
||||
|
||||
file.read_to_string(&mut file_content)?;
|
||||
|
||||
Ok(Value::string(file_content))
|
||||
}
|
||||
|
@ -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!(
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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