Continue syntax overhaul
This commit is contained in:
parent
059e55c7aa
commit
3e87d8b322
36
src/abstract_tree/assignment.rs
Normal file
36
src/abstract_tree/assignment.rs
Normal file
@ -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<Self> {
|
||||
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<Value> {
|
||||
let key = self.identifier.clone().take_inner();
|
||||
let value = self.statement.run(context)?;
|
||||
|
||||
context.set_value(key, value)?;
|
||||
|
||||
Ok(Value::Empty)
|
||||
}
|
||||
}
|
58
src/abstract_tree/expression.rs
Normal file
58
src/abstract_tree/expression.rs
Normal file
@ -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<Math>),
|
||||
Logic(Box<Logic>),
|
||||
FunctionCall(FunctionCall),
|
||||
ToolCall(Box<ToolCall>),
|
||||
}
|
||||
|
||||
impl AbstractTree for Expression {
|
||||
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
|
||||
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<Value> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
50
src/abstract_tree/function_call.rs
Normal file
50
src/abstract_tree/function_call.rs
Normal file
@ -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<Expression>,
|
||||
}
|
||||
|
||||
impl AbstractTree for FunctionCall {
|
||||
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
|
||||
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<Value> {
|
||||
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))
|
||||
}
|
||||
}
|
35
src/abstract_tree/identifier.rs
Normal file
35
src/abstract_tree/identifier.rs
Normal file
@ -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<Self> {
|
||||
let identifier = &source[node.byte_range()];
|
||||
|
||||
Ok(Identifier(identifier.to_string()))
|
||||
}
|
||||
|
||||
fn run(&self, context: &mut VariableMap) -> Result<Value> {
|
||||
let value = context.get_value(&self.0)?.unwrap_or_default();
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
48
src/abstract_tree/if_else.rs
Normal file
48
src/abstract_tree/if_else.rs
Normal file
@ -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<Statement>,
|
||||
}
|
||||
|
||||
impl AbstractTree for IfElse {
|
||||
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
|
||||
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<Value> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
45
src/abstract_tree/item.rs
Normal file
45
src/abstract_tree/item.rs
Normal file
@ -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<Self> {
|
||||
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<Value> {
|
||||
match self {
|
||||
Item::Comment(text) => Ok(Value::String(text.clone())),
|
||||
Item::Statement(statement) => statement.run(context),
|
||||
}
|
||||
}
|
||||
}
|
61
src/abstract_tree/logic.rs
Normal file
61
src/abstract_tree/logic.rs
Normal file
@ -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<Self> {
|
||||
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<Value> {
|
||||
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,
|
||||
}
|
22
src/abstract_tree/match.rs
Normal file
22
src/abstract_tree/match.rs
Normal file
@ -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<Self> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn run(&self, context: &mut VariableMap) -> Result<Value> {
|
||||
todo!()
|
||||
}
|
||||
}
|
81
src/abstract_tree/math.rs
Normal file
81
src/abstract_tree/math.rs
Normal file
@ -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<Self> {
|
||||
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<Value> {
|
||||
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,
|
||||
}
|
38
src/abstract_tree/mod.rs
Normal file
38
src/abstract_tree/mod.rs
Normal file
@ -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<Self>;
|
||||
|
||||
/// Execute dust code by traversing the tree
|
||||
fn run(&self, context: &mut VariableMap) -> Result<Value>;
|
||||
}
|
59
src/abstract_tree/statement.rs
Normal file
59
src/abstract_tree/statement.rs
Normal file
@ -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<Assignment>),
|
||||
Expression(Expression),
|
||||
IfElse(Box<IfElse>),
|
||||
Match(Match),
|
||||
Tool(ToolCall),
|
||||
}
|
||||
|
||||
impl AbstractTree for Statement {
|
||||
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
|
||||
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<Value> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
43
src/abstract_tree/tool.rs
Normal file
43
src/abstract_tree/tool.rs
Normal file
@ -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<Self> {
|
||||
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<crate::Value> {
|
||||
match self {
|
||||
ToolCall::Output(expression) => {
|
||||
let value = expression.run(context)?;
|
||||
|
||||
println!("{value}")
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Empty)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
314
src/evaluator.rs
Normal file
314
src/evaluator.rs
Normal file
@ -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<Result<Value>> {
|
||||
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<Result<Value>> {
|
||||
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<Result<Value>> {
|
||||
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 <messages, numbers> {
|
||||
['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> { 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)]);
|
||||
}
|
||||
}
|
720
src/interface.rs
720
src/interface.rs
@ -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<Result<Value>> {
|
||||
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<Result<Value>> {
|
||||
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<Self>;
|
||||
|
||||
/// Execute dust code by traversing the tree
|
||||
fn run(&self, context: &mut VariableMap) -> Result<Value>;
|
||||
}
|
||||
|
||||
/// 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<Result<Value>> {
|
||||
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<Self> {
|
||||
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<Value> {
|
||||
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<Assignment>),
|
||||
Expression(Expression),
|
||||
IfElse(Box<IfElse>),
|
||||
Match(Match),
|
||||
}
|
||||
|
||||
impl EvaluatorTree for Statement {
|
||||
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
|
||||
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<Value> {
|
||||
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<Math>),
|
||||
Logic(Box<Logic>),
|
||||
FunctionCall(FunctionCall),
|
||||
}
|
||||
|
||||
impl EvaluatorTree for Expression {
|
||||
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
|
||||
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<Value> {
|
||||
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<Self> {
|
||||
let identifier = &source[node.byte_range()];
|
||||
|
||||
Ok(Identifier(identifier.to_string()))
|
||||
}
|
||||
|
||||
fn run(&self, context: &mut VariableMap) -> Result<Value> {
|
||||
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<Statement>,
|
||||
}
|
||||
|
||||
impl EvaluatorTree for IfElse {
|
||||
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
|
||||
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<Value> {
|
||||
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<Self> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn run(&self, context: &mut VariableMap) -> Result<Value> {
|
||||
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<Self> {
|
||||
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<Value> {
|
||||
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<Self> {
|
||||
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<Value> {
|
||||
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<Self> {
|
||||
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<Value> {
|
||||
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<Expression>,
|
||||
}
|
||||
|
||||
impl EvaluatorTree for FunctionCall {
|
||||
fn from_syntax_node(node: Node, source: &str) -> Result<Self> {
|
||||
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<Value> {
|
||||
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 <messages, numbers> {
|
||||
['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> { output }"),
|
||||
vec![Ok(Value::Function(function))]
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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))
|
||||
}
|
||||
}
|
@ -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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
let argument = argument.as_string()?;
|
||||
|
||||
Command::new(argument).spawn()?.wait()?;
|
||||
|
||||
Ok(Value::Empty)
|
||||
}
|
||||
}
|
@ -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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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::<i64>() {
|
||||
Value::Integer(integer)
|
||||
} else if let Ok(float) = column.parse::<f64>() {
|
||||
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<Value> {
|
||||
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(),
|
||||
))
|
||||
}
|
||||
}
|
@ -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<Value> {
|
||||
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<Value> {
|
||||
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)
|
||||
}
|
||||
}
|
@ -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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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());
|
||||
}
|
||||
}
|
@ -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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
let argument = argument.as_int()?;
|
||||
|
||||
sleep(Duration::from_millis(argument as u64));
|
||||
|
||||
Ok(Value::Empty)
|
||||
}
|
||||
}
|
324
src/tools/gui.rs
324
src/tools/gui.rs
@ -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<Value> {
|
||||
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<Bar>,
|
||||
}
|
||||
|
||||
impl BarGraphGui {
|
||||
fn new(data: Vec<Bar>) -> 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<Value> {
|
||||
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<f64>,
|
||||
}
|
||||
|
||||
impl PlotGui {
|
||||
fn new(data: Vec<f64>) -> 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::<PlotPoints>();
|
||||
let line = Line::new(points);
|
||||
plot_ui.line(line);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GuiApp {
|
||||
text_edit_buffer: String,
|
||||
_whale_context: VariableMap,
|
||||
eval_result: Result<Value>,
|
||||
}
|
||||
|
||||
impl GuiApp {
|
||||
pub fn new(result: Result<Value>) -> 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());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
let function = argument.as_function()?;
|
||||
|
||||
function.run()?;
|
||||
|
||||
Loop.run(argument)
|
||||
}
|
||||
}
|
377
src/tools/mod.rs
377
src/tools/mod.rs
@ -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<Value>;
|
||||
|
||||
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<Value> {
|
||||
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<ValueType>,
|
||||
}
|
||||
|
||||
// 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<Value> {
|
||||
// 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<Value> {
|
||||
// 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<Value> {
|
||||
// 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<Value> {
|
||||
// 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<Value> {
|
||||
// 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<Value> {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Value> {
|
||||
let argument = argument.as_string()?;
|
||||
let output = reqwest::blocking::get(argument)?.text()?;
|
||||
|
||||
Ok(Value::String(output))
|
||||
}
|
||||
}
|
@ -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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Value> {
|
||||
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))
|
||||
}
|
||||
}
|
@ -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<Value> {
|
||||
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<Value> {
|
||||
let argument = argument.as_time()?;
|
||||
|
||||
Ok(Value::String(argument.as_local()))
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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<Value> {
|
||||
// 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<Option<Value>> {
|
||||
let split = identifier.rsplit_once('.');
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 7398f073ed8c844c442f21b0fd62579b14f36dd3
|
||||
Subproject commit 03c566485155076983ff78689c62839c59b47327
|
Loading…
Reference in New Issue
Block a user