mirror of
https://github.com/typst/typst
synced 2025-05-13 12:36:23 +08:00
Keyed pairs
This commit is contained in:
parent
e674fd7e90
commit
2a45650dcc
@ -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() {
|
||||
|
@ -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()),
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 |
@ -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)}
|
||||
|
@ -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)}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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::)}
|
||||
|
@ -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}
|
||||
|
@ -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 ""
|
||||
|
@ -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
|
||||
|
||||
---
|
||||
|
Loading…
x
Reference in New Issue
Block a user