diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index e4ef716..1eb455d 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -6,7 +6,8 @@ use parser::{ tree_node::{ AssignmentExpression, BinaryExpression, BlockExpression, DeviceDeclarationExpression, Expression, FunctionExpression, IfExpression, InvocationExpression, Literal, - LiteralOrVariable, LogicalExpression, LoopExpression, Span, Spanned, WhileExpression, + LiteralOrVariable, LogicalExpression, LoopExpression, MethodCallExpression, Span, Spanned, + WhileExpression, }, }; use quick_error::quick_error; @@ -351,6 +352,9 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { temp_name: Some(result_name), })) } + Expression::MethodCall(method_expr) => { + self.expression_method_call(method_expr.node, scope, method_expr.span) + } _ => Err(Error::Unknown( format!( "Expression type not yet supported in general expression context: {:?}", @@ -540,6 +544,28 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { scope, ); } + Expression::MethodCall(method_expr) => { + if let Some(result) = + self.expression_method_call(method_expr.node, scope, method_expr.span)? + { + let var_loc = scope.add_variable(&name_str, LocationRequest::Persist)?; + + // Move result from temp to new persistent variable + let result_reg = self.resolve_register(&result.location)?; + self.emit_variable_assignment(&name_str, &var_loc, result_reg)?; + + // Free the temp result + if let Some(name) = result.temp_name { + scope.free_temp(name)?; + } + (var_loc, None) + } else { + return Err(Error::Unknown( + "Method call did not return a value".into(), + Some(method_expr.span), + )); + } + } _ => { return Err(Error::Unknown( format!("`{name_str}` declaration of this type is not supported/implemented."), @@ -1252,6 +1278,25 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { scope.free_temp(name)?; } } + Expression::Invocation(invoke_expr) => { + self.expression_function_invocation(invoke_expr, scope)?; + // The result is already in RETURN_REGISTER from the call + } + Expression::MethodCall(method_expr) => { + if let Some(result) = + self.expression_method_call(method_expr.node, scope, method_expr.span)? + { + let result_reg = self.resolve_register(&result.location)?; + self.write_output(format!( + "move r{} {}", + VariableScope::RETURN_REGISTER, + result_reg + ))?; + if let Some(name) = result.temp_name { + scope.free_temp(name)?; + } + } + } _ => { return Err(Error::Unknown( format!("Unsupported `return` statement: {:?}", expr), @@ -1546,4 +1591,117 @@ impl<'a, W: std::io::Write> Compiler<'a, W> { self.write_output("j ra")?; Ok(()) } + + fn expression_method_call<'v>( + &mut self, + expr: MethodCallExpression, + scope: &mut VariableScope<'v>, + span: Span, + ) -> Result, Error> { + let object_name = expr.object.node; + + if let Some(device_val) = self.devices.get(&object_name).cloned() { + match expr.method.node.as_str() { + "load" => { + if expr.arguments.len() != 1 { + return Err(Error::AgrumentMismatch( + "load expects 1 argument".into(), + span, + )); + } + let arg = expr.arguments.into_iter().next().unwrap(); + let logic_type = match arg.node { + Expression::Literal(l) => match l.node { + Literal::String(s) => s, + _ => { + return Err(Error::AgrumentMismatch( + "load argument must be a string literal".into(), + arg.span, + )); + } + }, + _ => { + return Err(Error::AgrumentMismatch( + "load argument must be a string literal".into(), + arg.span, + )); + } + }; + + self.write_output(format!( + "l r{} {} {}", + VariableScope::RETURN_REGISTER, + device_val, + logic_type + ))?; + + // Move result to a temp register to be safe for use in expressions + let temp_name = self.next_temp_name(); + let temp_loc = scope.add_variable(&temp_name, LocationRequest::Temp)?; + self.emit_variable_assignment( + &temp_name, + &temp_loc, + format!("r{}", VariableScope::RETURN_REGISTER), + )?; + + return Ok(Some(CompilationResult { + location: temp_loc, + temp_name: Some(temp_name), + })); + } + "set" => { + if expr.arguments.len() != 2 { + return Err(Error::AgrumentMismatch( + "set expects 2 arguments".into(), + span, + )); + } + + let mut args_iter = expr.arguments.into_iter(); + let logic_type_arg = args_iter.next().unwrap(); + let value_arg = args_iter.next().unwrap(); + + let logic_type = match logic_type_arg.node { + Expression::Literal(l) => match l.node { + Literal::String(s) => s, + _ => { + return Err(Error::AgrumentMismatch( + "set expects a string literal as first argument".into(), + logic_type_arg.span, + )); + } + }, + _ => { + return Err(Error::AgrumentMismatch( + "set expects a string literal as first argument".into(), + logic_type_arg.span, + )); + } + }; + + let (val_str, cleanup) = self.compile_operand(value_arg, scope)?; + + self.write_output(format!("s {} {} {}", device_val, logic_type, val_str))?; + + if let Some(name) = cleanup { + scope.free_temp(name)?; + } + + return Ok(None); + } + _ => { + return Err(Error::Unknown( + format!( + "Unknown method '{}' on device '{}'", + expr.method.node, object_name + ), + Some(span), + )); + } + } + } + + Err(Error::UnknownIdentifier(object_name, expr.object.span)) + } } + diff --git a/rust_compiler/libs/parser/src/lib.rs b/rust_compiler/libs/parser/src/lib.rs index bf5217e..994e71f 100644 --- a/rust_compiler/libs/parser/src/lib.rs +++ b/rust_compiler/libs/parser/src/lib.rs @@ -121,7 +121,6 @@ impl<'a> Parser<'a> { } /// Calculates a Span from a given Token reference. - /// This is a static helper to avoid borrowing `self` when we already have a token ref. fn token_to_span(t: &Token) -> Span { let len = t.original_string.as_ref().map(|s| s.len()).unwrap_or(0); Span { @@ -149,7 +148,6 @@ impl<'a> Parser<'a> { where F: FnOnce(&mut Self) -> Result, { - // Peek at the start token. If no current token (parsing hasn't started), peek the buffer. let start_token = if self.current_token.is_some() { self.current_token.clone() } else { @@ -163,7 +161,6 @@ impl<'a> Parser<'a> { let node = parser(self)?; - // The end token is the current_token after parsing. let end_token = self.current_token.as_ref(); let (end_line, end_col) = end_token @@ -184,26 +181,15 @@ impl<'a> Parser<'a> { }) } - /// Skips tokens until a statement boundary is found to recover from errors. fn synchronize(&mut self) -> Result<(), Error> { - // We advance once to consume the error-causing token if we haven't already - // But often the error happens after we consumed something. - // Safe bet: consume current, then look. - - // If we assign next, we might be skipping the very token we want to sync on if the error didn't consume it? - // Usually, in recursive descent, the error is raised when `current` is unexpected. - // We want to discard `current` and move on. self.assign_next()?; while let Some(token) = &self.current_token { if token.token_type == TokenType::Symbol(Symbol::Semicolon) { - // Consuming the semicolon is a good place to stop and resume parsing next statement self.assign_next()?; return Ok(()); } - // Check if the token looks like the start of a statement. - // If so, we don't consume it; we return so the loop in parse_all can try to parse it. match token.token_type { TokenType::Keyword(Keyword::Fn) | TokenType::Keyword(Keyword::Let) @@ -231,7 +217,6 @@ impl<'a> Parser<'a> { let mut expressions = Vec::>::new(); loop { - // Check EOF without unwrapping error match self.tokenizer.peek() { Ok(None) => break, Err(e) => { @@ -248,19 +233,13 @@ impl<'a> Parser<'a> { Ok(None) => break, Err(e) => { self.errors.push(e); - // Recover if self.synchronize().is_err() { - // If sync failed (e.g. EOF during sync), break break; } } } } - // Even if we had errors, we return whatever partial AST we managed to build. - // If expressions is empty and we had errors, it's a failed parse, but we return a block. - - // Use the last token position for end span, or start if nothing parsed let end_token_opt = self.tokenizer.peek().unwrap_or(None); let (end_line, end_col) = end_token_opt .map(|tok| { @@ -285,7 +264,6 @@ impl<'a> Parser<'a> { pub fn parse(&mut self) -> Result>, Error> { self.assign_next()?; - // If assign_next hit EOF or error? if self.current_token.is_none() { return Ok(None); } @@ -317,15 +295,18 @@ impl<'a> Parser<'a> { return Ok(None); }; - // check if the next or current token is an operator, comparison, or logical symbol + // Handle Postfix operators (Member Access, Method Call) immediately after unary + let lhs = self.parse_postfix(lhs)?; + + // Handle Infix operators (Binary, Logical, Assignment) if self_matches_peek!( self, - TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() + TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign) ) { return Ok(Some(self.infix(lhs)?)); } else if self_matches_current!( self, - TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() + TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign) ) { self.tokenizer.seek(SeekFrom::Current(-1))?; return Ok(Some(self.infix(lhs)?)); @@ -334,6 +315,116 @@ impl<'a> Parser<'a> { Ok(Some(lhs)) } + /// Handles dot notation chains: x.y.z() + fn parse_postfix( + &mut self, + mut lhs: Spanned, + ) -> Result, Error> { + loop { + if self_matches_peek!(self, TokenType::Symbol(Symbol::Dot)) { + self.assign_next()?; // consume Dot + + let identifier_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?; + let identifier_span = Self::token_to_span(identifier_token); + let identifier = match identifier_token.token_type { + TokenType::Identifier(ref id) => id.clone(), + _ => { + return Err(Error::UnexpectedToken( + identifier_span, + identifier_token.clone(), + )); + } + }; + + // Check for Method Call '()' + if self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) { + // Method Call + self.assign_next()?; // consume '(' + let mut arguments = Vec::>::new(); + + while !token_matches!( + self.get_next()?.ok_or(Error::UnexpectedEOF)?, + TokenType::Symbol(Symbol::RParen) + ) { + let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; + + // Block expressions not allowed in args + if let Expression::Block(_) = expression.node { + return Err(Error::InvalidSyntax( + self.current_span(), + String::from("Block expressions are not allowed in method calls"), + )); + } + arguments.push(expression); + + if !self_matches_peek!(self, TokenType::Symbol(Symbol::Comma)) + && !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen)) + { + let next_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?; + return Err(Error::UnexpectedToken( + Self::token_to_span(next_token), + next_token.clone(), + )); + } + + if !self_matches_peek!(self, TokenType::Symbol(Symbol::RParen)) { + self.assign_next()?; + } + } + + // End span is the ')' + let end_span = self.current_span(); + let combined_span = Span { + start_line: lhs.span.start_line, + start_col: lhs.span.start_col, + end_line: end_span.end_line, + end_col: end_span.end_col, + }; + + lhs = Spanned { + span: combined_span, + node: Expression::MethodCall(Spanned { + span: combined_span, + node: MethodCallExpression { + object: boxed!(lhs), + method: Spanned { + span: identifier_span, + node: identifier, + }, + arguments, + }, + }), + }; + } else { + // Member Access + let combined_span = Span { + start_line: lhs.span.start_line, + start_col: lhs.span.start_col, + end_line: identifier_span.end_line, + end_col: identifier_span.end_col, + }; + + lhs = Spanned { + span: combined_span, + node: Expression::MemberAccess(Spanned { + span: combined_span, + node: MemberAccessExpression { + object: boxed!(lhs), + member: Spanned { + span: identifier_span, + node: identifier, + }, + }, + }), + }; + } + } else { + break; + } + } + Ok(lhs) + } + fn unary(&mut self) -> Result>, Error> { macro_rules! matches_keyword { ($keyword:expr, $($pattern:pat),+) => { @@ -357,10 +448,7 @@ impl<'a> Parser<'a> { )); } - TokenType::Keyword(Keyword::Let) => { - // declaration is wrapped in spanned inside the function, but expects 'let' to be current - Some(self.spanned(|p| p.declaration())?) - } + TokenType::Keyword(Keyword::Let) => Some(self.spanned(|p| p.declaration())?), TokenType::Keyword(Keyword::Device) => { let spanned_dev = self.spanned(|p| p.device())?; @@ -404,7 +492,6 @@ impl<'a> Parser<'a> { TokenType::Keyword(Keyword::Break) => { let span = self.current_span(); - // make sure the next token is a semi-colon let next = self.get_next()?.ok_or(Error::UnexpectedEOF)?; if !token_matches!(next, TokenType::Symbol(Symbol::Semicolon)) { return Err(Error::UnexpectedToken( @@ -451,16 +538,6 @@ impl<'a> Parser<'a> { }) } - TokenType::Identifier(_) - if self_matches_peek!(self, TokenType::Symbol(Symbol::Assign)) => - { - let spanned_assign = self.spanned(|p| p.assignment())?; - Some(Spanned { - span: spanned_assign.span, - node: Expression::Assignment(spanned_assign), - }) - } - TokenType::Identifier(ref id) => { let span = self.current_span(); Some(Spanned { @@ -489,24 +566,36 @@ impl<'a> Parser<'a> { } TokenType::Symbol(Symbol::LParen) => { - // Priority handles its own spanning self.spanned(|p| p.priority())?.node.map(|node| *node) } TokenType::Symbol(Symbol::Minus) => { - // Need to handle span manually because unary call is next let start_span = self.current_span(); self.assign_next()?; let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?; + // NOTE: Unary negation can also have postfix applied to the inner expression + // But generally -a.b parses as -(a.b), which is what parse_postfix ensures if called here. + // However, we call parse_postfix on the RESULT of unary in expression(), so + // `expression` sees `Negation`. `parse_postfix` doesn't apply to Negation node unless we allow it? + // Actually, `x.y` binds tighter than `-`. `postfix` logic belongs inside `unary` logic or + // `expression` logic. + // If I have `-x.y`, standard precedence says `-(x.y)`. + // `unary` returns `Negation(x)`. Then `expression` calls `postfix` on `Negation(x)`. + // `postfix` loop runs on `Negation`. This implies `(-x).y`. This is usually WRONG. + // `.` binds tighter than `-`. + // So `unary` must call `postfix` on the *operand* of the negation. + + let inner_with_postfix = self.parse_postfix(inner_expr)?; + let combined_span = Span { start_line: start_span.start_line, start_col: start_span.start_col, - end_line: inner_expr.span.end_line, - end_col: inner_expr.span.end_col, + end_line: inner_with_postfix.span.end_line, + end_col: inner_with_postfix.span.end_col, }; Some(Spanned { span: combined_span, - node: Expression::Negation(boxed!(inner_expr)), + node: Expression::Negation(boxed!(inner_with_postfix)), }) } @@ -514,17 +603,18 @@ impl<'a> Parser<'a> { let start_span = self.current_span(); self.assign_next()?; let inner_expr = self.unary()?.ok_or(Error::UnexpectedEOF)?; + let inner_with_postfix = self.parse_postfix(inner_expr)?; let combined_span = Span { start_line: start_span.start_line, start_col: start_span.start_col, - end_line: inner_expr.span.end_line, - end_col: inner_expr.span.end_col, + end_line: inner_with_postfix.span.end_line, + end_col: inner_with_postfix.span.end_col, }; Some(Spanned { span: combined_span, node: Expression::Logical(Spanned { span: combined_span, - node: LogicalExpression::Not(boxed!(inner_expr)), + node: LogicalExpression::Not(boxed!(inner_with_postfix)), }), }) } @@ -543,41 +633,44 @@ impl<'a> Parser<'a> { fn get_infix_child_node(&mut self) -> Result, Error> { let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?; - match current_token.token_type { + let start_span = self.current_span(); + + let expr = match current_token.token_type { TokenType::Number(_) | TokenType::Boolean(_) => { let lit = self.spanned(|p| p.literal())?; - Ok(Spanned { + Spanned { span: lit.span, node: Expression::Literal(lit), - }) + } } TokenType::Identifier(ref ident) if !self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) => { + // This is a Variable. We need to check for Postfix operations on it. let span = self.current_span(); - Ok(Spanned { + Spanned { span, node: Expression::Variable(Spanned { span, node: ident.clone(), }), - }) + } } - TokenType::Symbol(Symbol::LParen) => Ok(*self + TokenType::Symbol(Symbol::LParen) => *self .spanned(|p| p.priority())? .node - .ok_or(Error::UnexpectedEOF)?), + .ok_or(Error::UnexpectedEOF)?, + TokenType::Identifier(_) if self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) => { let inv = self.spanned(|p| p.invocation())?; - Ok(Spanned { + Spanned { span: inv.span, node: Expression::Invocation(inv), - }) + } } TokenType::Symbol(Symbol::Minus) => { - let start_span = self.current_span(); self.assign_next()?; let inner = self.get_infix_child_node()?; let span = Span { @@ -586,13 +679,12 @@ impl<'a> Parser<'a> { end_line: inner.span.end_line, end_col: inner.span.end_col, }; - Ok(Spanned { + Spanned { span, node: Expression::Negation(boxed!(inner)), - }) + } } TokenType::Symbol(Symbol::LogicalNot) => { - let start_span = self.current_span(); self.assign_next()?; let inner = self.get_infix_child_node()?; let span = Span { @@ -601,19 +693,25 @@ impl<'a> Parser<'a> { end_line: inner.span.end_line, end_col: inner.span.end_col, }; - Ok(Spanned { + Spanned { span, node: Expression::Logical(Spanned { span, node: LogicalExpression::Not(boxed!(inner)), }), - }) + } } - _ => Err(Error::UnexpectedToken( - self.current_span(), - current_token.clone(), - )), - } + _ => { + return Err(Error::UnexpectedToken( + self.current_span(), + current_token.clone(), + )); + } + }; + + // Important: We must check for postfix operations here too + // e.g. a + b.c + self.parse_postfix(expr) } fn device(&mut self) -> Result { @@ -665,39 +763,6 @@ impl<'a> Parser<'a> { }) } - fn assignment(&mut self) -> Result { - let identifier_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?; - let identifier_span = Self::token_to_span(identifier_token); - let identifier = match identifier_token.token_type { - TokenType::Identifier(ref id) => id.clone(), - _ => { - return Err(Error::UnexpectedToken( - self.current_span(), - self.current_token.clone().ok_or(Error::UnexpectedEOF)?, - )); - } - }; - - let current_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?.clone(); - if !token_matches!(current_token, TokenType::Symbol(Symbol::Assign)) { - return Err(Error::UnexpectedToken( - Self::token_to_span(¤t_token), - current_token.clone(), - )); - } - self.assign_next()?; - - let expression = self.expression()?.ok_or(Error::UnexpectedEOF)?; - - Ok(AssignmentExpression { - identifier: Spanned { - span: identifier_span, - node: identifier, - }, - expression: boxed!(expression), - }) - } - fn infix(&mut self, previous: Spanned) -> Result, Error> { let current_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?.clone(); @@ -708,7 +773,9 @@ impl<'a> Parser<'a> { | Expression::Priority(_) | Expression::Literal(_) | Expression::Variable(_) - | Expression::Negation(_) => {} + | Expression::Negation(_) + | Expression::MemberAccess(_) + | Expression::MethodCall(_) => {} _ => { return Err(Error::InvalidSyntax( self.current_span(), @@ -722,9 +789,10 @@ impl<'a> Parser<'a> { let mut temp_token = current_token.clone(); + // Include Assign in the operator loop while token_matches!( temp_token, - TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() + TokenType::Symbol(s) if s.is_operator() || s.is_comparison() || s.is_logical() || matches!(s, Symbol::Assign) ) { let operator = match temp_token.token_type { TokenType::Symbol(s) => s, @@ -955,6 +1023,37 @@ impl<'a> Parser<'a> { } operators.retain(|symbol| !matches!(symbol, Symbol::LogicalOr)); + // --- PRECEDENCE LEVEL 8: Assignment (=) --- + // Assignment is Right Associative: a = b = c => a = (b = c) + // We iterate Right to Left + for (i, operator) in operators.iter().enumerate().rev() { + if matches!(operator, Symbol::Assign) { + let right = expressions.remove(i + 1); + let left = expressions.remove(i); + let span = Span { + start_line: left.span.start_line, + start_col: left.span.start_col, + end_line: right.span.end_line, + end_col: right.span.end_col, + }; + + expressions.insert( + i, + Spanned { + span, + node: Expression::Assignment(Spanned { + span, + node: AssignmentExpression { + assignee: boxed!(left), + expression: boxed!(right), + }, + }), + }, + ); + } + } + operators.retain(|symbol| !matches!(symbol, Symbol::Assign)); + if expressions.len() != 1 || !operators.is_empty() { return Err(Error::InvalidSyntax( self.current_span(), @@ -1506,7 +1605,6 @@ impl<'a> Parser<'a> { Literal::String(variable), ))) } - // ... (implementing other syscalls similarly using patterns above) "setOnDevice" => { check_length(self, &invocation.arguments, 3)?; let mut args = invocation.arguments.into_iter(); @@ -1531,23 +1629,10 @@ impl<'a> Parser<'a> { boxed!(variable), ))) } - _ => { - // For Math functions or unknown functions - if SysCall::is_syscall(&invocation.name.node) { - // Attempt to parse as math if applicable, or error if strict - // Here we are falling back to simple handling or error. - // Since Math isn't fully expanded in this snippet, we return Unsupported. - Err(Error::UnsupportedKeyword( - self.current_span(), - self.current_token.clone().ok_or(Error::UnexpectedEOF)?, - )) - } else { - Err(Error::UnsupportedKeyword( - self.current_span(), - self.current_token.clone().ok_or(Error::UnexpectedEOF)?, - )) - } - } + _ => Err(Error::UnsupportedKeyword( + self.current_span(), + self.current_token.clone().ok_or(Error::UnexpectedEOF)?, + )), } } } diff --git a/rust_compiler/libs/parser/src/tree_node.rs b/rust_compiler/libs/parser/src/tree_node.rs index a968ed4..1d39d64 100644 --- a/rust_compiler/libs/parser/src/tree_node.rs +++ b/rust_compiler/libs/parser/src/tree_node.rs @@ -74,13 +74,13 @@ impl std::fmt::Display for LogicalExpression { #[derive(Debug, PartialEq, Eq)] pub struct AssignmentExpression { - pub identifier: Spanned, + pub assignee: Box>, pub expression: Box>, } impl std::fmt::Display for AssignmentExpression { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "({} = {})", self.identifier, self.expression) + write!(f, "({} = {})", self.assignee, self.expression) } } @@ -145,6 +145,41 @@ impl std::fmt::Display for InvocationExpression { } } +#[derive(Debug, PartialEq, Eq)] +pub struct MemberAccessExpression { + pub object: Box>, + pub member: Spanned, +} + +impl std::fmt::Display for MemberAccessExpression { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}", self.object, self.member) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct MethodCallExpression { + pub object: Box>, + pub method: Spanned, + pub arguments: Vec>, +} + +impl std::fmt::Display for MethodCallExpression { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}.{}({})", + self.object, + self.method, + self.arguments + .iter() + .map(|e| e.to_string()) + .collect::>() + .join(", ") + ) + } +} + #[derive(Debug, PartialEq, Eq)] pub enum LiteralOrVariable { Literal(Literal), @@ -290,6 +325,8 @@ pub enum Expression { Literal(Spanned), Logical(Spanned), Loop(Spanned), + MemberAccess(Spanned), + MethodCall(Spanned), Negation(Box>), Priority(Box>), Return(Box>), @@ -314,6 +351,8 @@ impl std::fmt::Display for Expression { Expression::Literal(l) => write!(f, "{}", l), Expression::Logical(e) => write!(f, "{}", e), Expression::Loop(e) => write!(f, "{}", e), + Expression::MemberAccess(e) => write!(f, "{}", e), + Expression::MethodCall(e) => write!(f, "{}", e), Expression::Negation(e) => write!(f, "(-{})", e), Expression::Priority(e) => write!(f, "({})", e), Expression::Return(e) => write!(f, "(return {})", e), @@ -323,3 +362,4 @@ impl std::fmt::Display for Expression { } } } +