Implement operator assignments

Implements #34
This commit is contained in:
Sebastian Schmidt 2019-08-29 13:09:58 +03:00
parent b9c4b34a2f
commit b7233a3337
6 changed files with 252 additions and 20 deletions

View File

@ -25,9 +25,17 @@ impl Display for Operator {
Or => write!(f, "||"),
Not => write!(f, "!"),
Tuple => write!(f, ", "),
Assign => write!(f, " = "),
AddAssign => write!(f, " += "),
SubAssign => write!(f, " -= "),
MulAssign => write!(f, " *= "),
DivAssign => write!(f, " /= "),
ModAssign => write!(f, " %= "),
ExpAssign => write!(f, " ^= "),
AndAssign => write!(f, " &&= "),
OrAssign => write!(f, " ||= "),
Tuple => write!(f, ", "),
Chain => write!(f, "; "),
Const { value } => write!(f, "{}", value),

View File

@ -26,9 +26,17 @@ pub enum Operator {
Or,
Not,
Tuple,
Assign,
AddAssign,
SubAssign,
MulAssign,
DivAssign,
ModAssign,
ExpAssign,
AndAssign,
OrAssign,
Tuple,
Chain,
Const { value: Value },
@ -67,9 +75,10 @@ impl Operator {
Or => 70,
Not => 110,
Tuple => 40,
Assign => 50,
Assign | AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign
| AndAssign | OrAssign => 50,
Tuple => 40,
Chain => 0,
Const { value: _ } => 200,
@ -113,7 +122,8 @@ impl Operator {
use crate::operator::Operator::*;
match self {
Add | Sub | Mul | Div | Mod | Exp | Eq | Neq | Gt | Lt | Geq | Leq | And | Or
| Assign => Some(2),
| Assign | AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign
| AndAssign | OrAssign => Some(2),
Tuple | Chain => None,
Not | Neg | RootNode => Some(1),
Const { value: _ } => Some(0),
@ -420,8 +430,9 @@ impl Operator {
Ok(Value::Boolean(false))
}
},
Assign | AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign
| AndAssign | OrAssign => Err(EvalexprError::ContextNotManipulable),
Tuple => Ok(Value::Tuple(arguments.into())),
Assign => Err(EvalexprError::ContextNotManipulable),
Chain => {
if arguments.is_empty() {
return Err(EvalexprError::wrong_operator_argument_amount(0, 1));
@ -435,6 +446,8 @@ impl Operator {
Ok(value.clone())
},
VariableIdentifier { identifier } => {
expect_operator_argument_amount(arguments.len(), 0)?;
if let Some(value) = context.get_value(&identifier).cloned() {
Ok(value)
} else {
@ -475,6 +488,35 @@ impl Operator {
Ok(Value::Empty)
},
AddAssign | SubAssign | MulAssign | DivAssign | ModAssign | ExpAssign | AndAssign
| OrAssign => {
expect_operator_argument_amount(arguments.len(), 2)?;
let target = arguments[0].as_string()?;
let left_value = Operator::VariableIdentifier {
identifier: target.clone(),
}
.eval(&Vec::new(), context)?;
let arguments = vec![left_value, arguments[1].clone()];
let result = match self {
AddAssign => Operator::Add.eval(&arguments, context),
SubAssign => Operator::Sub.eval(&arguments, context),
MulAssign => Operator::Mul.eval(&arguments, context),
DivAssign => Operator::Div.eval(&arguments, context),
ModAssign => Operator::Mod.eval(&arguments, context),
ExpAssign => Operator::Exp.eval(&arguments, context),
AndAssign => Operator::And.eval(&arguments, context),
OrAssign => Operator::Or.eval(&arguments, context),
_ => unreachable!(
"Forgot to add a match arm for an assign operation: {}",
self
),
}?;
context.set_value(target.into(), result)?;
Ok(Value::Empty)
},
_ => self.eval(arguments, context),
}
}

View File

@ -28,9 +28,19 @@ impl fmt::Display for Token {
LBrace => write!(f, "("),
RBrace => write!(f, ")"),
// Assignment
Assign => write!(f, "="),
PlusAssign => write!(f, "+="),
MinusAssign => write!(f, "-="),
StarAssign => write!(f, "*="),
SlashAssign => write!(f, "/="),
PercentAssign => write!(f, "%="),
HatAssign => write!(f, "^="),
AndAssign => write!(f, "&&="),
OrAssign => write!(f, "||="),
// Special
Comma => write!(f, ","),
Assign => write!(f, "="),
Semicolon => write!(f, ";"),
// Values => write!(f, ""), Variables and Functions
@ -50,6 +60,12 @@ impl fmt::Display for PartialToken {
Token(token) => token.fmt(f),
Literal(literal) => literal.fmt(f),
Whitespace => write!(f, " "),
Plus => write!(f, "+"),
Minus => write!(f, "-"),
Star => write!(f, "*"),
Slash => write!(f, "/"),
Percent => write!(f, "%"),
Hat => write!(f, "^"),
Eq => write!(f, "="),
ExclamationMark => write!(f, "!"),
Gt => write!(f, ">"),

View File

@ -30,9 +30,19 @@ pub enum Token {
LBrace,
RBrace,
// Assignment
Assign,
PlusAssign,
MinusAssign,
StarAssign,
SlashAssign,
PercentAssign,
HatAssign,
AndAssign,
OrAssign,
// Special
Comma,
Assign,
Semicolon,
// Values, Variables and Functions
@ -47,6 +57,12 @@ pub enum Token {
pub enum PartialToken {
Token(Token),
Literal(String),
Plus,
Minus,
Star,
Slash,
Percent,
Hat,
Whitespace,
Eq,
ExclamationMark,
@ -59,12 +75,12 @@ pub enum PartialToken {
// Make this a const fn as soon as match gets stable (issue #57563)
fn char_to_partial_token(c: char) -> PartialToken {
match c {
'+' => PartialToken::Token(Token::Plus),
'-' => PartialToken::Token(Token::Minus),
'*' => PartialToken::Token(Token::Star),
'/' => PartialToken::Token(Token::Slash),
'%' => PartialToken::Token(Token::Percent),
'^' => PartialToken::Token(Token::Hat),
'+' => PartialToken::Plus,
'-' => PartialToken::Minus,
'*' => PartialToken::Star,
'/' => PartialToken::Slash,
'%' => PartialToken::Percent,
'^' => PartialToken::Hat,
'(' => PartialToken::Token(Token::LBrace),
')' => PartialToken::Token(Token::RBrace),
@ -114,9 +130,18 @@ impl Token {
Token::RBrace => false,
Token::Comma => false,
Token::Assign => false,
Token::Semicolon => false,
Token::Assign => false,
Token::PlusAssign => false,
Token::MinusAssign => false,
Token::StarAssign => false,
Token::SlashAssign => false,
Token::PercentAssign => false,
Token::HatAssign => false,
Token::AndAssign => false,
Token::OrAssign => false,
Token::Identifier(_) => true,
Token::Float(_) => true,
Token::Int(_) => true,
@ -149,9 +174,18 @@ impl Token {
Token::RBrace => true,
Token::Comma => false,
Token::Assign => false,
Token::Semicolon => false,
Token::Assign => false,
Token::PlusAssign => false,
Token::MinusAssign => false,
Token::StarAssign => false,
Token::SlashAssign => false,
Token::PercentAssign => false,
Token::HatAssign => false,
Token::AndAssign => false,
Token::OrAssign => false,
Token::Identifier(_) => true,
Token::Float(_) => true,
Token::Int(_) => true,
@ -159,6 +193,15 @@ impl Token {
Token::String(_) => true,
}
}
pub(crate) fn is_assignment(&self) -> bool {
use Token::*;
match self {
Assign | PlusAssign | MinusAssign | StarAssign | SlashAssign | PercentAssign
| HatAssign | AndAssign | OrAssign => true,
_ => false,
}
}
}
/// Parses an escape sequence within a string literal.
@ -228,6 +271,7 @@ fn partial_tokens_to_tokens(mut tokens: &[PartialToken]) -> EvalexprResult<Vec<T
while tokens.len() > 0 {
let first = tokens[0].clone();
let second = tokens.get(1).cloned();
let third = tokens.get(2).cloned();
let mut cutoff = 2;
result.extend(
@ -236,6 +280,48 @@ fn partial_tokens_to_tokens(mut tokens: &[PartialToken]) -> EvalexprResult<Vec<T
cutoff = 1;
Some(token)
},
PartialToken::Plus => match second {
Some(PartialToken::Eq) => Some(Token::PlusAssign),
_ => {
cutoff = 1;
Some(Token::Plus)
},
},
PartialToken::Minus => match second {
Some(PartialToken::Eq) => Some(Token::MinusAssign),
_ => {
cutoff = 1;
Some(Token::Minus)
},
},
PartialToken::Star => match second {
Some(PartialToken::Eq) => Some(Token::StarAssign),
_ => {
cutoff = 1;
Some(Token::Star)
},
},
PartialToken::Slash => match second {
Some(PartialToken::Eq) => Some(Token::SlashAssign),
_ => {
cutoff = 1;
Some(Token::Slash)
},
},
PartialToken::Percent => match second {
Some(PartialToken::Eq) => Some(Token::PercentAssign),
_ => {
cutoff = 1;
Some(Token::Percent)
},
},
PartialToken::Hat => match second {
Some(PartialToken::Eq) => Some(Token::HatAssign),
_ => {
cutoff = 1;
Some(Token::Hat)
},
},
PartialToken::Literal(literal) => {
cutoff = 1;
if let Ok(number) = literal.parse::<IntType>() {
@ -281,11 +367,23 @@ fn partial_tokens_to_tokens(mut tokens: &[PartialToken]) -> EvalexprResult<Vec<T
},
},
PartialToken::Ampersand => match second {
Some(PartialToken::Ampersand) => Some(Token::And),
Some(PartialToken::Ampersand) => match third {
Some(PartialToken::Eq) => {
cutoff = 3;
Some(Token::AndAssign)
},
_ => Some(Token::And),
},
_ => return Err(EvalexprError::unmatched_partial_token(first, second)),
},
PartialToken::VerticalBar => match second {
Some(PartialToken::VerticalBar) => Some(Token::Or),
Some(PartialToken::VerticalBar) => match third {
Some(PartialToken::Eq) => {
cutoff = 3;
Some(Token::OrAssign)
},
_ => Some(Token::Or),
},
_ => return Err(EvalexprError::unmatched_partial_token(first, second)),
},
}

View File

@ -529,14 +529,23 @@ pub(crate) fn tokens_to_operator_tree(tokens: Vec<Token>) -> EvalexprResult<Node
}
},
Token::Comma => Some(Node::new(Operator::Tuple)),
Token::Assign => Some(Node::new(Operator::Assign)),
Token::PlusAssign => Some(Node::new(Operator::AddAssign)),
Token::MinusAssign => Some(Node::new(Operator::SubAssign)),
Token::StarAssign => Some(Node::new(Operator::MulAssign)),
Token::SlashAssign => Some(Node::new(Operator::DivAssign)),
Token::PercentAssign => Some(Node::new(Operator::ModAssign)),
Token::HatAssign => Some(Node::new(Operator::ExpAssign)),
Token::AndAssign => Some(Node::new(Operator::AndAssign)),
Token::OrAssign => Some(Node::new(Operator::OrAssign)),
Token::Comma => Some(Node::new(Operator::Tuple)),
Token::Semicolon => Some(Node::new(Operator::Chain)),
Token::Identifier(identifier) => {
let mut result = Some(Node::new(Operator::variable_identifier(identifier.clone())));
if let Some(next) = next {
if next == &Token::Assign {
if next.is_assignment() {
result = Some(Node::new(Operator::value(identifier.clone().into())));
} else if next.is_leftsided_value() {
result = Some(Node::new(Operator::function_identifier(identifier)));

View File

@ -636,3 +636,62 @@ fn test_implicit_context() {
Ok("xyzabc".to_string())
);
}
#[test]
fn test_operator_assignments() {
let mut context = HashMapContext::new();
assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(()));
assert_eq!(eval_empty_with_context_mut("a += 5", &mut context), Ok(()));
assert_eq!(eval_empty_with_context_mut("a -= 5", &mut context), Ok(()));
assert_eq!(eval_empty_with_context_mut("a *= 5", &mut context), Ok(()));
assert_eq!(eval_empty_with_context_mut("b = 5.0", &mut context), Ok(()));
assert_eq!(eval_empty_with_context_mut("b /= 5", &mut context), Ok(()));
assert_eq!(eval_empty_with_context_mut("b %= 5", &mut context), Ok(()));
assert_eq!(eval_empty_with_context_mut("b ^= 5", &mut context), Ok(()));
assert_eq!(
eval_empty_with_context_mut("c = true", &mut context),
Ok(())
);
assert_eq!(
eval_empty_with_context_mut("c &&= false", &mut context),
Ok(())
);
assert_eq!(
eval_empty_with_context_mut("c ||= true", &mut context),
Ok(())
);
let mut context = HashMapContext::new();
assert_eq!(eval_int_with_context_mut("a = 5; a", &mut context), Ok(5));
assert_eq!(eval_int_with_context_mut("a += 3; a", &mut context), Ok(8));
assert_eq!(eval_int_with_context_mut("a -= 5; a", &mut context), Ok(3));
assert_eq!(eval_int_with_context_mut("a *= 5; a", &mut context), Ok(15));
assert_eq!(
eval_float_with_context_mut("b = 5.0; b", &mut context),
Ok(5.0)
);
assert_eq!(
eval_float_with_context_mut("b /= 2; b", &mut context),
Ok(2.5)
);
assert_eq!(
eval_float_with_context_mut("b %= 2; b", &mut context),
Ok(0.5)
);
assert_eq!(
eval_float_with_context_mut("b ^= 2; b", &mut context),
Ok(0.25)
);
assert_eq!(
eval_boolean_with_context_mut("c = true; c", &mut context),
Ok(true)
);
assert_eq!(
eval_boolean_with_context_mut("c &&= false; c", &mut context),
Ok(false)
);
assert_eq!(
eval_boolean_with_context_mut("c ||= true; c", &mut context),
Ok(true)
);
}