mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Spreading into arrays and dictionaries
This commit is contained in:
parent
790bd536eb
commit
938b0af889
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
can_group = false;
|
NodeKind::Named if kind.is_none() => {
|
||||||
}
|
kind = Some(CollectionKind::Named);
|
||||||
|
can_group = false;
|
||||||
if item_kind == NodeKind::Spread {
|
}
|
||||||
can_group = false;
|
_ if kind.is_none() => {
|
||||||
|
kind = Some(CollectionKind::Positional);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user