Implement dict key interpolation (#2559)

This commit is contained in:
Matt Fellenz 2023-11-02 09:08:08 -07:00 committed by GitHub
parent b716700b61
commit 8fd546760c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 50 additions and 20 deletions

View File

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

View File

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

View File

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

View File

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