diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 13c8b23..5660f4a 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -37,6 +37,10 @@ pub enum Expr { callee: Box>, arguments: Vec>, }, + Dereference { + object: Box>, + name: Spanned, + }, } impl Expr { @@ -127,6 +131,17 @@ impl Expr { ) } + pub fn dereference(object: Spanned, name: Spanned) -> Spanned { + let span = object.span().join(&name.span()); + Spanned::new( + Self::Dereference { + object: Box::new(object), + name, + }, + span, + ) + } + pub fn as_str(&self) -> &'static str { match self { Expr::Binary { .. } => "binary", @@ -137,6 +152,7 @@ impl Expr { Expr::Assignment { .. } => "assignment", Expr::Logical { .. } => "logical", Expr::Call { .. } => "function call", + Expr::Dereference { .. } => "dereference", } } } @@ -191,6 +207,13 @@ impl Expr { other => panic!("expected function call, got {:?}", other.as_str()), } } + + pub fn unwrap_deref(&self) -> (&Expr, &str) { + match self { + Expr::Dereference { object, name } => (object.as_ref(), name.as_ref()), + other => panic!("expected dereference, got {:?}", other.as_str()), + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display)] diff --git a/src/class.rs b/src/class.rs index 0475593..c93d2ee 100644 --- a/src/class.rs +++ b/src/class.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::rc::Rc; use crate::{ @@ -23,6 +24,7 @@ impl Class { fn instantiate(&self) -> Rc { Rc::new(ClassInstance { class: self.clone(), + fields: HashMap::new(), }) } } @@ -41,10 +43,15 @@ impl Callable for Class { #[derive(Clone, Debug)] pub struct ClassInstance { class: Class, + fields: HashMap, } impl ClassInstance { pub fn class(&self) -> Class { self.class.clone() } + + pub fn get(&self, name: &str) -> Option { + self.fields.get(name).cloned() + } } diff --git a/src/interpreter.rs b/src/interpreter.rs index 9d9c236..5296a80 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -201,6 +201,13 @@ impl<'a> Interpreter<'a> { // Call it callable.call(self, evaluated_args) } + Expr::Dereference { object, name } => { + let Some(instance) = self.interpret_expr(object, env)?.as_instance() else { + todo!("deref target isn't class instance") + }; + let value = instance.get(name).expect("field not defined"); + Ok(value) + } } } diff --git a/src/parser.rs b/src/parser.rs index 02d5291..6a3ed23 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -86,7 +86,7 @@ impl Parser { fn parse_var_decl(&mut self) -> Result> { let var_token = self.expect_token(MatchToken::Var)?; - let name = self.expect_identifier()?; + let name = self.expect_identifier()?.into_inner(); let initializer = self .pop_if_matches(MatchToken::Eq) .map(|_| self.parse_expr()) @@ -106,7 +106,7 @@ impl Parser { #[tracing::instrument(name = "class", skip_all)] fn parse_class_decl(&mut self) -> Result> { let class_token = self.expect_token(MatchToken::Class)?; - let name = self.expect_identifier()?; + let name = self.expect_identifier()?.into_inner(); let (_, methods, close_brace) = self.parse_until_close_brace(|p| { let (name_token, method, fun_close_brace) = p.parse_fun_or_method()?; @@ -234,12 +234,13 @@ impl Parser { } fn parse_fun_or_method(&mut self) -> Result<(Token, Function, Token)> { - let name = self.expect_identifier()?; + let name = self.expect_identifier()?.into_inner(); let name_token = self.previous().unwrap(); self.expect_token(MatchToken::LeftParen)?; - let params = - self.parse_comma_separated_list(MatchToken::RightParen, Self::expect_identifier)?; + let params = self.parse_comma_separated_list(MatchToken::RightParen, |p| { + p.expect_identifier().map(Spanned::into_inner) + })?; self.expect_token(MatchToken::RightParen)?; let (_, body, close_brace) = self.parse_block_body()?; @@ -443,14 +444,14 @@ impl Parser { let mut expr = self.parse_primary()?; loop { - let Some(left_paren) = self.pop_if_map(|t| match t.kind { - TokenKind::LeftParen => Some(t), - _ => None, - }) else { + if let Some(left_paren) = self.pop_if_matches(MatchToken::LeftParen) { + expr = self.finish_call_args(expr, &left_paren)?; + } else if self.pop_if_matches(MatchToken::Dot).is_some() { + let name = self.expect_identifier()?; + expr = Expr::dereference(expr, name); + } else { return Ok(expr); }; - - expr = self.finish_call_args(expr, &left_paren)?; } } @@ -531,9 +532,9 @@ impl Parser { .ok_or_else(|| ParserError::missing_semicolon(self.previous())) } - fn expect_identifier(&mut self) -> Result { + fn expect_identifier(&mut self) -> Result> { self.pop_if_map(|t| match t.kind { - TokenKind::Ident { name } => Some(name), + TokenKind::Ident { name } => Some(Spanned::new(name, t.span)), _ => None, }) .ok_or_else(|| { @@ -1097,6 +1098,21 @@ mod tests { assert!(args[1].unwrap_literal().unwrap_bool()); } + #[test] + fn parses_deref_expr() { + start_test_tracing(); + + let expr = parse_to_expr("foo.bar"); + let (object, name) = expr.unwrap_deref(); + assert_eq!(object.unwrap_var(), "foo"); + assert_eq!(name, "bar"); + + let expr = parse_to_expr("a.b.c.d"); + let (object, name) = expr.unwrap_deref(); + assert_matches!(object, Expr::Dereference { .. }); + assert_eq!(name, "d"); + } + #[test] fn parses_multiple_calls() { start_test_tracing(); diff --git a/src/resolver.rs b/src/resolver.rs index 4b0c00e..f877c3d 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -179,6 +179,9 @@ impl Resolver { self.resolve_expr(arg)?; } } + Expr::Dereference { object, .. } => { + self.resolve_expr(object)?; + } Expr::Binary { left, right, .. } => { self.resolve_expr(left)?; self.resolve_expr(right)?; diff --git a/src/span.rs b/src/span.rs index 7387e81..094854d 100644 --- a/src/span.rs +++ b/src/span.rs @@ -47,6 +47,10 @@ impl Spanned { &mut self.0 } + pub fn into_inner(self) -> T { + self.0 + } + pub fn span(&self) -> Span { self.1 } diff --git a/src/value.rs b/src/value.rs index 133650f..037943d 100644 --- a/src/value.rs +++ b/src/value.rs @@ -43,6 +43,13 @@ impl Value { _ => None, } } + + pub fn as_instance(&self) -> Option> { + match self { + Value::ClassInstance(instance) => Some(Rc::clone(instance)), + _ => None, + } + } } impl PartialEq for Value {