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

TyRoXx / NonlocalityOS / 15228062767

24 May 2025 02:34PM UTC coverage: 72.62% (+0.3%) from 72.333%
15228062767

Pull #255

github

web-flow
Merge 39804b878 into 43d85118a
Pull Request #255: GH-244: Multiple lambda parameters work

54 of 58 new or added lines in 4 files covered. (93.1%)

1 existing line in 1 file now uncovered.

3249 of 4474 relevant lines covered (72.62%)

2233.2 hits per line

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

86.81
/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 + std::fmt::Debug,
22
{
23
    Literal(TreeLike),
24
    Apply { callee: E, argument: E },
25
    Argument,
26
    Environment,
27
    Lambda { environment: E, body: E },
28
    ConstructTree(Vec<E>),
29
    GetChild { parent: E, index: u16 },
30
}
31

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

80
impl<E, TreeLike> Expression<E, TreeLike>
81
where
82
    E: Clone + Display + PrintExpression,
83
    TreeLike: Clone + std::fmt::Debug,
84
{
85
    pub fn make_literal(value: TreeLike) -> Self {
16✔
86
        Expression::Literal(value)
16✔
87
    }
88

89
    pub fn make_apply(callee: E, argument: E) -> Self {
6✔
90
        Expression::Apply { callee, argument }
91
    }
92

93
    pub fn make_argument() -> Self {
29✔
94
        Expression::Argument
29✔
95
    }
96

97
    pub fn make_environment() -> Self {
10✔
98
        Expression::Environment
10✔
99
    }
100

101
    pub fn make_lambda(environment: E, body: E) -> Self {
15✔
102
        Expression::Lambda { environment, body }
103
    }
104

105
    pub fn make_construct_tree(arguments: Vec<E>) -> Self {
51✔
106
        Expression::ConstructTree(arguments)
51✔
107
    }
108

109
    pub fn make_get_child(parent: E, index: u16) -> Self {
34✔
110
        Expression::GetChild { parent, index }
111
    }
112

113
    pub async fn map_child_expressions<
209✔
114
        't,
115
        Expr: Clone + Display + PrintExpression,
116
        TreeLike2: Clone + std::fmt::Debug,
117
        Error,
118
        F,
119
        G,
120
    >(
121
        &self,
122
        transform_expression: &'t F,
123
        transform_tree: &'t G,
124
    ) -> Result<Expression<Expr, TreeLike2>, Error>
125
    where
126
        F: Fn(&E) -> Pin<Box<dyn Future<Output = Result<Expr, Error>> + 't>>,
127
        G: Fn(&TreeLike) -> Pin<Box<dyn Future<Output = Result<TreeLike2, Error>> + 't>>,
128
    {
129
        match self {
209✔
130
            Expression::Literal(value) => Ok(Expression::Literal(transform_tree(value).await?)),
36✔
131
            Expression::Apply { callee, argument } => Ok(Expression::Apply {
9✔
132
                callee: transform_expression(callee).await?,
9✔
133
                argument: transform_expression(argument).await?,
9✔
134
            }),
135
            Expression::Argument => Ok(Expression::Argument),
27✔
136
            Expression::Environment => Ok(Expression::Environment),
25✔
137
            Expression::Lambda { environment, body } => Ok(Expression::Lambda {
21✔
138
                environment: transform_expression(environment).await?,
21✔
139
                body: transform_expression(body).await?,
21✔
140
            }),
141
            Expression::ConstructTree(items) => {
49✔
142
                let mut transformed_items = Vec::new();
49✔
143
                for item in items.iter() {
125✔
144
                    transformed_items.push(transform_expression(item).await?);
228✔
145
                }
146
                Ok(Expression::ConstructTree(transformed_items))
49✔
147
            }
148
            Expression::GetChild { parent, index } => Ok(Expression::GetChild {
42✔
149
                parent: transform_expression(parent).await?,
42✔
150
                index: *index,
42✔
151
            }),
152
        }
153
    }
154
}
155

156
impl<E, V> Display for Expression<E, V>
157
where
158
    E: Clone + Display + PrintExpression,
159
    V: Clone + std::fmt::Debug,
160
{
161
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
162
        self.print(f, 0)
×
163
    }
164
}
165

166
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone)]
167
pub struct DeepExpression(pub Expression<Arc<DeepExpression>, Tree>);
168

169
impl PrintExpression for DeepExpression {
170
    fn print(&self, writer: &mut dyn std::fmt::Write, level: usize) -> std::fmt::Result {
1✔
171
        self.0.print(writer, level)
1✔
172
    }
173
}
174

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

181
impl Display for DeepExpression {
182
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
183
        write!(f, "{}", self.0)
×
184
    }
185
}
186

187
pub type ShallowExpression = Expression<BlobDigest, BlobDigest>;
188

189
impl PrintExpression for BlobDigest {
190
    fn print(&self, writer: &mut dyn std::fmt::Write, _level: usize) -> std::fmt::Result {
1✔
191
        write!(writer, "{self}")
1✔
192
    }
193
}
194

195
pub type ReferenceExpression = Expression<ReferenceIndex, ReferenceIndex>;
196

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

203
pub fn to_reference_expression(
72✔
204
    expression: &ShallowExpression,
205
) -> (ReferenceExpression, Vec<BlobDigest>) {
206
    match expression {
72✔
207
        Expression::Literal(value) => (
12✔
208
            ReferenceExpression::Literal(ReferenceIndex(0)),
12✔
209
            vec![*value],
12✔
210
        ),
211
        Expression::Apply { callee, argument } => (
3✔
212
            ReferenceExpression::Apply {
3✔
213
                callee: ReferenceIndex(0),
3✔
214
                argument: ReferenceIndex(1),
3✔
215
            },
216
            // TODO: deduplicate?
217
            vec![*callee, *argument],
3✔
218
        ),
219
        Expression::Argument => (ReferenceExpression::Argument, vec![]),
10✔
220
        Expression::Environment => (ReferenceExpression::Environment, vec![]),
9✔
221
        Expression::Lambda { environment, body } => (
7✔
222
            ReferenceExpression::Lambda {
7✔
223
                environment: ReferenceIndex(0),
7✔
224
                body: ReferenceIndex(1),
7✔
225
            },
226
            vec![*environment, *body],
7✔
227
        ),
228
        Expression::ConstructTree(items) => (
17✔
229
            ReferenceExpression::ConstructTree(
17✔
230
                (0..items.len())
17✔
231
                    .map(|index| ReferenceIndex(index as u64))
43✔
232
                    .collect(),
17✔
233
            ),
234
            // TODO: deduplicate?
235
            items.clone(),
17✔
236
        ),
237
        Expression::GetChild { parent, index } => (
14✔
238
            ReferenceExpression::GetChild {
14✔
239
                parent: ReferenceIndex(0),
14✔
240
                index: *index,
14✔
241
            },
242
            vec![*parent],
14✔
243
        ),
244
    }
245
}
246

247
pub async fn deserialize_shallow(tree: &Tree) -> Result<ShallowExpression, ()> {
138✔
248
    let reference_expression: ReferenceExpression = postcard::from_bytes(tree.blob().as_slice())
69✔
249
        .unwrap(/*TODO*/);
250
    reference_expression
69✔
251
        .map_child_expressions(
252
            &|child: &ReferenceIndex| -> Pin<Box<dyn Future<Output = Result<BlobDigest, ()>>>> {
59✔
253
                let child = tree.references()[child.0 as usize];
59✔
254
                Box::pin(async move { Ok(child) })
118✔
255
            },
256
            &|child: &ReferenceIndex| -> Pin<Box<dyn Future<Output = Result<BlobDigest, ()>>>> {
12✔
257
                let child = tree.references()[child.0 as usize];
12✔
258
                Box::pin(async move { Ok(child) })
24✔
259
            },
260
        )
261
        .await
69✔
262
}
263

264
pub async fn deserialize_recursively(
69✔
265
    root: &BlobDigest,
266
    load_tree: &(dyn LoadTree + Sync),
267
) -> Result<DeepExpression, ()> {
268
    let root_loaded = load_tree.load_tree(root).await.unwrap(/*TODO*/).hash().unwrap(/*TODO*/);
207✔
269
    let shallow = deserialize_shallow(root_loaded.tree()).await?;
138✔
270
    let deep = shallow
69✔
271
        .map_child_expressions(
272
            &|child: &BlobDigest| -> Pin<Box<dyn Future<Output = Result<Arc<DeepExpression>, ()>>>> {
59✔
273
                let child = *child;
59✔
274
                Box::pin(async move { deserialize_recursively(&child, load_tree)
118✔
275
                    .await
59✔
276
                    .map(Arc::new) })
59✔
277
            },
278
            &|child: &BlobDigest| -> Pin<Box<dyn Future<Output = Result<Tree, ()>>>> {
12✔
279
                let child = *child;
12✔
280
                Box::pin(async move { Ok((**load_tree.load_tree(&child).await
24✔
281
                    .map(|tree| tree.hash().unwrap(/*TODO*/) ).unwrap(/*TODO*/).tree()).clone())})
24✔
282
            },
283
        )
284
        .await?;
×
285
    Ok(DeepExpression(deep))
×
286
}
287

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

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

305
pub async fn serialize_recursively(
71✔
306
    expression: &DeepExpression,
307
    storage: &(dyn StoreTree + Sync),
308
) -> std::result::Result<BlobDigest, StoreError> {
309
    let shallow_expression: ShallowExpression = expression
142✔
310
        .0
71✔
311
        .map_child_expressions(&|child: &Arc<DeepExpression>| -> Pin<
71✔
312
            Box<dyn Future<Output = Result<BlobDigest, StoreError>>>,
313
        > {
60✔
314
            let child = child.clone();
60✔
315
            Box::pin(async move {
120✔
316
                serialize_recursively(&child, storage)
60✔
317
                    .await
60✔
318
            })
319
        },&|child: &Tree| -> Pin<
60✔
320
        Box<dyn Future<Output = Result<BlobDigest, StoreError>>>,
321
        > {
12✔
322
            let child = child.clone();
12✔
323
            Box::pin(async move {
24✔
324
                storage.store_tree(&HashedTree::from(Arc::new(child))).await
12✔
325
            })
326
        })
327
        .await?;
71✔
328
    serialize_shallow(&shallow_expression, storage).await
×
329
}
330

331
#[derive(Debug)]
332
pub struct Closure {
333
    environment: BlobDigest,
334
    body: Arc<DeepExpression>,
335
}
336

337
#[derive(Debug, Serialize, Deserialize)]
338
pub struct ClosureBlob {}
339

340
impl Default for ClosureBlob {
341
    fn default() -> Self {
×
342
        Self::new()
×
343
    }
344
}
345

346
impl ClosureBlob {
347
    pub fn new() -> Self {
10✔
348
        Self {}
349
    }
350
}
351

352
impl Closure {
353
    pub fn new(environment: BlobDigest, body: Arc<DeepExpression>) -> Self {
19✔
354
        Self { environment, body }
355
    }
356

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

375
    pub async fn deserialize(
9✔
376
        root: &BlobDigest,
377
        load_tree: &(dyn LoadTree + Sync),
378
    ) -> Result<Closure, TreeDeserializationError> {
379
        let loaded_root = match load_tree.load_tree(root).await {
18✔
380
            Some(success) => success,
9✔
381
            None => return Err(TreeDeserializationError::BlobUnavailable(*root)),
×
382
        };
383
        let root_tree = loaded_root.hash().unwrap(/*TODO*/).tree().clone();
9✔
384
        let _closure_blob: ClosureBlob = match postcard::from_bytes(root_tree.blob().as_slice()) {
18✔
385
            Ok(success) => success,
×
386
            Err(error) => return Err(TreeDeserializationError::Postcard(error)),
×
387
        };
388
        let environment_reference = &root_tree.references()[0];
×
389
        let body_reference = &root_tree.references()[1];
×
390
        let body = deserialize_recursively(body_reference, load_tree).await.unwrap(/*TODO*/);
18✔
391
        Ok(Closure::new(*environment_reference, Arc::new(body)))
9✔
392
    }
393
}
394

395
async fn call_method(
9✔
396
    body: &DeepExpression,
397
    argument: &BlobDigest,
398
    environment: &BlobDigest,
399
    load_tree: &(dyn LoadTree + Sync),
400
    store_tree: &(dyn StoreTree + Sync),
401
) -> std::result::Result<BlobDigest, StoreError> {
402
    Box::pin(evaluate(
9✔
403
        body,
9✔
404
        load_tree,
9✔
405
        store_tree,
9✔
406
        &Some(*argument),
9✔
407
        &Some(*environment),
9✔
408
    ))
409
    .await
9✔
410
}
411

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

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

445
pub async fn evaluate_apply(
6✔
446
    callee: &DeepExpression,
447
    argument: &DeepExpression,
448
    load_tree: &(dyn LoadTree + Sync),
449
    store_tree: &(dyn StoreTree + Sync),
450
    current_lambda_argument: &Option<BlobDigest>,
451
    current_lambda_environment: &Option<BlobDigest>,
452
) -> std::result::Result<BlobDigest, StoreError> {
453
    let evaluated_argument = Box::pin(evaluate(
12✔
454
        argument,
6✔
455
        load_tree,
6✔
456
        store_tree,
6✔
457
        current_lambda_argument,
6✔
458
        current_lambda_environment,
6✔
459
    ))
460
    .await?;
6✔
461
    apply_evaluated_argument(
462
        callee,
×
463
        &evaluated_argument,
×
464
        load_tree,
×
465
        store_tree,
×
466
        current_lambda_argument,
×
NEW
467
        current_lambda_environment,
×
468
    )
469
    .await
×
470
}
471

472
pub async fn evaluate(
56✔
473
    expression: &DeepExpression,
474
    load_tree: &(dyn LoadTree + Sync),
475
    store_tree: &(dyn StoreTree + Sync),
476
    current_lambda_argument: &Option<BlobDigest>,
477
    current_lambda_environment: &Option<BlobDigest>,
478
) -> std::result::Result<BlobDigest, StoreError> {
479
    match &expression.0 {
56✔
480
        Expression::Literal(literal_value) => {
14✔
481
            store_tree
14✔
482
                .store_tree(&HashedTree::from(Arc::new(literal_value.clone())))
14✔
483
                .await
14✔
484
        }
485
        Expression::Apply { callee, argument } => {
6✔
486
            evaluate_apply(
487
                callee,
6✔
488
                argument,
6✔
489
                load_tree,
6✔
490
                store_tree,
6✔
491
                current_lambda_argument,
6✔
492
                current_lambda_environment,
6✔
493
            )
494
            .await
6✔
495
        }
496
        Expression::Argument => {
497
            if let Some(argument) = current_lambda_argument {
10✔
498
                Ok(*argument)
5✔
499
            } else {
500
                todo!("We are not in a lambda context; argument is not available")
501
            }
502
        }
503
        Expression::Environment => {
504
            if let Some(environment) = current_lambda_environment {
4✔
505
                Ok(*environment)
2✔
506
            } else {
507
                todo!("We are not in a lambda context; environment is not available")
508
            }
509
        }
510
        Expression::Lambda { environment, body } => {
10✔
511
            let evaluated_environment = Box::pin(evaluate(
20✔
512
                environment,
10✔
513
                load_tree,
10✔
514
                store_tree,
10✔
515
                current_lambda_argument,
10✔
516
                current_lambda_environment,
10✔
517
            ))
518
            .await?;
10✔
519
            let closure = Closure::new(evaluated_environment, body.clone());
×
520
            let serialized = closure.serialize(store_tree).await?;
10✔
521
            Ok(serialized)
×
522
        }
523
        Expression::ConstructTree(arguments) => {
13✔
524
            let mut evaluated_arguments = Vec::new();
13✔
525
            for argument in arguments {
39✔
526
                let evaluated_argument = Box::pin(evaluate(
26✔
527
                    argument,
13✔
528
                    load_tree,
13✔
529
                    store_tree,
13✔
530
                    current_lambda_argument,
13✔
531
                    current_lambda_environment,
13✔
532
                ))
533
                .await?;
13✔
534
                evaluated_arguments.push(evaluated_argument);
×
535
            }
536
            store_tree
13✔
537
                .store_tree(&HashedTree::from(Arc::new(Tree::new(
13✔
538
                    TreeBlob::empty(),
13✔
539
                    evaluated_arguments,
13✔
540
                ))))
541
                .await
13✔
542
        }
543
        Expression::GetChild { parent, index } => {
6✔
544
            let evaluated_parent = Box::pin(evaluate(
12✔
545
                parent,
6✔
546
                load_tree,
6✔
547
                store_tree,
6✔
548
                current_lambda_argument,
6✔
549
                current_lambda_environment,
6✔
550
            ))
551
            .await?;
6✔
552
            let loaded_parent = load_tree.load_tree(&evaluated_parent).await.unwrap(/*TODO*/);
12✔
553
            let hashed_tree = loaded_parent
6✔
554
                .hash()
555
                .unwrap(/*TODO*/);
556
            let child = hashed_tree
6✔
557
                .tree()
558
                .references()
559
                .get(*index as usize)
6✔
560
                .expect("TODO handle out of range error");
561
            Ok(*child)
6✔
562
        }
563
    }
564
}
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