use crate::nodes::{
BinaryExpression, BinaryOperator, Block, Expression, FunctionCall, Prefix, Statement,
};
use crate::process::{Evaluator, IdentifierTracker, NodeProcessor, NodeVisitor, ScopeVisitor};
use crate::rules::{
Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleMetadata, RuleProperties,
};
use crate::utils::{expressions_as_statement, preserve_arguments_side_effects};
use super::verify_no_rule_properties;
pub const CONVERT_SQUARE_ROOT_CALL_RULE_NAME: &str = "convert_square_root_call";
const DEFAULT_MATH_LIBRARY: &str = "math";
const DEFAULT_MATH_SQRT_NAME: &str = "sqrt";
#[derive(Default)]
struct Processor {
evaluator: Evaluator,
identifier_tracker: IdentifierTracker,
}
impl Processor {
fn new() -> Self {
Self::default()
}
fn is_math_sqrt_call(&self, call: &FunctionCall) -> bool {
if call.has_method() {
return false;
}
if call.get_arguments().len() != 1 {
return false;
}
if let Prefix::Field(field_expr) = call.get_prefix() {
if field_expr.get_field().get_name() != DEFAULT_MATH_SQRT_NAME {
return false;
}
if let Prefix::Identifier(identifier) = field_expr.get_prefix() {
if identifier.get_name() == DEFAULT_MATH_LIBRARY
&& !self
.identifier_tracker
.is_identifier_used(DEFAULT_MATH_LIBRARY)
{
return true;
}
}
}
false
}
}
impl std::ops::Deref for Processor {
type Target = IdentifierTracker;
fn deref(&self) -> &Self::Target {
&self.identifier_tracker
}
}
impl std::ops::DerefMut for Processor {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.identifier_tracker
}
}
impl NodeProcessor for Processor {
fn process_expression(&mut self, expression: &mut Expression) {
if let Expression::Call(call) = expression {
if self.is_math_sqrt_call(call) {
let arguments = call.get_arguments();
let expressions = arguments.clone().to_expressions();
if let Some(argument) = expressions.first() {
*expression = BinaryExpression::new(
BinaryOperator::Caret,
argument.clone(),
Expression::from(0.5),
)
.into();
}
}
}
}
fn process_statement(&mut self, statement: &mut Statement) {
if let Statement::Call(call) = statement {
if self.is_math_sqrt_call(call) {
let values = preserve_arguments_side_effects(&self.evaluator, call.get_arguments());
*statement = expressions_as_statement(values);
}
}
}
}
#[derive(Debug, Default)]
pub struct ConvertSquareRootCall {
metadata: RuleMetadata,
}
impl FlawlessRule for ConvertSquareRootCall {
fn flawless_process(&self, block: &mut Block, _: &Context) {
let mut processor = Processor::new();
ScopeVisitor::visit_block(block, &mut processor);
}
}
impl RuleConfiguration for ConvertSquareRootCall {
fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
verify_no_rule_properties(&properties)
}
fn get_name(&self) -> &'static str {
CONVERT_SQUARE_ROOT_CALL_RULE_NAME
}
fn serialize_to_properties(&self) -> RuleProperties {
RuleProperties::new()
}
fn set_metadata(&mut self, metadata: RuleMetadata) {
self.metadata = metadata;
}
fn metadata(&self) -> &RuleMetadata {
&self.metadata
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::rules::Rule;
use insta::assert_json_snapshot;
fn new_rule() -> ConvertSquareRootCall {
ConvertSquareRootCall::default()
}
#[test]
fn serialize_default_rule() {
let rule: Box<dyn Rule> = Box::new(new_rule());
assert_json_snapshot!(rule, @r###""convert_square_root_call""###);
}
#[test]
fn configure_with_extra_field_error() {
let result = json5::from_str::<Box<dyn Rule>>(
r#"{
rule: 'convert_square_root_call',
prop: "something",
}"#,
);
insta::assert_snapshot!(result.unwrap_err().to_string(), @"unexpected field 'prop' at line 1 column 1");
}
}