1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use crate::nodes::{Block, Expression, LocalAssignStatement, Statement};
use crate::process::processors::FindVariables;
use crate::process::{DefaultVisitor, NodeProcessor, NodeVisitor};
use crate::rules::{
    Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleProperties,
};

use std::iter;

use super::verify_no_rule_properties;

#[derive(Debug, Clone, Default)]
struct GroupLocalProcessor {}

impl GroupLocalProcessor {
    fn filter_statements(&self, block: &mut Block) -> Vec<Statement> {
        let statements = block.mutate_statements();
        let mut filter_statements = Vec::new();
        let mut iter = statements.drain(..);
        let mut previous_statement = iter.next();
        let mut current_statement = iter.next();

        while let Some(current) = current_statement {
            previous_statement = if let Some(previous) = previous_statement {
                use Statement::LocalAssign;

                match (previous, current) {
                    (LocalAssign(mut previous), LocalAssign(mut current)) => {
                        if self.should_merge(&previous, &mut current) {
                            self.merge(&mut previous, current);

                            Some(LocalAssign(previous))
                        } else {
                            filter_statements.push(LocalAssign(previous));
                            Some(LocalAssign(current))
                        }
                    }
                    (previous, current) => {
                        filter_statements.push(previous);
                        Some(current)
                    }
                }
            } else {
                None
            };

            current_statement = iter.next();
        }

        if let Some(previous) = previous_statement {
            filter_statements.push(previous);
        }

        filter_statements
    }

    fn should_merge(&self, first: &LocalAssignStatement, next: &mut LocalAssignStatement) -> bool {
        let first_value_count = first.value_count();

        if first.variable_count() > first_value_count && first_value_count != 0 {
            return false;
        }

        let mut find_variables = FindVariables::from(first.get_variables());

        next.iter_mut_values().all(|expression| {
            DefaultVisitor::visit_expression(expression, &mut find_variables);
            !find_variables.has_found_usage()
        })
    }

    fn merge(&self, first: &mut LocalAssignStatement, mut other: LocalAssignStatement) {
        if first.value_count() == 0 && other.value_count() != 0 {
            let variable_count = first.variable_count();
            first.extend_values(iter::repeat(Expression::nil()).take(variable_count));
        }

        if other.value_count() == 0 && first.value_count() != 0 {
            let variable_count = other.variable_count();
            other.extend_values(iter::repeat(Expression::nil()).take(variable_count));
        }

        let (mut variables, mut values) = other.into_assignments();
        first.append_variables(&mut variables);
        first.append_values(&mut values);
    }
}

impl NodeProcessor for GroupLocalProcessor {
    fn process_block(&mut self, block: &mut Block) {
        let filter_statements = self.filter_statements(block);

        *block.mutate_statements() = filter_statements;
    }
}

pub const GROUP_LOCAL_ASSIGNMENT: &str = "group_local_assignment";

/// Group local assign statements into one statement.
#[derive(Debug, Default, PartialEq, Eq)]
pub struct GroupLocalAssignment {}

impl FlawlessRule for GroupLocalAssignment {
    fn flawless_process(&self, block: &mut Block, _: &mut Context) {
        let mut processor = GroupLocalProcessor::default();
        DefaultVisitor::visit_block(block, &mut processor);
    }
}

impl RuleConfiguration for GroupLocalAssignment {
    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
        verify_no_rule_properties(&properties)?;

        Ok(())
    }

    fn get_name(&self) -> &'static str {
        GROUP_LOCAL_ASSIGNMENT
    }

    fn serialize_to_properties(&self) -> RuleProperties {
        RuleProperties::new()
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::rules::Rule;

    use insta::assert_json_snapshot;

    fn new_rule() -> GroupLocalAssignment {
        GroupLocalAssignment::default()
    }

    #[test]
    fn serialize_default_rule() {
        let rule: Box<dyn Rule> = Box::new(new_rule());

        assert_json_snapshot!("default_group_local_assignment", rule);
    }
}