summaryrefslogtreecommitdiffstats
path: root/crates
diff options
context:
space:
mode:
authorMatthias Schiffer <mschiffer@universe-factory.net>2024-05-04 23:53:39 +0200
committerMatthias Schiffer <mschiffer@universe-factory.net>2024-05-05 00:10:01 +0200
commit5c04851b48099463afbfd4fb9e015d99d28ab4b9 (patch)
tree67561f72cbd5c0a56bdaf869748ae3e92f707368 /crates
parentbd9a27bdd4145507ab239801fe1d125322db55df (diff)
downloadrebel-5c04851b48099463afbfd4fb9e015d99d28ab4b9.tar
rebel-5c04851b48099463afbfd4fb9e015d99d28ab4b9.zip
rebel-parse, rebel-lang: add support for map literals
Diffstat (limited to 'crates')
-rw-r--r--crates/rebel-lang/src/typing.rs12
-rw-r--r--crates/rebel-lang/src/value.rs12
-rw-r--r--crates/rebel-parse/src/ast/expr.rs14
-rw-r--r--crates/rebel-parse/src/grammar/recipe.rs8
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 }