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

geo-engine / geoengine / 13695057840

06 Mar 2025 09:08AM UTC coverage: 90.081%. First build
13695057840

Pull #1030

github

web-flow
Merge fc686a2a2 into dd906c06e
Pull Request #1030: Rust-2024

2219 of 2334 new or added lines in 98 files covered. (95.07%)

126332 of 140243 relevant lines covered (90.08%)

114789.68 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)]
23,660✔
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> {
6✔
46
    PrattParser::new()
6✔
47
        .op(Op::infix(Rule::add, Assoc::Left) | Op::infix(Rule::subtract, Assoc::Left))
6✔
48
        .op(Op::infix(Rule::multiply, Assoc::Left) | Op::infix(Rule::divide, Assoc::Left))
6✔
49
        .op(Op::infix(Rule::power, Assoc::Right))
6✔
50
}
6✔
51

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

59
impl ExpressionParser {
60
    pub fn new(parameters: &[Parameter], out_type: DataType) -> Result<Self> {
108✔
61
        match duplicate_or_empty_str_slice(parameters) {
108✔
62
            crate::util::DuplicateOrEmpty::Ok => (), // fine
108✔
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 {
108✔
77
            parameters: parameters.to_vec(),
108✔
78
            out_type,
108✔
79
            functions: Rc::new(RefCell::new(Default::default())),
108✔
80
        })
108✔
81
    }
108✔
82

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

208
                        return Ok(AstNode::AssignmentsAndExpression {
94✔
209
                            assignments,
94✔
210
                            expression: Box::new(expression),
94✔
211
                        });
94✔
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
    }
490✔
223

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

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

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

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

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

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

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

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

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

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

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

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

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

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

58✔
313
        Ok(AstNode::Function { function, args })
58✔
314
    }
62✔
315

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

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

329
        let fn_name = match op.as_rule() {
100✔
330
            Rule::add => "add",
68✔
331
            Rule::subtract => "sub",
8✔
332
            Rule::multiply => "mul",
18✔
333
            Rule::divide => "div",
4✔
334
            Rule::power => "pow",
2✔
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
100✔
345
            .get_or_init(init_functions)
100✔
346
            .get(fn_name)
100✔
347
            .ok_or_else(|| {
100✔
348
                ExpressionSemanticError::UnknownFunction {
×
349
                    function: fn_name.to_string(),
×
350
                }
×
351
                .into_parser_error(op.as_span())
×
352
            })?
100✔
353
            .generate(&[DataType::Number, DataType::Number])
100✔
354
            .map_err(|e| e.into_parser_error(op.as_span()))?;
100✔
355

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

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

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

378
    fn resolve_boolean_expression_rule(
42✔
379
        &self,
42✔
380
        pair: Pair<Rule>,
42✔
381
        variables: &HashMap<Identifier, DataType>,
42✔
382
    ) -> Result<BooleanExpression> {
42✔
383
        let span = pair.as_span();
42✔
384
        match pair.as_rule() {
42✔
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();
8✔
389

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

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

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

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

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

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

437
                let left_expression = self.build_ast(first_pair.into_inner(), variables)?;
18✔
438
                let comparison = match second_pair.as_rule() {
18✔
439
                    Rule::equals => BooleanComparator::Equal,
4✔
440
                    Rule::not_equals => BooleanComparator::NotEqual,
×
441
                    Rule::smaller => BooleanComparator::LessThan,
6✔
442
                    Rule::smaller_equals => BooleanComparator::LessThanOrEqual,
4✔
443
                    Rule::larger => BooleanComparator::GreaterThan,
4✔
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)?;
18✔
453

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

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

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

482
        let boolean_operator = match op.as_rule() {
4✔
483
            Rule::and => BooleanOperator::And,
4✔
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 {
4✔
494
            left: Box::new(left),
4✔
495
            op: boolean_operator,
4✔
496
            right: Box::new(right),
4✔
497
        })
4✔
498
    }
4✔
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 {
44✔
510
        let parameters: Vec<Parameter> = parameters
44✔
511
            .iter()
44✔
512
            .map(|&p| Parameter::Number(Identifier::from(p)))
44✔
513
            .collect();
44✔
514

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

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

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

531
        Ok(ast.into_token_stream().to_string())
46✔
532
    }
62✔
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) {
32✔
555
            tokens.extend(match self {
32✔
556
                FunctionDefs::Add => quote! {
20✔
557
                    #[inline]
20✔
558
                    fn expression_fn_add__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
20✔
559
                        match (a, b) {
20✔
560
                            (Some(a), Some(b)) => Some(std::ops::Add::add(a, b)),
20✔
561
                            _ => None,
20✔
562
                        }
20✔
563
                    }
20✔
564
                },
20✔
565
                FunctionDefs::Sub => quote! {
6✔
566
                    #[inline]
6✔
567
                    fn expression_fn_sub__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
6✔
568
                        match (a, b) {
6✔
569
                            (Some(a), Some(b)) => Some(std::ops::Sub::sub(a, b)),
6✔
570
                            _ => None,
6✔
571
                        }
6✔
572
                    }
6✔
573
                },
6✔
574
                FunctionDefs::Mul => quote! {
2✔
575
                    #[inline]
2✔
576
                    fn expression_fn_mul__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
2✔
577
                        match (a, b) {
2✔
578
                            (Some(a), Some(b)) => Some(std::ops::Mul::mul(a, b)),
2✔
579
                            _ => None,
2✔
580
                        }
2✔
581
                    }
2✔
582
                },
2✔
583
                FunctionDefs::Div => quote! {
2✔
584
                    #[inline]
2✔
585
                    fn expression_fn_div__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
2✔
586
                        match (a, b) {
2✔
587
                            (Some(a), Some(b)) => Some(std::ops::Div::div(a, b)),
2✔
588
                            _ => None,
2✔
589
                        }
2✔
590
                    }
2✔
591
                },
2✔
592
                FunctionDefs::Pow => quote! {
2✔
593
                    #[inline]
2✔
594
                    fn expression_fn_pow__n_n(a: Option<f64>, b: Option<f64>) -> Option<f64> {
2✔
595
                        match (a, b) {
2✔
596
                            (Some(a), Some(b)) => Some(f64::powf(a, b)),
2✔
597
                            _ => None,
2✔
598
                        }
2✔
599
                    }
2✔
600
                },
2✔
601
            });
602
        }
32✔
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() {
2✔
613
        assert_eq_pretty!(
2✔
614
            parse("expression", &[], "1"),
2✔
615
            quote! {
2✔
616
                #Prelude
2✔
617

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

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

2✔
631
                #ADD_FN
2✔
632

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

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

2✔
646
                #ADD_FN
2✔
647

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

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

2✔
661
                #SUB_FN
2✔
662

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

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

2✔
676
                #ADD_FN
2✔
677

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

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

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

2✔
702
                #POW_FN
2✔
703

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

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

2✔
720
                #ADD_FN
2✔
721

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

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

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

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

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

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

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

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

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

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

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

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

2✔
830
                #[unsafe(no_mangle)]
2✔
831
                pub extern "Rust" fn waves() -> Option<f64> {
2✔
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)))))))
2✔
833
                }
2✔
834
            }
2✔
835
            .to_string()
2✔
836
        );
2✔
837

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

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

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

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

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

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

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

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

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

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

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

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

945
        assert_eq!(
2✔
946
            try_parse("will_not_compile", &[], DataType::Number, "max(1, 2, 3)")
2✔
947
                .unwrap_err()
2✔
948
                .to_string(),
2✔
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]"
2✔
950
        );
2✔
951
    }
2✔
952

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

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

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

2✔
987
                #MUL_FN
2✔
988

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

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

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

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

2✔
1033
                #ADD_FN
2✔
1034

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

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

2✔
1058
                #ADD_FN
2✔
1059
                #SUB_FN
2✔
1060

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

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

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

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

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

2✔
1126
                #ADD_FN
2✔
1127

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

1141
        assert_eq!(
2✔
1142
            try_parse(
2✔
1143
                "expression",
2✔
1144
                &[Parameter::Number("A".into())],
2✔
1145
                DataType::Number,
2✔
1146
                "let b = A;
2✔
1147
                let b = C;
2✔
1148
                let c = 2;
2✔
1149
                a + b",
2✔
1150
            )
2✔
1151
            .unwrap_err()
2✔
1152
            .to_string(),
2✔
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!(
2✔
1158
            try_parse(
2✔
1159
                "expression",
2✔
1160
                &[Parameter::Number("A".into())],
2✔
1161
                DataType::Number,
2✔
1162
                "let A = 2;
2✔
1163
                a",
2✔
1164
            )
2✔
1165
            .unwrap_err()
2✔
1166
            .to_string(),
2✔
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
    }
2✔
1171

1172
    #[test]
1173
    fn it_fails_when_using_wrong_datatypes() {
2✔
1174
        assert_eq!(
2✔
1175
            try_parse(
2✔
1176
                "expression",
2✔
1177
                &[
2✔
1178
                    Parameter::Number("A".into()),
2✔
1179
                    Parameter::MultiPoint("B".into())
2✔
1180
                ],
2✔
1181
                DataType::Number,
2✔
1182
                "if true { A } else { B }",
2✔
1183
            )
2✔
1184
            .unwrap_err()
2✔
1185
            .to_string(),
2✔
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!(
2✔
1191
            try_parse(
2✔
1192
                "expression",
2✔
1193
                &[
2✔
1194
                    Parameter::Number("A".into()),
2✔
1195
                    Parameter::MultiPoint("B".into())
2✔
1196
                ],
2✔
1197
                DataType::Number,
2✔
1198
                "if B IS NODATA { A } else { A }",
2✔
1199
            )
2✔
1200
            .unwrap_err()
2✔
1201
            .to_string(),
2✔
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!(
2✔
1207
            try_parse(
2✔
1208
                "expression",
2✔
1209
                &[Parameter::MultiPoint("A".into())],
2✔
1210
                DataType::Number,
2✔
1211
                "A + 1",
2✔
1212
            )
2✔
1213
            .unwrap_err()
2✔
1214
            .to_string(),
2✔
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!(
2✔
1220
            try_parse(
2✔
1221
                "expression",
2✔
1222
                &[Parameter::MultiPoint("A".into())],
2✔
1223
                DataType::Number,
2✔
1224
                "sqrt(A)",
2✔
1225
            )
2✔
1226
            .unwrap_err()
2✔
1227
            .to_string(),
2✔
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!(
2✔
1233
            try_parse("expression", &[], DataType::MultiPoint, "1",)
2✔
1234
                .unwrap_err()
2✔
1235
                .to_string(),
2✔
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
    }
2✔
1240

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

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

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