leo_ast/expressions/
literal.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use crate::IntegerType;
18
19use super::*;
20
21/// A literal.
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct Literal {
24    pub span: Span,
25    pub id: NodeID,
26    pub variant: LiteralVariant,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
30pub enum LiteralVariant {
31    /// An address literal, e.g., `aleo1qnr4dkkvkgfqph0vzc3y6z2eu975wnpz2925ntjccd5cfqxtyu8s7pyjh9` or `hello.aleo`.
32    Address(String),
33    /// A boolean literal, either `true` or `false`.
34    Boolean(bool),
35    /// A field literal, e.g., `42field`.
36    /// A signed number followed by the keyword `field`.
37    Field(String),
38    /// A group literal, eg `42group`.
39    Group(String),
40    /// An integer literal, e.g., `42u32`.
41    Integer(IntegerType, String),
42    /// A literal `None` for optional types.
43    None,
44    /// A scalar literal, e.g. `1scalar`.
45    /// An unsigned number followed by the keyword `scalar`.
46    Scalar(String),
47    /// An unsuffixed literal, e.g. `42` (without a type suffix)
48    Unsuffixed(String),
49    /// A string literal, e.g., `"foobar"`.
50    String(String),
51}
52
53impl fmt::Display for LiteralVariant {
54    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
55        match &self {
56            Self::Address(address) => write!(f, "{address}"),
57            Self::Boolean(boolean) => write!(f, "{boolean}"),
58            Self::Field(field) => write!(f, "{field}field"),
59            Self::Group(group) => write!(f, "{group}group"),
60            Self::Integer(type_, value) => write!(f, "{value}{type_}"),
61            Self::None => write!(f, "none"),
62            Self::Scalar(scalar) => write!(f, "{scalar}scalar"),
63            Self::Unsuffixed(value) => write!(f, "{value}"),
64            Self::String(string) => write!(f, "\"{string}\""),
65        }
66    }
67}
68
69impl fmt::Display for Literal {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        self.variant.fmt(f)
72    }
73}
74
75crate::simple_node_impl!(Literal);
76
77struct DisplayDecimal<'a>(&'a Literal);
78
79impl Literal {
80    pub fn string(s: String, span: Span, id: NodeID) -> Self {
81        Literal { variant: LiteralVariant::String(s), span, id }
82    }
83
84    pub fn field(s: String, span: Span, id: NodeID) -> Self {
85        Literal { variant: LiteralVariant::Field(s), span, id }
86    }
87
88    pub fn group(s: String, span: Span, id: NodeID) -> Self {
89        Literal { variant: LiteralVariant::Group(s), span, id }
90    }
91
92    pub fn address(s: String, span: Span, id: NodeID) -> Self {
93        Literal { variant: LiteralVariant::Address(s), span, id }
94    }
95
96    pub fn scalar(s: String, span: Span, id: NodeID) -> Self {
97        Literal { variant: LiteralVariant::Scalar(s), span, id }
98    }
99
100    pub fn boolean(s: bool, span: Span, id: NodeID) -> Self {
101        Literal { variant: LiteralVariant::Boolean(s), span, id }
102    }
103
104    pub fn integer(integer_type: IntegerType, s: String, span: Span, id: NodeID) -> Self {
105        Literal { variant: LiteralVariant::Integer(integer_type, s), span, id }
106    }
107
108    pub fn unsuffixed(s: String, span: Span, id: NodeID) -> Self {
109        Literal { variant: LiteralVariant::Unsuffixed(s), span, id }
110    }
111
112    pub fn none(span: Span, id: NodeID) -> Self {
113        Literal { variant: LiteralVariant::None, span, id }
114    }
115
116    /// For displaying a literal as decimal, regardless of the radix in which it was parsed.
117    ///
118    /// In particular this is useful for outputting .aleo files.
119    pub fn display_decimal(&self) -> impl '_ + fmt::Display {
120        DisplayDecimal(self)
121    }
122
123    /// For an integer literal, parse it and cast it to a u32.
124    pub fn as_u32(&self) -> Option<u32> {
125        if let LiteralVariant::Integer(_, s) = &self.variant {
126            u32::from_str_by_radix(&s.replace("_", "")).ok()
127        } else {
128            None
129        }
130    }
131}
132
133impl fmt::Display for DisplayDecimal<'_> {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        // This function is duplicated in `interpreter/src/cursor.rs`,
136        // but there's not really a great place to put a common implementation
137        // right now.
138        fn prepare_snarkvm_string(s: &str, suffix: &str) -> String {
139            // If there's a `-`, separate it from the rest of the string.
140            let (neg, rest) = s.strip_prefix("-").map(|rest| ("-", rest)).unwrap_or(("", s));
141            // Remove leading zeros.
142            let mut rest = rest.trim_start_matches('0');
143            if rest.is_empty() {
144                rest = "0";
145            }
146            format!("{neg}{rest}{suffix}")
147        }
148
149        match &self.0.variant {
150            LiteralVariant::Address(address) => write!(f, "{address}"),
151            LiteralVariant::Boolean(boolean) => write!(f, "{boolean}"),
152            LiteralVariant::Field(field) => write!(f, "{}", prepare_snarkvm_string(field, "field")),
153            LiteralVariant::Group(group) => write!(f, "{}", prepare_snarkvm_string(group, "group")),
154            LiteralVariant::Integer(type_, value) => {
155                let string = value.replace('_', "");
156                if value.starts_with('-') {
157                    let v = i128::from_str_by_radix(&string).expect("Failed to parse integer?");
158                    write!(f, "{v}{type_}")
159                } else {
160                    let v = u128::from_str_by_radix(&string).expect("Failed to parse integer?");
161                    write!(f, "{v}{type_}")
162                }
163            }
164            LiteralVariant::None => write!(f, "none"),
165            LiteralVariant::Scalar(scalar) => write!(f, "{}", prepare_snarkvm_string(scalar, "scalar")),
166            LiteralVariant::Unsuffixed(value) => write!(f, "{value}"),
167            LiteralVariant::String(string) => write!(f, "\"{string}\""),
168        }
169    }
170}
171
172impl From<Literal> for Expression {
173    fn from(value: Literal) -> Self {
174        Expression::Literal(value)
175    }
176}
177
178/// This trait allows to parse integer literals of any type generically.
179///
180/// The literal may optionally start with a `-` and/or `0x` or `0o` or 0b`.
181pub trait FromStrRadix: Sized {
182    fn from_str_by_radix(src: &str) -> Result<Self, std::num::ParseIntError>;
183}
184
185macro_rules! implement_from_str_radix {
186    ($($ty:ident)*) => {
187        $(
188            impl FromStrRadix for $ty {
189                fn from_str_by_radix(src: &str) -> Result<Self, std::num::ParseIntError> {
190                    if let Some(stripped) = src.strip_prefix("0x") {
191                        Self::from_str_radix(stripped, 16)
192                    } else if let Some(stripped) = src.strip_prefix("0o") {
193                        Self::from_str_radix(stripped, 8)
194                    } else if let Some(stripped) = src.strip_prefix("0b") {
195                        Self::from_str_radix(stripped, 2)
196                    } else if let Some(stripped) = src.strip_prefix("-0x") {
197                        // We have to remove the 0x prefix and put back in a - to use
198                        // std's parsing. Alternatively we could jump through
199                        // a few hoops to avoid allocating.
200                        let mut s = String::new();
201                        s.push('-');
202                        s.push_str(stripped);
203                        Self::from_str_radix(&s, 16)
204                    } else if let Some(stripped) = src.strip_prefix("-0o") {
205                        // Ditto.
206                        let mut s = String::new();
207                        s.push('-');
208                        s.push_str(stripped);
209                        Self::from_str_radix(&s, 8)
210                    } else if let Some(stripped) = src.strip_prefix("-0b") {
211                        // Ditto.
212                        let mut s = String::new();
213                        s.push('-');
214                        s.push_str(stripped);
215                        Self::from_str_radix(&s, 2)
216                    } else {
217                        Self::from_str_radix(src, 10)
218                    }
219                }
220            }
221        )*
222    };
223}
224
225implement_from_str_radix! { u8 u16 u32 u64 u128 i8 i16 i32 i64 i128 }