• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geo-engine / geoengine / 3929938005

pending completion
3929938005

push

github

GitHub
Merge #713

84930 of 96741 relevant lines covered (87.79%)

79640.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.74
/operators/src/processing/expression/parser.rs
1
use std::{cell::RefCell, collections::HashSet, rc::Rc};
2

3
use pest::{
4
    iterators::{Pair, Pairs},
5
    pratt_parser::{Assoc, Op, PrattParser},
6
    Parser,
7
};
8
use pest_derive::Parser;
9
use snafu::{ensure, ResultExt};
10

11
use crate::{processing::expression::codegen::Parameter, util::duplicate_or_empty_str_slice};
12

13
use super::{
14
    codegen::{
15
        Assignment, AstFunction, AstNode, AstOperator, BooleanComparator, BooleanExpression,
16
        BooleanOperator, Branch, ExpressionAst, Identifier,
17
    },
18
    error::{self, ExpressionError},
19
    functions::FUNCTIONS,
20
};
21

22
type Result<T, E = ExpressionError> = std::result::Result<T, E>;
23

24
pub type PestError = pest::error::Error<Rule>;
25

26
#[derive(Parser)]
8,063✔
27
#[grammar = "processing/expression/expression.pest"] // relative to src
28
struct _ExpressionParser;
29

30
/// A parser for user-defined expressions.
31
pub struct ExpressionParser {
32
    parameters: Vec<Parameter>,
33
    numeric_parameters: HashSet<Identifier>,
34
    variables: Rc<RefCell<Vec<Identifier>>>,
35
    functions: Rc<RefCell<Vec<AstFunction>>>,
36
}
37

38
lazy_static::lazy_static! {
39
    static ref EXPRESSION_PARSER: PrattParser<Rule> = PrattParser::new()
40
        .op(Op::infix(Rule::add, Assoc::Left) | Op::infix(Rule::subtract, Assoc::Left))
41
        .op(Op::infix(Rule::multiply, Assoc::Left) | Op::infix(Rule::divide, Assoc::Left))
42
        .op(Op::infix(Rule::power, Assoc::Right));
43

44
    static ref BOOLEAN_EXPRESSION_PARSER: PrattParser<Rule> = PrattParser::new()
45
        .op(Op::infix(Rule::or, Assoc::Left))
46
        .op(Op::infix(Rule::and, Assoc::Left));
47
}
48

49
impl ExpressionParser {
50
    pub fn new(parameters: &[Parameter]) -> Result<Self> {
33✔
51
        match duplicate_or_empty_str_slice(parameters) {
33✔
52
            crate::util::DuplicateOrEmpty::Ok => (), // fine
33✔
53
            crate::util::DuplicateOrEmpty::Duplicate(parameter) => {
×
54
                return Err(ExpressionError::DuplicateParameterName { parameter });
×
55
            }
56
            crate::util::DuplicateOrEmpty::Empty => {
57
                return Err(ExpressionError::EmptyParameterName);
×
58
            }
59
        };
60

61
        let mut numeric_parameters = HashSet::with_capacity(parameters.len());
33✔
62
        for parameter in parameters {
64✔
63
            match parameter {
31✔
64
                Parameter::Number(name) => numeric_parameters.insert(name.clone()),
31✔
65
            };
31✔
66
        }
31✔
67

68
        Ok(Self {
33✔
69
            parameters: parameters.to_vec(),
33✔
70
            numeric_parameters,
33✔
71
            variables: Rc::new(RefCell::new(Vec::new())),
33✔
72
            functions: Rc::new(RefCell::new(vec![])),
33✔
73
        })
33✔
74
    }
33✔
75

76
    pub fn parse(self, name: &str, input: &str) -> Result<ExpressionAst> {
33✔
77
        ensure!(!name.is_empty(), error::EmptyExpressionName);
33✔
78

79
        let pairs = _ExpressionParser::parse(Rule::main, input).context(error::Parser)?;
33✔
80

81
        let root = self.build_ast(pairs)?;
33✔
82

83
        ExpressionAst::new(
33✔
84
            name.to_string().into(),
33✔
85
            self.parameters,
33✔
86
            self.functions.borrow_mut().drain(..).collect(),
33✔
87
            root,
33✔
88
        )
33✔
89
    }
33✔
90

91
    fn build_ast(&self, pairs: Pairs<'_, Rule>) -> Result<AstNode> {
129✔
92
        EXPRESSION_PARSER
129✔
93
            .map_primary(|primary| self.resolve_expression_rule(primary))
165✔
94
            .map_infix(|left, op, right| self.resolve_infix_operations(left, &op, right))
129✔
95
            // TODO: is there another way to remove EOIs?
129✔
96
            .parse(pairs.filter(|pair| pair.as_rule() != Rule::EOI))
234✔
97
    }
129✔
98

99
    fn resolve_expression_rule(&self, pair: Pair<Rule>) -> Result<AstNode> {
165✔
100
        match pair.as_rule() {
165✔
101
            Rule::expression => self.build_ast(pair.into_inner()),
2✔
102
            Rule::number => Ok(AstNode::Constant(
103
                pair.as_str().parse().context(error::InvalidNumber)?,
61✔
104
            )),
105
            Rule::identifier => {
106
                let identifier = pair.as_str().into();
36✔
107
                if self.numeric_parameters.contains(&identifier)
36✔
108
                    || self.variables.borrow().contains(&identifier)
2✔
109
                {
110
                    Ok(AstNode::Variable(identifier))
36✔
111
                } else {
112
                    Err(ExpressionError::UnknownVariable {
×
113
                        variable: identifier.to_string(),
×
114
                    })
×
115
                }
116
            }
117
            Rule::nodata => Ok(AstNode::NoData),
2✔
118
            Rule::function => self.resolve_function(pair.into_inner()),
24✔
119
            Rule::branch => {
120
                // pairs are boolean -> expression
121
                // and last one is just an expression
122
                let mut pairs = pair.into_inner();
7✔
123

7✔
124
                let mut condition_branches: Vec<Branch> = vec![];
7✔
125

126
                while let Some(pair) = pairs.next() {
19✔
127
                    if matches!(pair.as_rule(), Rule::boolean_expression) {
19✔
128
                        let boolean = self.build_boolean_expression(pair.into_inner())?;
12✔
129

130
                        let next_pair = pairs
12✔
131
                            .next()
12✔
132
                            .ok_or(ExpressionError::BranchStructureMalformed)?;
12✔
133
                        let expression = self.build_ast(next_pair.into_inner())?;
12✔
134

135
                        condition_branches.push(Branch {
12✔
136
                            condition: boolean,
12✔
137
                            body: expression,
12✔
138
                        });
12✔
139
                    } else {
140
                        let expression = self.build_ast(pair.into_inner())?;
7✔
141

142
                        return Ok(AstNode::Branch {
7✔
143
                            condition_branches,
7✔
144
                            else_branch: Box::new(expression),
7✔
145
                        });
7✔
146
                    }
147
                }
148

149
                Err(ExpressionError::UnexpectedBranchStructure)
×
150
            }
151
            Rule::assignments_and_expression => {
152
                let mut assignments: Vec<Assignment> = vec![];
33✔
153

154
                for pair in pair.into_inner() {
35✔
155
                    if matches!(pair.as_rule(), Rule::assignment) {
35✔
156
                        let mut pairs = pair.into_inner();
2✔
157

158
                        let first_pair = pairs
2✔
159
                            .next()
2✔
160
                            .ok_or(ExpressionError::AssignmentNeedsTwoParts)?;
2✔
161
                        let second_pair = pairs
2✔
162
                            .next()
2✔
163
                            .ok_or(ExpressionError::AssignmentNeedsTwoParts)?;
2✔
164

165
                        let identifier = first_pair.as_str().into();
2✔
166

2✔
167
                        if self.numeric_parameters.contains(&identifier) {
2✔
168
                            return Err(ExpressionError::CannotAssignToParameter {
×
169
                                parameter: identifier.to_string(),
×
170
                            });
×
171
                        }
2✔
172

2✔
173
                        // having an assignment allows more variables
2✔
174
                        self.variables.borrow_mut().push(identifier.clone());
2✔
175

176
                        let expression = self.build_ast(second_pair.into_inner())?;
2✔
177

178
                        assignments.push(Assignment {
2✔
179
                            identifier,
2✔
180
                            expression,
2✔
181
                        });
2✔
182
                    } else {
183
                        let expression = self.build_ast(pair.into_inner())?;
33✔
184

185
                        return Ok(AstNode::AssignmentsAndExpression {
33✔
186
                            assignments,
33✔
187
                            expression: Box::new(expression),
33✔
188
                        });
33✔
189
                    }
190
                }
191

192
                Err(ExpressionError::DoesNotEndWithExpression)
×
193
            }
194
            _ => Err(ExpressionError::UnexpectedRule {
×
195
                rule: format!("{:?}", pair.as_rule()),
×
196
            }),
×
197
        }
198
    }
165✔
199

200
    fn resolve_function(&self, mut pairs: Pairs<Rule>) -> Result<AstNode> {
24✔
201
        // first one is name
202
        let name: Identifier = pairs
24✔
203
            .next()
24✔
204
            .ok_or(ExpressionError::MissingFunctionName)?
24✔
205
            .as_str()
24✔
206
            .into();
24✔
207

208
        let args = pairs
24✔
209
            .map(|pair| self.build_ast(pair.into_inner()))
30✔
210
            .collect::<Result<Vec<_>, _>>()?;
24✔
211

212
        match FUNCTIONS.get(name.as_ref()) {
24✔
213
            Some(function) if function.num_args.contains(&args.len()) => {
24✔
214
                self.functions.borrow_mut().push(AstFunction {
24✔
215
                    name: name.clone(),
24✔
216
                    num_parameters: args.len(),
24✔
217
                });
24✔
218

24✔
219
                Ok(AstNode::Function { name, args })
24✔
220
            }
221
            Some(function) => Err(ExpressionError::InvalidFunctionArgumentCount {
×
222
                function: name.to_string(),
×
223
                expected_min: *function.num_args.start(),
×
224
                expected_max: *function.num_args.end(),
×
225
                actual: args.len(),
×
226
            }),
×
227
            None => Err(ExpressionError::UnknownFunction {
×
228
                function: name.to_string(),
×
229
            }),
×
230
        }
231
    }
24✔
232

233
    fn resolve_infix_operations(
36✔
234
        &self,
36✔
235
        left: Result<AstNode>,
36✔
236
        op: &Pair<Rule>,
36✔
237
        right: Result<AstNode>,
36✔
238
    ) -> Result<AstNode> {
36✔
239
        let (left, right) = (left?, right?);
36✔
240

241
        // change some operators to functions
242
        if matches!(op.as_rule(), Rule::power) {
36✔
243
            self.functions.borrow_mut().push(AstFunction {
1✔
244
                name: "pow".into(),
1✔
245
                num_parameters: 2,
1✔
246
            });
1✔
247

1✔
248
            return Ok(AstNode::Function {
1✔
249
                name: "pow".into(),
1✔
250
                args: vec![left, right],
1✔
251
            });
1✔
252
        }
35✔
253

254
        let ast_operator = match op.as_rule() {
35✔
255
            Rule::add => AstOperator::Add,
23✔
256
            Rule::subtract => AstOperator::Subtract,
4✔
257
            Rule::multiply => AstOperator::Multiply,
6✔
258
            Rule::divide => AstOperator::Divide,
2✔
259
            _ => {
260
                return Err(ExpressionError::UnexpectedOperator {
×
261
                    operator: format!("{:?}", op.as_rule()),
×
262
                })
×
263
            }
264
        };
265

266
        Ok(AstNode::Operation {
35✔
267
            left: Box::new(left),
35✔
268
            op: ast_operator,
35✔
269
            right: Box::new(right),
35✔
270
        })
35✔
271
    }
36✔
272

273
    fn build_boolean_expression(&self, pairs: Pairs<'_, Rule>) -> Result<BooleanExpression> {
13✔
274
        BOOLEAN_EXPRESSION_PARSER
13✔
275
            .map_primary(|primary| self.resolve_boolean_expression_rule(primary))
15✔
276
            .map_infix(|left, op, right| Self::resolve_infix_boolean_operations(left, &op, right))
13✔
277
            .parse(pairs)
13✔
278
    }
13✔
279

280
    fn resolve_boolean_expression_rule(&self, pair: Pair<Rule>) -> Result<BooleanExpression> {
15✔
281
        match pair.as_rule() {
15✔
282
            Rule::identifier_is_nodata => {
283
                // convert `A IS NODATA` to the check for `A.is_none()`
284

285
                let mut pairs = pair.into_inner();
3✔
286

287
                let identifier = pairs
3✔
288
                    .next()
3✔
289
                    .ok_or(ExpressionError::MissingIdentifier)?
3✔
290
                    .as_str()
3✔
291
                    .into();
3✔
292

3✔
293
                Ok(BooleanExpression::Comparison {
3✔
294
                    left: Box::new(AstNode::Variable(identifier)),
3✔
295
                    op: BooleanComparator::Equal,
3✔
296
                    right: Box::new(AstNode::NoData),
3✔
297
                })
3✔
298
            }
299
            Rule::boolean_true => Ok(BooleanExpression::Constant(true)),
4✔
300
            Rule::boolean_false => Ok(BooleanExpression::Constant(false)),
2✔
301
            Rule::boolean_comparison => {
302
                let mut pairs = pair.into_inner();
5✔
303

304
                let first_pair = pairs
5✔
305
                    .next()
5✔
306
                    .ok_or(ExpressionError::ComparisonNeedsThreeParts)?;
5✔
307
                let second_pair = pairs
5✔
308
                    .next()
5✔
309
                    .ok_or(ExpressionError::ComparisonNeedsThreeParts)?;
5✔
310
                let third_pair = pairs
5✔
311
                    .next()
5✔
312
                    .ok_or(ExpressionError::ComparisonNeedsThreeParts)?;
5✔
313

314
                let left_expression = self.build_ast(first_pair.into_inner())?;
5✔
315
                let comparison = match second_pair.as_rule() {
5✔
316
                    Rule::equals => BooleanComparator::Equal,
2✔
317
                    Rule::not_equals => BooleanComparator::NotEqual,
×
318
                    Rule::smaller => BooleanComparator::LessThan,
3✔
319
                    Rule::smaller_equals => BooleanComparator::LessThanOrEqual,
×
320
                    Rule::larger => BooleanComparator::GreaterThan,
×
321
                    Rule::larger_equals => BooleanComparator::GreaterThanOrEqual,
×
322
                    _ => {
323
                        return Err(ExpressionError::UnexpectedComparator {
×
324
                            comparator: format!("{:?}", second_pair.as_rule()),
×
325
                        })
×
326
                    }
327
                };
328
                let right_expression = self.build_ast(third_pair.into_inner())?;
5✔
329

330
                Ok(BooleanExpression::Comparison {
5✔
331
                    left: Box::new(left_expression),
5✔
332
                    op: comparison,
5✔
333
                    right: Box::new(right_expression),
5✔
334
                })
5✔
335
            }
336
            Rule::boolean_expression => self.build_boolean_expression(pair.into_inner()),
1✔
337
            _ => Err(ExpressionError::UnexpectedBooleanRule {
×
338
                rule: format!("{:?}", pair.as_rule()),
×
339
            }),
×
340
        }
341
    }
15✔
342

343
    fn resolve_infix_boolean_operations(
2✔
344
        left: Result<BooleanExpression>,
2✔
345
        op: &Pair<Rule>,
2✔
346
        right: Result<BooleanExpression>,
2✔
347
    ) -> Result<BooleanExpression> {
2✔
348
        let (left, right) = (left?, right?);
2✔
349

350
        let boolean_operator = match op.as_rule() {
2✔
351
            Rule::and => BooleanOperator::And,
2✔
352
            Rule::or => BooleanOperator::Or,
×
353
            _ => {
354
                return Err(ExpressionError::UnexpectedBooleanOperator {
×
355
                    operator: format!("{:?}", op.as_rule()),
×
356
                });
×
357
            }
358
        };
359

360
        Ok(BooleanExpression::Operation {
2✔
361
            left: Box::new(left),
2✔
362
            op: boolean_operator,
2✔
363
            right: Box::new(right),
2✔
364
        })
2✔
365
    }
2✔
366
}
367

368
#[cfg(test)]
369
mod tests {
370
    use super::*;
371

372
    use quote::{quote, ToTokens};
373

374
    fn parse(name: &str, parameters: &[&str], input: &str) -> String {
23✔
375
        let parameters: Vec<Parameter> = parameters
23✔
376
            .iter()
23✔
377
            .map(|&p| Parameter::Number(Identifier::from(p)))
23✔
378
            .collect();
23✔
379

23✔
380
        let parser = ExpressionParser::new(&parameters).unwrap();
23✔
381
        let ast = parser.parse(name, input).unwrap();
23✔
382

23✔
383
        ast.into_token_stream().to_string()
23✔
384
    }
23✔
385

386
    fn prelude() -> impl ToTokens {
6✔
387
        quote! {
6✔
388
            #[inline]
6✔
389
            fn apply(a: Option<f64>, b: Option<f64>, f: fn(f64, f64) -> f64) -> Option<f64> {
6✔
390
                match (a, b) {
6✔
391
                    (Some(a), Some(b)) => Some(f(a, b)),
6✔
392
                    _ => None,
6✔
393
                }
6✔
394
            }
6✔
395
        }
6✔
396
    }
6✔
397

398
    #[test]
1✔
399
    fn simple() {
1✔
400
        let prelude = prelude();
1✔
401

1✔
402
        assert_eq!(
1✔
403
            parse("expression", &[], "1"),
1✔
404
            quote! {
1✔
405
                #prelude
1✔
406

1✔
407
                #[no_mangle]
1✔
408
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
409
                    Some(1f64)
1✔
410
                }
1✔
411
            }
1✔
412
            .to_string()
1✔
413
        );
1✔
414

415
        assert_eq!(
1✔
416
            parse("foo", &[], "1 + 2"),
1✔
417
            quote! {
1✔
418
                #prelude
1✔
419

1✔
420
                #[no_mangle]
1✔
421
                pub extern "Rust" fn foo() -> Option<f64> {
1✔
422
                    apply(Some(1f64), Some(2f64), std::ops::Add::add)
1✔
423
                }
1✔
424
            }
1✔
425
            .to_string()
1✔
426
        );
1✔
427

428
        assert_eq!(
1✔
429
            parse("bar", &[], "-1 + 2"),
1✔
430
            quote! {
1✔
431
                #prelude
1✔
432

1✔
433
                #[no_mangle]
1✔
434
                pub extern "Rust" fn bar() -> Option<f64> {
1✔
435
                    apply(Some(-1f64), Some(2f64), std::ops::Add::add)
1✔
436
                }
1✔
437
            }
1✔
438
            .to_string()
1✔
439
        );
1✔
440

441
        assert_eq!(
1✔
442
            parse("baz", &[], "1 - -2"),
1✔
443
            quote! {
1✔
444
                #prelude
1✔
445

1✔
446
                #[no_mangle]
1✔
447
                pub extern "Rust" fn baz() -> Option<f64> {
1✔
448
                    apply(Some(1f64), Some(-2f64), std::ops::Sub::sub)
1✔
449
                }
1✔
450
            }
1✔
451
            .to_string()
1✔
452
        );
1✔
453

454
        assert_eq!(
1✔
455
            parse("expression", &[], "1 + 2 / 3"),
1✔
456
            quote! {
1✔
457
                #prelude
1✔
458

1✔
459
                #[no_mangle]
1✔
460
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
461
                    apply(
1✔
462
                        Some(1f64),
1✔
463
                        apply(Some(2f64), Some(3f64), std::ops::Div::div),
1✔
464
                        std::ops::Add::add
1✔
465
                    )
1✔
466
                }
1✔
467
            }
1✔
468
            .to_string()
1✔
469
        );
1✔
470

471
        assert_eq!(
1✔
472
            parse("expression", &[], "2**4"),
1✔
473
            quote! {
1✔
474
                #[inline]
1✔
475
                fn import_pow__2(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
476
                    apply(a, b, f64::powf)
1✔
477
                }
1✔
478

1✔
479
                #prelude
1✔
480

1✔
481
                #[no_mangle]
1✔
482
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
483
                    import_pow__2(Some(2f64) , Some(4f64))
1✔
484
                }
1✔
485
            }
1✔
486
            .to_string()
1✔
487
        );
1✔
488
    }
1✔
489

490
    #[test]
1✔
491
    fn params() {
1✔
492
        let prelude = prelude();
1✔
493

1✔
494
        assert_eq!(
1✔
495
            parse("expression", &["a"], "a + 1"),
1✔
496
            quote! {
1✔
497
                #prelude
1✔
498

1✔
499
                #[no_mangle]
1✔
500
                pub extern "Rust" fn expression(a: Option<f64>) -> Option<f64> {
1✔
501
                    apply(a, Some(1f64), std::ops::Add::add)
1✔
502
                }
1✔
503
            }
1✔
504
            .to_string()
1✔
505
        );
1✔
506

507
        assert_eq!(
1✔
508
            parse("ndvi", &["a", "b"], "(a-b) / (a+b)"),
1✔
509
            quote! {
1✔
510
                #prelude
1✔
511

1✔
512
                #[no_mangle]
1✔
513
                pub extern "Rust" fn ndvi(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
514
                    apply(
1✔
515
                        apply(a, b, std::ops::Sub::sub),
1✔
516
                        apply(a, b, std::ops::Add::add),
1✔
517
                        std::ops::Div::div
1✔
518
                    )
1✔
519
                }
1✔
520
            }
1✔
521
            .to_string()
1✔
522
        );
1✔
523
    }
1✔
524

525
    #[test]
1✔
526
    #[allow(clippy::too_many_lines)]
527
    fn functions() {
1✔
528
        let prelude = prelude();
1✔
529

1✔
530
        assert_eq!(
1✔
531
            parse("expression", &["a"], "max(a, 0)"),
1✔
532
            quote! {
1✔
533
                #[inline]
1✔
534
                fn import_max__2(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
535
                    apply(a, b, f64::max)
1✔
536
                }
1✔
537

1✔
538
                #prelude
1✔
539

1✔
540
                #[no_mangle]
1✔
541
                pub extern "Rust" fn expression(a: Option<f64>) -> Option<f64> {
1✔
542
                    import_max__2(a, Some(0f64))
1✔
543
                }
1✔
544
            }
1✔
545
            .to_string()
1✔
546
        );
1✔
547

548
        assert_eq!(
1✔
549
            parse("expression", &["a"], "pow(sqrt(a), 2)"),
1✔
550
            quote! {
1✔
551
                #[inline]
1✔
552
                fn import_pow__2(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
553
                    apply(a, b, f64::powf)
1✔
554
                }
1✔
555
                #[inline]
1✔
556
                fn import_sqrt__1(a: Option<f64>) -> Option<f64> {
1✔
557
                    a.map(f64::sqrt)
1✔
558
                }
1✔
559

1✔
560
                #prelude
1✔
561

1✔
562
                #[no_mangle]
1✔
563
                pub extern "Rust" fn expression(a: Option<f64>) -> Option<f64> {
1✔
564
                    import_pow__2(import_sqrt__1(a), Some(2f64))
1✔
565
                }
1✔
566
            }
1✔
567
            .to_string()
1✔
568
        );
1✔
569

570
        assert_eq!(
1✔
571
            parse("waves", &[],  "cos(sin(tan(acos(asin(atan(1))))))"),
1✔
572
            quote! {
1✔
573
                #[inline]
1✔
574
                fn import_acos__1(a: Option<f64>) -> Option<f64> {
1✔
575
                    a.map(f64::acos)
1✔
576
                }
1✔
577
                #[inline]
1✔
578
                fn import_asin__1(a: Option<f64>) -> Option<f64> {
1✔
579
                    a.map(f64::asin)
1✔
580
                }
1✔
581
                #[inline]
1✔
582
                fn import_atan__1(a: Option<f64>) -> Option<f64> {
1✔
583
                    a.map(f64::atan)
1✔
584
                }
1✔
585
                #[inline]
1✔
586
                fn import_cos__1(a: Option<f64>) -> Option<f64> {
1✔
587
                    a.map(f64::cos)
1✔
588
                }
1✔
589
                #[inline]
1✔
590
                fn import_sin__1(a: Option<f64>) -> Option<f64> {
1✔
591
                    a.map(f64::sin)
1✔
592
                }
1✔
593
                #[inline]
1✔
594
                fn import_tan__1(a: Option<f64>) -> Option<f64> {
1✔
595
                    a.map(f64::tan)
1✔
596
                }
1✔
597

1✔
598
                #prelude
1✔
599

1✔
600
                #[no_mangle]
1✔
601
                pub extern "Rust" fn waves() -> Option<f64> {
1✔
602
                    import_cos__1(import_sin__1(import_tan__1(import_acos__1(import_asin__1(import_atan__1(Some(1f64)))))))
1✔
603
                }
1✔
604
            }
1✔
605
            .to_string()
1✔
606
        );
1✔
607

608
        assert_eq!(
1✔
609
            parse("non_linear", &[], "ln(log10(pi()))"),
1✔
610
            quote! {
1✔
611
                #[inline]
1✔
612
                fn import_ln__1(a: Option<f64>) -> Option<f64> {
1✔
613
                    a.map(f64::ln)
1✔
614
                }
1✔
615
                #[inline]
1✔
616
                fn import_log10__1(a: Option<f64>) -> Option<f64> {
1✔
617
                    a.map(f64::log10)
1✔
618
                }
1✔
619
                #[inline]
1✔
620
                fn import_pi__0() -> Option<f64> {
1✔
621
                    Some(std::f64::consts::PI)
1✔
622
                }
1✔
623

1✔
624
                #prelude
1✔
625

1✔
626
                #[no_mangle]
1✔
627
                pub extern "Rust" fn non_linear() -> Option<f64> {
1✔
628
                    import_ln__1(import_log10__1(import_pi__0()))
1✔
629
                }
1✔
630
            }
1✔
631
            .to_string()
1✔
632
        );
1✔
633

634
        assert_eq!(
1✔
635
            parse("three", &[],  "min(1, 2, max(3, 4, 5))"),
1✔
636
            quote! {
1✔
637
                #[inline]
1✔
638
                fn import_max__3(a: Option<f64>, b: Option<f64>, c: Option<f64>) -> Option<f64> {
1✔
639
                    match (a, b, c) {
1✔
640
                        (Some(a), Some(b), Some(c)) => Some(f64::max(a, f64::max(b, c))),
1✔
641
                        _ => None,
1✔
642
                    }
1✔
643
                }
1✔
644
                #[inline]
1✔
645
                fn import_min__3(a: Option<f64>, b: Option<f64>, c: Option<f64>) -> Option<f64> {
1✔
646
                    match (a, b, c) {
1✔
647
                        (Some(a), Some(b), Some(c)) => Some(f64::min(a, f64::min(b, c))),
1✔
648
                        _ => None,
1✔
649
                    }
1✔
650
                }
1✔
651

1✔
652
                #prelude
1✔
653

1✔
654
                #[no_mangle]
1✔
655
                pub extern "Rust" fn three() -> Option<f64> {
1✔
656
                    import_min__3(Some(1f64), Some(2f64), import_max__3(Some(3f64), Some(4f64), Some(5f64)))
1✔
657
                }
1✔
658
            }
1✔
659
            .to_string()
1✔
660
        );
1✔
661

662
        assert_eq!(
1✔
663
            parse("rounding", &[], "round(1.3) + ceil(1.2) + floor(1.1)"),
1✔
664
            quote! {
1✔
665
                #[inline]
1✔
666
                fn import_ceil__1(a: Option<f64>) -> Option<f64> {
1✔
667
                    a.map(f64::ceil)
1✔
668
                }
1✔
669
                #[inline]
1✔
670
                fn import_floor__1(a: Option<f64>) -> Option<f64> {
1✔
671
                    a.map(f64::floor)
1✔
672
                }
1✔
673
                #[inline]
1✔
674
                fn import_round__1(a: Option<f64>) -> Option<f64> {
1✔
675
                    a.map(f64::round)
1✔
676
                }
1✔
677

1✔
678
                #prelude
1✔
679

1✔
680
                #[no_mangle]
1✔
681
                pub extern "Rust" fn rounding() -> Option<f64> {
1✔
682
                    apply(
1✔
683
                        apply(
1✔
684
                            import_round__1(Some(1.3f64)),
1✔
685
                            import_ceil__1(Some(1.2f64)),
1✔
686
                            std::ops::Add::add
1✔
687
                        ),
1✔
688
                        import_floor__1(Some(1.1f64)),
1✔
689
                        std::ops::Add::add
1✔
690
                    )
1✔
691
                }
1✔
692
            }
1✔
693
            .to_string()
1✔
694
        );
1✔
695

696
        assert_eq!(
1✔
697
            parse("radians", &[], "to_radians(1.3) + to_degrees(1.3)"),
1✔
698
            quote! {
1✔
699
                #[inline]
1✔
700
                fn import_to_degrees__1(a: Option<f64>) -> Option<f64> {
1✔
701
                    a.map(f64::to_degrees)
1✔
702
                }
1✔
703
                #[inline]
1✔
704
                fn import_to_radians__1(a: Option<f64>) -> Option<f64> {
1✔
705
                    a.map(f64::to_radians)
1✔
706
                }
1✔
707

1✔
708
                #prelude
1✔
709

1✔
710
                #[no_mangle]
1✔
711
                pub extern "Rust" fn radians() -> Option<f64> {
1✔
712
                    apply(import_to_radians__1(Some(1.3f64)), import_to_degrees__1(Some(1.3f64)), std::ops::Add::add)
1✔
713
                }
1✔
714
            }
1✔
715
            .to_string()
1✔
716
        );
1✔
717

718
        assert_eq!(
1✔
719
            parse("mod_e", &[], "mod(5, e())"),
1✔
720
            quote! {
1✔
721
                #[inline]
1✔
722
                fn import_e__0() -> Option<f64> {
1✔
723
                    Some(std::f64::consts::E)
1✔
724
                }
1✔
725
                #[inline]
1✔
726
                fn import_mod__2(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
727
                    apply(a, b, std::ops::Rem::rem)
1✔
728
                }
1✔
729

1✔
730
                #prelude
1✔
731

1✔
732
                #[no_mangle]
1✔
733
                pub extern "Rust" fn mod_e() -> Option<f64> {
1✔
734
                    import_mod__2(Some(5f64), import_e__0())
1✔
735
                }
1✔
736
            }
1✔
737
            .to_string()
1✔
738
        );
1✔
739
    }
1✔
740

741
    #[test]
1✔
742
    fn boolean_params() {
1✔
743
        let prelude = prelude();
1✔
744

1✔
745
        assert_eq!(
1✔
746
            parse("expression", &["a"], "if a is nodata { 0 } else { a }"),
1✔
747
            quote! {
1✔
748
                #prelude
1✔
749

1✔
750
                #[no_mangle]
1✔
751
                pub extern "Rust" fn expression(a: Option<f64>) -> Option<f64> {
1✔
752
                    if ((a) == (None)) {
1✔
753
                        Some(0f64)
1✔
754
                    } else {
1✔
755
                        a
1✔
756
                    }
1✔
757
                }
1✔
758
            }
1✔
759
            .to_string()
1✔
760
        );
1✔
761

762
        assert_eq!(
1✔
763
            parse(
1✔
764
                "expression",
1✔
765
                &["A", "B"],
1✔
766
                "if A IS NODATA {
1✔
767
                    B * 2
1✔
768
                } else if A == 6 {
1✔
769
                    NODATA
1✔
770
                } else {
1✔
771
                    A
1✔
772
                }"
1✔
773
            ),
1✔
774
            quote! {
1✔
775
                #prelude
1✔
776

1✔
777
                #[no_mangle]
1✔
778
                pub extern "Rust" fn expression(A: Option<f64>, B: Option<f64>) -> Option<f64> {
1✔
779
                    if ((A) == (None)) {
1✔
780
                        apply(B, Some(2f64), std::ops::Mul::mul)
1✔
781
                    } else if ((A) == (Some(6f64))) {
1✔
782
                        None
1✔
783
                    } else {
1✔
784
                        A
1✔
785
                    }
1✔
786
                }
1✔
787
            }
1✔
788
            .to_string()
1✔
789
        );
1✔
790
    }
1✔
791

792
    #[test]
1✔
793
    #[allow(clippy::too_many_lines)]
794
    fn branches() {
1✔
795
        let prelude = prelude();
1✔
796

1✔
797
        assert_eq!(
1✔
798
            parse("expression", &[], "if true { 1 } else { 2 }"),
1✔
799
            quote! {
1✔
800
                #prelude
1✔
801

1✔
802
                #[no_mangle]
1✔
803
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
804
                    if true {
1✔
805
                        Some(1f64)
1✔
806
                    } else {
1✔
807
                        Some(2f64)
1✔
808
                    }
1✔
809
                }
1✔
810
            }
1✔
811
            .to_string()
1✔
812
        );
1✔
813

814
        assert_eq!(
1✔
815
            parse(
1✔
816
                "expression",
1✔
817
                &[],
1✔
818
                "if TRUE { 1 } else if false { 2 } else { 1 + 2 }"
1✔
819
            ),
1✔
820
            quote! {
1✔
821
                #prelude
1✔
822

1✔
823
                #[no_mangle]
1✔
824
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
825
                    if true {
1✔
826
                        Some(1f64)
1✔
827
                    } else if false {
1✔
828
                        Some(2f64)
1✔
829
                    } else {
1✔
830
                        apply(Some(1f64), Some(2f64), std::ops::Add::add)
1✔
831
                    }
1✔
832
                }
1✔
833
            }
1✔
834
            .to_string()
1✔
835
        );
1✔
836

837
        assert_eq!(
1✔
838
            parse(
1✔
839
                "expression",
1✔
840
                &[],
1✔
841
                "if 1 < 2 { 1 } else if 1 + 5 < 3 - 1 { 2 } else { 1 + 2 }"
1✔
842
            ),
1✔
843
            quote! {
1✔
844
                #prelude
1✔
845

1✔
846
                #[no_mangle]
1✔
847
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
848
                    if ((Some(1f64)) < (Some(2f64))) {
1✔
849
                        Some(1f64)
1✔
850
                    } else if ((apply(Some(1f64), Some(5f64), std::ops::Add::add)) < (apply(Some(3f64), Some(1f64), std::ops::Sub::sub))) {
1✔
851
                        Some(2f64)
1✔
852
                    } else {
1✔
853
                        apply(Some(1f64), Some(2f64), std::ops::Add::add)
1✔
854
                    }
1✔
855
                }
1✔
856
            }
1✔
857
            .to_string()
1✔
858
        );
1✔
859

860
        assert_eq!(
1✔
861
            parse(
1✔
862
                "expression",
1✔
863
                &[],
1✔
864
                "if true && false {
1✔
865
                    1
1✔
866
                } else if (1 < 2) && true {
1✔
867
                    2
1✔
868
                } else {
1✔
869
                    max(1, 2)
1✔
870
                }"
1✔
871
            ),
1✔
872
            quote! {
1✔
873
                #[inline]
1✔
874
                fn import_max__2(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
875
                    apply(a, b, f64::max)
1✔
876
                }
1✔
877

1✔
878
                #prelude
1✔
879

1✔
880
                #[no_mangle]
1✔
881
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
882
                    if ((true) && (false)) {
1✔
883
                        Some(1f64)
1✔
884
                    } else if ( (( (Some(1f64)) < (Some(2f64)) )) && (true) ) {
1✔
885
                        Some(2f64)
1✔
886
                    } else {
1✔
887
                        import_max__2(Some(1f64), Some(2f64))
1✔
888
                    }
1✔
889
                }
1✔
890
            }
1✔
891
            .to_string()
1✔
892
        );
1✔
893
    }
1✔
894

895
    #[test]
1✔
896
    fn assignments() {
1✔
897
        let prelude = prelude();
1✔
898

1✔
899
        assert_eq!(
1✔
900
            parse(
1✔
901
                "expression",
1✔
902
                &[],
1✔
903
                "let a = 1.2;
1✔
904
                let b = 2;
1✔
905
                a + b + 1"
1✔
906
            ),
1✔
907
            quote! {
1✔
908
                #prelude
1✔
909

1✔
910
                #[no_mangle]
1✔
911
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
912
                    let a = Some(1.2f64);
1✔
913
                    let b = Some(2f64);
1✔
914
                    apply(
1✔
915
                        apply(a, b, std::ops::Add::add),
1✔
916
                        Some(1f64),
1✔
917
                        std::ops::Add::add
1✔
918
                    )
1✔
919
                }
1✔
920
            }
1✔
921
            .to_string()
1✔
922
        );
1✔
923
    }
1✔
924
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc