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}