Refactor how function call args are parsed
This commit is contained in:
parent
c68a55327b
commit
42cbd685d3
193
src/args.rs
193
src/args.rs
@ -2,6 +2,7 @@ use std::collections::BTreeMap;
|
|||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
|
|
||||||
use crate::common::{Scope, Symbol};
|
use crate::common::{Scope, Symbol};
|
||||||
|
use crate::error::SassResult;
|
||||||
use crate::utils::{devour_whitespace, devour_whitespace_or_comment};
|
use crate::utils::{devour_whitespace, devour_whitespace_or_comment};
|
||||||
use crate::value::Value;
|
use crate::value::Value;
|
||||||
use crate::{Token, TokenKind};
|
use crate::{Token, TokenKind};
|
||||||
@ -127,141 +128,79 @@ pub(crate) fn eat_func_args<I: Iterator<Item = Token>>(
|
|||||||
pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
|
pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
|
||||||
toks: &mut Peekable<I>,
|
toks: &mut Peekable<I>,
|
||||||
scope: &Scope,
|
scope: &Scope,
|
||||||
) -> CallArgs {
|
) -> SassResult<CallArgs> {
|
||||||
let mut args: BTreeMap<String, Value> = BTreeMap::new();
|
let mut args: BTreeMap<String, Value> = BTreeMap::new();
|
||||||
devour_whitespace_or_comment(toks);
|
devour_whitespace_or_comment(toks);
|
||||||
let mut name: Option<String> = None;
|
let mut name: String;
|
||||||
let mut val = Vec::new();
|
let mut val: Vec<Token> = Vec::new();
|
||||||
while let Some(Token { kind, pos }) = toks.next() {
|
loop {
|
||||||
match kind {
|
match toks.peek().unwrap().kind {
|
||||||
TokenKind::Variable(v) => {
|
TokenKind::Variable(_) => {
|
||||||
|
let v = toks.next().unwrap();
|
||||||
devour_whitespace_or_comment(toks);
|
devour_whitespace_or_comment(toks);
|
||||||
match toks.peek() {
|
if toks.next().unwrap().equals_symbol(Symbol::Colon) {
|
||||||
Some(Token {
|
name = v.kind.to_string();
|
||||||
kind: TokenKind::Symbol(Symbol::Colon),
|
} else {
|
||||||
..
|
val.push(v);
|
||||||
}) => name = Some(v),
|
name = args.len().to_string();
|
||||||
Some(Token {
|
|
||||||
kind: TokenKind::Symbol(Symbol::Comma),
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
toks.next();
|
|
||||||
match name {
|
|
||||||
Some(ref name) => {
|
|
||||||
args.insert(name.clone(), scope.get_var(&v).unwrap().clone())
|
|
||||||
}
|
|
||||||
None => args.insert(
|
|
||||||
format!("{}", args.len()),
|
|
||||||
scope.get_var(&v).unwrap().clone(),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
if let Some(ref mut s) = name {
|
|
||||||
s.clear();
|
|
||||||
}
|
|
||||||
val.clear();
|
|
||||||
}
|
|
||||||
Some(Token {
|
|
||||||
kind: TokenKind::Symbol(Symbol::CloseParen),
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
toks.next();
|
|
||||||
match name {
|
|
||||||
Some(name) => args.insert(name, scope.get_var(&v).unwrap().clone()),
|
|
||||||
None => args.insert(
|
|
||||||
format!("{}", args.len()),
|
|
||||||
scope.get_var(&v).unwrap().clone(),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => todo!("unexpected token after variable in call args"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TokenKind::Symbol(Symbol::Colon) => {
|
|
||||||
devour_whitespace_or_comment(toks);
|
|
||||||
while let Some(tok) = toks.peek() {
|
|
||||||
match &tok.kind {
|
|
||||||
TokenKind::Symbol(Symbol::Comma) => {
|
|
||||||
toks.next();
|
|
||||||
args.insert(
|
|
||||||
name.clone().unwrap(),
|
|
||||||
Value::from_tokens(&mut val.clone().into_iter().peekable(), scope)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
if let Some(ref mut s) = name {
|
|
||||||
s.clear();
|
|
||||||
}
|
|
||||||
val.clear();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
TokenKind::Symbol(Symbol::CloseParen) => {
|
|
||||||
args.insert(
|
|
||||||
name.clone().unwrap(),
|
|
||||||
Value::from_tokens(&mut val.clone().into_iter().peekable(), scope)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => val.push(toks.next().expect("we know this exists!")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TokenKind::Symbol(Symbol::OpenParen) => {
|
|
||||||
val.push(Token { kind, pos });
|
|
||||||
let mut unclosed_parens = 0;
|
|
||||||
while let Some(tok) = toks.next() {
|
|
||||||
match &tok.kind {
|
|
||||||
TokenKind::Symbol(Symbol::OpenParen) => {
|
|
||||||
unclosed_parens += 1;
|
|
||||||
}
|
|
||||||
TokenKind::Symbol(Symbol::CloseParen) => {
|
|
||||||
if unclosed_parens <= 1 {
|
|
||||||
val.push(tok);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
unclosed_parens -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
val.push(tok);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TokenKind::Symbol(Symbol::CloseParen) => {
|
TokenKind::Symbol(Symbol::CloseParen) => {
|
||||||
if val.is_empty() {
|
toks.next();
|
||||||
break;
|
return Ok(CallArgs(args));
|
||||||
}
|
|
||||||
match name {
|
|
||||||
Some(name) => args.insert(
|
|
||||||
name,
|
|
||||||
Value::from_tokens(&mut val.into_iter().peekable(), scope).unwrap(),
|
|
||||||
),
|
|
||||||
None => args.insert(
|
|
||||||
format!("{}", args.len()),
|
|
||||||
Value::from_tokens(&mut val.into_iter().peekable(), scope).unwrap(),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
TokenKind::Symbol(Symbol::Comma) => {
|
_ => name = args.len().to_string(),
|
||||||
match name {
|
|
||||||
Some(ref name) => args.insert(
|
|
||||||
name.clone(),
|
|
||||||
Value::from_tokens(&mut val.clone().into_iter().peekable(), scope).unwrap(),
|
|
||||||
),
|
|
||||||
None => args.insert(
|
|
||||||
format!("{}", args.len()),
|
|
||||||
Value::from_tokens(&mut val.clone().into_iter().peekable(), scope).unwrap(),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
if let Some(ref mut s) = name {
|
|
||||||
s.clear();
|
|
||||||
}
|
|
||||||
val.clear();
|
|
||||||
}
|
|
||||||
_ => val.push(Token { kind, pos }),
|
|
||||||
}
|
}
|
||||||
devour_whitespace_or_comment(toks);
|
devour_whitespace_or_comment(toks);
|
||||||
|
|
||||||
|
while let Some(tok) = toks.next() {
|
||||||
|
match tok.kind {
|
||||||
|
TokenKind::Symbol(Symbol::CloseParen) => {
|
||||||
|
args.insert(
|
||||||
|
name,
|
||||||
|
Value::from_tokens(&mut val.clone().into_iter().peekable(), scope)?,
|
||||||
|
);
|
||||||
|
return Ok(CallArgs(args));
|
||||||
|
}
|
||||||
|
TokenKind::Symbol(Symbol::Comma) => break,
|
||||||
|
TokenKind::Symbol(Symbol::OpenParen) => {
|
||||||
|
val.push(tok);
|
||||||
|
val.extend(read_until_close_paren(toks));
|
||||||
|
}
|
||||||
|
_ => val.push(tok),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args.insert(
|
||||||
|
name,
|
||||||
|
Value::from_tokens(&mut val.clone().into_iter().peekable(), scope)?,
|
||||||
|
);
|
||||||
|
val.clear();
|
||||||
|
devour_whitespace_or_comment(toks);
|
||||||
|
|
||||||
|
if toks.peek().is_none() {
|
||||||
|
return Ok(CallArgs(args));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CallArgs(args)
|
}
|
||||||
|
|
||||||
|
fn read_until_close_paren<I: Iterator<Item = Token>>(toks: &mut Peekable<I>) -> Vec<Token> {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
let mut scope = 0;
|
||||||
|
while let Some(tok) = toks.next() {
|
||||||
|
match tok.kind {
|
||||||
|
TokenKind::Symbol(Symbol::CloseParen) => {
|
||||||
|
if scope <= 1 {
|
||||||
|
v.push(tok);
|
||||||
|
return v;
|
||||||
|
} else {
|
||||||
|
scope -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TokenKind::Symbol(Symbol::OpenParen) => scope += 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
v.push(tok)
|
||||||
|
}
|
||||||
|
v
|
||||||
}
|
}
|
||||||
|
14
src/lib.rs
14
src/lib.rs
@ -96,6 +96,12 @@ pub(crate) struct Token {
|
|||||||
pub kind: TokenKind,
|
pub kind: TokenKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Token {
|
||||||
|
pub fn equals_symbol(&self, s: Symbol) -> bool {
|
||||||
|
self.kind.equals_symbol(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IsWhitespace for Token {
|
impl IsWhitespace for Token {
|
||||||
fn is_whitespace(&self) -> bool {
|
fn is_whitespace(&self) -> bool {
|
||||||
if let TokenKind::Whitespace(_) = self.kind {
|
if let TokenKind::Whitespace(_) = self.kind {
|
||||||
@ -147,6 +153,12 @@ pub(crate) enum TokenKind {
|
|||||||
Interpolation,
|
Interpolation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TokenKind {
|
||||||
|
pub fn equals_symbol(&self, s: Symbol) -> bool {
|
||||||
|
self == &TokenKind::Symbol(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for TokenKind {
|
impl Display for TokenKind {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
@ -159,7 +171,7 @@ impl Display for TokenKind {
|
|||||||
TokenKind::Attribute(s) => write!(f, "{}", s),
|
TokenKind::Attribute(s) => write!(f, "{}", s),
|
||||||
TokenKind::Keyword(kw) => write!(f, "{}", kw),
|
TokenKind::Keyword(kw) => write!(f, "{}", kw),
|
||||||
TokenKind::MultilineComment(s) => write!(f, "/*{}*/", s),
|
TokenKind::MultilineComment(s) => write!(f, "/*{}*/", s),
|
||||||
TokenKind::Variable(s) => write!(f, "${}", s),
|
TokenKind::Variable(s) => write!(f, "{}", s),
|
||||||
TokenKind::Interpolation => {
|
TokenKind::Interpolation => {
|
||||||
panic!("we don't want to format TokenKind::Interpolation using Display")
|
panic!("we don't want to format TokenKind::Interpolation using Display")
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ pub(crate) fn eat_include<I: Iterator<Item = Token>>(
|
|||||||
match tok.kind {
|
match tok.kind {
|
||||||
TokenKind::Symbol(Symbol::SemiColon) => CallArgs::new(),
|
TokenKind::Symbol(Symbol::SemiColon) => CallArgs::new(),
|
||||||
TokenKind::Symbol(Symbol::OpenParen) => {
|
TokenKind::Symbol(Symbol::OpenParen) => {
|
||||||
let tmp = eat_call_args(toks, scope);
|
let tmp = eat_call_args(toks, scope)?;
|
||||||
devour_whitespace(toks);
|
devour_whitespace(toks);
|
||||||
if let Some(tok) = toks.next() {
|
if let Some(tok) = toks.next() {
|
||||||
assert_eq!(tok.kind, TokenKind::Symbol(Symbol::SemiColon));
|
assert_eq!(tok.kind, TokenKind::Symbol(Symbol::SemiColon));
|
||||||
|
@ -223,7 +223,7 @@ impl Value {
|
|||||||
let func = match scope.get_fn(&s) {
|
let func = match scope.get_fn(&s) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(_) => match GLOBAL_FUNCTIONS.get(&s) {
|
Err(_) => match GLOBAL_FUNCTIONS.get(&s) {
|
||||||
Some(f) => return f(&eat_call_args(toks, scope), scope),
|
Some(f) => return f(&eat_call_args(toks, scope)?, scope),
|
||||||
None => {
|
None => {
|
||||||
s.push('(');
|
s.push('(');
|
||||||
let mut unclosed_parens = 0;
|
let mut unclosed_parens = 0;
|
||||||
@ -260,7 +260,7 @@ impl Value {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
Ok(func.clone().args(&eat_call_args(toks, scope)).call())
|
Ok(func.clone().args(&eat_call_args(toks, scope)?).call())
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if let Ok(c) = crate::color::ColorName::try_from(s.as_ref()) {
|
if let Ok(c) = crate::color::ColorName::try_from(s.as_ref()) {
|
||||||
|
@ -81,11 +81,11 @@ test!(
|
|||||||
opacity_function_number_unit,
|
opacity_function_number_unit,
|
||||||
"a {\n color: opacity(1px);\n}\n"
|
"a {\n color: opacity(1px);\n}\n"
|
||||||
);
|
);
|
||||||
// blocked on better function-call argument parsing
|
// blocked on better value parsing
|
||||||
// specifically, passing lists as values
|
// specifically, this is parsed as a number and a list
|
||||||
// test!(
|
// test!(
|
||||||
// rgba_one_arg,
|
// rgba_one_arg,
|
||||||
// "a {\n color: rgba(1 2 3);;\n}\n",
|
// "a {\n color: rgba(1 2 3);\n}\n",
|
||||||
// "a {\n color: #010203;\n}\n"
|
// "a {\n color: #010203;\n}\n"
|
||||||
// );
|
// );
|
||||||
test!(
|
test!(
|
||||||
@ -293,17 +293,16 @@ test!(
|
|||||||
"a {\n color: adjust-hue(#811, 45deg);\n}\n",
|
"a {\n color: adjust-hue(#811, 45deg);\n}\n",
|
||||||
"a {\n color: #886a11;\n}\n"
|
"a {\n color: #886a11;\n}\n"
|
||||||
);
|
);
|
||||||
// blocked on better parsing of call args
|
test!(
|
||||||
// test!(
|
adjust_hue_named_args,
|
||||||
// adjust_hue_named_args,
|
"a {\n color: adjust-hue($color: hsl(120, 30%, 90%), $degrees: 60deg);\n}\n",
|
||||||
// "a {\n color: adjust-hue($color: hsl(120, 30%, 90%), $degrees: 60deg);\n}\n",
|
"a {\n color: #deeded;\n}\n"
|
||||||
// "a {\n color: #deeded;\n}\n"
|
);
|
||||||
// );
|
test!(
|
||||||
// test!(
|
lighten_named_args,
|
||||||
// lighten_named_args,
|
"a {\n color: lighten($color: hsl(0, 0%, 0%), $amount: 30%);\n}\n",
|
||||||
// "a {\n color: lighten($color: hsl(0, 0%, 0%), $amount: 30%);\n}\n",
|
"a {\n color: #4d4d4d;\n}\n"
|
||||||
// "a {\n color: #deeded;\n}\n"
|
);
|
||||||
// );
|
|
||||||
test!(
|
test!(
|
||||||
lighten_basic,
|
lighten_basic,
|
||||||
"a {\n color: lighten(hsl(0, 0%, 0%), 30%);\n}\n",
|
"a {\n color: lighten(hsl(0, 0%, 0%), 30%);\n}\n",
|
||||||
@ -316,12 +315,11 @@ test!(
|
|||||||
// blocked on recognizing when to use 3-hex over 6-hex
|
// blocked on recognizing when to use 3-hex over 6-hex
|
||||||
"a {\n color: #ee0000;\n}\n"
|
"a {\n color: #ee0000;\n}\n"
|
||||||
);
|
);
|
||||||
// blocked on better parsing of call args
|
test!(
|
||||||
// test!(
|
darken_named_args,
|
||||||
// darken_named_args,
|
"a {\n color: darken($color: hsl(25, 100%, 80%), $amount: 30%);\n}\n",
|
||||||
// "a {\n color: darken($color: hsl(25, 100%, 80%), $amount: 30%);\n}\n",
|
"a {\n color: #ff6a00;\n}\n"
|
||||||
// "a {\n color: #ff6a00;\n}\n"
|
);
|
||||||
// );
|
|
||||||
test!(
|
test!(
|
||||||
darken_basic,
|
darken_basic,
|
||||||
"a {\n color: darken(hsl(25, 100%, 80%), 30%);\n}\n",
|
"a {\n color: darken(hsl(25, 100%, 80%), 30%);\n}\n",
|
||||||
@ -334,12 +332,11 @@ test!(
|
|||||||
// blocked on recognizing when to use 3-hex over 6-hex
|
// blocked on recognizing when to use 3-hex over 6-hex
|
||||||
"a {\n color: #220000;\n}\n"
|
"a {\n color: #220000;\n}\n"
|
||||||
);
|
);
|
||||||
// blocked on better parsing of call args
|
test!(
|
||||||
// test!(
|
saturate_named_args,
|
||||||
// saturate_named_args,
|
"a {\n color: saturate($color: hsl(25, 100%, 80%), $amount: 30%);\n}\n",
|
||||||
// "a {\n color: saturate($color: hsl(25, 100%, 80%), $amount: 30%);\n}\n",
|
"a {\n color: #ffc499;\n}\n"
|
||||||
// "a {\n color: #ff6a00;\n}\n"
|
);
|
||||||
// );
|
|
||||||
test!(
|
test!(
|
||||||
saturate_basic,
|
saturate_basic,
|
||||||
"a {\n color: saturate(hsl(120, 30%, 90%), 20%);\n}\n",
|
"a {\n color: saturate(hsl(120, 30%, 90%), 20%);\n}\n",
|
||||||
@ -350,12 +347,11 @@ test!(
|
|||||||
"a {\n color: saturate(#855, 20%);\n}\n",
|
"a {\n color: saturate(#855, 20%);\n}\n",
|
||||||
"a {\n color: #9e3f3f;\n}\n"
|
"a {\n color: #9e3f3f;\n}\n"
|
||||||
);
|
);
|
||||||
// blocked on better parsing of call args
|
test!(
|
||||||
// test!(
|
desaturate_named_args,
|
||||||
// desaturate_named_args,
|
"a {\n color: desaturate($color: hsl(25, 100%, 80%), $amount: 30%);\n}\n",
|
||||||
// "a {\n color: desaturate($color: hsl(25, 100%, 80%), $amount: 30%);\n}\n",
|
"a {\n color: #f0c6a8;\n}\n"
|
||||||
// "a {\n color: #ff6a00;\n}\n"
|
);
|
||||||
// );
|
|
||||||
test!(
|
test!(
|
||||||
desaturate_basic,
|
desaturate_basic,
|
||||||
"a {\n color: desaturate(hsl(120, 30%, 90%), 20%);\n}\n",
|
"a {\n color: desaturate(hsl(120, 30%, 90%), 20%);\n}\n",
|
||||||
@ -402,6 +398,7 @@ test!(
|
|||||||
"a {\n color: gray;\n}\n"
|
"a {\n color: gray;\n}\n"
|
||||||
);
|
);
|
||||||
test!(grayscale_number, "a {\n color: grayscale(15%);\n}\n");
|
test!(grayscale_number, "a {\n color: grayscale(15%);\n}\n");
|
||||||
|
// handle special functions better!
|
||||||
// test!(
|
// test!(
|
||||||
// grayscale_number_casing,
|
// grayscale_number_casing,
|
||||||
// "a {\n color: grAyscaLe(15%);\n}\n"
|
// "a {\n color: grAyscaLe(15%);\n}\n"
|
||||||
|
@ -98,11 +98,8 @@ test!(
|
|||||||
"a {\n color: unquote('');\n}\n",
|
"a {\n color: unquote('');\n}\n",
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
// blocked on refactoring how function-call args are parsed
|
test!(
|
||||||
// right now, whitespace is eaten between idents with no
|
str_len_space,
|
||||||
// regard for quotes
|
"a {\n color: str-length(\"foo bar\");\n}\n",
|
||||||
// test!(
|
"a {\n color: 7;\n}\n"
|
||||||
// str_len_space,
|
);
|
||||||
// "a {\n color: str-length(\"foo bar\");\n}\n",
|
|
||||||
// "a {\n color: 7;\n}\n"
|
|
||||||
// );
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user