1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/*
 * AquaVM Workflow Engine
 *
 * Copyright (C) 2024 Fluence DAO
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation version 3 of the
 * License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

use air_parser::ast;

use core::fmt;
use std::fmt::Display;

/// A virtual `hopon` instruction.
pub(crate) struct HopOn<'i> {
    pub peer_id: ast::ResolvableToPeerIdVariable<'i>,
}

impl Display for HopOn<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "hopon {}", self.peer_id)
    }
}

/// Try to parse the `new` instruction and its nested elements as a virtual `hopon` instruction.
///
/// For example:
/// ```clojure
/// (new #uniq1_name
///    (new $uniq2_name
///       (canon peer_id $uniq2_name #uniq1_name)))
/// ```
/// is parsed as a virtual instruction
/// ```clojure
/// (hopon peer_id)
/// ```
pub(crate) fn try_hopon<'i>(root_new: &ast::New<'i>) -> Option<HopOn<'i>> {
    let expected_stream_name = &root_new.argument;

    if let (ast::Instruction::New(nested_new), ast::NewArgument::Stream(stream_name)) =
        (&root_new.instruction, expected_stream_name)
    {
        let expected_nested_canon_name = &nested_new.argument;

        if let (ast::Instruction::Canon(canon), ast::NewArgument::CanonStream(nested_canon_name)) =
            (&nested_new.instruction, expected_nested_canon_name)
        {
            if canon.canon_stream.name == nested_canon_name.name
                && canon.stream.name == stream_name.name
                // this condition handles case that is never generated by an Aqua compiler, but
                // can be crafted manually
                //
                // see `hopon_shadowing` test for an example
                && !canon_shadows_peer_id(nested_canon_name.name, &canon.peer_id)
            {
                return Some(HopOn {
                    peer_id: canon.peer_id.clone(),
                });
            }
        }
    }

    None
}

fn canon_shadows_peer_id(canon_name: &str, peer_id: &ast::ResolvableToPeerIdVariable<'_>) -> bool {
    use ast::ResolvableToPeerIdVariable::*;
    match peer_id {
        InitPeerId => false,
        Literal(_) => false,
        Scalar(_) => false,
        ScalarWithLambda(_) => false,
        CanonStreamMapWithLambda(_) => false,
        CanonStreamWithLambda(canon_with_lambda) => canon_with_lambda.name == canon_name,
    }
}