differentiate named and positional args

This commit is contained in:
ConnorSkees 2020-04-02 12:28:28 -04:00
parent c16b8f448b
commit f9fc0ed8cb
5 changed files with 63 additions and 26 deletions

View File

@ -28,16 +28,26 @@ impl FuncArgs {
} }
} }
#[derive(Debug, Clone, std::default::Default)] #[derive(Debug, Clone)]
pub(crate) struct CallArgs(pub HashMap<String, Value>); pub(crate) struct CallArgs(HashMap<CallArg, Value>);
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
enum CallArg {
Named(String),
Positional(usize),
}
impl CallArgs { impl CallArgs {
pub fn new() -> Self { pub fn new() -> Self {
CallArgs(HashMap::new()) CallArgs(HashMap::new())
} }
pub fn get(&self, val: &str) -> Option<&Value> { pub fn get_named(&self, val: String) -> Option<&Value> {
self.0.get(val) self.0.get(&CallArg::Named(val))
}
pub fn get_positional(&self, val: usize) -> Option<&Value> {
self.0.get(&CallArg::Positional(val))
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
@ -48,8 +58,12 @@ impl CallArgs {
self.0.len() == 0 self.0.len() == 0
} }
pub fn remove(&mut self, s: &str) -> Option<Value> { pub fn remove_named(&mut self, s: String) -> Option<Value> {
self.0.remove(s) self.0.remove(&CallArg::Named(s))
}
pub fn remove_positional(&mut self, s: usize) -> Option<Value> {
self.0.remove(&CallArg::Positional(s))
} }
} }
@ -174,9 +188,9 @@ pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
scope: &Scope, scope: &Scope,
super_selector: &Selector, super_selector: &Selector,
) -> SassResult<CallArgs> { ) -> SassResult<CallArgs> {
let mut args: HashMap<String, Value> = HashMap::new(); let mut args: HashMap<CallArg, Value> = HashMap::new();
devour_whitespace_or_comment(toks)?; devour_whitespace_or_comment(toks)?;
let mut name: String; let mut name = String::new();
let mut val: Vec<Token> = Vec::new(); let mut val: Vec<Token> = Vec::new();
loop { loop {
match toks.peek().unwrap().kind { match toks.peek().unwrap().kind {
@ -190,14 +204,14 @@ pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
} else { } else {
val.push(Token::new(Pos::new(), '$')); val.push(Token::new(Pos::new(), '$'));
val.extend(v.chars().map(|x| Token::new(Pos::new(), x))); val.extend(v.chars().map(|x| Token::new(Pos::new(), x)));
name = args.len().to_string(); name.clear();
} }
} }
')' => { ')' => {
toks.next(); toks.next();
return Ok(CallArgs(args)); return Ok(CallArgs(args));
} }
_ => name = args.len().to_string(), _ => name.clear(),
} }
devour_whitespace_or_comment(toks)?; devour_whitespace_or_comment(toks)?;
@ -205,7 +219,11 @@ pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
match tok.kind { match tok.kind {
')' => { ')' => {
args.insert( args.insert(
name.replace('_', "-"), if name.is_empty() {
CallArg::Positional(args.len())
} else {
CallArg::Named(name.replace('_', "-"))
},
Value::from_tokens(&mut val.into_iter().peekable(), scope, super_selector)?, Value::from_tokens(&mut val.into_iter().peekable(), scope, super_selector)?,
); );
return Ok(CallArgs(args)); return Ok(CallArgs(args));
@ -228,7 +246,11 @@ pub(crate) fn eat_call_args<I: Iterator<Item = Token>>(
} }
args.insert( args.insert(
name.replace('_', "-"), if name.is_empty() {
CallArg::Positional(args.len())
} else {
CallArg::Named(name.replace('_', "-"))
},
Value::from_tokens( Value::from_tokens(
&mut val.clone().into_iter().peekable(), &mut val.clone().into_iter().peekable(),
scope, scope,

View File

@ -45,9 +45,9 @@ impl Function {
pub fn args(mut self, args: &mut CallArgs) -> SassResult<Function> { pub fn args(mut self, args: &mut CallArgs) -> SassResult<Function> {
for (idx, arg) in self.args.0.iter().enumerate() { for (idx, arg) in self.args.0.iter().enumerate() {
let val = match args.remove(&format!("{}", idx)) { let val = match args.remove_positional(idx) {
Some(v) => v, Some(v) => v,
None => match args.remove(&arg.name) { None => match args.remove_named(arg.name.clone()) {
Some(v) => v, Some(v) => v,
None => match &arg.default { None => match &arg.default {
Some(v) => v.clone(), Some(v) => v.clone(),

View File

@ -61,9 +61,9 @@ impl Mixin {
pub fn args(mut self, args: &mut CallArgs) -> SassResult<Mixin> { pub fn args(mut self, args: &mut CallArgs) -> SassResult<Mixin> {
for (idx, arg) in self.args.0.iter().enumerate() { for (idx, arg) in self.args.0.iter().enumerate() {
let val = match args.remove(&format!("{}", idx)) { let val = match args.remove_positional(idx) {
Some(v) => v, Some(v) => v,
None => match args.remove(&arg.name) { None => match args.remove_named(arg.name.clone()) {
Some(v) => v, Some(v) => v,
None => match &arg.default { None => match &arg.default {
Some(v) => v.clone(), Some(v) => v.clone(),

View File

@ -11,7 +11,7 @@ use crate::value::{Number, Value};
macro_rules! opt_rgba { macro_rules! opt_rgba {
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => {
let x = $low; let x = $low;
let $name = match arg!($args, -1, $arg = Value::Null) { let $name = match named_arg!($args, $arg = Value::Null) {
Value::Dimension(n, u) => Some(bound!($arg, n, u, x, $high)), Value::Dimension(n, u) => Some(bound!($arg, n, u, x, $high)),
Value::Null => None, Value::Null => None,
v => return Err(format!("${}: {} is not a number.", $arg, v).into()), v => return Err(format!("${}: {} is not a number.", $arg, v).into()),
@ -22,7 +22,7 @@ macro_rules! opt_rgba {
macro_rules! opt_hsl { macro_rules! opt_hsl {
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => {
let x = $low; let x = $low;
let $name = match arg!($args, -1, $arg = Value::Null) { let $name = match named_arg!($args, $arg = Value::Null) {
Value::Dimension(n, u) => Some(bound!($arg, n, u, x, $high) / Number::from(100)), Value::Dimension(n, u) => Some(bound!($arg, n, u, x, $high) / Number::from(100)),
Value::Null => None, Value::Null => None,
v => return Err(format!("${}: {} is not a number.", $arg, v).into()), v => return Err(format!("${}: {} is not a number.", $arg, v).into()),
@ -32,7 +32,7 @@ macro_rules! opt_hsl {
pub(crate) fn register(f: &mut HashMap<String, Builtin>) { pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
f.insert("change-color".to_owned(), Box::new(|args, _| { f.insert("change-color".to_owned(), Box::new(|args, _| {
if args.get("1").is_some() { if args.get_positional(1).is_some() {
return Err("Only one positional argument is allowed. All other arguments must be passed by name.".into()); return Err("Only one positional argument is allowed. All other arguments must be passed by name.".into());
} }
@ -50,7 +50,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
return Ok(Value::Color(Color::from_rgba(red.unwrap_or(color.red()), green.unwrap_or(color.green()), blue.unwrap_or(color.blue()), alpha.unwrap_or(color.alpha())))) return Ok(Value::Color(Color::from_rgba(red.unwrap_or(color.red()), green.unwrap_or(color.green()), blue.unwrap_or(color.blue()), alpha.unwrap_or(color.alpha()))))
} }
let hue = match arg!(args, -1, "hue"=Value::Null) { let hue = match named_arg!(args, "hue"=Value::Null) {
Value::Dimension(n, _) => Some(n), Value::Dimension(n, _) => Some(n),
Value::Null => None, Value::Null => None,
v => return Err(format!("$hue: {} is not a number.", v).into()), v => return Err(format!("$hue: {} is not a number.", v).into()),
@ -93,7 +93,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
))); )));
} }
let hue = match arg!(args, -1, "hue" = Value::Null) { let hue = match named_arg!(args, "hue" = Value::Null) {
Value::Dimension(n, _) => Some(n), Value::Dimension(n, _) => Some(n),
Value::Null => None, Value::Null => None,
v => return Err(format!("$hue: {} is not a number.", v).into()), v => return Err(format!("$hue: {} is not a number.", v).into()),
@ -132,7 +132,7 @@ pub(crate) fn register(f: &mut HashMap<String, Builtin>) {
macro_rules! opt_scale_arg { macro_rules! opt_scale_arg {
($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => { ($args:ident, $name:ident, $arg:literal, $low:literal, $high:literal) => {
let x = $low; let x = $low;
let $name = match arg!($args, -1, $arg = Value::Null) { let $name = match named_arg!($args, $arg = Value::Null) {
Value::Dimension(n, Unit::Percent) => { Value::Dimension(n, Unit::Percent) => {
Some(bound!($arg, n, Unit::Percent, x, $high) / Number::from(100)) Some(bound!($arg, n, Unit::Percent, x, $high) / Number::from(100))
} }

View File

@ -1,17 +1,17 @@
macro_rules! arg { macro_rules! arg {
($args:ident, $idx:literal, $name:literal) => { ($args:ident, $idx:literal, $name:literal) => {
match $args.remove(stringify!($idx)) { match $args.remove_positional($idx) {
Some(v) => v.eval()?, Some(v) => v.eval()?,
None => match $args.remove($name) { None => match $args.remove_named($name.to_owned()) {
Some(v) => v.eval()?, Some(v) => v.eval()?,
None => return Err(concat!("Missing argument $", $name, ".").into()), None => return Err(concat!("Missing argument $", $name, ".").into()),
}, },
}; };
}; };
($args:ident, $idx:literal, $name:literal=$default:expr) => { ($args:ident, $idx:literal, $name:literal=$default:expr) => {
match $args.remove(stringify!($idx)) { match $args.remove_positional($idx) {
Some(v) => v.eval()?, Some(v) => v.eval()?,
None => match $args.remove($name) { None => match $args.remove_named($name.to_owned()) {
Some(v) => v.eval()?, Some(v) => v.eval()?,
None => $default, None => $default,
}, },
@ -19,6 +19,21 @@ macro_rules! arg {
}; };
} }
macro_rules! named_arg {
($args:ident, $name:literal) => {
match $args.remove_named($name.to_owned()) {
Some(v) => v.eval()?,
None => return Err(concat!("Missing argument $", $name, ".").into()),
};
};
($args:ident, $name:literal=$default:expr) => {
match $args.remove_named($name.to_owned()) {
Some(v) => v.eval()?,
None => $default,
};
};
}
macro_rules! max_args { macro_rules! max_args {
($args:ident, $count:literal) => { ($args:ident, $count:literal) => {
if $args.len() > $count { if $args.len() > $count {