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
|
//! 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.
|
//! 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};
|
use std::{fmt, io, time::SystemTimeError};
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ pub enum Error {
|
|||||||
VariableIdentifierNotFound(String),
|
VariableIdentifierNotFound(String),
|
||||||
|
|
||||||
/// A `FunctionIdentifier` operation did not find its value in the context.
|
/// A `FunctionIdentifier` operation did not find its value in the context.
|
||||||
FunctionIdentifierNotFound(String),
|
FunctionIdentifierNotFound(Identifier),
|
||||||
|
|
||||||
/// A value has the wrong type.
|
/// A value has the wrong type.
|
||||||
/// Only use this if there is no other error that describes the expected and
|
/// 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.
|
//! interpreting Dust code. Most of the language's features are implemented in the [tools] module.
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
|
abstract_tree::*,
|
||||||
error::*,
|
error::*,
|
||||||
interface::*,
|
evaluator::*,
|
||||||
value::{
|
value::{
|
||||||
function::Function, table::Table, time::Time, value_type::ValueType,
|
function::Function, table::Table, time::Time, value_type::ValueType,
|
||||||
variable_map::VariableMap, Value,
|
variable_map::VariableMap, Value,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod abstract_tree;
|
||||||
mod error;
|
mod error;
|
||||||
mod interface;
|
mod evaluator;
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
use tree_sitter::Language;
|
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 serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{EvaluatorTree, Identifier, Result, Statement, Value, VariableMap};
|
use crate::{Identifier, Statement};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
pub struct Function {
|
pub struct Function {
|
||||||
@ -23,7 +23,7 @@ impl Display for Function {
|
|||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"function < {:?} > {{ {:?} }}",
|
"function < {:?} > {{ {:?} }}", // TODO: Correct this output
|
||||||
self.identifiers, self.statements
|
self.identifiers, self.statements
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Types that represent runtime values.
|
//! Types that represent runtime values.
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{Error, Result},
|
error::{Error, Result},
|
||||||
EvaluatorTree, Function, Identifier, Statement, Table, Time, ValueType, VariableMap,
|
AbstractTree, Function, Identifier, Statement, Table, Time, ValueType, VariableMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
use json::JsonValue;
|
use json::JsonValue;
|
||||||
|
@ -4,7 +4,7 @@ use std::{
|
|||||||
fmt::{self, Display, Formatter},
|
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.
|
/// 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.
|
/// 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>> {
|
pub fn get_value(&self, identifier: &str) -> Result<Option<Value>> {
|
||||||
let split = identifier.rsplit_once('.');
|
let split = identifier.rsplit_once('.');
|
||||||
|
@ -6,7 +6,7 @@ use dust::*;
|
|||||||
fn collections() {
|
fn collections() {
|
||||||
let file_contents = read_to_string("examples/collections.ds").unwrap();
|
let file_contents = read_to_string("examples/collections.ds").unwrap();
|
||||||
|
|
||||||
for result in eval(&file_contents) {
|
for result in evaluate(&file_contents) {
|
||||||
result.unwrap();
|
result.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -15,7 +15,7 @@ fn collections() {
|
|||||||
fn list() {
|
fn list() {
|
||||||
let file_contents = read_to_string("examples/list.ds").unwrap();
|
let file_contents = read_to_string("examples/list.ds").unwrap();
|
||||||
|
|
||||||
for result in eval(&file_contents) {
|
for result in evaluate(&file_contents) {
|
||||||
result.unwrap();
|
result.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -24,7 +24,7 @@ fn list() {
|
|||||||
fn table() {
|
fn table() {
|
||||||
let file_contents = read_to_string("examples/table.ds").unwrap();
|
let file_contents = read_to_string("examples/table.ds").unwrap();
|
||||||
|
|
||||||
for result in eval(&file_contents) {
|
for result in evaluate(&file_contents) {
|
||||||
result.unwrap();
|
result.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,7 +33,7 @@ fn table() {
|
|||||||
fn variables() {
|
fn variables() {
|
||||||
let file_contents = read_to_string("examples/variables.ds").unwrap();
|
let file_contents = read_to_string("examples/variables.ds").unwrap();
|
||||||
|
|
||||||
for result in eval(&file_contents) {
|
for result in evaluate(&file_contents) {
|
||||||
result.unwrap();
|
result.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ fn variables() {
|
|||||||
fn scope() {
|
fn scope() {
|
||||||
let file_contents = read_to_string("examples/scope.ds").unwrap();
|
let file_contents = read_to_string("examples/scope.ds").unwrap();
|
||||||
|
|
||||||
for result in eval(&file_contents) {
|
for result in evaluate(&file_contents) {
|
||||||
result.unwrap();
|
result.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ fn scope() {
|
|||||||
fn data_formats() {
|
fn data_formats() {
|
||||||
let file_contents = read_to_string("examples/data_formats.ds").unwrap();
|
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();
|
result.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 7398f073ed8c844c442f21b0fd62579b14f36dd3
|
Subproject commit 03c566485155076983ff78689c62839c59b47327
|
Loading…
Reference in New Issue
Block a user