From 2a45650dcc87c3bf047adaf030fd392bbe9fbb5e Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 4 May 2022 23:04:19 +0200 Subject: [PATCH] Keyed pairs --- src/eval/dict.rs | 7 ++++- src/eval/mod.rs | 3 ++ src/parse/mod.rs | 61 ++++++++++++++++++++++--------------- src/syntax/ast.rs | 37 +++++++++++++++++++--- src/syntax/highlight.rs | 1 + src/syntax/mod.rs | 6 +++- tests/ref/code/dict.png | Bin 1973 -> 4764 bytes tests/typ/code/array.typ | 4 +++ tests/typ/code/call.typ | 3 ++ tests/typ/code/closure.typ | 12 ++++++++ tests/typ/code/dict.typ | 18 ++++++++--- tests/typ/code/field.typ | 16 ++++++++++ tests/typ/code/import.typ | 4 +++ tests/typ/code/spread.typ | 2 +- 14 files changed, 136 insertions(+), 38 deletions(-) diff --git a/src/eval/dict.rs b/src/eval/dict.rs index 6b4dbbd77..22b73e769 100644 --- a/src/eval/dict.rs +++ b/src/eval/dict.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use super::{Args, Array, Func, Value}; use crate::diag::{StrResult, TypResult}; +use crate::parse::is_ident; use crate::syntax::Spanned; use crate::util::{ArcExt, EcoString}; use crate::Context; @@ -127,7 +128,11 @@ impl Debug for Dict { f.write_char(':')?; } for (i, (key, value)) in self.iter().enumerate() { - f.write_str(key)?; + if is_ident(key) { + f.write_str(key)?; + } else { + write!(f, "{key:?}")?; + } f.write_str(": ")?; value.fmt(f)?; if i + 1 < self.0.len() { diff --git a/src/eval/mod.rs b/src/eval/mod.rs index e9d62a69c..677fa4aa1 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -335,6 +335,9 @@ impl Eval for DictExpr { DictItem::Named(named) => { map.insert(named.name().take(), named.expr().eval(ctx, scp)?); } + DictItem::Keyed(keyed) => { + map.insert(keyed.key(), keyed.expr().eval(ctx, scp)?); + } DictItem::Spread(expr) => match expr.eval(ctx, scp)? { Value::None => {} Value::Dict(dict) => map.extend(dict.into_iter()), diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 7811482be..8bf7b1c5f 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -526,7 +526,7 @@ fn parenthesized(p: &mut Parser, atomic: bool) -> ParseResult { p.start_group(Group::Paren); let colon = p.eat_if(NodeKind::Colon); - let kind = collection(p).0; + let kind = collection(p, true).0; p.end_group(); // Leading colon makes this a dictionary. @@ -568,14 +568,14 @@ enum CollectionKind { /// /// Returns the length of the collection and whether the literal contained any /// commas. -fn collection(p: &mut Parser) -> (CollectionKind, usize) { +fn collection(p: &mut Parser, keyed: bool) -> (CollectionKind, usize) { let mut kind = None; let mut items = 0; let mut can_group = true; let mut missing_coma: Option = None; while !p.eof() { - if let Ok(item_kind) = item(p) { + if let Ok(item_kind) = item(p, keyed) { match item_kind { NodeKind::Spread => can_group = false, NodeKind::Named if kind.is_none() => { @@ -619,7 +619,7 @@ fn collection(p: &mut Parser) -> (CollectionKind, usize) { /// Parse an expression or a named pair, returning whether it's a spread or a /// named pair. -fn item(p: &mut Parser) -> ParseResult { +fn item(p: &mut Parser, keyed: bool) -> ParseResult { let marker = p.marker(); if p.eat_if(NodeKind::Dots) { marker.perform(p, NodeKind::Spread, expr)?; @@ -629,18 +629,27 @@ fn item(p: &mut Parser) -> ParseResult { expr(p)?; if p.at(NodeKind::Colon) { - marker.perform(p, NodeKind::Named, |p| { - if let Some(NodeKind::Ident(_)) = marker.after(p).map(|c| c.kind()) { + match marker.after(p).map(|c| c.kind()) { + Some(NodeKind::Ident(_)) => { p.eat(); - expr(p) - } else { - let error = NodeKind::Error(ErrorPos::Full, "expected identifier".into()); + marker.perform(p, NodeKind::Named, expr)?; + } + Some(NodeKind::Str(_)) if keyed => { + p.eat(); + marker.perform(p, NodeKind::Keyed, expr)?; + } + _ => { + let mut msg = EcoString::from("expected identifier"); + if keyed { + msg.push_str(" or string"); + } + let error = NodeKind::Error(ErrorPos::Full, msg); marker.end(p, error); p.eat(); - expr(p).ok(); - Err(ParseError) + marker.perform(p, NodeKind::Named, expr).ok(); + return Err(ParseError); } - })?; + } Ok(NodeKind::Named) } else { @@ -653,6 +662,7 @@ 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::Keyed => Err("expected expression, found keyed pair"), _ => Ok(()), }); marker.end(p, NodeKind::ArrayExpr); @@ -664,18 +674,18 @@ fn dict(p: &mut Parser, marker: Marker) { let mut used = HashSet::new(); marker.filter_children(p, |x| match x.kind() { kind if kind.is_paren() => Ok(()), - NodeKind::Named => { - if let Some(NodeKind::Ident(ident)) = + NodeKind::Named | NodeKind::Keyed => { + if let Some(NodeKind::Ident(key) | NodeKind::Str(key)) = x.children().first().map(|child| child.kind()) { - if !used.insert(ident.clone()) { + if !used.insert(key.clone()) { return Err("pair has duplicate key"); } } Ok(()) } - NodeKind::Comma | NodeKind::Colon | NodeKind::Spread => Ok(()), - _ => Err("expected named pair, found expression"), + NodeKind::Spread | NodeKind::Comma | NodeKind::Colon => Ok(()), + _ => Err("expected named or keyed pair, found expression"), }); marker.end(p, NodeKind::DictExpr); } @@ -685,7 +695,7 @@ fn dict(p: &mut Parser, marker: Marker) { fn params(p: &mut Parser, marker: Marker) { marker.filter_children(p, |x| match x.kind() { kind if kind.is_paren() => Ok(()), - NodeKind::Named | NodeKind::Comma | NodeKind::Ident(_) => Ok(()), + NodeKind::Named | NodeKind::Ident(_) | NodeKind::Comma => Ok(()), NodeKind::Spread if matches!( x.children().last().map(|child| child.kind()), @@ -694,7 +704,7 @@ fn params(p: &mut Parser, marker: Marker) { { Ok(()) } - _ => Err("expected identifier"), + _ => Err("expected identifier, named pair or argument sink"), }); marker.end(p, NodeKind::ClosureParams); } @@ -746,12 +756,12 @@ fn args(p: &mut Parser, direct: bool, brackets: bool) -> ParseResult { if p.at(NodeKind::LeftParen) { let marker = p.marker(); p.start_group(Group::Paren); - collection(p); + collection(p, false); p.end_group(); let mut used = HashSet::new(); - marker.filter_children(p, |x| { - if x.kind() == &NodeKind::Named { + marker.filter_children(p, |x| match x.kind() { + NodeKind::Named => { if let Some(NodeKind::Ident(ident)) = x.children().first().map(|child| child.kind()) { @@ -759,8 +769,9 @@ fn args(p: &mut Parser, direct: bool, brackets: bool) -> ParseResult { return Err("duplicate argument"); } } + Ok(()) } - Ok(()) + _ => Ok(()), }); } @@ -785,7 +796,7 @@ fn let_expr(p: &mut Parser) -> ParseResult { if has_params { let marker = p.marker(); p.start_group(Group::Paren); - collection(p); + collection(p, false); p.end_group(); params(p, marker); } @@ -904,7 +915,7 @@ fn import_expr(p: &mut Parser) -> ParseResult { p.perform(NodeKind::ImportItems, |p| { p.start_group(Group::Imports); let marker = p.marker(); - let items = collection(p).1; + let items = collection(p, false).1; if items == 0 { p.expected("import items"); } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index fa00fe4ba..1add2fd67 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -472,7 +472,7 @@ impl ArrayExpr { /// An item in an array expresssion. #[derive(Debug, Clone, PartialEq, Hash)] pub enum ArrayItem { - /// A simple value: `12`. + /// A bare value: `12`. Pos(Expr), /// A spreaded value: `..things`. Spread(Expr), @@ -509,8 +509,10 @@ impl DictExpr { /// An item in an dictionary expresssion. #[derive(Debug, Clone, PartialEq, Hash)] pub enum DictItem { - /// A simple named pair: `12`. + /// A named pair: `thickness: 3pt`. Named(Named), + /// A keyed pair: `"spaced key": true`. + Keyed(Keyed), /// A spreaded value: `..things`. Spread(Expr), } @@ -519,6 +521,7 @@ impl TypedNode for DictItem { fn from_red(node: RedRef) -> Option { match node.kind() { NodeKind::Named => node.cast().map(Self::Named), + NodeKind::Keyed => node.cast().map(Self::Keyed), NodeKind::Spread => node.cast_first_child().map(Self::Spread), _ => None, } @@ -527,28 +530,52 @@ impl TypedNode for DictItem { fn as_red(&self) -> RedRef<'_> { match self { Self::Named(v) => v.as_red(), + Self::Keyed(v) => v.as_red(), Self::Spread(v) => v.as_red(), } } } node! { - /// A pair of a name and an expression: `pattern: dashed`. + /// A pair of a name and an expression: `thickness: 3pt`. Named } impl Named { - /// The name: `pattern`. + /// The name: `thickness`. pub fn name(&self) -> Ident { self.0.cast_first_child().expect("named pair is missing name") } - /// The right-hand side of the pair: `dashed`. + /// The right-hand side of the pair: `3pt`. pub fn expr(&self) -> Expr { self.0.cast_last_child().expect("named pair is missing expression") } } +node! { + /// A pair of a string key and an expression: `"spaced key": true`. + Keyed +} + +impl Keyed { + /// The key: `"spaced key"`. + pub fn key(&self) -> EcoString { + self.0 + .children() + .find_map(|node| match node.kind() { + NodeKind::Str(key) => Some(key.clone()), + _ => None, + }) + .expect("keyed pair is missing key") + } + + /// The right-hand side of the pair: `true`. + pub fn expr(&self) -> Expr { + self.0.cast_last_child().expect("keyed pair is missing expression") + } +} + node! { /// A unary operation: `-x`. UnaryExpr: UnaryExpr diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index dae379acb..06e88691c 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -215,6 +215,7 @@ impl Category { NodeKind::ArrayExpr => None, NodeKind::DictExpr => None, NodeKind::Named => None, + NodeKind::Keyed => None, NodeKind::UnaryExpr => None, NodeKind::BinaryExpr => None, NodeKind::FieldAccess => None, diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index d21597ffd..09d7265d8 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -648,6 +648,8 @@ pub enum NodeKind { DictExpr, /// A named pair: `thickness: 3pt`. Named, + /// A keyed pair: `"spaced key": true`. + Keyed, /// A unary operation: `-x`. UnaryExpr, /// A binary operation: `a + b`. @@ -896,7 +898,8 @@ impl NodeKind { Self::GroupExpr => "group", Self::ArrayExpr => "array", Self::DictExpr => "dictionary", - Self::Named => "named argument", + Self::Named => "named pair", + Self::Keyed => "keyed pair", Self::UnaryExpr => "unary expression", Self::BinaryExpr => "binary expression", Self::FieldAccess => "field access", @@ -1021,6 +1024,7 @@ impl Hash for NodeKind { Self::ArrayExpr => {} Self::DictExpr => {} Self::Named => {} + Self::Keyed => {} Self::UnaryExpr => {} Self::BinaryExpr => {} Self::FieldAccess => {} diff --git a/tests/ref/code/dict.png b/tests/ref/code/dict.png index 28e44999d9be8c25ef16d187a30bdb6097d71603..d7ade71d60185617a977a00540d29b57b53ef50e 100644 GIT binary patch literal 4764 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 delta 1972 zcmV;l2TS;zCAANb7k_XF0{{R3&l3Jy0008eP)t-s|NsC0{{H^{{{Q{{{r&y^`~3d; z{QLX+{rUU)`uhF&`uX|!{r36z`1t(u_xASo`||en_4WJj^z-xc`tJ1d^78QT@cHZV z`ReiS@9+8P@b2#J?d|RC?Ck67>-XgC_v7s9>gws~>G$F5=YQwt=H}+*<>mF<=;Y+& zW+}qpR+1c6G*VpgJ+waEN)YR0| z)6>$@($CM&%*@Q|y3xwY%E`&e>bK9x$jHaX$Lh4r>9fto#>T_L!@|PC!NI}jtjNH? zz`eb_yu7@+x_`Rlpuo4cx8j?SVo155prr39-nVFf_b)=V7 zb8~WXa>+u1aBy(QKY(s-Zf$LCYinz2YHDa`XvH*nXJ=<-W@cq&Wn^Sz!!C7TU|_;3 za$jFx!G9@oUS3{ZU0qyUT)-r5TU%RNT3T3GSXWn9RaI3~R8+khXj4;Dy%}dxQc_V- zQM?vqP*7025@Na#VM|L(N=iz(4PUtoUbzciNJvOWM@L3RMny$Mw*^{4LPE9zShfLI zKtMo0KR-P^Jv=--IyyQyI5;;qH#9UfGcz+XGJi5IE-oxAEGjB0DJdx@CnqK*CM6{$ zBqSswBO@OlA08ea8X6iI85tND7#0>56%`c|6B7~=5)ly*5D*X#4-XCw4h;tUOHYlg@i(J%-H%YVbo zlpizF%+R5kj^zGr zfWI*U0SG_<0uX=z1Rwwb2tWV=5PyIG1Rwwb2pkcz{*!yvGP8Ihl>Cye9yD1jX1xmy z7&7FPNN&tJ@w!2J$DDob6{q>L^rI#G5EghewrIuGG4`m=bz={g7(z5nIdnI0s*(>ve0z?RUjNF**QWR~$FCWrK!{zkBvy6evd^v}eS~1ognZwFu$hev-}C%C zeGkG5u1oxNPJ28Tgu-T1Ab(pYw(dPDZuH5dkrE-Fv3%8u=Uzto2#TJ8*e;$IzGom7 zJI@MyJ2w|{LD;%4kc{*cnaNbjrUhyR!qm>rshTJ2A%ylM8rt-uPEVr2pS&Zb`?hjH zsM{`OzWo`gtXPdeNlA242%$ZRHi-A=^dx%Pd8_=nspEpsSk|qyuz$502;ri4UyQ+? zw2+nQ9zM(UjGUkR4!VdC4s4RLn<@_f>1~fLLP*br;(NL)uvyL*gDbF22e=?Oq+iGz z|M}setcg>iiqO5MsZ?C4*Kxp;&c_vHm4gey3TbOnDZ3!*k3+YfXMEH#SFhpeFMoHJ zvUacFf-t@_uqxZYx_^GVCTSs9*RQwgBGlB+SIsMX#?`dtD5f-f#=O4k7G<5*Ii3pw z9a`gm%@)7D%{KqTpT1v2bd4YgAB#H$K^WRKHCGhX@8RWkyM3v1x!rCb+*Mx6#0=Fj z%ARZ49>Ev>jr5v5pKz_V`{E9Nz2*?{LC9-K@h1hpGgcoz{eSQ=qG`vZi0hiMdA;gA zjW>KDMU3x*!}e-4SM)5AT7F7ldp;($eAQQ`wB&I@pz2y_&6pH!k!rE%Ui2lFt2y3> zp60r0;)RgdP-db&$LGHdqUeUHydjYr0wrwA?fWfnj{1Mk none} + +--- +// Error: 10-15 expected identifier +#let foo("key": b) = key + +--- +// Error: 10-14 expected identifier +#let foo(none: b) = key diff --git a/tests/typ/code/dict.typ b/tests/typ/code/dict.typ index 23af145f3..00c78c17b 100644 --- a/tests/typ/code/dict.typ +++ b/tests/typ/code/dict.typ @@ -7,8 +7,12 @@ // Empty {(:)} -// Two pairs. -{(a1: 1, a2: 2)} +// Two pairs and string key. +#let dict = (normal: 1, "spaced key": 2) +#dict + +#test(dict.normal, 1) +#test(dict("spaced key"), 2) --- // Test lvalue and rvalue access. @@ -39,14 +43,18 @@ // Error: 24-32 pair has duplicate key {(first: 1, second: 2, first: 3)} +--- +// Error: 17-23 pair has duplicate key +{(a: 1, "b": 2, "a": 3)} + --- // Simple expression after already being identified as a dictionary. -// Error: 9-10 expected named pair, found expression +// Error: 9-10 expected named or keyed pair, found expression {(a: 1, b)} // Identified as dictionary due to initial colon. -// Error: 4-5 expected named pair, found expression +// Error: 4-5 expected named or keyed pair, found expression // Error: 5 expected comma -// Error: 12-16 expected identifier +// Error: 12-16 expected identifier or string // Error: 17-18 expected expression, found colon {(:1 b:"", true::)} diff --git a/tests/typ/code/field.typ b/tests/typ/code/field.typ index abccaedee..b63a8768f 100644 --- a/tests/typ/code/field.typ +++ b/tests/typ/code/field.typ @@ -2,6 +2,7 @@ // Ref: false --- +// Test field on dictionary. #let dict = (nothing: "ness", hello: "world") #test(dict.nothing, "ness") { @@ -11,6 +12,16 @@ test(world, "world") } +--- +// Test field on node. +#show node: list as { + test(node.items.len(), 3) +} + +- A +- B +- C + --- // Error: 6-13 dictionary does not contain key "invalid" {(:).invalid} @@ -19,6 +30,11 @@ // Error: 2-7 cannot access field on boolean {false.ok} +--- +// Error: 29-32 unknown field "fun" +#show node: heading as node.fun += A + --- // Error: 8-12 expected identifier, found boolean {false.true} diff --git a/tests/typ/code/import.typ b/tests/typ/code/import.typ index 683bb52a3..312ee6768 100644 --- a/tests/typ/code/import.typ +++ b/tests/typ/code/import.typ @@ -115,3 +115,7 @@ This is never reached. // Should output `, a from "target.typ"`. // Error: 10 expected keyword `from` #import *, a from "target.typ" + +--- +// Error: 9-13 expected identifier +#import a: 1 from "" diff --git a/tests/typ/code/spread.typ b/tests/typ/code/spread.typ index c5415c42b..a41e04b9d 100644 --- a/tests/typ/code/spread.typ +++ b/tests/typ/code/spread.typ @@ -62,7 +62,7 @@ #min(.."nope") --- -// Error: 8-14 expected identifier +// Error: 8-14 expected identifier, named pair or argument sink #let f(..true) = none ---