Spreading into arrays and dictionaries

This commit is contained in:
Laurenz 2022-04-11 15:52:57 +02:00
parent 790bd536eb
commit 938b0af889
4 changed files with 136 additions and 44 deletions

View File

@ -39,6 +39,8 @@ pub use show::*;
pub use styles::*; pub use styles::*;
pub use value::*; pub use value::*;
use std::collections::BTreeMap;
use parking_lot::{MappedRwLockWriteGuard, RwLockWriteGuard}; use parking_lot::{MappedRwLockWriteGuard, RwLockWriteGuard};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
@ -307,7 +309,21 @@ impl Eval for ArrayExpr {
type Output = Array; type Output = Array;
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { 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; type Output = Dict;
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> { fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
self.items() let mut map = BTreeMap::new();
.map(|x| Ok((x.name().take(), x.expr().eval(ctx, scp)?)))
.collect() 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))
} }
} }

View File

@ -516,7 +516,7 @@ fn parenthesized(p: &mut Parser, atomic: bool) -> ParseResult {
let kind = collection(p).0; let kind = collection(p).0;
p.end_group(); p.end_group();
// Leading colon makes this a (empty) dictionary. // Leading colon makes this a dictionary.
if colon { if colon {
dict(p, marker); dict(p, marker);
return Ok(()); return Ok(());
@ -556,21 +556,23 @@ enum CollectionKind {
/// Returns the length of the collection and whether the literal contained any /// Returns the length of the collection and whether the literal contained any
/// commas. /// commas.
fn collection(p: &mut Parser) -> (CollectionKind, usize) { fn collection(p: &mut Parser) -> (CollectionKind, usize) {
let mut kind = CollectionKind::Positional; let mut kind = None;
let mut items = 0; let mut items = 0;
let mut can_group = true; let mut can_group = true;
let mut error = false;
let mut missing_coma: Option<Marker> = None; let mut missing_coma: Option<Marker> = None;
while !p.eof() { while !p.eof() {
if let Ok(item_kind) = item(p) { if let Ok(item_kind) = item(p) {
if items == 0 && item_kind == NodeKind::Named { match item_kind {
kind = CollectionKind::Named; NodeKind::Spread => can_group = false,
NodeKind::Named if kind.is_none() => {
kind = Some(CollectionKind::Named);
can_group = false; can_group = false;
} }
_ if kind.is_none() => {
if item_kind == NodeKind::Spread { kind = Some(CollectionKind::Positional);
can_group = false; }
_ => {}
} }
items += 1; items += 1;
@ -589,13 +591,15 @@ fn collection(p: &mut Parser) -> (CollectionKind, usize) {
missing_coma = Some(p.trivia_start()); missing_coma = Some(p.trivia_start());
} }
} else { } else {
error = true; kind = Some(CollectionKind::Group);
} }
} }
if error || (can_group && items == 1) { let kind = if can_group && items == 1 {
kind = CollectionKind::Group; CollectionKind::Group
} } else {
kind.unwrap_or(CollectionKind::Positional)
};
(kind, items) (kind, items)
} }
@ -636,7 +640,6 @@ fn item(p: &mut Parser) -> ParseResult<NodeKind> {
fn array(p: &mut Parser, marker: Marker) { fn array(p: &mut Parser, marker: Marker) {
marker.filter_children(p, |x| match x.kind() { marker.filter_children(p, |x| match x.kind() {
NodeKind::Named => Err("expected expression, found named pair"), NodeKind::Named => Err("expected expression, found named pair"),
NodeKind::Spread => Err("spreading is not allowed here"),
_ => Ok(()), _ => Ok(()),
}); });
marker.end(p, NodeKind::ArrayExpr); marker.end(p, NodeKind::ArrayExpr);
@ -647,8 +650,7 @@ fn array(p: &mut Parser, marker: Marker) {
fn dict(p: &mut Parser, marker: Marker) { fn dict(p: &mut Parser, marker: Marker) {
marker.filter_children(p, |x| match x.kind() { marker.filter_children(p, |x| match x.kind() {
kind if kind.is_paren() => Ok(()), kind if kind.is_paren() => Ok(()),
NodeKind::Named | NodeKind::Comma | NodeKind::Colon => Ok(()), NodeKind::Named | NodeKind::Comma | NodeKind::Colon | NodeKind::Spread => Ok(()),
NodeKind::Spread => Err("spreading is not allowed here"),
_ => Err("expected named pair, found expression"), _ => Err("expected named pair, found expression"),
}); });
marker.end(p, NodeKind::DictExpr); marker.end(p, NodeKind::DictExpr);

View File

@ -447,11 +447,36 @@ node! {
impl ArrayExpr { impl ArrayExpr {
/// The array items. /// 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) 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! { node! {
/// A dictionary expression: `(thickness: 3pt, pattern: dashed)`. /// A dictionary expression: `(thickness: 3pt, pattern: dashed)`.
DictExpr: DictExpr DictExpr: DictExpr
@ -459,11 +484,37 @@ node! {
impl DictExpr { impl DictExpr {
/// The named dictionary items. /// 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) 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! { node! {
/// A pair of a name and an expression: `pattern: dashed`. /// A pair of a name and an expression: `pattern: dashed`.
Named Named
@ -801,9 +852,9 @@ pub enum CallArg {
impl TypedNode for CallArg { impl TypedNode for CallArg {
fn from_red(node: RedRef) -> Option<Self> { fn from_red(node: RedRef) -> Option<Self> {
match node.kind() { match node.kind() {
NodeKind::Named => node.cast().map(CallArg::Named), NodeKind::Named => node.cast().map(Self::Named),
NodeKind::Spread => node.cast_first_child().map(CallArg::Spread), NodeKind::Spread => node.cast_first_child().map(Self::Spread),
_ => node.cast().map(CallArg::Pos), _ => 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! { node! {
/// A closure expression: `(x, y) => z`. /// A closure expression: `(x, y) => z`.
ClosureExpr: ClosureExpr ClosureExpr: ClosureExpr
@ -870,9 +910,9 @@ pub enum ClosureParam {
impl TypedNode for ClosureParam { impl TypedNode for ClosureParam {
fn from_red(node: RedRef) -> Option<Self> { fn from_red(node: RedRef) -> Option<Self> {
match node.kind() { match node.kind() {
NodeKind::Ident(_) => node.cast().map(ClosureParam::Pos), NodeKind::Ident(_) => node.cast().map(Self::Pos),
NodeKind::Named => node.cast().map(ClosureParam::Named), NodeKind::Named => node.cast().map(Self::Named),
NodeKind::Spread => node.cast_first_child().map(ClosureParam::Sink), NodeKind::Spread => node.cast_first_child().map(Self::Sink),
_ => None, _ => None,
} }
} }

View File

@ -70,9 +70,26 @@
#let f(..a, ..b) = none #let f(..a, ..b) = none
--- ---
// Error: 3-6 spreading is not allowed here // Test spreading into array and dictionary.
{(..x)} {
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 // Error: 11-17 cannot spread dictionary into array
{(1, 2, ..(1, 2))} {(1, 2, ..(a: 1))}
---
// Error: 5-11 cannot spread array into dictionary
{(..(1, 2), a: 1)}