diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 3f5801782..9e5a85550 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -39,6 +39,8 @@ pub use show::*; pub use styles::*; pub use value::*; +use std::collections::BTreeMap; + use parking_lot::{MappedRwLockWriteGuard, RwLockWriteGuard}; use unicode_segmentation::UnicodeSegmentation; @@ -307,7 +309,21 @@ impl Eval for ArrayExpr { type Output = Array; fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { - self.items().map(|expr| expr.eval(ctx, scp)).collect() + let items = self.items(); + + let mut vec = Vec::with_capacity(items.size_hint().0); + for item in items { + match item { + ArrayItem::Pos(expr) => vec.push(expr.eval(ctx, scp)?), + ArrayItem::Spread(expr) => match expr.eval(ctx, scp)? { + Value::None => {} + Value::Array(array) => vec.extend(array.into_iter()), + v => bail!(expr.span(), "cannot spread {} into array", v.type_name()), + }, + } + } + + Ok(Array::from_vec(vec)) } } @@ -315,9 +331,26 @@ impl Eval for DictExpr { type Output = Dict; fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult { - self.items() - .map(|x| Ok((x.name().take(), x.expr().eval(ctx, scp)?))) - .collect() + let mut map = BTreeMap::new(); + + for item in self.items() { + match item { + DictItem::Named(named) => { + map.insert(named.name().take(), named.expr().eval(ctx, scp)?); + } + DictItem::Spread(expr) => match expr.eval(ctx, scp)? { + Value::None => {} + Value::Dict(dict) => map.extend(dict.into_iter()), + v => bail!( + expr.span(), + "cannot spread {} into dictionary", + v.type_name() + ), + }, + } + } + + Ok(Dict::from_map(map)) } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 28b7f81d5..3bf274f2a 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -516,7 +516,7 @@ fn parenthesized(p: &mut Parser, atomic: bool) -> ParseResult { let kind = collection(p).0; p.end_group(); - // Leading colon makes this a (empty) dictionary. + // Leading colon makes this a dictionary. if colon { dict(p, marker); return Ok(()); @@ -556,21 +556,23 @@ enum CollectionKind { /// Returns the length of the collection and whether the literal contained any /// commas. fn collection(p: &mut Parser) -> (CollectionKind, usize) { - let mut kind = CollectionKind::Positional; + let mut kind = None; let mut items = 0; let mut can_group = true; - let mut error = false; let mut missing_coma: Option = None; while !p.eof() { if let Ok(item_kind) = item(p) { - if items == 0 && item_kind == NodeKind::Named { - kind = CollectionKind::Named; - can_group = false; - } - - if item_kind == NodeKind::Spread { - can_group = false; + match item_kind { + NodeKind::Spread => can_group = false, + NodeKind::Named if kind.is_none() => { + kind = Some(CollectionKind::Named); + can_group = false; + } + _ if kind.is_none() => { + kind = Some(CollectionKind::Positional); + } + _ => {} } items += 1; @@ -589,13 +591,15 @@ fn collection(p: &mut Parser) -> (CollectionKind, usize) { missing_coma = Some(p.trivia_start()); } } else { - error = true; + kind = Some(CollectionKind::Group); } } - if error || (can_group && items == 1) { - kind = CollectionKind::Group; - } + let kind = if can_group && items == 1 { + CollectionKind::Group + } else { + kind.unwrap_or(CollectionKind::Positional) + }; (kind, items) } @@ -636,7 +640,6 @@ fn item(p: &mut Parser) -> ParseResult { fn array(p: &mut Parser, marker: Marker) { marker.filter_children(p, |x| match x.kind() { NodeKind::Named => Err("expected expression, found named pair"), - NodeKind::Spread => Err("spreading is not allowed here"), _ => Ok(()), }); marker.end(p, NodeKind::ArrayExpr); @@ -647,8 +650,7 @@ fn array(p: &mut Parser, marker: Marker) { fn dict(p: &mut Parser, marker: Marker) { marker.filter_children(p, |x| match x.kind() { kind if kind.is_paren() => Ok(()), - NodeKind::Named | NodeKind::Comma | NodeKind::Colon => Ok(()), - NodeKind::Spread => Err("spreading is not allowed here"), + NodeKind::Named | NodeKind::Comma | NodeKind::Colon | NodeKind::Spread => Ok(()), _ => Err("expected named pair, found expression"), }); marker.end(p, NodeKind::DictExpr); diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 087b8d779..1318852df 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -447,11 +447,36 @@ node! { impl ArrayExpr { /// The array items. - pub fn items(&self) -> impl Iterator + '_ { + pub fn items(&self) -> impl Iterator + '_ { self.0.children().filter_map(RedRef::cast) } } +/// An item in an array expresssion. +#[derive(Debug, Clone, PartialEq, Hash)] +pub enum ArrayItem { + /// A simple value: `12`. + Pos(Expr), + /// A spreaded value: `..things`. + Spread(Expr), +} + +impl TypedNode for ArrayItem { + fn from_red(node: RedRef) -> Option { + match node.kind() { + NodeKind::Spread => node.cast_first_child().map(Self::Spread), + _ => node.cast().map(Self::Pos), + } + } + + fn as_red(&self) -> RedRef<'_> { + match self { + Self::Pos(v) => v.as_red(), + Self::Spread(v) => v.as_red(), + } + } +} + node! { /// A dictionary expression: `(thickness: 3pt, pattern: dashed)`. DictExpr: DictExpr @@ -459,11 +484,37 @@ node! { impl DictExpr { /// The named dictionary items. - pub fn items(&self) -> impl Iterator + '_ { + pub fn items(&self) -> impl Iterator + '_ { self.0.children().filter_map(RedRef::cast) } } +/// An item in an dictionary expresssion. +#[derive(Debug, Clone, PartialEq, Hash)] +pub enum DictItem { + /// A simple named pair: `12`. + Named(Named), + /// A spreaded value: `..things`. + Spread(Expr), +} + +impl TypedNode for DictItem { + fn from_red(node: RedRef) -> Option { + match node.kind() { + NodeKind::Named => node.cast().map(Self::Named), + NodeKind::Spread => node.cast_first_child().map(Self::Spread), + _ => None, + } + } + + fn as_red(&self) -> RedRef<'_> { + match self { + Self::Named(v) => v.as_red(), + Self::Spread(v) => v.as_red(), + } + } +} + node! { /// A pair of a name and an expression: `pattern: dashed`. Named @@ -801,9 +852,9 @@ pub enum CallArg { impl TypedNode for CallArg { fn from_red(node: RedRef) -> Option { match node.kind() { - NodeKind::Named => node.cast().map(CallArg::Named), - NodeKind::Spread => node.cast_first_child().map(CallArg::Spread), - _ => node.cast().map(CallArg::Pos), + NodeKind::Named => node.cast().map(Self::Named), + NodeKind::Spread => node.cast_first_child().map(Self::Spread), + _ => node.cast().map(Self::Pos), } } @@ -816,17 +867,6 @@ impl TypedNode for CallArg { } } -impl CallArg { - /// The name of this argument. - pub fn span(&self) -> Span { - match self { - Self::Pos(expr) => expr.span(), - Self::Named(named) => named.span(), - Self::Spread(expr) => expr.span(), - } - } -} - node! { /// A closure expression: `(x, y) => z`. ClosureExpr: ClosureExpr @@ -870,9 +910,9 @@ pub enum ClosureParam { impl TypedNode for ClosureParam { fn from_red(node: RedRef) -> Option { match node.kind() { - NodeKind::Ident(_) => node.cast().map(ClosureParam::Pos), - NodeKind::Named => node.cast().map(ClosureParam::Named), - NodeKind::Spread => node.cast_first_child().map(ClosureParam::Sink), + NodeKind::Ident(_) => node.cast().map(Self::Pos), + NodeKind::Named => node.cast().map(Self::Named), + NodeKind::Spread => node.cast_first_child().map(Self::Sink), _ => None, } } diff --git a/tests/typ/code/spread.typ b/tests/typ/code/spread.typ index 5f7d20617..c5415c42b 100644 --- a/tests/typ/code/spread.typ +++ b/tests/typ/code/spread.typ @@ -70,9 +70,26 @@ #let f(..a, ..b) = none --- -// Error: 3-6 spreading is not allowed here -{(..x)} +// Test spreading into array and dictionary. +{ + let l = (1, 2, 3) + let r = (5, 6, 7) + test((..l, 4, ..r), range(1, 8)) + test((..none), ()) +} + +{ + let x = (a: 1) + let y = (b: 2) + let z = (a: 3) + test((:..x, ..y, ..z), (a: 3, b: 2)) + test((..(a: 1), b: 2), (a: 1, b: 2)) +} --- -// Error: 9-17 spreading is not allowed here -{(1, 2, ..(1, 2))} +// Error: 11-17 cannot spread dictionary into array +{(1, 2, ..(a: 1))} + +--- +// Error: 5-11 cannot spread array into dictionary +{(..(1, 2), a: 1)}