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.
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,

View File

@ -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)
}
}

View File

@ -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!(

View File

@ -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)
} else {
Err(Error::IdentifierNotFound(self.identifier.clone()))
}
if let Some(function) = configuration.get_function(&self.identifier) {
// Function::call checks for correct argument amount
function.call(arguments)
} else {
unimplemented!()
Err(Error::FunctionIdentifierNotFound(self.identifier.clone()))
}
}
}

View File

@ -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 {
PartialToken::Token(token) => {
cutoff = 1;
token
result.extend(
match first {
PartialToken::Token(token) => {
cutoff = 1;
Some(token)
}
PartialToken::Literal(literal) => {
cutoff = 1;
if let Ok(number) = literal.parse::<IntType>() {
Some(Token::Int(number))
} else if let Ok(number) = literal.parse::<FloatType>() {
Some(Token::Float(number))
} else if let Ok(boolean) = literal.parse::<bool>() {
Some(Token::Boolean(boolean))
} else {
Some(Token::Identifier(literal.to_string()))
}
}
PartialToken::Whitespace => {
cutoff = 1;
None
}
PartialToken::Eq => match second {
Some(PartialToken::Eq) => Some(Token::Eq),
_ => return Err(Error::unmatched_partial_token(first, second)),
},
PartialToken::ExclamationMark => match second {
Some(PartialToken::Eq) => Some(Token::Eq),
_ => {
cutoff = 1;
Some(Token::Not)
}
},
PartialToken::Gt => match second {
Some(PartialToken::Eq) => Some(Token::Geq),
_ => {
cutoff = 1;
Some(Token::Gt)
}
},
PartialToken::Lt => match second {
Some(PartialToken::Eq) => Some(Token::Leq),
_ => {
cutoff = 1;
Some(Token::Lt)
}
},
PartialToken::Ampersand => match second {
Some(PartialToken::Ampersand) => Some(Token::And),
_ => return Err(Error::unmatched_partial_token(first, second)),
},
PartialToken::VerticalBar => match second {
Some(PartialToken::VerticalBar) => Some(Token::Or),
_ => return Err(Error::unmatched_partial_token(first, second)),
},
}
PartialToken::Literal(literal) => {
cutoff = 1;
if let Ok(number) = literal.parse::<IntType>() {
Token::Int(number)
} else if let Ok(number) = literal.parse::<FloatType>() {
Token::Float(number)
} else if let Ok(boolean) = literal.parse::<bool>() {
Token::Boolean(boolean)
} else {
Token::Identifier(literal.to_string())
}
}
PartialToken::Eq => match second {
Some(PartialToken::Eq) => Token::Eq,
_ => return Err(Error::unmatched_partial_token(first, second)),
},
PartialToken::ExclamationMark => match second {
Some(PartialToken::Eq) => Token::Eq,
_ => {
cutoff = 1;
Token::Not
}
},
PartialToken::Gt => match second {
Some(PartialToken::Eq) => Token::Geq,
_ => {
cutoff = 1;
Token::Gt
}
},
PartialToken::Lt => match second {
Some(PartialToken::Eq) => Token::Leq,
_ => {
cutoff = 1;
Token::Lt
}
},
PartialToken::Ampersand => match second {
Some(PartialToken::Ampersand) => Token::And,
_ => return Err(Error::unmatched_partial_token(first, second)),
},
PartialToken::VerticalBar => match second {
Some(PartialToken::VerticalBar) => Token::Or,
_ => return Err(Error::unmatched_partial_token(first, second)),
},
});
.into_iter(),
);
tokens = &tokens[cutoff..];
}

View File

@ -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,24 +44,26 @@ 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(())
}
node.children.push(last_child);
Ok(())
}
} else {
self.children.push(node);
@ -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 {