1#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
2#[serde(tag = "type")]
3pub enum Boolean {
4 IsNotNull {
6 value: ftd::PropertyValue,
7 },
8 IsNull {
10 value: ftd::PropertyValue,
11 },
12 IsNotEmpty {
14 value: ftd::PropertyValue,
15 },
16 IsEmpty {
18 value: ftd::PropertyValue,
19 },
20 Equal {
22 left: ftd::PropertyValue,
23 right: ftd::PropertyValue,
24 },
25 NotEqual {
27 left: ftd::PropertyValue,
28 right: ftd::PropertyValue,
29 },
30 Not {
32 of: Box<Boolean>,
33 },
34 Literal {
36 value: bool,
37 },
38 ListIsEmpty {
40 value: ftd::PropertyValue,
41 },
42}
43
44impl Boolean {
45 pub fn to_condition(
46 &self,
47 line_number: usize,
48 doc: &ftd::p2::TDoc,
49 ) -> ftd::p1::Result<ftd::Condition> {
50 let (variable, value) = match self {
51 Self::Equal { left, right } => {
52 let variable = resolve_variable(left, line_number, doc)?;
53
54 let value = match right {
55 ftd::PropertyValue::Value { value } => value.to_owned(),
56 ftd::PropertyValue::Variable { name, .. } => doc.get_value(0, name)?,
57 _ => {
58 return ftd::p2::utils::e2(
59 format!("{:?} must be value or argument", right),
60 doc.name,
61 line_number,
62 );
63 }
64 };
65
66 (variable, value)
67 }
68 Self::IsNotNull { value } => {
69 let variable = resolve_variable(value, line_number, doc)?;
70 (
71 variable,
72 ftd::Value::String {
73 text: "$IsNotNull$".to_string(),
74 source: ftd::TextSource::Header,
75 },
76 )
77 }
78 Self::IsNull { value } => {
79 let variable = resolve_variable(value, line_number, doc)?;
80 (
81 variable,
82 ftd::Value::String {
83 text: "$IsNull$".to_string(),
84 source: ftd::TextSource::Header,
85 },
86 )
87 }
88 _ => {
89 return ftd::p2::utils::e2(
90 format!("{:?} must not happen", self),
91 doc.name,
92 line_number,
93 )
94 }
95 };
96 return match value.to_serde_value() {
97 None => {
98 return ftd::p2::utils::e2(
99 format!(
100 "expected value of type String, Integer, Decimal or Boolean, found: {:?}",
101 value
102 ),
103 doc.name,
104 line_number,
105 )
106 }
107 Some(value) => Ok(ftd::Condition { variable, value }),
108 };
109
110 fn resolve_variable(
111 value: &ftd::PropertyValue,
112 line_number: usize,
113 doc: &ftd::p2::TDoc,
114 ) -> ftd::p1::Result<String> {
115 match value {
116 ftd::PropertyValue::Variable { name, .. }
117 | ftd::PropertyValue::Reference { name, .. } => Ok(name.to_string()),
118 _ => ftd::p2::utils::e2(
119 format!("{:?} must be variable or local variable", value),
120 doc.name,
121 line_number,
122 ),
123 }
124 }
125 }
126
127 pub fn boolean_left_right(
128 line_number: usize,
129 expr: &str,
130 doc_id: &str,
131 ) -> ftd::p1::Result<(String, String, Option<String>)> {
132 let expr: String = expr.split_whitespace().collect::<Vec<&str>>().join(" ");
133 if expr == "true" || expr == "false" {
134 return Ok(("Literal".to_string(), expr, None));
135 }
136 let (left, rest) = match expr.split_once(' ') {
137 None => return Ok(("Equal".to_string(), expr.to_string(), None)),
138 Some(v) => v,
139 };
140 if left == "not" {
141 return Ok(("NotEqual".to_string(), rest.to_string(), None));
142 }
143 Ok(match rest {
144 "is not null" => ("IsNotNull".to_string(), left.to_string(), None),
145 "is null" => ("IsNull".to_string(), left.to_string(), None),
146 "is not empty" => ("IsNotEmpty".to_string(), left.to_string(), None),
147 "is empty" => ("IsEmpty".to_string(), left.to_string(), None),
148 _ if rest.starts_with("==") => (
149 "Equal".to_string(),
150 left.to_string(),
151 Some(rest.replace("==", "").trim().to_string()),
152 ),
153 _ => {
154 return ftd::p2::utils::e2(
155 format!("'{}' is not valid condition", rest),
156 doc_id,
157 line_number,
158 )
159 }
160 })
161 }
162
163 pub fn from_expression(
164 expr: &str,
165 doc: &ftd::p2::TDoc,
166 arguments: &ftd::Map<ftd::p2::Kind>,
167 left_right_resolved_property: (Option<ftd::PropertyValue>, Option<ftd::PropertyValue>),
168 line_number: usize,
169 ) -> ftd::p1::Result<Self> {
170 let (boolean, mut left, mut right) =
171 ftd::p2::Boolean::boolean_left_right(line_number, expr, doc.name)?;
172 left = doc.resolve_reference_name(line_number, left.as_str(), arguments)?;
173 if let Some(ref r) = right {
174 right = doc.resolve_reference_name(line_number, r, arguments).ok();
175 }
176 return Ok(match boolean.as_str() {
177 "Literal" => Boolean::Literal {
178 value: left == "true",
179 },
180 "IsNotNull" | "IsNull" => {
181 let value = if !left.starts_with("$PARENT") {
182 let value = property_value(
183 &left,
184 None,
185 doc,
186 arguments,
187 left_right_resolved_property.0,
188 line_number,
189 )?;
190 if !value.kind().is_optional() {
191 return ftd::p2::utils::e2(
192 format!("'{}' is not to an optional", left),
193 doc.name,
194 line_number,
195 );
196 }
197 value
198 } else {
199 property_value(
200 &left,
201 None,
202 doc,
203 arguments,
204 left_right_resolved_property.0,
205 line_number,
206 )
207 .unwrap_or(ftd::PropertyValue::Variable {
208 name: left.trim_start_matches('$').to_string(),
209 kind: ftd::p2::Kind::Element,
210 })
211 };
212 if boolean.as_str() == "IsNotNull" {
213 Boolean::IsNotNull { value }
214 } else {
215 Boolean::IsNull { value }
216 }
217 }
218 "IsNotEmpty" | "IsEmpty" => {
219 let value = property_value(
220 &left,
221 None,
222 doc,
223 arguments,
224 left_right_resolved_property.0,
225 line_number,
226 )?;
227 if !value.kind().is_list() {
228 return ftd::p2::utils::e2(
229 format!("'{}' is not to a list", left),
230 doc.name,
231 line_number,
232 );
233 }
234 if boolean.as_str() == "IsNotEmpty" {
235 Boolean::IsNotEmpty { value }
236 } else {
237 Boolean::IsEmpty { value }
238 }
239 }
240 "NotEqual" | "Equal" => {
241 if let Some(right) = right {
242 let left = property_value(
243 &left,
244 None,
245 doc,
246 arguments,
247 left_right_resolved_property.0,
248 line_number,
249 )?;
250 Boolean::Equal {
251 left: left.to_owned(),
252 right: property_value(
253 &right,
254 Some(left.kind()),
255 doc,
256 arguments,
257 left_right_resolved_property.1,
258 line_number,
259 )?,
260 }
261 } else {
262 Boolean::Equal {
263 left: property_value(
264 &left,
265 Some(ftd::p2::Kind::boolean()),
266 doc,
267 arguments,
268 left_right_resolved_property.0,
269 line_number,
270 )?,
271 right: ftd::PropertyValue::Value {
272 value: ftd::Value::Boolean {
273 value: boolean.as_str() == "Equal",
274 },
275 },
276 }
277 }
278 }
279 _ => {
280 return ftd::p2::utils::e2(
281 format!("'{}' is not valid condition", expr),
282 doc.name,
283 line_number,
284 )
285 }
286 });
287
288 fn property_value(
289 value: &str,
290 expected_kind: Option<ftd::p2::Kind>,
291 doc: &ftd::p2::TDoc,
292 arguments: &ftd::Map<ftd::p2::Kind>,
293 loop_already_resolved_property: Option<ftd::PropertyValue>,
294 line_number: usize,
295 ) -> ftd::p1::Result<ftd::PropertyValue> {
296 Ok(
297 match ftd::PropertyValue::resolve_value(
298 line_number,
299 value,
300 expected_kind,
301 doc,
302 arguments,
303 None,
304 ) {
305 Ok(v) => v,
306 Err(e) => match &loop_already_resolved_property {
307 Some(ftd::PropertyValue::Variable { .. }) => {
308 loop_already_resolved_property.clone().expect("")
309 }
310 _ if value.starts_with("$PARENT") => ftd::PropertyValue::Variable {
311 name: value.trim_start_matches('$').to_string(),
312 kind: ftd::p2::Kind::Element,
313 },
314 _ => return Err(e),
315 },
316 },
317 )
318 }
319 }
320
321 pub fn is_constant(&self) -> bool {
322 let is_loop_constant = {
323 let mut constant = false;
324 if let ftd::p2::Boolean::Equal {
325 left: ftd::PropertyValue::Variable { name, .. },
326 right: ftd::PropertyValue::Value { .. },
327 } = self
328 {
329 if name.starts_with("$loop$") {
330 constant = true;
331 }
332 }
333 constant
334 };
335 (!matches!(
336 self,
337 Self::Equal {
338 left: ftd::PropertyValue::Reference { .. },
339 right: ftd::PropertyValue::Value { .. },
340 ..
341 }
342 ) && !matches!(
343 self,
344 Self::Equal {
345 left: ftd::PropertyValue::Variable { .. },
346 right: ftd::PropertyValue::Value { .. },
347 ..
348 }
349 ) && !matches!(self, Self::IsNotNull { .. })
350 && !matches!(self, Self::IsNull { .. }))
351 || is_loop_constant
352 }
353
354 pub fn is_arg_constant(&self) -> bool {
355 let is_loop_constant = {
356 let mut constant = false;
357 if let ftd::p2::Boolean::Equal {
358 left: ftd::PropertyValue::Variable { name, .. },
359 right: ftd::PropertyValue::Value { .. },
360 } = self
361 {
362 if name.starts_with("$loop$") {
363 constant = true;
364 }
365 }
366 constant
367 };
368 (!matches!(
369 self,
370 Self::Equal {
371 left: ftd::PropertyValue::Reference { .. },
372 right: ftd::PropertyValue::Value { .. },
373 ..
374 }
375 ) && !matches!(
376 self,
377 Self::Equal {
378 left: ftd::PropertyValue::Variable { .. },
379 right: ftd::PropertyValue::Value { .. },
380 ..
381 }
382 ) && !matches!(
383 self,
384 Self::Equal {
385 left: ftd::PropertyValue::Reference { .. },
386 right: ftd::PropertyValue::Variable { .. },
387 ..
388 }
389 ) && !matches!(
390 self,
391 Self::Equal {
392 left: ftd::PropertyValue::Variable { .. },
393 right: ftd::PropertyValue::Variable { .. },
394 ..
395 }
396 ) && !matches!(self, Self::IsNotNull { .. })
397 && !matches!(self, Self::IsNull { .. }))
398 || is_loop_constant
399 }
400
401 pub fn eval(&self, line_number: usize, doc: &ftd::p2::TDoc) -> ftd::p1::Result<bool> {
402 Ok(match self {
403 Self::Literal { value } => *value,
404 Self::IsNotNull { value } => !value.resolve(line_number, doc)?.is_null(),
405 Self::IsNull { value } => value.resolve(line_number, doc)?.is_null(),
406 Self::IsNotEmpty { value } => !value.resolve(line_number, doc)?.is_empty(),
407 Self::IsEmpty { value } => value.resolve(line_number, doc)?.is_empty(),
408 Self::Equal { left, right } => left
409 .resolve(line_number, doc)?
410 .is_equal(&right.resolve(line_number, doc)?),
411 _ => {
412 return ftd::p2::utils::e2(
413 format!("unknown Boolean found: {:?}", self),
414 doc.name,
415 line_number,
416 )
417 }
418 })
419 }
420
421 pub fn set_null(&self, line_number: usize, doc_id: &str) -> ftd::p1::Result<bool> {
422 Ok(match self {
423 Self::Literal { .. } | Self::IsNotEmpty { .. } | Self::IsEmpty { .. } => true,
424 Self::Equal { left, right } => match (left, right) {
425 (ftd::PropertyValue::Value { .. }, ftd::PropertyValue::Value { .. })
426 | (ftd::PropertyValue::Value { .. }, ftd::PropertyValue::Variable { .. })
427 | (ftd::PropertyValue::Variable { .. }, ftd::PropertyValue::Value { .. })
428 | (ftd::PropertyValue::Variable { .. }, ftd::PropertyValue::Variable { .. }) => {
429 true
430 }
431 _ => false,
432 },
433 Self::IsNotNull { .. } | Self::IsNull { .. } => false,
434 _ => {
435 return ftd::p2::utils::e2(
436 format!("unimplemented for type: {:?}", self),
437 doc_id,
438 line_number,
439 )
440 }
441 })
442 }
443}