diff --git a/src/common.rs b/src/common.rs index 05f60d1..c3b08a4 100644 --- a/src/common.rs +++ b/src/common.rs @@ -201,6 +201,87 @@ pub enum AtRule { CounterStyle, } +impl TryFrom<&str> for AtRule { + type Error = &'static str; + + fn try_from(c: &str) -> Result { + match c { + "use" => Ok(AtRule::Use), + "forward" => Ok(AtRule::Forward), + "import" => Ok(AtRule::Import), + "mixin" => Ok(AtRule::Mixin), + "include" => Ok(AtRule::Include), + "function" => Ok(AtRule::Function), + "extend" => Ok(AtRule::Extend), + "atroot" => Ok(AtRule::AtRoot), + "error" => Ok(AtRule::Error), + "warn" => Ok(AtRule::Warn), + "debug" => Ok(AtRule::Debug), + "if" => Ok(AtRule::If), + "each" => Ok(AtRule::Each), + "for" => Ok(AtRule::For), + "while" => Ok(AtRule::While), + "charset" => Ok(AtRule::Charset), + "namespace" => Ok(AtRule::Namespace), + "media" => Ok(AtRule::Media), + "supports" => Ok(AtRule::Supports), + "page" => Ok(AtRule::Page), + "fontface" => Ok(AtRule::FontFace), + "keyframes" => Ok(AtRule::Keyframes), + "fontfeaturevalues" => Ok(AtRule::FontFeatureValues), + "swash" => Ok(AtRule::Swash), + "ornaments" => Ok(AtRule::Ornaments), + "annotation" => Ok(AtRule::Annotation), + "stylistic" => Ok(AtRule::Stylistic), + "styleset" => Ok(AtRule::Styleset), + "charactervariant" => Ok(AtRule::CharacterVariant), + "viewport" => Ok(AtRule::Viewport), + "document" => Ok(AtRule::Document), + "counterstyle" => Ok(AtRule::CounterStyle), + _ => Err("invalid at rule"), + } + } +} + +impl Display for AtRule { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AtRule::Use => write!(f, "@use"), + AtRule::Forward => write!(f, "@forward"), + AtRule::Import => write!(f, "@import"), + AtRule::Mixin => write!(f, "@mixin"), + AtRule::Include => write!(f, "@include"), + AtRule::Function => write!(f, "@function"), + AtRule::Extend => write!(f, "@extend"), + AtRule::AtRoot => write!(f, "@atroot"), + AtRule::Error => write!(f, "@error"), + AtRule::Warn => write!(f, "@warn"), + AtRule::Debug => write!(f, "@debug"), + AtRule::If => write!(f, "@if"), + AtRule::Each => write!(f, "@each"), + AtRule::For => write!(f, "@for"), + AtRule::While => write!(f, "@while"), + AtRule::Charset => write!(f, "@charset"), + AtRule::Namespace => write!(f, "@namespace"), + AtRule::Media => write!(f, "@media"), + AtRule::Supports => write!(f, "@supports"), + AtRule::Page => write!(f, "@page"), + AtRule::FontFace => write!(f, "@fontface"), + AtRule::Keyframes => write!(f, "@keyframes"), + AtRule::FontFeatureValues => write!(f, "@fontfeaturevalues"), + AtRule::Swash => write!(f, "@swash"), + AtRule::Ornaments => write!(f, "@ornaments"), + AtRule::Annotation => write!(f, "@annotation"), + AtRule::Stylistic => write!(f, "@stylistic"), + AtRule::Styleset => write!(f, "@styleset"), + AtRule::CharacterVariant => write!(f, "@charactervariant"), + AtRule::Viewport => write!(f, "@viewport"), + AtRule::Document => write!(f, "@document"), + AtRule::CounterStyle => write!(f, "@counterstyle"), + } + } +} + #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Whitespace { Space, @@ -268,6 +349,7 @@ pub enum Keyword { And, Or, Null, + In, } impl Display for Keyword { @@ -286,6 +368,7 @@ impl Display for Keyword { Keyword::And => write!(f, "and"), Keyword::Or => write!(f, "or"), Keyword::Null => write!(f, "null"), + Keyword::In => write!(f, "in"), } } } @@ -306,6 +389,7 @@ impl Into<&'static str> for Keyword { Keyword::And => "and", Keyword::Or => "or", Keyword::Null => "null", + Keyword::In => "in", } } } @@ -329,6 +413,7 @@ impl TryFrom<&str> for Keyword { "and" => Ok(Keyword::And), "or" => Ok(Keyword::Or), "null" => Ok(Keyword::Null), + "in" => Ok(Keyword::In), _ => Err("invalid keyword"), } } @@ -653,7 +738,7 @@ pub struct Pos { impl Pos { pub const fn new() -> Self { - Pos { line: 0, column: 0 } + Pos { line: 1, column: 1 } } pub const fn line(self) -> u32 { diff --git a/src/lexer.rs b/src/lexer.rs index 735151c..e7b0a2f 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use std::iter::Peekable; use std::str::Chars; -use crate::common::{Keyword, Op, Pos, Symbol}; +use crate::common::{AtRule, Keyword, Op, Pos, Symbol}; use crate::selector::{Attribute, AttributeKind, Selector}; use crate::units::Unit; use crate::{Token, TokenKind, Whitespace}; @@ -46,8 +46,15 @@ impl<'a> Iterator for Lexer<'a> { '"' => symbol!(self, DoubleQuote), ' ' => whitespace!(self, Space), '\t' => whitespace!(self, Tab), - '\n' => whitespace!(self, Newline), - '\r' => whitespace!(self, CarriageReturn), + '\n' => { + self.buf.next(); + self.pos.newline(); + TokenKind::Whitespace(Whitespace::Newline) + } + '\r' => { + self.buf.next(); + TokenKind::Whitespace(Whitespace::CarriageReturn) + } '#' => symbol!(self, Hash), '{' => symbol!(self, OpenBrace), '*' => symbol!(self, Mul), @@ -125,6 +132,7 @@ impl<'a> Lexer<'a> { } fn lex_at_rule(&mut self) -> TokenKind { + self.buf.next(); let mut string = String::with_capacity(99); while let Some(c) = self.buf.peek() { if !c.is_alphabetic() && c != &'-' && c != &'_' { @@ -138,8 +146,8 @@ impl<'a> Lexer<'a> { string.push(tok); } - if let Ok(kw) = Unit::try_from(string.as_ref()) { - TokenKind::Unit(kw) + if let Ok(rule) = AtRule::try_from(string.as_ref()) { + TokenKind::AtRule(rule) } else { panic!("expected ident after `@`") } diff --git a/src/main.rs b/src/main.rs index 86e9835..a229f93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,7 @@ use std::fs; use std::io; use std::iter::{Iterator, Peekable}; -use crate::common::{Keyword, Op, Pos, Symbol, Whitespace}; +use crate::common::{AtRule, Keyword, Op, Pos, Symbol, Whitespace}; use crate::css::Css; use crate::error::SassError; use crate::format::PrettyPrinter; @@ -55,7 +55,7 @@ pub enum TokenKind { Ident(String), Symbol(Symbol), Function(String, Vec), - AtRule(String), + AtRule(AtRule), Keyword(Keyword), Number(String), Unit(Unit), @@ -71,8 +71,9 @@ pub enum TokenKind { impl Display for TokenKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TokenKind::Ident(s) | TokenKind::Number(s) | TokenKind::AtRule(s) => write!(f, "{}", s), + TokenKind::Ident(s) | TokenKind::Number(s) => write!(f, "{}", s), TokenKind::Symbol(s) => write!(f, "{}", s), + TokenKind::AtRule(s) => write!(f, "{}", s), TokenKind::Op(s) => write!(f, "{}", s), TokenKind::Unit(s) => write!(f, "{}", s), TokenKind::Whitespace(s) => write!(f, "{}", s), @@ -119,7 +120,6 @@ enum Expr { } impl StyleSheet { - #[must_use] pub fn new(input: &str) -> SassResult { StyleSheetParser { global_variables: HashMap::new(), @@ -154,8 +154,8 @@ struct StyleSheetParser<'a> { impl<'a> StyleSheetParser<'a> { fn parse_toplevel(&mut self) -> SassResult { let mut rules = Vec::new(); - while let Some(tok) = self.lexer.peek() { - match tok.kind.clone() { + while let Some(Token { kind, .. }) = self.lexer.peek() { + match kind.clone() { TokenKind::Ident(_) | TokenKind::Selector(_) | TokenKind::Symbol(Symbol::Hash) @@ -168,27 +168,69 @@ impl<'a> StyleSheetParser<'a> { continue; } TokenKind::Variable(name) => { - self.lexer.next(); - self.devour_whitespace(); - if self + let Token { pos, .. } = self .lexer .next() - .expect("expected something after variable") - // .unwrap_or(Err(SassError::new("expected value after variable", this_tok.pos))?) + .expect("this cannot occur as we have already peeked"); + self.devour_whitespace(); + if self + .lexer + .next() + .unwrap_or_else(|| self.error(pos, "expected value after variable")) .kind != TokenKind::Symbol(Symbol::Colon) { - panic!("unexpected variable use at toplevel") + self.error(pos, "unexpected variable use at toplevel"); } let val = self.eat_variable_value(); self.global_variables.insert(name, val); } + TokenKind::AtRule(_) => self.eat_at_rule(), _ => todo!("unexpected toplevel token"), }; } Ok(StyleSheet { rules }) } + fn eat_at_rule(&mut self) { + if let Some(Token { + kind: TokenKind::AtRule(rule), + pos, + }) = self.lexer.next() + { + match rule { + AtRule::Error => { + let message = self + .lexer + .by_ref() + .take_while(|x| x.kind != TokenKind::Symbol(Symbol::SemiColon)) + .map(|x| x.kind.to_string()) + .collect::(); + self.error(pos, &message); + } + _ => todo!("encountered unimplemented at rule"), + } + } + } + + fn error(&mut self, pos: Pos, message: &str) -> ! { + eprintln!("Error: {}", message); + eprintln!( + "filename {}:{} scope on line {} at column {}", + pos.line(), + pos.column(), + pos.line(), + pos.column() + ); + let padding = vec![' '; format!("{}", pos.line()).len() + 1].iter().collect::(); + eprintln!("{}|", padding); + eprint!("{} | ", pos.line()); + eprintln!("placeholder!"); + eprintln!("{}| {}^", padding, vec![' '; pos.column() as usize].iter().collect::()); + eprintln!("{}|", padding); + std::process::exit(1); + } + fn eat_variable_value(&mut self) -> Vec { self.devour_whitespace(); let iter1 = self @@ -230,6 +272,9 @@ impl<'a> StyleSheetParser<'a> { rules, })); self.scope -= 1; + if self.scope == 0 { + return stmts; + } } Expr::VariableDecl(name, val) => { if self.scope == 0 {