less-rs 0.0.0

A Rust implementation of the Less CSS preprocessor.
Documentation
use std::{
  fmt::Write,
  ops::{Deref, DerefMut},
};

use cssparser::{Delimiter, Parser, RuleBodyParser, match_ignore_ascii_case};
use less_allocator::{Allocator, Box, Vec};
use selectors::parser::{NestingRequirement, ParseErrorRecovery};

use crate::{
  error::ParserError,
  printer::{Printer, PrinterError},
  properties::{PropertyDeclaration, PropertyId},
  rules::{
    CssRule, CssRuleList, StyleRule,
    variable::{Variable, VariableValue},
  },
  selector::{SelectorList, SelectorParser},
  traits::{Parse, ToCss},
};

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum State {
  Start = 1,
  Layers = 2,
  Imports = 3,
  Namespaces = 4,
  Body = 5,
}

pub struct TopLevelRuleParser<'a, 'i> {
  allocator: &'i Allocator,
  state: State,
  // debug
  pub(crate) rules: &'a mut CssRuleList<'i>,
}

impl<'a, 'i> TopLevelRuleParser<'a, 'i> {
  pub fn new(allocator: &'i Allocator, rules: &'a mut CssRuleList<'i>) -> Self {
    TopLevelRuleParser {
      allocator,
      state: State::Start,
      rules,
    }
  }
}

#[derive(Debug)]
pub enum AtRulePrelude<'i> {
  LessVariable(Variable<'i>),
}

impl<'a, 'i> TopLevelRuleParser<'a, 'i> {
  fn nested(&mut self) -> NestedRuleParser<'_, 'i> {
    NestedRuleParser {
      allocator: self.allocator,
      declarations: DeclarationList(Vec::new_in(self.allocator)),
      rules: self.rules,
    }
  }
}

impl<'a, 'i> cssparser::AtRuleParser<'i> for TopLevelRuleParser<'a, 'i> {
  type Prelude = AtRulePrelude<'i>;

  type AtRule = ();

  type Error = ParserError<'i>;

  fn parse_prelude<'t>(
    &mut self,
    name: cssparser::CowRcStr<'i>,
    input: &mut Parser<'i, 't>,
  ) -> Result<Self::Prelude, cssparser::ParseError<'i, Self::Error>> {
    match_ignore_ascii_case! { &*name,
      _ => cssparser::AtRuleParser::parse_prelude(&mut self.nested(), name, input)
    }
  }

  fn rule_without_block(
    &mut self,
    prelude: Self::Prelude,
    start: &cssparser::ParserState,
  ) -> Result<Self::AtRule, ()> {
    cssparser::AtRuleParser::rule_without_block(&mut self.nested(), prelude, start)
  }

  fn parse_block<'t>(
    &mut self,
    prelude: Self::Prelude,
    start: &cssparser::ParserState,
    input: &mut Parser<'i, 't>,
  ) -> Result<Self::AtRule, cssparser::ParseError<'i, Self::Error>> {
    self.state = State::Body;
    cssparser::AtRuleParser::parse_block(&mut self.nested(), prelude, start, input)
  }
}

#[derive(Debug)]
pub struct DeclarationList<'i>(pub(crate) Vec<'i, PropertyDeclaration<'i>>);

impl<'i> ToCss<'i> for DeclarationList<'i> {
  fn to_css<W: std::fmt::Write>(
    &self,
    dest: &mut Printer<'_, 'i, W>,
  ) -> Result<(), PrinterError<'i>> {
    for declaration in self.0.iter() {
      declaration.to_css(dest)?;
    }
    Ok(())
  }
}

impl<'i> Deref for DeclarationList<'i> {
  type Target = Vec<'i, PropertyDeclaration<'i>>;

  fn deref(&self) -> &Self::Target {
    &self.0
  }
}

impl DerefMut for DeclarationList<'_> {
  fn deref_mut(&mut self) -> &mut Self::Target {
    &mut self.0
  }
}

struct NestedRuleParser<'a, 'i> {
  allocator: &'i Allocator,
  declarations: DeclarationList<'i>,
  rules: &'a mut CssRuleList<'i>,
}

impl<'a, 'i> NestedRuleParser<'a, 'i> {
  /// Parse [less variables](https://lesscss.org/features/#variables-feature
  fn parse_at_variables_declaration<'t>(
    &mut self,
    allocator: &'i Allocator,
    name: cssparser::CowRcStr<'i>,
    input: &mut Parser<'i, 't>,
  ) -> Result<AtRulePrelude<'i>, cssparser::ParseError<'i, ParserError<'i>>> {
    input.expect_colon()?;
    let value = VariableValue::parse(allocator, input)?;
    Ok(AtRulePrelude::LessVariable(Variable::new(name, value)))
  }

  /// Parse nested declarations or rules
  fn parse_nested<'t>(
    &mut self,
    input: &mut Parser<'i, 't>,
  ) -> Result<(DeclarationList<'i>, CssRuleList<'i>), cssparser::ParseError<'i, ParserError<'i>>>
  {
    let mut rules = CssRuleList(Vec::new_in(self.allocator));
    let mut nested_parser = NestedRuleParser {
      allocator: self.allocator,
      declarations: DeclarationList(Vec::new_in(self.allocator)),
      rules: &mut rules,
    };

    let iter = RuleBodyParser::new(input, &mut nested_parser);
    for result in iter {
      match result {
        Ok(_) => {}
        Err(err) => {
          dbg!(err);
        }
      }
      // TODO: handle errors
      continue;
    }

    Ok((nested_parser.declarations, rules))
  }
}

impl<'a, 'i> cssparser::AtRuleParser<'i> for NestedRuleParser<'a, 'i> {
  type Prelude = AtRulePrelude<'i>;

  type AtRule = ();

  type Error = ParserError<'i>;

  fn parse_prelude<'t>(
    &mut self,
    name: cssparser::CowRcStr<'i>,
    input: &mut Parser<'i, 't>,
  ) -> Result<Self::Prelude, cssparser::ParseError<'i, Self::Error>> {
    match_ignore_ascii_case! { &*name,
      _ => self.parse_at_variables_declaration(self.allocator, name, input)
    }
  }

  fn rule_without_block(
    &mut self,
    prelude: Self::Prelude,
    _start: &cssparser::ParserState,
  ) -> Result<Self::AtRule, ()> {
    if let AtRulePrelude::LessVariable(variable) = prelude {
      self
        .rules
        .push(CssRule::LessVariable(Box(self.allocator.alloc(variable))));
    };
    Ok(())
  }

  fn parse_block<'t>(
    &mut self,
    prelude: Self::Prelude,
    start: &cssparser::ParserState,
    input: &mut Parser<'i, 't>,
  ) -> Result<Self::AtRule, cssparser::ParseError<'i, Self::Error>> {
    let _ = prelude;
    let _ = start;
    Err(input.new_error(cssparser::BasicParseErrorKind::AtRuleBodyInvalid))
  }
}

impl<'a, 'i> cssparser::QualifiedRuleParser<'i> for NestedRuleParser<'a, 'i> {
  type Prelude = SelectorList<'i>;

  type QualifiedRule = ();

  type Error = ParserError<'i>;

  fn parse_prelude<'t>(
    &mut self,
    input: &mut Parser<'i, 't>,
  ) -> Result<Self::Prelude, cssparser::ParseError<'i, Self::Error>> {
    let parser = SelectorParser {};
    SelectorList::parse(
      &parser,
      input,
      ParseErrorRecovery::DiscardList,
      NestingRequirement::None,
    )
  }

  fn parse_block<'t>(
    &mut self,
    prelude: Self::Prelude,
    start: &cssparser::ParserState,
    input: &mut Parser<'i, 't>,
  ) -> Result<Self::QualifiedRule, cssparser::ParseError<'i, Self::Error>> {
    let (declarations, rules) = self.parse_nested(input)?;
    self
      .rules
      .push(CssRule::Style(Box(self.allocator.alloc(StyleRule {
        selector: prelude,
        declarations,
        rules,
      }))));
    Ok(())
  }
}

impl<'a, 'i> cssparser::RuleBodyItemParser<'i, (), ParserError<'i>> for NestedRuleParser<'a, 'i> {
  fn parse_qualified(&self) -> bool {
    true
  }

  fn parse_declarations(&self) -> bool {
    true
    // self.allow_declarations
  }
}

impl<'a, 'i> cssparser::DeclarationParser<'i> for NestedRuleParser<'a, 'i> {
  type Declaration = ();
  type Error = ParserError<'i>;

  fn parse_value<'t>(
    &mut self,
    name: cssparser::CowRcStr<'i>,
    input: &mut cssparser::Parser<'i, 't>,
    _declaration_start: &cssparser::ParserState,
  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
    let property_id = PropertyId::from(name);
    let declaration = input.parse_until_before(Delimiter::Bang, |input| {
      PropertyDeclaration::parse(self.allocator, &property_id, input)
    })?;
    self.declarations.push(declaration);
    Ok(())
  }
}

impl<'a, 'i> cssparser::QualifiedRuleParser<'i> for TopLevelRuleParser<'a, 'i> {
  type Prelude = SelectorList<'i>;

  type QualifiedRule = ();

  type Error = ParserError<'i>;

  fn parse_prelude<'t>(
    &mut self,
    input: &mut Parser<'i, 't>,
  ) -> Result<Self::Prelude, cssparser::ParseError<'i, Self::Error>> {
    self.state = State::Body;
    cssparser::QualifiedRuleParser::parse_prelude(&mut self.nested(), input)
  }

  fn parse_block<'t>(
    &mut self,
    prelude: Self::Prelude,
    start: &cssparser::ParserState,
    input: &mut Parser<'i, 't>,
  ) -> Result<Self::QualifiedRule, cssparser::ParseError<'i, Self::Error>> {
    cssparser::QualifiedRuleParser::parse_block(&mut self.nested(), prelude, start, input)
  }
}