Merge pull request #107 from ISibboI/sebschmi/i106_identifier_is_variable_bug

Split VariableIdentifier node into read and write variants
This commit is contained in:
ISibboI 2022-07-06 11:23:20 +03:00 committed by GitHub
commit c0b46a4e8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 19 deletions

View File

@ -9,6 +9,7 @@
* Builtin functions to check for nan, infinity and subnormality in floats (#101) * Builtin functions to check for nan, infinity and subnormality in floats (#101)
* Builtin random function (#102) * Builtin random function (#102)
* Implement `TryFrom<Value>` for all types a value can hold (#105) * Implement `TryFrom<Value>` for all types a value can hold (#105)
* Split VariableIdentifier node into read and write variants (#106)
### Removed ### Removed
@ -24,6 +25,7 @@ My warmhearted thanks goes to:
* [Ophir LOJKINE](https://github.com/lovasoa) * [Ophir LOJKINE](https://github.com/lovasoa)
* [Joe Grund](https://github.com/jgrund) * [Joe Grund](https://github.com/jgrund)
* [Luka Maljic](https://github.com/malj)
## [7.2.0](https://github.com/ISibboI/evalexpr/compare/7.1.1...7.2.0) - 2022-03-16 ## [7.2.0](https://github.com/ISibboI/evalexpr/compare/7.1.1...7.2.0) - 2022-03-16

View File

@ -41,7 +41,9 @@ impl Display for Operator {
Chain => write!(f, "; "), Chain => write!(f, "; "),
Const { value } => write!(f, "{}", value), Const { value } => write!(f, "{}", value),
VariableIdentifier { identifier } => write!(f, "{}", identifier), VariableIdentifierWrite { identifier } | VariableIdentifierRead { identifier } => {
write!(f, "{}", identifier)
},
FunctionIdentifier { identifier } => write!(f, "{}", identifier), FunctionIdentifier { identifier } => write!(f, "{}", identifier),
} }
} }

View File

@ -75,8 +75,13 @@ pub enum Operator {
/** The value of the constant. */ /** The value of the constant. */
value: Value, value: Value,
}, },
/// A variable identifier. /// A write to a variable identifier.
VariableIdentifier { VariableIdentifierWrite {
/// The identifier of the variable.
identifier: String,
},
/// A read from a variable identifier.
VariableIdentifierRead {
/// The identifier of the variable. /// The identifier of the variable.
identifier: String, identifier: String,
}, },
@ -92,8 +97,12 @@ impl Operator {
Operator::Const { value } Operator::Const { value }
} }
pub(crate) fn variable_identifier(identifier: String) -> Self { pub(crate) fn variable_identifier_write(identifier: String) -> Self {
Operator::VariableIdentifier { identifier } Operator::VariableIdentifierWrite { identifier }
}
pub(crate) fn variable_identifier_read(identifier: String) -> Self {
Operator::VariableIdentifierRead { identifier }
} }
pub(crate) fn function_identifier(identifier: String) -> Self { pub(crate) fn function_identifier(identifier: String) -> Self {
@ -123,9 +132,9 @@ impl Operator {
Tuple => 40, Tuple => 40,
Chain => 0, Chain => 0,
Const { value: _ } => 200, Const { .. } => 200,
VariableIdentifier { identifier: _ } => 200, VariableIdentifierWrite { .. } | VariableIdentifierRead { .. } => 200,
FunctionIdentifier { identifier: _ } => 190, FunctionIdentifier { .. } => 190,
} }
} }
@ -134,7 +143,7 @@ impl Operator {
/// Left-to-right chaining has priority if operators with different order but same precedence are chained. /// Left-to-right chaining has priority if operators with different order but same precedence are chained.
pub(crate) const fn is_left_to_right(&self) -> bool { pub(crate) const fn is_left_to_right(&self) -> bool {
use crate::operator::Operator::*; use crate::operator::Operator::*;
!matches!(self, Assign | FunctionIdentifier { identifier: _ }) !matches!(self, Assign | FunctionIdentifier { .. })
} }
/// Returns true if chains of this operator should be flattened into one operator with many arguments. /// Returns true if chains of this operator should be flattened into one operator with many arguments.
@ -158,9 +167,9 @@ impl Operator {
| AndAssign | OrAssign => Some(2), | AndAssign | OrAssign => Some(2),
Tuple | Chain => None, Tuple | Chain => None,
Not | Neg | RootNode => Some(1), Not | Neg | RootNode => Some(1),
Const { value: _ } => Some(0), Const { .. } => Some(0),
VariableIdentifier { identifier: _ } => Some(0), VariableIdentifierWrite { .. } | VariableIdentifierRead { .. } => Some(0),
FunctionIdentifier { identifier: _ } => Some(1), FunctionIdentifier { .. } => Some(1),
} }
} }
@ -422,7 +431,12 @@ impl Operator {
Ok(value.clone()) Ok(value.clone())
}, },
VariableIdentifier { identifier } => { VariableIdentifierWrite { identifier } => {
expect_operator_argument_amount(arguments.len(), 0)?;
Ok(identifier.clone().into())
},
VariableIdentifierRead { identifier } => {
expect_operator_argument_amount(arguments.len(), 0)?; expect_operator_argument_amount(arguments.len(), 0)?;
if let Some(value) = context.get_value(identifier).cloned() { if let Some(value) = context.get_value(identifier).cloned() {
@ -473,7 +487,7 @@ impl Operator {
expect_operator_argument_amount(arguments.len(), 2)?; expect_operator_argument_amount(arguments.len(), 2)?;
let target = arguments[0].as_string()?; let target = arguments[0].as_string()?;
let left_value = Operator::VariableIdentifier { let left_value = Operator::VariableIdentifierRead {
identifier: target.clone(), identifier: target.clone(),
} }
.eval(&Vec::new(), context)?; .eval(&Vec::new(), context)?;

View File

@ -444,7 +444,7 @@ pub(crate) fn tokenize(string: &str) -> EvalexprResult<Vec<Token>> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::token::{char_to_partial_token, tokenize}; use crate::token::{char_to_partial_token, tokenize, Token};
use std::fmt::Write; use std::fmt::Write;
#[test] #[test]
@ -474,4 +474,17 @@ mod tests {
assert_eq!(token_string, result_string); assert_eq!(token_string, result_string);
} }
#[test]
fn assignment_lhs_is_identifier() {
let tokens = tokenize("a = 1").unwrap();
assert_eq!(
tokens.as_slice(),
[
Token::Identifier("a".to_string()),
Token::Assign,
Token::Int(1)
]
);
}
} }

View File

@ -69,7 +69,8 @@ impl Node {
/// ``` /// ```
pub fn iter_identifiers(&self) -> impl Iterator<Item = &str> { pub fn iter_identifiers(&self) -> impl Iterator<Item = &str> {
self.iter().filter_map(|node| match node.operator() { self.iter().filter_map(|node| match node.operator() {
Operator::VariableIdentifier { identifier } Operator::VariableIdentifierWrite { identifier }
| Operator::VariableIdentifierRead { identifier }
| Operator::FunctionIdentifier { identifier } => Some(identifier.as_str()), | Operator::FunctionIdentifier { identifier } => Some(identifier.as_str()),
_ => None, _ => None,
}) })
@ -92,7 +93,8 @@ impl Node {
/// ``` /// ```
pub fn iter_variable_identifiers(&self) -> impl Iterator<Item = &str> { pub fn iter_variable_identifiers(&self) -> impl Iterator<Item = &str> {
self.iter().filter_map(|node| match node.operator() { self.iter().filter_map(|node| match node.operator() {
Operator::VariableIdentifier { identifier } => Some(identifier.as_str()), Operator::VariableIdentifierWrite { identifier }
| Operator::VariableIdentifierRead { identifier } => Some(identifier.as_str()),
_ => None, _ => None,
}) })
} }
@ -616,10 +618,14 @@ pub(crate) fn tokens_to_operator_tree(tokens: Vec<Token>) -> EvalexprResult<Node
Token::Semicolon => Some(Node::new(Operator::Chain)), Token::Semicolon => Some(Node::new(Operator::Chain)),
Token::Identifier(identifier) => { Token::Identifier(identifier) => {
let mut result = Some(Node::new(Operator::variable_identifier(identifier.clone()))); let mut result = Some(Node::new(Operator::variable_identifier_read(
identifier.clone(),
)));
if let Some(next) = next { if let Some(next) = next {
if next.is_assignment() { if next.is_assignment() {
result = Some(Node::new(Operator::value(identifier.clone().into()))); result = Some(Node::new(Operator::variable_identifier_write(
identifier.clone(),
)));
} else if next.is_leftsided_value() { } else if next.is_leftsided_value() {
result = Some(Node::new(Operator::function_identifier(identifier))); result = Some(Node::new(Operator::function_identifier(identifier)));
} }

View File

@ -2083,3 +2083,28 @@ fn test_try_from() {
); );
assert_eq!(EmptyType::try_from(value.clone()), Ok(())); assert_eq!(EmptyType::try_from(value.clone()), Ok(()));
} }
#[test]
fn assignment_lhs_is_identifier() {
let tree = build_operator_tree("a = 1").unwrap();
let operators: Vec<_> = tree.iter().map(|node| node.operator().clone()).collect();
let mut context = HashMapContext::new();
tree.eval_with_context_mut(&mut context).unwrap();
assert_eq!(context.get_value("a"), Some(&Value::Int(1)));
assert!(
matches!(
operators.as_slice(),
[
Operator::Assign,
Operator::VariableIdentifierWrite { identifier: value },
Operator::Const {
value: Value::Int(1)
}
] if value == "a"
),
"actual: {:#?}",
operators
);
}