Skip to main content

rs_teststand/expression/
check.rs

1//! Checking an expression before it is run.
2//!
3//! Both engine checks answer the same shape of question: is this expression
4//! usable, and if not, what is wrong and where. The engine reports that split
5//! across a boolean return and three by-reference arguments, which this module
6//! folds into one value.
7
8use std::fmt;
9
10bitflags::bitflags! {
11    /// How the engine evaluates an expression (`EvaluationOptions`).
12    ///
13    /// See the
14    /// [EvaluationOptions reference](https://www.ni.com/docs/en-US/bundle/teststand-api-reference/page/reference/evaluationoptions.html).
15    #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16    pub struct EvaluationOptions: i32 {
17        /// Default behavior (`EvalOption_NoOptions`).
18        const NONE = 0;
19        /// Leave every variable and property the expression touches unchanged
20        /// (`EvalOption_DoNotAlterValues`).
21        const DO_NOT_ALTER_VALUES = 1;
22        /// Accept an empty expression instead of calling it a syntax error
23        /// (`EvalOption_AllowEmptyExpression`).
24        const ALLOW_EMPTY_EXPRESSION = 2;
25        /// Subscripting an empty array yields a value of the element type
26        /// rather than an error (`EvalOption_AllowIndexingEmptyArrays`).
27        const ALLOW_INDEXING_EMPTY_ARRAYS = 4;
28        /// Stand in temporary objects for variables the expression names but
29        /// that do not exist (`EvalOption_CreateNonExistentVariables`).
30        const CREATE_NON_EXISTENT_VARIABLES = 8;
31        /// Actually evaluate, rather than only parse, so a fault that would
32        /// only surface during a run is found now
33        /// (`EvalOption_ForErrorChecking`).
34        const FOR_ERROR_CHECKING = 16;
35        /// Report errors the `#NoValidation` directive would otherwise suppress
36        /// (`EvalOption_IgnoreNoValidationDirective`).
37        const IGNORE_NO_VALIDATION_DIRECTIVE = 32;
38    }
39}
40
41/// What is wrong with an expression, and where.
42///
43/// The positions are indices into the expression text that was checked: `start`
44/// is where the error begins and `end` is the first character past it, so the
45/// pair slices the offending span.
46#[derive(Debug, Clone, PartialEq, Eq)]
47#[non_exhaustive]
48pub struct ExpressionError {
49    /// The engine's description of the error.
50    pub description: String,
51    /// Index in the expression where the error begins.
52    pub start: i32,
53    /// Index of the first character beyond the error.
54    pub end: i32,
55}
56
57impl ExpressionError {
58    /// Builds an error from what the engine wrote into its output arguments.
59    pub(crate) const fn new(description: String, start: i32, end: i32) -> Self {
60        Self {
61            description,
62            start,
63            end,
64        }
65    }
66
67    /// The part of `expression` the engine flagged, when the reported span
68    /// falls inside it.
69    ///
70    /// A caller underlining the error in a panel wants the span rather than the
71    /// indices. Returns `None` if the span is not a valid range within the text,
72    /// which is what the engine reports when the error has no single location.
73    #[must_use]
74    pub fn span<'text>(&self, expression: &'text str) -> Option<&'text str> {
75        let start = usize::try_from(self.start).ok()?;
76        let end = usize::try_from(self.end).ok()?;
77        expression.get(start..end)
78    }
79}
80
81impl fmt::Display for ExpressionError {
82    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
83        write!(
84            formatter,
85            "{} (characters {} to {})",
86            self.description, self.start, self.end
87        )
88    }
89}
90
91impl std::error::Error for ExpressionError {}
92
93#[cfg(test)]
94mod tests {
95    use super::{EvaluationOptions, ExpressionError};
96
97    /// The reported span slices the text the caller checked, which is what a
98    /// panel underlines.
99    #[test]
100    fn an_error_locates_itself_in_the_expression_it_came_from() {
101        let error = ExpressionError::new("undefined variable".to_owned(), 7, 13);
102        assert_eq!(error.span("Locals.absent + 1"), Some("absent"));
103    }
104
105    /// The engine reports 0 and 0 for an error with no single location, and a
106    /// caller should get nothing rather than an empty slice at the front.
107    #[test]
108    fn an_error_out_of_range_reports_no_span() {
109        let error = ExpressionError::new("bad".to_owned(), 4, 99);
110        assert_eq!(error.span("abc"), None);
111    }
112
113    /// The default is the engine's own default, not an arbitrary bit.
114    #[test]
115    fn the_default_evaluation_is_no_options() {
116        assert_eq!(EvaluationOptions::default().bits(), 0);
117    }
118}