use crate::{
error::parse::{
expression::{ExpressionError, ExpressionErrorKind},
line::LineErrorKind,
},
line::{
expression::{apply_order_of_operations, Operand, Operator},
parse::{parse_variable, split_line_at_separator_parenthesis},
Expression,
},
};
pub const MATHEMATICAL_OPERATORS: &[char] = &['+', '-', '*', '/', '%'];
pub fn parse_expression(content: &str) -> Result<Expression, ExpressionError> {
split_line_into_operation_terms(content)
.and_then(|operations| parse_expression_from_operation_terms(operations))
.map(|expression| apply_order_of_operations(&expression))
.map_err(|kind| ExpressionError {
content: content.to_string(),
kind,
})
}
fn parse_expression_from_operation_terms(
mut operation_strings: Vec<String>,
) -> Result<Expression, ExpressionErrorKind> {
operation_strings
.split_first_mut()
.ok_or(ExpressionErrorKind::Empty)
.and_then(|(head_string, tail_strings)| {
let (head, head_multiplier) = get_head_operand_and_multiplier(head_string)?;
let mut tail = tail_strings
.into_iter()
.map(|content| get_tail_operator_and_operand(content))
.collect::<Result<Vec<_>, _>>()?;
if let Some(multiplier) = head_multiplier {
tail.insert(0, multiplier);
}
Ok(Expression { head, tail })
})
}
fn get_head_operand_and_multiplier(
content: &str,
) -> Result<(Operand, Option<(Operator, Operand)>), ExpressionErrorKind> {
let mut buffer = content.to_string();
let multiplier = match split_off_operator(&mut buffer) {
Some(Operator::Subtract) => {
let operand = Operand::Variable((-1).into());
Ok(Some((Operator::Multiply, operand)))
}
Some(Operator::Add) | None => Ok(None),
_ => Err(ExpressionErrorKind::InvalidHead {
head: content.to_string(),
}),
}?;
let head = parse_operand(buffer.trim())?;
Ok((head, multiplier))
}
fn get_tail_operator_and_operand(
content: &mut String,
) -> Result<(Operator, Operand), ExpressionErrorKind> {
let operator = split_off_operator(content).ok_or(ExpressionErrorKind::NoOperator {
content: content.to_string(),
})?;
let operand = parse_operand(content.trim())?;
Ok((operator, operand))
}
fn split_line_into_operation_terms(content: &str) -> Result<Vec<String>, ExpressionErrorKind> {
let mut buffer = content.trim().to_string();
let mut operations = Vec::new();
while !buffer.trim().is_empty() {
let operation_string = read_next_operation_string(&mut buffer)?;
operations.push(operation_string);
}
Ok(operations)
}
fn parse_operand(content: &str) -> Result<Operand, ExpressionErrorKind> {
if content.starts_with('(') && content.ends_with(')') && content.len() > 1 {
let inner = content.get(1..content.bytes().len() - 1).unwrap();
parse_expression(inner)
.map(|expression| Operand::Nested(Box::new(expression)))
.map_err(|err| err.kind)
} else {
parse_variable(content)
.map(|variable| Operand::Variable(variable))
.map_err(|err| ExpressionErrorKind::InvalidVariable(err))
}
}
fn split_off_operator(buffer: &mut String) -> Option<Operator> {
let operator = buffer.chars().next().and_then(|c| match c {
'+' => Some(Operator::Add),
'-' => Some(Operator::Subtract),
'*' => Some(Operator::Multiply),
'/' => Some(Operator::Divide),
'%' => Some(Operator::Remainder),
_ => None,
});
if operator.is_some() {
buffer.drain(..1);
}
operator
}
fn read_next_operation_string(buffer: &mut String) -> Result<String, ExpressionErrorKind> {
let (head, tail) = split_leading_operator(&buffer);
let head_size = head.len();
let mut last_index = 0;
let index = loop {
let haystack = tail.get(last_index..).unwrap();
let i = get_closest_split_index(haystack)
.map_err(|_| ExpressionErrorKind::UnmatchedParenthesis)?;
let index = i + last_index;
if buffer
.get(..index + 1)
.map(|s| s.matches(|c| c == '"').count() % 2 == 0)
.unwrap_or(true)
|| index >= tail.bytes().len()
{
break index;
} else {
last_index += i + 1;
}
};
Ok(buffer.drain(..index + head_size).collect())
}
fn split_leading_operator(content: &str) -> (&str, &str) {
let index = if content
.chars()
.next()
.map(|c| MATHEMATICAL_OPERATORS.contains(&c))
.unwrap_or(false)
{
1
} else {
0
};
content.split_at(index)
}
fn get_closest_split_index(content: &str) -> Result<usize, LineErrorKind> {
get_split_index(content, "+")
.and_then(|current_min| get_split_index(&content, "-").map(|next| current_min.min(next)))
.and_then(|current_min| get_split_index(&content, "*").map(|next| current_min.min(next)))
.and_then(|current_min| get_split_index(&content, "/").map(|next| current_min.min(next)))
.and_then(|current_min| get_split_index(&content, "%").map(|next| current_min.min(next)))
}
fn get_split_index(content: &str, separator: &str) -> Result<usize, LineErrorKind> {
split_line_at_separator_parenthesis(content, separator, Some(1))
.map(|parts| parts[0].as_bytes().len())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
follow::FollowData,
knot::Address,
line::{evaluate_expression, Variable},
story::types::VariableInfo,
};
use std::collections::HashMap;
fn mock_follow_data(knots: &[(&str, &str, u32)], variables: &[(&str, Variable)]) -> FollowData {
let mut knot_visit_counts = HashMap::new();
for (knot, stitch, num_visited) in knots {
let mut stitch_count = HashMap::new();
stitch_count.insert(stitch.to_string(), *num_visited);
knot_visit_counts.insert(knot.to_string(), stitch_count);
}
let variables = variables
.into_iter()
.cloned()
.enumerate()
.map(|(i, (name, var))| (name, VariableInfo::new(var, i)))
.map(|(name, var)| (name.to_string(), var))
.collect();
FollowData {
knot_visit_counts,
variables,
}
}
#[test]
fn single_number_parses_into_expression_with_only_head() {
let expression = parse_expression("5").unwrap();
assert_eq!(expression.head, Operand::Variable(Variable::Int(5)));
assert!(expression.tail.is_empty());
}
#[test]
fn number_then_operand_then_number_parses_into_addition_expression() {
let expression = parse_expression("1 + 2").unwrap();
assert_eq!(expression.head, Operand::Variable(Variable::Int(1)));
assert_eq!(expression.tail.len(), 1);
assert_eq!(
expression.tail[0],
(Operator::Add, Operand::Variable(Variable::Int(2)))
);
}
#[test]
fn many_operations_created_nested_structure_based_on_operator_precedence() {
let data = mock_follow_data(&[], &[]);
let expression = parse_expression("1 + 2 - 2 * 3 + 1 / 5 + 5").unwrap();
let equiv_expression = parse_expression("1 + 2 - (2 * 3) + (1 / 5) + 5").unwrap();
assert_eq!(
evaluate_expression(&expression, &data).unwrap(),
evaluate_expression(&equiv_expression, &data).unwrap()
);
}
#[test]
fn whitespace_does_not_matter() {
let data = mock_follow_data(&[], &[]);
let expression = parse_expression("1 + 2 - 2 * 3 + 1 / 5 + 5").unwrap();
let equiv_expression = parse_expression("1+2-(2*3)+(1/5)+5").unwrap();
assert_eq!(
evaluate_expression(&expression, &data).unwrap(),
evaluate_expression(&equiv_expression, &data).unwrap()
);
}
#[test]
fn nested_parenthesis_are_evaluated_correctly() {
let data = mock_follow_data(&[], &[]);
let expression = parse_expression("1 + ((2 * (4 + 6)) * (3 - 5))").unwrap();
assert_eq!(
evaluate_expression(&expression, &data).unwrap(),
Variable::Int(-39),
);
}
#[test]
fn parenthesis_can_nest_several_levels_at_once() {
let data = mock_follow_data(&[], &[]);
let expression = parse_expression("((((1 + 2))))").unwrap();
assert_eq!(
evaluate_expression(&expression, &data).unwrap(),
Variable::Int(3),
);
}
#[test]
fn strings_can_be_inside_expressions() {
let data = mock_follow_data(&[], &[]);
let expression = parse_expression("\"str\" + \"ing\"").unwrap();
assert_eq!(
evaluate_expression(&expression, &data).unwrap(),
Variable::String("string".to_string())
);
}
#[test]
fn parsing_expression_from_no_terms_yields_empty_error() {
match parse_expression_from_operation_terms(vec![]) {
Err(ExpressionErrorKind::Empty) => (),
other => panic!("expected `ExpressionErrorKind::Empty` but got {:?}", other),
}
}
#[test]
fn parsing_expression_from_single_term_yields_single_term_expression() {
let expression = parse_expression_from_operation_terms(vec!["a".to_string()]).unwrap();
assert_eq!(
expression.head,
Operand::Variable(Variable::Address(Address::Raw("a".to_string())))
);
}
#[test]
fn parsing_expression_from_single_term_with_leading_plus_gives_regular_expression() {
let expression = parse_expression_from_operation_terms(vec!["+a".to_string()]).unwrap();
let expression_equiv =
parse_expression_from_operation_terms(vec!["a".to_string()]).unwrap();
assert_eq!(expression, expression_equiv);
}
#[test]
fn parsing_expression_from_single_negated_term_creates_multiplication_by_negative_one() {
let expression =
parse_expression_from_operation_terms(vec!["-a ".to_string(), "+ 1".to_string()])
.unwrap();
let expression_equiv = parse_expression_from_operation_terms(vec![
"a ".to_string(),
"* -1".to_string(),
"+ 1".to_string(),
])
.unwrap();
assert_eq!(expression, expression_equiv);
}
#[test]
fn parsing_expression_from_single_term_with_leading_mul_div_or_rem_marker_yields_error() {
match parse_expression_from_operation_terms(vec!["*a".to_string()]) {
Err(ExpressionErrorKind::InvalidHead { head }) => {
assert_eq!(head, "*a");
}
other => panic!(
"expected `ExpressionErrorKind::InvalidHead` but got {:?}",
other
),
}
match parse_expression_from_operation_terms(vec!["/a".to_string()]) {
Err(ExpressionErrorKind::InvalidHead { head }) => {
assert_eq!(head, "/a");
}
other => panic!(
"expected `ExpressionErrorKind::InvalidHead` but got {:?}",
other
),
}
match parse_expression_from_operation_terms(vec!["%a".to_string()]) {
Err(ExpressionErrorKind::InvalidHead { head }) => {
assert_eq!(head, "%a");
}
other => panic!(
"expected `ExpressionErrorKind::InvalidHead` but got {:?}",
other
),
}
}
#[test]
fn empty_string_splits_into_no_strings() {
assert!(split_line_into_operation_terms("").unwrap().is_empty());
assert!(split_line_into_operation_terms(" ").unwrap().is_empty());
}
#[test]
fn string_with_single_term_splits_into_single_term_list() {
assert_eq!(split_line_into_operation_terms("a").unwrap(), &["a"]);
}
#[test]
fn string_with_pure_number_operations_splits_cleanly_into_parts() {
assert_eq!(
split_line_into_operation_terms("a + b * c - d / e + f % g").unwrap(),
&["a ", "+ b ", "* c ", "- d ", "/ e ", "+ f ", "% g"]
);
}
#[test]
fn whitespace_is_trimmed_from_ends_when_splitting_into_terms() {
assert_eq!(
split_line_into_operation_terms(" a + b ").unwrap(),
&["a ", "+ b"]
);
}
#[test]
fn string_with_parenthesis_as_terms_keep_them_whole() {
assert_eq!(
split_line_into_operation_terms("a + (b * (c - d)) / (e + g)").unwrap(),
&["a ", "+ (b * (c - d)) ", "/ (e + g)"]
);
}
#[test]
fn whitespace_between_operators_is_not_needed() {
assert_eq!(
split_line_into_operation_terms("a+(b*(c-d))/(e+g)").unwrap(),
&["a", "+(b*(c-d))", "/(e+g)"]
);
}
#[test]
fn string_that_starts_with_mathematical_operator_returns_the_whole_term_as_first() {
assert_eq!(
split_line_into_operation_terms("+ a - b").unwrap(),
&["+ a ", "- b"]
);
}
#[test]
fn operators_inside_string_terms_do_not_split() {
assert_eq!(
split_line_into_operation_terms("a + \"word-with-dash\" + b").unwrap(),
&["a ", "+ \"word-with-dash\" ", "+ b"]
);
}
#[test]
fn variables_may_be_multibyte_characters() {
assert_eq!(
split_line_into_operation_terms("a + 한글 / (e + g)").unwrap(),
&["a ", "+ 한글 ", "/ (e + g)"]
);
}
#[test]
fn string_terms_may_contain_multibyte_characters_without_affecting_behavior() {
assert_eq!(
split_line_into_operation_terms("a + \"word-with-한글\" + b").unwrap(),
&["a ", "+ \"word-with-한글\" ", "+ b"]
);
}
#[test]
fn string_terms_can_come_first_and_last() {
assert_eq!(
split_line_into_operation_terms("\"one\" + \"two\"").unwrap(),
&["\"one\" ", "+ \"two\""]
);
}
#[test]
fn ummatched_string_quotes_keeps_all_content_from_opening_quote_as_one() {
assert_eq!(
split_line_into_operation_terms("\"one + \"two\"").unwrap(),
&["\"one + \"two\""]
);
assert_eq!(
split_line_into_operation_terms("\"one\" + two\"").unwrap(),
&["\"one\" ", "+ two\""]
);
assert_eq!(
split_line_into_operation_terms("\"one\" + word-with-dash\"").unwrap(),
&["\"one\" ", "+ word", "-with", "-dash\""]
);
}
}