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

geo-engine / geoengine / 16354606328

17 Jul 2025 07:51PM UTC coverage: 88.876%. First build
16354606328

Pull #1061

github

web-flow
Merge 019f6a1a8 into b8910c811
Pull Request #1061: feat(operators): skip empty tiles and merge masks in onnx; remove trace/debug in release mode

415 of 570 new or added lines in 44 files covered. (72.81%)

111626 of 125597 relevant lines covered (88.88%)

80287.59 hits per line

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

91.4
/expression/src/codegen.rs
1
use super::error::{ExpressionParserError, ExpressionSemanticError};
2
use crate::functions::Function;
3
use proc_macro2::TokenStream;
4
use quote::{ToTokens, format_ident, quote};
5
use std::{collections::BTreeSet, fmt::Debug, hash::Hash};
6

7
type Result<T, E = ExpressionParserError> = std::result::Result<T, E>;
8

9
/// An expression as an abstract syntax tree.
10
/// Allows genering Rust code.
11
#[derive(Debug, Clone)]
12
pub struct ExpressionAst {
13
    /// This name is the generated function name after generating code.
14
    name: Identifier,
15
    root: AstNode,
16
    parameters: Vec<Parameter>,
17
    out_type: DataType,
18
    functions: BTreeSet<AstFunction>,
19
}
20

21
impl ExpressionAst {
22
    pub fn new(
46✔
23
        name: Identifier,
46✔
24
        parameters: Vec<Parameter>,
46✔
25
        out_type: DataType,
46✔
26
        functions: BTreeSet<AstFunction>,
46✔
27
        root: AstNode,
46✔
28
    ) -> Result<ExpressionAst> {
46✔
29
        if name.as_ref().is_empty() {
46✔
30
            return Err(ExpressionSemanticError::EmptyExpressionName.into_definition_parser_error());
×
31
        }
46✔
32

33
        Ok(Self {
46✔
34
            name,
46✔
35
            root,
46✔
36
            parameters,
46✔
37
            out_type,
46✔
38
            functions,
46✔
39
        })
46✔
40
    }
46✔
41

42
    /// Outputs the generated code (file) as a string.
43
    pub fn code(&self) -> String {
17✔
44
        self.to_token_stream().to_string()
17✔
45
    }
17✔
46

47
    /// Outputs the generated code (file) as a formatted string.
48
    pub fn pretty_code(&self) -> String {
6✔
49
        match syn::parse2(self.to_token_stream()) {
6✔
50
            Ok(code) => prettyplease::unparse(&code),
6✔
51
            Err(e) => {
×
52
                // fallback to unformatted code
NEW
53
                tracing::error!("Cannot parse expression: {e}");
×
54
                self.code()
×
55
            }
56
        }
57
    }
6✔
58

59
    pub fn name(&self) -> &str {
23✔
60
        self.name.as_ref()
23✔
61
    }
23✔
62
}
63

64
impl ToTokens for ExpressionAst {
65
    fn to_tokens(&self, tokens: &mut TokenStream) {
46✔
66
        Prelude.to_tokens(tokens);
46✔
67

68
        for function in &self.functions {
108✔
69
            function.to_tokens(tokens);
62✔
70
        }
62✔
71

72
        let fn_name = &self.name;
46✔
73
        let params: Vec<TokenStream> = self
46✔
74
            .parameters
46✔
75
            .iter()
46✔
76
            .map(|p| {
60✔
77
                let param = p.identifier();
60✔
78
                let dtype = p.data_type();
60✔
79
                quote! { #param: Option<#dtype> }
60✔
80
            })
60✔
81
            .collect();
46✔
82
        let content = &self.root;
46✔
83

84
        let dtype = self.out_type;
46✔
85

86
        tokens.extend(quote! {
46✔
87
            #[unsafe(no_mangle)]
88
            pub extern "Rust" fn #fn_name (#(#params),*) -> Option<#dtype> {
89
                #content
90
            }
91
        });
92
    }
46✔
93
}
94

95
/// Generic imports and settings before the actual expression function.
96
pub struct Prelude;
97

98
impl ToTokens for Prelude {
99
    fn to_tokens(&self, tokens: &mut TokenStream) {
69✔
100
        tokens.extend(quote! {
69✔
101
            #![allow(unused_variables)] // expression inputs that are not used
102
            #![allow(unused_parens)] // safety-first parentheses in generated code
103
            #![allow(non_snake_case)] // we use double underscores for generated function names
104
            #![allow(unused_imports)] // TODO: only import dependencies that are actually used
105

106
            extern crate geoengine_expression_deps;
107

108
            use geoengine_expression_deps::*;
109
        });
110
    }
69✔
111
}
112

113
#[derive(Debug, Clone)]
114
pub enum AstNode {
115
    Constant(f64),
116
    NoData,
117
    Variable {
118
        name: Identifier,
119
        data_type: DataType,
120
    },
121
    Function {
122
        function: Function,
123
        args: Vec<AstNode>,
124
    },
125
    Branch {
126
        condition_branches: Vec<Branch>,
127
        else_branch: Box<AstNode>,
128
    },
129
    AssignmentsAndExpression {
130
        assignments: Vec<Assignment>,
131
        expression: Box<AstNode>,
132
    },
133
}
134

135
impl AstNode {
136
    pub fn data_type(&self) -> DataType {
292✔
137
        match self {
292✔
138
            // - only support number constants
139
            // - no data is a number for now
140
            Self::Constant(_) | Self::NoData => DataType::Number,
79✔
141

142
            Self::Variable { data_type, .. } => *data_type,
72✔
143

144
            Self::Function { function, .. } => function.output_type(),
83✔
145

146
            // we have to check beforehand that all branches have the same type
147
            Self::Branch { else_branch, .. } => else_branch.data_type(),
10✔
148

149
            Self::AssignmentsAndExpression { expression, .. } => expression.data_type(),
48✔
150
        }
151
    }
292✔
152
}
153

154
impl ToTokens for AstNode {
155
    fn to_tokens(&self, tokens: &mut TokenStream) {
275✔
156
        let new_tokens = match self {
275✔
157
            Self::Constant(n) => quote! { Some(#n) },
68✔
158
            Self::NoData => quote! { None },
5✔
159
            Self::Variable { name, .. } => quote! { #name },
67✔
160
            Self::Function { function, args } => {
79✔
161
                let fn_name = function.name();
79✔
162
                quote! { #fn_name(#(#args),*) }
79✔
163
            }
164
            AstNode::Branch {
165
                condition_branches,
10✔
166
                else_branch: default_branch,
10✔
167
            } => {
168
                let mut new_tokens = TokenStream::new();
10✔
169
                for (i, branch) in condition_branches.iter().enumerate() {
16✔
170
                    let condition = &branch.condition;
16✔
171
                    let body = &branch.body;
16✔
172

173
                    new_tokens.extend(if i == 0 {
16✔
174
                        // first
175
                        quote! {
10✔
176
                            if #condition {
177
                                #body
178
                            }
179
                        }
180
                    } else {
181
                        // middle
182
                        quote! {
6✔
183
                            else if #condition {
184
                                #body
185
                            }
186
                        }
187
                    });
188
                }
189

190
                new_tokens.extend(quote! {
10✔
191
                    else {
192
                        #default_branch
193
                    }
194
                });
195

196
                new_tokens
10✔
197
            }
198
            Self::AssignmentsAndExpression {
199
                assignments,
46✔
200
                expression,
46✔
201
            } => {
202
                quote! {
46✔
203
                    #(#assignments)*
204
                    #expression
205
                }
206
            }
207
        };
208

209
        tokens.extend(new_tokens);
275✔
210
    }
275✔
211
}
212

213
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
214
#[repr(transparent)]
215
pub struct Identifier(String);
216

217
impl ToTokens for Identifier {
218
    fn to_tokens(&self, tokens: &mut TokenStream) {
316✔
219
        let identifier = format_ident!("{}", self.0);
316✔
220
        tokens.extend(quote! { #identifier });
316✔
221
    }
316✔
222
}
223

224
impl From<String> for Identifier {
225
    fn from(s: String) -> Self {
162✔
226
        Self(s)
162✔
227
    }
162✔
228
}
229

230
impl From<&str> for Identifier {
231
    fn from(s: &str) -> Self {
130✔
232
        Self(s.to_string())
130✔
233
    }
130✔
234
}
235

236
impl From<&String> for Identifier {
237
    fn from(s: &String) -> Self {
11✔
238
        Self(s.to_string())
11✔
239
    }
11✔
240
}
241

242
impl AsRef<str> for Identifier {
243
    fn as_ref(&self) -> &str {
168✔
244
        &self.0
168✔
245
    }
168✔
246
}
247

248
impl std::fmt::Display for Identifier {
249
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
106✔
250
        std::fmt::Display::fmt(&self.0, f)
106✔
251
    }
106✔
252
}
253

254
#[derive(Debug, Clone)]
255
pub struct Branch {
256
    pub condition: BooleanExpression,
257
    pub body: AstNode,
258
}
259

260
#[derive(Debug, Clone)]
261
pub enum BooleanExpression {
262
    Constant(bool),
263
    Comparison {
264
        left: Box<AstNode>,
265
        op: BooleanComparator,
266
        right: Box<AstNode>,
267
    },
268
    Operation {
269
        left: Box<BooleanExpression>,
270
        op: BooleanOperator,
271
        right: Box<BooleanExpression>,
272
    },
273
}
274

275
impl ToTokens for BooleanExpression {
276
    fn to_tokens(&self, tokens: &mut TokenStream) {
20✔
277
        let new_tokens = match self {
20✔
278
            Self::Constant(b) => quote! { #b },
6✔
279
            Self::Comparison { left, op, right } => quote! { ((#left) #op (#right)) },
12✔
280
            Self::Operation { left, op, right } => quote! { ( (#left) #op (#right) ) },
2✔
281
        };
282

283
        tokens.extend(new_tokens);
20✔
284
    }
20✔
285
}
286

287
#[derive(Debug, Clone)]
288
pub enum BooleanComparator {
289
    Equal,
290
    NotEqual,
291
    LessThan,
292
    LessThanOrEqual,
293
    GreaterThan,
294
    GreaterThanOrEqual,
295
}
296

297
impl ToTokens for BooleanComparator {
298
    fn to_tokens(&self, tokens: &mut TokenStream) {
12✔
299
        let new_tokens = match self {
12✔
300
            Self::Equal => quote! { == },
5✔
301
            Self::NotEqual => quote! { != },
×
302
            Self::LessThan => quote! { < },
3✔
303
            Self::LessThanOrEqual => quote! { <= },
2✔
304
            Self::GreaterThan => quote! { > },
2✔
305
            Self::GreaterThanOrEqual => quote! { >= },
×
306
        };
307

308
        tokens.extend(new_tokens);
12✔
309
    }
12✔
310
}
311

312
#[derive(Debug, Clone)]
313
pub enum BooleanOperator {
314
    And,
315
    Or,
316
}
317

318
impl ToTokens for BooleanOperator {
319
    fn to_tokens(&self, tokens: &mut TokenStream) {
2✔
320
        let new_tokens = match self {
2✔
321
            Self::And => quote! { && },
2✔
322
            Self::Or => quote! { || },
×
323
        };
324

325
        tokens.extend(new_tokens);
2✔
326
    }
2✔
327
}
328

329
#[derive(Debug, Clone)]
330
pub struct Assignment {
331
    pub identifier: Identifier,
332
    pub expression: AstNode,
333
}
334

335
impl ToTokens for Assignment {
336
    fn to_tokens(&self, tokens: &mut TokenStream) {
2✔
337
        let Self {
338
            identifier,
2✔
339
            expression,
2✔
340
        } = self;
2✔
341
        let new_tokens = quote! {
2✔
342
            let #identifier = #expression;
343
        };
344

345
        tokens.extend(new_tokens);
2✔
346
    }
2✔
347
}
348

349
#[derive(Debug, Clone)]
350
pub enum Parameter {
351
    Number(Identifier),
352
    MultiPoint(Identifier),
353
    MultiLineString(Identifier),
354
    MultiPolygon(Identifier),
355
}
356

357
impl AsRef<str> for Parameter {
358
    fn as_ref(&self) -> &str {
68✔
359
        match self {
68✔
360
            Self::Number(identifier)
57✔
361
            | Self::MultiPoint(identifier)
7✔
362
            | Self::MultiLineString(identifier)
×
363
            | Self::MultiPolygon(identifier) => identifier.as_ref(),
68✔
364
        }
365
    }
68✔
366
}
367

368
impl Parameter {
369
    pub fn identifier(&self) -> &Identifier {
128✔
370
        match self {
128✔
371
            Self::Number(identifier)
110✔
372
            | Self::MultiPoint(identifier)
10✔
373
            | Self::MultiLineString(identifier)
×
374
            | Self::MultiPolygon(identifier) => identifier,
128✔
375
        }
376
    }
128✔
377

378
    pub fn data_type(&self) -> DataType {
128✔
379
        match self {
128✔
380
            Self::Number(_) => DataType::Number,
110✔
381
            Self::MultiPoint(_) => DataType::MultiPoint,
10✔
382
            Self::MultiLineString(_) => DataType::MultiLineString,
×
383
            Self::MultiPolygon(_) => DataType::MultiPolygon,
8✔
384
        }
385
    }
128✔
386
}
387

388
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
389
pub enum DataType {
390
    Number,
391
    MultiPoint,
392
    MultiLineString,
393
    MultiPolygon,
394
}
395

396
impl std::fmt::Display for DataType {
397
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
6✔
398
        let s = match self {
6✔
399
            Self::Number => "number",
4✔
400
            Self::MultiPoint => "geometry (multipoint)",
2✔
401
            Self::MultiLineString => "geometry (multilinestring)",
×
402
            Self::MultiPolygon => "geometry (multipolygon)",
×
403
        };
404

405
        write!(f, "{s}")
6✔
406
    }
6✔
407
}
408

409
impl ToTokens for DataType {
410
    fn to_tokens(&self, tokens: &mut TokenStream) {
266✔
411
        tokens.extend(match self {
266✔
412
            Self::Number => quote! { f64 },
249✔
413
            Self::MultiPoint => {
414
                quote! { MultiPoint }
9✔
415
            }
416
            Self::MultiLineString => {
417
                quote! { MultiLineString }
×
418
            }
419
            Self::MultiPolygon => {
420
                quote! { MultiPolygon }
8✔
421
            }
422
        });
423
    }
266✔
424
}
425

426
impl DataType {
427
    pub fn group_name(&self) -> &str {
3✔
428
        match self {
3✔
429
            Self::Number => "number",
3✔
430
            Self::MultiPoint | Self::MultiLineString | Self::MultiPolygon => "geometry",
×
431
        }
432
    }
3✔
433

434
    /// A unique short name without spaces, etc.
435
    pub fn call_name_suffix(self) -> char {
131✔
436
        match self {
131✔
437
            Self::Number => 'n',
126✔
438
            Self::MultiPoint => 'p',
×
439
            Self::MultiLineString => 'l',
×
440
            Self::MultiPolygon => 'q',
5✔
441
        }
442
    }
131✔
443
}
444

445
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
446
pub struct AstFunction {
447
    pub function: Function,
448
}
449

450
impl ToTokens for AstFunction {
451
    fn to_tokens(&self, tokens: &mut TokenStream) {
62✔
452
        let function = &self.function;
62✔
453
        tokens.extend(quote! {
62✔
454
            #[inline]
455
            #function
456
        });
457
    }
62✔
458
}
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