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

TyRoXx / NonlocalityOS / 15093856571

18 May 2025 08:02AM UTC coverage: 72.396% (+0.3%) from 72.054%
15093856571

Pull #245

github

web-flow
Merge 84b21f68a into 4e7b17925
Pull Request #245: GH-244: multiple parameters actually work

135 of 157 new or added lines in 7 files covered. (85.99%)

2 existing lines in 1 file now uncovered.

3218 of 4445 relevant lines covered (72.4%)

2231.44 hits per line

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

86.72
/lambda/src/expressions.rs
1
use crate::name::Name;
2
use astraea::tree::{BlobDigest, HashedTree, ReferenceIndex, Tree, TreeDeserializationError};
3
use astraea::{
4
    storage::{LoadTree, StoreError, StoreTree},
5
    tree::TreeBlob,
6
};
7
use serde::{Deserialize, Serialize};
8
use std::fmt::Display;
9
use std::future::Future;
10
use std::hash::Hash;
11
use std::{pin::Pin, sync::Arc};
12

13
pub trait PrintExpression {
14
    fn print(&self, writer: &mut dyn std::fmt::Write, level: usize) -> std::fmt::Result;
15
}
16

17
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
18
pub enum Expression<E, TreeLike>
19
where
20
    E: Clone + Display + PrintExpression,
21
    TreeLike: Clone + Display,
22
{
23
    Literal(TreeLike),
24
    Apply {
25
        callee: E,
26
        argument: E,
27
    },
28
    Argument,
29
    Environment,
30
    Lambda {
31
        environment: E,
32
        parameter_name: Name,
33
        body: E,
34
    },
35
    ConstructTree(Vec<E>),
36
}
37

38
impl<E, V> PrintExpression for Expression<E, V>
39
where
40
    E: Clone + Display + PrintExpression,
41
    V: Clone + Display,
42
{
43
    fn print(&self, writer: &mut dyn std::fmt::Write, level: usize) -> std::fmt::Result {
19✔
44
        match self {
19✔
45
            Expression::Literal(literal_value) => {
8✔
46
                write!(writer, "literal({literal_value})")
8✔
47
            }
48
            Expression::Apply { callee, argument } => {
1✔
49
                callee.print(writer, level)?;
1✔
50
                write!(writer, "(")?;
1✔
51
                argument.print(writer, level)?;
1✔
52
                write!(writer, ")")
1✔
53
            }
NEW
54
            Expression::Argument => {
×
55
                write!(writer, "$arg")
1✔
56
            }
NEW
57
            Expression::Environment => {
×
58
                write!(writer, "$env")
2✔
59
            }
60
            Expression::Lambda {
×
61
                environment,
4✔
62
                parameter_name,
4✔
63
                body,
4✔
64
            } => {
4✔
65
                write!(writer, "$env={{")?;
4✔
66
                let indented = level + 1;
4✔
67
                environment.print(writer, indented)?;
4✔
68
                writeln!(writer, "}}({parameter_name}) =>")?;
4✔
69
                for _ in 0..(indented * 2) {
4✔
70
                    write!(writer, " ")?;
10✔
71
                }
72
                body.print(writer, indented)
4✔
73
            }
74
            Expression::ConstructTree(arguments) => {
3✔
75
                write!(writer, "[")?;
3✔
76
                for argument in arguments {
13✔
77
                    argument.print(writer, level)?;
5✔
78
                    write!(writer, ", ")?;
5✔
79
                }
80
                write!(writer, "]")
3✔
81
            }
82
        }
83
    }
84
}
85

86
impl<E, TreeLike> Expression<E, TreeLike>
87
where
88
    E: Clone + Display + PrintExpression,
89
    TreeLike: Clone + Display,
90
{
91
    pub fn make_literal(value: TreeLike) -> Self {
45✔
92
        Expression::Literal(value)
45✔
93
    }
94

95
    pub fn make_apply(callee: E, argument: E) -> Self {
6✔
96
        Expression::Apply { callee, argument }
97
    }
98

99
    pub fn make_argument() -> Self {
17✔
100
        Expression::Argument
17✔
101
    }
102

103
    pub fn make_environment() -> Self {
2✔
104
        Expression::Environment
2✔
105
    }
106

107
    pub fn make_lambda(environment: E, parameter_name: Name, body: E) -> Self {
14✔
108
        Expression::Lambda {
109
            environment,
110
            parameter_name,
111
            body,
112
        }
113
    }
114

115
    pub fn make_construct_tree(arguments: Vec<E>) -> Self {
9✔
116
        Expression::ConstructTree(arguments)
9✔
117
    }
118

119
    pub async fn map_child_expressions<
65✔
120
        't,
121
        Expr: Clone + Display + PrintExpression,
122
        TreeLike2: Clone + Display,
123
        Error,
124
        F,
125
        G,
126
    >(
127
        &self,
128
        transform_expression: &'t F,
129
        transform_tree: &'t G,
130
    ) -> Result<Expression<Expr, TreeLike2>, Error>
131
    where
132
        F: Fn(&E) -> Pin<Box<dyn Future<Output = Result<Expr, Error>> + 't>>,
133
        G: Fn(&TreeLike) -> Pin<Box<dyn Future<Output = Result<TreeLike2, Error>> + 't>>,
134
    {
135
        match self {
65✔
136
            Expression::Literal(value) => Ok(Expression::Literal(transform_tree(value).await?)),
27✔
137
            Expression::Apply { callee, argument } => Ok(Expression::Apply {
6✔
138
                callee: transform_expression(callee).await?,
6✔
139
                argument: transform_expression(argument).await?,
6✔
140
            }),
141
            Expression::Argument => Ok(Expression::Argument),
9✔
142
            Expression::Environment => Ok(Expression::Environment),
4✔
UNCOV
143
            Expression::Lambda {
×
144
                environment,
6✔
145
                parameter_name,
6✔
146
                body,
6✔
147
            } => Ok(Expression::Lambda {
6✔
148
                environment: transform_expression(environment).await?,
6✔
149
                parameter_name: parameter_name.clone(),
6✔
150
                body: transform_expression(body).await?,
6✔
151
            }),
152
            Expression::ConstructTree(items) => {
13✔
153
                let mut transformed_items = Vec::new();
13✔
154
                for item in items.iter() {
32✔
155
                    transformed_items.push(transform_expression(item).await?);
57✔
156
                }
157
                Ok(Expression::ConstructTree(transformed_items))
13✔
158
            }
159
        }
160
    }
161
}
162

163
impl<E, V> Display for Expression<E, V>
164
where
165
    E: Clone + Display + PrintExpression,
166
    V: Clone + Display,
167
{
168
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
169
        self.print(f, 0)
×
170
    }
171
}
172

173
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone)]
174
pub struct DeepExpression(pub Expression<Arc<DeepExpression>, BlobDigest>);
175

176
impl PrintExpression for DeepExpression {
177
    fn print(&self, writer: &mut dyn std::fmt::Write, level: usize) -> std::fmt::Result {
1✔
178
        self.0.print(writer, level)
1✔
179
    }
180
}
181

182
impl PrintExpression for Arc<DeepExpression> {
183
    fn print(&self, writer: &mut dyn std::fmt::Write, level: usize) -> std::fmt::Result {
15✔
184
        self.0.print(writer, level)
15✔
185
    }
186
}
187

188
impl Display for DeepExpression {
189
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
190
        write!(f, "{}", self.0)
×
191
    }
192
}
193

194
pub type ShallowExpression = Expression<BlobDigest, BlobDigest>;
195

196
impl PrintExpression for BlobDigest {
197
    fn print(&self, writer: &mut dyn std::fmt::Write, _level: usize) -> std::fmt::Result {
1✔
198
        write!(writer, "{self}")
1✔
199
    }
200
}
201

202
pub type ReferenceExpression = Expression<ReferenceIndex, ReferenceIndex>;
203

204
impl PrintExpression for ReferenceIndex {
205
    fn print(&self, writer: &mut dyn std::fmt::Write, _level: usize) -> std::fmt::Result {
1✔
206
        write!(writer, "{self}")
1✔
207
    }
208
}
209

210
pub fn to_reference_expression(
24✔
211
    expression: &ShallowExpression,
212
) -> (ReferenceExpression, Vec<BlobDigest>) {
213
    match expression {
24✔
214
        Expression::Literal(value) => (
9✔
215
            ReferenceExpression::Literal(ReferenceIndex(0)),
9✔
216
            vec![*value],
9✔
217
        ),
218
        Expression::Apply { callee, argument } => (
2✔
219
            ReferenceExpression::Apply {
2✔
220
                callee: ReferenceIndex(0),
2✔
221
                argument: ReferenceIndex(1),
2✔
222
            },
223
            // TODO: deduplicate?
224
            vec![*callee, *argument],
2✔
225
        ),
226
        Expression::Argument => (ReferenceExpression::Argument, vec![]),
4✔
227
        Expression::Environment => (ReferenceExpression::Environment, vec![]),
2✔
228
        Expression::Lambda {
229
            environment,
2✔
230
            parameter_name,
2✔
231
            body,
2✔
232
        } => (
2✔
233
            ReferenceExpression::Lambda {
2✔
234
                environment: ReferenceIndex(0),
2✔
235
                parameter_name: parameter_name.clone(),
2✔
236
                body: ReferenceIndex(1),
2✔
237
            },
238
            vec![*environment, *body],
2✔
239
        ),
240
        Expression::ConstructTree(items) => (
5✔
241
            ReferenceExpression::ConstructTree(
5✔
242
                (0..items.len())
5✔
243
                    .map(|index| ReferenceIndex(index as u64))
12✔
244
                    .collect(),
5✔
245
            ),
246
            // TODO: deduplicate?
247
            items.clone(),
5✔
248
        ),
249
    }
250
}
251

252
pub async fn deserialize_shallow(tree: &Tree) -> Result<ShallowExpression, ()> {
42✔
253
    let reference_expression: ReferenceExpression = postcard::from_bytes(tree.blob().as_slice())
21✔
254
        .unwrap(/*TODO*/);
255
    reference_expression
21✔
256
        .map_child_expressions(
257
            &|child: &ReferenceIndex| -> Pin<Box<dyn Future<Output = Result<BlobDigest, ()>>>> {
14✔
258
                let child = tree.references()[child.0 as usize];
14✔
259
                Box::pin(async move { Ok(child) })
28✔
260
            },
261
            &|child: &ReferenceIndex| -> Pin<Box<dyn Future<Output = Result<BlobDigest, ()>>>> {
9✔
262
                let child = tree.references()[child.0 as usize];
9✔
263
                Box::pin(async move { Ok(child) })
18✔
264
            },
265
        )
266
        .await
21✔
267
}
268

269
pub async fn deserialize_recursively(
21✔
270
    root: &BlobDigest,
271
    load_tree: &(dyn LoadTree + Sync),
272
) -> Result<DeepExpression, ()> {
273
    let root_loaded = load_tree.load_tree(root).await.unwrap(/*TODO*/).hash().unwrap(/*TODO*/);
63✔
274
    let shallow = deserialize_shallow(root_loaded.tree()).await?;
42✔
275
    let deep = shallow
21✔
276
        .map_child_expressions(
277
            &|child: &BlobDigest| -> Pin<Box<dyn Future<Output = Result<Arc<DeepExpression>, ()>>>> {
14✔
278
                let child = *child;
14✔
279
                Box::pin(async move { deserialize_recursively(&child, load_tree)
28✔
280
                    .await
14✔
281
                    .map(Arc::new) })
14✔
282
            },
283
            &|child: &BlobDigest| -> Pin<Box<dyn Future<Output = Result<BlobDigest, ()>>>> {
9✔
284
                let child = *child;
9✔
285
                Box::pin(async move { Ok(child) })
18✔
286
            },
287
        )
288
        .await?;
×
289
    Ok(DeepExpression(deep))
×
290
}
291

292
pub fn expression_to_tree(expression: &ShallowExpression) -> Tree {
23✔
293
    let (reference_expression, references) = to_reference_expression(expression);
23✔
294
    let blob = postcard::to_allocvec(&reference_expression).unwrap(/*TODO*/);
23✔
295
    Tree::new(
296
        TreeBlob::try_from(bytes::Bytes::from_owner(blob)).unwrap(/*TODO*/),
23✔
297
        references,
23✔
298
    )
299
}
300

301
pub async fn serialize_shallow(
23✔
302
    expression: &ShallowExpression,
303
    storage: &(dyn StoreTree + Sync),
304
) -> std::result::Result<BlobDigest, StoreError> {
305
    let tree = expression_to_tree(expression);
23✔
306
    storage.store_tree(&HashedTree::from(Arc::new(tree))).await
23✔
307
}
308

309
pub async fn serialize_recursively(
23✔
310
    expression: &DeepExpression,
311
    storage: &(dyn StoreTree + Sync),
312
) -> std::result::Result<BlobDigest, StoreError> {
313
    let shallow_expression: ShallowExpression = expression
46✔
314
        .0
23✔
315
        .map_child_expressions(&|child: &Arc<DeepExpression>| -> Pin<
23✔
316
            Box<dyn Future<Output = Result<BlobDigest, StoreError>>>,
317
        > {
15✔
318
            let child = child.clone();
15✔
319
            Box::pin(async move {
30✔
320
                serialize_recursively(&child, storage)
15✔
321
                    .await
15✔
322
            })
323
        },&|child: &BlobDigest| -> Pin<
15✔
324
        Box<dyn Future<Output = Result<BlobDigest, StoreError>>>,
325
        > {
9✔
326
            let child = *child;
9✔
327
            Box::pin(async move {
18✔
328
                Ok(child)
9✔
329
            })
330
        })
331
        .await?;
23✔
332
    serialize_shallow(&shallow_expression, storage).await
×
333
}
334

335
#[derive(Debug)]
336
pub struct Closure {
337
    environment: BlobDigest,
338
    parameter_name: Name,
339
    body: Arc<DeepExpression>,
340
}
341

342
#[derive(Debug, Serialize, Deserialize)]
343
pub struct ClosureBlob {
344
    parameter_name: Name,
345
}
346

347
impl ClosureBlob {
348
    pub fn new(parameter_name: Name) -> Self {
7✔
349
        Self { parameter_name }
350
    }
351
}
352

353
impl Closure {
354
    pub fn new(environment: BlobDigest, parameter_name: Name, body: Arc<DeepExpression>) -> Self {
13✔
355
        Self {
356
            environment,
357
            parameter_name,
358
            body,
359
        }
360
    }
361

362
    pub async fn serialize(
7✔
363
        &self,
364
        store_tree: &(dyn StoreTree + Sync),
365
    ) -> Result<BlobDigest, StoreError> {
366
        let references = vec![
14✔
367
            self.environment,
7✔
368
            serialize_recursively(&self.body, store_tree).await?,
7✔
369
        ];
NEW
370
        let closure_blob = ClosureBlob::new(self.parameter_name.clone());
×
371
        let closure_blob_bytes = postcard::to_allocvec(&closure_blob).unwrap(/*TODO*/);
×
372
        store_tree
×
373
            .store_tree(&HashedTree::from(Arc::new(Tree::new(
×
374
                TreeBlob::try_from(bytes::Bytes::from_owner(closure_blob_bytes)).unwrap(/*TODO*/),
×
375
                references,
×
376
            ))))
377
            .await
×
378
    }
379

380
    pub async fn deserialize(
6✔
381
        root: &BlobDigest,
382
        load_tree: &(dyn LoadTree + Sync),
383
    ) -> Result<Closure, TreeDeserializationError> {
384
        let loaded_root = match load_tree.load_tree(root).await {
12✔
385
            Some(success) => success,
6✔
386
            None => return Err(TreeDeserializationError::BlobUnavailable(*root)),
×
387
        };
388
        let root_tree = loaded_root.hash().unwrap(/*TODO*/).tree().clone();
6✔
389
        let closure_blob: ClosureBlob = match postcard::from_bytes(root_tree.blob().as_slice()) {
12✔
390
            Ok(success) => success,
×
391
            Err(error) => return Err(TreeDeserializationError::Postcard(error)),
×
392
        };
NEW
393
        let environment_reference = &root_tree.references()[0];
×
NEW
394
        let body_reference = &root_tree.references()[1];
×
395
        let body = deserialize_recursively(body_reference, load_tree).await.unwrap(/*TODO*/);
12✔
396
        Ok(Closure::new(
6✔
397
            *environment_reference,
6✔
398
            closure_blob.parameter_name,
6✔
399
            Arc::new(body),
6✔
400
        ))
401
    }
402
}
403

404
async fn call_method(
6✔
405
    body: &DeepExpression,
406
    argument: &BlobDigest,
407
    load_tree: &(dyn LoadTree + Sync),
408
    store_tree: &(dyn StoreTree + Sync),
409
) -> std::result::Result<BlobDigest, StoreError> {
410
    Box::pin(evaluate(body, load_tree, store_tree, &Some(*argument))).await
6✔
411
}
412

413
pub type ReadVariable =
414
    dyn Fn(&Name) -> Pin<Box<dyn core::future::Future<Output = BlobDigest> + Send>> + Send + Sync;
415

416
pub async fn apply_evaluated_argument(
6✔
417
    callee: &DeepExpression,
418
    evaluated_argument: &BlobDigest,
419
    load_tree: &(dyn LoadTree + Sync),
420
    store_tree: &(dyn StoreTree + Sync),
421
    current_lambda_argument: &Option<BlobDigest>,
422
) -> std::result::Result<BlobDigest, StoreError> {
423
    let evaluated_callee = Box::pin(evaluate(
12✔
424
        callee,
6✔
425
        load_tree,
6✔
426
        store_tree,
6✔
427
        current_lambda_argument,
6✔
428
    ))
429
    .await?;
6✔
430
    let closure = match Closure::deserialize(&evaluated_callee, load_tree).await {
6✔
431
        Ok(success) => success,
6✔
432
        Err(_) => todo!(),
433
    };
434
    call_method(&closure.body, evaluated_argument, load_tree, store_tree).await
6✔
435
}
436

437
pub async fn evaluate_apply(
4✔
438
    callee: &DeepExpression,
439
    argument: &DeepExpression,
440
    load_tree: &(dyn LoadTree + Sync),
441
    store_tree: &(dyn StoreTree + Sync),
442
    current_lambda_argument: &Option<BlobDigest>,
443
) -> std::result::Result<BlobDigest, StoreError> {
444
    let evaluated_argument = Box::pin(evaluate(
8✔
445
        argument,
4✔
446
        load_tree,
4✔
447
        store_tree,
4✔
448
        current_lambda_argument,
4✔
449
    ))
450
    .await?;
4✔
451
    apply_evaluated_argument(
452
        callee,
×
453
        &evaluated_argument,
×
454
        load_tree,
×
455
        store_tree,
×
NEW
456
        current_lambda_argument,
×
457
    )
458
    .await
×
459
}
460

461
pub async fn evaluate(
33✔
462
    expression: &DeepExpression,
463
    load_tree: &(dyn LoadTree + Sync),
464
    store_tree: &(dyn StoreTree + Sync),
465
    current_lambda_argument: &Option<BlobDigest>,
466
) -> std::result::Result<BlobDigest, StoreError> {
467
    match &expression.0 {
33✔
468
        Expression::Literal(literal_value) => Ok(*literal_value),
17✔
469
        Expression::Apply { callee, argument } => {
4✔
470
            evaluate_apply(
471
                callee,
4✔
472
                argument,
4✔
473
                load_tree,
4✔
474
                store_tree,
4✔
475
                current_lambda_argument,
4✔
476
            )
477
            .await
4✔
478
        }
479
        Expression::Argument => {
480
            if let Some(argument) = current_lambda_argument {
4✔
481
                Ok(*argument)
2✔
482
            } else {
483
                todo!("We are not in a lambda context; argument is not available")
484
            }
485
        }
486
        Expression::Environment => {
487
            todo!()
488
        }
489
        Expression::Lambda {
490
            environment,
7✔
491
            parameter_name,
7✔
492
            body,
7✔
493
        } => {
494
            let evaluated_environment = Box::pin(evaluate(
14✔
495
                environment,
7✔
496
                load_tree,
7✔
497
                store_tree,
7✔
498
                current_lambda_argument,
7✔
499
            ))
500
            .await?;
7✔
NEW
501
            let closure = Closure::new(evaluated_environment, parameter_name.clone(), body.clone());
×
502
            let serialized = closure.serialize(store_tree).await?;
7✔
503
            Ok(serialized)
×
504
        }
505
        Expression::ConstructTree(arguments) => {
3✔
506
            let mut evaluated_arguments = Vec::new();
3✔
507
            for argument in arguments {
13✔
508
                let evaluated_argument = Box::pin(evaluate(
10✔
509
                    argument,
5✔
510
                    load_tree,
5✔
511
                    store_tree,
5✔
512
                    current_lambda_argument,
5✔
513
                ))
514
                .await?;
5✔
UNCOV
515
                evaluated_arguments.push(evaluated_argument);
×
516
            }
517
            store_tree
3✔
518
                .store_tree(&HashedTree::from(Arc::new(Tree::new(
3✔
519
                    TreeBlob::empty(),
3✔
520
                    evaluated_arguments,
3✔
521
                ))))
522
                .await
3✔
523
        }
524
    }
525
}
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