less-rs 0.0.0

A Rust implementation of the Less CSS preprocessor.
Documentation
use std::{cell::RefCell, fmt::Write as _};

use cssparser::{CowRcStr, Delimiter, ParseError, Parser, ParserInput, Token};
use less_allocator::{Allocator, Box, Vec};

use crate::{
  error::ParserError,
  printer::{Printer, PrinterError},
  rules::variable::{Variable, VariableValue},
  traits::{Parse, ToCss},
  values::{token::TokenList, variable::VariableReference},
};

/// A CSS token or less variable reference.
#[derive(Debug)]
enum TokenOrVariable<'i> {
  Token(Token<'i>),
  Variable(VariableReference<'i>),
}

impl<'i> ToCss<'i> for TokenOrVariable<'i> {
  fn to_css<W>(&self, dest: &mut Printer<'_, 'i, W>) -> Result<(), PrinterError<'i>>
  where
    W: std::fmt::Write,
  {
    match self {
      TokenOrVariable::Token(token) => <Token as cssparser::ToCss>::to_css(token, dest)?,
      TokenOrVariable::Variable(variable_reference) => {
        todo!()
      }
    };
    Ok(())
  }
}

#[derive(Debug)]
struct UnparsedProperty<'i> {
  property_id: PropertyId<'i>,
  tokens: TokenList<'i>,
  raw_string: &'i str,
}

impl<'i> UnparsedProperty<'i> {
  fn parse<'t>(
    allocator: &'i less_allocator::Allocator,
    property_id: PropertyId<'i>,
    input: &mut cssparser::Parser<'i, 't>,
  ) -> Result<Self, cssparser::ParseError<'i, crate::error::ParserError<'i>>> {
    let start_position = input.position();
    let tokens = TokenList::parse_until_before(allocator, input, Delimiter::Semicolon)?;
    dbg!(input.slice_from(start_position));
    Ok(UnparsedProperty {
      property_id,
      tokens,
      raw_string: input.slice_from(start_position),
    })
  }
}

impl<'i> ToCss<'i> for UnparsedProperty<'i> {
  fn to_css<W>(&self, dest: &mut Printer<'_, 'i, W>) -> Result<(), PrinterError<'i>>
  where
    W: std::fmt::Write,
  {
    let tokens = self.tokens.evaluate(dest)?;
    for token in tokens.iter() {
      cssparser::ToCss::to_css(token, dest)?;
    }
    Ok(())
  }
}

#[derive(Debug)]
pub enum PropertyDeclaration<'i> {
  Unparsed(Box<'i, UnparsedProperty<'i>>),
}

/// Resolves a variable reference to its parsed value, if available.
/// If the variable is not parsed yet, it will parse it and store the tokens in the variable's value.
pub(crate) fn resolve_and_evaluate_variable<'i, W: std::fmt::Write>(
  variable: &VariableReference<'i>,
  dest: &mut Printer<'_, 'i, W>,
) -> Result<&'i Variable<'i>, PrinterError<'i>> {
  let resolved_variable = match dest.context.resolve_variable(variable) {
    Ok(v) => v,
    Err(_) => {
      return Err(PrinterError::LessVariableUndefined(variable.clone()));
    }
  };
  if resolved_variable.evaluating.get() {
    return Err(PrinterError::LessRecursiveVariable(variable.clone()));
  }
  resolved_variable.evaluating.set(true);
  let variable_parsed = matches!(*resolved_variable.value.borrow(), VariableValue::Parsed(_));
  if variable_parsed {
    return Ok(resolved_variable);
  }

  let tokens =
    TokenList::parse_raw_until_before(dest.allocator, resolved_variable.raw, Delimiter::Semicolon)
      .map_err(PrinterError::LessVariableParseError)?;
  *resolved_variable.value.borrow_mut() = VariableValue::Parsed(tokens);

  Ok(resolved_variable)
}

impl<'i> ToCss<'i> for PropertyDeclaration<'i> {
  fn to_css<W: std::fmt::Write>(
    &self,
    dest: &mut Printer<'_, 'i, W>,
  ) -> Result<(), PrinterError<'i>> {
    match self {
      PropertyDeclaration::Unparsed(unparsed) => {
        unparsed.property_id.to_css(dest)?;
        dest.write_str(": ")?;
        unparsed.to_css(dest)?;
        dest.write_char(';')?;
        dest.write_newline()?;
      }
    }
    Ok(())
  }
}

#[derive(Debug)]
struct LessVariableReference<'i> {
  property_id: PropertyId<'i>,
  variable: VariableReference<'i>,
}

impl<'i> LessVariableReference<'i> {
  fn parse<'t>(
    allocator: &'i less_allocator::Allocator,
    property_id: &PropertyId<'i>,
    input: &mut cssparser::Parser<'i, 't>,
  ) -> Result<Self, cssparser::ParseError<'i, crate::error::ParserError<'i>>> {
    Ok(Self {
      property_id: property_id.clone(),
      variable: VariableReference::parse(allocator, input)?,
    })
  }
}

#[derive(Debug, Clone)]
pub struct PropertyId<'i>(CowRcStr<'i>);

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

impl<'i> From<CowRcStr<'i>> for PropertyId<'i> {
  fn from(name: CowRcStr<'i>) -> Self {
    PropertyId(name)
  }
}

impl<'i> PropertyDeclaration<'i> {
  pub fn parse<'t>(
    allocator: &'i less_allocator::Allocator,
    property_id: &PropertyId<'i>,
    input: &mut cssparser::Parser<'i, 't>,
  ) -> Result<Self, cssparser::ParseError<'i, crate::error::ParserError<'i>>> {
    Ok(Self::Unparsed(Box(allocator.alloc(
      UnparsedProperty::parse(allocator, property_id.clone(), input)?,
    ))))
  }
}