From 3e87d8b32273e5afde79911ac4aad21b0948284d Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 6 Oct 2023 13:32:58 -0400 Subject: [PATCH] Continue syntax overhaul --- src/abstract_tree/assignment.rs | 36 ++ src/abstract_tree/expression.rs | 58 +++ src/abstract_tree/function_call.rs | 50 ++ src/abstract_tree/identifier.rs | 35 ++ src/abstract_tree/if_else.rs | 48 ++ src/abstract_tree/item.rs | 45 ++ src/abstract_tree/logic.rs | 61 +++ src/abstract_tree/match.rs | 22 + src/abstract_tree/math.rs | 81 ++++ src/abstract_tree/mod.rs | 38 ++ src/abstract_tree/statement.rs | 59 +++ src/abstract_tree/tool.rs | 43 ++ src/error.rs | 4 +- src/evaluator.rs | 314 +++++++++++++ src/interface.rs | 720 ----------------------------- src/lib.rs | 6 +- src/tools/collections.rs | 416 ----------------- src/tools/command.rs | 119 ----- src/tools/data_formats.rs | 171 ------- src/tools/disks.rs | 123 ----- src/tools/filesystem.rs | 487 ------------------- src/tools/general.rs | 160 ------- src/tools/gui.rs | 324 ------------- src/tools/logic.rs | 162 ------- src/tools/mod.rs | 377 --------------- src/tools/network.rs | 23 - src/tools/random.rs | 161 ------- src/tools/system.rs | 28 -- src/tools/time.rs | 43 -- src/value/function.rs | 4 +- src/value/mod.rs | 2 +- src/value/variable_map.rs | 38 +- tests/dust_examples.rs | 12 +- tree-sitter-dust | 2 +- 34 files changed, 907 insertions(+), 3365 deletions(-) create mode 100644 src/abstract_tree/assignment.rs create mode 100644 src/abstract_tree/expression.rs create mode 100644 src/abstract_tree/function_call.rs create mode 100644 src/abstract_tree/identifier.rs create mode 100644 src/abstract_tree/if_else.rs create mode 100644 src/abstract_tree/item.rs create mode 100644 src/abstract_tree/logic.rs create mode 100644 src/abstract_tree/match.rs create mode 100644 src/abstract_tree/math.rs create mode 100644 src/abstract_tree/mod.rs create mode 100644 src/abstract_tree/statement.rs create mode 100644 src/abstract_tree/tool.rs create mode 100644 src/evaluator.rs delete mode 100644 src/interface.rs delete mode 100644 src/tools/collections.rs delete mode 100644 src/tools/command.rs delete mode 100644 src/tools/data_formats.rs delete mode 100644 src/tools/disks.rs delete mode 100644 src/tools/filesystem.rs delete mode 100644 src/tools/general.rs delete mode 100644 src/tools/gui.rs delete mode 100644 src/tools/logic.rs delete mode 100644 src/tools/mod.rs delete mode 100644 src/tools/network.rs delete mode 100644 src/tools/random.rs delete mode 100644 src/tools/system.rs delete mode 100644 src/tools/time.rs diff --git a/src/abstract_tree/assignment.rs b/src/abstract_tree/assignment.rs new file mode 100644 index 0000000..3be7869 --- /dev/null +++ b/src/abstract_tree/assignment.rs @@ -0,0 +1,36 @@ +use serde::{Deserialize, Serialize}; +use tree_sitter::Node; + +use crate::{AbstractTree, Result, Value, VariableMap}; + +use super::{identifier::Identifier, statement::Statement}; + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] +pub struct Assignment { + identifier: Identifier, + statement: Statement, +} + +impl AbstractTree for Assignment { + fn from_syntax_node(node: Node, source: &str) -> Result { + let identifier_node = node.child(0).unwrap(); + let identifier = Identifier::from_syntax_node(identifier_node, source)?; + + let statement_node = node.child(2).unwrap(); + let statement = Statement::from_syntax_node(statement_node, source)?; + + Ok(Assignment { + identifier, + statement, + }) + } + + fn run(&self, context: &mut VariableMap) -> Result { + let key = self.identifier.clone().take_inner(); + let value = self.statement.run(context)?; + + context.set_value(key, value)?; + + Ok(Value::Empty) + } +} diff --git a/src/abstract_tree/expression.rs b/src/abstract_tree/expression.rs new file mode 100644 index 0000000..c25268d --- /dev/null +++ b/src/abstract_tree/expression.rs @@ -0,0 +1,58 @@ +use serde::{Deserialize, Serialize}; +use tree_sitter::Node; + +use crate::{tool::ToolCall, AbstractTree, Error, Identifier, Result, Value, VariableMap}; + +use super::{function_call::FunctionCall, logic::Logic, math::Math}; + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] +pub enum Expression { + Identifier(Identifier), + Value(Value), + Math(Box), + Logic(Box), + FunctionCall(FunctionCall), + ToolCall(Box), +} + +impl AbstractTree for Expression { + fn from_syntax_node(node: Node, source: &str) -> Result { + debug_assert_eq!("expression", node.kind()); + + let child = node.child(0).unwrap(); + + let expression = match child.kind() { + "value" => Expression::Value(Value::from_syntax_node(child, source)?), + "identifier" => Self::Identifier(Identifier::from_syntax_node(child, source)?), + "math" => Expression::Math(Box::new(Math::from_syntax_node(child, source)?)), + "logic" => Expression::Logic(Box::new(Logic::from_syntax_node(child, source)?)), + "function_call" => { + Expression::FunctionCall(FunctionCall::from_syntax_node(child, source)?) + } + "tool_call" => { + Expression::ToolCall(Box::new(ToolCall::from_syntax_node(child, source)?)) + } + _ => { + return Err(Error::UnexpectedSyntax { + expected: "value, identifier, math or function_call", + actual: child.kind(), + location: child.start_position(), + relevant_source: source[node.byte_range()].to_string(), + }) + } + }; + + Ok(expression) + } + + fn run(&self, context: &mut VariableMap) -> Result { + match self { + Expression::Value(value) => Ok(value.clone()), + Expression::Identifier(identifier) => identifier.run(context), + Expression::Math(math) => math.run(context), + Expression::Logic(logic) => logic.run(context), + Expression::FunctionCall(function_call) => function_call.run(context), + Expression::ToolCall(tool_call) => tool_call.run(context), + } + } +} diff --git a/src/abstract_tree/function_call.rs b/src/abstract_tree/function_call.rs new file mode 100644 index 0000000..03f45aa --- /dev/null +++ b/src/abstract_tree/function_call.rs @@ -0,0 +1,50 @@ +use serde::{Deserialize, Serialize}; +use tree_sitter::Node; + +use crate::{AbstractTree, Result, Value, VariableMap}; + +use super::{expression::Expression, identifier::Identifier}; + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] +pub struct FunctionCall { + identifier: Identifier, + expressions: Vec, +} + +impl AbstractTree for FunctionCall { + fn from_syntax_node(node: Node, source: &str) -> Result { + debug_assert_eq!("function_call", node.kind()); + + let identifier_node = node.child(1).unwrap(); + let identifier = Identifier::from_syntax_node(identifier_node, source)?; + + let mut expressions = Vec::new(); + + let mut current_index = 2; + while current_index < node.child_count() - 1 { + let expression_node = node.child(current_index).unwrap(); + let expression = Expression::from_syntax_node(expression_node, source)?; + + expressions.push(expression); + + current_index += 1; + } + + Ok(FunctionCall { + identifier, + expressions, + }) + } + + fn run(&self, context: &mut VariableMap) -> Result { + let identifier = &self.identifier; + let definition = if let Some(value) = context.get_value(identifier.inner())? { + value + } else { + return Err(crate::Error::FunctionIdentifierNotFound(identifier.clone())); + }; + let mut arguments = Vec::with_capacity(self.expressions.len()); + + Ok(Value::List(arguments)) + } +} diff --git a/src/abstract_tree/identifier.rs b/src/abstract_tree/identifier.rs new file mode 100644 index 0000000..d9d7c33 --- /dev/null +++ b/src/abstract_tree/identifier.rs @@ -0,0 +1,35 @@ +use serde::{Deserialize, Serialize}; +use tree_sitter::Node; + +use crate::{AbstractTree, Result, Value, VariableMap}; + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] +pub struct Identifier(String); + +impl Identifier { + pub fn new(inner: String) -> Self { + Identifier(inner) + } + + pub fn take_inner(self) -> String { + self.0 + } + + pub fn inner(&self) -> &String { + &self.0 + } +} + +impl AbstractTree for Identifier { + fn from_syntax_node(node: Node, source: &str) -> Result { + let identifier = &source[node.byte_range()]; + + Ok(Identifier(identifier.to_string())) + } + + fn run(&self, context: &mut VariableMap) -> Result { + let value = context.get_value(&self.0)?.unwrap_or_default(); + + Ok(value) + } +} diff --git a/src/abstract_tree/if_else.rs b/src/abstract_tree/if_else.rs new file mode 100644 index 0000000..987afb6 --- /dev/null +++ b/src/abstract_tree/if_else.rs @@ -0,0 +1,48 @@ +use serde::{Deserialize, Serialize}; +use tree_sitter::Node; + +use crate::{AbstractTree, Expression, Result, Statement, Value, VariableMap}; + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] +pub struct IfElse { + if_expression: Expression, + then_statement: Statement, + else_statement: Option, +} + +impl AbstractTree for IfElse { + fn from_syntax_node(node: Node, source: &str) -> Result { + let if_node = node.child(1).unwrap(); + let if_expression = Expression::from_syntax_node(if_node, source)?; + + let then_node = node.child(3).unwrap(); + let then_statement = Statement::from_syntax_node(then_node, source)?; + + let else_node = node.child(5); + let else_statement = if let Some(node) = else_node { + Some(Statement::from_syntax_node(node, source)?) + } else { + None + }; + + println!("{if_node:?} {then_node:?} {else_node:?}"); + + Ok(IfElse { + if_expression, + then_statement, + else_statement, + }) + } + + fn run(&self, context: &mut VariableMap) -> Result { + let if_boolean = self.if_expression.run(context)?.as_boolean()?; + + if if_boolean { + self.then_statement.run(context) + } else if let Some(statement) = &self.else_statement { + statement.run(context) + } else { + Ok(Value::Empty) + } + } +} diff --git a/src/abstract_tree/item.rs b/src/abstract_tree/item.rs new file mode 100644 index 0000000..c80b2c0 --- /dev/null +++ b/src/abstract_tree/item.rs @@ -0,0 +1,45 @@ +//! Top-level unit of Dust code. + +use tree_sitter::Node; + +use crate::{AbstractTree, Error, Result, Statement, Value, VariableMap}; + +/// An abstractiton of an independent unit of source code, or a comment. +/// +/// Items are either comments, which do nothing, or statements, which can be run +/// to produce a single value or interact with a context by creating or +/// referencing variables. +#[derive(Debug)] +pub enum Item { + Comment(String), + Statement(Statement), +} + +impl AbstractTree for Item { + fn from_syntax_node(node: Node, source: &str) -> Result { + let child = node.child(0).unwrap(); + + if child.kind() == "comment" { + let byte_range = child.byte_range(); + let comment_text = &source[byte_range]; + + Ok(Item::Comment(comment_text.to_string())) + } else if child.kind() == "statement" { + Ok(Item::Statement(Statement::from_syntax_node(child, source)?)) + } else { + Err(Error::UnexpectedSyntax { + expected: "comment or statement", + actual: child.kind(), + location: child.start_position(), + relevant_source: source[node.byte_range()].to_string(), + }) + } + } + + fn run(&self, context: &mut VariableMap) -> Result { + match self { + Item::Comment(text) => Ok(Value::String(text.clone())), + Item::Statement(statement) => statement.run(context), + } + } +} diff --git a/src/abstract_tree/logic.rs b/src/abstract_tree/logic.rs new file mode 100644 index 0000000..5a8e828 --- /dev/null +++ b/src/abstract_tree/logic.rs @@ -0,0 +1,61 @@ +use serde::{Deserialize, Serialize}; +use tree_sitter::Node; + +use crate::{AbstractTree, Error, Expression, Result, Value, VariableMap}; + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] +pub struct Logic { + left: Expression, + operator: LogicOperator, + right: Expression, +} + +impl AbstractTree for Logic { + fn from_syntax_node(node: Node, source: &str) -> Result { + let left_node = node.child(0).unwrap(); + let left = Expression::from_syntax_node(left_node, source)?; + + let operator_node = node.child(1).unwrap().child(0).unwrap(); + let operator = match operator_node.kind() { + "==" => LogicOperator::Equal, + "&&" => LogicOperator::And, + "||" => LogicOperator::Or, + _ => { + return Err(Error::UnexpectedSyntax { + expected: "==, && or ||", + actual: operator_node.kind(), + location: operator_node.start_position(), + relevant_source: source[operator_node.byte_range()].to_string(), + }) + } + }; + + let right_node = node.child(2).unwrap(); + let right = Expression::from_syntax_node(right_node, source)?; + + Ok(Logic { + left, + operator, + right, + }) + } + + fn run(&self, context: &mut VariableMap) -> Result { + let left_value = self.left.run(context)?; + let right_value = self.right.run(context)?; + let outcome = match self.operator { + LogicOperator::Equal => left_value == right_value, + LogicOperator::And => left_value.as_boolean()? && right_value.as_boolean()?, + LogicOperator::Or => left_value.as_boolean()? || right_value.as_boolean()?, + }; + + Ok(Value::Boolean(outcome)) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] +pub enum LogicOperator { + Equal, + And, + Or, +} diff --git a/src/abstract_tree/match.rs b/src/abstract_tree/match.rs new file mode 100644 index 0000000..5dad58a --- /dev/null +++ b/src/abstract_tree/match.rs @@ -0,0 +1,22 @@ +//! Pattern matching. +//! +//! Note that this module is called "match" but is escaped as "r#match" because +//! "match" is a keyword in Rust. + +use serde::{Deserialize, Serialize}; +use tree_sitter::Node; + +use crate::{AbstractTree, Result, Value, VariableMap}; + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] +pub struct Match {} + +impl AbstractTree for Match { + fn from_syntax_node(node: Node, source: &str) -> Result { + todo!() + } + + fn run(&self, context: &mut VariableMap) -> Result { + todo!() + } +} diff --git a/src/abstract_tree/math.rs b/src/abstract_tree/math.rs new file mode 100644 index 0000000..8b8d617 --- /dev/null +++ b/src/abstract_tree/math.rs @@ -0,0 +1,81 @@ +use serde::{Deserialize, Serialize}; +use tree_sitter::Node; + +use crate::{AbstractTree, Error, Expression, Result, Value, VariableMap}; + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] +pub struct Math { + left: Expression, + operator: MathOperator, + right: Expression, +} + +impl AbstractTree for Math { + fn from_syntax_node(node: Node, source: &str) -> Result { + let left_node = node.child(0).unwrap(); + let left = Expression::from_syntax_node(left_node, source)?; + + let operator_node = node.child(1).unwrap().child(0).unwrap(); + let operator = match operator_node.kind() { + "+" => MathOperator::Add, + "-" => MathOperator::Subtract, + "*" => MathOperator::Multiply, + "/" => MathOperator::Divide, + "%" => MathOperator::Modulo, + _ => { + return Err(Error::UnexpectedSyntax { + expected: "+, -, *, / or %", + actual: operator_node.kind(), + location: operator_node.start_position(), + relevant_source: source[operator_node.byte_range()].to_string(), + }) + } + }; + + let right_node = node.child(2).unwrap(); + let right = Expression::from_syntax_node(right_node, source)?; + + Ok(Math { + left, + operator, + right, + }) + } + + fn run(&self, context: &mut VariableMap) -> Result { + match self.operator { + MathOperator::Add | MathOperator::Subtract | MathOperator::Multiply => { + let left_value = self.left.run(context)?.as_int()?; + let right_value = self.right.run(context)?.as_int()?; + let outcome = match &self.operator { + MathOperator::Add => left_value + right_value, + MathOperator::Subtract => left_value - right_value, + MathOperator::Multiply => left_value * right_value, + _ => panic!("Unreachable"), + }; + + Ok(Value::Integer(outcome)) + } + MathOperator::Divide | MathOperator::Modulo => { + let left_value = self.left.run(context)?.as_number()?; + let right_value = self.right.run(context)?.as_number()?; + let outcome = match self.operator { + MathOperator::Divide => left_value / right_value, + MathOperator::Modulo => left_value % right_value, + _ => panic!("Unreachable"), + }; + + Ok(Value::Float(outcome)) + } + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] +pub enum MathOperator { + Add, + Subtract, + Multiply, + Divide, + Modulo, +} diff --git a/src/abstract_tree/mod.rs b/src/abstract_tree/mod.rs new file mode 100644 index 0000000..6c15687 --- /dev/null +++ b/src/abstract_tree/mod.rs @@ -0,0 +1,38 @@ +pub mod assignment; +pub mod expression; +pub mod function_call; +pub mod identifier; +pub mod if_else; +pub mod item; +pub mod logic; +pub mod r#match; +pub mod math; +pub mod statement; +pub mod tool; + +pub use { + assignment::*, expression::*, function_call::*, identifier::*, if_else::*, item::*, logic::*, + math::*, r#match::*, statement::*, +}; + +use tree_sitter::Node; + +use crate::{Result, Value, VariableMap}; + +/// This trait is implemented by the Evaluator's internal types to form an +/// executable tree that resolves to a single value. +pub trait AbstractTree: Sized { + /// Interpret the syntax tree at the given node and return the abstraction. + /// + /// This function is used to convert nodes in the Tree Sitter concrete + /// syntax tree into executable nodes in an abstract tree. This function is + /// where the tree should be traversed by accessing sibling and child nodes. + /// Each node in the CST should be traversed only once. + /// + /// If necessary, the source code can be accessed directly by getting the + /// node's byte range. + fn from_syntax_node(node: Node, source: &str) -> Result; + + /// Execute dust code by traversing the tree + fn run(&self, context: &mut VariableMap) -> Result; +} diff --git a/src/abstract_tree/statement.rs b/src/abstract_tree/statement.rs new file mode 100644 index 0000000..2070ec2 --- /dev/null +++ b/src/abstract_tree/statement.rs @@ -0,0 +1,59 @@ +use serde::{Deserialize, Serialize}; +use tree_sitter::Node; + +use crate::{ + tool::ToolCall, AbstractTree, Assignment, Error, Expression, IfElse, Match, Result, Value, + VariableMap, +}; + +/// Abstract representation of a statement. +/// +/// Items are either comments, which do nothing, or statements, which can be run +/// to produce a single value or interact with their context. +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] +pub enum Statement { + Assignment(Box), + Expression(Expression), + IfElse(Box), + Match(Match), + Tool(ToolCall), +} + +impl AbstractTree for Statement { + fn from_syntax_node(node: Node, source: &str) -> Result { + debug_assert_eq!("statement", node.kind()); + + let child = node.child(0).unwrap(); + + match child.kind() { + "assignment" => Ok(Statement::Assignment(Box::new( + Assignment::from_syntax_node(child, source)?, + ))), + "expression" => Ok(Self::Expression(Expression::from_syntax_node( + child, source, + )?)), + "if_else" => Ok(Statement::IfElse(Box::new(IfElse::from_syntax_node( + child, source, + )?))), + "tool" => Ok(Statement::IfElse(Box::new(IfElse::from_syntax_node( + child, source, + )?))), + _ => Err(Error::UnexpectedSyntax { + expected: "assignment, expression, if...else or tool", + actual: child.kind(), + location: child.start_position(), + relevant_source: source[node.byte_range()].to_string(), + }), + } + } + + fn run(&self, context: &mut VariableMap) -> Result { + match self { + Statement::Assignment(assignment) => assignment.run(context), + Statement::Expression(expression) => expression.run(context), + Statement::IfElse(if_else) => if_else.run(context), + Statement::Match(r#match) => r#match.run(context), + Statement::Tool(tool) => tool.run(context), + } + } +} diff --git a/src/abstract_tree/tool.rs b/src/abstract_tree/tool.rs new file mode 100644 index 0000000..96a248b --- /dev/null +++ b/src/abstract_tree/tool.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Serialize}; +use tree_sitter::Node; + +use crate::{AbstractTree, Error, Expression, Result, Value, VariableMap}; + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] +pub enum ToolCall { + Output(Expression), +} + +impl AbstractTree for ToolCall { + fn from_syntax_node(node: Node, source: &str) -> Result { + let tool_node = node.child(1).unwrap(); + let tool_name = tool_node.kind(); + + match tool_name { + "output" => { + let expression_node = node.child(1).unwrap(); + let expression = Expression::from_syntax_node(expression_node, source)?; + + Ok(ToolCall::Output(expression)) + } + _ => Err(Error::UnexpectedSyntax { + expected: "output", + actual: tool_name, + location: tool_node.start_position(), + relevant_source: tool_node.kind().to_string(), + }), + } + } + + fn run(&self, context: &mut VariableMap) -> Result { + match self { + ToolCall::Output(expression) => { + let value = expression.run(context)?; + + println!("{value}") + } + } + + Ok(Value::Empty) + } +} diff --git a/src/error.rs b/src/error.rs index b2738ff..c78cfc9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,7 +3,7 @@ //! To deal with errors from dependencies, either create a new error variant //! or use the MacroFailure variant if the error can only occur inside a macro. -use crate::{value::value_type::ValueType, value::Value}; +use crate::{value::value_type::ValueType, value::Value, Identifier}; use std::{fmt, io, time::SystemTimeError}; @@ -125,7 +125,7 @@ pub enum Error { VariableIdentifierNotFound(String), /// A `FunctionIdentifier` operation did not find its value in the context. - FunctionIdentifierNotFound(String), + FunctionIdentifierNotFound(Identifier), /// A value has the wrong type. /// Only use this if there is no other error that describes the expected and diff --git a/src/evaluator.rs b/src/evaluator.rs new file mode 100644 index 0000000..a90bdad --- /dev/null +++ b/src/evaluator.rs @@ -0,0 +1,314 @@ +//! The top level of Dust's API with functions to interpret Dust code. +//! +//! You can use this library externally by calling either of the "eval" +//! functions or by constructing your own Evaluator. +use std::fmt::{self, Debug, Formatter}; + +use tree_sitter::{Parser, Tree as TSTree}; + +use crate::{abstract_tree::item::Item, language, AbstractTree, Result, Value, VariableMap}; + +/// Evaluate the given source code. +/// +/// Returns a vector of results from evaluating the source code. Each comment +/// and statemtent will have its own result. +/// +/// # Examples +/// +/// ```rust +/// # use dust::*; +/// assert_eq!(evaluate("1 + 2 + 3"), vec![Ok(Value::Integer(6))]); +/// ``` +pub fn evaluate(source: &str) -> Vec> { + let mut context = VariableMap::new(); + + evaluate_with_context(source, &mut context) +} + +/// Evaluate the given source code with the given context. +/// +/// # Examples +/// +/// ```rust +/// # use dust::*; +/// let mut context = VariableMap::new(); +/// +/// context.set_value("one".into(), 1.into()); +/// context.set_value("two".into(), 2.into()); +/// context.set_value("three".into(), 3.into()); +/// +/// let dust_code = "four = 4 one + two + three + four"; +/// +/// assert_eq!( +/// evaluate_with_context(dust_code, &mut context), +/// vec![Ok(Value::Empty), Ok(Value::Integer(10))] +/// ); +/// ``` +pub fn evaluate_with_context(source: &str, context: &mut VariableMap) -> Vec> { + let mut parser = Parser::new(); + parser.set_language(language()).unwrap(); + + Evaluator::new(parser, context, source).run() +} + +/// A collection of statements and comments interpreted from a syntax tree. +/// +/// The Evaluator turns a tree sitter concrete syntax tree into a vector of +/// abstract trees called [Item][]s that can be run to execute the source code. +pub struct Evaluator<'context, 'code> { + _parser: Parser, + context: &'context mut VariableMap, + source: &'code str, + syntax_tree: TSTree, +} + +impl Debug for Evaluator<'_, '_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Evaluator context: {}", self.context) + } +} + +impl<'context, 'code> Evaluator<'context, 'code> { + fn new(mut parser: Parser, context: &'context mut VariableMap, source: &'code str) -> Self { + let syntax_tree = parser.parse(source, None).unwrap(); + + Evaluator { + _parser: parser, + context, + source, + syntax_tree, + } + } + + fn run(self) -> Vec> { + let mut cursor = self.syntax_tree.walk(); + let root_node = cursor.node(); + let item_count = root_node.child_count(); + let mut results = Vec::with_capacity(item_count); + + println!("{}", root_node.to_sexp()); + + for item_node in root_node.children(&mut cursor) { + let item_result = Item::from_syntax_node(item_node, self.source); + + match item_result { + Ok(item) => { + let eval_result = item.run(self.context); + + results.push(eval_result); + } + Err(error) => results.push(Err(error)), + } + } + + results + } +} + +#[cfg(test)] +mod tests { + use crate::{ + abstract_tree::{expression::Expression, identifier::Identifier, statement::Statement}, + tool::ToolCall, + value::variable_map, + Function, FunctionCall, Table, + }; + + use super::*; + + #[test] + fn evaluate_empty() { + assert_eq!(evaluate("x = 9"), vec![Ok(Value::Empty)]); + assert_eq!(evaluate("x = 1 + 1"), vec![Ok(Value::Empty)]); + } + + #[test] + fn evaluate_integer() { + assert_eq!(evaluate("1"), vec![Ok(Value::Integer(1))]); + assert_eq!(evaluate("123"), vec![Ok(Value::Integer(123))]); + assert_eq!(evaluate("-666"), vec![Ok(Value::Integer(-666))]); + } + + #[test] + fn evaluate_float() { + assert_eq!(evaluate("0.1"), vec![Ok(Value::Float(0.1))]); + assert_eq!(evaluate("12.3"), vec![Ok(Value::Float(12.3))]); + assert_eq!(evaluate("-6.66"), vec![Ok(Value::Float(-6.66))]); + } + + #[test] + fn evaluate_string() { + assert_eq!( + evaluate("\"one\""), + vec![Ok(Value::String("one".to_string()))] + ); + assert_eq!( + evaluate("'one'"), + vec![Ok(Value::String("one".to_string()))] + ); + assert_eq!( + evaluate("`one`"), + vec![Ok(Value::String("one".to_string()))] + ); + assert_eq!( + evaluate("`'one'`"), + vec![Ok(Value::String("'one'".to_string()))] + ); + assert_eq!( + evaluate("'`one`'"), + vec![Ok(Value::String("`one`".to_string()))] + ); + assert_eq!( + evaluate("\"'one'\""), + vec![Ok(Value::String("'one'".to_string()))] + ); + } + + #[test] + fn evaluate_list() { + assert_eq!( + evaluate("[1, 2, 'foobar']"), + vec![Ok(Value::List(vec![ + Value::Integer(1), + Value::Integer(2), + Value::String("foobar".to_string()), + ]))] + ); + } + + #[test] + fn evaluate_map() { + let mut map = VariableMap::new(); + + map.set_value("x".to_string(), Value::Integer(1)).unwrap(); + map.set_value("foo".to_string(), Value::String("bar".to_string())) + .unwrap(); + + assert_eq!(evaluate("{ x = 1 foo = 'bar' }"), vec![Ok(Value::Map(map))]); + } + + #[test] + fn evaluate_table() { + let mut table = Table::new(vec!["messages".to_string(), "numbers".to_string()]); + + table + .insert(vec![Value::String("hiya".to_string()), Value::Integer(42)]) + .unwrap(); + table + .insert(vec![Value::String("foo".to_string()), Value::Integer(57)]) + .unwrap(); + table + .insert(vec![Value::String("bar".to_string()), Value::Float(99.99)]) + .unwrap(); + + assert_eq!( + evaluate( + " + table { + ['hiya', 42] + ['foo', 57] + ['bar', 99.99] + } + " + ), + vec![Ok(Value::Table(table))] + ); + } + + #[test] + fn evaluate_if_then() { + assert_eq!( + evaluate("if true then 'true'"), + vec![Ok(Value::String("true".to_string()))] + ); + } + + #[test] + fn evaluate_if_then_else() { + assert_eq!( + evaluate("if false then 1 else 2"), + vec![Ok(Value::Integer(2))] + ); + assert_eq!( + evaluate("if true then 1.0 else 42.0"), + vec![Ok(Value::Float(1.0))] + ); + } + + #[test] + fn evaluate_if_else_else_if_else() { + assert_eq!( + evaluate( + " + if false + then 'no' + else if 1 + 1 == 3 + then 'nope' + else + 'ok' + " + ), + vec![Ok(Value::String("ok".to_string()))] + ); + } + + #[test] + fn evaluate_if_else_else_if_else_if_else_if_else() { + assert_eq!( + evaluate( + " + if false + then 'no' + else if 1 + 1 == 1 + then 'nope' + else if 9 / 2 == 4 + then 'nope' + else if 'foo' == 'bar' + then 'nope' + else 'ok' + " + ), + vec![Ok(Value::String("ok".to_string()))] + ); + } + + #[test] + fn evaluate_function() { + let function = Function::new( + vec![Identifier::new("message".to_string())], + vec![Statement::Expression(Expression::Identifier( + Identifier::new("message".to_string()), + ))], + ); + + assert_eq!( + evaluate("function { message }"), + vec![Ok(Value::Function(function))] + ); + } + + #[test] + fn evaluate_function_call() { + let mut context = VariableMap::new(); + let function = Function::new( + vec![Identifier::new("message".to_string())], + vec![Statement::Expression(Expression::Identifier( + Identifier::new("message".to_string()), + ))], + ); + + context + .set_value("foobar".to_string(), Value::Function(function)) + .unwrap(); + + assert_eq!( + evaluate("(foobar 'Hiya')"), + vec![Ok(Value::String("Hiya".to_string()))] + ); + } + + #[test] + fn evaluate_tool_call() { + assert_eq!(evaluate("(output 'Hiya')"), vec![Ok(Value::Empty)]); + } +} diff --git a/src/interface.rs b/src/interface.rs deleted file mode 100644 index 600ef5e..0000000 --- a/src/interface.rs +++ /dev/null @@ -1,720 +0,0 @@ -//! The top level of Dust's API with functions to interpret Dust code. -//! -//! You can use this library externally by calling either of the "eval" -//! functions or by constructing your own Evaluator. -use std::fmt::{self, Debug, Formatter}; - -use serde::{Deserialize, Serialize}; -use tree_sitter::{Node, Parser, Tree as TSTree}; - -use crate::{language, Error, Result, Value, VariableMap}; - -/// Evaluate the given source code. -/// -/// Returns a vector of results from evaluating the source code. Each comment -/// and statemtent will have its own result. -/// -/// # Examples -/// -/// ```rust -/// # use dust::*; -/// assert_eq!(evaluate("1 + 2 + 3"), vec![Ok(Value::Integer(6))]); -/// ``` -pub fn evaluate(source: &str) -> Vec> { - let mut context = VariableMap::new(); - - evaluate_with_context(source, &mut context) -} - -/// Evaluate the given source code with the given context. -/// -/// # Examples -/// -/// ```rust -/// # use dust::*; -/// let mut context = VariableMap::new(); -/// -/// context.set_value("one".into(), 1.into()); -/// context.set_value("two".into(), 2.into()); -/// context.set_value("three".into(), 3.into()); -/// -/// let dust_code = "four = 4 one + two + three + four"; -/// -/// assert_eq!( -/// evaluate_with_context(dust_code, &mut context), -/// vec![Ok(Value::Empty), Ok(Value::Integer(10))] -/// ); -/// ``` -pub fn evaluate_with_context(source: &str, context: &mut VariableMap) -> Vec> { - let mut parser = Parser::new(); - parser.set_language(language()).unwrap(); - - Evaluator::new(parser, context, source).run() -} - -/// This trait is implemented by the Evaluator's internal types. -pub trait EvaluatorTree: Sized { - /// Interpret the syntax tree at the given node and return the abstraction. - /// - /// This function is used to convert nodes in the Tree Sitter concrete - /// syntax tree into executable nodes in an abstract tree. This function is - /// where the tree should be traversed by accessing sibling and child nodes. - /// Each node in the CST should be traversed only once. - /// - /// If necessary, the source code can be accessed directly by getting the - /// node's byte range. - fn from_syntax_node(node: Node, source: &str) -> Result; - - /// Execute dust code by traversing the tree - fn run(&self, context: &mut VariableMap) -> Result; -} - -/// A collection of statements and comments interpreted from a syntax tree. -/// -/// The Evaluator turns a tree sitter concrete syntax tree into a vector of -/// abstract trees called [Item][]s that can be run to execute the source code. -pub struct Evaluator<'context, 'code> { - _parser: Parser, - context: &'context mut VariableMap, - source: &'code str, - tree: TSTree, -} - -impl Debug for Evaluator<'_, '_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "Evaluator context: {}", self.context) - } -} - -impl<'context, 'code> Evaluator<'context, 'code> { - fn new(mut parser: Parser, context: &'context mut VariableMap, source: &'code str) -> Self { - let tree = parser.parse(source, None).unwrap(); - - Evaluator { - _parser: parser, - context, - source, - tree, - } - } - - fn run(self) -> Vec> { - let mut cursor = self.tree.walk(); - let root_node = cursor.node(); - let item_count = root_node.child_count(); - let mut results = Vec::with_capacity(item_count); - - println!("{}", root_node.to_sexp()); - - for item_node in root_node.children(&mut cursor) { - let item_result = Item::from_syntax_node(item_node, self.source); - - match item_result { - Ok(item) => { - let eval_result = item.run(self.context); - - results.push(eval_result); - } - Err(error) => results.push(Err(error)), - } - } - - results - } -} - -/// An abstractiton of an independent unit of source code. -/// -/// Items are either comments, which do nothing, or statements, which can be run -/// to produce a single value or interact with a context by creating or -/// referencing variables. -#[derive(Debug)] -pub enum Item { - Comment(String), - Statement(Statement), -} - -impl EvaluatorTree for Item { - fn from_syntax_node(node: Node, source: &str) -> Result { - let child = node.child(0).unwrap(); - - if child.kind() == "comment" { - let byte_range = child.byte_range(); - let comment_text = &source[byte_range]; - - Ok(Item::Comment(comment_text.to_string())) - } else if child.kind() == "statement" { - Ok(Item::Statement(Statement::from_syntax_node(child, source)?)) - } else { - Err(Error::UnexpectedSyntax { - expected: "comment or statement", - actual: child.kind(), - location: child.start_position(), - relevant_source: source[node.byte_range()].to_string(), - }) - } - } - - fn run(&self, context: &mut VariableMap) -> Result { - match self { - Item::Comment(text) => Ok(Value::String(text.clone())), - Item::Statement(statement) => statement.run(context), - } - } -} - -/// Abstract representation of a statement. -/// -/// Items are either comments, which do nothing, or statements, which can be run -/// to produce a single value or interact with their context. -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] -pub enum Statement { - Assignment(Box), - Expression(Expression), - IfElse(Box), - Match(Match), -} - -impl EvaluatorTree for Statement { - fn from_syntax_node(node: Node, source: &str) -> Result { - debug_assert_eq!("statement", node.kind()); - - let child = node.child(0).unwrap(); - - match child.kind() { - "assignment" => Ok(Statement::Assignment(Box::new( - Assignment::from_syntax_node(child, source)?, - ))), - "expression" => Ok(Self::Expression(Expression::from_syntax_node( - child, source, - )?)), - "if_else" => Ok(Statement::IfElse(Box::new(IfElse::from_syntax_node( - child, source, - )?))), - _ => Err(Error::UnexpectedSyntax { - expected: "assignment, expression or if...else", - actual: child.kind(), - location: child.start_position(), - relevant_source: source[node.byte_range()].to_string(), - }), - } - } - - fn run(&self, context: &mut VariableMap) -> Result { - match self { - Statement::Assignment(assignment) => assignment.run(context), - Statement::Expression(expression) => expression.run(context), - Statement::IfElse(if_else) => if_else.run(context), - Statement::Match(r#match) => r#match.run(context), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] -pub enum Expression { - Identifier(Identifier), - Value(Value), - Math(Box), - Logic(Box), - FunctionCall(FunctionCall), -} - -impl EvaluatorTree for Expression { - fn from_syntax_node(node: Node, source: &str) -> Result { - let child = node.child(0).unwrap(); - - let expression = match child.kind() { - "value" => Expression::Value(Value::from_syntax_node(child, source)?), - "identifier" => Self::Identifier(Identifier::from_syntax_node(child, source)?), - "math" => Expression::Math(Box::new(Math::from_syntax_node(child, source)?)), - "logic" => Expression::Logic(Box::new(Logic::from_syntax_node(child, source)?)), - "function_call" => { - Expression::FunctionCall(FunctionCall::from_syntax_node(child, source)?) - } - _ => { - return Err(Error::UnexpectedSyntax { - expected: "value, identifier, math or function_call", - actual: child.kind(), - location: child.start_position(), - relevant_source: source[node.byte_range()].to_string(), - }) - } - }; - - Ok(expression) - } - - fn run(&self, context: &mut VariableMap) -> Result { - match self { - Expression::Value(value) => Ok(value.clone()), - Expression::Identifier(identifier) => identifier.run(context), - Expression::Math(math) => math.run(context), - Expression::FunctionCall(function_call) => function_call.run(context), - Expression::Logic(logic) => logic.run(context), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] -pub struct Identifier(String); - -impl Identifier { - pub fn new(inner: String) -> Self { - Identifier(inner) - } - - pub fn take_inner(self) -> String { - self.0 - } - - pub fn inner(&self) -> &String { - &self.0 - } -} - -impl EvaluatorTree for Identifier { - fn from_syntax_node(node: Node, source: &str) -> Result { - let identifier = &source[node.byte_range()]; - - Ok(Identifier(identifier.to_string())) - } - - fn run(&self, context: &mut VariableMap) -> Result { - let value = context.get_value(&self.0)?.unwrap_or_default(); - - Ok(value) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] -pub struct IfElse { - if_expression: Expression, - then_statement: Statement, - else_statement: Option, -} - -impl EvaluatorTree for IfElse { - fn from_syntax_node(node: Node, source: &str) -> Result { - let if_node = node.child(1).unwrap(); - let if_expression = Expression::from_syntax_node(if_node, source)?; - - let then_node = node.child(3).unwrap(); - let then_statement = Statement::from_syntax_node(then_node, source)?; - - let else_node = node.child(5); - let else_statement = if let Some(node) = else_node { - Some(Statement::from_syntax_node(node, source)?) - } else { - None - }; - - println!("{if_node:?} {then_node:?} {else_node:?}"); - - Ok(IfElse { - if_expression, - then_statement, - else_statement, - }) - } - - fn run(&self, context: &mut VariableMap) -> Result { - let if_boolean = self.if_expression.run(context)?.as_boolean()?; - - if if_boolean { - self.then_statement.run(context) - } else if let Some(statement) = &self.else_statement { - statement.run(context) - } else { - Ok(Value::Empty) - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] -pub struct Match {} - -impl EvaluatorTree for Match { - fn from_syntax_node(node: Node, source: &str) -> Result { - todo!() - } - - fn run(&self, context: &mut VariableMap) -> Result { - todo!() - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] -pub struct Assignment { - identifier: Identifier, - statement: Statement, -} - -impl EvaluatorTree for Assignment { - fn from_syntax_node(node: Node, source: &str) -> Result { - let identifier_node = node.child(0).unwrap(); - let identifier = Identifier::from_syntax_node(identifier_node, source)?; - - let statement_node = node.child(2).unwrap(); - let statement = Statement::from_syntax_node(statement_node, source)?; - - Ok(Assignment { - identifier, - statement, - }) - } - - fn run(&self, context: &mut VariableMap) -> Result { - let key = self.identifier.clone().take_inner(); - let value = self.statement.run(context)?; - - context.set_value(key, value)?; - - Ok(Value::Empty) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] -pub struct Math { - left: Expression, - operator: MathOperator, - right: Expression, -} - -impl EvaluatorTree for Math { - fn from_syntax_node(node: Node, source: &str) -> Result { - let left_node = node.child(0).unwrap(); - let left = Expression::from_syntax_node(left_node, source)?; - - let operator_node = node.child(1).unwrap().child(0).unwrap(); - let operator = match operator_node.kind() { - "+" => MathOperator::Add, - "-" => MathOperator::Subtract, - "*" => MathOperator::Multiply, - "/" => MathOperator::Divide, - "%" => MathOperator::Modulo, - _ => { - return Err(Error::UnexpectedSyntax { - expected: "+, -, *, / or %", - actual: operator_node.kind(), - location: operator_node.start_position(), - relevant_source: source[operator_node.byte_range()].to_string(), - }) - } - }; - - let right_node = node.child(2).unwrap(); - let right = Expression::from_syntax_node(right_node, source)?; - - Ok(Math { - left, - operator, - right, - }) - } - - fn run(&self, context: &mut VariableMap) -> Result { - match self.operator { - MathOperator::Add | MathOperator::Subtract | MathOperator::Multiply => { - let left_value = self.left.run(context)?.as_int()?; - let right_value = self.right.run(context)?.as_int()?; - let outcome = match &self.operator { - MathOperator::Add => left_value + right_value, - MathOperator::Subtract => left_value - right_value, - MathOperator::Multiply => left_value * right_value, - _ => panic!("Unreachable"), - }; - - Ok(Value::Integer(outcome)) - } - MathOperator::Divide | MathOperator::Modulo => { - let left_value = self.left.run(context)?.as_number()?; - let right_value = self.right.run(context)?.as_number()?; - let outcome = match self.operator { - MathOperator::Divide => left_value / right_value, - MathOperator::Modulo => left_value % right_value, - _ => panic!("Unreachable"), - }; - - Ok(Value::Float(outcome)) - } - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] -pub enum MathOperator { - Add, - Subtract, - Multiply, - Divide, - Modulo, -} - -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] -pub struct Logic { - left: Expression, - operator: LogicOperator, - right: Expression, -} - -impl EvaluatorTree for Logic { - fn from_syntax_node(node: Node, source: &str) -> Result { - let left_node = node.child(0).unwrap(); - let left = Expression::from_syntax_node(left_node, source)?; - - let operator_node = node.child(1).unwrap().child(0).unwrap(); - let operator = match operator_node.kind() { - "==" => LogicOperator::Equal, - "&&" => LogicOperator::And, - "||" => LogicOperator::Or, - _ => { - return Err(Error::UnexpectedSyntax { - expected: "==, && or ||", - actual: operator_node.kind(), - location: operator_node.start_position(), - relevant_source: source[operator_node.byte_range()].to_string(), - }) - } - }; - - let right_node = node.child(2).unwrap(); - let right = Expression::from_syntax_node(right_node, source)?; - - Ok(Logic { - left, - operator, - right, - }) - } - - fn run(&self, context: &mut VariableMap) -> Result { - let left_value = self.left.run(context)?; - let right_value = self.right.run(context)?; - let outcome = match self.operator { - LogicOperator::Equal => left_value == right_value, - LogicOperator::And => left_value.as_boolean()? && right_value.as_boolean()?, - LogicOperator::Or => left_value.as_boolean()? || right_value.as_boolean()?, - }; - - Ok(Value::Boolean(outcome)) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] -pub enum LogicOperator { - Equal, - And, - Or, -} - -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] -pub struct FunctionCall { - identifier: Identifier, - expressions: Vec, -} - -impl EvaluatorTree for FunctionCall { - fn from_syntax_node(node: Node, source: &str) -> Result { - let identifier_node = node.child(0).unwrap(); - let identifier = Identifier::from_syntax_node(identifier_node, source)?; - - let mut expressions = Vec::new(); - - todo!(); - - Ok(FunctionCall { - identifier, - expressions, - }) - } - - fn run(&self, context: &mut VariableMap) -> Result { - let mut arguments = Vec::with_capacity(self.expressions.len()); - - for expression in &self.expressions { - let value = expression.run(context)?; - - arguments.push(value); - } - - context.call_function(self.identifier.inner(), &Value::List(arguments)) - } -} - -#[cfg(test)] -mod tests { - use crate::{Function, Table}; - - use super::*; - - #[test] - fn evaluate_empty() { - assert_eq!(evaluate("x = 9"), vec![Ok(Value::Empty)]); - assert_eq!(evaluate("x = 1 + 1"), vec![Ok(Value::Empty)]); - } - - #[test] - fn evaluate_integer() { - assert_eq!(evaluate("1"), vec![Ok(Value::Integer(1))]); - assert_eq!(evaluate("123"), vec![Ok(Value::Integer(123))]); - assert_eq!(evaluate("-666"), vec![Ok(Value::Integer(-666))]); - } - - #[test] - fn evaluate_float() { - assert_eq!(evaluate("0.1"), vec![Ok(Value::Float(0.1))]); - assert_eq!(evaluate("12.3"), vec![Ok(Value::Float(12.3))]); - assert_eq!(evaluate("-6.66"), vec![Ok(Value::Float(-6.66))]); - } - - #[test] - fn evaluate_string() { - assert_eq!( - evaluate("\"one\""), - vec![Ok(Value::String("one".to_string()))] - ); - assert_eq!( - evaluate("'one'"), - vec![Ok(Value::String("one".to_string()))] - ); - assert_eq!( - evaluate("`one`"), - vec![Ok(Value::String("one".to_string()))] - ); - assert_eq!( - evaluate("`'one'`"), - vec![Ok(Value::String("'one'".to_string()))] - ); - assert_eq!( - evaluate("'`one`'"), - vec![Ok(Value::String("`one`".to_string()))] - ); - assert_eq!( - evaluate("\"'one'\""), - vec![Ok(Value::String("'one'".to_string()))] - ); - } - - #[test] - fn evaluate_list() { - assert_eq!( - evaluate("[1, 2, 'foobar']"), - vec![Ok(Value::List(vec![ - Value::Integer(1), - Value::Integer(2), - Value::String("foobar".to_string()), - ]))] - ); - } - - #[test] - fn evaluate_map() { - let mut map = VariableMap::new(); - - map.set_value("x".to_string(), Value::Integer(1)).unwrap(); - map.set_value("foo".to_string(), Value::String("bar".to_string())) - .unwrap(); - - assert_eq!(evaluate("{ x = 1 foo = 'bar' }"), vec![Ok(Value::Map(map))]); - } - - #[test] - fn evaluate_table() { - let mut table = Table::new(vec!["messages".to_string(), "numbers".to_string()]); - - table - .insert(vec![Value::String("hiya".to_string()), Value::Integer(42)]) - .unwrap(); - table - .insert(vec![Value::String("foo".to_string()), Value::Integer(57)]) - .unwrap(); - table - .insert(vec![Value::String("bar".to_string()), Value::Float(99.99)]) - .unwrap(); - - assert_eq!( - evaluate( - " - table { - ['hiya', 42] - ['foo', 57] - ['bar', 99.99] - } - " - ), - vec![Ok(Value::Table(table))] - ); - } - - #[test] - fn evaluate_if_then() { - assert_eq!( - evaluate("if true then 'true'"), - vec![Ok(Value::String("true".to_string()))] - ); - } - - #[test] - fn evaluate_if_then_else() { - assert_eq!( - evaluate("if false then 1 else 2"), - vec![Ok(Value::Integer(2))] - ); - assert_eq!( - evaluate("if true then 1.0 else 42.0"), - vec![Ok(Value::Float(1.0))] - ); - } - - #[test] - fn evaluate_if_else_else_if_else() { - assert_eq!( - evaluate( - " - if false - then 'no' - else if 1 + 1 == 3 - then 'nope' - else - 'ok' - " - ), - vec![Ok(Value::String("ok".to_string()))] - ); - } - - #[test] - fn evaluate_if_else_else_if_else_if_else_if_else() { - assert_eq!( - evaluate( - " - if false - then 'no' - else if 1 + 1 == 1 - then 'nope' - else if 9 / 2 == 4 - then 'nope' - else if 'foo' == 'bar' - then 'nope' - else 'ok' - " - ), - vec![Ok(Value::String("ok".to_string()))] - ); - } - - #[test] - fn evaluate_function() { - let function = Function::new( - vec![Identifier::new("output".to_string())], - vec![Statement::Expression(Expression::Identifier( - Identifier::new("output".to_string()), - ))], - ); - - assert_eq!( - evaluate("function { output }"), - vec![Ok(Value::Function(function))] - ); - } -} diff --git a/src/lib.rs b/src/lib.rs index 6f99822..5aa59eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,16 +5,18 @@ //! interpreting Dust code. Most of the language's features are implemented in the [tools] module. pub use crate::{ + abstract_tree::*, error::*, - interface::*, + evaluator::*, value::{ function::Function, table::Table, time::Time, value_type::ValueType, variable_map::VariableMap, Value, }, }; +mod abstract_tree; mod error; -mod interface; +mod evaluator; mod value; use tree_sitter::Language; diff --git a/src/tools/collections.rs b/src/tools/collections.rs deleted file mode 100644 index 273dda2..0000000 --- a/src/tools/collections.rs +++ /dev/null @@ -1,416 +0,0 @@ -//! Macros for collection values: strings, lists, maps and tables. -//! -//! Tests for this module are written in Dust and can be found at tests/collections.ds. - -use crate::{Error, Result, Table, Tool, ToolInfo, Value, ValueType, VariableMap}; - -pub struct Sort; - -impl Tool for Sort { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "sort", - description: "Apply default ordering to a list or table.", - group: "collections", - inputs: vec![ValueType::List, ValueType::Table], - } - } - - fn run(&self, argument: &Value) -> Result { - if let Ok(mut list) = argument.as_list().cloned() { - list.sort(); - - Ok(Value::List(list)) - } else if let Ok(mut table) = argument.as_table().cloned() { - table.sort(); - - Ok(Value::Table(table)) - } else { - Err(crate::Error::ExpectedList { - actual: argument.clone(), - }) - } - } -} - -pub struct Transform; - -impl Tool for Transform { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "transform", - description: "Alter a list by calling a function on each value.", - group: "collections", - inputs: vec![ValueType::ListExact(vec![ - ValueType::List, - ValueType::Function, - ])], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = self.check_type(argument)?.as_list()?; - let list = argument[0].as_list()?; - let function = argument[1].as_function()?; - let mut mapped_list = Vec::with_capacity(list.len()); - - for value in list { - let mut context = VariableMap::new(); - - context.set_value("input".to_string(), value.clone())?; - - let mapped_value = function.run_with_context(&mut context)?; - - mapped_list.push(mapped_value); - } - - Ok(Value::List(mapped_list)) - } -} - -pub struct String; - -impl Tool for String { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "string", - description: "Stringify a value.", - group: "collections", - inputs: vec![ - ValueType::String, - ValueType::Function, - ValueType::Float, - ValueType::Integer, - ValueType::Boolean, - ], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = self.check_type(argument)?; - - let string = match argument { - Value::String(string) => string.clone(), - Value::Function(function) => function.to_string(), - Value::Float(float) => float.to_string(), - Value::Integer(integer) => integer.to_string(), - Value::Boolean(boolean) => boolean.to_string(), - _ => return self.fail(argument), - }; - - Ok(Value::String(string)) - } -} - -pub struct Replace; - -impl Tool for Replace { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "replace", - description: "Replace all occurences of a substring in a string.", - group: "collections", - inputs: vec![ValueType::ListExact(vec![ - ValueType::String, - ValueType::String, - ValueType::String, - ])], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = self.check_type(argument)?.as_list()?; - let target = argument[0].as_string()?; - let to_remove = argument[1].as_string()?; - let replacement = argument[2].as_string()?; - let result = target.replace(to_remove, replacement); - - Ok(Value::String(result)) - } -} - -pub struct Count; - -impl Tool for Count { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "count", - description: "Return the number of items in a collection.", - group: "collections", - inputs: vec![ - ValueType::String, - ValueType::List, - ValueType::Map, - ValueType::Table, - ], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = self.check_type(argument)?; - - let len = match argument { - Value::String(string) => string.chars().count(), - Value::List(list) => list.len(), - Value::Map(map) => map.len(), - Value::Table(table) => table.len(), - Value::Function(_) - | Value::Float(_) - | Value::Integer(_) - | Value::Boolean(_) - | Value::Time(_) - | Value::Empty => return self.fail(argument), - }; - - Ok(Value::Integer(len as i64)) - } -} - -pub struct CreateTable; - -impl Tool for CreateTable { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "create_table", - description: "Define a new table with a list of column names and list of rows.", - group: "collections", - inputs: vec![ValueType::ListExact(vec![ - ValueType::ListOf(Box::new(ValueType::String)), - ValueType::ListOf(Box::new(ValueType::List)), - ])], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = self.check_type(argument)?.as_list()?; - - let column_name_inputs = argument[0].as_list()?; - let mut column_names = Vec::with_capacity(column_name_inputs.len()); - - for name in column_name_inputs { - column_names.push(name.as_string()?.clone()); - } - - let column_count = column_names.len(); - let rows = argument[1].as_list()?; - let mut table = Table::new(column_names); - - for row in rows { - let row = row.as_fixed_len_list(column_count)?; - - table.insert(row.clone()).unwrap(); - } - - Ok(Value::Table(table)) - } -} - -pub struct Rows; - -impl Tool for Rows { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "rows", - description: "Extract a table's rows as a list.", - group: "collections", - inputs: vec![ValueType::Table], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = self.check_type(argument)?; - - if let Value::Table(table) = argument { - let rows = table - .rows() - .iter() - .map(|row| Value::List(row.clone())) - .collect(); - - Ok(Value::List(rows)) - } else { - self.fail(argument) - } - } -} - -pub struct Insert; - -impl Tool for Insert { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "insert", - description: "Add new rows to a table.", - group: "collections", - inputs: vec![ValueType::ListExact(vec![ - ValueType::Table, - ValueType::ListOf(Box::new(ValueType::List)), - ])], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_list()?; - let new_rows = argument[1].as_list()?; - let mut table = argument[0].as_table()?.clone(); - - table.reserve(new_rows.len()); - - for row in new_rows { - let row = row.as_list()?.clone(); - - table.insert(row)?; - } - - Ok(Value::Table(table)) - } -} - -pub struct Select; - -impl Tool for Select { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "select", - description: "Extract one or more values based on their key.", - group: "collections", - inputs: vec![ - ValueType::ListExact(vec![ValueType::Table, ValueType::String]), - ValueType::ListExact(vec![ - ValueType::Table, - ValueType::ListOf(Box::new(ValueType::String)), - ]), - ], - } - } - - fn run(&self, argument: &Value) -> Result { - let arguments = argument.as_fixed_len_list(2)?; - let collection = &arguments[0]; - - if let Value::List(list) = collection { - let mut selected = Vec::new(); - - let index = arguments[1].as_int()?; - let value = list.get(index as usize); - - if let Some(value) = value { - selected.push(value.clone()); - return Ok(Value::List(selected)); - } else { - return Ok(Value::List(selected)); - } - } - - let mut column_names = Vec::new(); - - if let Value::List(columns) = &arguments[1] { - for column in columns { - let name = column.as_string()?; - - column_names.push(name.clone()); - } - } else if let Value::String(column) = &arguments[1] { - column_names.push(column.clone()); - } else { - todo!() - }; - - if let Value::Map(map) = collection { - let mut selected = VariableMap::new(); - - for (key, value) in map.inner() { - if column_names.contains(key) { - selected.set_value(key.to_string(), value.clone())?; - } - } - - return Ok(Value::Map(selected)); - } - - if let Value::Table(table) = collection { - let selected = table.select(&column_names); - - return Ok(Value::Table(selected)); - } - - todo!() - } -} - -pub struct ForEach; - -impl Tool for ForEach { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "for_each", - description: "Run an operation on every item in a collection.", - group: "collections", - inputs: vec![], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_list()?; - - Error::expected_minimum_function_argument_amount( - self.info().identifier, - 2, - argument.len(), - )?; - - let table = argument[0].as_table()?; - let columns = argument[1].as_list()?; - let mut column_names = Vec::new(); - - for column in columns { - let name = column.as_string()?; - - column_names.push(name.clone()); - } - - let selected = table.select(&column_names); - - Ok(Value::Table(selected)) - } -} - -pub struct Where; - -impl Tool for Where { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "where", - description: "Keep rows matching a predicate.", - group: "collections", - inputs: vec![ValueType::ListExact(vec![ - ValueType::Table, - ValueType::Function, - ])], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = self.check_type(argument)?.as_list()?; - let table = &argument[0].as_table()?; - let function = argument[1].as_function()?; - - let mut context = VariableMap::new(); - let mut new_table = Table::new(table.column_names().clone()); - - for row in table.rows() { - for (column_index, cell) in row.iter().enumerate() { - let column_name = table.column_names().get(column_index).unwrap(); - - context.set_value(column_name.to_string(), cell.clone())?; - } - let keep_row = function.run_with_context(&mut context)?.as_boolean()?; - - if keep_row { - new_table.insert(row.clone())?; - } - } - - Ok(Value::Table(new_table)) - } -} diff --git a/src/tools/command.rs b/src/tools/command.rs deleted file mode 100644 index df9a909..0000000 --- a/src/tools/command.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::process::Command; - -use crate::{Result, Tool, ToolInfo, Value, ValueType}; - -pub struct Sh; - -impl Tool for Sh { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "sh", - description: "Pass input to the Bourne Shell.", - group: "command", - inputs: vec![ValueType::String], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_string()?; - - Command::new("sh").arg("-c").arg(argument).spawn()?.wait()?; - - Ok(Value::Empty) - } -} - -pub struct Bash; - -impl Tool for Bash { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "bash", - description: "Pass input to the Bourne Again Shell.", - group: "command", - inputs: vec![ValueType::String], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_string()?; - - Command::new("bash") - .arg("-c") - .arg(argument) - .spawn()? - .wait()?; - - Ok(Value::Empty) - } -} -pub struct Fish; - -impl Tool for Fish { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "fish", - description: "Pass input to the fish shell.", - group: "command", - inputs: vec![ValueType::String], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_string()?; - - Command::new("fish") - .arg("-c") - .arg(argument) - .spawn()? - .wait()?; - - Ok(Value::Empty) - } -} - -pub struct Zsh; - -impl Tool for Zsh { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "zsh", - description: "Pass input to the Z shell.", - group: "command", - inputs: vec![ValueType::String], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_string()?; - - Command::new("zsh") - .arg("-c") - .arg(argument) - .spawn()? - .wait()?; - - Ok(Value::Empty) - } -} - -pub struct Raw; - -impl Tool for Raw { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "raw", - description: "Run input as a command without a shell", - group: "command", - inputs: vec![ValueType::String], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_string()?; - - Command::new(argument).spawn()?.wait()?; - - Ok(Value::Empty) - } -} diff --git a/src/tools/data_formats.rs b/src/tools/data_formats.rs deleted file mode 100644 index 066092f..0000000 --- a/src/tools/data_formats.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! Convert values to and from data formats like JSON and TOML. - -use crate::{Result, Table, Tool, ToolInfo, Value, ValueType}; - -pub struct FromToml; - -impl Tool for FromToml { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "from_toml", - description: "Create a value from a TOML string.", - group: "data", - inputs: vec![ValueType::String], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_string()?; - let value = toml::from_str(&argument)?; - - Ok(value) - } -} - -pub struct FromJson; - -impl Tool for FromJson { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "from_json", - description: "Get a whale value from a JSON string.", - group: "data", - inputs: vec![ValueType::String], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_string()?; - let value = serde_json::from_str(argument)?; - - Ok(value) - } -} - -pub struct ToJson; - -impl Tool for ToJson { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "to_json", - description: "Create a JSON string from a whale value.", - group: "data", - inputs: vec![ValueType::Any], - } - } - - fn run(&self, argument: &Value) -> Result { - let json = serde_json::to_string(argument)?; - - Ok(Value::String(json)) - } -} - -pub struct FromCsv; - -impl Tool for FromCsv { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "from_csv", - description: "Create a whale value from a CSV string.", - group: "data", - inputs: vec![ValueType::String], - } - } - - fn run(&self, argument: &Value) -> Result { - let csv = argument.as_string()?; - let mut reader = csv::Reader::from_reader(csv.as_bytes()); - - let headers = reader - .headers()? - .iter() - .map(|header| header.trim().trim_matches('"').to_string()) - .collect(); - - let mut table = Table::new(headers); - - for result in reader.records() { - let row = result? - .iter() - .map(|column| { - let column = column.trim().trim_matches('"').trim_matches('\''); - - if let Ok(integer) = column.parse::() { - Value::Integer(integer) - } else if let Ok(float) = column.parse::() { - Value::Float(float) - } else { - Value::String(column.to_string()) - } - }) - .collect(); - - table.insert(row)?; - } - - Ok(Value::Table(table)) - } -} - -pub struct ToCsv; - -impl Tool for ToCsv { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "to_csv", - description: "Convert a value to a string of comma-separated values.", - group: "data", - inputs: vec![ValueType::Any], - } - } - - fn run(&self, argument: &Value) -> Result { - let mut buffer = Vec::new(); - let mut writer = csv::Writer::from_writer(&mut buffer); - - match argument { - Value::String(string) => { - writer.write_record([string])?; - } - Value::Float(float) => { - writer.write_record(&[float.to_string()])?; - } - Value::Integer(integer) => { - writer.write_record(&[integer.to_string()])?; - } - Value::Boolean(boolean) => { - writer.write_record(&[boolean.to_string()])?; - } - Value::List(list) => { - let string_list = list.iter().map(|value| value.to_string()); - - writer.write_record(string_list)?; - } - Value::Empty => {} - Value::Map(map) => { - writer.write_record(map.inner().keys())?; - writer.write_record(map.inner().values().map(|value| value.to_string()))?; - } - Value::Table(table) => { - writer.write_record(table.column_names())?; - - for row in table.rows() { - let row_string = row.iter().map(|value| value.to_string()); - - writer.write_record(row_string)?; - } - } - Value::Function(_) => todo!(), - Value::Time(time) => { - writer.write_record(&[time.to_string()])?; - } - } - - writer.flush()?; - - Ok(Value::String( - String::from_utf8_lossy(writer.get_ref()).to_string(), - )) - } -} diff --git a/src/tools/disks.rs b/src/tools/disks.rs deleted file mode 100644 index efbee18..0000000 --- a/src/tools/disks.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::process::Command; - -use sysinfo::{DiskExt, System, SystemExt}; - -use crate::{Result, Table, Tool, ToolInfo, Value, ValueType}; - -pub struct ListDisks; - -impl Tool for ListDisks { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "list_disks", - description: "List all block devices.", - group: "disks", - inputs: vec![ValueType::Empty], - } - } - - fn run(&self, argument: &Value) -> Result { - argument.as_empty()?; - - let mut sys = System::new_all(); - sys.refresh_all(); - - let mut disk_table = Table::new(vec![ - "name".to_string(), - "kind".to_string(), - "file system".to_string(), - "mount point".to_string(), - "total space".to_string(), - "available space".to_string(), - "is removable".to_string(), - ]); - - for disk in sys.disks() { - let name = disk.name().to_string_lossy().to_string(); - let kind = disk.kind(); - let file_system = String::from_utf8_lossy(disk.file_system()).to_string(); - let mount_point = disk.mount_point().to_str().unwrap().to_string(); - let total_space = disk.total_space() as i64; - let available_space = disk.available_space() as i64; - let is_removable = disk.is_removable(); - - let row = vec![ - Value::String(name), - Value::String(format!("{kind:?}")), - Value::String(file_system), - Value::String(mount_point), - Value::Integer(total_space), - Value::Integer(available_space), - Value::Boolean(is_removable), - ]; - - disk_table.insert(row)?; - } - - Ok(Value::Table(disk_table)) - } -} - -pub struct Partition; - -impl Tool for Partition { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "partition", - description: "Partition a disk, clearing its content.", - group: "disks", - inputs: vec![ValueType::Map], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_map()?; - let path = argument - .get_value("path")? - .unwrap_or(Value::Empty) - .as_string()? - .clone(); - let label = argument - .get_value("label")? - .unwrap_or(Value::Empty) - .as_string()? - .clone(); - let name = argument - .get_value("name")? - .unwrap_or(Value::Empty) - .as_string()? - .clone(); - let filesystem = argument - .get_value("filesystem")? - .unwrap_or(Value::Empty) - .as_string()? - .clone(); - let range = argument - .get_value("range")? - .unwrap_or(Value::Empty) - .as_list()? - .clone(); - - if range.len() != 2 { - return Err(crate::Error::ExpectedFixedLenList { - expected_len: 2, - actual: Value::List(range), - }); - } - - let range_start = range[0].as_string()?; - let range_end = range[1].as_string()?; - - let script = format!( - "sudo parted {path} mklabel {label} mkpart {name} {filesystem} {range_start} {range_end}" - ); - - Command::new("fish") - .arg("-c") - .arg(&script) - .spawn()? - .wait()?; - - Ok(Value::Empty) - } -} diff --git a/src/tools/filesystem.rs b/src/tools/filesystem.rs deleted file mode 100644 index 57639da..0000000 --- a/src/tools/filesystem.rs +++ /dev/null @@ -1,487 +0,0 @@ -//! Dust commands for managing files and directories. -use std::{ - fs::{self, OpenOptions}, - io::{Read, Write as IoWrite}, - path::PathBuf, -}; - -use crate::{Error, Result, Table, Time, Tool, ToolInfo, Value, ValueType}; - -pub struct Append; - -impl Tool for Append { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "append", - description: "Append data to a file.", - group: "filesystem", - inputs: vec![ValueType::ListExact(vec![ - ValueType::String, - ValueType::Any, - ])], - } - } - - fn run(&self, argument: &Value) -> Result { - let arguments = argument.as_fixed_len_list(2)?; - let path = arguments[0].as_string()?; - let content = arguments[1].as_string()?; - let mut file = OpenOptions::new().append(true).open(path)?; - - file.write_all(content.as_bytes())?; - - Ok(Value::Empty) - } -} - -pub struct CreateDir; - -impl Tool for CreateDir { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "create_dir", - description: "Create one or more directories.", - group: "filesystem", - inputs: vec![ - ValueType::String, - ValueType::ListOf(Box::new(ValueType::String)), - ], - } - } - - fn run(&self, argument: &Value) -> Result { - let path = argument.as_string()?; - fs::create_dir_all(path)?; - - Ok(Value::Empty) - } -} - -pub struct FileMetadata; - -impl Tool for FileMetadata { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "file_metadata", - description: "Get metadata for files.", - group: "filesystem", - inputs: vec![ - ValueType::String, - ValueType::ListOf(Box::new(ValueType::String)), - ], - } - } - - fn run(&self, argument: &Value) -> Result { - let path_string = argument.as_string()?; - let metadata = PathBuf::from(path_string).metadata()?; - let created = metadata.accessed()?.elapsed()?.as_secs() / 60; - let accessed = metadata.accessed()?.elapsed()?.as_secs() / 60; - let modified = metadata.modified()?.elapsed()?.as_secs() / 60; - let read_only = metadata.permissions().readonly(); - let size = metadata.len(); - - let mut file_table = Table::new(vec![ - "path".to_string(), - "size".to_string(), - "created".to_string(), - "accessed".to_string(), - "modified".to_string(), - "read only".to_string(), - ]); - - file_table.insert(vec![ - Value::String(path_string.clone()), - Value::Integer(size as i64), - Value::Integer(created as i64), - Value::Integer(accessed as i64), - Value::Integer(modified as i64), - Value::Boolean(read_only), - ])?; - - Ok(Value::Table(file_table)) - } -} - -pub struct ReadDir; - -impl Tool for ReadDir { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "read_dir", - description: "Read the content of a directory.", - group: "filesystem", - inputs: vec![ValueType::String, ValueType::Empty], - } - } - - fn run(&self, argument: &Value) -> Result { - let path = if let Ok(path) = argument.as_string() { - path - } else if argument.is_empty() { - "." - } else { - return Err(Error::TypeError { - expected: &[ValueType::Empty, ValueType::String], - actual: argument.clone(), - }); - }; - let dir = fs::read_dir(path)?; - let mut file_table = Table::new(vec![ - "path".to_string(), - "size".to_string(), - "created".to_string(), - "accessed".to_string(), - "modified".to_string(), - "read only".to_string(), - ]); - - for entry in dir { - let entry = entry?; - let file_type = entry.file_type()?; - let file_name = if file_type.is_dir() { - let name = entry.file_name().into_string().unwrap_or_default(); - - format!("{name}/") - } else { - entry.file_name().into_string().unwrap_or_default() - }; - let metadata = entry.path().metadata()?; - - let created_timestamp = metadata.accessed()?; - let created = Time::from(created_timestamp); - - let accessed_timestamp = metadata.accessed()?; - let accessed = Time::from(accessed_timestamp); - - let modified_timestamp = metadata.modified()?; - let modified = Time::from(modified_timestamp); - - let read_only = metadata.permissions().readonly(); - let size = metadata.len(); - - file_table.insert(vec![ - Value::String(file_name), - Value::Integer(size as i64), - Value::Time(created), - Value::Time(accessed), - Value::Time(modified), - Value::Boolean(read_only), - ])?; - } - - Ok(Value::Table(file_table)) - } -} - -pub struct ReadFile; - -impl Tool for ReadFile { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "read_file", - description: "Read file contents.", - group: "filesystem", - inputs: vec![ValueType::String], - } - } - - fn run(&self, argument: &Value) -> Result { - let path = argument.as_string()?; - let mut contents = String::new(); - - OpenOptions::new() - .read(true) - .create(false) - .open(path)? - .read_to_string(&mut contents)?; - - Ok(Value::String(contents)) - } -} - -pub struct RemoveDir; - -impl Tool for RemoveDir { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "remove_dir", - description: "Remove directories.", - group: "filesystem", - inputs: vec![ - ValueType::String, - ValueType::ListOf(Box::new(ValueType::String)), - ], - } - } - - fn run(&self, argument: &Value) -> Result { - let path = argument.as_string()?; - fs::remove_dir(path)?; - - Ok(Value::Empty) - } -} - -pub struct MoveDir; - -impl Tool for MoveDir { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "move_dir", - description: "Move a directory to a new path.", - group: "filesystem", - inputs: vec![ValueType::ListExact(vec![ - ValueType::String, - ValueType::String, - ])], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_list()?; - - Error::expect_function_argument_amount(self.info().identifier, argument.len(), 2)?; - - let current_path = argument[0].as_string()?; - let target_path = argument[1].as_string()?; - let file_list = ReadDir.run(&Value::String(current_path.clone()))?; - - for path in file_list.as_list()? { - let path = PathBuf::from(path.as_string()?); - let new_path = PathBuf::from(&target_path).join(&path); - - if path.is_file() { - fs::copy(&path, target_path)?; - } - - if path.is_symlink() && path.symlink_metadata()?.is_file() { - fs::copy(&path, new_path)?; - } - } - - Ok(Value::Empty) - } -} - -pub struct Trash; - -impl Tool for Trash { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "trash", - description: "Move a file or directory to the trash.", - group: "filesystem", - inputs: vec![ - ValueType::String, - ValueType::ListOf(Box::new(ValueType::String)), - ], - } - } - - fn run(&self, argument: &Value) -> Result { - let path = argument.as_string()?; - - trash::delete(path)?; - - Ok(Value::Empty) - } -} - -pub struct Write; - -impl Tool for Write { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "write", - description: "Write data to a file.", - group: "filesystem", - inputs: vec![ValueType::ListExact(vec![ - ValueType::String, - ValueType::Any, - ])], - } - } - - fn run(&self, argument: &Value) -> Result { - let strings = argument.as_list()?; - - Error::expect_function_argument_amount(self.info().identifier, strings.len(), 2)?; - - let path = strings.first().unwrap().as_string()?; - let mut file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(path)?; - - for content in &strings[1..] { - let content = content.to_string(); - - file.write_all(content.as_bytes())?; - } - - Ok(Value::Empty) - } -} - -pub struct RemoveFile; - -impl Tool for RemoveFile { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "remove_file", - description: "Permanently delete a file.", - group: "filesystem", - inputs: vec![ - ValueType::String, - ValueType::ListOf(Box::new(ValueType::String)), - ], - } - } - - fn run(&self, argument: &Value) -> Result { - if let Ok(path) = argument.as_string() { - fs::remove_file(path)?; - - return Ok(Value::Empty); - } - - if let Ok(path_list) = argument.as_list() { - for path in path_list { - let path = path.as_string()?; - - fs::remove_file(path)?; - } - - return Ok(Value::Empty); - } - - Err(Error::expected_string(argument.clone())) - } -} - -pub struct Watch; - -impl Tool for Watch { - fn info(&self) -> crate::ToolInfo<'static> { - crate::ToolInfo { - identifier: "watch", - description: "Wait until a file changes.", - group: "filesystem", - inputs: vec![ValueType::String], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_string()?; - let path = PathBuf::from(argument); - let modified_old = path.metadata()?.modified()?; - let wait_time = loop { - let modified_new = path.metadata()?.modified()?; - - if modified_old != modified_new { - break modified_new - .duration_since(modified_old) - .unwrap_or_default() - .as_millis() as i64; - } - }; - - Ok(Value::Integer(wait_time)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn create_dir() { - let path = PathBuf::from("./target/create_dir/"); - let path_value = Value::String(path.to_string_lossy().to_string()); - let _ = std::fs::remove_file(&path); - - CreateDir.run(&path_value).unwrap(); - - assert!(path.is_dir()); - } - - #[test] - fn create_dir_nested() { - let path = PathBuf::from("./target/create_dir/nested"); - let path_value = Value::String(path.to_string_lossy().to_string()); - let _ = std::fs::remove_file(&path); - - CreateDir.run(&path_value).unwrap(); - - assert!(path.is_dir()); - } - - #[test] - fn write() { - let path = PathBuf::from("./target/write.txt"); - let path_value = Value::String(path.to_string_lossy().to_string()); - let message = "hiya".to_string(); - let message_value = Value::String(message.clone()); - let _ = std::fs::remove_file(&path); - - Write - .run(&Value::List(vec![path_value, message_value])) - .unwrap(); - - assert!(path.is_file()); - } - - #[test] - fn append() { - let path = PathBuf::from("./target/append.txt"); - let path_value = Value::String(path.to_string_lossy().to_string()); - let message = "hiya".to_string(); - let message_value = Value::String(message.clone()); - let _ = std::fs::remove_file(&path); - - Write - .run(&Value::List(vec![ - path_value.clone(), - message_value.clone(), - ])) - .unwrap(); - Append - .run(&Value::List(vec![path_value, message_value])) - .unwrap(); - - let read = fs::read_to_string(&path).unwrap(); - - assert_eq!("hiyahiya", read); - } - - #[test] - fn read_file() { - let path = PathBuf::from("./target/read_file.txt"); - let path_value = Value::String(path.to_string_lossy().to_string()); - let message = "hiya".to_string(); - let message_value = Value::String(message.clone()); - let _ = std::fs::remove_file(&path); - - Write - .run(&Value::List(vec![path_value.clone(), message_value])) - .unwrap(); - - let test = ReadFile.run(&path_value).unwrap(); - let read = fs::read_to_string(&path).unwrap(); - - assert_eq!(test, Value::String(read)); - } - - #[test] - fn remove_file() { - let path = PathBuf::from("./target/remove_file.txt"); - let path_value = Value::String(path.to_string_lossy().to_string()); - let _ = std::fs::File::create(&path); - - RemoveFile.run(&path_value).unwrap(); - - assert!(!path.exists()); - } -} diff --git a/src/tools/general.rs b/src/tools/general.rs deleted file mode 100644 index 48fa42a..0000000 --- a/src/tools/general.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::{thread::sleep, time::Duration}; - -use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; - -use crate::{Result, Table, Tool, ToolInfo, Value, ValueType, TOOL_LIST}; - -pub struct Help; - -impl Tool for Help { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "help", - description: "Get help using dust.", - group: "general", - inputs: vec![ValueType::Empty, ValueType::String], - } - } - - fn run(&self, argument: &Value) -> Result { - self.check_type(argument)?; - - let mut table = Table::new(vec![ - "tool".to_string(), - "description".to_string(), - "group".to_string(), - "inputs".to_string(), - ]); - - for tool in TOOL_LIST { - let tool_group = tool.info().group.to_string(); - - if let Ok(group) = argument.as_string() { - if &tool_group != group { - continue; - } - } - - let row = vec![ - Value::String(tool.info().identifier.to_string()), - Value::String(tool.info().description.to_string()), - Value::String(tool_group), - Value::List( - tool.info() - .inputs - .iter() - .map(|value_type| Value::String(value_type.to_string())) - .collect(), - ), - ]; - - table.insert(row)?; - } - - Ok(Value::Table(table)) - } -} - -pub struct Output; - -impl Tool for Output { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "output", - description: "Print a value.", - group: "general", - inputs: vec![ValueType::Any], - } - } - - fn run(&self, argument: &Value) -> Result { - println!("{argument}"); - - Ok(Value::Empty) - } -} -pub struct Repeat; - -impl Tool for Repeat { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "repeat", - description: "Run a function the given number of times.", - group: "general", - inputs: vec![ValueType::ListExact(vec![ - ValueType::Function, - ValueType::Integer, - ])], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_list()?; - let function = argument[0].as_function()?; - let count = argument[1].as_int()?; - let mut result_list = Vec::with_capacity(count as usize); - - for _ in 0..count { - let result = function.run()?; - - result_list.push(result); - } - - Ok(Value::List(result_list)) - } -} - -pub struct Run; - -impl Tool for Run { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "run", - description: "Run functions in parallel.", - group: "general", - inputs: vec![ValueType::ListOf(Box::new(ValueType::Function))], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument_list = argument.as_list()?; - let results = argument_list - .par_iter() - .map(|value| { - let function = if let Ok(function) = value.as_function() { - function - } else { - return value.clone(); - }; - - match function.run() { - Ok(value) => value, - Err(error) => Value::String(error.to_string()), - } - }) - .collect(); - - Ok(Value::List(results)) - } -} - -pub struct Wait; - -impl Tool for Wait { - fn info(&self) -> crate::ToolInfo<'static> { - ToolInfo { - identifier: "wait", - description: "Wait for the given number of milliseconds.", - group: "general", - inputs: vec![ValueType::Integer], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_int()?; - - sleep(Duration::from_millis(argument as u64)); - - Ok(Value::Empty) - } -} diff --git a/src/tools/gui.rs b/src/tools/gui.rs deleted file mode 100644 index 7da4f2d..0000000 --- a/src/tools/gui.rs +++ /dev/null @@ -1,324 +0,0 @@ -use eframe::{ - egui::{ - plot::{Bar, BarChart, Line, Plot as EguiPlot, PlotPoints}, - CentralPanel, Context, Direction, Layout, RichText, ScrollArea, Ui, - }, - emath::Align, - epaint::{Color32, Stroke}, - run_native, NativeOptions, -}; -use egui_extras::{Column, StripBuilder, TableBuilder}; - -use crate::{Error, Result, Table, Tool, ToolInfo, Value, ValueType, VariableMap}; - -pub struct BarGraph; - -impl Tool for BarGraph { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "bar_graph", - description: "Render a list of values as a bar graph.", - group: "gui", - inputs: vec![ValueType::ListOf(Box::new(ValueType::List))], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_list()?; - let mut bars = Vec::new(); - - for (index, value) in argument.iter().enumerate() { - let list = value.as_list()?; - let mut name = None; - let mut height = None; - - for value in list { - match value { - Value::Float(float) => { - if height.is_none() { - height = Some(float); - } - } - Value::Integer(_integer) => {} - Value::String(string) => name = Some(string), - Value::Boolean(_) - | Value::List(_) - | Value::Map(_) - | Value::Table(_) - | Value::Time(_) - | Value::Function(_) - | Value::Empty => continue, - } - } - - let height = - match height { - Some(height) => *height, - None => return Err(Error::CustomMessage( - "Could not create bar graph. No float value was found to use as a height." - .to_string(), - )), - }; - - let bar = Bar::new(index as f64, height).name(name.unwrap_or(&"".to_string())); - - bars.push(bar); - } - - run_native( - "bar_graph", - NativeOptions::default(), - Box::new(|_cc| Box::new(BarGraphGui::new(bars))), - ) - .unwrap(); - - Ok(Value::Empty) - } -} - -struct BarGraphGui { - bars: Vec, -} - -impl BarGraphGui { - fn new(data: Vec) -> Self { - Self { bars: data } - } -} - -impl eframe::App for BarGraphGui { - fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) { - CentralPanel::default().show(ctx, |ui| { - EguiPlot::new("bar_graph").show(ui, |plot_ui| { - plot_ui.bar_chart(BarChart::new(self.bars.clone()).color(Color32::RED)); - }); - }); - } -} - -pub struct Plot; - -impl Tool for Plot { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "plot", - description: "Render a list of numbers as a scatter plot graph.", - group: "gui", - inputs: vec![ - ValueType::ListOf(Box::new(ValueType::Float)), - ValueType::ListOf(Box::new(ValueType::Integer)), - ], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_list()?; - let mut floats = Vec::new(); - - for value in argument { - if let Ok(float) = value.as_float() { - floats.push(float); - } else if let Ok(integer) = value.as_int() { - floats.push(integer as f64); - } else { - return Err(Error::expected_number(value.clone())); - } - } - - run_native( - "plot", - NativeOptions { - resizable: true, - centered: true, - ..Default::default() - }, - Box::new(|_cc| Box::new(PlotGui::new(floats))), - ) - .unwrap(); - - Ok(Value::Empty) - } -} - -struct PlotGui { - data: Vec, -} - -impl PlotGui { - fn new(data: Vec) -> Self { - Self { data } - } -} - -impl eframe::App for PlotGui { - fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) { - CentralPanel::default().show(ctx, |ui| { - EguiPlot::new("plot").show(ui, |plot_ui| { - let points = self - .data - .iter() - .enumerate() - .map(|(index, value)| [index as f64, *value]) - .collect::(); - let line = Line::new(points); - plot_ui.line(line); - }) - }); - } -} - -pub struct GuiApp { - text_edit_buffer: String, - _whale_context: VariableMap, - eval_result: Result, -} - -impl GuiApp { - pub fn new(result: Result) -> Self { - GuiApp { - text_edit_buffer: String::new(), - _whale_context: VariableMap::new(), - eval_result: result, - } - } - - fn _table_ui(&mut self, table: &Table, ui: &mut Ui) { - TableBuilder::new(ui) - .resizable(true) - .striped(true) - .columns(Column::remainder(), table.column_names().len()) - .header(30.0, |mut row| { - for name in table.column_names() { - row.col(|ui| { - ui.label(name); - }); - } - }) - .body(|body| { - body.rows(20.0, table.rows().len(), |index, mut row| { - let row_data = table.rows().get(index).unwrap(); - - for cell_data in row_data { - row.col(|ui| { - ui.label(cell_data.to_string()); - }); - } - }); - }); - } -} - -impl eframe::App for GuiApp { - fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) { - CentralPanel::default().show(ctx, |ui| { - ui.with_layout( - Layout { - main_dir: Direction::TopDown, - main_wrap: false, - main_align: Align::Center, - main_justify: false, - cross_align: Align::Center, - cross_justify: true, - }, - |ui| { - ui.text_edit_multiline(&mut self.text_edit_buffer); - ui.horizontal(|ui| { - let clear = ui.button("clear"); - let submit = ui.button("submit"); - - if clear.clicked() { - self.text_edit_buffer.clear(); - } - - if submit.clicked() { - todo!() - } - }); - }, - ); - ui.separator(); - - StripBuilder::new(ui) - .sizes(egui_extras::Size::remainder(), 1) - .vertical(|mut strip| { - strip.cell(|ui| { - let rectangle = ui.available_rect_before_wrap(); - let corner = 5.0; - let border = Stroke::new(1.0, Color32::DARK_GREEN); - let item_size = 20.0; - - ui.painter().rect_stroke(rectangle, corner, border); - - match &self.eval_result { - Ok(value) => match value { - Value::String(string) => { - ui.label(RichText::new(string).size(item_size)); - } - Value::Float(float) => { - ui.label(RichText::new(float.to_string()).size(item_size)); - } - Value::Integer(integer) => { - ui.label(RichText::new(integer.to_string()).size(item_size)); - } - Value::Boolean(boolean) => { - ui.label(RichText::new(boolean.to_string()).size(item_size)); - } - Value::List(list) => { - for value in list { - ui.label(RichText::new(value.to_string()).size(item_size)); - } - } - Value::Map(_) => todo!(), - Value::Table(table) => { - ScrollArea::both().show(ui, |ui| { - TableBuilder::new(ui) - .resizable(true) - .striped(true) - .vscroll(true) - .columns( - Column::remainder(), - table.column_names().len(), - ) - .header(20.0, |mut row| { - for name in table.column_names() { - row.col(|ui| { - ui.label(name); - }); - } - }) - .body(|body| { - body.rows( - 20.0, - table.rows().len(), - |index, mut row| { - let row_data = - table.rows().get(index).unwrap(); - - for value in row_data { - row.col(|ui| { - ui.label(value.to_string()); - }); - } - }, - ); - }); - }); - } - Value::Function(_) => todo!(), - Value::Empty => {} - Value::Time(_) => todo!(), - }, - Err(error) => { - let rectangle = ui.available_rect_before_wrap(); - let corner = 5.0; - let border = Stroke::new(1.0, Color32::DARK_RED); - - ui.painter().rect_stroke(rectangle, corner, border); - ui.label(error.to_string()); - } - } - }); - }); - }); - } -} diff --git a/src/tools/logic.rs b/src/tools/logic.rs deleted file mode 100644 index 24d64bd..0000000 --- a/src/tools/logic.rs +++ /dev/null @@ -1,162 +0,0 @@ -use crate::{Error, Tool, ToolInfo, Result, Value, ValueType }; - - -pub struct Assert; - -impl Tool for Assert { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "assert", - description: "Panic if a boolean is false.", - group: "test", - inputs: vec![ - ValueType::Boolean, - ValueType::Function - ], - } - } - - fn run(&self, argument: &Value) -> Result { - let boolean = argument.as_boolean()?; - - if boolean { - Ok(Value::Empty) - } else { - Err(Error::AssertFailed) - } - - } -} - -pub struct AssertEqual; - -impl Tool for AssertEqual { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "assert_equal", - description: "Panic if two values do not match.", - group: "test", - inputs: vec![ValueType::ListExact(vec![ValueType::Any, ValueType::Any])], - } - } - - fn run(&self, argument: &Value) -> Result { - let arguments = argument.as_fixed_len_list(2)?; - - if arguments[0] == arguments[1] { - Ok(Value::Empty) - } else { - Err(Error::AssertEqualFailed { - expected: arguments[0].clone(), - actual: arguments[1].clone() - }) - } - - } -} - -pub struct If; - -impl Tool for If { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "if", - description: "Evaluates the first argument. If true, it does the second argument.", - group: "logic", - inputs: vec![ - ValueType::ListExact(vec![ - ValueType::Boolean, - ValueType::Any, - ]), - ValueType::ListExact(vec![ - ValueType::Function, - ValueType::Any, - ])], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_fixed_len_list(2)?; - let (condition, if_true) = (&argument[0], &argument[1]); - let condition = if let Ok(function) = condition.as_function() { - function.run()?.as_boolean()? - } else { - condition.as_boolean()? - }; - - if condition { - Ok(if_true.clone()) - } else { - Ok(Value::Empty) - } - - } -} - -pub struct IfElse; - -impl Tool for IfElse { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "if_else", - description: "Evaluates the first argument. If true, it does the second argument. If false, it does the third argument", - group: "logic", - inputs: vec![ - ValueType::ListExact(vec![ - ValueType::Boolean, - ValueType::Any, - ValueType::Any, - ]), - ValueType::ListExact(vec![ - ValueType::Function, - ValueType::Any, - ValueType::Any, - ])], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_fixed_len_list(3)?; - let (condition, if_true, if_false) = (&argument[0], &argument[1], &argument[2]); - - let condition_is_true = if let Ok(boolean) = condition.as_boolean() { - boolean - } else if let Ok(function) = condition.as_function() { - function.run()?.as_boolean()? - } else { - return Err(Error::TypeError { - expected: &[ValueType::Boolean, ValueType::Function], - actual: condition.clone(), - }); - }; - - let should_yield = if condition_is_true { if_true } else { if_false }; - - if let Ok(function) = should_yield.as_function() { - function.run() - } else { - Ok(should_yield.clone()) - } - } -} - -pub struct Loop; - -impl Tool for Loop { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "loop", - description: "Repeats a function until the program ends.", - group: "general", - inputs: vec![ValueType::Function], - } - } - - fn run(&self, argument: &Value) -> Result { - let function = argument.as_function()?; - - function.run()?; - - Loop.run(argument) - } -} diff --git a/src/tools/mod.rs b/src/tools/mod.rs deleted file mode 100644 index 17ee210..0000000 --- a/src/tools/mod.rs +++ /dev/null @@ -1,377 +0,0 @@ -//! Dust's built-in commands. -//! -//! When a tool in invoked in Dust, the input is checked against the inputs listed in its ToolInfo. -//! The input should then be double-checked by `Tool::check_input` when you implement `run`. The -//! purpose of the second check is to weed out mistakes in how the inputs were described in the -//! ToolInfo. The errors from the second check should only come up during development and should not //! be seen by the user. -//! -//! ## Writing macros -//! -//! - Snake case identifier, this is enforced by a test -//! - The description should be brief, it will display in the shell -//! - Recycle code that is already written and tested -//! - Write non-trivial tests, do not write tests just for the sake of writing them -//! -//! ## Usage -//! -//! Commands can be used in Rust by passing a Value to the run method. -//! -//! ``` -//! # use dust_lib::{tools::collections::Count, Tool, Value}; -//! let value = Value::List(vec![ -//! Value::Integer(1), -//! Value::Integer(2), -//! Value::Integer(3), -//! ]); -//! let count = Count -//! .run(&value) -//! .unwrap() -//! .as_int() -//! .unwrap(); -//! -//! assert_eq!(count, 3); -//! ``` - -use crate::{Result, Value, ValueType}; - -pub mod collections; -pub mod command; -pub mod data_formats; -pub mod disks; -pub mod filesystem; -pub mod general; -pub mod gui; -pub mod logic; -pub mod network; -pub mod random; -pub mod system; -pub mod time; - -/// Master list of all tools. -/// -/// This list is used to match identifiers with tools and to provide info to the shell. -pub const TOOL_LIST: [&'static dyn Tool; 52] = [ - &collections::Count, - &collections::CreateTable, - &collections::Insert, - &collections::Rows, - &collections::Select, - &collections::String, - &collections::Sort, - &collections::Replace, - &collections::Transform, - &collections::Where, - &command::Bash, - &command::Fish, - &command::Raw, - &command::Sh, - &command::Zsh, - &data_formats::FromCsv, - &data_formats::ToCsv, - &data_formats::FromJson, - &data_formats::ToJson, - &disks::ListDisks, - &disks::Partition, - &filesystem::Append, - &filesystem::CreateDir, - &filesystem::FileMetadata, - &filesystem::MoveDir, - &filesystem::ReadDir, - &filesystem::ReadFile, - &filesystem::RemoveDir, - &filesystem::Trash, - &filesystem::Watch, - &filesystem::Write, - &general::Help, - &general::Run, - &general::Output, - &general::Repeat, - &general::Wait, - &gui::BarGraph, - &gui::Plot, - &logic::If, - &logic::IfElse, - &logic::Loop, - &network::Download, - &random::Random, - &random::RandomBoolean, - &random::RandomFloat, - &random::RandomInteger, - &random::RandomString, - &system::Users, - &logic::Assert, - &logic::AssertEqual, - &time::Local, - &time::Now, -]; - -/// A whale macro function. -pub trait Tool: Sync + Send { - fn info(&self) -> ToolInfo<'static>; - fn run(&self, argument: &Value) -> Result; - - fn check_type<'a>(&self, argument: &'a Value) -> Result<&'a Value> { - if self - .info() - .inputs - .iter() - .any(|value_type| &argument.value_type() == value_type) - { - Ok(argument) - } else { - Err(crate::Error::TypeCheckFailure { - tool_info: self.info(), - argument: argument.clone(), - }) - } - } - - fn fail(&self, argument: &Value) -> Result { - Err(crate::Error::TypeCheckFailure { - tool_info: self.info(), - argument: argument.clone(), - }) - } -} - -/// Information needed for each macro. -#[derive(Clone, Debug, PartialEq)] -pub struct ToolInfo<'a> { - /// Text pattern that triggers this macro. - pub identifier: &'a str, - - /// User-facing information about how the macro works. - pub description: &'a str, - - /// Category used to sort macros in the shell. - pub group: &'a str, - - pub inputs: Vec, -} - -// pub struct SystemInfo; - -// impl Macro for SystemInfo { -// fn info(&self) -> MacroInfo<'static> { -// MacroInfo { -// identifier: "system_info", -// description: "Get information on the system.", -// } -// } - -// fn run(&self, argument: &Value) -> crate::Result { -// argument.as_empty()?; - -// let mut map = VariableMap::new(); - -// map.set_value("hostname", Value::String(hostname()?))?; - -// Ok(Value::Map(map)) -// } -// } - -// pub struct Map; - -// impl Macro for Map { -// fn info(&self) -> MacroInfo<'static> { -// MacroInfo { -// identifier: "map", -// description: "Create a map from a value.", -// } -// } - -// fn run(&self, argument: &Value) -> Result { -// match argument { -// Value::String(_) => todo!(), -// Value::Float(_) => todo!(), -// Value::Integer(_) => todo!(), -// Value::Boolean(_) => todo!(), -// Value::List(_) => todo!(), -// Value::Map(_) => todo!(), -// Value::Table(table) => Ok(Value::Map(VariableMap::from(table))), -// Value::Function(_) => todo!(), -// Value::Empty => todo!(), -// } -// } -// } - -// pub struct Status; - -// impl Macro for Status { -// fn info(&self) -> MacroInfo<'static> { -// MacroInfo { -// identifier: "git_status", -// description: "Get the repository status for the current directory.", -// } -// } - -// fn run(&self, argument: &Value) -> Result { -// argument.as_empty()?; - -// let repo = Repository::open(".")?; -// let mut table = Table::new(vec![ -// "path".to_string(), -// "status".to_string(), -// "staged".to_string(), -// ]); - -// for entry in repo.statuses(None)?.into_iter() { -// let (status, staged) = { -// if entry.status().is_wt_new() { -// ("created".to_string(), false) -// } else if entry.status().is_wt_deleted() { -// ("deleted".to_string(), false) -// } else if entry.status().is_wt_modified() { -// ("modified".to_string(), false) -// } else if entry.status().is_index_new() { -// ("created".to_string(), true) -// } else if entry.status().is_index_deleted() { -// ("deleted".to_string(), true) -// } else if entry.status().is_index_modified() { -// ("modified".to_string(), true) -// } else if entry.status().is_ignored() { -// continue; -// } else { -// ("".to_string(), false) -// } -// }; -// let path = entry.path().unwrap().to_string(); - -// table.insert(vec![ -// Value::String(path), -// Value::String(status), -// Value::Boolean(staged), -// ])?; -// } - -// Ok(Value::Table(table)) -// } -// } - -// pub struct DocumentConvert; - -// impl Macro for DocumentConvert { -// fn info(&self) -> MacroInfo<'static> { -// MacroInfo { -// identifier: "convert_document", -// description: "Convert a file's contents to a format and set the extension.", -// } -// } - -// fn run(&self, argument: &Value) -> Result { -// let argument = argument.as_list()?; - -// if argument.len() != 3 { -// return Err(Error::WrongFunctionArgumentAmount { -// expected: 3, -// actual: argument.len(), -// }); -// } - -// let (path, from, to) = ( -// argument[0].as_string()?, -// argument[1].as_string()?, -// argument[2].as_string()?, -// ); -// let mut file_name = PathBuf::from(&path); -// file_name.set_extension(to); -// let new_file_name = file_name.to_str().unwrap(); -// let script = format!("pandoc --from {from} --to {to} --output {new_file_name} {path}"); - -// Command::new("fish").arg("-c").arg(script).spawn()?.wait()?; - -// Ok(Value::Empty) -// } -// } - -// pub struct Trash; - -// impl Macro for Trash { -// fn info(&self) -> MacroInfo<'static> { -// MacroInfo { -// identifier: "trash_dir", -// description: "Move a directory to the trash.", -// } -// } - -// fn run(&self, argument: &Value) -> Result { -// let path = argument.as_string()?; - -// trash::delete(path)?; - -// Ok(Value::Empty) -// } -// } - -// pub struct Get; - -// impl Macro for Get { -// fn info(&self) -> MacroInfo<'static> { -// MacroInfo { -// identifier: "get", -// description: "Extract a value from a collection.", -// } -// } - -// fn run(&self, argument: &Value) -> Result { -// let argument_list = argument.as_list()?; -// let collection = &argument_list[0]; -// let index = &argument_list[1]; - -// if let Ok(list) = collection.as_list() { -// let index = index.as_int()?; -// let value = list.get(index as usize).unwrap_or(&Value::Empty); - -// return Ok(value.clone()); -// } - -// if let Ok(table) = collection.as_table() { -// let index = index.as_int()?; -// let get_row = table.get(index as usize); - -// if let Some(row) = get_row { -// return Ok(Value::List(row.clone())); -// } -// } - -// Err(Error::TypeError { -// expected: &[ -// ValueType::List, -// ValueType::Map, -// ValueType::Table, -// ValueType::String, -// ], -// actual: collection.clone(), -// }) -// } -// } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn tool_identifier_formatting() { - for function in TOOL_LIST { - let identifier = function.info().identifier; - - assert_eq!(identifier.to_lowercase(), identifier); - assert!(identifier.is_ascii()); - assert!(!identifier.is_empty()); - assert!(!identifier.contains(' ')); - assert!(!identifier.contains(':')); - assert!(!identifier.contains('.')); - assert!(!identifier.contains('-')); - } - } - - #[test] - fn tool_inputs_exist() { - for function in TOOL_LIST { - let identifier = function.info().identifier; - let input_count = function.info().inputs.len(); - - assert!(input_count > 0, "{} has no inputs declared", identifier); - } - } -} diff --git a/src/tools/network.rs b/src/tools/network.rs deleted file mode 100644 index eae0b30..0000000 --- a/src/tools/network.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! Macros for network access. - -use crate::{Result, Tool, ToolInfo, Value, ValueType}; - -pub struct Download; - -impl Tool for Download { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "download", - description: "Fetch a network resource.", - group: "network", - inputs: vec![ValueType::String], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = argument.as_string()?; - let output = reqwest::blocking::get(argument)?.text()?; - - Ok(Value::String(output)) - } -} diff --git a/src/tools/random.rs b/src/tools/random.rs deleted file mode 100644 index 67176c4..0000000 --- a/src/tools/random.rs +++ /dev/null @@ -1,161 +0,0 @@ -use std::convert::TryInto; - -use rand::{random, thread_rng, Rng}; - -use crate::{Error, Result, Tool, ToolInfo, Value, ValueType}; - -pub struct RandomBoolean; - -impl Tool for RandomBoolean { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "random_boolean", - description: "Create a random boolean.", - group: "random", - inputs: vec![ValueType::Empty], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = self.check_type(argument)?; - - if let Value::Empty = argument { - let boolean = rand::thread_rng().gen(); - - Ok(Value::Boolean(boolean)) - } else { - self.fail(argument) - } - } -} - -pub struct RandomInteger; - -impl Tool for RandomInteger { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "random_integer", - description: "Create a random integer.", - group: "random", - inputs: vec![ - ValueType::Empty, - ValueType::Integer, - ValueType::ListExact(vec![ValueType::Integer, ValueType::Integer]), - ], - } - } - - fn run(&self, argument: &Value) -> Result { - match argument { - Value::Integer(max) => { - let integer = rand::thread_rng().gen_range(0..*max); - - Ok(Value::Integer(integer)) - } - Value::List(min_max) => { - Error::expect_function_argument_amount(self.info().identifier, min_max.len(), 2)?; - - let min = min_max[0].as_int()?; - let max = min_max[1].as_int()? + 1; - let integer = rand::thread_rng().gen_range(min..max); - - Ok(Value::Integer(integer)) - } - Value::Empty => Ok(crate::Value::Integer(random())), - _ => self.fail(argument), - } - } -} - -pub struct RandomString; - -impl Tool for RandomString { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "random_string", - description: "Generate a random string.", - group: "random", - inputs: vec![ValueType::Empty, ValueType::Integer], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = self.check_type(argument)?; - - if let Value::Integer(length) = argument { - let length: usize = length.unsigned_abs().try_into().unwrap_or(0); - let mut random = String::with_capacity(length); - - for _ in 0..length { - let random_char = thread_rng().gen_range('A'..='z').to_string(); - - random.push_str(&random_char); - } - - return Ok(Value::String(random)); - } - - if let Value::Empty = argument { - let mut random = String::with_capacity(10); - - for _ in 0..10 { - let random_char = thread_rng().gen_range('A'..='z').to_string(); - - random.push_str(&random_char); - } - - return Ok(Value::String(random)); - } - - self.fail(argument) - } -} - -pub struct RandomFloat; - -impl Tool for RandomFloat { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "random_float", - description: "Generate a random floating point value between 0 and 1.", - group: "random", - inputs: vec![ValueType::Empty], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = self.check_type(argument)?; - - if argument.is_empty() { - Ok(Value::Float(random())) - } else { - self.fail(argument) - } - } -} - -pub struct Random; - -impl Tool for Random { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "random", - description: "Select a random item from a list.", - group: "random", - inputs: vec![ValueType::List], - } - } - - fn run(&self, argument: &Value) -> Result { - let argument = self.check_type(argument)?; - - if let Value::List(list) = argument { - let random_index = thread_rng().gen_range(0..list.len()); - let random_item = list.get(random_index).unwrap(); - - Ok(random_item.clone()) - } else { - self.fail(argument) - } - } -} diff --git a/src/tools/system.rs b/src/tools/system.rs deleted file mode 100644 index 5838bb5..0000000 --- a/src/tools/system.rs +++ /dev/null @@ -1,28 +0,0 @@ -use sysinfo::{RefreshKind, System, SystemExt, UserExt}; - -use crate::{Result, Tool, ToolInfo, Value, ValueType}; - -pub struct Users; - -impl Tool for Users { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "users", - description: "Get a list of the system's users.", - group: "system", - inputs: vec![ValueType::Empty], - } - } - - fn run(&self, argument: &Value) -> Result { - argument.as_empty()?; - - let users = System::new_with_specifics(RefreshKind::new().with_users_list()) - .users() - .iter() - .map(|user| Value::String(user.name().to_string())) - .collect(); - - Ok(Value::List(users)) - } -} diff --git a/src/tools/time.rs b/src/tools/time.rs deleted file mode 100644 index 200a5aa..0000000 --- a/src/tools/time.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::time::Instant; - -use crate::{Result, Time, Tool, ToolInfo, Value, ValueType}; - -pub struct Now; - -impl Tool for Now { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "now", - description: "Return the current time.", - group: "time", - inputs: vec![ValueType::Empty], - } - } - - fn run(&self, argument: &crate::Value) -> Result { - argument.as_empty()?; - - let time = Time::monotonic(Instant::now()); - - Ok(Value::Time(time)) - } -} - -pub struct Local; - -impl Tool for Local { - fn info(&self) -> ToolInfo<'static> { - ToolInfo { - identifier: "local", - description: "Show a time value adjusted for the current time zone.", - group: "time", - inputs: vec![ValueType::Time], - } - } - - fn run(&self, argument: &crate::Value) -> Result { - let argument = argument.as_time()?; - - Ok(Value::String(argument.as_local())) - } -} diff --git a/src/value/function.rs b/src/value/function.rs index 66c3fbf..0da5543 100644 --- a/src/value/function.rs +++ b/src/value/function.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Display, Formatter}; use serde::{Deserialize, Serialize}; -use crate::{EvaluatorTree, Identifier, Result, Statement, Value, VariableMap}; +use crate::{Identifier, Statement}; #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)] pub struct Function { @@ -23,7 +23,7 @@ impl Display for Function { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!( f, - "function < {:?} > {{ {:?} }}", + "function < {:?} > {{ {:?} }}", // TODO: Correct this output self.identifiers, self.statements ) } diff --git a/src/value/mod.rs b/src/value/mod.rs index 83118fb..1df8893 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -1,7 +1,7 @@ //! Types that represent runtime values. use crate::{ error::{Error, Result}, - EvaluatorTree, Function, Identifier, Statement, Table, Time, ValueType, VariableMap, + AbstractTree, Function, Identifier, Statement, Table, Time, ValueType, VariableMap, }; use json::JsonValue; diff --git a/src/value/variable_map.rs b/src/value/variable_map.rs index 28f07b8..27c6ca6 100644 --- a/src/value/variable_map.rs +++ b/src/value/variable_map.rs @@ -4,7 +4,7 @@ use std::{ fmt::{self, Display, Formatter}, }; -use crate::{value::Value, Error, Result, Table}; +use crate::{value::Value, AbstractTree, Error, Result, Table}; /// A collection dust variables comprised of key-value pairs. /// @@ -23,42 +23,6 @@ impl VariableMap { } } - /// Invokes built-in tools or user-defined functions based on the identifier and passes the - /// argument. Returns an error a tool is called with the wrong inputs or if the identifier does - /// not match any tools or functions. - pub fn call_function(&self, identifier: &str, argument: &Value) -> Result { - // for macro_item in TOOL_LIST { - // let valid_input_types = macro_item.info().inputs; - - // if identifier == macro_item.info().identifier { - // let input_type = argument.value_type(); - - // if valid_input_types.contains(&input_type) { - // return macro_item.run(argument); - // } else { - // return Err(Error::MacroArgumentType { - // macro_info: macro_item.info(), - // actual: argument.clone(), - // }); - // } - // } - // } - - // for (key, value) in &self.variables { - // if identifier == key { - // if let Ok(function) = value.as_function() { - // let mut context = self.clone(); - - // context.set_value("input".to_string(), argument.clone())?; - - // return function.run_with_context(&mut context); - // } - // } - // } - - Err(Error::FunctionIdentifierNotFound(identifier.to_string())) - } - /// Returns a Value assigned to the identifer, allowing dot notation to retrieve Values that are /// nested in Lists or Maps. Returns None if there is no variable with a key matching the /// identifier. Returns an error if a Map or List is indexed incorrectly. pub fn get_value(&self, identifier: &str) -> Result> { let split = identifier.rsplit_once('.'); diff --git a/tests/dust_examples.rs b/tests/dust_examples.rs index 0daea19..32d9d71 100644 --- a/tests/dust_examples.rs +++ b/tests/dust_examples.rs @@ -6,7 +6,7 @@ use dust::*; fn collections() { let file_contents = read_to_string("examples/collections.ds").unwrap(); - for result in eval(&file_contents) { + for result in evaluate(&file_contents) { result.unwrap(); } } @@ -15,7 +15,7 @@ fn collections() { fn list() { let file_contents = read_to_string("examples/list.ds").unwrap(); - for result in eval(&file_contents) { + for result in evaluate(&file_contents) { result.unwrap(); } } @@ -24,7 +24,7 @@ fn list() { fn table() { let file_contents = read_to_string("examples/table.ds").unwrap(); - for result in eval(&file_contents) { + for result in evaluate(&file_contents) { result.unwrap(); } } @@ -33,7 +33,7 @@ fn table() { fn variables() { let file_contents = read_to_string("examples/variables.ds").unwrap(); - for result in eval(&file_contents) { + for result in evaluate(&file_contents) { result.unwrap(); } } @@ -42,7 +42,7 @@ fn variables() { fn scope() { let file_contents = read_to_string("examples/scope.ds").unwrap(); - for result in eval(&file_contents) { + for result in evaluate(&file_contents) { result.unwrap(); } } @@ -51,7 +51,7 @@ fn scope() { fn data_formats() { let file_contents = read_to_string("examples/data_formats.ds").unwrap(); - for result in eval(&file_contents) { + for result in evaluate(&file_contents) { result.unwrap(); } } diff --git a/tree-sitter-dust b/tree-sitter-dust index 7398f07..03c5664 160000 --- a/tree-sitter-dust +++ b/tree-sitter-dust @@ -1 +1 @@ -Subproject commit 7398f073ed8c844c442f21b0fd62579b14f36dd3 +Subproject commit 03c566485155076983ff78689c62839c59b47327