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, 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
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}