Keyed pairs

This commit is contained in:
Laurenz 2022-05-04 23:04:19 +02:00
parent e674fd7e90
commit 2a45650dcc
14 changed files with 136 additions and 38 deletions

View File

@ -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() {

View File

@ -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()),

View File

@ -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<Marker> = 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<NodeKind> {
fn item(p: &mut Parser, keyed: bool) -> ParseResult<NodeKind> {
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<NodeKind> {
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<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::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");
}

View File

@ -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<Self> {
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

View File

@ -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,

View File

@ -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 => {}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -92,3 +92,7 @@
// Named pair after this is already identified as an array.
// Error: 6-10 expected expression, found named pair
{(1, b: 2)}
// Keyed pair after this is already identified as an array.
// Error: 6-14 expected expression, found keyed pair
{(1, "key": 2)}

View File

@ -86,6 +86,9 @@
// Error: 7-8 expected identifier
#func(1:2)
// Error: 7-12 expected identifier
#func("abc":2)
// Error: 7-10 expected identifier
{func((x):1)}

View File

@ -143,3 +143,15 @@
// Error: 23-35 unexpected argument
test(greet("Typst", whatever: 10))
}
---
// Error: 6-16 expected identifier, named pair or argument sink
{(a, "named": b) => none}
---
// Error: 10-15 expected identifier
#let foo("key": b) = key
---
// Error: 10-14 expected identifier
#let foo(none: b) = key

View File

@ -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::)}

View File

@ -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}

View File

@ -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 ""

View File

@ -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
---