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 super::{Args, Array, Func, Value};
use crate::diag::{StrResult, TypResult}; use crate::diag::{StrResult, TypResult};
use crate::parse::is_ident;
use crate::syntax::Spanned; use crate::syntax::Spanned;
use crate::util::{ArcExt, EcoString}; use crate::util::{ArcExt, EcoString};
use crate::Context; use crate::Context;
@ -127,7 +128,11 @@ impl Debug for Dict {
f.write_char(':')?; f.write_char(':')?;
} }
for (i, (key, value)) in self.iter().enumerate() { for (i, (key, value)) in self.iter().enumerate() {
if is_ident(key) {
f.write_str(key)?; f.write_str(key)?;
} else {
write!(f, "{key:?}")?;
}
f.write_str(": ")?; f.write_str(": ")?;
value.fmt(f)?; value.fmt(f)?;
if i + 1 < self.0.len() { if i + 1 < self.0.len() {

View File

@ -335,6 +335,9 @@ impl Eval for DictExpr {
DictItem::Named(named) => { DictItem::Named(named) => {
map.insert(named.name().take(), named.expr().eval(ctx, scp)?); 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)? { DictItem::Spread(expr) => match expr.eval(ctx, scp)? {
Value::None => {} Value::None => {}
Value::Dict(dict) => map.extend(dict.into_iter()), 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); p.start_group(Group::Paren);
let colon = p.eat_if(NodeKind::Colon); let colon = p.eat_if(NodeKind::Colon);
let kind = collection(p).0; let kind = collection(p, true).0;
p.end_group(); p.end_group();
// Leading colon makes this a dictionary. // Leading colon makes this a dictionary.
@ -568,14 +568,14 @@ 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, keyed: bool) -> (CollectionKind, usize) {
let mut kind = None; let mut kind = None;
let mut items = 0; let mut items = 0;
let mut can_group = true; let mut can_group = true;
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, keyed) {
match item_kind { match item_kind {
NodeKind::Spread => can_group = false, NodeKind::Spread => can_group = false,
NodeKind::Named if kind.is_none() => { 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 /// Parse an expression or a named pair, returning whether it's a spread or a
/// named pair. /// named pair.
fn item(p: &mut Parser) -> ParseResult<NodeKind> { fn item(p: &mut Parser, keyed: bool) -> ParseResult<NodeKind> {
let marker = p.marker(); let marker = p.marker();
if p.eat_if(NodeKind::Dots) { if p.eat_if(NodeKind::Dots) {
marker.perform(p, NodeKind::Spread, expr)?; marker.perform(p, NodeKind::Spread, expr)?;
@ -629,18 +629,27 @@ fn item(p: &mut Parser) -> ParseResult<NodeKind> {
expr(p)?; expr(p)?;
if p.at(NodeKind::Colon) { if p.at(NodeKind::Colon) {
marker.perform(p, NodeKind::Named, |p| { match marker.after(p).map(|c| c.kind()) {
if let Some(NodeKind::Ident(_)) = marker.after(p).map(|c| c.kind()) { Some(NodeKind::Ident(_)) => {
p.eat(); p.eat();
expr(p) marker.perform(p, NodeKind::Named, expr)?;
} else { }
let error = NodeKind::Error(ErrorPos::Full, "expected identifier".into()); 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); marker.end(p, error);
p.eat(); p.eat();
expr(p).ok(); marker.perform(p, NodeKind::Named, expr).ok();
Err(ParseError) return Err(ParseError);
}
} }
})?;
Ok(NodeKind::Named) Ok(NodeKind::Named)
} else { } else {
@ -653,6 +662,7 @@ 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::Keyed => Err("expected expression, found keyed pair"),
_ => Ok(()), _ => Ok(()),
}); });
marker.end(p, NodeKind::ArrayExpr); marker.end(p, NodeKind::ArrayExpr);
@ -664,18 +674,18 @@ fn dict(p: &mut Parser, marker: Marker) {
let mut used = HashSet::new(); let mut used = HashSet::new();
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::Named | NodeKind::Keyed => {
if let Some(NodeKind::Ident(ident)) = if let Some(NodeKind::Ident(key) | NodeKind::Str(key)) =
x.children().first().map(|child| child.kind()) x.children().first().map(|child| child.kind())
{ {
if !used.insert(ident.clone()) { if !used.insert(key.clone()) {
return Err("pair has duplicate key"); return Err("pair has duplicate key");
} }
} }
Ok(()) Ok(())
} }
NodeKind::Comma | NodeKind::Colon | NodeKind::Spread => Ok(()), NodeKind::Spread | NodeKind::Comma | NodeKind::Colon => Ok(()),
_ => Err("expected named pair, found expression"), _ => Err("expected named or keyed pair, found expression"),
}); });
marker.end(p, NodeKind::DictExpr); marker.end(p, NodeKind::DictExpr);
} }
@ -685,7 +695,7 @@ fn dict(p: &mut Parser, marker: Marker) {
fn params(p: &mut Parser, marker: Marker) { fn params(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::Ident(_) => Ok(()), NodeKind::Named | NodeKind::Ident(_) | NodeKind::Comma => Ok(()),
NodeKind::Spread NodeKind::Spread
if matches!( if matches!(
x.children().last().map(|child| child.kind()), x.children().last().map(|child| child.kind()),
@ -694,7 +704,7 @@ fn params(p: &mut Parser, marker: Marker) {
{ {
Ok(()) Ok(())
} }
_ => Err("expected identifier"), _ => Err("expected identifier, named pair or argument sink"),
}); });
marker.end(p, NodeKind::ClosureParams); marker.end(p, NodeKind::ClosureParams);
} }
@ -746,12 +756,12 @@ fn args(p: &mut Parser, direct: bool, brackets: bool) -> ParseResult {
if p.at(NodeKind::LeftParen) { if p.at(NodeKind::LeftParen) {
let marker = p.marker(); let marker = p.marker();
p.start_group(Group::Paren); p.start_group(Group::Paren);
collection(p); collection(p, false);
p.end_group(); p.end_group();
let mut used = HashSet::new(); let mut used = HashSet::new();
marker.filter_children(p, |x| { marker.filter_children(p, |x| match x.kind() {
if x.kind() == &NodeKind::Named { NodeKind::Named => {
if let Some(NodeKind::Ident(ident)) = if let Some(NodeKind::Ident(ident)) =
x.children().first().map(|child| child.kind()) 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"); return Err("duplicate argument");
} }
} }
}
Ok(()) Ok(())
}
_ => Ok(()),
}); });
} }
@ -785,7 +796,7 @@ fn let_expr(p: &mut Parser) -> ParseResult {
if has_params { if has_params {
let marker = p.marker(); let marker = p.marker();
p.start_group(Group::Paren); p.start_group(Group::Paren);
collection(p); collection(p, false);
p.end_group(); p.end_group();
params(p, marker); params(p, marker);
} }
@ -904,7 +915,7 @@ fn import_expr(p: &mut Parser) -> ParseResult {
p.perform(NodeKind::ImportItems, |p| { p.perform(NodeKind::ImportItems, |p| {
p.start_group(Group::Imports); p.start_group(Group::Imports);
let marker = p.marker(); let marker = p.marker();
let items = collection(p).1; let items = collection(p, false).1;
if items == 0 { if items == 0 {
p.expected("import items"); p.expected("import items");
} }

View File

@ -472,7 +472,7 @@ impl ArrayExpr {
/// An item in an array expresssion. /// An item in an array expresssion.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
pub enum ArrayItem { pub enum ArrayItem {
/// A simple value: `12`. /// A bare value: `12`.
Pos(Expr), Pos(Expr),
/// A spreaded value: `..things`. /// A spreaded value: `..things`.
Spread(Expr), Spread(Expr),
@ -509,8 +509,10 @@ impl DictExpr {
/// An item in an dictionary expresssion. /// An item in an dictionary expresssion.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
pub enum DictItem { pub enum DictItem {
/// A simple named pair: `12`. /// A named pair: `thickness: 3pt`.
Named(Named), Named(Named),
/// A keyed pair: `"spaced key": true`.
Keyed(Keyed),
/// A spreaded value: `..things`. /// A spreaded value: `..things`.
Spread(Expr), Spread(Expr),
} }
@ -519,6 +521,7 @@ impl TypedNode for DictItem {
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(Self::Named), NodeKind::Named => node.cast().map(Self::Named),
NodeKind::Keyed => node.cast().map(Self::Keyed),
NodeKind::Spread => node.cast_first_child().map(Self::Spread), NodeKind::Spread => node.cast_first_child().map(Self::Spread),
_ => None, _ => None,
} }
@ -527,28 +530,52 @@ impl TypedNode for DictItem {
fn as_red(&self) -> RedRef<'_> { fn as_red(&self) -> RedRef<'_> {
match self { match self {
Self::Named(v) => v.as_red(), Self::Named(v) => v.as_red(),
Self::Keyed(v) => v.as_red(),
Self::Spread(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: `thickness: 3pt`.
Named Named
} }
impl Named { impl Named {
/// The name: `pattern`. /// The name: `thickness`.
pub fn name(&self) -> Ident { pub fn name(&self) -> Ident {
self.0.cast_first_child().expect("named pair is missing name") 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 { pub fn expr(&self) -> Expr {
self.0.cast_last_child().expect("named pair is missing expression") 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! { node! {
/// A unary operation: `-x`. /// A unary operation: `-x`.
UnaryExpr: UnaryExpr UnaryExpr: UnaryExpr

View File

@ -215,6 +215,7 @@ impl Category {
NodeKind::ArrayExpr => None, NodeKind::ArrayExpr => None,
NodeKind::DictExpr => None, NodeKind::DictExpr => None,
NodeKind::Named => None, NodeKind::Named => None,
NodeKind::Keyed => None,
NodeKind::UnaryExpr => None, NodeKind::UnaryExpr => None,
NodeKind::BinaryExpr => None, NodeKind::BinaryExpr => None,
NodeKind::FieldAccess => None, NodeKind::FieldAccess => None,

View File

@ -648,6 +648,8 @@ pub enum NodeKind {
DictExpr, DictExpr,
/// A named pair: `thickness: 3pt`. /// A named pair: `thickness: 3pt`.
Named, Named,
/// A keyed pair: `"spaced key": true`.
Keyed,
/// A unary operation: `-x`. /// A unary operation: `-x`.
UnaryExpr, UnaryExpr,
/// A binary operation: `a + b`. /// A binary operation: `a + b`.
@ -896,7 +898,8 @@ impl NodeKind {
Self::GroupExpr => "group", Self::GroupExpr => "group",
Self::ArrayExpr => "array", Self::ArrayExpr => "array",
Self::DictExpr => "dictionary", Self::DictExpr => "dictionary",
Self::Named => "named argument", Self::Named => "named pair",
Self::Keyed => "keyed pair",
Self::UnaryExpr => "unary expression", Self::UnaryExpr => "unary expression",
Self::BinaryExpr => "binary expression", Self::BinaryExpr => "binary expression",
Self::FieldAccess => "field access", Self::FieldAccess => "field access",
@ -1021,6 +1024,7 @@ impl Hash for NodeKind {
Self::ArrayExpr => {} Self::ArrayExpr => {}
Self::DictExpr => {} Self::DictExpr => {}
Self::Named => {} Self::Named => {}
Self::Keyed => {}
Self::UnaryExpr => {} Self::UnaryExpr => {}
Self::BinaryExpr => {} Self::BinaryExpr => {}
Self::FieldAccess => {} 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. // Named pair after this is already identified as an array.
// Error: 6-10 expected expression, found named pair // Error: 6-10 expected expression, found named pair
{(1, b: 2)} {(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 // Error: 7-8 expected identifier
#func(1:2) #func(1:2)
// Error: 7-12 expected identifier
#func("abc":2)
// Error: 7-10 expected identifier // Error: 7-10 expected identifier
{func((x):1)} {func((x):1)}

View File

@ -143,3 +143,15 @@
// Error: 23-35 unexpected argument // Error: 23-35 unexpected argument
test(greet("Typst", whatever: 10)) 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 // Empty
{(:)} {(:)}
// Two pairs. // Two pairs and string key.
{(a1: 1, a2: 2)} #let dict = (normal: 1, "spaced key": 2)
#dict
#test(dict.normal, 1)
#test(dict("spaced key"), 2)
--- ---
// Test lvalue and rvalue access. // Test lvalue and rvalue access.
@ -39,14 +43,18 @@
// Error: 24-32 pair has duplicate key // Error: 24-32 pair has duplicate key
{(first: 1, second: 2, first: 3)} {(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. // 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)} {(a: 1, b)}
// Identified as dictionary due to initial colon. // 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: 5 expected comma
// Error: 12-16 expected identifier // Error: 12-16 expected identifier or string
// Error: 17-18 expected expression, found colon // Error: 17-18 expected expression, found colon
{(:1 b:"", true::)} {(:1 b:"", true::)}

View File

@ -2,6 +2,7 @@
// Ref: false // Ref: false
--- ---
// Test field on dictionary.
#let dict = (nothing: "ness", hello: "world") #let dict = (nothing: "ness", hello: "world")
#test(dict.nothing, "ness") #test(dict.nothing, "ness")
{ {
@ -11,6 +12,16 @@
test(world, "world") 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" // Error: 6-13 dictionary does not contain key "invalid"
{(:).invalid} {(:).invalid}
@ -19,6 +30,11 @@
// Error: 2-7 cannot access field on boolean // Error: 2-7 cannot access field on boolean
{false.ok} {false.ok}
---
// Error: 29-32 unknown field "fun"
#show node: heading as node.fun
= A
--- ---
// Error: 8-12 expected identifier, found boolean // Error: 8-12 expected identifier, found boolean
{false.true} {false.true}

View File

@ -115,3 +115,7 @@ This is never reached.
// Should output `, a from "target.typ"`. // Should output `, a from "target.typ"`.
// Error: 10 expected keyword `from` // Error: 10 expected keyword `from`
#import *, a from "target.typ" #import *, a from "target.typ"
---
// Error: 9-13 expected identifier
#import a: 1 from ""

View File

@ -62,7 +62,7 @@
#min(.."nope") #min(.."nope")
--- ---
// Error: 8-14 expected identifier // Error: 8-14 expected identifier, named pair or argument sink
#let f(..true) = none #let f(..true) = none
--- ---