summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-04-11 15:52:57 +0200
committerLaurenz <laurmaedje@gmail.com>2022-04-11 15:52:57 +0200
commit938b0af889b8fbe6265695b1b8e54aee338ba87f (patch)
tree0121d733a556576d147742d460fe4df0d3c8cb1d /src
parent790bd536eba76a2a48d61ea6b1bde78cde3d31f3 (diff)
Spreading into arrays and dictionaries
Diffstat (limited to 'src')
-rw-r--r--src/eval/mod.rs41
-rw-r--r--src/parse/mod.rs36
-rw-r--r--src/syntax/ast.rs78
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,
}
}