From 19a053a2882279be6aeea0d6d93ce83d7525de34 Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 24 Aug 2023 05:06:18 -0400 Subject: [PATCH] Improve type checking --- src/tools/mod.rs | 35 +++++++++++++++-- src/tools/random.rs | 96 ++++++++++++++++++++------------------------- 2 files changed, 74 insertions(+), 57 deletions(-) diff --git a/src/tools/mod.rs b/src/tools/mod.rs index 9f097c7..bc99c29 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -1,4 +1,9 @@ -//! This module contains dust's built-in commands. +//! Dust's built-in commands. +//! +//! When a tool in invoked in Dust, the input is checked against the inputs listed in its ToolInfo. +//! The input should then be double-checked by `Tool::check_input` when you implement `run`. The +//! purpose of the second check is to weed out mistakes in how the inputs were described in the +//! ToolInfo. The errors from the second check should only come up during development and should not //! be seen by the user. //! //! ## Writing macros //! @@ -43,10 +48,9 @@ pub mod random; pub mod system; pub mod time; -/// Master list of all macros. +/// Master list of all tools. /// -/// This list is used to match identifiers with macros and to provide info to -/// the shell. +/// This list is used to match identifiers with tools and to provide info to the shell. pub const TOOL_LIST: [&'static dyn Tool; 57] = [ &collections::Count, &collections::CreateTable, @@ -111,6 +115,29 @@ pub const TOOL_LIST: [&'static dyn Tool; 57] = [ pub trait Tool: Sync + Send { fn info(&self) -> ToolInfo<'static>; fn run(&self, argument: &Value) -> Result; + + fn check_type<'a>(&self, argument: &'a Value) -> Result<&'a Value> { + if self + .info() + .inputs + .iter() + .any(|value_type| &argument.value_type() == value_type) + { + Ok(argument) + } else { + Err(crate::Error::TypeCheckFailure { + tool_info: self.info(), + argument: argument.clone(), + }) + } + } + + fn fail(&self, argument: &Value) -> Result { + Err(crate::Error::TypeCheckFailure { + tool_info: self.info(), + argument: argument.clone(), + }) + } } /// Information needed for each macro. diff --git a/src/tools/random.rs b/src/tools/random.rs index a4e14a7..a9e93da 100644 --- a/src/tools/random.rs +++ b/src/tools/random.rs @@ -17,24 +17,14 @@ impl Tool for RandomBoolean { } fn run(&self, argument: &Value) -> Result { - match argument { - Value::Empty => { - let boolean = rand::thread_rng().gen(); + let argument = self.check_type(argument)?; - Ok(Value::Boolean(boolean)) - } - Value::String(_) - | Value::Float(_) - | Value::Integer(_) - | Value::Boolean(_) - | Value::List(_) - | Value::Map(_) - | Value::Table(_) - | Value::Time(_) - | Value::Function(_) => Err(Error::TypeCheckFailure { - tool_info: self.info(), - argument: argument.clone(), - }), + if let Value::Empty = argument { + let boolean = rand::thread_rng().gen(); + + Ok(Value::Boolean(boolean)) + } else { + self.fail(argument) } } } @@ -72,10 +62,7 @@ impl Tool for RandomInteger { Ok(Value::Integer(integer)) } Value::Empty => Ok(crate::Value::Integer(random())), - _ => Err(Error::TypeCheckFailure { - tool_info: self.info(), - argument: argument.clone(), - }), + _ => self.fail(argument), } } } @@ -93,35 +80,34 @@ impl Tool for RandomString { } fn run(&self, argument: &Value) -> Result { - match argument { - Value::Integer(length) => { - let length: usize = length.unsigned_abs().try_into().unwrap_or(0); - let mut random = String::with_capacity(length); + let argument = self.check_type(argument)?; - for _ in 0..length { - let random_char = thread_rng().gen_range('A'..='z').to_string(); + if let Value::Integer(length) = argument { + let length: usize = length.unsigned_abs().try_into().unwrap_or(0); + let mut random = String::with_capacity(length); - random.push_str(&random_char); - } + for _ in 0..length { + let random_char = thread_rng().gen_range('A'..='z').to_string(); - Ok(Value::String(random)) + random.push_str(&random_char); } - Value::Empty => { - let mut random = String::with_capacity(10); - for _ in 0..10 { - let random_char = thread_rng().gen_range('A'..='z').to_string(); - - random.push_str(&random_char); - } - - Ok(Value::String(random)) - } - _ => Err(Error::TypeCheckFailure { - tool_info: self.info(), - argument: argument.clone(), - }), + return Ok(Value::String(random)); } + + if let Value::Empty = argument { + let mut random = String::with_capacity(10); + + for _ in 0..10 { + let random_char = thread_rng().gen_range('A'..='z').to_string(); + + random.push_str(&random_char); + } + + return Ok(Value::String(random)); + } + + self.fail(argument) } } @@ -133,14 +119,18 @@ impl Tool for RandomFloat { identifier: "random_float", description: "Generate a random floating point value between 0 and 1.", group: "random", - inputs: vec![], + inputs: vec![ValueType::Empty], } } fn run(&self, argument: &Value) -> Result { - argument.as_empty()?; + let argument = self.check_type(argument)?; - Ok(Value::Float(random())) + if argument.is_empty() { + Ok(Value::Float(random())) + } else { + self.fail(argument) + } } } @@ -150,22 +140,22 @@ impl Tool for Random { fn info(&self) -> ToolInfo<'static> { ToolInfo { identifier: "random", - description: "Select a random item from a collection.", + description: "Select a random item from a list.", group: "random", - inputs: vec![], + inputs: vec![ValueType::List], } } fn run(&self, argument: &Value) -> Result { - if let Ok(list) = argument.as_list() { + let argument = self.check_type(argument)?; + + if let Value::List(list) = argument { let random_index = thread_rng().gen_range(0..list.len()); let random_item = list.get(random_index).unwrap(); Ok(random_item.clone()) } else { - Err(Error::ExpectedCollection { - actual: argument.clone(), - }) + self.fail(argument) } } }