Skip to main content

glitter_lang/
ast.rs

1#[cfg(test)]
2use proptest::collection::vec;
3#[cfg(test)]
4use proptest::prelude::*;
5use std::fmt;
6use std::iter::{Extend, FromIterator, IntoIterator};
7
8/// All valid expression names
9///
10/// Defines the "standard library" of named expressions which represent git stats
11#[derive(Debug, PartialEq, Eq, Copy, Clone)]
12pub enum Name {
13    Branch,
14    Remote,
15    Ahead,
16    Behind,
17    Conflict,
18    Added,
19    Untracked,
20    Modified,
21    Unstaged,
22    Deleted,
23    DeletedStaged,
24    Renamed,
25    Stashed,
26    Quote,
27}
28
29impl fmt::Display for Name {
30    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31        let literal = match self {
32            Name::Stashed => "h",
33            Name::Branch => "b",
34            Name::Remote => "B",
35            Name::Ahead => "+",
36            Name::Behind => "-",
37            Name::Conflict => "u",
38            Name::Added => "A",
39            Name::Untracked => "a",
40            Name::Modified => "M",
41            Name::Unstaged => "m",
42            Name::Deleted => "d",
43            Name::DeletedStaged => "D",
44            Name::Renamed => "R",
45            Name::Quote => "\\\'",
46        };
47        write!(f, "{}", literal)
48    }
49}
50
51#[cfg(test)]
52pub fn arb_name() -> impl Strategy<Value = Name> {
53    use self::Name::*;
54
55    prop_oneof![
56        Just(Branch),
57        Just(Remote),
58        Just(Ahead),
59        Just(Behind),
60        Just(Conflict),
61        Just(Added),
62        Just(Untracked),
63        Just(Modified),
64        Just(Unstaged),
65        Just(Deleted),
66        Just(DeletedStaged),
67        Just(Renamed),
68        Just(Stashed),
69        Just(Quote),
70    ]
71}
72
73#[derive(Debug, PartialEq, Eq, Copy, Clone)]
74pub enum Color {
75    /// Make text red
76    Red,
77    /// Make text green
78    Green,
79    /// Make the text yellow
80    Yellow,
81    /// Make the text blue
82    Blue,
83    /// Make the text purple
84    Magenta,
85    /// Make the text cyan
86    Cyan,
87    /// Make the text white
88    White,
89    /// Make the text bright black
90    Black,
91    /// Provide a 256 color table text color value
92    RGB(u8, u8, u8),
93}
94
95/// All valid style markers
96///
97/// Defines the range of possible styles
98#[derive(Debug, PartialEq, Eq, Copy, Clone)]
99pub enum Style {
100    /// Reset text to plain terminal style; ANSI code 00 equivalent
101    Reset,
102    /// Bold text in the terminal; ANSI code 01 equivalent
103    Bold,
104    /// Underline text in the terminal; ANSI code 04 equivalent
105    Underline,
106    /// Italisize text in the terminal; ANSI code 03 equivalent
107    Italic,
108    /// Set a foreground color
109    Fg(Color),
110    /// Set a background color
111    Bg(Color),
112}
113
114impl fmt::Display for Style {
115    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116        use Color::*;
117        match self {
118            Style::Reset => write!(f, "~")?,
119            Style::Bold => write!(f, "*")?,
120            Style::Underline => write!(f, "_")?,
121            Style::Italic => write!(f, "i")?,
122            Style::Fg(Red) => write!(f, "r")?,
123            Style::Bg(Red) => write!(f, "R")?,
124            Style::Fg(Green) => write!(f, "g")?,
125            Style::Bg(Green) => write!(f, "G")?,
126            Style::Fg(Yellow) => write!(f, "y")?,
127            Style::Bg(Yellow) => write!(f, "Y")?,
128            Style::Fg(Blue) => write!(f, "b")?,
129            Style::Bg(Blue) => write!(f, "B")?,
130            Style::Fg(Magenta) => write!(f, "m")?,
131            Style::Bg(Magenta) => write!(f, "M")?,
132            Style::Fg(Cyan) => write!(f, "c")?,
133            Style::Bg(Cyan) => write!(f, "C")?,
134            Style::Fg(White) => write!(f, "w")?,
135            Style::Bg(White) => write!(f, "W")?,
136            Style::Fg(Black) => write!(f, "k")?,
137            Style::Bg(Black) => write!(f, "K")?,
138            &Style::Fg(RGB(r, g, b)) => write!(f, "[{},{},{}]", r, g, b)?,
139            &Style::Bg(RGB(r, g, b)) => write!(f, "{{{},{},{}}}", r, g, b)?,
140        };
141        Ok(())
142    }
143}
144
145#[cfg(test)]
146pub fn arb_style() -> impl Strategy<Value = Style> {
147    use self::Color::*;
148    use self::Style::*;
149
150    prop_oneof![
151        Just(Reset),
152        Just(Bold),
153        Just(Underline),
154        Just(Italic),
155        Just(Fg(Red)),
156        Just(Bg(Red)),
157        Just(Fg(Green)),
158        Just(Bg(Green)),
159        Just(Fg(Yellow)),
160        Just(Bg(Yellow)),
161        Just(Fg(Blue)),
162        Just(Bg(Blue)),
163        Just(Fg(Magenta)),
164        Just(Bg(Magenta)),
165        Just(Fg(Cyan)),
166        Just(Bg(Cyan)),
167        Just(Fg(White)),
168        Just(Bg(White)),
169        Just(Fg(Black)),
170        Just(Bg(Black)),
171        any::<(u8, u8, u8)>().prop_map(|(r, g, b)| Fg(RGB(r, g, b))),
172        any::<(u8, u8, u8)>().prop_map(|(r, g, b)| Bg(RGB(r, g, b))),
173    ]
174}
175
176/// An aggregate unit which describes the sub total of a set of styles
177///
178/// ```
179/// use glitter_lang::ast::{Style, CompleteStyle, Color};
180/// use Style::*;
181/// use Color::*;
182/// let complete: CompleteStyle = [Fg(Green), Bold].iter().collect();
183/// assert_eq!(complete, CompleteStyle {
184///     fg: Some(Green),
185///     bold: true,
186///     ..Default::default()
187/// });
188/// ```
189///
190/// The conversion from a collection of styles is lossy:
191///
192/// ```
193/// use glitter_lang::ast::{Style, CompleteStyle, Color};
194/// use Style::*;
195/// use Color::*;
196///
197/// // Style::Reset at the final position is the same as
198/// // CompleteStyle::default()
199/// let reset_to_default: CompleteStyle = [Bg(Red), Reset].iter().collect();
200/// assert_eq!(reset_to_default, CompleteStyle::default());
201///
202/// // Information about repeated styles is lost
203/// let green: CompleteStyle = [Fg(Green)].iter().collect();
204/// let green_repeat: CompleteStyle = std::iter::repeat(&Fg(Green)).take(10).collect();
205/// assert_eq!(green, green_repeat);
206/// ```
207#[derive(Debug, Copy, Clone, PartialEq, Eq)]
208pub struct CompleteStyle {
209    pub fg: Option<Color>,
210    pub bg: Option<Color>,
211    pub bold: bool,
212    pub italics: bool,
213    pub underline: bool,
214}
215
216impl CompleteStyle {
217    pub fn add(&mut self, style: Style) {
218        use Style::*;
219        match style {
220            Fg(color) => self.fg = Some(color),
221            Bg(color) => self.bg = Some(color),
222            Bold => self.bold = true,
223            Italic => self.italics = true,
224            Underline => self.underline = true,
225            Reset => *self = Default::default(),
226        }
227    }
228}
229
230impl Default for CompleteStyle {
231    fn default() -> Self {
232        CompleteStyle {
233            fg: None,
234            bg: None,
235            bold: false,
236            italics: false,
237            underline: false,
238        }
239    }
240}
241
242impl std::ops::AddAssign for CompleteStyle {
243    fn add_assign(&mut self, with: Self) {
244        if with == Default::default() {
245            return *self = Default::default();
246        }
247
248        *self = Self {
249            fg: with.fg.or(self.fg),
250            bg: with.bg.or(self.bg),
251            bold: with.bold || self.bold,
252            italics: with.italics || self.italics,
253            underline: with.underline || self.underline,
254        }
255    }
256}
257
258impl From<Style> for CompleteStyle {
259    fn from(s: Style) -> Self {
260        let mut ctx = Self::default();
261        ctx.add(s);
262        ctx
263    }
264}
265
266impl fmt::Display for CompleteStyle {
267    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
268        use Style::*;
269
270        if *self == Self::default() {
271            return write!(f, "{}", Reset);
272        }
273
274        if let Some(color) = self.fg {
275            write!(f, "{}", Fg(color))?;
276        }
277        if let Some(color) = self.bg {
278            write!(f, "{}", Bg(color))?;
279        }
280        if self.bold {
281            write!(f, "{}", Bold)?;
282        }
283        if self.italics {
284            write!(f, "{}", Italic)?;
285        }
286        if self.underline {
287            write!(f, "{}", Underline)?;
288        }
289
290        Ok(())
291    }
292}
293
294impl<'a> Extend<&'a Style> for CompleteStyle {
295    fn extend<E: IntoIterator<Item = &'a Style>>(&mut self, styles: E) {
296        for style in styles {
297            self.add(*style)
298        }
299    }
300}
301
302impl<'a> FromIterator<&'a Style> for CompleteStyle {
303    fn from_iter<I: IntoIterator<Item = &'a Style>>(iter: I) -> CompleteStyle {
304        let mut complete = CompleteStyle::default();
305        complete.extend(iter);
306        complete
307    }
308}
309
310#[derive(Debug, PartialEq, Eq, Clone, Copy)]
311pub enum Delimiter {
312    /// <>
313    Angle,
314    /// []
315    Square,
316    /// {}
317    Curly,
318    /// \()
319    Parens,
320}
321
322impl Delimiter {
323    pub fn left(&self) -> &'static str {
324        use Delimiter::*;
325        match self {
326            Angle => "<",
327            Square => "[",
328            Curly => "{",
329            Parens => "(",
330        }
331    }
332
333    pub fn right(&self) -> &'static str {
334        use Delimiter::*;
335        match self {
336            Angle => ">",
337            Square => "]",
338            Curly => "}",
339            Parens => ")",
340        }
341    }
342}
343
344#[cfg(test)]
345pub fn arb_delimiter() -> impl Strategy<Value = Delimiter> {
346    use self::Delimiter::*;
347
348    prop_oneof![Just(Angle), Just(Square), Just(Curly), Just(Parens)]
349}
350
351/// Special separator characters which can appear between expressions
352#[derive(Debug, Copy, Clone, Eq, PartialEq)]
353pub enum Separator {
354    At,
355    Bar,
356    Dot,
357    Comma,
358    Space,
359    Colon,
360    Semicolon,
361    Underscore,
362}
363
364impl Separator {
365    pub fn as_str(&self) -> &'static str {
366        use Separator::*;
367        match self {
368            At => "@",
369            Bar => "|",
370            Dot => ".",
371            Comma => ",",
372            Space => " ",
373            Colon => ":",
374            Semicolon => ";",
375            Underscore => "_",
376        }
377    }
378}
379
380impl AsRef<str> for Separator {
381    fn as_ref(&self) -> &'static str {
382        self.as_str()
383    }
384}
385
386impl fmt::Display for Separator {
387    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
388        write!(f, "{}", self.as_ref())
389    }
390}
391
392#[cfg(test)]
393pub fn arb_separator() -> impl Strategy<Value = Separator> {
394    use Separator::*;
395
396    prop_oneof![
397        Just(At),
398        Just(Bar),
399        Just(Dot),
400        Just(Comma),
401        Just(Space),
402        Just(Colon),
403        Just(Semicolon),
404        Just(Underscore),
405    ]
406}
407
408/// The types of possible expressions which form an expression tree
409///
410/// The gist format has three types of valid expressions:
411///
412/// 1. Named expressions
413/// 2. Group Expressions
414/// 3. Literal Expressions
415///
416/// The interpreter transforms these expressions to their final output after they have been
417/// parsed from the input string.
418///
419/// **Named expressions** take one of two forms: the plain form with no arguments, or with arguments
420///
421/// - `name` plain form
422/// - `name(exp1exp2...exp3)` any number of expressions
423///
424/// **Group expressions** are set of expressions, which are not comma seperated.  There are a few
425/// base group types:
426///
427/// - `\()` parentheses - wrap with parens
428/// - `\{}` curly braces - wrap with curly braces
429/// - `\[]` square brackets - wrap contents with square brackets
430/// - `\<>` angle brackets - wrap contenst with angle brackets
431///
432/// By nesting groups of expressions, we can create an implicit tree.
433///
434/// A **literal expression** is any valid utf8 characters between single quites, except for single
435/// quotes and backslashes.
436///
437/// ```txt
438/// 'hello''we''are''literal''expressions''I am one including whitespace'
439/// ```
440#[derive(Debug, PartialEq, Eq, Clone)]
441pub enum Expression {
442    /// An expression with a name and optional arguments which represents git repository stats
443    Named {
444        /// Name of the expression
445        name: Name,
446        /// Arguments to the expression, zero or more
447        sub: Tree,
448    },
449    /// An expression which represents terminal text formatting
450    Format { style: CompleteStyle, sub: Tree },
451    /// A group of sub-expressions which forms an expression tree
452    Group {
453        /// Group delimiter type, [], <>, {}, or \()
454        d: Delimiter,
455        /// A tree of sub expressions
456        sub: Tree,
457    },
458    /// Literal characters including whitespace, surrounded by single quotes
459    Literal(String),
460    /// Separator between elements in a tree
461    Separator(Separator),
462}
463
464impl fmt::Display for Expression {
465    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
466        match self {
467            Expression::Named { ref name, ref sub } => {
468                write!(f, "{}", name)?;
469                if sub.0.is_empty() {
470                    Ok(())
471                } else {
472                    write!(f, "({})", sub)
473                }
474            }
475            Expression::Group { ref d, ref sub } => match d {
476                Delimiter::Square => write!(f, "[{}]", sub),
477                Delimiter::Angle => write!(f, "<{}>", sub),
478                Delimiter::Parens => write!(f, "\\({})", sub),
479                Delimiter::Curly => write!(f, "{{{}}}", sub),
480            },
481            Expression::Format { ref style, ref sub } => {
482                write!(f, "#")?;
483                write!(f, "{}", style)?;
484                write!(f, "({})", sub)
485            }
486            Expression::Literal(ref string) => write!(f, "'{}'", string),
487            Expression::Separator(s) => write!(f, "{}", s),
488        }
489    }
490}
491
492#[cfg(test)]
493pub fn arb_expression() -> impl Strategy<Value = Expression> {
494    use self::Expression::*;
495
496    let leaf = prop_oneof![
497        arb_name().prop_map(|name| Named {
498            name: name,
499            sub: Tree::new(),
500        }),
501        vec(arb_style(), 1..5).prop_map(|style| Format {
502            style: style.iter().collect(),
503            sub: Tree::new(),
504        }),
505        "[^']*".prop_map(Literal),
506        arb_separator().prop_map(Separator),
507    ];
508
509    leaf.prop_recursive(8, 64, 10, |inner| {
510        prop_oneof![
511            (arb_name(), vec(inner.clone(), 0..10)).prop_map(|(name, sub)| Named {
512                name: name,
513                sub: Tree(sub),
514            }),
515            (vec(arb_style(), 1..10), vec(inner.clone(), 0..10)).prop_map(|(style, sub)| Format {
516                style: style.iter().collect(),
517                sub: Tree(sub),
518            }),
519            (arb_delimiter(), vec(inner.clone(), 0..10)).prop_map(|(delimiter, sub)| Group {
520                d: delimiter,
521                sub: Tree(sub),
522            }),
523            arb_separator().prop_map(Separator),
524        ]
525    })
526}
527
528/// A collection of expressions which may recursively form an expression tree
529///
530/// Seperate struct, use mutual recursion between tree and expressions to make parsing easier to
531/// implement.  May combine them in the future.
532#[derive(Debug, PartialEq, Eq, Clone)]
533pub struct Tree(pub Vec<Expression>);
534
535impl Tree {
536    /// Create an empty tree
537    pub fn new() -> Tree {
538        Tree(Vec::new())
539    }
540}
541
542impl Default for Tree {
543    fn default() -> Self {
544        Tree(Vec::new())
545    }
546}
547
548impl fmt::Display for Tree {
549    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
550        for exp in &self.0 {
551            write!(f, "{}", exp)?;
552        }
553        Ok(())
554    }
555}
556
557#[cfg(test)]
558pub fn arb_tree(n: usize) -> impl Strategy<Value = Tree> {
559    vec(arb_expression(), 0..n).prop_map(Tree)
560}