diff options
author | Matthias Schiffer <mschiffer@universe-factory.net> | 2024-05-04 23:53:39 +0200 |
---|---|---|
committer | Matthias Schiffer <mschiffer@universe-factory.net> | 2024-05-05 00:10:01 +0200 |
commit | 5c04851b48099463afbfd4fb9e015d99d28ab4b9 (patch) | |
tree | 67561f72cbd5c0a56bdaf869748ae3e92f707368 | |
parent | bd9a27bdd4145507ab239801fe1d125322db55df (diff) | |
download | rebel-5c04851b48099463afbfd4fb9e015d99d28ab4b9.tar rebel-5c04851b48099463afbfd4fb9e015d99d28ab4b9.zip |
rebel-parse, rebel-lang: add support for map literals
-rw-r--r-- | crates/rebel-lang/src/typing.rs | 12 | ||||
-rw-r--r-- | crates/rebel-lang/src/value.rs | 12 | ||||
-rw-r--r-- | crates/rebel-parse/src/ast/expr.rs | 14 | ||||
-rw-r--r-- | crates/rebel-parse/src/grammar/recipe.rs | 8 |
4 files changed, 46 insertions, 0 deletions
diff --git a/crates/rebel-lang/src/typing.rs b/crates/rebel-lang/src/typing.rs index ef46201..bd8ba10 100644 --- a/crates/rebel-lang/src/typing.rs +++ b/crates/rebel-lang/src/typing.rs @@ -511,6 +511,18 @@ impl<'scope> Context<'scope> { acc.unify(self.type_expr(elem)?, Coerce::Common) })?, )), + Literal::Map(entries) => { + let (key, value) = entries.iter().try_fold( + (Type::Free, Type::Free), + |(acc_key, acc_value), expr::MapEntry { key, value }| { + Ok(( + acc_key.unify(self.type_expr(key)?, Coerce::Common)?, + acc_value.unify(self.type_expr(value)?, Coerce::Common)?, + )) + }, + )?; + Type::Map(key.try_into()?, Box::new(value)) + } Literal::Struct(entries) => Struct( entries .iter() diff --git a/crates/rebel-lang/src/value.rs b/crates/rebel-lang/src/value.rs index bd040aa..ad78ac7 100644 --- a/crates/rebel-lang/src/value.rs +++ b/crates/rebel-lang/src/value.rs @@ -431,6 +431,18 @@ impl<'scope> Context<'scope> { .map(|elem| self.eval_expr(elem)) .collect::<Result<_>>()?, ), + Literal::Map(entries) => { + let map: FxHashMap<_, _> = entries + .iter() + .map(|expr::MapEntry { key, value }| { + Ok((self.eval_expr(key)?.try_into()?, self.eval_expr(value)?)) + }) + .collect::<Result<_>>()?; + if map.len() != entries.len() { + return Err(Error::eval("duplicate map key")); + } + Map(map) + } Literal::Struct(entries) => Struct( entries .iter() diff --git a/crates/rebel-parse/src/ast/expr.rs b/crates/rebel-parse/src/ast/expr.rs index 341b8ad..0a47cb2 100644 --- a/crates/rebel-parse/src/ast/expr.rs +++ b/crates/rebel-parse/src/ast/expr.rs @@ -193,6 +193,7 @@ pub enum Literal<'a> { }, Tuple(Vec<Expr<'a>>), Array(Vec<Expr<'a>>), + Map(Vec<MapEntry<'a>>), Struct(Vec<StructField<'a>>), } @@ -224,6 +225,13 @@ impl<'a> Literal<'a> { } Ok(()) } + Literal::Map(entries) => { + for MapEntry { key, value } in entries { + key.validate()?; + value.validate()?; + } + Ok(()) + } Literal::Struct(entries) => { let mut fields = FxHashSet::default(); for StructField { name, value } in entries { @@ -262,6 +270,12 @@ impl<'a> TryFrom<&token::StrPiece<'a>> for StrPiece<'a> { } #[derive(Debug, Clone, PartialEq, Eq)] +pub struct MapEntry<'a> { + pub key: Expr<'a>, + pub value: Expr<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub struct StructField<'a> { pub name: &'a str, pub value: Expr<'a>, diff --git a/crates/rebel-parse/src/grammar/recipe.rs b/crates/rebel-parse/src/grammar/recipe.rs index 1ada80c..8181ee2 100644 --- a/crates/rebel-parse/src/grammar/recipe.rs +++ b/crates/rebel-parse/src/grammar/recipe.rs @@ -178,10 +178,18 @@ peg::parser! { / p('[') elements:delimited(<expr()>, <p(',')>) p(']') { expr::Literal::Array(elements) } + / [Token::Keyword(Keyword::Map)] p('{') entries:delimited(<map_entry()>, <p(',')>) p('}') { + expr::Literal::Map(entries) + } / [Token::Keyword(Keyword::Struct)] p('{') entries:delimited(<struct_field()>, <p(',')>) p('}') { expr::Literal::Struct(entries) } + rule map_entry() -> expr::MapEntry<'a> + = key:expr() p2('=', '>') value:expr() { + expr::MapEntry { key, value } + } + rule struct_field() -> expr::StructField<'a> = field:field() p('=') value:expr() { expr::StructField { name: field.name, value } |