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

View File

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

View File

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

View File

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