Implement basic at rule parsing and @error
This commit is contained in:
parent
46a6fd90f6
commit
8320c3de77
@ -201,6 +201,87 @@ pub enum AtRule {
|
|||||||
CounterStyle,
|
CounterStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for AtRule {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(c: &str) -> Result<Self, &'static str> {
|
||||||
|
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)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Whitespace {
|
pub enum Whitespace {
|
||||||
Space,
|
Space,
|
||||||
@ -268,6 +349,7 @@ pub enum Keyword {
|
|||||||
And,
|
And,
|
||||||
Or,
|
Or,
|
||||||
Null,
|
Null,
|
||||||
|
In,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Keyword {
|
impl Display for Keyword {
|
||||||
@ -286,6 +368,7 @@ impl Display for Keyword {
|
|||||||
Keyword::And => write!(f, "and"),
|
Keyword::And => write!(f, "and"),
|
||||||
Keyword::Or => write!(f, "or"),
|
Keyword::Or => write!(f, "or"),
|
||||||
Keyword::Null => write!(f, "null"),
|
Keyword::Null => write!(f, "null"),
|
||||||
|
Keyword::In => write!(f, "in"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -306,6 +389,7 @@ impl Into<&'static str> for Keyword {
|
|||||||
Keyword::And => "and",
|
Keyword::And => "and",
|
||||||
Keyword::Or => "or",
|
Keyword::Or => "or",
|
||||||
Keyword::Null => "null",
|
Keyword::Null => "null",
|
||||||
|
Keyword::In => "in",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -329,6 +413,7 @@ impl TryFrom<&str> for Keyword {
|
|||||||
"and" => Ok(Keyword::And),
|
"and" => Ok(Keyword::And),
|
||||||
"or" => Ok(Keyword::Or),
|
"or" => Ok(Keyword::Or),
|
||||||
"null" => Ok(Keyword::Null),
|
"null" => Ok(Keyword::Null),
|
||||||
|
"in" => Ok(Keyword::In),
|
||||||
_ => Err("invalid keyword"),
|
_ => Err("invalid keyword"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -653,7 +738,7 @@ pub struct Pos {
|
|||||||
|
|
||||||
impl Pos {
|
impl Pos {
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Pos { line: 0, column: 0 }
|
Pos { line: 1, column: 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn line(self) -> u32 {
|
pub const fn line(self) -> u32 {
|
||||||
|
18
src/lexer.rs
18
src/lexer.rs
@ -2,7 +2,7 @@ use std::convert::TryFrom;
|
|||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::str::Chars;
|
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::selector::{Attribute, AttributeKind, Selector};
|
||||||
use crate::units::Unit;
|
use crate::units::Unit;
|
||||||
use crate::{Token, TokenKind, Whitespace};
|
use crate::{Token, TokenKind, Whitespace};
|
||||||
@ -46,8 +46,15 @@ impl<'a> Iterator for Lexer<'a> {
|
|||||||
'"' => symbol!(self, DoubleQuote),
|
'"' => symbol!(self, DoubleQuote),
|
||||||
' ' => whitespace!(self, Space),
|
' ' => whitespace!(self, Space),
|
||||||
'\t' => whitespace!(self, Tab),
|
'\t' => whitespace!(self, Tab),
|
||||||
'\n' => whitespace!(self, Newline),
|
'\n' => {
|
||||||
'\r' => whitespace!(self, CarriageReturn),
|
self.buf.next();
|
||||||
|
self.pos.newline();
|
||||||
|
TokenKind::Whitespace(Whitespace::Newline)
|
||||||
|
}
|
||||||
|
'\r' => {
|
||||||
|
self.buf.next();
|
||||||
|
TokenKind::Whitespace(Whitespace::CarriageReturn)
|
||||||
|
}
|
||||||
'#' => symbol!(self, Hash),
|
'#' => symbol!(self, Hash),
|
||||||
'{' => symbol!(self, OpenBrace),
|
'{' => symbol!(self, OpenBrace),
|
||||||
'*' => symbol!(self, Mul),
|
'*' => symbol!(self, Mul),
|
||||||
@ -125,6 +132,7 @@ impl<'a> Lexer<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn lex_at_rule(&mut self) -> TokenKind {
|
fn lex_at_rule(&mut self) -> TokenKind {
|
||||||
|
self.buf.next();
|
||||||
let mut string = String::with_capacity(99);
|
let mut string = String::with_capacity(99);
|
||||||
while let Some(c) = self.buf.peek() {
|
while let Some(c) = self.buf.peek() {
|
||||||
if !c.is_alphabetic() && c != &'-' && c != &'_' {
|
if !c.is_alphabetic() && c != &'-' && c != &'_' {
|
||||||
@ -138,8 +146,8 @@ impl<'a> Lexer<'a> {
|
|||||||
string.push(tok);
|
string.push(tok);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(kw) = Unit::try_from(string.as_ref()) {
|
if let Ok(rule) = AtRule::try_from(string.as_ref()) {
|
||||||
TokenKind::Unit(kw)
|
TokenKind::AtRule(rule)
|
||||||
} else {
|
} else {
|
||||||
panic!("expected ident after `@`")
|
panic!("expected ident after `@`")
|
||||||
}
|
}
|
||||||
|
69
src/main.rs
69
src/main.rs
@ -29,7 +29,7 @@ use std::fs;
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::iter::{Iterator, Peekable};
|
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::css::Css;
|
||||||
use crate::error::SassError;
|
use crate::error::SassError;
|
||||||
use crate::format::PrettyPrinter;
|
use crate::format::PrettyPrinter;
|
||||||
@ -55,7 +55,7 @@ pub enum TokenKind {
|
|||||||
Ident(String),
|
Ident(String),
|
||||||
Symbol(Symbol),
|
Symbol(Symbol),
|
||||||
Function(String, Vec<String>),
|
Function(String, Vec<String>),
|
||||||
AtRule(String),
|
AtRule(AtRule),
|
||||||
Keyword(Keyword),
|
Keyword(Keyword),
|
||||||
Number(String),
|
Number(String),
|
||||||
Unit(Unit),
|
Unit(Unit),
|
||||||
@ -71,8 +71,9 @@ pub enum TokenKind {
|
|||||||
impl Display for TokenKind {
|
impl Display for TokenKind {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
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::Symbol(s) => write!(f, "{}", s),
|
||||||
|
TokenKind::AtRule(s) => write!(f, "{}", s),
|
||||||
TokenKind::Op(s) => write!(f, "{}", s),
|
TokenKind::Op(s) => write!(f, "{}", s),
|
||||||
TokenKind::Unit(s) => write!(f, "{}", s),
|
TokenKind::Unit(s) => write!(f, "{}", s),
|
||||||
TokenKind::Whitespace(s) => write!(f, "{}", s),
|
TokenKind::Whitespace(s) => write!(f, "{}", s),
|
||||||
@ -119,7 +120,6 @@ enum Expr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StyleSheet {
|
impl StyleSheet {
|
||||||
#[must_use]
|
|
||||||
pub fn new(input: &str) -> SassResult<StyleSheet> {
|
pub fn new(input: &str) -> SassResult<StyleSheet> {
|
||||||
StyleSheetParser {
|
StyleSheetParser {
|
||||||
global_variables: HashMap::new(),
|
global_variables: HashMap::new(),
|
||||||
@ -154,8 +154,8 @@ struct StyleSheetParser<'a> {
|
|||||||
impl<'a> StyleSheetParser<'a> {
|
impl<'a> StyleSheetParser<'a> {
|
||||||
fn parse_toplevel(&mut self) -> SassResult<StyleSheet> {
|
fn parse_toplevel(&mut self) -> SassResult<StyleSheet> {
|
||||||
let mut rules = Vec::new();
|
let mut rules = Vec::new();
|
||||||
while let Some(tok) = self.lexer.peek() {
|
while let Some(Token { kind, .. }) = self.lexer.peek() {
|
||||||
match tok.kind.clone() {
|
match kind.clone() {
|
||||||
TokenKind::Ident(_)
|
TokenKind::Ident(_)
|
||||||
| TokenKind::Selector(_)
|
| TokenKind::Selector(_)
|
||||||
| TokenKind::Symbol(Symbol::Hash)
|
| TokenKind::Symbol(Symbol::Hash)
|
||||||
@ -168,27 +168,69 @@ impl<'a> StyleSheetParser<'a> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
TokenKind::Variable(name) => {
|
TokenKind::Variable(name) => {
|
||||||
self.lexer.next();
|
let Token { pos, .. } = self
|
||||||
self.devour_whitespace();
|
|
||||||
if self
|
|
||||||
.lexer
|
.lexer
|
||||||
.next()
|
.next()
|
||||||
.expect("expected something after variable")
|
.expect("this cannot occur as we have already peeked");
|
||||||
// .unwrap_or(Err(SassError::new("expected value after variable", this_tok.pos))?)
|
self.devour_whitespace();
|
||||||
|
if self
|
||||||
|
.lexer
|
||||||
|
.next()
|
||||||
|
.unwrap_or_else(|| self.error(pos, "expected value after variable"))
|
||||||
.kind
|
.kind
|
||||||
!= TokenKind::Symbol(Symbol::Colon)
|
!= TokenKind::Symbol(Symbol::Colon)
|
||||||
{
|
{
|
||||||
panic!("unexpected variable use at toplevel")
|
self.error(pos, "unexpected variable use at toplevel");
|
||||||
}
|
}
|
||||||
let val = self.eat_variable_value();
|
let val = self.eat_variable_value();
|
||||||
self.global_variables.insert(name, val);
|
self.global_variables.insert(name, val);
|
||||||
}
|
}
|
||||||
|
TokenKind::AtRule(_) => self.eat_at_rule(),
|
||||||
_ => todo!("unexpected toplevel token"),
|
_ => todo!("unexpected toplevel token"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Ok(StyleSheet { rules })
|
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::<String>();
|
||||||
|
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::<String>();
|
||||||
|
eprintln!("{}|", padding);
|
||||||
|
eprint!("{} | ", pos.line());
|
||||||
|
eprintln!("placeholder!");
|
||||||
|
eprintln!("{}| {}^", padding, vec![' '; pos.column() as usize].iter().collect::<String>());
|
||||||
|
eprintln!("{}|", padding);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
fn eat_variable_value(&mut self) -> Vec<Token> {
|
fn eat_variable_value(&mut self) -> Vec<Token> {
|
||||||
self.devour_whitespace();
|
self.devour_whitespace();
|
||||||
let iter1 = self
|
let iter1 = self
|
||||||
@ -230,6 +272,9 @@ impl<'a> StyleSheetParser<'a> {
|
|||||||
rules,
|
rules,
|
||||||
}));
|
}));
|
||||||
self.scope -= 1;
|
self.scope -= 1;
|
||||||
|
if self.scope == 0 {
|
||||||
|
return stmts;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Expr::VariableDecl(name, val) => {
|
Expr::VariableDecl(name, val) => {
|
||||||
if self.scope == 0 {
|
if self.scope == 0 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user