wip -- dot notation

This commit is contained in:
2025-12-01 18:46:59 -07:00
parent c911b0af18
commit 836fd3bf99
3 changed files with 407 additions and 124 deletions

View File

@@ -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<Option<CompilationResult>, 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))
}
}

View File

@@ -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<T, Error>,
{
// 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::<Spanned<Expression>>::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<Option<Spanned<tree_node::Expression>>, 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<Expression>,
) -> Result<Spanned<Expression>, 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::<Spanned<Expression>>::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<Option<Spanned<tree_node::Expression>>, 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<Spanned<tree_node::Expression>, 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(
}
_ => {
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<DeviceDeclarationExpression, Error> {
@@ -665,39 +763,6 @@ impl<'a> Parser<'a> {
})
}
fn assignment(&mut self) -> Result<AssignmentExpression, Error> {
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(&current_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<Expression>) -> Result<Spanned<Expression>, 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(
_ => 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)?,
))
}
}
)),
}
}
}

View File

@@ -74,13 +74,13 @@ impl std::fmt::Display for LogicalExpression {
#[derive(Debug, PartialEq, Eq)]
pub struct AssignmentExpression {
pub identifier: Spanned<String>,
pub assignee: Box<Spanned<Expression>>,
pub expression: Box<Spanned<Expression>>,
}
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<Spanned<Expression>>,
pub member: Spanned<String>,
}
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<Spanned<Expression>>,
pub method: Spanned<String>,
pub arguments: Vec<Spanned<Expression>>,
}
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::<Vec<String>>()
.join(", ")
)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum LiteralOrVariable {
Literal(Literal),
@@ -290,6 +325,8 @@ pub enum Expression {
Literal(Spanned<Literal>),
Logical(Spanned<LogicalExpression>),
Loop(Spanned<LoopExpression>),
MemberAccess(Spanned<MemberAccessExpression>),
MethodCall(Spanned<MethodCallExpression>),
Negation(Box<Spanned<Expression>>),
Priority(Box<Spanned<Expression>>),
Return(Box<Spanned<Expression>>),
@@ -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 {
}
}
}