Skip to main content

snarkvm_synthesizer_program/view/
parse.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18impl<N: Network> Parser for ViewCore<N> {
19    /// Parses a string into a view function.
20    #[inline]
21    fn parse(string: &str) -> ParserResult<Self> {
22        // Parse the whitespace and comments from the string.
23        let (string, _) = Sanitizer::parse(string)?;
24        // Parse the 'view' keyword from the string.
25        let (string, _) = tag(Self::type_name())(string)?;
26        // Parse the whitespace from the string.
27        let (string, _) = Sanitizer::parse_whitespaces(string)?;
28        // Parse the view name from the string.
29        let (string, name) = Identifier::<N>::parse(string)?;
30        // Parse the whitespace from the string.
31        let (string, _) = Sanitizer::parse_whitespaces(string)?;
32        // Parse the colon ':' keyword from the string.
33        let (string, _) = tag(":")(string)?;
34
35        // Parse the inputs, commands, and outputs from the string. All three are `many0` —
36        // views permit zero commands (passthrough / no-op shapes) and zero outputs (assertional
37        // / guard views; the Aleo analogue of Solidity `view` functions that don't return
38        // anything). The constraints that matter (no record-touching ops, no state writes, no
39        // `async`/`await`/`call`/`rand.chacha`) are enforced by `ViewCore::add_command`, not by
40        // the parser arity.
41        let (string, inputs) = many0(Input::parse)(string)?;
42        let (string, commands) = many0(Command::<N>::parse)(string)?;
43        let (string, outputs) = many0(Output::parse)(string)?;
44
45        map_res(take(0usize), move |_| {
46            let mut view = Self::new(name);
47            inputs.iter().cloned().try_for_each(|input| view.add_input(input))?;
48            commands.iter().cloned().try_for_each(|command| view.add_command(command))?;
49            outputs.iter().cloned().try_for_each(|output| view.add_output(output))?;
50            Ok::<_, Error>(view)
51        })(string)
52    }
53}
54
55impl<N: Network> FromStr for ViewCore<N> {
56    type Err = Error;
57
58    fn from_str(string: &str) -> Result<Self> {
59        match Self::parse(string) {
60            Ok((remainder, object)) => {
61                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
62                Ok(object)
63            }
64            Err(error) => bail!("Failed to parse string. {error}"),
65        }
66    }
67}
68
69impl<N: Network> Debug for ViewCore<N> {
70    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
71        Display::fmt(self, f)
72    }
73}
74
75impl<N: Network> Display for ViewCore<N> {
76    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
77        write!(f, "{} {}:", Self::type_name(), self.name)?;
78        self.inputs.iter().try_for_each(|input| write!(f, "\n    {input}"))?;
79        self.commands.iter().try_for_each(|command| write!(f, "\n    {command}"))?;
80        self.outputs.iter().try_for_each(|output| write!(f, "\n    {output}"))
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use console::network::MainnetV0;
88
89    type CurrentNetwork = MainnetV0;
90
91    #[test]
92    fn test_view_parse() {
93        let view = ViewCore::<CurrentNetwork>::parse(
94            r"
95view foo:
96    input r0 as field.public;
97    input r1 as field.public;
98    add r0 r1 into r2;
99    output r2 as field.public;",
100        )
101        .unwrap()
102        .1;
103        assert_eq!("foo", view.name().to_string());
104        assert_eq!(2, view.inputs().len());
105        assert_eq!(1, view.commands().len());
106        assert_eq!(1, view.outputs().len());
107    }
108
109    #[test]
110    fn test_view_parse_no_inputs() {
111        let view = ViewCore::<CurrentNetwork>::parse(
112            r"
113view foo:
114    add 1u64 2u64 into r0;
115    output r0 as u64.public;",
116        )
117        .unwrap()
118        .1;
119        assert_eq!("foo", view.name().to_string());
120        assert_eq!(0, view.inputs().len());
121        assert_eq!(1, view.commands().len());
122        assert_eq!(1, view.outputs().len());
123    }
124
125    #[test]
126    fn test_view_display() {
127        let expected = r"view foo:
128    input r0 as field.public;
129    input r1 as field.public;
130    add r0 r1 into r2;
131    output r2 as field.public;";
132        let view = ViewCore::<CurrentNetwork>::parse(expected).unwrap().1;
133        assert_eq!(expected, format!("{view}"));
134    }
135
136    #[test]
137    fn test_view_parse_fails() {
138        // Missing 'view' keyword.
139        assert!(
140            ViewCore::<CurrentNetwork>::from_str(
141                r"
142foo:
143    add 1u64 2u64 into r0;
144    output r0 as u64.public;"
145            )
146            .is_err()
147        );
148        // Missing colon after the view name.
149        assert!(
150            ViewCore::<CurrentNetwork>::from_str(
151                r"
152view foo
153    add 1u64 2u64 into r0;
154    output r0 as u64.public;"
155            )
156            .is_err()
157        );
158        // 'set' is forbidden in a view.
159        assert!(
160            ViewCore::<CurrentNetwork>::from_str(
161                r"
162view foo:
163    input r0 as u64.public;
164    set r0 into balances[r0];
165    output r0 as u64.public;"
166            )
167            .is_err()
168        );
169    }
170
171    #[test]
172    fn test_view_parse_no_outputs_guard() {
173        // A guard view: asserts a precondition and returns nothing. Callers observe success
174        // via tx acceptance and failure (assertion fails) via tx rejection.
175        let view = ViewCore::<CurrentNetwork>::parse(
176            r"
177view require_zero:
178    input r0 as u64.public;
179    assert.eq r0 0u64;",
180        )
181        .unwrap()
182        .1;
183        assert_eq!("require_zero", view.name().to_string());
184        assert_eq!(1, view.inputs().len());
185        assert_eq!(1, view.commands().len());
186        assert_eq!(0, view.outputs().len());
187    }
188
189    #[test]
190    fn test_view_parse_no_commands_passthrough() {
191        // A passthrough view: no commands, output is the input register directly.
192        let view = ViewCore::<CurrentNetwork>::parse(
193            r"
194view identity:
195    input r0 as u64.public;
196    output r0 as u64.public;",
197        )
198        .unwrap()
199        .1;
200        assert_eq!("identity", view.name().to_string());
201        assert_eq!(1, view.inputs().len());
202        assert_eq!(0, view.commands().len());
203        assert_eq!(1, view.outputs().len());
204    }
205
206    #[test]
207    fn test_view_parse_fully_empty() {
208        // A no-op view: no inputs, commands, or outputs. Permitted for symmetry with `function`.
209        let view = ViewCore::<CurrentNetwork>::parse(
210            r"
211view noop:",
212        )
213        .unwrap()
214        .1;
215        assert_eq!("noop", view.name().to_string());
216        assert_eq!(0, view.inputs().len());
217        assert_eq!(0, view.commands().len());
218        assert_eq!(0, view.outputs().len());
219    }
220}