Add type checking
This commit is contained in:
parent
ab769b4b2a
commit
0646d010c5
10
README.md
10
README.md
@ -28,7 +28,7 @@ if (random_boolean) {
|
||||
}
|
||||
```
|
||||
|
||||
Dust is an interpreted, dynamically typed language with first class functions. It emphasises concurrency by allowing any group of statements to be executed in parallel. It is *data-oriented*, with extensive tools to manage structured and relational data. Dust includes built-in tooling to import and export data in a variety of formats, including JSON, TOML, YAML and CSV.
|
||||
Dust is an interpreted, strictly typed language with first class functions. It emphasises concurrency by allowing any group of statements to be executed in parallel. Dust includes built-in tooling to import and export data in a variety of formats, including JSON, TOML, YAML and CSV.
|
||||
|
||||
<!--toc:start-->
|
||||
- [Dust](#dust)
|
||||
@ -50,10 +50,10 @@ Dust is an interpreted, dynamically typed language with first class functions. I
|
||||
## Features
|
||||
|
||||
- Simplicity: Dust is designed to be easy to learn.
|
||||
- Speed: Dust is built on [Tree Sitter] and [Rust] to prioritize performance and correctness.
|
||||
- Data format: Dust is data-oriented, making it a great language for defining data.
|
||||
- Format conversion: Effortlessly convert between dust and formats like JSON, CSV and TOML.
|
||||
- Structured data: Dust can represent data with more than just strings. Lists, maps and tables are easy to make and manage.
|
||||
- Speed: Dust is built on [Tree Sitter] and [Rust] to prioritize performance and correctness. See [Benchmarks] below.
|
||||
- Concurrency: Easily and safely write code that runs in parallel.
|
||||
- Safety: Written in safe, stable Rust.
|
||||
- Correctness: Type checking makes it easy to write good code that works.
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{AbstractTree, Error, Identifier, Map, Result, Statement, Value};
|
||||
use crate::{AbstractTree, Error, Identifier, Map, Result, Statement, Type, Value};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Assignment {
|
||||
identifier: Identifier,
|
||||
r#type: Option<Type>,
|
||||
operator: AssignmentOperator,
|
||||
statement: Statement,
|
||||
}
|
||||
@ -21,10 +22,21 @@ impl AbstractTree for Assignment {
|
||||
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "assignment", node)?;
|
||||
|
||||
let identifier_node = node.child(0).unwrap();
|
||||
let identifier_node = node.child_by_field_name("identifier").unwrap();
|
||||
let identifier = Identifier::from_syntax_node(source, identifier_node)?;
|
||||
|
||||
let operator_node = node.child(1).unwrap().child(0).unwrap();
|
||||
let type_node = node.child_by_field_name("type");
|
||||
let r#type = if let Some(type_node) = type_node {
|
||||
Some(Type::from_syntax_node(source, type_node)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let operator_node = node
|
||||
.child_by_field_name("assignment_operator")
|
||||
.unwrap()
|
||||
.child(0)
|
||||
.unwrap();
|
||||
let operator = match operator_node.kind() {
|
||||
"=" => AssignmentOperator::Equal,
|
||||
"+=" => AssignmentOperator::PlusEqual,
|
||||
@ -39,11 +51,12 @@ impl AbstractTree for Assignment {
|
||||
}
|
||||
};
|
||||
|
||||
let statement_node = node.child(2).unwrap();
|
||||
let statement_node = node.child_by_field_name("statement").unwrap();
|
||||
let statement = Statement::from_syntax_node(source, statement_node)?;
|
||||
|
||||
Ok(Assignment {
|
||||
identifier,
|
||||
r#type,
|
||||
operator,
|
||||
statement,
|
||||
})
|
||||
@ -73,6 +86,28 @@ impl AbstractTree for Assignment {
|
||||
AssignmentOperator::Equal => value,
|
||||
};
|
||||
|
||||
let expected_type = self.r#type.as_ref().unwrap_or(&Type::Any);
|
||||
|
||||
match (expected_type, new_value.r#type()) {
|
||||
(Type::Any, _)
|
||||
| (Type::Boolean, Type::Boolean)
|
||||
| (Type::Float, Type::Float)
|
||||
| (Type::Function, Type::Function)
|
||||
| (Type::Integer, Type::Integer)
|
||||
| (Type::List, Type::List)
|
||||
| (Type::Map, Type::Map)
|
||||
| (Type::String, Type::String)
|
||||
| (Type::Table, Type::Table) => {}
|
||||
(Type::Boolean, _) => return Err(Error::ExpectedBoolean { actual: new_value }),
|
||||
(Type::Float, _) => return Err(Error::ExpectedFloat { actual: new_value }),
|
||||
(Type::Function, _) => return Err(Error::ExpectedFunction { actual: new_value }),
|
||||
(Type::Integer, _) => return Err(Error::ExpectedInteger { actual: new_value }),
|
||||
(Type::List, _) => return Err(Error::ExpectedList { actual: new_value }),
|
||||
(Type::Map, _) => return Err(Error::ExpectedMap { actual: new_value }),
|
||||
(Type::String, _) => return Err(Error::ExpectedString { actual: new_value }),
|
||||
(Type::Table, _) => return Err(Error::ExpectedTable { actual: new_value }),
|
||||
}
|
||||
|
||||
context.variables_mut()?.insert(key, new_value);
|
||||
|
||||
Ok(Value::Empty)
|
||||
|
@ -20,7 +20,9 @@ impl AbstractTree for Type {
|
||||
fn from_syntax_node(source: &str, node: Node) -> Result<Self> {
|
||||
Error::expect_syntax_node(source, "type", node)?;
|
||||
|
||||
let r#type = match &source[node.byte_range()] {
|
||||
let range_without_punctuation = node.start_byte() + 1..node.end_byte() - 1;
|
||||
|
||||
let r#type = match &source[range_without_punctuation] {
|
||||
"any" => Type::Any,
|
||||
"bool" => Type::Boolean,
|
||||
"float" => Type::Float,
|
||||
|
36
src/error.rs
36
src/error.rs
@ -59,7 +59,7 @@ pub enum Error {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedInt {
|
||||
ExpectedInteger {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
@ -270,24 +270,20 @@ impl fmt::Display for Error {
|
||||
"{identifier} expected a minimum of {minimum} arguments, but got {actual}.",
|
||||
),
|
||||
ExpectedString { actual } => {
|
||||
write!(f, "Expected a Value::String, but got {:?}.", actual)
|
||||
write!(f, "Expected a string but got {:?}.", actual)
|
||||
}
|
||||
ExpectedInteger { actual } => write!(f, "Expected an integer, but got {:?}.", actual),
|
||||
ExpectedFloat { actual } => write!(f, "Expected a float, but got {:?}.", actual),
|
||||
ExpectedNumber { actual } => {
|
||||
write!(f, "Expected a float or integer but got {:?}.", actual)
|
||||
}
|
||||
ExpectedNumberOrString { actual } => {
|
||||
write!(f, "Expected a number or string, but got {:?}.", actual)
|
||||
}
|
||||
ExpectedInt { actual } => write!(f, "Expected a Value::Int, but got {:?}.", actual),
|
||||
ExpectedFloat { actual } => write!(f, "Expected a Value::Float, but got {:?}.", actual),
|
||||
ExpectedNumber { actual } => write!(
|
||||
f,
|
||||
"Expected a Value::Float or Value::Int, but got {:?}.",
|
||||
actual
|
||||
),
|
||||
ExpectedNumberOrString { actual } => write!(
|
||||
f,
|
||||
"Expected a Value::Number or a Value::String, but got {:?}.",
|
||||
actual
|
||||
),
|
||||
ExpectedBoolean { actual } => {
|
||||
write!(f, "Expected a Value::Boolean, but got {:?}.", actual)
|
||||
write!(f, "Expected a boolean, but got {:?}.", actual)
|
||||
}
|
||||
ExpectedList { actual } => write!(f, "Expected a Value::List, but got {:?}.", actual),
|
||||
ExpectedList { actual } => write!(f, "Expected a list, but got {:?}.", actual),
|
||||
ExpectedMinLengthList {
|
||||
minimum_len,
|
||||
actual_len,
|
||||
@ -300,12 +296,12 @@ impl fmt::Display for Error {
|
||||
actual,
|
||||
} => write!(
|
||||
f,
|
||||
"Expected a Value::List of len {}, but got {:?}.",
|
||||
"Expected a list of len {}, but got {:?}.",
|
||||
expected_len, actual
|
||||
),
|
||||
ExpectedEmpty { actual } => write!(f, "Expected a Value::Empty, but got {:?}.", actual),
|
||||
ExpectedMap { actual } => write!(f, "Expected a Value::Map, but got {:?}.", actual),
|
||||
ExpectedTable { actual } => write!(f, "Expected a Value::Table, but got {:?}.", actual),
|
||||
ExpectedEmpty { actual } => write!(f, "Expected an empty value, but got {:?}.", actual),
|
||||
ExpectedMap { actual } => write!(f, "Expected a map, but got {:?}.", actual),
|
||||
ExpectedTable { actual } => write!(f, "Expected a table, but got {:?}.", actual),
|
||||
ExpectedFunction { actual } => {
|
||||
write!(f, "Expected Value::Function, but got {:?}.", actual)
|
||||
}
|
||||
|
@ -52,9 +52,9 @@ impl Value {
|
||||
Value::Function(_) => Type::Function,
|
||||
Value::String(_) => Type::String,
|
||||
Value::Float(_) => Type::Float,
|
||||
Value::Integer(_) => todo!(),
|
||||
Value::Boolean(_) => todo!(),
|
||||
Value::Empty => todo!(),
|
||||
Value::Integer(_) => Type::Integer,
|
||||
Value::Boolean(_) => Type::Boolean,
|
||||
Value::Empty => Type::Any,
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ impl Value {
|
||||
pub fn as_integer(&self) -> Result<i64> {
|
||||
match self {
|
||||
Value::Integer(i) => Ok(*i),
|
||||
value => Err(Error::ExpectedInt {
|
||||
value => Err(Error::ExpectedInteger {
|
||||
actual: value.clone(),
|
||||
}),
|
||||
}
|
||||
@ -540,7 +540,7 @@ impl TryFrom<Value> for i64 {
|
||||
if let Value::Integer(value) = value {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(Error::ExpectedInt { actual: value })
|
||||
Err(Error::ExpectedInteger { actual: value })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,24 @@ x = y
|
||||
(expression
|
||||
(identifier))))))
|
||||
|
||||
================================================================================
|
||||
Simple Assignment with Type
|
||||
================================================================================
|
||||
|
||||
x <int> = y
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
(root
|
||||
(statement
|
||||
(assignment
|
||||
(identifier)
|
||||
(type)
|
||||
(assignment_operator)
|
||||
(statement
|
||||
(expression
|
||||
(identifier))))))
|
||||
|
||||
================================================================================
|
||||
Map Item Assignment
|
||||
================================================================================
|
||||
|
@ -62,7 +62,7 @@ Function Call
|
||||
Complex Function
|
||||
================================================================================
|
||||
|
||||
|message:str number:int| => {
|
||||
|message <str> number <int>| => {
|
||||
(output message)
|
||||
(output number)
|
||||
}
|
||||
@ -75,9 +75,9 @@ Complex Function
|
||||
(value
|
||||
(function
|
||||
(identifier)
|
||||
(type_definition)
|
||||
(type)
|
||||
(identifier)
|
||||
(type_definition)
|
||||
(type)
|
||||
(block
|
||||
(statement
|
||||
(expression
|
||||
|
@ -44,7 +44,7 @@ module.exports = grammar({
|
||||
seq('(', $._expression_kind, ')'),
|
||||
)),
|
||||
|
||||
_expression_kind: $ => choice(
|
||||
_expression_kind: $ => prec.right(choice(
|
||||
$.function_call,
|
||||
$.identifier,
|
||||
$.index,
|
||||
@ -52,7 +52,7 @@ module.exports = grammar({
|
||||
$.math,
|
||||
$.value,
|
||||
$.yield,
|
||||
),
|
||||
)),
|
||||
|
||||
_expression_list: $ => repeat1(prec.right(seq(
|
||||
$.expression,
|
||||
@ -152,9 +152,10 @@ module.exports = grammar({
|
||||
),
|
||||
|
||||
assignment: $ => seq(
|
||||
$.identifier,
|
||||
$.assignment_operator,
|
||||
$.statement,
|
||||
field('identifier', $.identifier),
|
||||
optional(field('type', $.type)),
|
||||
field('assignment_operator', $.assignment_operator),
|
||||
field('statement', $.statement),
|
||||
),
|
||||
|
||||
index_assignment: $ => seq(
|
||||
@ -258,15 +259,19 @@ module.exports = grammar({
|
||||
$.string,
|
||||
),
|
||||
|
||||
type_definition: $ => choice(
|
||||
'any',
|
||||
'bool',
|
||||
'fn',
|
||||
'int',
|
||||
'list',
|
||||
'map',
|
||||
'str',
|
||||
'table',
|
||||
type: $ => seq(
|
||||
'<',
|
||||
choice(
|
||||
'any',
|
||||
'bool',
|
||||
'fn',
|
||||
'int',
|
||||
'list',
|
||||
'map',
|
||||
'str',
|
||||
'table',
|
||||
),
|
||||
'>',
|
||||
),
|
||||
|
||||
function: $ => seq(
|
||||
@ -274,8 +279,7 @@ module.exports = grammar({
|
||||
'|',
|
||||
field('parameter', repeat(seq(
|
||||
$.identifier,
|
||||
':',
|
||||
$.type_definition,
|
||||
$.type,
|
||||
optional(',')
|
||||
))),
|
||||
'|',
|
||||
|
@ -154,37 +154,41 @@
|
||||
}
|
||||
},
|
||||
"_expression_kind": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "function_call"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "identifier"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "index"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "logic"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "math"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "value"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "yield"
|
||||
}
|
||||
]
|
||||
"type": "PREC_RIGHT",
|
||||
"value": 0,
|
||||
"content": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "function_call"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "identifier"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "index"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "logic"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "math"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "value"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "yield"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"_expression_list": {
|
||||
"type": "REPEAT1",
|
||||
@ -704,16 +708,44 @@
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "identifier"
|
||||
"type": "FIELD",
|
||||
"name": "identifier",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "identifier"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "assignment_operator"
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "type",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "type"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "statement"
|
||||
"type": "FIELD",
|
||||
"name": "assignment_operator",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "assignment_operator"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "statement",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "statement"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -1073,36 +1105,53 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"type_definition": {
|
||||
"type": "CHOICE",
|
||||
"type": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "bool"
|
||||
"value": "<"
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "any"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "bool"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "fn"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "int"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "list"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "map"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "str"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "table"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "fn"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "int"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "list"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "map"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "str"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "table"
|
||||
"value": ">"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -1131,13 +1180,9 @@
|
||||
"type": "SYMBOL",
|
||||
"name": "identifier"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ":"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "type_definition"
|
||||
"name": "type"
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
|
@ -2,24 +2,47 @@
|
||||
{
|
||||
"type": "assignment",
|
||||
"named": true,
|
||||
"fields": {},
|
||||
"children": {
|
||||
"multiple": true,
|
||||
"required": true,
|
||||
"types": [
|
||||
{
|
||||
"type": "assignment_operator",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "identifier",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "statement",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
"fields": {
|
||||
"assignment_operator": {
|
||||
"multiple": false,
|
||||
"required": true,
|
||||
"types": [
|
||||
{
|
||||
"type": "assignment_operator",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"identifier": {
|
||||
"multiple": false,
|
||||
"required": true,
|
||||
"types": [
|
||||
{
|
||||
"type": "identifier",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"statement": {
|
||||
"multiple": false,
|
||||
"required": true,
|
||||
"types": [
|
||||
{
|
||||
"type": "statement",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"multiple": false,
|
||||
"required": false,
|
||||
"types": [
|
||||
{
|
||||
"type": "type",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -180,16 +203,12 @@
|
||||
"type": ",",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": ":",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "identifier",
|
||||
"named": true
|
||||
},
|
||||
{
|
||||
"type": "type_definition",
|
||||
"type": "type",
|
||||
"named": true
|
||||
}
|
||||
]
|
||||
@ -562,7 +581,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "type_definition",
|
||||
"type": "type",
|
||||
"named": true,
|
||||
"fields": {}
|
||||
},
|
||||
@ -762,6 +781,10 @@
|
||||
"type": "]",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "any",
|
||||
"named": false
|
||||
},
|
||||
{
|
||||
"type": "append",
|
||||
"named": false
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user