Skip to main content

darklua_core/rules/
convert_square_root_call.rs

1use crate::nodes::{
2    BinaryExpression, BinaryOperator, Block, Expression, FunctionCall, Prefix, Statement,
3};
4use crate::process::{Evaluator, IdentifierTracker, NodeProcessor, NodeVisitor, ScopeVisitor};
5use crate::rules::{
6    Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleMetadata, RuleProperties,
7};
8use crate::utils::{expressions_as_statement, preserve_arguments_side_effects};
9
10use super::verify_no_rule_properties;
11
12pub const CONVERT_SQUARE_ROOT_CALL_RULE_NAME: &str = "convert_square_root_call";
13
14const DEFAULT_MATH_LIBRARY: &str = "math";
15const DEFAULT_MATH_SQRT_NAME: &str = "sqrt";
16
17#[derive(Default)]
18struct Processor {
19    evaluator: Evaluator,
20    identifier_tracker: IdentifierTracker,
21}
22
23impl Processor {
24    fn new() -> Self {
25        Self::default()
26    }
27
28    fn is_math_sqrt_call(&self, call: &FunctionCall) -> bool {
29        if call.has_method() {
30            return false;
31        }
32
33        if call.get_arguments().len() != 1 {
34            return false;
35        }
36
37        if let Prefix::Field(field_expr) = call.get_prefix() {
38            if field_expr.get_field().get_name() != DEFAULT_MATH_SQRT_NAME {
39                return false;
40            }
41
42            if let Prefix::Identifier(identifier) = field_expr.get_prefix() {
43                if identifier.get_name() == DEFAULT_MATH_LIBRARY
44                    && !self
45                        .identifier_tracker
46                        .is_identifier_used(DEFAULT_MATH_LIBRARY)
47                {
48                    return true;
49                }
50            }
51        }
52
53        false
54    }
55}
56
57impl std::ops::Deref for Processor {
58    type Target = IdentifierTracker;
59
60    fn deref(&self) -> &Self::Target {
61        &self.identifier_tracker
62    }
63}
64
65impl std::ops::DerefMut for Processor {
66    fn deref_mut(&mut self) -> &mut Self::Target {
67        &mut self.identifier_tracker
68    }
69}
70
71impl NodeProcessor for Processor {
72    fn process_expression(&mut self, expression: &mut Expression) {
73        if let Expression::Call(call) = expression {
74            if self.is_math_sqrt_call(call) {
75                let arguments = call.get_arguments();
76                let expressions = arguments.clone().to_expressions();
77                if let Some(argument) = expressions.first() {
78                    *expression = BinaryExpression::new(
79                        BinaryOperator::Caret,
80                        argument.clone(),
81                        Expression::from(0.5),
82                    )
83                    .into();
84                }
85            }
86        }
87    }
88
89    fn process_statement(&mut self, statement: &mut Statement) {
90        if let Statement::Call(call) = statement {
91            if self.is_math_sqrt_call(call) {
92                let values = preserve_arguments_side_effects(&self.evaluator, call.get_arguments());
93
94                *statement = expressions_as_statement(values);
95            }
96        }
97    }
98}
99
100/// A rule that converts square root calls (`math.sqrt(x)`) to exponentiation calls (`x ^ 0.5`).
101#[derive(Debug, Default)]
102pub struct ConvertSquareRootCall {
103    metadata: RuleMetadata,
104}
105
106impl FlawlessRule for ConvertSquareRootCall {
107    fn flawless_process(&self, block: &mut Block, _: &Context) {
108        let mut processor = Processor::new();
109        ScopeVisitor::visit_block(block, &mut processor);
110    }
111}
112
113impl RuleConfiguration for ConvertSquareRootCall {
114    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
115        verify_no_rule_properties(&properties)
116    }
117
118    fn get_name(&self) -> &'static str {
119        CONVERT_SQUARE_ROOT_CALL_RULE_NAME
120    }
121
122    fn serialize_to_properties(&self) -> RuleProperties {
123        RuleProperties::new()
124    }
125
126    fn set_metadata(&mut self, metadata: RuleMetadata) {
127        self.metadata = metadata;
128    }
129
130    fn metadata(&self) -> &RuleMetadata {
131        &self.metadata
132    }
133}
134
135#[cfg(test)]
136mod test {
137    use super::*;
138    use crate::rules::Rule;
139
140    use insta::assert_json_snapshot;
141
142    fn new_rule() -> ConvertSquareRootCall {
143        ConvertSquareRootCall::default()
144    }
145
146    #[test]
147    fn serialize_default_rule() {
148        let rule: Box<dyn Rule> = Box::new(new_rule());
149
150        assert_json_snapshot!(rule, @r###""convert_square_root_call""###);
151    }
152
153    #[test]
154    fn configure_with_extra_field_error() {
155        let result = json5::from_str::<Box<dyn Rule>>(
156            r#"{
157            rule: 'convert_square_root_call',
158            prop: "something",
159        }"#,
160        );
161        insta::assert_snapshot!(result.unwrap_err().to_string(), @"unexpected field 'prop' at line 1 column 1");
162    }
163}