Implement dict key interpolation (#2559)
This commit is contained in:
parent
b716700b61
commit
8fd546760c
@ -1270,11 +1270,8 @@ node! {
|
||||
|
||||
impl<'a> Keyed<'a> {
|
||||
/// The key: `"spacy key"`.
|
||||
pub fn key(self) -> Str<'a> {
|
||||
self.0
|
||||
.children()
|
||||
.find_map(|node| node.cast::<Str>())
|
||||
.unwrap_or_default()
|
||||
pub fn key(self) -> Expr<'a> {
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
Some(SyntaxKind::Ident) => SyntaxKind::Named,
|
||||
Some(SyntaxKind::Str) if keyed => SyntaxKind::Keyed,
|
||||
Some(_) if keyed => SyntaxKind::Keyed,
|
||||
_ => {
|
||||
for child in p.post_process(m) {
|
||||
if child.kind() == SyntaxKind::Colon {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut message = EcoString::from("expected identifier");
|
||||
if keyed {
|
||||
message.push_str(" or string");
|
||||
}
|
||||
message.push_str(", found ");
|
||||
message.push_str(child.kind().name());
|
||||
let expected = if keyed { "expression" } else { "identifier" };
|
||||
let message = eco_format!(
|
||||
"expected {expected}, found {found}",
|
||||
found = child.kind().name(),
|
||||
);
|
||||
child.convert_to_error(message);
|
||||
}
|
||||
SyntaxKind::Named
|
||||
@ -1235,9 +1234,12 @@ fn validate_dict<'a>(children: impl Iterator<Item = &'a mut SyntaxNode>) {
|
||||
match child.kind() {
|
||||
SyntaxKind::Named | SyntaxKind::Keyed => {
|
||||
let Some(first) = child.children_mut().first_mut() else { continue };
|
||||
let key = match first.cast::<ast::Str>() {
|
||||
Some(str) => str.get(),
|
||||
None => first.text().clone(),
|
||||
let key = if let Some(str) = first.cast::<ast::Str>() {
|
||||
str.get()
|
||||
} else if let Some(ident) = first.cast::<ast::Ident>() {
|
||||
ident.get().clone()
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !used.insert(key.clone()) {
|
||||
|
@ -1012,13 +1012,22 @@ impl Eval for ast::Dict<'_> {
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let mut map = indexmap::IndexMap::new();
|
||||
|
||||
let mut invalid_keys = eco_vec![];
|
||||
|
||||
for item in self.items() {
|
||||
match item {
|
||||
ast::DictItem::Named(named) => {
|
||||
map.insert(named.name().get().clone().into(), named.expr().eval(vm)?);
|
||||
}
|
||||
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)? {
|
||||
Value::None => {}
|
||||
@ -1028,6 +1037,10 @@ impl Eval for ast::Dict<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
if !invalid_keys.is_empty() {
|
||||
return Err(invalid_keys);
|
||||
}
|
||||
|
||||
Ok(map.into())
|
||||
}
|
||||
}
|
||||
|
@ -92,15 +92,12 @@
|
||||
#(a: 1, b)
|
||||
|
||||
// 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: 5 expected comma
|
||||
// Error: 12-16 expected identifier or string, found boolean
|
||||
// Error: 17 expected expression
|
||||
#(:1 b:"", true:)
|
||||
|
||||
// Error: 3-8 expected identifier or string, found binary expression
|
||||
#(a + b: "hey")
|
||||
|
||||
---
|
||||
// Error: 3-15 cannot mutate a temporary value
|
||||
#((key: "val").other = "some")
|
||||
@ -124,3 +121,24 @@
|
||||
// Error: 8-15 type dictionary has no method `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