diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-04-11 15:52:57 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-04-11 15:52:57 +0200 |
| commit | 938b0af889b8fbe6265695b1b8e54aee338ba87f (patch) | |
| tree | 0121d733a556576d147742d460fe4df0d3c8cb1d /src | |
| parent | 790bd536eba76a2a48d61ea6b1bde78cde3d31f3 (diff) | |
Spreading into arrays and dictionaries
Diffstat (limited to 'src')
| -rw-r--r-- | src/eval/mod.rs | 41 | ||||
| -rw-r--r-- | src/parse/mod.rs | 36 | ||||
| -rw-r--r-- | src/syntax/ast.rs | 78 |
3 files changed, 115 insertions, 40 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 3f580178..9e5a8555 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::Output> { - 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::Output> { - 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 28b7f81d..3bf274f2 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<Marker> = 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<NodeKind> { 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 087b8d77..1318852d 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<Item = Expr> + '_ { + pub fn items(&self) -> impl Iterator<Item = ArrayItem> + '_ { 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<Self> { + 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<Item = Named> + '_ { + pub fn items(&self) -> impl Iterator<Item = DictItem> + '_ { 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<Self> { + 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<Self> { 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<Self> { 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, } } |
