Evaluate arbitrary functions

This commit is contained in:
Sebastian Schmidt 2019-03-18 19:21:07 +02:00
parent 22d0d2c3d0
commit 879f1fcd22
6 changed files with 238 additions and 86 deletions

View File

@ -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. /// Tried to append a child to a node such that the precedence of the child is not higher.
PrecedenceViolation, PrecedenceViolation,
/// An identifier operation did not find its value in the configuration. /// A `VariableIdentifier` operation did not find its value in the configuration.
IdentifierNotFound(String), VariableIdentifierNotFound(String),
/// A `FunctionIdentifier` operation did not find its value in the configuration.
FunctionIdentifierNotFound(String),
/// A value has the wrong type. /// A value has the wrong type.
TypeError, TypeError,

View File

@ -1,7 +1,24 @@
use error::Error; use error::{self, Error};
use value::Value; use value::Value;
pub struct Function { pub struct Function {
parameter_amount: usize, argument_amount: usize,
function: fn() -> Result<Value, Error>, // TODO continue type 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)
}
} }

View File

@ -30,6 +30,7 @@ mod test {
use configuration::HashMapConfiguration; use configuration::HashMapConfiguration;
use error::Error; use error::Error;
use eval_with_configuration; use eval_with_configuration;
use Function;
#[test] #[test]
fn test_unary_examples() { fn test_unary_examples() {
@ -39,7 +40,7 @@ mod test {
assert_eq!(eval("false"), Ok(Value::Boolean(false))); assert_eq!(eval("false"), Ok(Value::Boolean(false)));
assert_eq!( assert_eq!(
eval("blub"), 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"), Ok(Value::Int(-3)));
assert_eq!(eval("-3.6"), Ok(Value::Float(-3.6))); 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] #[test]
fn test_errors() { fn test_errors() {
assert_eq!( assert_eq!(

View File

@ -1,6 +1,7 @@
use crate::{configuration::Configuration, error::*, value::Value}; use crate::{configuration::Configuration, error::*, value::Value};
use std::fmt::Debug;
pub trait Operator { pub trait Operator: Debug {
/// Returns the precedence of the operator. /// Returns the precedence of the operator.
/// A high precedence means that the operator has priority to be deeper in the tree. /// A high precedence means that the operator has priority to be deeper in the tree.
// Make this a const fn once #57563 is resolved // 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>; fn eval(&self, arguments: &[Value], configuration: &Configuration) -> Result<Value, Error>;
} }
#[derive(Debug)]
pub struct RootNode; pub struct RootNode;
#[derive(Debug)]
pub struct Add; pub struct Add;
#[derive(Debug)]
pub struct Sub; pub struct Sub;
#[derive(Debug)]
pub struct Neg; pub struct Neg;
#[derive(Debug)]
pub struct Mul; pub struct Mul;
#[derive(Debug)]
pub struct Div; pub struct Div;
#[derive(Debug)]
pub struct Mod; pub struct Mod;
#[derive(Debug)]
pub struct Eq; pub struct Eq;
#[derive(Debug)]
pub struct Neq; pub struct Neq;
#[derive(Debug)]
pub struct Gt; pub struct Gt;
#[derive(Debug)]
pub struct Lt; pub struct Lt;
#[derive(Debug)]
pub struct Geq; pub struct Geq;
#[derive(Debug)]
pub struct Leq; pub struct Leq;
#[derive(Debug)]
pub struct And; pub struct And;
#[derive(Debug)]
pub struct Or; pub struct Or;
#[derive(Debug)]
pub struct Not; pub struct Not;
#[derive(Debug)]
pub struct Const { pub struct Const {
value: Value, value: Value,
} }
@ -49,11 +67,23 @@ impl Const {
} }
} }
pub struct Identifier { #[derive(Debug)]
pub struct VariableIdentifier {
identifier: String, 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 { pub fn new(identifier: String) -> Self {
Self { identifier } Self { identifier }
} }
@ -466,24 +496,39 @@ impl Operator for Const {
} }
} }
impl Operator for Identifier { impl Operator for VariableIdentifier {
fn precedence(&self) -> i32 { fn precedence(&self) -> i32 {
200 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 { fn argument_amount(&self) -> usize {
1 1
} }
fn eval(&self, arguments: &[Value], configuration: &Configuration) -> Result<Value, Error> { fn eval(&self, arguments: &[Value], configuration: &Configuration) -> Result<Value, Error> {
if arguments.len() == 0 { if let Some(function) = configuration.get_function(&self.identifier) {
if let Some(value) = configuration.get_value(&self.identifier).cloned() { // Function::call checks for correct argument amount
Ok(value) function.call(arguments)
} else { } else {
Err(Error::IdentifierNotFound(self.identifier.clone())) Err(Error::FunctionIdentifierNotFound(self.identifier.clone()))
}
} else {
unimplemented!()
} }
} }
} }

View File

@ -25,7 +25,6 @@ pub enum Token {
// Precedence // Precedence
LBrace, LBrace,
RBrace, RBrace,
Whitespace,
// Complex tokens // Complex tokens
Identifier(String), Identifier(String),
@ -38,6 +37,7 @@ pub enum Token {
pub enum PartialToken { pub enum PartialToken {
Token(Token), Token(Token),
Literal(String), Literal(String),
Whitespace,
Eq, Eq,
ExclamationMark, ExclamationMark,
Gt, Gt,
@ -67,7 +67,7 @@ fn char_to_partial_token(c: char) -> PartialToken {
c => { c => {
if c.is_whitespace() { if c.is_whitespace() {
PartialToken::Token(Token::Whitespace) PartialToken::Whitespace
} else { } else {
PartialToken::Literal(c.to_string()) PartialToken::Literal(c.to_string())
} }
@ -77,7 +77,36 @@ fn char_to_partial_token(c: char) -> PartialToken {
impl Token { impl Token {
// Make this a const fn as soon as match gets stable (issue #57563) // 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 { match self {
Token::Plus => false, Token::Plus => false,
Token::Minus => false, Token::Minus => false,
@ -97,7 +126,6 @@ impl Token {
Token::LBrace => false, Token::LBrace => false,
Token::RBrace => true, Token::RBrace => true,
Token::Whitespace => false,
Token::Identifier(_) => true, Token::Identifier(_) => true,
Token::Float(_) => 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 second = tokens.get(1).cloned();
let mut cutoff = 2; let mut cutoff = 2;
result.push(match first { result.extend(
match first {
PartialToken::Token(token) => { PartialToken::Token(token) => {
cutoff = 1; cutoff = 1;
token Some(token)
} }
PartialToken::Literal(literal) => { PartialToken::Literal(literal) => {
cutoff = 1; cutoff = 1;
if let Ok(number) = literal.parse::<IntType>() { if let Ok(number) = literal.parse::<IntType>() {
Token::Int(number) Some(Token::Int(number))
} else if let Ok(number) = literal.parse::<FloatType>() { } else if let Ok(number) = literal.parse::<FloatType>() {
Token::Float(number) Some(Token::Float(number))
} else if let Ok(boolean) = literal.parse::<bool>() { } else if let Ok(boolean) = literal.parse::<bool>() {
Token::Boolean(boolean) Some(Token::Boolean(boolean))
} else { } else {
Token::Identifier(literal.to_string()) Some(Token::Identifier(literal.to_string()))
} }
} }
PartialToken::Whitespace => {
cutoff = 1;
None
}
PartialToken::Eq => match second { PartialToken::Eq => match second {
Some(PartialToken::Eq) => Token::Eq, Some(PartialToken::Eq) => Some(Token::Eq),
_ => return Err(Error::unmatched_partial_token(first, second)), _ => return Err(Error::unmatched_partial_token(first, second)),
}, },
PartialToken::ExclamationMark => match second { PartialToken::ExclamationMark => match second {
Some(PartialToken::Eq) => Token::Eq, Some(PartialToken::Eq) => Some(Token::Eq),
_ => { _ => {
cutoff = 1; cutoff = 1;
Token::Not Some(Token::Not)
} }
}, },
PartialToken::Gt => match second { PartialToken::Gt => match second {
Some(PartialToken::Eq) => Token::Geq, Some(PartialToken::Eq) => Some(Token::Geq),
_ => { _ => {
cutoff = 1; cutoff = 1;
Token::Gt Some(Token::Gt)
} }
}, },
PartialToken::Lt => match second { PartialToken::Lt => match second {
Some(PartialToken::Eq) => Token::Leq, Some(PartialToken::Eq) => Some(Token::Leq),
_ => { _ => {
cutoff = 1; cutoff = 1;
Token::Lt Some(Token::Lt)
} }
}, },
PartialToken::Ampersand => match second { PartialToken::Ampersand => match second {
Some(PartialToken::Ampersand) => Token::And, Some(PartialToken::Ampersand) => Some(Token::And),
_ => return Err(Error::unmatched_partial_token(first, second)), _ => return Err(Error::unmatched_partial_token(first, second)),
}, },
PartialToken::VerticalBar => match second { PartialToken::VerticalBar => match second {
Some(PartialToken::VerticalBar) => Token::Or, Some(PartialToken::VerticalBar) => Some(Token::Or),
_ => return Err(Error::unmatched_partial_token(first, second)), _ => return Err(Error::unmatched_partial_token(first, second)),
}, },
}); }
.into_iter(),
);
tokens = &tokens[cutoff..]; tokens = &tokens[cutoff..];
} }

View File

@ -1,6 +1,7 @@
use crate::{configuration::Configuration, error::Error, operator::*, value::Value}; use crate::{configuration::Configuration, error::Error, operator::*, value::Value};
use token::Token; use token::Token;
#[derive(Debug)]
pub struct Node { pub struct Node {
children: Vec<Node>, children: Vec<Node>,
operator: Box<dyn Operator>, operator: Box<dyn Operator>,
@ -43,25 +44,27 @@ impl Node {
if self.operator().is_leaf() { if self.operator().is_leaf() {
Err(Error::AppendedToLeafNode) Err(Error::AppendedToLeafNode)
} else if self.has_correct_amount_of_children() { } 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() < node.operator().precedence()
// Function call
//|| self.children().last().unwrap()
{ {
self.children self.children
.last_mut() .last_mut()
.unwrap() .unwrap()
.insert_back_prioritized(node, false) .insert_back_prioritized(node, false)
} else { } else {
if node.operator().is_leaf() {
return Err(Error::AppendedToLeafNode);
}
let last_child = self.children.pop().unwrap(); let last_child = self.children.pop().unwrap();
self.children.push(node); self.children.push(node);
let node = self.children.last_mut().unwrap(); let node = self.children.last_mut().unwrap();
if node.operator().is_leaf() {
Err(Error::AppendedToLeafNode)
} else {
node.children.push(last_child); node.children.push(last_child);
Ok(()) Ok(())
} }
}
} else { } else {
self.children.push(node); self.children.push(node);
Ok(()) Ok(())
@ -74,13 +77,16 @@ impl Node {
pub fn tokens_to_operator_tree(tokens: Vec<Token>) -> Result<Node, Error> { pub fn tokens_to_operator_tree(tokens: Vec<Token>) -> Result<Node, Error> {
let mut root = vec![Node::root_node()]; 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() { let node = match token.clone() {
Token::Plus => Some(Node::new(Add)), Token::Plus => Some(Node::new(Add)),
Token::Minus => { Token::Minus => {
if last_non_whitespace_token_is_value { if last_token_is_rightsided_value {
Some(Node::new(Sub)) Some(Node::new(Sub))
} else { } else {
Some(Node::new(Neg)) Some(Node::new(Neg))
@ -111,9 +117,16 @@ pub fn tokens_to_operator_tree(tokens: Vec<Token>) -> Result<Node, Error> {
root.pop() 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::Float(number) => Some(Node::new(Const::new(Value::Float(number)))),
Token::Int(number) => Some(Node::new(Const::new(Value::Int(number)))), Token::Int(number) => Some(Node::new(Const::new(Value::Int(number)))),
Token::Boolean(boolean) => Some(Node::new(Const::new(Value::Boolean(boolean)))), 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_token_is_rightsided_value = token.is_rightsided_value();
last_non_whitespace_token_is_value = token.is_value();
}
} }
if root.len() > 1 { if root.len() > 1 {