From 8d15e1ab1c3c3bca3c794c5f1499e0108d60d42f Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Wed, 1 May 2024 09:50:26 +0200 Subject: rebel-lang: scope: allow assigning fields and array elements --- crates/rebel-lang/src/scope.rs | 166 +++++++++++++++++++++++++++++++---------- 1 file changed, 127 insertions(+), 39 deletions(-) diff --git a/crates/rebel-lang/src/scope.rs b/crates/rebel-lang/src/scope.rs index bb3f2e9..1c6d52f 100644 --- a/crates/rebel-lang/src/scope.rs +++ b/crates/rebel-lang/src/scope.rs @@ -20,6 +20,32 @@ pub struct LookupError; pub type Result = std::result::Result; +#[derive(Debug)] +enum ValueSlot<'a> { + Value(&'a mut Value), + Option(&'a mut Option), +} + +impl<'a> ValueSlot<'a> { + fn assign(&mut self, value: Value) { + match self { + ValueSlot::Value(slot) => { + **slot = value; + } + ValueSlot::Option(slot) => { + **slot = Some(value); + } + } + } + + fn into_inner(self) -> Option<&'a mut Value> { + match self { + ValueSlot::Value(slot) => Some(slot), + ValueSlot::Option(slot) => slot.as_mut(), + } + } +} + #[derive(Debug, Clone)] pub struct Context { pub vars: Module, @@ -54,17 +80,6 @@ impl Context { } } - fn local_ident<'a>(path: &'a ast::Path) -> Option> { - if path.root != ast::PathRoot::Relative { - return None; - } - - let [ident] = path.components[..] else { - return None; - }; - Some(ident) - } - pub fn lookup_var(&self, path: &ast::Path) -> Result<&Var> { if path.root != ast::PathRoot::Relative { return Err(LookupError); @@ -89,6 +104,78 @@ impl Context { self.types.lookup(&path.components).ok_or(LookupError) } + fn lookup_path_var_mut(&mut self, path: &ast::Path) -> Result<&mut Var> { + if path.root != ast::PathRoot::Relative { + return Err(LookupError); + } + + self.vars.lookup_mut(&path.components).ok_or(LookupError) + } + + fn lookup_destr_pat_var_type_mut(&mut self, pat: &pat::DestrPat) -> Result<&mut Type> { + Ok(match pat { + pat::DestrPat::Index { pat, index: _ } => { + match self.lookup_destr_pat_var_type_mut(pat)? { + Type::Array(inner) => inner.as_mut(), + _ => return Err(LookupError), + } + } + pat::DestrPat::Field { pat, field } => { + match self.lookup_destr_pat_var_type_mut(pat)? { + Type::Struct(entries) => entries.get_mut(field.name).ok_or(LookupError)?, + _ => return Err(LookupError), + } + } + pat::DestrPat::Paren(subpat) => self.lookup_destr_pat_var_type_mut(subpat)?, + pat::DestrPat::Path(path) => &mut self.lookup_path_var_mut(path)?.inferred_type, + }) + } + + fn lookup_destr_pat_var_type_value_mut( + &mut self, + pat: &pat::DestrPat, + ) -> Result<(&mut Type, ValueSlot)> { + Ok(match pat { + pat::DestrPat::Index { pat, index } => { + let index = + usize::try_from(match Value::eval(self, index).or(Err(LookupError))? { + Value::Int(index) => index, + _ => { + return Err(LookupError); + } + }) + .or(Err(LookupError))?; + let (typ, value) = self.lookup_destr_pat_var_type_value_mut(pat)?; + let (inner_type, inner_value) = match (typ, value.into_inner().ok_or(LookupError)?) + { + (Type::Array(inner_type), Value::Array(inner_value)) => ( + inner_type.as_mut(), + inner_value.get_mut(index).ok_or(LookupError)?, + ), + _ => return Err(LookupError), + }; + (inner_type, ValueSlot::Value(inner_value)) + } + pat::DestrPat::Field { pat, field } => { + let (typ, value) = self.lookup_destr_pat_var_type_value_mut(pat)?; + let (inner_type, inner_value) = match (typ, value.into_inner().ok_or(LookupError)?) + { + (Type::Struct(type_entries), Value::Struct(value_entries)) => ( + type_entries.get_mut(field.name).ok_or(LookupError)?, + value_entries.get_mut(field.name).ok_or(LookupError)?, + ), + _ => return Err(LookupError), + }; + (inner_type, ValueSlot::Value(inner_value)) + } + pat::DestrPat::Paren(subpat) => self.lookup_destr_pat_var_type_value_mut(subpat)?, + pat::DestrPat::Path(path) => { + let var = self.lookup_path_var_mut(path)?; + (&mut var.inferred_type, ValueSlot::Option(&mut var.value)) + } + }) + } + pub fn record_type(&mut self, stmt: &ast::BlockStmt) -> typing::Result { match stmt { ast::BlockStmt::Let { dest, expr } => { @@ -122,25 +209,16 @@ impl Context { } } ast::BlockStmt::Assign { dest, expr } => { - let pat::DestrPat::Path(dest_path) = dest.as_ref() else { - // TODO: Other pattern types - return Err(TypeError); - }; - let dest_ident = Self::local_ident(dest_path).ok_or(TypeError)?; - let expr_type = Type::ast_expr_type(self, expr)?; - // TODO: Lexical scoping - let Some(ModuleEntry::Def(var)) = self.vars.0.get_mut(dest_ident.name) else { - return Err(TypeError); - }; - let inferred_type = var.inferred_type.clone().unify(expr_type, Coerce::Common)?; + let pat_type = self + .lookup_destr_pat_var_type_mut(dest) + .or(Err(TypeError))?; - var.explicit_type - .clone() - .unify(inferred_type.clone(), Coerce::Assign)?; + let inferred_type = pat_type.clone().unify(expr_type, Coerce::Common)?; + *pat_type = inferred_type.clone(); - var.inferred_type = inferred_type.clone(); + // TODO: Check explicit type return Ok(inferred_type); } @@ -192,26 +270,21 @@ impl Context { } } ast::BlockStmt::Assign { dest, expr } => { - let pat::DestrPat::Path(dest_path) = dest.as_ref() else { - // TODO: Other pattern types - panic!("Type error during eval"); - }; - let dest_ident = Self::local_ident(dest_path).expect("Type error during eval"); - let value = Value::eval(self, expr)?; - let expr_type = value.typ().or(Err(EvalError))?; + let expr_type = value.typ().expect("Type error during eval"); - let Some(ModuleEntry::Def(var)) = self.vars.0.get_mut(dest_ident.name) else { - unreachable!("Type error during eval"); - }; + let (pat_type, mut pat_value) = self + .lookup_destr_pat_var_type_value_mut(dest) + .or(Err(EvalError))?; - var.inferred_type = var - .inferred_type + *pat_type = pat_type .clone() .unify(expr_type, Coerce::Common) .expect("Type error during eval"); + pat_value.assign(value.clone()); + // TODO: Debug check: Test against explicit type - var.value = Some(value.clone()); + return Ok(value); } ast::BlockStmt::Expr { expr } => { @@ -245,6 +318,21 @@ impl Module { } } } + + pub fn lookup_mut(&mut self, path: &[ast::Ident<'_>]) -> Option<&mut T> { + let (ident, rest) = path.split_first()?; + + match self.0.get_mut(ident.name)? { + ModuleEntry::Module(module) => module.lookup_mut(rest), + ModuleEntry::Def(def) => { + if rest.is_empty() { + Some(def) + } else { + None + } + } + } + } } impl Default for Module { -- cgit v1.2.3