mirror of
https://github.com/typst/typst
synced 2025-06-28 16:22:53 +08:00
Implement dict key interpolation (#2559)
This commit is contained in:
parent
b716700b61
commit
8fd546760c
@ -1270,11 +1270,8 @@ node! {
|
|||||||
|
|
||||||
impl<'a> Keyed<'a> {
|
impl<'a> Keyed<'a> {
|
||||||
/// The key: `"spacy key"`.
|
/// The key: `"spacy key"`.
|
||||||
pub fn key(self) -> Str<'a> {
|
pub fn key(self) -> Expr<'a> {
|
||||||
self.0
|
self.0.cast_first_match().unwrap_or_default()
|
||||||
.children()
|
|
||||||
.find_map(|node| node.cast::<Str>())
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The right-hand side of the pair: `true`.
|
/// The right-hand side of the pair: `true`.
|
||||||
|
@ -943,19 +943,18 @@ fn item(p: &mut Parser, keyed: bool) -> SyntaxKind {
|
|||||||
|
|
||||||
let kind = match p.node(m).map(SyntaxNode::kind) {
|
let kind = match p.node(m).map(SyntaxNode::kind) {
|
||||||
Some(SyntaxKind::Ident) => SyntaxKind::Named,
|
Some(SyntaxKind::Ident) => SyntaxKind::Named,
|
||||||
Some(SyntaxKind::Str) if keyed => SyntaxKind::Keyed,
|
Some(_) if keyed => SyntaxKind::Keyed,
|
||||||
_ => {
|
_ => {
|
||||||
for child in p.post_process(m) {
|
for child in p.post_process(m) {
|
||||||
if child.kind() == SyntaxKind::Colon {
|
if child.kind() == SyntaxKind::Colon {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut message = EcoString::from("expected identifier");
|
let expected = if keyed { "expression" } else { "identifier" };
|
||||||
if keyed {
|
let message = eco_format!(
|
||||||
message.push_str(" or string");
|
"expected {expected}, found {found}",
|
||||||
}
|
found = child.kind().name(),
|
||||||
message.push_str(", found ");
|
);
|
||||||
message.push_str(child.kind().name());
|
|
||||||
child.convert_to_error(message);
|
child.convert_to_error(message);
|
||||||
}
|
}
|
||||||
SyntaxKind::Named
|
SyntaxKind::Named
|
||||||
@ -1235,9 +1234,12 @@ fn validate_dict<'a>(children: impl Iterator<Item = &'a mut SyntaxNode>) {
|
|||||||
match child.kind() {
|
match child.kind() {
|
||||||
SyntaxKind::Named | SyntaxKind::Keyed => {
|
SyntaxKind::Named | SyntaxKind::Keyed => {
|
||||||
let Some(first) = child.children_mut().first_mut() else { continue };
|
let Some(first) = child.children_mut().first_mut() else { continue };
|
||||||
let key = match first.cast::<ast::Str>() {
|
let key = if let Some(str) = first.cast::<ast::Str>() {
|
||||||
Some(str) => str.get(),
|
str.get()
|
||||||
None => first.text().clone(),
|
} else if let Some(ident) = first.cast::<ast::Ident>() {
|
||||||
|
ident.get().clone()
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if !used.insert(key.clone()) {
|
if !used.insert(key.clone()) {
|
||||||
|
@ -1012,13 +1012,22 @@ impl Eval for ast::Dict<'_> {
|
|||||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
let mut map = indexmap::IndexMap::new();
|
let mut map = indexmap::IndexMap::new();
|
||||||
|
|
||||||
|
let mut invalid_keys = eco_vec![];
|
||||||
|
|
||||||
for item in self.items() {
|
for item in self.items() {
|
||||||
match item {
|
match item {
|
||||||
ast::DictItem::Named(named) => {
|
ast::DictItem::Named(named) => {
|
||||||
map.insert(named.name().get().clone().into(), named.expr().eval(vm)?);
|
map.insert(named.name().get().clone().into(), named.expr().eval(vm)?);
|
||||||
}
|
}
|
||||||
ast::DictItem::Keyed(keyed) => {
|
ast::DictItem::Keyed(keyed) => {
|
||||||
map.insert(keyed.key().get().into(), keyed.expr().eval(vm)?);
|
let raw_key = keyed.key();
|
||||||
|
let key = raw_key.eval(vm)?;
|
||||||
|
let key = key.cast::<Str>().unwrap_or_else(|error| {
|
||||||
|
let error = SourceDiagnostic::error(raw_key.span(), error);
|
||||||
|
invalid_keys.push(error);
|
||||||
|
Str::default()
|
||||||
|
});
|
||||||
|
map.insert(key, keyed.expr().eval(vm)?);
|
||||||
}
|
}
|
||||||
ast::DictItem::Spread(expr) => match expr.eval(vm)? {
|
ast::DictItem::Spread(expr) => match expr.eval(vm)? {
|
||||||
Value::None => {}
|
Value::None => {}
|
||||||
@ -1028,6 +1037,10 @@ impl Eval for ast::Dict<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !invalid_keys.is_empty() {
|
||||||
|
return Err(invalid_keys);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(map.into())
|
Ok(map.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,15 +92,12 @@
|
|||||||
#(a: 1, b)
|
#(a: 1, b)
|
||||||
|
|
||||||
// Identified as dictionary due to initial colon.
|
// Identified as dictionary due to initial colon.
|
||||||
|
// The boolean key is allowed for now since it will only cause an error at the evaluation stage.
|
||||||
// Error: 4-5 expected named or keyed pair, found integer
|
// Error: 4-5 expected named or keyed pair, found integer
|
||||||
// Error: 5 expected comma
|
// Error: 5 expected comma
|
||||||
// Error: 12-16 expected identifier or string, found boolean
|
|
||||||
// Error: 17 expected expression
|
// Error: 17 expected expression
|
||||||
#(:1 b:"", true:)
|
#(:1 b:"", true:)
|
||||||
|
|
||||||
// Error: 3-8 expected identifier or string, found binary expression
|
|
||||||
#(a + b: "hey")
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 3-15 cannot mutate a temporary value
|
// Error: 3-15 cannot mutate a temporary value
|
||||||
#((key: "val").other = "some")
|
#((key: "val").other = "some")
|
||||||
@ -124,3 +121,24 @@
|
|||||||
// Error: 8-15 type dictionary has no method `nonfunc`
|
// Error: 8-15 type dictionary has no method `nonfunc`
|
||||||
dict.nonfunc()
|
dict.nonfunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
#let a = "hello"
|
||||||
|
#let b = "world"
|
||||||
|
#let c = "value"
|
||||||
|
#let d = "conflict"
|
||||||
|
|
||||||
|
#assert.eq(((a): b), ("hello": "world"))
|
||||||
|
#assert.eq(((a): 1, (a): 2), ("hello": 2))
|
||||||
|
#assert.eq((hello: 1, (a): 2), ("hello": 2))
|
||||||
|
#assert.eq((a + b: c, (a + b): d, (a): "value2", a: "value3"), ("helloworld": "conflict", "hello": "value2", "a": "value3"))
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 7-10 expected identifier, found group
|
||||||
|
// Error: 12-14 expected identifier, found integer
|
||||||
|
#let ((a): 10) = "world"
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 3-7 expected string, found boolean
|
||||||
|
// Error: 16-18 expected string, found integer
|
||||||
|
#(true: false, 42: 3)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user