Evaluate arbitrary functions
This commit is contained in:
parent
22d0d2c3d0
commit
879f1fcd22
@ -28,8 +28,11 @@ pub enum Error {
|
||||
/// Tried to append a child to a node such that the precedence of the child is not higher.
|
||||
PrecedenceViolation,
|
||||
|
||||
/// An identifier operation did not find its value in the configuration.
|
||||
IdentifierNotFound(String),
|
||||
/// A `VariableIdentifier` operation did not find its value in the configuration.
|
||||
VariableIdentifierNotFound(String),
|
||||
|
||||
/// A `FunctionIdentifier` operation did not find its value in the configuration.
|
||||
FunctionIdentifierNotFound(String),
|
||||
|
||||
/// A value has the wrong type.
|
||||
TypeError,
|
||||
|
@ -1,7 +1,24 @@
|
||||
use error::Error;
|
||||
use error::{self, Error};
|
||||
use value::Value;
|
||||
|
||||
pub struct Function {
|
||||
parameter_amount: usize,
|
||||
function: fn() -> Result<Value, Error>, // TODO continue type
|
||||
argument_amount: usize,
|
||||
function: Box<Fn(&[Value]) -> Result<Value, Error>>,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn new(
|
||||
argument_amount: usize,
|
||||
function: Box<Fn(&[Value]) -> Result<Value, Error>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
argument_amount,
|
||||
function,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(&self, arguments: &[Value]) -> Result<Value, Error> {
|
||||
error::expect_argument_amount(self.argument_amount, arguments.len())?;
|
||||
(self.function)(arguments)
|
||||
}
|
||||
}
|
||||
|
43
src/lib.rs
43
src/lib.rs
@ -30,6 +30,7 @@ mod test {
|
||||
use configuration::HashMapConfiguration;
|
||||
use error::Error;
|
||||
use eval_with_configuration;
|
||||
use Function;
|
||||
|
||||
#[test]
|
||||
fn test_unary_examples() {
|
||||
@ -39,7 +40,7 @@ mod test {
|
||||
assert_eq!(eval("false"), Ok(Value::Boolean(false)));
|
||||
assert_eq!(
|
||||
eval("blub"),
|
||||
Err(Error::IdentifierNotFound("blub".to_string()))
|
||||
Err(Error::VariableIdentifierNotFound("blub".to_string()))
|
||||
);
|
||||
assert_eq!(eval("-3"), Ok(Value::Int(-3)));
|
||||
assert_eq!(eval("-3.6"), Ok(Value::Float(-3.6)));
|
||||
@ -152,6 +153,46 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_functions() {
|
||||
let mut configuration = HashMapConfiguration::new();
|
||||
configuration.insert_function(
|
||||
"sub2".to_string(),
|
||||
Function::new(
|
||||
1,
|
||||
Box::new(|arguments| {
|
||||
if let Value::Int(int) = arguments[0] {
|
||||
Ok(Value::Int(int - 2))
|
||||
} else {
|
||||
Err(Error::expected_number(arguments[0].clone()))
|
||||
}
|
||||
}),
|
||||
),
|
||||
);
|
||||
configuration.insert_variable("five".to_string(), Value::Int(5));
|
||||
|
||||
assert_eq!(
|
||||
eval_with_configuration("sub2 5", &configuration),
|
||||
Ok(Value::Int(3))
|
||||
);
|
||||
assert_eq!(
|
||||
eval_with_configuration("sub2(5)", &configuration),
|
||||
Ok(Value::Int(3))
|
||||
);
|
||||
assert_eq!(
|
||||
eval_with_configuration("sub2 five", &configuration),
|
||||
Ok(Value::Int(3))
|
||||
);
|
||||
assert_eq!(
|
||||
eval_with_configuration("sub2(five)", &configuration),
|
||||
Ok(Value::Int(3))
|
||||
);
|
||||
assert_eq!(
|
||||
eval_with_configuration("sub2(3) + five", &configuration),
|
||||
Ok(Value::Int(6))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_errors() {
|
||||
assert_eq!(
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::{configuration::Configuration, error::*, value::Value};
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait Operator {
|
||||
pub trait Operator: Debug {
|
||||
/// Returns the precedence of the operator.
|
||||
/// A high precedence means that the operator has priority to be deeper in the tree.
|
||||
// Make this a const fn once #57563 is resolved
|
||||
@ -20,25 +21,42 @@ pub trait Operator {
|
||||
fn eval(&self, arguments: &[Value], configuration: &Configuration) -> Result<Value, Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RootNode;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Add;
|
||||
#[derive(Debug)]
|
||||
pub struct Sub;
|
||||
#[derive(Debug)]
|
||||
pub struct Neg;
|
||||
#[derive(Debug)]
|
||||
pub struct Mul;
|
||||
#[derive(Debug)]
|
||||
pub struct Div;
|
||||
#[derive(Debug)]
|
||||
pub struct Mod;
|
||||
#[derive(Debug)]
|
||||
|
||||
pub struct Eq;
|
||||
#[derive(Debug)]
|
||||
pub struct Neq;
|
||||
#[derive(Debug)]
|
||||
pub struct Gt;
|
||||
#[derive(Debug)]
|
||||
pub struct Lt;
|
||||
#[derive(Debug)]
|
||||
pub struct Geq;
|
||||
#[derive(Debug)]
|
||||
pub struct Leq;
|
||||
#[derive(Debug)]
|
||||
pub struct And;
|
||||
#[derive(Debug)]
|
||||
pub struct Or;
|
||||
#[derive(Debug)]
|
||||
pub struct Not;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Const {
|
||||
value: Value,
|
||||
}
|
||||
@ -49,11 +67,23 @@ impl Const {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Identifier {
|
||||
#[derive(Debug)]
|
||||
pub struct VariableIdentifier {
|
||||
identifier: String,
|
||||
}
|
||||
|
||||
impl Identifier {
|
||||
impl VariableIdentifier {
|
||||
pub fn new(identifier: String) -> Self {
|
||||
Self { identifier }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionIdentifier {
|
||||
identifier: String,
|
||||
}
|
||||
|
||||
impl FunctionIdentifier {
|
||||
pub fn new(identifier: String) -> Self {
|
||||
Self { identifier }
|
||||
}
|
||||
@ -466,24 +496,39 @@ impl Operator for Const {
|
||||
}
|
||||
}
|
||||
|
||||
impl Operator for Identifier {
|
||||
impl Operator for VariableIdentifier {
|
||||
fn precedence(&self) -> i32 {
|
||||
200
|
||||
}
|
||||
|
||||
fn argument_amount(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn eval(&self, _arguments: &[Value], configuration: &Configuration) -> Result<Value, Error> {
|
||||
if let Some(value) = configuration.get_value(&self.identifier).cloned() {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(Error::VariableIdentifierNotFound(self.identifier.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Operator for FunctionIdentifier {
|
||||
fn precedence(&self) -> i32 {
|
||||
190
|
||||
}
|
||||
|
||||
fn argument_amount(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn eval(&self, arguments: &[Value], configuration: &Configuration) -> Result<Value, Error> {
|
||||
if arguments.len() == 0 {
|
||||
if let Some(value) = configuration.get_value(&self.identifier).cloned() {
|
||||
Ok(value)
|
||||
if let Some(function) = configuration.get_function(&self.identifier) {
|
||||
// Function::call checks for correct argument amount
|
||||
function.call(arguments)
|
||||
} else {
|
||||
Err(Error::IdentifierNotFound(self.identifier.clone()))
|
||||
}
|
||||
} else {
|
||||
unimplemented!()
|
||||
Err(Error::FunctionIdentifierNotFound(self.identifier.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ pub enum Token {
|
||||
// Precedence
|
||||
LBrace,
|
||||
RBrace,
|
||||
Whitespace,
|
||||
|
||||
// Complex tokens
|
||||
Identifier(String),
|
||||
@ -38,6 +37,7 @@ pub enum Token {
|
||||
pub enum PartialToken {
|
||||
Token(Token),
|
||||
Literal(String),
|
||||
Whitespace,
|
||||
Eq,
|
||||
ExclamationMark,
|
||||
Gt,
|
||||
@ -67,7 +67,7 @@ fn char_to_partial_token(c: char) -> PartialToken {
|
||||
|
||||
c => {
|
||||
if c.is_whitespace() {
|
||||
PartialToken::Token(Token::Whitespace)
|
||||
PartialToken::Whitespace
|
||||
} else {
|
||||
PartialToken::Literal(c.to_string())
|
||||
}
|
||||
@ -77,7 +77,36 @@ fn char_to_partial_token(c: char) -> PartialToken {
|
||||
|
||||
impl Token {
|
||||
// Make this a const fn as soon as match gets stable (issue #57563)
|
||||
pub fn is_value(&self) -> bool {
|
||||
pub fn is_leftsided_value(&self) -> bool {
|
||||
match self {
|
||||
Token::Plus => false,
|
||||
Token::Minus => false,
|
||||
Token::Star => false,
|
||||
Token::Slash => false,
|
||||
Token::Percent => false,
|
||||
|
||||
Token::Eq => false,
|
||||
Token::Neq => false,
|
||||
Token::Gt => false,
|
||||
Token::Lt => false,
|
||||
Token::Geq => false,
|
||||
Token::Leq => false,
|
||||
Token::And => false,
|
||||
Token::Or => false,
|
||||
Token::Not => false,
|
||||
|
||||
Token::LBrace => true,
|
||||
Token::RBrace => false,
|
||||
|
||||
Token::Identifier(_) => true,
|
||||
Token::Float(_) => true,
|
||||
Token::Int(_) => true,
|
||||
Token::Boolean(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
// Make this a const fn as soon as match gets stable (issue #57563)
|
||||
pub fn is_rightsided_value(&self) -> bool {
|
||||
match self {
|
||||
Token::Plus => false,
|
||||
Token::Minus => false,
|
||||
@ -97,7 +126,6 @@ impl Token {
|
||||
|
||||
Token::LBrace => false,
|
||||
Token::RBrace => true,
|
||||
Token::Whitespace => false,
|
||||
|
||||
Token::Identifier(_) => true,
|
||||
Token::Float(_) => true,
|
||||
@ -138,57 +166,64 @@ fn resolve_literals(mut tokens: &[PartialToken]) -> Result<Vec<Token>, Error> {
|
||||
let second = tokens.get(1).cloned();
|
||||
let mut cutoff = 2;
|
||||
|
||||
result.push(match first {
|
||||
result.extend(
|
||||
match first {
|
||||
PartialToken::Token(token) => {
|
||||
cutoff = 1;
|
||||
token
|
||||
Some(token)
|
||||
}
|
||||
PartialToken::Literal(literal) => {
|
||||
cutoff = 1;
|
||||
if let Ok(number) = literal.parse::<IntType>() {
|
||||
Token::Int(number)
|
||||
Some(Token::Int(number))
|
||||
} else if let Ok(number) = literal.parse::<FloatType>() {
|
||||
Token::Float(number)
|
||||
Some(Token::Float(number))
|
||||
} else if let Ok(boolean) = literal.parse::<bool>() {
|
||||
Token::Boolean(boolean)
|
||||
Some(Token::Boolean(boolean))
|
||||
} else {
|
||||
Token::Identifier(literal.to_string())
|
||||
Some(Token::Identifier(literal.to_string()))
|
||||
}
|
||||
}
|
||||
PartialToken::Whitespace => {
|
||||
cutoff = 1;
|
||||
None
|
||||
}
|
||||
PartialToken::Eq => match second {
|
||||
Some(PartialToken::Eq) => Token::Eq,
|
||||
Some(PartialToken::Eq) => Some(Token::Eq),
|
||||
_ => return Err(Error::unmatched_partial_token(first, second)),
|
||||
},
|
||||
PartialToken::ExclamationMark => match second {
|
||||
Some(PartialToken::Eq) => Token::Eq,
|
||||
Some(PartialToken::Eq) => Some(Token::Eq),
|
||||
_ => {
|
||||
cutoff = 1;
|
||||
Token::Not
|
||||
Some(Token::Not)
|
||||
}
|
||||
},
|
||||
PartialToken::Gt => match second {
|
||||
Some(PartialToken::Eq) => Token::Geq,
|
||||
Some(PartialToken::Eq) => Some(Token::Geq),
|
||||
_ => {
|
||||
cutoff = 1;
|
||||
Token::Gt
|
||||
Some(Token::Gt)
|
||||
}
|
||||
},
|
||||
PartialToken::Lt => match second {
|
||||
Some(PartialToken::Eq) => Token::Leq,
|
||||
Some(PartialToken::Eq) => Some(Token::Leq),
|
||||
_ => {
|
||||
cutoff = 1;
|
||||
Token::Lt
|
||||
Some(Token::Lt)
|
||||
}
|
||||
},
|
||||
PartialToken::Ampersand => match second {
|
||||
Some(PartialToken::Ampersand) => Token::And,
|
||||
Some(PartialToken::Ampersand) => Some(Token::And),
|
||||
_ => return Err(Error::unmatched_partial_token(first, second)),
|
||||
},
|
||||
PartialToken::VerticalBar => match second {
|
||||
Some(PartialToken::VerticalBar) => Token::Or,
|
||||
Some(PartialToken::VerticalBar) => Some(Token::Or),
|
||||
_ => return Err(Error::unmatched_partial_token(first, second)),
|
||||
},
|
||||
});
|
||||
}
|
||||
.into_iter(),
|
||||
);
|
||||
|
||||
tokens = &tokens[cutoff..];
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::{configuration::Configuration, error::Error, operator::*, value::Value};
|
||||
use token::Token;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Node {
|
||||
children: Vec<Node>,
|
||||
operator: Box<dyn Operator>,
|
||||
@ -43,25 +44,27 @@ impl Node {
|
||||
if self.operator().is_leaf() {
|
||||
Err(Error::AppendedToLeafNode)
|
||||
} else if self.has_correct_amount_of_children() {
|
||||
if self.children.last_mut().unwrap().operator().precedence()
|
||||
if self.children.last().unwrap().operator().precedence()
|
||||
< node.operator().precedence()
|
||||
// Function call
|
||||
//|| self.children().last().unwrap()
|
||||
{
|
||||
self.children
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.insert_back_prioritized(node, false)
|
||||
} else {
|
||||
if node.operator().is_leaf() {
|
||||
return Err(Error::AppendedToLeafNode);
|
||||
}
|
||||
|
||||
let last_child = self.children.pop().unwrap();
|
||||
self.children.push(node);
|
||||
let node = self.children.last_mut().unwrap();
|
||||
|
||||
if node.operator().is_leaf() {
|
||||
Err(Error::AppendedToLeafNode)
|
||||
} else {
|
||||
node.children.push(last_child);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.children.push(node);
|
||||
Ok(())
|
||||
@ -74,13 +77,16 @@ impl Node {
|
||||
|
||||
pub fn tokens_to_operator_tree(tokens: Vec<Token>) -> Result<Node, Error> {
|
||||
let mut root = vec![Node::root_node()];
|
||||
let mut last_non_whitespace_token_is_value = false;
|
||||
let mut last_token_is_rightsided_value = false;
|
||||
let mut token_iter = tokens.iter().peekable();
|
||||
|
||||
while let Some(token) = token_iter.next().cloned() {
|
||||
let next = token_iter.peek().cloned();
|
||||
|
||||
for token in tokens {
|
||||
let node = match token.clone() {
|
||||
Token::Plus => Some(Node::new(Add)),
|
||||
Token::Minus => {
|
||||
if last_non_whitespace_token_is_value {
|
||||
if last_token_is_rightsided_value {
|
||||
Some(Node::new(Sub))
|
||||
} else {
|
||||
Some(Node::new(Neg))
|
||||
@ -111,9 +117,16 @@ pub fn tokens_to_operator_tree(tokens: Vec<Token>) -> Result<Node, Error> {
|
||||
root.pop()
|
||||
}
|
||||
}
|
||||
Token::Whitespace => None,
|
||||
|
||||
Token::Identifier(identifier) => Some(Node::new(Identifier::new(identifier))),
|
||||
Token::Identifier(identifier) => {
|
||||
let mut result = Some(Node::new(VariableIdentifier::new(identifier.clone())));
|
||||
if let Some(next) = next {
|
||||
if next.is_leftsided_value() {
|
||||
result = Some(Node::new(FunctionIdentifier::new(identifier)));
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
Token::Float(number) => Some(Node::new(Const::new(Value::Float(number)))),
|
||||
Token::Int(number) => Some(Node::new(Const::new(Value::Int(number)))),
|
||||
Token::Boolean(boolean) => Some(Node::new(Const::new(Value::Boolean(boolean)))),
|
||||
@ -127,9 +140,7 @@ pub fn tokens_to_operator_tree(tokens: Vec<Token>) -> Result<Node, Error> {
|
||||
}
|
||||
}
|
||||
|
||||
if token != Token::Whitespace {
|
||||
last_non_whitespace_token_is_value = token.is_value();
|
||||
}
|
||||
last_token_is_rightsided_value = token.is_rightsided_value();
|
||||
}
|
||||
|
||||
if root.len() > 1 {
|
||||
|
Loading…
Reference in New Issue
Block a user