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

geo-engine / geoengine / 13719457298

07 Mar 2025 11:11AM UTC coverage: 90.08% (+0.004%) from 90.076%
13719457298

Pull #1026

github

web-flow
Merge d09cf15d1 into c96026921
Pull Request #1026: Ubuntu 24 LTS

2350 of 2476 new or added lines in 108 files covered. (94.91%)

6 existing lines in 4 files now uncovered.

126337 of 140250 relevant lines covered (90.08%)

57391.91 hits per line

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

95.22
/expression/src/parser.rs
1
use crate::{codegen::DataType, error::ExpressionParserError};
2

3
use super::{
4
    codegen::{
5
        Assignment, AstFunction, AstNode, BooleanComparator, BooleanExpression, BooleanOperator,
6
        Branch, ExpressionAst, Identifier, Parameter,
7
    },
8
    error::{self, ExpressionSemanticError},
9
    functions::{FUNCTIONS, init_functions},
10
    util::duplicate_or_empty_str_slice,
11
};
12
use pest::{
13
    Parser,
14
    iterators::{Pair, Pairs},
15
    pratt_parser::{Assoc, Op, PrattParser},
16
};
17
use pest_derive::Parser;
18
use snafu::{OptionExt, ResultExt};
19
use std::{
20
    cell::RefCell,
21
    collections::{BTreeSet, HashMap, hash_map},
22
    rc::Rc,
23
    sync::OnceLock,
24
};
25

26
type Result<T, E = ExpressionParserError> = std::result::Result<T, E>;
27

28
pub type PestError = pest::error::Error<Rule>;
29

30
#[derive(Parser)]
11,830✔
31
#[grammar = "expression.pest"] // relative to src
32
struct _ExpressionParser;
33

34
/// A parser for user-defined expressions.
35
pub struct ExpressionParser {
36
    parameters: Vec<Parameter>,
37
    out_type: DataType,
38
    functions: Rc<RefCell<BTreeSet<AstFunction>>>,
39
}
40

41
static EXPRESSION_PARSER: OnceLock<PrattParser<Rule>> = OnceLock::new();
42
static BOOLEAN_EXPRESSION_PARSER: OnceLock<PrattParser<Rule>> = OnceLock::new();
43

44
// TODO: change to [`std::sync::LazyLock'] once stable
45
fn init_expression_parser() -> PrattParser<Rule> {
3✔
46
    PrattParser::new()
3✔
47
        .op(Op::infix(Rule::add, Assoc::Left) | Op::infix(Rule::subtract, Assoc::Left))
3✔
48
        .op(Op::infix(Rule::multiply, Assoc::Left) | Op::infix(Rule::divide, Assoc::Left))
3✔
49
        .op(Op::infix(Rule::power, Assoc::Right))
3✔
50
}
3✔
51

52
// TODO: change to [`std::sync::LazyLock'] once stable
53
fn init_boolean_expression_parser() -> PrattParser<Rule> {
2✔
54
    PrattParser::new()
2✔
55
        .op(Op::infix(Rule::or, Assoc::Left))
2✔
56
        .op(Op::infix(Rule::and, Assoc::Left))
2✔
57
}
2✔
58

59
impl ExpressionParser {
60
    pub fn new(parameters: &[Parameter], out_type: DataType) -> Result<Self> {
54✔
61
        match duplicate_or_empty_str_slice(parameters) {
54✔
62
            crate::util::DuplicateOrEmpty::Ok => (), // fine
54✔
63
            crate::util::DuplicateOrEmpty::Duplicate(parameter) => {
×
64
                return Err(
×
65
                    ExpressionSemanticError::DuplicateParameterName { parameter }
×
66
                        .into_definition_parser_error(),
×
67
                );
×
68
            }
69
            crate::util::DuplicateOrEmpty::Empty => {
70
                return Err(
×
71
                    ExpressionSemanticError::EmptyParameterName.into_definition_parser_error()
×
72
                );
×
73
            }
74
        };
75

76
        Ok(Self {
54✔
77
            parameters: parameters.to_vec(),
54✔
78
            out_type,
54✔
79
            functions: Rc::new(RefCell::new(Default::default())),
54✔
80
        })
54✔
81
    }
54✔
82

83
    pub fn parse(self, name: &str, input: &str) -> Result<ExpressionAst> {
54✔
84
        if name.is_empty() {
54✔
85
            return Err(ExpressionSemanticError::EmptyExpressionName.into_definition_parser_error());
×
86
        }
54✔
87

88
        let pairs = _ExpressionParser::parse(Rule::main, input)
54✔
89
            .map_err(ExpressionParserError::from_syntactic_error)?;
54✔
90

91
        let variables = self
54✔
92
            .parameters
54✔
93
            .iter()
54✔
94
            .map(|param| (param.identifier().clone(), param.data_type()))
68✔
95
            .collect();
54✔
96

97
        let root = self.build_ast(pairs, &variables)?;
54✔
98

99
        if root.data_type() != self.out_type {
47✔
100
            return Err(ExpressionSemanticError::WrongOutputType {
1✔
101
                expected: self.out_type,
1✔
102
                actual: root.data_type(),
1✔
103
            }
1✔
104
            .into_definition_parser_error());
1✔
105
        }
46✔
106

46✔
107
        ExpressionAst::new(
46✔
108
            name.to_string().into(),
46✔
109
            self.parameters,
46✔
110
            self.out_type,
46✔
111
            self.functions.borrow_mut().clone(),
46✔
112
            root,
46✔
113
        )
46✔
114
    }
54✔
115

116
    fn build_ast(
194✔
117
        &self,
194✔
118
        pairs: Pairs<'_, Rule>,
194✔
119
        variables: &HashMap<Identifier, DataType>,
194✔
120
    ) -> Result<AstNode> {
194✔
121
        EXPRESSION_PARSER
194✔
122
            .get_or_init(init_expression_parser)
194✔
123
            .map_primary(|primary| self.resolve_expression_rule(primary, variables))
245✔
124
            .map_infix(|left, op, right| self.resolve_infix_operations(left, &op, right))
194✔
125
            // TODO: is there another way to remove EOIs?
194✔
126
            .parse(pairs.filter(|pair| pair.as_rule() != Rule::EOI))
350✔
127
    }
194✔
128

129
    fn resolve_expression_rule(
245✔
130
        &self,
245✔
131
        pair: Pair<Rule>,
245✔
132
        variables: &HashMap<Identifier, DataType>,
245✔
133
    ) -> Result<AstNode> {
245✔
134
        let span = pair.as_span();
245✔
135
        match pair.as_rule() {
245✔
136
            Rule::expression => self.build_ast(pair.into_inner(), variables),
2✔
137
            Rule::number => Ok(AstNode::Constant(
138
                pair.as_str()
74✔
139
                    .parse()
74✔
140
                    .context(error::ConstantIsNotAdNumber {
74✔
141
                        constant: pair.as_str(),
74✔
142
                    })
74✔
143
                    .map_err(|e| e.into_parser_error(span))?,
74✔
144
            )),
145
            Rule::identifier => {
146
                let identifier = pair.as_str().into();
70✔
147
                let data_type = *variables
70✔
148
                    .get(&identifier)
70✔
149
                    .context(error::UnknownVariable {
70✔
150
                        variable: identifier.to_string(),
70✔
151
                    })
70✔
152
                    .map_err(|e| e.into_parser_error(span))?;
70✔
153
                Ok(AstNode::Variable {
69✔
154
                    name: identifier,
69✔
155
                    data_type,
69✔
156
                })
69✔
157
            }
158
            Rule::nodata => Ok(AstNode::NoData),
2✔
159
            Rule::function => self.resolve_function(pair.into_inner(), span, variables),
31✔
160
            Rule::branch => self.resolve_branch(pair, span, variables),
12✔
161
            Rule::assignments_and_expression => {
162
                let mut assignments: Vec<Assignment> = vec![];
54✔
163

54✔
164
                let mut variables = variables.clone();
54✔
165

166
                for pair in pair.into_inner() {
57✔
167
                    if matches!(pair.as_rule(), Rule::assignment) {
57✔
168
                        let mut pairs = pair.into_inner();
5✔
169

170
                        let first_pair = pairs.next().ok_or(
5✔
171
                            ExpressionSemanticError::AssignmentNeedsTwoParts
5✔
172
                                .into_parser_error(span),
5✔
173
                        )?;
5✔
174
                        let second_pair = pairs.next().ok_or(
5✔
175
                            ExpressionSemanticError::AssignmentNeedsTwoParts
5✔
176
                                .into_parser_error(span),
5✔
177
                        )?;
5✔
178

179
                        let identifier: Identifier = first_pair.as_str().into();
5✔
180

181
                        let expression = self.build_ast(second_pair.into_inner(), &variables)?;
5✔
182
                        let expression_data_type = expression.data_type();
4✔
183

4✔
184
                        assignments.push(Assignment {
4✔
185
                            identifier: identifier.clone(),
4✔
186
                            expression,
4✔
187
                        });
4✔
188

4✔
189
                        // having an assignment allows more variables,
4✔
190
                        // but only in the next assignments or expression
4✔
191
                        match variables.entry(identifier) {
4✔
192
                            hash_map::Entry::Vacant(entry) => {
3✔
193
                                entry.insert(expression_data_type);
3✔
194
                            }
3✔
195
                            hash_map::Entry::Occupied(entry) => {
1✔
196
                                // we do not allow shadowing for now
1✔
197

1✔
198
                                let identifier: &Identifier = entry.key();
1✔
199
                                return Err(ExpressionSemanticError::VariableShadowing {
1✔
200
                                    variable: identifier.to_string(),
1✔
201
                                }
1✔
202
                                .into_parser_error(span));
1✔
203
                            }
204
                        };
205
                    } else {
206
                        let expression = self.build_ast(pair.into_inner(), &variables)?;
52✔
207

208
                        return Ok(AstNode::AssignmentsAndExpression {
47✔
209
                            assignments,
47✔
210
                            expression: Box::new(expression),
47✔
211
                        });
47✔
212
                    }
213
                }
214

215
                Err(ExpressionSemanticError::DoesNotEndWithExpression.into_parser_error(span))
×
216
            }
217
            _ => Err(ExpressionSemanticError::UnexpectedRule {
×
218
                rule: pair.as_str().to_string(),
×
219
            }
×
220
            .into_parser_error(span)),
×
221
        }
222
    }
245✔
223

224
    fn resolve_branch(
12✔
225
        &self,
12✔
226
        pair: Pair<Rule>,
12✔
227
        span: pest::Span<'_>,
12✔
228
        variables: &HashMap<Identifier, DataType>,
12✔
229
    ) -> Result<AstNode> {
12✔
230
        // pairs are boolean -> expression
12✔
231
        // and last one is just an expression
12✔
232
        let mut pairs = pair.into_inner();
12✔
233

12✔
234
        let mut condition_branches: Vec<Branch> = vec![];
12✔
235

12✔
236
        let mut data_type = None;
12✔
237

238
        while let Some(pair) = pairs.next() {
29✔
239
            if matches!(pair.as_rule(), Rule::boolean_expression) {
29✔
240
                let condition = self.build_boolean_expression(pair.into_inner(), variables)?;
18✔
241

242
                let next_pair = pairs
17✔
243
                    .next()
17✔
244
                    .ok_or(ExpressionSemanticError::MissingBranch.into_parser_error(span))?;
17✔
245
                let body = self.build_ast(next_pair.into_inner(), variables)?;
17✔
246

247
                if let Some(data_type) = data_type {
17✔
248
                    if data_type != body.data_type() {
6✔
249
                        return Err(ExpressionSemanticError::AllBranchesMustOutputSameType
×
250
                            .into_parser_error(span));
×
251
                    }
6✔
252
                } else {
11✔
253
                    data_type = Some(body.data_type());
11✔
254
                }
11✔
255

256
                condition_branches.push(Branch { condition, body });
17✔
257
            } else {
258
                let expression = self.build_ast(pair.into_inner(), variables)?;
11✔
259

260
                if let Some(data_type) = data_type {
11✔
261
                    if data_type != expression.data_type() {
11✔
262
                        return Err(ExpressionSemanticError::AllBranchesMustOutputSameType
1✔
263
                            .into_parser_error(span));
1✔
264
                    }
10✔
265
                }
×
266

267
                return Ok(AstNode::Branch {
10✔
268
                    condition_branches,
10✔
269
                    else_branch: Box::new(expression),
10✔
270
                });
10✔
271
            }
272
        }
273

274
        Err(ExpressionSemanticError::BranchStructureMalformed.into_parser_error(span))
×
275
    }
12✔
276

277
    fn resolve_function(
31✔
278
        &self,
31✔
279
        mut pairs: Pairs<Rule>,
31✔
280
        span: pest::Span<'_>,
31✔
281
        variables: &HashMap<Identifier, DataType>,
31✔
282
    ) -> Result<AstNode> {
31✔
283
        // first one is name
284
        let name: Identifier = pairs
31✔
285
            .next()
31✔
286
            .ok_or(ExpressionSemanticError::MalformedFunctionCall.into_parser_error(span))?
31✔
287
            .as_str()
31✔
288
            .into();
31✔
289

290
        let args = pairs
31✔
291
            .map(|pair| self.build_ast(pair.into_inner(), variables))
35✔
292
            .collect::<Result<Vec<_>, _>>()?;
31✔
293

294
        let function = FUNCTIONS
31✔
295
            .get_or_init(init_functions)
31✔
296
            .get(name.as_ref())
31✔
297
            .context(error::UnknownFunction {
31✔
298
                function: name.to_string(),
31✔
299
            })
31✔
300
            .map_err(|e| e.into_parser_error(span))?
31✔
301
            .generate(
31✔
302
                args.iter()
31✔
303
                    .map(AstNode::data_type)
31✔
304
                    .collect::<Vec<_>>()
31✔
305
                    .as_slice(),
31✔
306
            )
31✔
307
            .map_err(|e| e.into_parser_error(span))?;
31✔
308

309
        self.functions.borrow_mut().insert(AstFunction {
29✔
310
            function: function.clone(),
29✔
311
        });
29✔
312

29✔
313
        Ok(AstNode::Function { function, args })
29✔
314
    }
31✔
315

316
    fn resolve_infix_operations(
51✔
317
        &self,
51✔
318
        left: Result<AstNode>,
51✔
319
        op: &Pair<Rule>,
51✔
320
        right: Result<AstNode>,
51✔
321
    ) -> Result<AstNode> {
51✔
322
        let (left, right) = (left?, right?);
51✔
323

324
        if left.data_type() != DataType::Number || right.data_type() != DataType::Number {
51✔
325
            return Err(ExpressionSemanticError::OperatorsMustBeUsedWithNumbers
1✔
326
                .into_parser_error(op.as_span()));
1✔
327
        }
50✔
328

329
        let fn_name = match op.as_rule() {
50✔
330
            Rule::add => "add",
34✔
331
            Rule::subtract => "sub",
4✔
332
            Rule::multiply => "mul",
9✔
333
            Rule::divide => "div",
2✔
334
            Rule::power => "pow",
1✔
335
            _ => {
336
                return Err(ExpressionSemanticError::UnexpectedOperator {
×
337
                    found: op.as_str().to_string(),
×
338
                }
×
NEW
339
                .into_parser_error(op.as_span()));
×
340
            }
341
        };
342

343
        // we will change the operators to function calls
344
        let function = FUNCTIONS
50✔
345
            .get_or_init(init_functions)
50✔
346
            .get(fn_name)
50✔
347
            .ok_or_else(|| {
50✔
348
                ExpressionSemanticError::UnknownFunction {
×
349
                    function: fn_name.to_string(),
×
350
                }
×
351
                .into_parser_error(op.as_span())
×
352
            })?
50✔
353
            .generate(&[DataType::Number, DataType::Number])
50✔
354
            .map_err(|e| e.into_parser_error(op.as_span()))?;
50✔
355

356
        self.functions.borrow_mut().insert(AstFunction {
50✔
357
            function: function.clone(),
50✔
358
        });
50✔
359

50✔
360
        Ok(AstNode::Function {
50✔
361
            function,
50✔
362
            args: vec![left, right],
50✔
363
        })
50✔
364
    }
51✔
365

366
    fn build_boolean_expression(
19✔
367
        &self,
19✔
368
        pairs: Pairs<'_, Rule>,
19✔
369
        variables: &HashMap<Identifier, DataType>,
19✔
370
    ) -> Result<BooleanExpression> {
19✔
371
        BOOLEAN_EXPRESSION_PARSER
19✔
372
            .get_or_init(init_boolean_expression_parser)
19✔
373
            .map_primary(|primary| self.resolve_boolean_expression_rule(primary, variables))
21✔
374
            .map_infix(|left, op, right| Self::resolve_infix_boolean_operations(left, &op, right))
19✔
375
            .parse(pairs)
19✔
376
    }
19✔
377

378
    fn resolve_boolean_expression_rule(
21✔
379
        &self,
21✔
380
        pair: Pair<Rule>,
21✔
381
        variables: &HashMap<Identifier, DataType>,
21✔
382
    ) -> Result<BooleanExpression> {
21✔
383
        let span = pair.as_span();
21✔
384
        match pair.as_rule() {
21✔
385
            Rule::identifier_is_nodata => {
386
                // convert `A IS NODATA` to the check for `A.is_none()`
387

388
                let mut pairs = pair.into_inner();
4✔
389

390
                let identifier = pairs
4✔
391
                    .next()
4✔
392
                    .ok_or(
4✔
393
                        ExpressionSemanticError::MalformedIdentifierIsNodata
4✔
394
                            .into_parser_error(span),
4✔
395
                    )?
4✔
396
                    .as_str()
4✔
397
                    .into();
4✔
398

399
                let data_type = *variables
4✔
400
                    .get(&identifier)
4✔
401
                    .context(error::UnknownVariable {
4✔
402
                        variable: identifier.to_string(),
4✔
403
                    })
4✔
404
                    .map_err(|e| e.into_parser_error(span))?;
4✔
405

406
                if data_type != DataType::Number {
4✔
407
                    return Err(ExpressionSemanticError::ComparisonsMustBeUsedWithNumbers
1✔
408
                        .into_parser_error(span));
1✔
409
                }
3✔
410

3✔
411
                let left = AstNode::Variable {
3✔
412
                    name: identifier,
3✔
413
                    data_type,
3✔
414
                };
3✔
415

3✔
416
                Ok(BooleanExpression::Comparison {
3✔
417
                    left: Box::new(left),
3✔
418
                    op: BooleanComparator::Equal,
3✔
419
                    right: Box::new(AstNode::NoData),
3✔
420
                })
3✔
421
            }
422
            Rule::boolean_true => Ok(BooleanExpression::Constant(true)),
5✔
423
            Rule::boolean_false => Ok(BooleanExpression::Constant(false)),
2✔
424
            Rule::boolean_comparison => {
425
                let mut pairs = pair.into_inner();
9✔
426

427
                let first_pair = pairs.next().ok_or(
9✔
428
                    ExpressionSemanticError::ComparisonNeedsThreeParts.into_parser_error(span),
9✔
429
                )?;
9✔
430
                let second_pair = pairs.next().ok_or(
9✔
431
                    ExpressionSemanticError::ComparisonNeedsThreeParts.into_parser_error(span),
9✔
432
                )?;
9✔
433
                let third_pair = pairs.next().ok_or(
9✔
434
                    ExpressionSemanticError::ComparisonNeedsThreeParts.into_parser_error(span),
9✔
435
                )?;
9✔
436

437
                let left_expression = self.build_ast(first_pair.into_inner(), variables)?;
9✔
438
                let comparison = match second_pair.as_rule() {
9✔
439
                    Rule::equals => BooleanComparator::Equal,
2✔
440
                    Rule::not_equals => BooleanComparator::NotEqual,
×
441
                    Rule::smaller => BooleanComparator::LessThan,
3✔
442
                    Rule::smaller_equals => BooleanComparator::LessThanOrEqual,
2✔
443
                    Rule::larger => BooleanComparator::GreaterThan,
2✔
444
                    Rule::larger_equals => BooleanComparator::GreaterThanOrEqual,
×
445
                    _ => {
446
                        return Err(ExpressionSemanticError::UnexpectedComparator {
×
447
                            comparator: format!("{:?}", second_pair.as_rule()),
×
448
                        }
×
NEW
449
                        .into_parser_error(span));
×
450
                    }
451
                };
452
                let right_expression = self.build_ast(third_pair.into_inner(), variables)?;
9✔
453

454
                if left_expression.data_type() != DataType::Number
9✔
455
                    || right_expression.data_type() != DataType::Number
9✔
456
                {
457
                    return Err(ExpressionSemanticError::ComparisonsMustBeUsedWithNumbers
×
458
                        .into_parser_error(span));
×
459
                }
9✔
460

9✔
461
                Ok(BooleanExpression::Comparison {
9✔
462
                    left: Box::new(left_expression),
9✔
463
                    op: comparison,
9✔
464
                    right: Box::new(right_expression),
9✔
465
                })
9✔
466
            }
467
            Rule::boolean_expression => self.build_boolean_expression(pair.into_inner(), variables),
1✔
468
            _ => Err(ExpressionSemanticError::UnexpectedBooleanRule {
×
469
                rule: format!("{:?}", pair.as_rule()),
×
470
            }
×
471
            .into_parser_error(span)),
×
472
        }
473
    }
21✔
474

475
    fn resolve_infix_boolean_operations(
2✔
476
        left: Result<BooleanExpression>,
2✔
477
        op: &Pair<Rule>,
2✔
478
        right: Result<BooleanExpression>,
2✔
479
    ) -> Result<BooleanExpression> {
2✔
480
        let (left, right) = (left?, right?);
2✔
481

482
        let boolean_operator = match op.as_rule() {
2✔
483
            Rule::and => BooleanOperator::And,
2✔
484
            Rule::or => BooleanOperator::Or,
×
485
            _ => {
486
                return Err(ExpressionSemanticError::UnexpectedBooleanOperator {
×
487
                    operator: format!("{:?}", op.as_rule()),
×
488
                }
×
489
                .into_parser_error(op.as_span()));
×
490
            }
491
        };
492

493
        Ok(BooleanExpression::Operation {
2✔
494
            left: Box::new(left),
2✔
495
            op: boolean_operator,
2✔
496
            right: Box::new(right),
2✔
497
        })
2✔
498
    }
2✔
499
}
500

501
#[cfg(test)]
502
mod tests {
503
    use super::*;
504
    use crate::codegen::Prelude;
505
    use pretty_assertions::assert_str_eq;
506
    use proc_macro2::TokenStream;
507
    use quote::{ToTokens, quote};
508

509
    fn parse(name: &str, parameters: &[&str], input: &str) -> String {
22✔
510
        let parameters: Vec<Parameter> = parameters
22✔
511
            .iter()
22✔
512
            .map(|&p| Parameter::Number(Identifier::from(p)))
22✔
513
            .collect();
22✔
514

22✔
515
        try_parse(name, &parameters, DataType::Number, input).unwrap()
22✔
516
    }
22✔
517

518
    fn parse2(name: &str, parameters: &[Parameter], out_type: DataType, input: &str) -> String {
1✔
519
        try_parse(name, parameters, out_type, input).unwrap()
1✔
520
    }
1✔
521

522
    fn try_parse(
31✔
523
        name: &str,
31✔
524
        parameters: &[Parameter],
31✔
525
        out_type: DataType,
31✔
526
        input: &str,
31✔
527
    ) -> Result<String> {
31✔
528
        let parser = ExpressionParser::new(parameters, out_type)?;
31✔
529
        let ast = parser.parse(name, input)?;
31✔
530

531
        Ok(ast.into_token_stream().to_string())
23✔
532
    }
31✔
533

534
    // This macro is used to compare the pretty printed output of the expression parser.
535
    // We will use a macro instead of a function to get errors in the places where they occur.
536
    macro_rules! assert_eq_pretty {
537
        ( $left:expr, $right:expr ) => {{
538
            assert_str_eq!(
539
                prettyplease::unparse(&syn::parse_file(&$left).unwrap()),
540
                prettyplease::unparse(&syn::parse_file(&$right).unwrap()),
541
            );
542
        }};
543
    }
544

545
    enum FunctionDefs {
546
        Add,
547
        Sub,
548
        Mul,
549
        Div,
550
        Pow,
551
    }
552

553
    impl ToTokens for FunctionDefs {
554
        fn to_tokens(&self, tokens: &mut TokenStream) {
16✔
555
            tokens.extend(match self {
16✔
556
                FunctionDefs::Add => quote! {
10✔
557
                    #[inline]
10✔
558
                    fn expression_fn_add__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
10✔
559
                        match (a, b) {
10✔
560
                            (Some(a), Some(b)) => Some(std::ops::Add::add(a, b)),
10✔
561
                            _ => None,
10✔
562
                        }
10✔
563
                    }
10✔
564
                },
10✔
565
                FunctionDefs::Sub => quote! {
3✔
566
                    #[inline]
3✔
567
                    fn expression_fn_sub__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
3✔
568
                        match (a, b) {
3✔
569
                            (Some(a), Some(b)) => Some(std::ops::Sub::sub(a, b)),
3✔
570
                            _ => None,
3✔
571
                        }
3✔
572
                    }
3✔
573
                },
3✔
574
                FunctionDefs::Mul => quote! {
1✔
575
                    #[inline]
1✔
576
                    fn expression_fn_mul__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
577
                        match (a, b) {
1✔
578
                            (Some(a), Some(b)) => Some(std::ops::Mul::mul(a, b)),
1✔
579
                            _ => None,
1✔
580
                        }
1✔
581
                    }
1✔
582
                },
1✔
583
                FunctionDefs::Div => quote! {
1✔
584
                    #[inline]
1✔
585
                    fn expression_fn_div__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
586
                        match (a, b) {
1✔
587
                            (Some(a), Some(b)) => Some(std::ops::Div::div(a, b)),
1✔
588
                            _ => None,
1✔
589
                        }
1✔
590
                    }
1✔
591
                },
1✔
592
                FunctionDefs::Pow => quote! {
1✔
593
                    #[inline]
1✔
594
                    fn expression_fn_pow__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
595
                        match (a, b) {
1✔
596
                            (Some(a), Some(b)) => Some(f64::powf(a, b)),
1✔
597
                            _ => None,
1✔
598
                        }
1✔
599
                    }
1✔
600
                },
1✔
601
            });
602
        }
16✔
603
    }
604

605
    const ADD_FN: FunctionDefs = FunctionDefs::Add;
606
    const SUB_FN: FunctionDefs = FunctionDefs::Sub;
607
    const DIV_FN: FunctionDefs = FunctionDefs::Div;
608
    const MUL_FN: FunctionDefs = FunctionDefs::Mul;
609
    const POW_FN: FunctionDefs = FunctionDefs::Pow;
610

611
    #[test]
612
    fn simple() {
1✔
613
        assert_eq_pretty!(
1✔
614
            parse("expression", &[], "1"),
1✔
615
            quote! {
1✔
616
                #Prelude
1✔
617

1✔
618
                #[unsafe(no_mangle)]
1✔
619
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
620
                    Some(1f64)
1✔
621
                }
1✔
622
            }
1✔
623
            .to_string()
1✔
624
        );
1✔
625

626
        assert_eq_pretty!(
1✔
627
            parse("foo", &[], "1 + 2"),
1✔
628
            quote! {
1✔
629
                #Prelude
1✔
630

1✔
631
                #ADD_FN
1✔
632

1✔
633
                #[unsafe(no_mangle)]
1✔
634
                pub extern "Rust" fn foo() -> Option<f64> {
1✔
635
                    expression_fn_add__n_n(Some(1f64), Some(2f64))
1✔
636
                }
1✔
637
            }
1✔
638
            .to_string()
1✔
639
        );
1✔
640

641
        assert_eq_pretty!(
1✔
642
            parse("bar", &[], "-1 + 2"),
1✔
643
            quote! {
1✔
644
                #Prelude
1✔
645

1✔
646
                #ADD_FN
1✔
647

1✔
648
                #[unsafe(no_mangle)]
1✔
649
                pub extern "Rust" fn bar() -> Option<f64> {
1✔
650
                    expression_fn_add__n_n(Some(-1f64), Some(2f64))
1✔
651
                }
1✔
652
            }
1✔
653
            .to_string()
1✔
654
        );
1✔
655

656
        assert_eq_pretty!(
1✔
657
            parse("baz", &[], "1 - -2"),
1✔
658
            quote! {
1✔
659
                #Prelude
1✔
660

1✔
661
                #SUB_FN
1✔
662

1✔
663
                #[unsafe(no_mangle)]
1✔
664
                pub extern "Rust" fn baz() -> Option<f64> {
1✔
665
                    expression_fn_sub__n_n(Some(1f64), Some(-2f64))
1✔
666
                }
1✔
667
            }
1✔
668
            .to_string()
1✔
669
        );
1✔
670

671
        assert_eq_pretty!(
1✔
672
            parse("expression", &[], "1 + 2 / 3"),
1✔
673
            quote! {
1✔
674
                #Prelude
1✔
675

1✔
676
                #ADD_FN
1✔
677

1✔
678
                #[inline]
1✔
679
                fn expression_fn_div__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
680
                    match (a, b) {
1✔
681
                        (Some(a), Some(b)) => Some(std::ops::Div::div(a, b)),
1✔
682
                        _ => None,
1✔
683
                    }
1✔
684
                }
1✔
685

1✔
686
                #[unsafe(no_mangle)]
1✔
687
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
688
                    expression_fn_add__n_n(
1✔
689
                        Some(1f64),
1✔
690
                        expression_fn_div__n_n(Some(2f64), Some(3f64)),
1✔
691
                    )
1✔
692
                }
1✔
693
            }
1✔
694
            .to_string()
1✔
695
        );
1✔
696

697
        assert_eq_pretty!(
1✔
698
            parse("expression", &[], "2**4"),
1✔
699
            quote! {
1✔
700
                #Prelude
1✔
701

1✔
702
                #POW_FN
1✔
703

1✔
704
                #[unsafe(no_mangle)]
1✔
705
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
706
                    expression_fn_pow__n_n(Some(2f64) , Some(4f64))
1✔
707
                }
1✔
708
            }
1✔
709
            .to_string()
1✔
710
        );
1✔
711
    }
1✔
712

713
    #[test]
714
    fn params() {
1✔
715
        assert_eq_pretty!(
1✔
716
            parse("expression", &["a"], "a + 1"),
1✔
717
            quote! {
1✔
718
                #Prelude
1✔
719

1✔
720
                #ADD_FN
1✔
721

1✔
722
                #[unsafe(no_mangle)]
1✔
723
                pub extern "Rust" fn expression(a: Option<f64>) -> Option<f64> {
1✔
724
                    expression_fn_add__n_n(a, Some(1f64))
1✔
725
                }
1✔
726
            }
1✔
727
            .to_string()
1✔
728
        );
1✔
729

730
        assert_eq_pretty!(
1✔
731
            parse("ndvi", &["a", "b"], "(a-b) / (a+b)"),
1✔
732
            quote! {
1✔
733
                #Prelude
1✔
734

1✔
735
                #ADD_FN
1✔
736
                #DIV_FN
1✔
737
                #SUB_FN
1✔
738

1✔
739
                #[unsafe(no_mangle)]
1✔
740
                pub extern "Rust" fn ndvi(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
741
                    expression_fn_div__n_n(
1✔
742
                        expression_fn_sub__n_n(a, b),
1✔
743
                        expression_fn_add__n_n(a, b),
1✔
744
                    )
1✔
745
                }
1✔
746
            }
1✔
747
            .to_string()
1✔
748
        );
1✔
749
    }
1✔
750

751
    #[test]
752
    #[allow(clippy::too_many_lines)]
753
    fn functions() {
1✔
754
        assert_eq_pretty!(
1✔
755
            parse("expression", &["a"], "max(a, 0)"),
1✔
756
            quote! {
1✔
757
                #Prelude
1✔
758

1✔
759
                #[inline]
1✔
760
                fn expression_fn_max__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
761
                    match (a, b) {
1✔
762
                        (Some(a), Some(b)) => Some(f64::max(a, b)),
1✔
763
                        _ => None,
1✔
764
                    }
1✔
765
                }
1✔
766

1✔
767
                #[unsafe(no_mangle)]
1✔
768
                pub extern "Rust" fn expression(a: Option<f64>) -> Option<f64> {
1✔
769
                    expression_fn_max__n_n(a, Some(0f64))
1✔
770
                }
1✔
771
            }
1✔
772
            .to_string()
1✔
773
        );
1✔
774

775
        assert_eq_pretty!(
1✔
776
            parse("expression", &["a"], "pow(sqrt(a), 2)"),
1✔
777
            quote! {
1✔
778
                #Prelude
1✔
779

1✔
780
                #[inline]
1✔
781
                fn expression_fn_pow__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
782
                    match (a, b) {
1✔
783
                        (Some(a), Some(b)) => Some(f64::powf(a, b)),
1✔
784
                        _ => None,
1✔
785
                    }
1✔
786
                }
1✔
787
                #[inline]
1✔
788
                fn expression_fn_sqrt__n(a: Option<f64>) -> Option<f64> {
1✔
789
                    a.map(f64::sqrt)
1✔
790
                }
1✔
791

1✔
792
                #[unsafe(no_mangle)]
1✔
793
                pub extern "Rust" fn expression(a: Option<f64>) -> Option<f64> {
1✔
794
                    expression_fn_pow__n_n(expression_fn_sqrt__n(a), Some(2f64))
1✔
795
                }
1✔
796
            }
1✔
797
            .to_string()
1✔
798
        );
1✔
799

800
        assert_eq_pretty!(
1✔
801
            parse("waves", &[],  "cos(sin(tan(acos(asin(atan(1))))))"),
1✔
802
            quote! {
1✔
803
                #Prelude
1✔
804

1✔
805
                #[inline]
1✔
806
                fn expression_fn_acos__n(a: Option<f64>) -> Option<f64> {
1✔
807
                    a.map(f64::acos)
1✔
808
                }
1✔
809
                #[inline]
1✔
810
                fn expression_fn_asin__n(a: Option<f64>) -> Option<f64> {
1✔
811
                    a.map(f64::asin)
1✔
812
                }
1✔
813
                #[inline]
1✔
814
                fn expression_fn_atan__n(a: Option<f64>) -> Option<f64> {
1✔
815
                    a.map(f64::atan)
1✔
816
                }
1✔
817
                #[inline]
1✔
818
                fn expression_fn_cos__n(a: Option<f64>) -> Option<f64> {
1✔
819
                    a.map(f64::cos)
1✔
820
                }
1✔
821
                #[inline]
1✔
822
                fn expression_fn_sin__n(a: Option<f64>) -> Option<f64> {
1✔
823
                    a.map(f64::sin)
1✔
824
                }
1✔
825
                #[inline]
1✔
826
                fn expression_fn_tan__n(a: Option<f64>) -> Option<f64> {
1✔
827
                    a.map(f64::tan)
1✔
828
                }
1✔
829

1✔
830
                #[unsafe(no_mangle)]
1✔
831
                pub extern "Rust" fn waves() -> Option<f64> {
1✔
832
                    expression_fn_cos__n(expression_fn_sin__n(expression_fn_tan__n(expression_fn_acos__n(expression_fn_asin__n(expression_fn_atan__n(Some(1f64)))))))
1✔
833
                }
1✔
834
            }
1✔
835
            .to_string()
1✔
836
        );
1✔
837

838
        assert_eq_pretty!(
1✔
839
            parse("non_linear", &[], "ln(log10(pi()))"),
1✔
840
            quote! {
1✔
841
                #Prelude
1✔
842

1✔
843
                #[inline]
1✔
844
                fn expression_fn_ln__n(a: Option<f64>) -> Option<f64> {
1✔
845
                    a.map(f64::ln)
1✔
846
                }
1✔
847
                #[inline]
1✔
848
                fn expression_fn_log10__n(a: Option<f64>) -> Option<f64> {
1✔
849
                    a.map(f64::log10)
1✔
850
                }
1✔
851
                #[inline]
1✔
852
                fn expression_fn_pi_() -> Option<f64> {
1✔
853
                    Some(std::f64::consts::PI)
1✔
854
                }
1✔
855

1✔
856
                #[unsafe(no_mangle)]
1✔
857
                pub extern "Rust" fn non_linear() -> Option<f64> {
1✔
858
                    expression_fn_ln__n(expression_fn_log10__n(expression_fn_pi_()))
1✔
859
                }
1✔
860
            }
1✔
861
            .to_string()
1✔
862
        );
1✔
863

864
        assert_eq_pretty!(
1✔
865
            parse("rounding", &[], "round(1.3) + ceil(1.2) + floor(1.1)"),
1✔
866
            quote! {
1✔
867
                #Prelude
1✔
868

1✔
869
                #ADD_FN
1✔
870
                #[inline]
1✔
871
                fn expression_fn_ceil__n(a: Option<f64>) -> Option<f64> {
1✔
872
                    a.map(f64::ceil)
1✔
873
                }
1✔
874
                #[inline]
1✔
875
                fn expression_fn_floor__n(a: Option<f64>) -> Option<f64> {
1✔
876
                    a.map(f64::floor)
1✔
877
                }
1✔
878
                #[inline]
1✔
879
                fn expression_fn_round__n(a: Option<f64>) -> Option<f64> {
1✔
880
                    a.map(f64::round)
1✔
881
                }
1✔
882

1✔
883
                #[unsafe(no_mangle)]
1✔
884
                pub extern "Rust" fn rounding() -> Option<f64> {
1✔
885
                    expression_fn_add__n_n(
1✔
886
                        expression_fn_add__n_n(
1✔
887
                            expression_fn_round__n(Some(1.3f64)),
1✔
888
                            expression_fn_ceil__n(Some(1.2f64)),
1✔
889
                        ),
1✔
890
                        expression_fn_floor__n(Some(1.1f64)),
1✔
891
                    )
1✔
892
                }
1✔
893
            }
1✔
894
            .to_string()
1✔
895
        );
1✔
896

897
        assert_eq_pretty!(
1✔
898
            parse("radians", &[], "to_radians(1.3) + to_degrees(1.3)"),
1✔
899
            quote! {
1✔
900
                #Prelude
1✔
901

1✔
902
                #ADD_FN
1✔
903
                #[inline]
1✔
904
                fn expression_fn_to_degrees__n(a: Option<f64>) -> Option<f64> {
1✔
905
                    a.map(f64::to_degrees)
1✔
906
                }
1✔
907
                #[inline]
1✔
908
                fn expression_fn_to_radians__n(a: Option<f64>) -> Option<f64> {
1✔
909
                    a.map(f64::to_radians)
1✔
910
                }
1✔
911

1✔
912
                #[unsafe(no_mangle)]
1✔
913
                pub extern "Rust" fn radians() -> Option<f64> {
1✔
914
                    expression_fn_add__n_n(expression_fn_to_radians__n(Some(1.3f64)), expression_fn_to_degrees__n(Some(1.3f64)))
1✔
915
                }
1✔
916
            }
1✔
917
            .to_string()
1✔
918
        );
1✔
919

920
        assert_eq_pretty!(
1✔
921
            parse("mod_e", &[], "mod(5, e())"),
1✔
922
            quote! {
1✔
923
                #Prelude
1✔
924

1✔
925
                #[inline]
1✔
926
                fn expression_fn_e_() -> Option<f64> {
1✔
927
                    Some(std::f64::consts::E)
1✔
928
                }
1✔
929
                #[inline]
1✔
930
                fn expression_fn_mod__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
931
                    match (a, b) {
1✔
932
                        (Some(a), Some(b)) => Some(std::ops::Rem::rem(a, b)),
1✔
933
                        _ => None,
1✔
934
                    }
1✔
935
                }
1✔
936

1✔
937
                #[unsafe(no_mangle)]
1✔
938
                pub extern "Rust" fn mod_e() -> Option<f64> {
1✔
939
                    expression_fn_mod__n_n(Some(5f64), expression_fn_e_())
1✔
940
                }
1✔
941
            }
1✔
942
            .to_string()
1✔
943
        );
1✔
944

945
        assert_eq!(
1✔
946
            try_parse("will_not_compile", &[], DataType::Number, "max(1, 2, 3)")
1✔
947
                .unwrap_err()
1✔
948
                .to_string(),
1✔
949
            " --> 1:1\n  |\n1 | max(1, 2, 3)\n  | ^----------^\n  |\n  = Invalid function arguments for function `max`: expected [number, number], got [number, number, number]"
1✔
950
        );
1✔
951
    }
1✔
952

953
    #[test]
954
    fn boolean_params() {
1✔
955
        assert_eq_pretty!(
1✔
956
            parse("expression", &["a"], "if a is nodata { 0 } else { a }"),
1✔
957
            quote! {
1✔
958
                #Prelude
1✔
959

1✔
960
                #[unsafe(no_mangle)]
1✔
961
                pub extern "Rust" fn expression(a: Option<f64>) -> Option<f64> {
1✔
962
                    if ((a) == (None)) {
1✔
963
                        Some(0f64)
1✔
964
                    } else {
1✔
965
                        a
1✔
966
                    }
1✔
967
                }
1✔
968
            }
1✔
969
            .to_string()
1✔
970
        );
1✔
971

972
        assert_eq_pretty!(
1✔
973
            parse(
1✔
974
                "expression",
1✔
975
                &["A", "B"],
1✔
976
                "if A IS NODATA {
1✔
977
                    B * 2
1✔
978
                } else if A == 6 {
1✔
979
                    NODATA
1✔
980
                } else {
1✔
981
                    A
1✔
982
                }",
1✔
983
            ),
1✔
984
            quote! {
1✔
985
                #Prelude
1✔
986

1✔
987
                #MUL_FN
1✔
988

1✔
989
                #[unsafe(no_mangle)]
1✔
990
                pub extern "Rust" fn expression(A: Option<f64>, B: Option<f64>) -> Option<f64> {
1✔
991
                    if ((A) == (None)) {
1✔
992
                        expression_fn_mul__n_n(B, Some(2f64))
1✔
993
                    } else if ((A) == (Some(6f64))) {
1✔
994
                        None
1✔
995
                    } else {
1✔
996
                        A
1✔
997
                    }
1✔
998
                }
1✔
999
            }
1✔
1000
            .to_string()
1✔
1001
        );
1✔
1002
    }
1✔
1003

1004
    #[test]
1005
    #[allow(clippy::too_many_lines)]
1006
    fn branches() {
1✔
1007
        assert_eq_pretty!(
1✔
1008
            parse("expression", &[], "if true { 1 } else { 2 }"),
1✔
1009
            quote! {
1✔
1010
                #Prelude
1✔
1011

1✔
1012
                #[unsafe(no_mangle)]
1✔
1013
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
1014
                    if true {
1✔
1015
                        Some(1f64)
1✔
1016
                    } else {
1✔
1017
                        Some(2f64)
1✔
1018
                    }
1✔
1019
                }
1✔
1020
            }
1✔
1021
            .to_string()
1✔
1022
        );
1✔
1023

1024
        assert_eq_pretty!(
1✔
1025
            parse(
1✔
1026
                "expression",
1✔
1027
                &[],
1✔
1028
                "if TRUE { 1 } else if false { 2 } else { 1 + 2 }",
1✔
1029
            ),
1✔
1030
            quote! {
1✔
1031
                #Prelude
1✔
1032

1✔
1033
                #ADD_FN
1✔
1034

1✔
1035
                #[unsafe(no_mangle)]
1✔
1036
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
1037
                    if true {
1✔
1038
                        Some(1f64)
1✔
1039
                    } else if false {
1✔
1040
                        Some(2f64)
1✔
1041
                    } else {
1✔
1042
                        expression_fn_add__n_n(Some(1f64), Some(2f64))
1✔
1043
                    }
1✔
1044
                }
1✔
1045
            }
1✔
1046
            .to_string()
1✔
1047
        );
1✔
1048

1049
        assert_eq_pretty!(
1✔
1050
            parse(
1✔
1051
                "expression",
1✔
1052
                &[],
1✔
1053
                "if 1 < 2 { 1 } else if 1 + 5 < 3 - 1 { 2 } else { 1 + 2 }"
1✔
1054
            ),
1✔
1055
            quote! {
1✔
1056
                #Prelude
1✔
1057

1✔
1058
                #ADD_FN
1✔
1059
                #SUB_FN
1✔
1060

1✔
1061
                #[unsafe(no_mangle)]
1✔
1062
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
1063
                    if ((Some(1f64)) < (Some(2f64))) {
1✔
1064
                        Some(1f64)
1✔
1065
                    } else if ((expression_fn_add__n_n(Some(1f64), Some(5f64))) < (expression_fn_sub__n_n(Some(3f64), Some(1f64)))) {
1✔
1066
                        Some(2f64)
1✔
1067
                    } else {
1✔
1068
                        expression_fn_add__n_n(Some(1f64), Some(2f64))
1✔
1069
                    }
1✔
1070
                }
1✔
1071
            }
1✔
1072
            .to_string()
1✔
1073
        );
1✔
1074

1075
        assert_eq_pretty!(
1✔
1076
            parse(
1✔
1077
                "expression",
1✔
1078
                &[],
1✔
1079
                "if true && false {
1✔
1080
                    1
1✔
1081
                } else if (1 < 2) && true {
1✔
1082
                    2
1✔
1083
                } else {
1✔
1084
                    max(1, 2)
1✔
1085
                }",
1✔
1086
            ),
1✔
1087
            quote! {
1✔
1088
                #Prelude
1✔
1089

1✔
1090
                #[inline]
1✔
1091
                fn expression_fn_max__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
1✔
1092
                    match (a, b) {
1✔
1093
                        (Some(a), Some(b)) => Some(f64::max(a, b)),
1✔
1094
                        _ => None,
1✔
1095
                    }
1✔
1096
                }
1✔
1097

1✔
1098
                #[unsafe(no_mangle)]
1✔
1099
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
1100
                    if ((true) && (false)) {
1✔
1101
                        Some(1f64)
1✔
1102
                    } else if ( (( (Some(1f64)) < (Some(2f64)) )) && (true) ) {
1✔
1103
                        Some(2f64)
1✔
1104
                    } else {
1✔
1105
                        expression_fn_max__n_n(Some(1f64), Some(2f64))
1✔
1106
                    }
1✔
1107
                }
1✔
1108
            }
1✔
1109
            .to_string()
1✔
1110
        );
1✔
1111
    }
1✔
1112

1113
    #[test]
1114
    fn assignments() {
1✔
1115
        assert_eq_pretty!(
1✔
1116
            parse(
1✔
1117
                "expression",
1✔
1118
                &[],
1✔
1119
                "let a = 1.2;
1✔
1120
                let b = 2;
1✔
1121
                a + b + 1",
1✔
1122
            ),
1✔
1123
            quote! {
1✔
1124
                #Prelude
1✔
1125

1✔
1126
                #ADD_FN
1✔
1127

1✔
1128
                #[unsafe(no_mangle)]
1✔
1129
                pub extern "Rust" fn expression() -> Option<f64> {
1✔
1130
                    let a = Some(1.2f64);
1✔
1131
                    let b = Some(2f64);
1✔
1132
                    expression_fn_add__n_n(
1✔
1133
                        expression_fn_add__n_n(a, b),
1✔
1134
                        Some(1f64),
1✔
1135
                    )
1✔
1136
                }
1✔
1137
            }
1✔
1138
            .to_string()
1✔
1139
        );
1✔
1140

1141
        assert_eq!(
1✔
1142
            try_parse(
1✔
1143
                "expression",
1✔
1144
                &[Parameter::Number("A".into())],
1✔
1145
                DataType::Number,
1✔
1146
                "let b = A;
1✔
1147
                let b = C;
1✔
1148
                let c = 2;
1✔
1149
                a + b",
1✔
1150
            )
1✔
1151
            .unwrap_err()
1✔
1152
            .to_string(),
1✔
1153
            " --> 2:25\n  |\n2 |                 let b = C;\n  |                         ^\n  |\n  = The variable `C` was not defined",
NEW
1154
            "no access before declaration"
×
1155
        );
1156

1157
        assert_eq!(
1✔
1158
            try_parse(
1✔
1159
                "expression",
1✔
1160
                &[Parameter::Number("A".into())],
1✔
1161
                DataType::Number,
1✔
1162
                "let A = 2;
1✔
1163
                a",
1✔
1164
            )
1✔
1165
            .unwrap_err()
1✔
1166
            .to_string(),
1✔
1167
            " --> 1:1\n  |\n1 | let A = 2;\n2 |                 a\n  | ^---------------^\n  |\n  = The variable `A` was already defined",
NEW
1168
            "no shadowing"
×
1169
        );
1170
    }
1✔
1171

1172
    #[test]
1173
    fn it_fails_when_using_wrong_datatypes() {
1✔
1174
        assert_eq!(
1✔
1175
            try_parse(
1✔
1176
                "expression",
1✔
1177
                &[
1✔
1178
                    Parameter::Number("A".into()),
1✔
1179
                    Parameter::MultiPoint("B".into())
1✔
1180
                ],
1✔
1181
                DataType::Number,
1✔
1182
                "if true { A } else { B }",
1✔
1183
            )
1✔
1184
            .unwrap_err()
1✔
1185
            .to_string(),
1✔
1186
            " --> 1:1\n  |\n1 | if true { A } else { B }\n  | ^----------------------^\n  |\n  = All branches of an if-then-else expression must output the same type",
1187
            "cannot use branches of different data types"
×
1188
        );
1189

1190
        assert_eq!(
1✔
1191
            try_parse(
1✔
1192
                "expression",
1✔
1193
                &[
1✔
1194
                    Parameter::Number("A".into()),
1✔
1195
                    Parameter::MultiPoint("B".into())
1✔
1196
                ],
1✔
1197
                DataType::Number,
1✔
1198
                "if B IS NODATA { A } else { A }",
1✔
1199
            )
1✔
1200
            .unwrap_err()
1✔
1201
            .to_string(),
1✔
1202
            " --> 1:4\n  |\n1 | if B IS NODATA { A } else { A }\n  |    ^---------^\n  |\n  = Comparisons can only be used with numbers",
1203
            "cannot use non-numeric comparison"
×
1204
        );
1205

1206
        assert_eq!(
1✔
1207
            try_parse(
1✔
1208
                "expression",
1✔
1209
                &[Parameter::MultiPoint("A".into())],
1✔
1210
                DataType::Number,
1✔
1211
                "A + 1",
1✔
1212
            )
1✔
1213
            .unwrap_err()
1✔
1214
            .to_string(),
1✔
1215
            " --> 1:3\n  |\n1 | A + 1\n  |   ^\n  |\n  = Operators can only be used with numbers",
1216
            "cannot use non-numeric operators"
×
1217
        );
1218

1219
        assert_eq!(
1✔
1220
            try_parse(
1✔
1221
                "expression",
1✔
1222
                &[Parameter::MultiPoint("A".into())],
1✔
1223
                DataType::Number,
1✔
1224
                "sqrt(A)",
1✔
1225
            )
1✔
1226
            .unwrap_err()
1✔
1227
            .to_string(),
1✔
1228
            " --> 1:1\n  |\n1 | sqrt(A)\n  | ^-----^\n  |\n  = Invalid function arguments for function `sqrt`: expected [number], got [geometry (multipoint)]",
1229
            "cannot call numeric fn with geom"
×
1230
        );
1231

1232
        assert_eq!(
1✔
1233
            try_parse("expression", &[], DataType::MultiPoint, "1",)
1✔
1234
                .unwrap_err()
1✔
1235
                .to_string(),
1✔
1236
            " --> 1:1\n  |\n1 | \n  | ^---\n  |\n  = The expression was expected to output `geometry (multipoint)`, but it outputs `number`",
1237
            "cannot call with wrong output"
×
1238
        );
1239
    }
1✔
1240

1241
    #[test]
1242
    fn it_works_with_geoms() {
1✔
1243
        assert_eq_pretty!(
1✔
1244
            parse2(
1✔
1245
                "make_centroid",
1✔
1246
                &[Parameter::MultiPolygon("geom".into())],
1✔
1247
                DataType::MultiPoint,
1✔
1248
                "centroid(geom)",
1✔
1249
            ),
1✔
1250
            quote! {
1✔
1251
                #Prelude
1✔
1252

1✔
1253
                #[inline]
1✔
1254
                fn expression_fn_centroid__q(
1✔
1255
                    geom: Option<MultiPolygon>
1✔
1256
                ) -> Option<MultiPoint> {
1✔
1257
                    geom.centroid()
1✔
1258
                }
1✔
1259

1✔
1260
                #[unsafe(no_mangle)]
1✔
1261
                pub extern "Rust" fn make_centroid(
1✔
1262
                    geom: Option<MultiPolygon>
1✔
1263
                ) -> Option<MultiPoint> {
1✔
1264
                    expression_fn_centroid__q(geom)
1✔
1265
                }
1✔
1266
            }
1✔
1267
            .to_string()
1✔
1268
        );
1✔
1269
    }
1✔
1270
}
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

© 2026 Coveralls, Inc