darklua_core/rules/
remove_floor_division.rs1use 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#[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}