From 2281a9bb504d5284f3c1b865bbde8cafcecad603 Mon Sep 17 00:00:00 2001 From: Olivier Date: Wed, 11 Jun 2025 22:16:42 +0100 Subject: [PATCH] Add `!` unwrap operator --- hornbeam_grammar/src/ast.rs | 5 ++++ hornbeam_grammar/src/hornbeam.pest | 2 +- hornbeam_grammar/src/parser.rs | 4 ++- hornbeam_interpreter/src/engine.rs | 48 ++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/hornbeam_grammar/src/ast.rs b/hornbeam_grammar/src/ast.rs index 495f177..fbb2375 100644 --- a/hornbeam_grammar/src/ast.rs +++ b/hornbeam_grammar/src/ast.rs @@ -225,4 +225,9 @@ pub enum Expression { args: Vec, loc: Locator, }, + + Unwrap { + obj: Box, + loc: Locator, + }, } diff --git a/hornbeam_grammar/src/hornbeam.pest b/hornbeam_grammar/src/hornbeam.pest index e0b8a7e..f579bca 100644 --- a/hornbeam_grammar/src/hornbeam.pest +++ b/hornbeam_grammar/src/hornbeam.pest @@ -213,7 +213,7 @@ bnot = { "not" ~ ws+ } // POSTFIX postfix = _{ unwrap | MethodCall | FieldLookup | Indexing } -unwrap = { "?" } // Not sure I'm convinced about this one, but we can think about it. +unwrap = { "!" } // Not sure I'm convinced about this one, but we can think about it. // Note that functions aren't first-class; we don't allow you to 'call' an arbitrary term. // This is probably for the best since we might have multiple backends for the templating. MethodCall = { "." ~ ws* ~ Identifier ~ "(" ~ commaSeparatedExprs ~ ")" } diff --git a/hornbeam_grammar/src/parser.rs b/hornbeam_grammar/src/parser.rs index 4422db3..4eff086 100644 --- a/hornbeam_grammar/src/parser.rs +++ b/hornbeam_grammar/src/parser.rs @@ -432,7 +432,9 @@ impl HornbeamParser { let node = Node::new_with_user_data(op, ud.clone()); let loc = nodeloc(&node); Ok(match node.as_rule() { - Rule::unwrap => unimplemented!("unimp unwrap"), + Rule::unwrap => { + Expression::Unwrap { obj: Box::new(lhs?), loc } + }, Rule::FieldLookup => { let ident = intern(node.into_children().single()?.as_str()); Expression::FieldLookup { obj: Box::new(lhs?), ident, loc } diff --git a/hornbeam_interpreter/src/engine.rs b/hornbeam_interpreter/src/engine.rs index 0586bc3..7ca67e6 100644 --- a/hornbeam_interpreter/src/engine.rs +++ b/hornbeam_interpreter/src/engine.rs @@ -855,6 +855,54 @@ impl<'a, O: OutputSystem + Send, LS: LocalisationSystem + Sync + Send> Interpret Expression::FunctionCall { .. } => { unimplemented!() } + Expression::Unwrap { obj, loc } => { + let obj_value = self.evaluate_expression(scope_idx, obj, loc)?; + + match &obj_value { + Value::Reflective(reflective) => match reflective.reflect_ref() { + ReflectRef::Enum(reflenum) => match reflenum.variant_name() { + "Some" => { + if reflenum.field_len() != 1 { + return Err(InterpreterError::TypeError { + context: "unwrap".to_owned(), + conflict: "wrong number of fields in Some".to_owned(), + location: loc.clone(), + }); + } + + if reflenum.variant_type() != VariantType::Tuple { + return Err(InterpreterError::TypeError { + context: "unwrap".to_owned(), + conflict: "Some is not a tuple variant".to_owned(), + location: loc.clone(), + }); + } + + Ok(Value::from_reflect( + reflenum.field_at(0).unwrap().clone_value(), + )) + } + "None" => Err(InterpreterError::TypeError { + context: "unwrap".to_owned(), + conflict: "tried to unwrap None".to_owned(), + location: loc.clone(), + }), + _other => { + warn!("unnecessary unwrap (!) at {loc}"); + Ok(obj_value) + } + }, + _other => { + warn!("unnecessary unwrap (!) at {loc}"); + Ok(obj_value) + } + }, + _other => { + warn!("unnecessary unwrap (!) at {loc}"); + Ok(obj_value) + } + } + } } }