grass_compiler 0.12.2

Internal implementation of the grass compiler
Documentation
use std::fmt;

use crate::{ast::KeyframesSelector, error::SassResult, lexer::Lexer, Token};

use super::BaseParser;

impl fmt::Display for KeyframesSelector {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            KeyframesSelector::To => f.write_str("to"),
            KeyframesSelector::From => f.write_str("from"),
            KeyframesSelector::Percent(p) => write!(f, "{}%", p),
        }
    }
}

pub(crate) struct KeyframesSelectorParser<'a> {
    toks: Lexer<'a>,
}

impl<'a> BaseParser<'a> for KeyframesSelectorParser<'a> {
    fn toks(&self) -> &Lexer<'a> {
        &self.toks
    }

    fn toks_mut(&mut self) -> &mut Lexer<'a> {
        &mut self.toks
    }
}

impl<'a> KeyframesSelectorParser<'a> {
    pub fn new(toks: Lexer<'a>) -> KeyframesSelectorParser<'a> {
        KeyframesSelectorParser { toks }
    }

    pub fn parse_keyframes_selector(&mut self) -> SassResult<Vec<KeyframesSelector>> {
        let mut selectors = Vec::new();
        loop {
            self.whitespace()?;
            if self.looking_at_identifier() {
                if self.scan_identifier("to", false)? {
                    selectors.push(KeyframesSelector::To);
                } else if self.scan_identifier("from", false)? {
                    selectors.push(KeyframesSelector::From);
                } else {
                    return Err(("Expected \"to\" or \"from\".", self.toks.current_span()).into());
                }
            } else {
                selectors.push(self.parse_percentage_selector()?);
            }

            self.whitespace()?;

            if !self.scan_char(',') {
                break;
            }
        }

        Ok(selectors)
    }

    fn parse_percentage_selector(&mut self) -> SassResult<KeyframesSelector> {
        let mut buffer = String::new();

        if self.scan_char('+') {
            buffer.push('+');
        }

        if !matches!(
            self.toks.peek(),
            Some(Token {
                kind: '0'..='9' | '.',
                ..
            })
        ) {
            return Err(("Expected number.", self.toks.current_span()).into());
        }

        while matches!(
            self.toks.peek(),
            Some(Token {
                kind: '0'..='9',
                ..
            })
        ) {
            buffer.push(self.toks.next().unwrap().kind);
        }

        if self.scan_char('.') {
            buffer.push('.');

            while matches!(
                self.toks.peek(),
                Some(Token {
                    kind: '0'..='9',
                    ..
                })
            ) {
                buffer.push(self.toks.next().unwrap().kind);
            }
        }

        if self.scan_ident_char('e', false)? {
            buffer.push('e');

            if matches!(
                self.toks.peek(),
                Some(Token {
                    kind: '+' | '-',
                    ..
                })
            ) {
                buffer.push(self.toks.next().unwrap().kind);
            }

            if !matches!(
                self.toks.peek(),
                Some(Token {
                    kind: '0'..='9',
                    ..
                })
            ) {
                return Err(("Expected digit.", self.toks.current_span()).into());
            }

            while matches!(
                self.toks.peek(),
                Some(Token {
                    kind: '0'..='9',
                    ..
                })
            ) {
                buffer.push(self.toks.next().unwrap().kind);
            }
        }

        self.expect_char('%')?;

        Ok(KeyframesSelector::Percent(buffer.into_boxed_str()))
    }
}