cedar_policy_formatter/pprint/
token.rs

1/*
2 * Copyright Cedar Contributors
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 *      https://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 */
16
17// PANIC SAFETY: there's little we can do about Logos.
18#![allow(clippy::indexing_slicing)]
19use logos::{Logos, Span};
20use smol_str::SmolStr;
21use std::fmt::{self, Display};
22
23// PANIC SAFETY: These regex patterns are valid
24#[allow(clippy::unwrap_used)]
25pub(crate) mod regex_constants {
26    use regex::Regex;
27    lazy_static::lazy_static! {
28        pub static ref COMMENT : Regex = Regex::new(r"//[^\n\r]*").unwrap();
29        pub static ref STRING : Regex = Regex::new(r#""(\\.|[^"\\])*""#).unwrap();
30    }
31}
32
33pub fn get_comment(text: &str) -> impl Iterator<Item = &str> + std::fmt::Debug {
34    regex_constants::COMMENT
35        .find_iter(text)
36        .map(|c| c.as_str().trim())
37}
38
39// Represent Cedar comments
40#[derive(Clone, Debug, Default, PartialEq, Eq)]
41pub struct Comment<'src> {
42    leading_comment: Vec<&'src str>,
43    trailing_comment: &'src str,
44}
45
46impl<'src> Comment<'src> {
47    pub fn new(leading_comment: &'src str, trailing_comment: &'src str) -> Self {
48        Self {
49            leading_comment: get_comment(leading_comment).collect(),
50            // The trailing comments must not have line breaks, so we don't need
51            // to find comments with regex matching. If the trimmed string is
52            // empty, then there was no comment.
53            trailing_comment: trailing_comment.trim(),
54        }
55    }
56
57    pub fn leading_comment(&self) -> &[&'src str] {
58        &self.leading_comment
59    }
60
61    pub fn trailing_comment(&self) -> &'src str {
62        self.trailing_comment
63    }
64
65    fn format_leading_comment(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        for comment_line in itertools::Itertools::intersperse(self.leading_comment.iter(), &"\n") {
67            write!(f, "{comment_line}")?;
68        }
69        if !self.leading_comment.is_empty() {
70            writeln!(f)?;
71        }
72        Ok(())
73    }
74
75    fn format_trailing_comment(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        write!(f, "{}", self.trailing_comment)
77    }
78}
79
80impl Display for Comment<'_> {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        self.format_leading_comment(f)?;
83        self.format_trailing_comment(f)?;
84        Ok(())
85    }
86}
87
88// Cedar tokens
89#[derive(Logos, Clone, Debug, PartialEq, Eq)]
90pub enum Token {
91    #[regex(r"\s*", logos::skip)]
92    Whitespace,
93
94    #[regex(r"//[^\n\r]*[\n\r]*", logos::skip)]
95    Comment,
96
97    #[token("true")]
98    True,
99
100    #[token("false")]
101    False,
102
103    #[token("if")]
104    If,
105
106    #[token("permit")]
107    Permit,
108
109    #[token("forbid")]
110    Forbid,
111
112    #[token("when")]
113    When,
114
115    #[token("unless")]
116    Unless,
117
118    #[token("in")]
119    In,
120
121    #[token("has")]
122    Has,
123
124    #[token("like")]
125    Like,
126
127    #[token("is")]
128    Is,
129
130    #[token("then")]
131    Then,
132
133    #[token("else")]
134    Else,
135
136    #[token("principal")]
137    Principal,
138
139    #[token("action")]
140    Action,
141
142    #[token("resource")]
143    Resource,
144
145    #[token("context")]
146    Context,
147
148    #[token("?principal")]
149    PrincipalSlot,
150
151    #[token("?resource")]
152    ResourceSlot,
153
154    #[regex(r"[_a-zA-Z][_a-zA-Z0-9]*", |lex| SmolStr::new(lex.slice()))]
155    Identifier(SmolStr),
156
157    #[regex("[0-9]+", |lex| SmolStr::new(lex.slice()))]
158    Number(SmolStr),
159
160    #[regex(r#""(\\.|[^"\\])*""#, |lex| SmolStr::new(lex.slice()))]
161    Str(SmolStr),
162
163    #[token("@")]
164    At,
165
166    #[token(".")]
167    Dot,
168
169    #[token(",")]
170    Comma,
171
172    #[token(";")]
173    SemiColon,
174
175    #[token(":")]
176    Colon,
177
178    #[token("::")]
179    DoubleColon,
180
181    #[token("(")]
182    LParen,
183
184    #[token(")")]
185    RParen,
186
187    #[token("{")]
188    LBrace,
189
190    #[token("}")]
191    RBrace,
192
193    #[token("[")]
194    LBracket,
195
196    #[token("]")]
197    RBracket,
198
199    #[token("==")]
200    Equal,
201
202    #[token("!=")]
203    NotEqual,
204
205    #[token("<")]
206    Lt,
207
208    #[token("<=")]
209    Le,
210
211    #[token(">")]
212    Gt,
213
214    #[token(">=")]
215    Ge,
216
217    #[token("||")]
218    Or,
219
220    #[token("&&")]
221    And,
222
223    #[token("+")]
224    Add,
225
226    #[token("-")]
227    Dash,
228
229    #[token("*")]
230    Mul,
231
232    #[token("/")]
233    Div,
234
235    #[token("%")]
236    Modulo,
237
238    #[token("!")]
239    Neg,
240}
241
242impl fmt::Display for Token {
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        match self {
245            Self::Action => write!(f, "action"),
246            Self::Add => write!(f, "+"),
247            Self::And => write!(f, "&&"),
248            Self::At => write!(f, "@"),
249            Self::Colon => write!(f, ":"),
250            Self::Comma => write!(f, ","),
251            // PANIC SAFETY: comment should be ignored as specified by the lexer regex
252            #[allow(clippy::unreachable)]
253            Self::Comment => unreachable!("comment should be skipped!"),
254            Self::Context => write!(f, "context"),
255            Self::Dash => write!(f, "-"),
256            Self::Div => write!(f, "/"),
257            Self::Dot => write!(f, "."),
258            Self::DoubleColon => write!(f, "::"),
259            Self::Else => write!(f, "else"),
260            Self::Equal => write!(f, "=="),
261            Self::False => write!(f, "false"),
262            Self::Forbid => write!(f, "forbid"),
263            Self::Ge => write!(f, ">="),
264            Self::Gt => write!(f, ">"),
265            Self::Has => write!(f, "has"),
266            Self::Identifier(i) => write!(f, "{}", i),
267            Self::If => write!(f, "if"),
268            Self::In => write!(f, "in"),
269            Self::LBrace => write!(f, "{{"),
270            Self::LBracket => write!(f, "["),
271            Self::LParen => write!(f, "("),
272            Self::Le => write!(f, "<="),
273            Self::Like => write!(f, "like"),
274            Self::Is => write!(f, "is"),
275            Self::Lt => write!(f, "<"),
276            Self::Modulo => write!(f, "%"),
277            Self::Mul => write!(f, "*"),
278            Self::Neg => write!(f, "!"),
279            Self::NotEqual => write!(f, "!="),
280            Self::Number(n) => write!(f, "{}", n),
281            Self::Or => write!(f, "||"),
282            Self::Permit => write!(f, "permit"),
283            Self::Principal => write!(f, "principal"),
284            Self::PrincipalSlot => write!(f, "principal?"),
285            Self::RBrace => write!(f, "}}"),
286            Self::RBracket => write!(f, "]"),
287            Self::RParen => write!(f, ")"),
288            Self::Resource => write!(f, "resource"),
289            Self::ResourceSlot => write!(f, "resource?"),
290            Self::SemiColon => write!(f, ";"),
291            Self::Str(s) => write!(f, "{}", s),
292            Self::Then => write!(f, "then"),
293            Self::True => write!(f, "true"),
294            Self::Unless => write!(f, "unless"),
295            Self::When => write!(f, "when"),
296            // PANIC SAFETY: whitespace should be ignored as specified by the lexer regex
297            #[allow(clippy::unreachable)]
298            Self::Whitespace => unreachable!("whitespace should be skipped!"),
299        }
300    }
301}
302
303// A wrapper for token span (i.e., (Token, Span))
304// We use this wrapper for easier processing of comments
305#[derive(Debug, Clone, PartialEq, Eq)]
306pub struct WrappedToken<'src> {
307    pub token: Token,
308    pub comment: Comment<'src>,
309    pub span: Span,
310}
311
312impl<'src> WrappedToken<'src> {
313    pub fn new(token: Token, span: Span, comment: Comment<'src>) -> Self {
314        Self {
315            token,
316            comment,
317            span,
318        }
319    }
320
321    fn clear_leading_comment(&mut self) {
322        self.comment.leading_comment.clear();
323    }
324
325    fn clear_trailing_comment(&mut self) {
326        self.comment.trailing_comment = "";
327    }
328
329    pub fn consume_leading_comment(&mut self) -> Vec<&'src str> {
330        let comment = self.comment.leading_comment.clone();
331        self.clear_leading_comment();
332        comment
333    }
334
335    pub fn consume_comment(&mut self) -> Comment<'src> {
336        let comment = self.comment.clone();
337        self.clear_leading_comment();
338        self.clear_trailing_comment();
339        comment
340    }
341}
342
343impl Display for WrappedToken<'_> {
344    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345        self.comment.format_leading_comment(f)?;
346        write!(f, "{} ", self.token)?;
347        self.comment.format_trailing_comment(f)?;
348        Ok(())
349    }
350}