darklua_core/rules/
convert_index_to_field.rs

1use crate::nodes::{
2    Block, Expression, FieldExpression, Identifier, IndexExpression, Prefix, TableEntry,
3    TableExpression, TableFieldEntry, Variable,
4};
5use crate::process::utils::is_valid_identifier;
6use crate::process::{DefaultVisitor, Evaluator, LuaValue, NodeProcessor, NodeVisitor};
7use crate::rules::{
8    Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleProperties,
9};
10
11use super::verify_no_rule_properties;
12
13use std::mem;
14
15#[derive(Debug, Clone, Default)]
16struct Converter {
17    evaluator: Evaluator,
18}
19
20impl Converter {
21    #[inline]
22    fn convert_index_to_field(&self, index: &IndexExpression) -> Option<FieldExpression> {
23        self.convert_to_field(index.get_index())
24            .map(|key| FieldExpression::new(index.get_prefix().clone(), Identifier::new(key)))
25    }
26
27    fn convert_to_field(&self, key_expression: &Expression) -> Option<String> {
28        if let LuaValue::String(string) = self.evaluator.evaluate(key_expression) {
29            if is_valid_identifier(&string) {
30                return Some(string);
31            }
32        }
33        None
34    }
35}
36
37impl NodeProcessor for Converter {
38    fn process_expression(&mut self, expression: &mut Expression) {
39        let field: Option<Expression> = if let Expression::Index(index) = expression {
40            self.convert_index_to_field(index).map(Into::into)
41        } else {
42            None
43        };
44        if let Some(mut field) = field {
45            mem::swap(expression, &mut field);
46        }
47    }
48
49    fn process_prefix_expression(&mut self, prefix: &mut Prefix) {
50        let field: Option<Prefix> = if let Prefix::Index(index) = prefix {
51            self.convert_index_to_field(index).map(Into::into)
52        } else {
53            None
54        };
55        if let Some(mut field) = field {
56            mem::swap(prefix, &mut field);
57        }
58    }
59
60    fn process_variable(&mut self, variable: &mut Variable) {
61        let field: Option<Variable> = if let Variable::Index(index) = variable {
62            self.convert_index_to_field(index).map(Into::into)
63        } else {
64            None
65        };
66        if let Some(mut field) = field {
67            mem::swap(variable, &mut field);
68        }
69    }
70
71    fn process_table_expression(&mut self, table: &mut TableExpression) {
72        for entry in table.iter_mut_entries() {
73            let replace_with = match entry {
74                TableEntry::Index(entry) => self
75                    .convert_to_field(entry.get_key())
76                    .map(|key| TableFieldEntry::new(key, entry.get_value().clone()))
77                    .map(TableEntry::from),
78
79                TableEntry::Field(_) | TableEntry::Value(_) => None,
80            };
81            if let Some(new_entry) = replace_with {
82                *entry = new_entry;
83            }
84        }
85    }
86}
87
88pub const CONVERT_INDEX_TO_FIELD_RULE_NAME: &str = "convert_index_to_field";
89
90/// A rule that converts index expression into field expression.
91#[derive(Debug, Default, PartialEq, Eq)]
92pub struct ConvertIndexToField {}
93
94impl FlawlessRule for ConvertIndexToField {
95    fn flawless_process(&self, block: &mut Block, _: &Context) {
96        let mut processor = Converter::default();
97        DefaultVisitor::visit_block(block, &mut processor);
98    }
99}
100
101impl RuleConfiguration for ConvertIndexToField {
102    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
103        verify_no_rule_properties(&properties)?;
104
105        Ok(())
106    }
107
108    fn get_name(&self) -> &'static str {
109        CONVERT_INDEX_TO_FIELD_RULE_NAME
110    }
111
112    fn serialize_to_properties(&self) -> RuleProperties {
113        RuleProperties::new()
114    }
115}
116
117#[cfg(test)]
118mod test {
119    use super::*;
120    use crate::rules::Rule;
121
122    use insta::assert_json_snapshot;
123
124    fn new_rule() -> ConvertIndexToField {
125        ConvertIndexToField::default()
126    }
127
128    #[test]
129    fn serialize_default_rule() {
130        let rule: Box<dyn Rule> = Box::new(new_rule());
131
132        assert_json_snapshot!("default_convert_index_to_field", rule);
133    }
134
135    #[test]
136    fn configure_with_extra_field_error() {
137        let result = json5::from_str::<Box<dyn Rule>>(
138            r#"{
139            rule: 'convert_index_to_field',
140            prop: "something",
141        }"#,
142        );
143        pretty_assertions::assert_eq!(result.unwrap_err().to_string(), "unexpected field 'prop'");
144    }
145}