Implement lexing, basic parsing, and pretty printing

This commit is contained in:
ConnorSkees 2020-01-04 22:55:04 -05:00
parent 87a4d7084f
commit f4278a29b7
8 changed files with 2329 additions and 0 deletions

1
src/color.rs Normal file
View File

@ -0,0 +1 @@

1052
src/common.rs Normal file

File diff suppressed because it is too large Load Diff

1
src/error.rs Normal file
View File

@ -0,0 +1 @@

159
src/format.rs Normal file
View File

@ -0,0 +1,159 @@
use std::io::{self, Write};
use crate::{RuleSet, Stmt, Style, StyleSheet};
pub(crate) struct PrettyPrinter<W: Write> {
buf: W,
scope: usize,
}
impl<W: Write> PrettyPrinter<W> {
pub(crate) fn new(buf: W) -> Self {
PrettyPrinter { buf, scope: 0 }
}
fn pretty_print_stmt(&mut self, stmt: &Stmt) -> io::Result<()> {
let padding = vec![' '; self.scope * 2].iter().collect::<String>();
match stmt {
Stmt::RuleSet(RuleSet {
selector, rules, ..
}) => {
writeln!(self.buf, "{}{} {{", padding, selector)?;
self.scope += 1;
for rule in rules {
self.pretty_print_stmt(rule)?;
}
writeln!(self.buf, "{}}}", padding)?;
self.scope -= 1;
}
Stmt::Style(Style { property, value }) => {
writeln!(
self.buf,
"{}{}: {};",
padding,
property,
value
.iter()
.map(ToString::to_string)
.collect::<Vec<String>>()
.join(" ")
)?;
}
}
Ok(())
}
pub(crate) fn pretty_print(&mut self, s: &StyleSheet) -> io::Result<()> {
for rule in &s.rules {
self.pretty_print_stmt(rule)?;
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::StyleSheet;
macro_rules! test {
($func:ident, $input:literal) => {
#[test]
fn $func() {
let mut buf = Vec::new();
StyleSheet::new($input)
.pretty_print(&mut buf)
.expect(concat!("failed to pretty print on ", $input));
assert_eq!(
String::from($input),
String::from_utf8(buf).expect("produced invalid utf8")
);
}
};
($func:ident, $input:literal, $output:literal) => {
#[test]
fn $func() {
let mut buf = Vec::new();
StyleSheet::new($input)
.pretty_print(&mut buf)
.expect(concat!("failed to pretty print on ", $input));
assert_eq!(
String::from($output),
String::from_utf8(buf).expect("produced invalid utf8")
);
}
};
}
test!(empty, "");
test!(basic_nesting, "a {\n b {\n }\n}\n");
test!(ident_with_num, "el1 {\n}\n");
test!(selector_element, "a {\n}\n");
test!(selector_id, "#id {\n}\n");
test!(selector_class, ".class {\n}\n");
test!(selector_el_descendant, "a a {\n}\n");
test!(selector_universal, "* {\n}\n");
test!(selector_el_class_and, "a.class {\n}\n");
test!(selector_el_id_and, "a#class {\n}\n");
test!(selector_el_class_descendant, "a .class {\n}\n");
test!(selector_el_id_descendant, "a #class {\n}\n");
test!(selector_el_universal_descendant, "a * {\n}\n");
test!(selector_universal_el_descendant, "* a {\n}\n");
test!(selector_attribute_any, "[attr] {\n}\n");
test!(selector_attribute_equals, "[attr=val] {\n}\n");
test!(selector_attribute_in, "[attr~=val] {\n}\n");
test!(
selector_attribute_begins_hyphen_or_exact,
"[attr|=val] {\n}\n"
);
test!(selector_attribute_starts_with, "[attr^=val] {\n}\n");
test!(selector_attribute_ends_with, "[attr$=val] {\n}\n");
test!(selector_attribute_contains, "[attr*=val] {\n}\n");
test!(selector_el_attribute_and, "a[attr] {\n}\n");
test!(selector_el_attribute_descendant, "a [attr] {\n}\n");
test!(selector_el_mul_el, "a, b {\n}\n");
test!(selector_el_immediate_child_el, "a > b {\n}\n");
test!(selector_el_following_el, "a + b {\n}\n");
test!(selector_el_preceding_el, "a ~ b {\n}\n");
test!(selector_pseudo, ":pseudo {\n}\n");
test!(selector_el_pseudo_and, "a:pseudo {\n}\n");
test!(selector_el_pseudo_descendant, "a :pseudo {\n}\n");
test!(basic_style, "a {\n color: red;\n}\n");
test!(two_styles, "a {\n color: red;\n color: blue;\n}\n");
test!(hyphenated_style_property, "a {\n font-family: Arial;\n}\n");
test!(hyphenated_style_value, "a {\n color: Open-Sans;\n}\n");
test!(
space_separated_style_value,
"a {\n border: solid red;\n}\n"
);
test!(
single_quoted_style_value,
"a {\n font: 'Open-Sans';\n}\n",
"a {\n font: Open-Sans;\n}\n"
);
test!(
double_quoted_style_value,
"a {\n font: \"Open-Sans\";\n}\n",
"a {\n font: Open-Sans;\n}\n"
);
// test!(comma_style_value, "a {\n font: Open-Sans, sans-serif;\n}\n");
test!(
nested_style_in_parent,
"a {\n color: red;\n b {\n }\n}\n"
);
test!(
nested_style_in_child,
"a {\n b {\n color: red;\n }\n}\n"
);
test!(
nested_style_in_both,
"a {\n color: red;\n b {\n color: red;\n }\n}\n"
);
test!(unit_none, "a {\n height: 1;\n}\n");
test!(unit_not_attached, "a {\n height: 1 px;\n}\n");
test!(unit_px, "a {\n height: 1px;\n}\n");
test!(unit_em, "a {\n height: 1em;\n}\n");
test!(unit_rem, "a {\n height: 1rem;\n}\n");
test!(unit_percent, "a {\n height: 1%;\n}\n");
}

305
src/lexer.rs Normal file
View File

@ -0,0 +1,305 @@
use std::convert::TryFrom;
use std::iter::Peekable;
use std::str::Chars;
use crate::common::{Keyword, Pos, Symbol};
use crate::selector::{Attribute, AttributeKind, Selector};
use crate::units::Unit;
use crate::{Token, TokenKind, Whitespace};
#[derive(Debug, Clone)]
pub struct Lexer<'a> {
tokens: Vec<Token>,
buf: Peekable<Chars<'a>>,
pos: Pos,
}
impl<'a> Iterator for Lexer<'a> {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
macro_rules! symbol {
($self:ident, $symbol:ident) => {{
$self.buf.next();
$self.pos.next_char();
TokenKind::Symbol(Symbol::$symbol)
}};
}
macro_rules! whitespace {
($self:ident, $whitespace:ident) => {{
$self.buf.next();
$self.pos.next_char();
TokenKind::Whitespace(Whitespace::$whitespace)
}};
}
let kind: TokenKind = match self.buf.peek().unwrap_or(&'\0') {
'a'..='z' | 'A'..='Z' => self.lex_ident(),
'@' => self.lex_at_rule(),
'0'..='9' => self.lex_num(),
'$' => self.lex_variable(),
':' => symbol!(self, Colon),
',' => symbol!(self, Comma),
'.' => symbol!(self, Period),
';' => symbol!(self, SemiColon),
'+' => symbol!(self, Plus),
'~' => symbol!(self, Tilde),
'\'' => symbol!(self, SingleQuote),
'"' => symbol!(self, DoubleQuote),
' ' => whitespace!(self, Space),
'\t' => whitespace!(self, Tab),
'\n' => whitespace!(self, Newline),
'\r' => whitespace!(self, CarriageReturn),
'#' => symbol!(self, Hash),
'{' => symbol!(self, OpenBrace),
'*' => symbol!(self, Mul),
'}' => symbol!(self, CloseBrace),
'%' => {
self.buf.next();
self.pos.next_char();
TokenKind::Unit(Unit::Percent)
}
'[' => {
self.buf.next();
self.pos.next_char();
self.lex_attr()
}
'<' => symbol!(self, Lt),
'>' => symbol!(self, Gt),
'\0' => return None,
_ => todo!("unknown char"),
};
self.pos.next_char();
Some(Token {
kind,
pos: self.pos,
})
}
}
fn is_whitespace(c: char) -> bool {
c == ' ' || c == '\n' || c == '\r'
}
impl<'a> Lexer<'a> {
pub fn new(buf: &'a str) -> Lexer<'a> {
Lexer {
tokens: Vec::with_capacity(buf.len()),
buf: buf.chars().peekable(),
pos: Pos::new(),
}
}
fn devour_whitespace(&mut self) {
while let Some(c) = self.buf.peek() {
if !is_whitespace(*c) {
break;
}
}
}
fn lex_at_rule(&mut self) -> TokenKind {
let mut string = String::with_capacity(99);
while let Some(c) = self.buf.peek() {
if !c.is_alphabetic() && c != &'-' {
break;
}
let tok = self
.buf
.next()
.expect("this is impossible because we have already peeked");
self.pos.next_char();
string.push(tok);
}
if let Ok(kw) = Unit::try_from(string.as_ref()) {
TokenKind::Unit(kw)
} else {
panic!("expected ident after `@`")
}
}
fn lex_num(&mut self) -> TokenKind {
let mut string = String::with_capacity(99);
while let Some(c) = self.buf.peek() {
if !c.is_numeric() && c != &'.' {
break;
}
let tok = self
.buf
.next()
.expect("this is impossible because we have already peeked");
self.pos.next_char();
string.push(tok);
}
TokenKind::Number(string)
}
fn lex_hash(&mut self) -> TokenKind {
todo!()
}
fn lex_attr(&mut self) -> TokenKind {
let mut attr = String::with_capacity(99);
while let Some(c) = self.buf.peek() {
if !c.is_alphabetic() && c != &'-' {
break;
}
let tok = self
.buf
.next()
.expect("this is impossible because we have already peeked");
self.pos.next_char();
attr.push(tok);
}
self.devour_whitespace();
let kind = match self
.buf
.next()
.expect("todo! expected kind (should be error)")
{
']' => {
return TokenKind::Selector(Selector::Attribute(Attribute {
kind: AttributeKind::Any,
attr,
value: String::new(),
case_sensitive: true,
}))
}
'i' => {
self.devour_whitespace();
assert!(self.buf.next() == Some(']'));
return TokenKind::Selector(Selector::Attribute(Attribute {
kind: AttributeKind::Any,
attr,
value: String::new(),
case_sensitive: false,
}));
}
'=' => AttributeKind::Equals,
'~' => AttributeKind::InList,
'|' => AttributeKind::BeginsWithHyphenOrExact,
'^' => AttributeKind::StartsWith,
'$' => AttributeKind::EndsWith,
'*' => AttributeKind::Contains,
_ => todo!("expected kind (should be error)"),
};
if kind != AttributeKind::Equals {
assert!(self.buf.next() == Some('='));
}
self.devour_whitespace();
match self
.buf
.peek()
.expect("todo! expected either value or quote")
{
'\'' | '"' => {
self.buf.next();
}
_ => {}
}
let mut value = String::with_capacity(99);
let mut case_sensitive = true;
while let Some(c) = self.buf.peek() {
if !c.is_alphabetic() && c != &'-' {
break;
}
if c == &'i' {
let tok = self
.buf
.next()
.expect("this is impossible because we have already peeked");
self.pos.next_char();
self.devour_whitespace();
match self.buf.next() {
Some(']') => case_sensitive = false,
Some(val) => {
self.pos.next_char();
value.push(tok);
value.push(val);
}
None => todo!("expected something to come after "),
}
continue;
}
let tok = self
.buf
.next()
.expect("this is impossible because we have already peeked");
self.pos.next_char();
value.push(tok);
}
match self
.buf
.peek()
.expect("todo! expected either value or quote")
{
'\'' | '"' => {
self.buf.next();
}
_ => {}
}
self.devour_whitespace();
assert!(self.buf.next() == Some(']'));
TokenKind::Selector(Selector::Attribute(Attribute {
kind,
attr,
value,
case_sensitive,
}))
}
fn lex_variable(&mut self) -> TokenKind {
let mut string = String::with_capacity(99);
while let Some(c) = self.buf.peek() {
if !c.is_alphabetic() && c != &'-' {
break;
}
let tok = self
.buf
.next()
.expect("this is impossible because we have already peeked");
self.pos.next_char();
string.push(tok);
}
TokenKind::Variable(string)
}
fn lex_ident(&mut self) -> TokenKind {
let mut string = String::with_capacity(99);
while let Some(c) = self.buf.peek() {
// we know that the first char is alphabetic from peeking
if !c.is_alphanumeric() && c != &'-' {
break;
}
let tok = self
.buf
.next()
.expect("this is impossible because we have already peeked");
self.pos.next_char();
string.push(tok);
}
if let Ok(kw) = Keyword::try_from(string.as_ref()) {
return TokenKind::Keyword(kw);
}
if let Ok(kw) = Unit::try_from(string.as_ref()) {
return TokenKind::Unit(kw);
}
TokenKind::Ident(string)
}
}

303
src/main.rs Normal file
View File

@ -0,0 +1,303 @@
#![warn(
clippy::all,
clippy::restriction,
clippy::pedantic,
clippy::nursery,
// clippy::cargo
)]
#![deny(missing_debug_implementations)]
#![allow(
dead_code,
clippy::pub_enum_variant_names,
clippy::implicit_return,
clippy::use_self,
clippy::missing_docs_in_private_items,
clippy::todo,
clippy::dbg_macro,
clippy::unreachable,
clippy::wildcard_enum_match_arm,
clippy::option_expect_used,
clippy::panic,
clippy::unused_self,
clippy::too_many_lines,
clippy::integer_arithmetic,
clippy::missing_errors_doc
)]
use std::collections::HashMap;
use std::fs;
use std::io;
use std::iter::Iterator;
use std::{
fmt::{self, Display},
iter::Peekable,
};
use crate::common::{Keyword, Pos, Symbol, Whitespace};
use crate::format::PrettyPrinter;
use crate::lexer::Lexer;
use crate::selector::Selector;
use crate::units::Unit;
mod color;
mod common;
mod error;
mod format;
mod lexer;
mod selector;
mod units;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TokenKind {
Ident(String),
Symbol(Symbol),
Function(String, Vec<String>),
AtRule(String),
Keyword(Keyword),
Number(String),
Unit(Unit),
Whitespace(Whitespace),
Variable(String),
Selector(Selector),
Style(Vec<Token>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum StyleToken {
Ident(String),
Function(String, Vec<String>),
Keyword(Keyword),
Symbol(Symbol),
Dimension(String, Unit),
}
impl Display for StyleToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StyleToken::Ident(s) => write!(f, "{}", s),
StyleToken::Symbol(s) => write!(f, "{}", s),
StyleToken::Function(name, args) => write!(f, "{}({})", name, args.join(", ")),
StyleToken::Keyword(kw) => write!(f, "{}", kw),
StyleToken::Dimension(val, unit) => write!(f, "{}{}", val, unit),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Token {
pos: Pos,
pub kind: TokenKind,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct StyleSheet {
rules: Vec<Stmt>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Style {
property: String,
value: Vec<StyleToken>,
}
impl Display for Style {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}: {};",
self.property,
self.value
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<String>>()
.join(" ")
)
}
}
impl Style {
fn from_tokens(raw: &[Token]) -> Result<Style, ()> {
let mut iter = raw.iter();
let property: String;
loop {
if let Some(tok) = iter.next() {
match tok.kind {
TokenKind::Whitespace(_) => continue,
TokenKind::Ident(ref s) => {
property = s.clone();
break;
}
_ => todo!(),
};
} else {
return Err(());
}
}
while let Some(tok) = iter.next() {
match tok.kind {
TokenKind::Whitespace(_) => continue,
TokenKind::Symbol(Symbol::Colon) => break,
_ => todo!("found tokens before style value"),
}
}
let mut value = Vec::new();
while let Some(tok) = iter.next() {
match tok.kind {
TokenKind::Whitespace(_)
| TokenKind::Symbol(Symbol::SingleQuote)
| TokenKind::Symbol(Symbol::DoubleQuote) => continue,
TokenKind::Ident(ref s) => value.push(StyleToken::Ident(s.clone())),
TokenKind::Symbol(s) => value.push(StyleToken::Symbol(s)),
TokenKind::Unit(u) => value.push(StyleToken::Ident(u.into())),
TokenKind::Number(ref num) => {
if let Some(t) = iter.next() {
match &t.kind {
&TokenKind::Unit(unit) => {
value.push(StyleToken::Dimension(num.clone(), unit))
}
TokenKind::Ident(ref s) => {
value.push(StyleToken::Dimension(num.clone(), Unit::None));
value.push(StyleToken::Ident(s.clone()));
}
TokenKind::Whitespace(_) => {
value.push(StyleToken::Dimension(num.clone(), Unit::None))
}
_ => todo!(),
}
} else {
value.push(StyleToken::Dimension(num.clone(), Unit::None))
}
}
_ => todo!("style value not ident or dimension"),
}
}
Ok(Style { property, value })
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Stmt {
Style(Style),
RuleSet(RuleSet),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RuleSet {
selector: Selector,
rules: Vec<Stmt>,
// potential optimization: we don't *need* to own the selector
super_selector: Option<Selector>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum Expr {
Style(Style),
Selector(Selector),
}
impl StyleSheet {
#[must_use]
pub fn new(input: &str) -> StyleSheet {
StyleSheetParser {
variables: HashMap::new(),
lexer: Lexer::new(input).peekable(),
rules: Vec::new(),
}
.parse_toplevel()
}
pub fn pretty_print<W: std::io::Write>(&self, buf: W) -> io::Result<()> {
PrettyPrinter::new(buf).pretty_print(self)
}
}
#[derive(Debug, Clone)]
struct StyleSheetParser<'a> {
variables: HashMap<String, String>,
lexer: Peekable<Lexer<'a>>,
rules: Vec<Stmt>,
}
impl<'a> StyleSheetParser<'a> {
fn parse_toplevel(&'a mut self) -> StyleSheet {
let mut rules = Vec::new();
while let Some(tok) = self.lexer.peek() {
match tok.kind {
TokenKind::Ident(_)
| TokenKind::Selector(_)
| TokenKind::Symbol(Symbol::Hash)
| TokenKind::Symbol(Symbol::Colon)
| TokenKind::Symbol(Symbol::Mul)
| TokenKind::Symbol(Symbol::Period) => rules.extend(self.eat_rules(&None)),
TokenKind::Whitespace(_) | TokenKind::Symbol(_) => {
self.lexer.next();
continue;
}
_ => todo!("unexpected toplevel token"),
};
}
StyleSheet { rules }
}
fn eat_rules(&mut self, super_selector: &Option<Selector>) -> Vec<Stmt> {
let mut stmts = Vec::new();
while let Ok(tok) = self.eat_expr() {
match tok {
Expr::Style(s) => stmts.push(Stmt::Style(s)),
Expr::Selector(s) => {
let rules = self.eat_rules(&Some(s.clone()));
stmts.push(Stmt::RuleSet(RuleSet {
super_selector: super_selector.clone(),
selector: s,
rules,
}));
}
}
}
stmts
}
fn eat_expr(&mut self) -> Result<Expr, ()> {
let mut values = Vec::with_capacity(5);
while let Some(tok) = self.lexer.next() {
match tok.kind {
TokenKind::Symbol(Symbol::SemiColon) | TokenKind::Symbol(Symbol::CloseBrace) => {
self.devour_whitespace();
return Ok(Expr::Style(Style::from_tokens(&values)?));
}
TokenKind::Symbol(Symbol::OpenBrace) => {
self.devour_whitespace();
return Ok(Expr::Selector(Selector::from_tokens(
values.iter().peekable(),
)));
}
_ => values.push(tok.clone()),
};
}
Err(())
}
fn devour_whitespace(&mut self) {
while let Some(tok) = self.lexer.peek() {
match tok.kind {
TokenKind::Whitespace(_) => {
self.lexer.next();
}
_ => break,
}
}
}
}
fn main() -> io::Result<()> {
let input = fs::read_to_string("input.scss")?;
let mut stdout = std::io::stdout();
let s = StyleSheet::new(&input);
// dbg!(s);
s.pretty_print(&mut stdout)?;
// println!("{}", s);
// drop(input);
Ok(())
}

247
src/selector.rs Normal file
View File

@ -0,0 +1,247 @@
use crate::common::Symbol;
use crate::{Token, TokenKind};
use std::iter::Peekable;
use std::{
fmt::{self, Display},
slice::Iter,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Selector {
/// An element selector: `button`
Element(String),
/// An id selector: `#footer`
Id(String),
/// A single class selector: `.button-active`
Class(String),
/// A universal selector: `*`
Universal,
/// A simple child selector: `ul li`
Descendant(Box<Selector>, Box<Selector>),
/// And selector: `button.active`
And(Box<Selector>, Box<Selector>),
/// Multiple unrelated selectors: `button, .active`
Multiple(Box<Selector>, Box<Selector>),
/// Select all immediate children: `ul > li`
ImmediateChild(Box<Selector>, Box<Selector>),
/// Select all elements immediately following: `div + p`
Following(Box<Selector>, Box<Selector>),
/// Select elements preceeded by: `p ~ ul`
Preceding(Box<Selector>, Box<Selector>),
/// Select elements with attribute: `html[lang|=en]`
Attribute(Attribute),
/// Pseudo selector: `:hover`
Pseudo(String),
}
impl Display for Selector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Selector::Element(s) => write!(f, "{}", s),
Selector::Id(s) => write!(f, "#{}", s),
Selector::Class(s) => write!(f, ".{}", s),
Selector::Universal => write!(f, "*"),
Selector::Descendant(lhs, rhs) => write!(f, "{} {}", lhs, rhs),
Selector::And(lhs, rhs) => write!(f, "{}{}", lhs, rhs),
Selector::Multiple(lhs, rhs) => write!(f, "{}, {}", lhs, rhs),
Selector::ImmediateChild(lhs, rhs) => write!(f, "{} > {}", lhs, rhs),
Selector::Following(lhs, rhs) => write!(f, "{} + {}", lhs, rhs),
Selector::Preceding(lhs, rhs) => write!(f, "{} ~ {}", lhs, rhs),
Selector::Attribute(attr) => write!(f, "{}", attr),
Selector::Pseudo(s) => write!(f, ":{}", s),
}
}
}
struct SelectorParser<'a> {
tokens: Peekable<Iter<'a, Token>>,
}
impl<'a> SelectorParser<'a> {
const fn new(tokens: Peekable<Iter<'a, Token>>) -> SelectorParser<'a> {
SelectorParser { tokens }
}
fn all_selectors(&mut self) -> Selector {
self.devour_whitespace();
let left = self
.consume_selector()
.expect("expected left handed selector");
let whitespace: bool = self.devour_whitespace();
match self.tokens.peek() {
Some(tok) => match tok.kind {
TokenKind::Ident(_) => {
return Selector::Descendant(Box::new(left), Box::new(self.all_selectors()))
}
TokenKind::Symbol(Symbol::Plus) => {
self.tokens.next();
self.devour_whitespace();
return Selector::Following(Box::new(left), Box::new(self.all_selectors()));
}
TokenKind::Symbol(Symbol::Tilde) => {
self.tokens.next();
self.devour_whitespace();
return Selector::Preceding(Box::new(left), Box::new(self.all_selectors()));
}
TokenKind::Symbol(Symbol::Comma) => {
self.tokens.next();
self.devour_whitespace();
return Selector::Multiple(Box::new(left), Box::new(self.all_selectors()));
}
TokenKind::Symbol(Symbol::Gt) => {
self.tokens.next();
self.devour_whitespace();
return Selector::ImmediateChild(
Box::new(left),
Box::new(self.all_selectors()),
);
}
TokenKind::Symbol(Symbol::Colon)
| TokenKind::Symbol(Symbol::Period)
| TokenKind::Symbol(Symbol::Mul)
| TokenKind::Selector(_)
| TokenKind::Symbol(Symbol::Hash) => {
if whitespace {
return Selector::Descendant(
Box::new(left),
Box::new(self.all_selectors()),
);
} else {
return Selector::And(Box::new(left), Box::new(self.all_selectors()));
}
}
TokenKind::Symbol(Symbol::Lt) => {}
_ => todo!(),
},
None => return left,
}
todo!()
}
fn devour_whitespace(&mut self) -> bool {
let mut found_whitespace = false;
while let Some(tok) = self.tokens.peek() {
match tok.kind {
TokenKind::Whitespace(_) => {
self.tokens.next();
found_whitespace = true;
}
_ => break,
}
}
found_whitespace
}
fn consume_selector(&mut self) -> Option<Selector> {
if let Some(tok) = self.tokens.next() {
let selector = match tok.kind {
TokenKind::Symbol(Symbol::Period) => match self
.tokens
.next()
.expect("expected ident after `.`")
.clone()
.kind
{
TokenKind::Ident(tok) => Selector::Class(tok),
_ => todo!("there should normally be an ident after `.`"),
},
TokenKind::Symbol(Symbol::Mul) => Selector::Universal,
TokenKind::Symbol(Symbol::Hash) => match self
.tokens
.next()
.expect("expected ident after `#`")
.clone()
.kind
{
TokenKind::Ident(tok) => Selector::Id(tok),
_ => todo!("there should normally be an ident after `#`"),
},
TokenKind::Symbol(Symbol::Colon) => {
match self
.tokens
.next()
.expect("expected ident after `:`")
.clone()
.kind
{
TokenKind::Ident(tok) => Selector::Pseudo(tok),
_ => todo!("there should normally be an ident after `:`"),
}
}
TokenKind::Ident(ref tok) => Selector::Element(tok.clone()),
TokenKind::Selector(ref sel) => sel.clone(),
_ => todo!(),
};
Some(selector)
} else {
None
}
}
}
impl Selector {
pub fn from_tokens(tokens: Peekable<Iter<Token>>) -> Selector {
SelectorParser::new(tokens).all_selectors()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Attribute {
pub attr: String,
pub value: String,
pub case_sensitive: bool,
pub kind: AttributeKind,
}
impl Display for Attribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.case_sensitive {
match self.kind {
AttributeKind::Any => write!(f, "[{}]", self.attr),
AttributeKind::Equals => write!(f, "[{}={}]", self.attr, self.value),
AttributeKind::InList => write!(f, "[{}~={}]", self.attr, self.value),
AttributeKind::BeginsWithHyphenOrExact => {
write!(f, "[{}|={}]", self.attr, self.value)
}
AttributeKind::StartsWith => write!(f, "[{}^={}]", self.attr, self.value),
AttributeKind::EndsWith => write!(f, "[{}$={}]", self.attr, self.value),
AttributeKind::Contains => write!(f, "[{}*={}]", self.attr, self.value),
}
} else {
match self.kind {
AttributeKind::Any => write!(f, "[{} i]", self.attr),
AttributeKind::Equals => write!(f, "[{}={} i]", self.attr, self.value),
AttributeKind::InList => write!(f, "[{}~={} i]", self.attr, self.value),
AttributeKind::BeginsWithHyphenOrExact => {
write!(f, "[{}|={} i]", self.attr, self.value)
}
AttributeKind::StartsWith => write!(f, "[{}^={} i]", self.attr, self.value),
AttributeKind::EndsWith => write!(f, "[{}$={} i]", self.attr, self.value),
AttributeKind::Contains => write!(f, "[{}*={} i]", self.attr, self.value),
}
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum AttributeKind {
/// [attr]
/// Represents elements with an attribute name of `attr`
Any,
/// [attr=value]
/// Represents elements with an attribute name of `attr` whose value is exactly `value`
Equals,
/// [attr~=value]
/// Represents elements with an attribute name of `attr` whose value is a whitespace-separated list of words, one of which is exactly `value`
InList,
/// [attr|=value]
/// Represents elements with an attribute name of `attr` whose value can be exactly value or can begin with `value` immediately followed by a hyphen (`-`)
BeginsWithHyphenOrExact,
/// [attr^=value]
StartsWith,
/// [attr$=value]
EndsWith,
/// [attr*=value]
/// Represents elements with an attribute name of `attr` whose value contains at least one occurrence of `value` within the string
Contains,
}

261
src/units.rs Normal file
View File

@ -0,0 +1,261 @@
use std::convert::TryFrom;
use std::fmt;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Unit {
// Absolute units
/// Pixels
Px,
/// Millimeters
Mm,
/// Inches
In,
/// Centimeters
Cm,
/// Quarter-millimeters
Q,
/// Points
Pt,
/// Picas
Pc,
// Font relative units
/// Font size of the parent element
Em,
/// Font size of the root element
Rem,
/// Line height of the element
Lh,
Percent,
/// x-height of the element's font
Ex,
/// The advance measure (width) of the glyph "0" of the element's font
Ch,
/// Represents the "cap height" (nominal height of capital letters) of the elements font
Cap,
/// Equal to the used advance measure of the "水" (CJK water ideograph, U+6C34) glyph found in the font used to render it
Ic,
/// Equal to the computed value of the line-height property on the root element (typically <html>), converted to an absolute length
Rlh,
// Viewport relative units
/// 1% of the viewport's width
Vw,
/// 1% of the viewport's height
Vh,
/// 1% of the viewport's smaller dimension
Vmin,
/// 1% of the viewport's larger dimension
Vmax,
/// Equal to 1% of the size of the initial containing block, in the direction of the root elements inline axis
Vi,
/// Equal to 1% of the size of the initial containing block, in the direction of the root elements block axis
Vb,
// Angle units
/// Represents an angle in degrees. One full circle is 360deg
Deg,
/// Represents an angle in gradians. One full circle is 400grad
Grad,
/// Represents an angle in radians. One full circle is 2π radians which approximates to 6.2832rad
Rad,
/// Represents an angle in a number of turns. One full circle is 1turn
Turn,
// Time units
/// Represents a time in seconds
S,
/// Represents a time in milliseconds
Ms,
// Frequency units
/// Represents a frequency in hertz
Hz,
/// Represents a frequency in kilohertz
Khz,
// Resolution units
/// Represents the number of dots per inch
Dpi,
/// Represents the number of dots per centimeter
Dpcm,
/// Represents the number of dots per px unit
Dppx,
/// Alias for dppx
X,
/// Represents a fraction of the available space in the grid container
Fr,
/// Unspecified unit
None,
}
impl TryFrom<&str> for Unit {
type Error = &'static str;
fn try_from(unit: &str) -> Result<Self, Self::Error> {
match unit.to_ascii_lowercase().as_bytes() {
b"px" => Ok(Unit::Px),
b"mm" => Ok(Unit::Mm),
b"in" => Ok(Unit::In),
b"cm" => Ok(Unit::Cm),
b"Q" => Ok(Unit::Q),
b"pt" => Ok(Unit::Pt),
b"pc" => Ok(Unit::Pc),
b"em" => Ok(Unit::Em),
b"rem" => Ok(Unit::Rem),
b"lh" => Ok(Unit::Lh),
b"%" => Ok(Unit::Percent),
b"ex" => Ok(Unit::Ex),
b"ch" => Ok(Unit::Ch),
b"cap" => Ok(Unit::Cap),
b"ic" => Ok(Unit::Ic),
b"rlh" => Ok(Unit::Rlh),
b"vw" => Ok(Unit::Vw),
b"vh" => Ok(Unit::Vh),
b"vmin" => Ok(Unit::Vmin),
b"vmax" => Ok(Unit::Vmax),
b"vi" => Ok(Unit::Vi),
b"vb" => Ok(Unit::Vb),
b"deg" => Ok(Unit::Deg),
b"grad" => Ok(Unit::Grad),
b"rad" => Ok(Unit::Rad),
b"turn" => Ok(Unit::Turn),
b"s" => Ok(Unit::S),
b"ms" => Ok(Unit::Ms),
b"Hz" => Ok(Unit::Hz),
b"kHz" => Ok(Unit::Khz),
b"dpi" => Ok(Unit::Dpi),
b"dpcm" => Ok(Unit::Dpcm),
b"dppx" | b"x" => Ok(Unit::Dppx),
b"fr" => Ok(Unit::Fr),
_ => Err("invalid unit"),
}
}
}
impl Into<String> for Unit {
fn into(self) -> String {
match self {
Unit::Px => "px",
Unit::Mm => "mm",
Unit::In => "in",
Unit::Cm => "cm",
Unit::Q => "Q",
Unit::Pt => "pt",
Unit::Pc => "pc",
Unit::Em => "em",
Unit::Rem => "rem",
Unit::Lh => "lh",
Unit::Percent => "%",
Unit::Ex => "ex",
Unit::Ch => "ch",
Unit::Cap => "cap",
Unit::Ic => "ic",
Unit::Rlh => "rlh",
Unit::Vw => "vw",
Unit::Vh => "vh",
Unit::Vmin => "vmin",
Unit::Vmax => "vmax",
Unit::Vi => "vi",
Unit::Vb => "vb",
Unit::Deg => "deg",
Unit::Grad => "grad",
Unit::Rad => "rad",
Unit::Turn => "turn",
Unit::S => "s",
Unit::Ms => "ms",
Unit::Hz => "Hz",
Unit::Khz => "kHz",
Unit::Dpi => "dpi",
Unit::Dpcm => "dpcm",
Unit::Dppx | Unit::X => "dppx",
Unit::Fr => "fr",
Unit::None => "",
}
.into()
}
}
impl Into<&'static str> for Unit {
fn into(self) -> &'static str {
match self {
Unit::Px => "px",
Unit::Mm => "mm",
Unit::In => "in",
Unit::Cm => "cm",
Unit::Q => "Q",
Unit::Pt => "pt",
Unit::Pc => "pc",
Unit::Em => "em",
Unit::Rem => "rem",
Unit::Lh => "lh",
Unit::Percent => "%",
Unit::Ex => "ex",
Unit::Ch => "ch",
Unit::Cap => "cap",
Unit::Ic => "ic",
Unit::Rlh => "rlh",
Unit::Vw => "vw",
Unit::Vh => "vh",
Unit::Vmin => "vmin",
Unit::Vmax => "vmax",
Unit::Vi => "vi",
Unit::Vb => "vb",
Unit::Deg => "deg",
Unit::Grad => "grad",
Unit::Rad => "rad",
Unit::Turn => "turn",
Unit::S => "s",
Unit::Ms => "ms",
Unit::Hz => "Hz",
Unit::Khz => "kHz",
Unit::Dpi => "dpi",
Unit::Dpcm => "dpcm",
Unit::Dppx | Unit::X => "dppx",
Unit::Fr => "fr",
Unit::None => "",
}
}
}
impl fmt::Display for Unit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Unit::Px => write!(f, "px"),
Unit::Mm => write!(f, "mm"),
Unit::In => write!(f, "in"),
Unit::Cm => write!(f, "cm"),
Unit::Q => write!(f, "Q"),
Unit::Pt => write!(f, "pt"),
Unit::Pc => write!(f, "pc"),
Unit::Em => write!(f, "em"),
Unit::Rem => write!(f, "rem"),
Unit::Lh => write!(f, "lh"),
Unit::Percent => write!(f, "%"),
Unit::Ex => write!(f, "ex"),
Unit::Ch => write!(f, "ch"),
Unit::Cap => write!(f, "cap"),
Unit::Ic => write!(f, "ic"),
Unit::Rlh => write!(f, "rlh"),
Unit::Vw => write!(f, "vw"),
Unit::Vh => write!(f, "vh"),
Unit::Vmin => write!(f, "vmin"),
Unit::Vmax => write!(f, "vmax"),
Unit::Vi => write!(f, "vi"),
Unit::Vb => write!(f, "vb"),
Unit::Deg => write!(f, "deg"),
Unit::Grad => write!(f, "grad"),
Unit::Rad => write!(f, "rad"),
Unit::Turn => write!(f, "turn"),
Unit::S => write!(f, "s"),
Unit::Ms => write!(f, "ms"),
Unit::Hz => write!(f, "Hz"),
Unit::Khz => write!(f, "kHz"),
Unit::Dpi => write!(f, "dpi"),
Unit::Dpcm => write!(f, "dpcm"),
Unit::Dppx | Unit::X => write!(f, "dppx"),
Unit::Fr => write!(f, "fr"),
Unit::None => write!(f, ""),
}
}
}