diff --git a/dust-lang/src/abstract_tree.rs b/dust-lang/src/abstract_tree.rs index dac2b01..e47c45b 100644 --- a/dust-lang/src/abstract_tree.rs +++ b/dust-lang/src/abstract_tree.rs @@ -22,6 +22,7 @@ pub enum Statement { // Expressions Add(Box, Box), + PropertyAccess(Box, Box), List(Vec), Multiply(Box, Box), diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index d0b6d74..184b3ab 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -1,4 +1,4 @@ -use crate::{Node, Span, Statement}; +use crate::{Node, Statement}; pub fn analyze(abstract_tree: Vec) -> Result<(), AnalyzerError> { let analyzer = Analyzer::new(abstract_tree); @@ -55,6 +55,17 @@ impl Analyzer { self.analyze_node(&left)?; self.analyze_node(&right)?; } + Statement::PropertyAccess(left, right) => { + if let Statement::Identifier(_) = &left.statement { + // Identifier is in the correct position + } else { + return Err(AnalyzerError::ExpectedIdentifier { + actual: left.as_ref().clone(), + }); + } + + self.analyze_node(&right)?; + } } Ok(()) diff --git a/dust-lang/src/lex.rs b/dust-lang/src/lex.rs index 49be36b..b562952 100644 --- a/dust-lang/src/lex.rs +++ b/dust-lang/src/lex.rs @@ -90,6 +90,10 @@ impl<'a> Lexer<'a> { self.position += 1; (Token::Comma, (self.position - 1, self.position)) } + '.' => { + self.position += 1; + (Token::Dot, (self.position - 1, self.position)) + } _ => (Token::Eof, (self.position, self.position)), } } else { @@ -122,12 +126,12 @@ impl<'a> Lexer<'a> { while let Some(c) = self.peek_char() { if c == '.' { - is_float = true; - self.next_char(); while let Some(c) = self.peek_char() { if c.is_ascii_digit() { + is_float = true; + self.next_char(); } else { break; diff --git a/dust-lang/src/parse.rs b/dust-lang/src/parse.rs index 8d5dbca..785f54a 100644 --- a/dust-lang/src/parse.rs +++ b/dust-lang/src/parse.rs @@ -85,6 +85,17 @@ impl<'src> Parser<'src> { (left_start, right_end), )); } + (Token::Dot, _) => { + self.next_token()?; + + let right_node = self.parse_node(self.current_precedence())?; + let right_end = right_node.span.1; + + return Ok(Node::new( + Statement::PropertyAccess(Box::new(left_node), Box::new(right_node)), + (left_start, right_end), + )); + } _ => {} } } @@ -165,6 +176,7 @@ impl<'src> Parser<'src> { fn current_precedence(&self) -> u8 { match self.current.0 { + Token::Dot => 4, Token::Equal => 3, Token::Plus => 1, Token::Star => 2, @@ -193,6 +205,29 @@ mod tests { use super::*; + #[test] + fn property_access() { + let input = "a.b"; + + assert_eq!( + parse(input), + Ok([Node::new( + Statement::PropertyAccess( + Box::new(Node::new( + Statement::Identifier(Identifier::new("a")), + (0, 1) + )), + Box::new(Node::new( + Statement::Identifier(Identifier::new("b")), + (2, 3) + )), + ), + (0, 3), + )] + .into()) + ); + } + #[test] fn complex_list() { let input = "[1, 1 + 1, 2 + (4 * 10)]"; diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index 2ddaa1c..fb18909 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -3,6 +3,7 @@ use crate::Identifier; #[derive(Debug, PartialEq, Clone)] pub enum Token { Comma, + Dot, Eof, Equal, Identifier(Identifier), diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index c6f76f8..e978b02 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -88,6 +88,37 @@ impl Value { _ => Err(ValueError::CannotAdd(self.clone(), other.clone())), } } + + pub fn property_access(&self, property: &Identifier) -> Result { + match self.inner().as_ref() { + ValueInner::Map(map) => { + if let Some(value) = map.get(property) { + Ok(value.clone()) + } else { + Err(ValueError::PropertyNotFound { + value: self.clone(), + property: property.clone(), + }) + } + } + ValueInner::Integer(integer) => match property.as_str() { + "is_even" => Ok(Value::boolean(integer % 2 == 0)), + "to_string" => Ok(Value::string(integer.to_string())), + _ => Err(ValueError::PropertyNotFound { + value: self.clone(), + property: property.clone(), + }), + }, + ValueInner::List(values) => match property.as_str() { + "length" => Ok(Value::integer(values.len() as i64)), + _ => Err(ValueError::PropertyNotFound { + value: self.clone(), + property: property.clone(), + }), + }, + _ => todo!(), + } + } } impl Display for Value { @@ -474,6 +505,18 @@ impl ValueInner { } } +pub trait ValueProperties<'a> {} + +pub struct IntegerProperties<'a>(&'a Value); + +impl<'a> IntegerProperties<'a> { + pub fn is_even(&self) -> bool { + self.0.as_integer().unwrap() % 2 == 0 + } +} + +impl<'a> ValueProperties<'a> for IntegerProperties<'a> {} + impl Eq for ValueInner {} impl PartialOrd for ValueInner { @@ -516,4 +559,5 @@ impl Ord for ValueInner { #[derive(Clone, Debug, PartialEq)] pub enum ValueError { CannotAdd(Value, Value), + PropertyNotFound { value: Value, property: Identifier }, } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 32c032c..0cfe87c 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -100,6 +100,28 @@ impl Vm { Ok(Some(Value::list(values))) } Statement::Multiply(_, _) => todo!(), + Statement::PropertyAccess(left, right) => { + let left_span = left.span; + let left = if let Some(value) = self.run_node(*left, variables)? { + value + } else { + return Err(VmError::ExpectedValue { + position: left_span, + }); + }; + let right_span = right.span; + let right = if let Statement::Identifier(identifier) = &right.statement { + identifier + } else { + return Err(VmError::ExpectedValue { + position: right_span, + }); + }; + + let value = left.property_access(right)?; + + Ok(Some(value)) + } } } } @@ -130,6 +152,13 @@ impl From for VmError { mod tests { use super::*; + #[test] + fn property_access() { + let input = "[1, 2, 3].length"; + + assert_eq!(run(input, &mut HashMap::new()), Ok(Some(Value::integer(3)))); + } + #[test] fn add() { let input = "1 + 2";