Skip to main content

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, RuleMetadata, 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    metadata: RuleMetadata,
92}
93
94impl FlawlessRule for RemoveFloorDivision {
95    fn flawless_process(&self, block: &mut Block, _: &Context) {
96        const MATH_FLOOR_IDENTIFIER: &str = "__DARKLUA_MATH_FLOOR";
97
98        let mut processor = RemoveFloorDivisionProcessor::new(MATH_FLOOR_IDENTIFIER);
99        ScopeVisitor::visit_block(block, &mut processor);
100
101        if processor.define_math_floor {
102            block.insert_statement(
103                0,
104                LocalAssignStatement::from_variable(MATH_FLOOR_IDENTIFIER).with_value(
105                    FieldExpression::new(
106                        Prefix::from_name(DEFAULT_MATH_LIBRARY),
107                        DEFAULT_MATH_FLOOR_NAME,
108                    ),
109                ),
110            );
111        }
112    }
113}
114
115impl RuleConfiguration for RemoveFloorDivision {
116    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
117        verify_no_rule_properties(&properties)?;
118
119        Ok(())
120    }
121
122    fn get_name(&self) -> &'static str {
123        REMOVE_FLOOR_DIVISION_RULE_NAME
124    }
125
126    fn serialize_to_properties(&self) -> RuleProperties {
127        RuleProperties::new()
128    }
129
130    fn set_metadata(&mut self, metadata: RuleMetadata) {
131        self.metadata = metadata;
132    }
133
134    fn metadata(&self) -> &RuleMetadata {
135        &self.metadata
136    }
137}
138
139#[cfg(test)]
140mod test {
141    use super::*;
142    use crate::rules::Rule;
143
144    use insta::assert_json_snapshot;
145
146    fn new_rule() -> RemoveFloorDivision {
147        RemoveFloorDivision::default()
148    }
149
150    #[test]
151    fn serialize_default_rule() {
152        let rule: Box<dyn Rule> = Box::new(new_rule());
153
154        assert_json_snapshot!(rule, @r###""remove_floor_division""###);
155    }
156
157    #[test]
158    fn configure_with_extra_field_error() {
159        let result = json5::from_str::<Box<dyn Rule>>(
160            r#"{
161            rule: 'remove_floor_division',
162            prop: "something",
163        }"#,
164        );
165        insta::assert_snapshot!(result.unwrap_err().to_string(), @"unexpected field 'prop' at line 1 column 1");
166    }
167}