darklua_core/rules/
remove_floor_division.rs

1use std::{mem, ops};
2
3use crate::nodes::{
4    BinaryOperator, Block, CompoundOperator, Expression, FieldExpression, FunctionCall,
5    LocalAssignStatement, Prefix, Statement,
6};
7use crate::process::{IdentifierTracker, NodeProcessor, NodeVisitor, ScopeVisitor};
8use crate::rules::{
9    verify_no_rule_properties, Context, FlawlessRule, RemoveCompoundAssignment, RuleConfiguration,
10    RuleConfigurationError, RuleProperties,
11};
12
13struct RemoveFloorDivisionProcessor {
14    math_floor_identifier: String,
15    define_math_floor: bool,
16    identifier_tracker: IdentifierTracker,
17}
18
19impl ops::Deref for RemoveFloorDivisionProcessor {
20    type Target = IdentifierTracker;
21
22    fn deref(&self) -> &Self::Target {
23        &self.identifier_tracker
24    }
25}
26
27impl ops::DerefMut for RemoveFloorDivisionProcessor {
28    fn deref_mut(&mut self) -> &mut Self::Target {
29        &mut self.identifier_tracker
30    }
31}
32
33const DEFAULT_MATH_LIBRARY: &str = "math";
34const DEFAULT_MATH_FLOOR_NAME: &str = "floor";
35
36impl RemoveFloorDivisionProcessor {
37    fn new(math_floor_identifier: impl Into<String>) -> Self {
38        Self {
39            math_floor_identifier: math_floor_identifier.into(),
40            define_math_floor: false,
41            identifier_tracker: Default::default(),
42        }
43    }
44
45    fn build_math_floor_call(&mut self, value: Expression) -> Expression {
46        FunctionCall::from_prefix(if self.is_identifier_used(DEFAULT_MATH_LIBRARY) {
47            self.define_math_floor = true;
48            Prefix::from_name(&self.math_floor_identifier)
49        } else {
50            FieldExpression::new(
51                Prefix::from_name(DEFAULT_MATH_LIBRARY),
52                DEFAULT_MATH_FLOOR_NAME,
53            )
54            .into()
55        })
56        .with_argument(value)
57        .into()
58    }
59}
60
61impl NodeProcessor for RemoveFloorDivisionProcessor {
62    fn process_statement(&mut self, statement: &mut Statement) {
63        match statement {
64            Statement::CompoundAssign(assign_statement)
65                if assign_statement.get_operator() == CompoundOperator::DoubleSlash =>
66            {
67                RemoveCompoundAssignment::default().replace_compound_assignment(statement);
68            }
69            _ => {}
70        }
71    }
72
73    fn process_expression(&mut self, expression: &mut Expression) {
74        if let Expression::Binary(binary) = expression {
75            if binary.operator() == BinaryOperator::DoubleSlash {
76                binary.set_operator(BinaryOperator::Slash);
77
78                let value = mem::replace(expression, Expression::nil());
79
80                *expression = self.build_math_floor_call(value);
81            }
82        }
83    }
84}
85
86pub const REMOVE_FLOOR_DIVISION_RULE_NAME: &str = "remove_floor_division";
87
88/// A rule that removes interpolated strings.
89#[derive(Debug, Default, PartialEq, Eq)]
90pub struct RemoveFloorDivision {}
91
92impl FlawlessRule for RemoveFloorDivision {
93    fn flawless_process(&self, block: &mut Block, _: &Context) {
94        const MATH_FLOOR_IDENTIFIER: &str = "__DARKLUA_MATH_FLOOR";
95
96        let mut processor = RemoveFloorDivisionProcessor::new(MATH_FLOOR_IDENTIFIER);
97        ScopeVisitor::visit_block(block, &mut processor);
98
99        if processor.define_math_floor {
100            block.insert_statement(
101                0,
102                LocalAssignStatement::from_variable(MATH_FLOOR_IDENTIFIER).with_value(
103                    FieldExpression::new(
104                        Prefix::from_name(DEFAULT_MATH_LIBRARY),
105                        DEFAULT_MATH_FLOOR_NAME,
106                    ),
107                ),
108            );
109        }
110    }
111}
112
113impl RuleConfiguration for RemoveFloorDivision {
114    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
115        verify_no_rule_properties(&properties)?;
116
117        Ok(())
118    }
119
120    fn get_name(&self) -> &'static str {
121        REMOVE_FLOOR_DIVISION_RULE_NAME
122    }
123
124    fn serialize_to_properties(&self) -> RuleProperties {
125        RuleProperties::new()
126    }
127}
128
129#[cfg(test)]
130mod test {
131    use super::*;
132    use crate::rules::Rule;
133
134    use insta::assert_json_snapshot;
135
136    fn new_rule() -> RemoveFloorDivision {
137        RemoveFloorDivision::default()
138    }
139
140    #[test]
141    fn serialize_default_rule() {
142        let rule: Box<dyn Rule> = Box::new(new_rule());
143
144        assert_json_snapshot!("default_remove_floor_division", rule);
145    }
146
147    #[test]
148    fn configure_with_extra_field_error() {
149        let result = json5::from_str::<Box<dyn Rule>>(
150            r#"{
151            rule: 'remove_floor_division',
152            prop: "something",
153        }"#,
154        );
155        pretty_assertions::assert_eq!(result.unwrap_err().to_string(), "unexpected field 'prop'");
156    }
157}