grass_compiler/ast/
expr.rs

1use std::{iter::Iterator, sync::Arc};
2
3use codemap::{Span, Spanned};
4
5use crate::{
6    color::Color,
7    common::{BinaryOp, Brackets, Identifier, ListSeparator, QuoteKind, UnaryOp},
8    unit::Unit,
9    value::{CalculationName, Number},
10};
11
12use super::{ArgumentInvocation, AstSupportsCondition, Interpolation, InterpolationPart};
13
14/// Represented by the `if` function
15#[derive(Debug, Clone)]
16pub struct Ternary(pub ArgumentInvocation);
17
18#[derive(Debug, Clone)]
19pub struct ListExpr {
20    pub elems: Vec<Spanned<AstExpr>>,
21    pub separator: ListSeparator,
22    pub brackets: Brackets,
23}
24
25#[derive(Debug, Clone)]
26pub struct FunctionCallExpr {
27    pub namespace: Option<Spanned<Identifier>>,
28    pub name: Identifier,
29    pub arguments: Arc<ArgumentInvocation>,
30    pub span: Span,
31}
32
33#[derive(Debug, Clone)]
34pub struct InterpolatedFunction {
35    pub name: Interpolation,
36    pub arguments: ArgumentInvocation,
37    pub span: Span,
38}
39
40#[derive(Debug, Clone, Default)]
41pub struct AstSassMap(pub Vec<(Spanned<AstExpr>, AstExpr)>);
42
43#[derive(Debug, Clone)]
44pub struct BinaryOpExpr {
45    pub lhs: AstExpr,
46    pub op: BinaryOp,
47    pub rhs: AstExpr,
48    pub allows_slash: bool,
49    pub span: Span,
50}
51
52#[derive(Debug, Clone)]
53pub enum AstExpr {
54    BinaryOp(Arc<BinaryOpExpr>),
55    True,
56    False,
57    Calculation {
58        name: CalculationName,
59        args: Vec<Self>,
60    },
61    Color(Arc<Color>),
62    FunctionCall(FunctionCallExpr),
63    If(Arc<Ternary>),
64    InterpolatedFunction(Arc<InterpolatedFunction>),
65    List(ListExpr),
66    Map(AstSassMap),
67    Null,
68    Number {
69        n: Number,
70        unit: Unit,
71    },
72    Paren(Arc<Self>),
73    ParentSelector,
74    String(StringExpr, Span),
75    Supports(Arc<AstSupportsCondition>),
76    UnaryOp(UnaryOp, Arc<Self>, Span),
77    Variable {
78        name: Spanned<Identifier>,
79        namespace: Option<Spanned<Identifier>>,
80    },
81}
82
83// todo: make quotes bool
84// todo: track span inside
85#[derive(Debug, Clone)]
86pub struct StringExpr(pub Interpolation, pub QuoteKind);
87
88impl StringExpr {
89    fn quote_inner_text(
90        text: &str,
91        quote: char,
92        buffer: &mut Interpolation,
93        // default=false
94        is_static: bool,
95    ) {
96        let mut chars = text.chars().peekable();
97        while let Some(char) = chars.next() {
98            if char == '\n' || char == '\r' {
99                buffer.add_char('\\');
100                buffer.add_char('a');
101                if let Some(next) = chars.peek() {
102                    if next.is_ascii_whitespace() || next.is_ascii_hexdigit() {
103                        buffer.add_char(' ');
104                    }
105                }
106            } else {
107                if char == quote
108                    || char == '\\'
109                    || (is_static && char == '#' && chars.peek() == Some(&'{'))
110                {
111                    buffer.add_char('\\');
112                }
113                buffer.add_char(char);
114            }
115        }
116    }
117
118    fn best_quote<'a>(strings: impl Iterator<Item = &'a str>) -> char {
119        let mut contains_double_quote = false;
120        for s in strings {
121            for c in s.chars() {
122                if c == '\'' {
123                    return '"';
124                }
125                if c == '"' {
126                    contains_double_quote = true;
127                }
128            }
129        }
130        if contains_double_quote {
131            '\''
132        } else {
133            '"'
134        }
135    }
136
137    pub fn as_interpolation(self, is_static: bool) -> Interpolation {
138        if self.1 == QuoteKind::None {
139            return self.0;
140        }
141
142        let quote = Self::best_quote(self.0.contents.iter().filter_map(|c| match c {
143            InterpolationPart::Expr(..) => None,
144            InterpolationPart::String(text) => Some(text.as_str()),
145        }));
146
147        let mut buffer = Interpolation::new();
148        buffer.add_char(quote);
149
150        for value in self.0.contents {
151            match value {
152                InterpolationPart::Expr(e) => buffer.add_expr(e),
153                InterpolationPart::String(text) => {
154                    Self::quote_inner_text(&text, quote, &mut buffer, is_static);
155                }
156            }
157        }
158
159        buffer.add_char(quote);
160
161        buffer
162    }
163}
164
165impl AstExpr {
166    pub fn is_variable(&self) -> bool {
167        matches!(self, Self::Variable { .. })
168    }
169
170    pub fn is_slash_operand(&self) -> bool {
171        match self {
172            Self::Number { .. } | Self::Calculation { .. } => true,
173            Self::BinaryOp(binop) => binop.allows_slash,
174            _ => false,
175        }
176    }
177
178    pub fn slash(left: Self, right: Self, span: Span) -> Self {
179        Self::BinaryOp(Arc::new(BinaryOpExpr {
180            lhs: left,
181            op: BinaryOp::Div,
182            rhs: right,
183            allows_slash: true,
184            span,
185        }))
186    }
187
188    pub const fn span(self, span: Span) -> Spanned<Self> {
189        Spanned { node: self, span }
190    }
191}