diff --git a/src/semantic/mod.rs b/src/semantic/mod.rs index b257e7e..6daa552 100644 --- a/src/semantic/mod.rs +++ b/src/semantic/mod.rs @@ -6,12 +6,12 @@ use std::collections::HashSet; use error::{ IncompatibleFunctionAnnotation, InvalidNamespaceName, MissingFunctionDeclaration, - UnexpectedExpression, UnresolvedMacroUsage, + UnresolvedMacroUsage, }; use crate::{ base::{self, source_file::SourceElement as _, Handler}, - lexical::token::{KeywordKind, MacroStringLiteral, MacroStringLiteralPart}, + lexical::token::{MacroStringLiteral, MacroStringLiteralPart}, syntax::syntax_tree::{ condition::{ BinaryCondition, Condition, ParenthesizedCondition, PrimaryCondition, UnaryCondition, @@ -292,18 +292,9 @@ impl Semicolon { handler: &impl Handler, ) -> Result<(), error::Error> { match self.statement() { - SemicolonStatement::Expression(expr) => match expr { - Expression::Primary(Primary::FunctionCall(func)) => { - func.analyze_semantics(function_names, macro_names, handler) - } - Expression::Primary(unexpected) => { - let error = error::Error::UnexpectedExpression(UnexpectedExpression( - Expression::Primary(unexpected.clone()), - )); - handler.receive(error.clone()); - Err(error) - } - }, + SemicolonStatement::Expression(expr) => { + expr.analyze_semantics(function_names, macro_names, handler) + } SemicolonStatement::VariableDeclaration(decl) => { decl.analyze_semantics(function_names, macro_names, handler) } @@ -449,6 +440,10 @@ impl Expression { ) -> Result<(), error::Error> { match self { Self::Primary(prim) => prim.analyze_semantics(function_names, macro_names, handler), + Self::Binary(_bin) => { + // TODO: correctly analyze the semantics of the binary expression + Ok(()) + } } } } @@ -527,90 +522,96 @@ impl VariableDeclaration { /// Analyzes the semantics of a variable declaration. pub fn analyze_semantics( &self, - function_names: &HashSet, - macro_names: &HashSet, - handler: &impl Handler, + _function_names: &HashSet, + _macro_names: &HashSet, + _handler: &impl Handler, ) -> Result<(), error::Error> { - match self { - Self::Array(array) => array.assignment().as_ref().map_or(Ok(()), |assignment| { - assignment - .expression() - .analyze_semantics(function_names, macro_names, handler) - }), - Self::Single(single) => { - if let Some(assignment) = single.assignment() { - let err = match single.variable_type().keyword { - KeywordKind::Int => !matches!( - assignment.expression(), - // TODO: also allow macro identifier but not macro string literal - Expression::Primary( - Primary::Integer(_) | Primary::Lua(_) | Primary::FunctionCall(_) - ) - ), - KeywordKind::Bool => !matches!( - assignment.expression(), - Expression::Primary( - Primary::Boolean(_) | Primary::Lua(_) | Primary::FunctionCall(_) - ) - ), - _ => false, - }; - if err { - let err = error::Error::UnexpectedExpression(UnexpectedExpression( - assignment.expression().clone(), - )); - handler.receive(err.clone()); - return Err(err); - } - assignment - .expression() - .analyze_semantics(function_names, macro_names, handler) - } else { - Ok(()) - } - } - Self::Score(score) => { - if let Some((_, assignment)) = score.target_assignment() { - // TODO: also allow macro identifier but not macro string literal - if !matches!( - assignment.expression(), - Expression::Primary( - Primary::Integer(_) | Primary::Lua(_) | Primary::FunctionCall(_) - ) - ) { - let err = error::Error::UnexpectedExpression(UnexpectedExpression( - assignment.expression().clone(), - )); - handler.receive(err.clone()); - return Err(err); - } - assignment - .expression() - .analyze_semantics(function_names, macro_names, handler) - } else { - Ok(()) - } - } - Self::Tag(tag) => { - if let Some((_, assignment)) = tag.target_assignment() { - if !matches!( - assignment.expression(), - Expression::Primary(Primary::Boolean(_) | Primary::Lua(_)) - ) { - let err = error::Error::UnexpectedExpression(UnexpectedExpression( - assignment.expression().clone(), - )); - handler.receive(err.clone()); - return Err(err); - } - assignment - .expression() - .analyze_semantics(function_names, macro_names, handler) - } else { - Ok(()) - } - } - } + // match self { + // Self::Array(array) => array.assignment().as_ref().map_or(Ok(()), |assignment| { + // assignment + // .expression() + // .analyze_semantics(function_names, macro_names, handler) + // }), + // Self::Single(single) => { + // if let Some(assignment) = single.assignment() { + // let err = match single.variable_type().keyword { + // KeywordKind::Int => { + // !matches!( + // assignment.expression(), + // // TODO: also allow macro identifier but not macro string literal + // Expression::Primary( + // Primary::Integer(_) + // | Primary::Lua(_) + // | Primary::FunctionCall(_) + // ) + // ) && !matches!(assignment.expression(), Expression::Binary(..)) + // } + // KeywordKind::Bool => !matches!( + // assignment.expression(), + // Expression::Primary( + // Primary::Boolean(_) | Primary::Lua(_) | Primary::FunctionCall(_) + // ) + // ), + // _ => false, + // }; + // if err { + // let err = error::Error::UnexpectedExpression(UnexpectedExpression( + // assignment.expression().clone(), + // )); + // handler.receive(err.clone()); + // return Err(err); + // } + // assignment + // .expression() + // .analyze_semantics(function_names, macro_names, handler) + // } else { + // Ok(()) + // } + // } + // Self::Score(score) => { + // if let Some((_, assignment)) = score.target_assignment() { + // // TODO: also allow macro identifier but not macro string literal + // if !matches!( + // assignment.expression(), + // Expression::Primary( + // Primary::Integer(_) | Primary::Lua(_) | Primary::FunctionCall(_) + // ) + // ) { + // let err = error::Error::UnexpectedExpression(UnexpectedExpression( + // assignment.expression().clone(), + // )); + // handler.receive(err.clone()); + // return Err(err); + // } + // assignment + // .expression() + // .analyze_semantics(function_names, macro_names, handler) + // } else { + // Ok(()) + // } + // } + // Self::Tag(tag) => { + // if let Some((_, assignment)) = tag.target_assignment() { + // if !matches!( + // assignment.expression(), + // Expression::Primary(Primary::Boolean(_) | Primary::Lua(_)) + // ) { + // let err = error::Error::UnexpectedExpression(UnexpectedExpression( + // assignment.expression().clone(), + // )); + // handler.receive(err.clone()); + // return Err(err); + // } + // assignment + // .expression() + // .analyze_semantics(function_names, macro_names, handler) + // } else { + // Ok(()) + // } + // } + // } + // TODO: correctly analyze the semantics of the variable declaration + Ok(()) } } diff --git a/src/syntax/error.rs b/src/syntax/error.rs index 4600214..c0483cf 100644 --- a/src/syntax/error.rs +++ b/src/syntax/error.rs @@ -41,6 +41,7 @@ pub enum SyntaxKind { AnyStringLiteral, Statement, Expression, + Operator, Type, ExecuteBlock, ExecuteBlockTail, @@ -79,6 +80,7 @@ impl SyntaxKind { Self::AnyStringLiteral => "a (macro) string literal".to_string(), Self::Statement => "a statement syntax".to_string(), Self::Expression => "an expression syntax".to_string(), + Self::Operator => "an operator".to_string(), Self::Type => "a type syntax".to_string(), Self::ExecuteBlock => "an execute block syntax".to_string(), Self::ExecuteBlockTail => "an execute block tail syntax".to_string(), diff --git a/src/syntax/syntax_tree/expression.rs b/src/syntax/syntax_tree/expression.rs index 5387749..a5ce599 100644 --- a/src/syntax/syntax_tree/expression.rs +++ b/src/syntax/syntax_tree/expression.rs @@ -1,5 +1,7 @@ //! Syntax tree nodes for expressions. +use std::cmp::Ordering; + use enum_as_inner::EnumAsInner; use getset::Getters; @@ -25,43 +27,137 @@ use crate::{ use super::ConnectedList; +/// Represents a binary operator in the syntax tree. +/// +/// Syntax Synopsis: +/// ```ebnf +/// BinaryOperator: +/// '+' +/// | '-' +/// | '*' +/// | '/' +/// | '%' +/// | '==' +/// | '!=' +/// | '<' +/// | '<=' +/// | '>' +/// | '>=' +/// | '&&' +/// | '||' +/// ; +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[allow(missing_docs)] +pub enum BinaryOperator { + Add(Punctuation), + Subtract(Punctuation), + Multiply(Punctuation), + Divide(Punctuation), + Modulo(Punctuation), + Equal(Punctuation, Punctuation), + NotEqual(Punctuation, Punctuation), + LessThan(Punctuation), + LessThanOrEqual(Punctuation, Punctuation), + GreaterThan(Punctuation), + GreaterThanOrEqual(Punctuation, Punctuation), + LogicalAnd(Punctuation, Punctuation), + LogicalOr(Punctuation, Punctuation), +} + +impl BinaryOperator { + /// Gets the precedence of the operator (the higher the number, the first it will be evaluated) + /// + /// The least operator has precedence 1. + #[must_use] + pub fn get_precedence(&self) -> u32 { + match self { + Self::LogicalOr(..) => 1, + Self::LogicalAnd(..) => 2, + Self::Equal(..) | Self::NotEqual(..) => 3, + Self::LessThan(..) + | Self::LessThanOrEqual(..) + | Self::GreaterThan(..) + | Self::GreaterThanOrEqual(..) => 4, + Self::Add(..) | Self::Subtract(..) => 5, + Self::Multiply(..) | Self::Divide(..) | Self::Modulo(..) => 6, + } + } +} + +impl SourceElement for BinaryOperator { + fn span(&self) -> Span { + match self { + Self::Add(token) + | Self::Subtract(token) + | Self::Multiply(token) + | Self::Divide(token) + | Self::Modulo(token) + | Self::LessThan(token) + | Self::GreaterThan(token) => token.span.clone(), + Self::Equal(token, token1) + | Self::NotEqual(token, token1) + | Self::LessThanOrEqual(token, token1) + | Self::GreaterThanOrEqual(token, token1) + | Self::LogicalAnd(token, token1) + | Self::LogicalOr(token, token1) => token.span().join(&token1.span).unwrap(), + } + } +} + +/// Represents a binary expression in the syntax tree. +/// +/// Syntax Synopsis: +/// ```ebnf +/// Binary: +/// Expression BinaryOperator Expression +/// ; +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Binary { + /// The left operand of the binary expression. + #[get = "pub"] + left_operand: Box, + /// The operator of the binary expression. + #[get = "pub"] + operator: BinaryOperator, + /// The right operand of the binary expression. + #[get = "pub"] + right_operand: Box, +} + +impl SourceElement for Binary { + fn span(&self) -> Span { + self.left_operand + .span() + .join(&self.right_operand.span()) + .unwrap() + } +} + /// Represents an expression in the syntax tree. /// /// Syntax Synopsis: /// /// ```ebnf /// Expression: -/// Primary +/// Primary | Binary /// ``` #[allow(missing_docs)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, EnumAsInner)] pub enum Expression { Primary(Primary), + Binary(Binary), } impl SourceElement for Expression { fn span(&self) -> Span { match self { Self::Primary(primary) => primary.span(), - } - } -} - -impl Expression { - /// Checks if the expression is compile-time. - #[must_use] - pub fn is_comptime(&self) -> bool { - match self { - Self::Primary(primary) => primary.is_comptime(), - } - } - - /// Evaluate at compile-time to a string. - #[must_use] - pub fn comptime_eval(&self) -> Option { - match self { - Self::Primary(primary) => primary.comptime_eval(), + Self::Binary(binary) => binary.span(), } } } @@ -104,38 +200,6 @@ impl SourceElement for Primary { } } -impl Primary { - /// Checks if the primary expression is compile-time. - #[must_use] - pub fn is_comptime(&self) -> bool { - match self { - Self::Boolean(_) - | Self::Integer(_) - | Self::StringLiteral(_) - | Self::MacroStringLiteral(_) - | Self::Lua(_) => true, - Self::FunctionCall(func) => func.is_comptime(), - } - } - - /// Evaluate at compile-time to a string. - #[must_use] - pub fn comptime_eval(&self) -> Option { - match self { - Self::Boolean(boolean) => Some(boolean.span.str().to_string()), - Self::Integer(int) => Some(int.span.str().to_string()), - Self::StringLiteral(string_literal) => Some(string_literal.str_content().to_string()), - // TODO: correctly evaluate lua code - Self::Lua(lua) => lua.eval_string(&VoidHandler).ok().flatten(), - Self::MacroStringLiteral(macro_string_literal) => { - Some(macro_string_literal.str_content()) - } - // TODO: correctly evaluate function calls - Self::FunctionCall(_) => None, - } - } -} - /// Represents a function call in the syntax tree. /// /// Syntax Synopsis: @@ -171,16 +235,6 @@ impl SourceElement for FunctionCall { } } -impl FunctionCall { - /// Checks if the function call is compile-time. - #[must_use] - pub fn is_comptime(&self) -> bool { - self.arguments - .as_ref() - .map_or(true, |args| args.elements().all(|elem| elem.is_comptime())) - } -} - /// Represents a lua code block in the syntax tree. /// /// Syntax Synopsis: @@ -246,7 +300,54 @@ impl<'a> Parser<'a> { &mut self, handler: &impl Handler, ) -> ParseResult { - self.parse_primary(handler).map(Expression::Primary) + let mut first_primary = Expression::Primary(self.parse_primary(handler)?); + let mut expressions = Vec::new(); + + // loop until there are no more binary operators + while let Ok(binary_operator) = self.try_parse(|p| p.parse_binary_operator(&VoidHandler)) { + expressions.push(( + binary_operator, + Some(Expression::Primary(self.parse_primary(handler)?)), + )); + } + + // fold based on precedence and associativity + + let mut candidate_index = 0; + let mut current_precedence; + + while !expressions.is_empty() { + current_precedence = 0; + + for (index, (binary_operator, _)) in expressions.iter().enumerate() { + let new_precedence = binary_operator.get_precedence(); + if new_precedence.cmp(¤t_precedence) == Ordering::Greater { + current_precedence = new_precedence; + candidate_index = index; + } + } + + assert!(current_precedence > 0, "Invalid precedence"); + + if candidate_index == 0 { + let (binary_operator, rhs) = expressions.remove(0); + first_primary = Expression::Binary(Binary { + left_operand: Box::new(first_primary), + operator: binary_operator, + right_operand: Box::new(rhs.expect("checked above")), + }); + } else { + let (binary_operator, rhs) = expressions.remove(candidate_index); + + expressions[candidate_index - 1].1 = Some(Expression::Binary(Binary { + left_operand: Box::new(expressions[candidate_index - 1].1.take().unwrap()), + operator: binary_operator, + right_operand: Box::new(rhs.expect("checked above")), + })); + } + } + + Ok(first_primary) } /// Parses an [`Primary`] @@ -404,4 +505,68 @@ impl<'a> Parser<'a> { } } } + + fn parse_binary_operator( + &mut self, + handler: &impl Handler, + ) -> ParseResult { + match self.next_significant_token() { + Reading::Atomic(Token::Punctuation(punc)) => match punc.punctuation { + '+' => Ok(BinaryOperator::Add(punc)), + '-' => Ok(BinaryOperator::Subtract(punc)), + '*' => Ok(BinaryOperator::Multiply(punc)), + '/' => Ok(BinaryOperator::Divide(punc)), + '%' => Ok(BinaryOperator::Modulo(punc)), + '!' => { + let equal = self.parse_punctuation('=', false, handler)?; + Ok(BinaryOperator::NotEqual(punc, equal)) + } + '=' => { + let equal = self.parse_punctuation('=', false, handler)?; + Ok(BinaryOperator::Equal(punc, equal)) + } + '<' => { + let equal = self.try_parse(|p| p.parse_punctuation('=', false, &VoidHandler)); + if let Ok(equal) = equal { + Ok(BinaryOperator::LessThanOrEqual(punc, equal)) + } else { + Ok(BinaryOperator::LessThan(punc)) + } + } + '>' => { + let equal = self.try_parse(|p| p.parse_punctuation('=', false, &VoidHandler)); + if let Ok(equal) = equal { + Ok(BinaryOperator::GreaterThanOrEqual(punc, equal)) + } else { + Ok(BinaryOperator::GreaterThan(punc)) + } + } + '&' => { + let second = self.parse_punctuation('&', false, handler)?; + Ok(BinaryOperator::LogicalAnd(punc, second)) + } + '|' => { + let second = self.parse_punctuation('|', false, handler)?; + Ok(BinaryOperator::LogicalOr(punc, second)) + } + + _ => { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { + expected: syntax::error::SyntaxKind::Operator, + found: Some(Token::Punctuation(punc)), + }); + handler.receive(err.clone()); + Err(err) + } + }, + unexpected => { + let err = Error::UnexpectedSyntax(UnexpectedSyntax { + expected: syntax::error::SyntaxKind::Operator, + found: unexpected.into_token(), + }); + handler.receive(err.clone()); + Err(err) + } + } + } } diff --git a/src/transpile/expression.rs b/src/transpile/expression.rs index 37eadc1..b3dc9de 100644 --- a/src/transpile/expression.rs +++ b/src/transpile/expression.rs @@ -2,11 +2,15 @@ use std::fmt::Display; -use crate::syntax::syntax_tree::expression::{Expression, Primary}; +use crate::{ + base::VoidHandler, + syntax::syntax_tree::expression::{Binary, BinaryOperator, Expression, Primary}, +}; #[cfg(feature = "shulkerbox")] use std::sync::Arc; +use derive_more::From; #[cfg(feature = "shulkerbox")] use shulkerbox::prelude::{Command, Condition, Execute}; @@ -19,6 +23,25 @@ use crate::{ transpile::{error::FunctionArgumentsNotAllowed, TranspileError}, }; +/// Compile-time evaluated value +#[allow(missing_docs)] +#[derive(Debug, Clone, PartialEq, Eq, From)] +pub enum ComptimeValue { + Boolean(bool), + Integer(i64), + String(String), +} + +impl Display for ComptimeValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Boolean(boolean) => write!(f, "{boolean}"), + Self::Integer(int) => write!(f, "{int}"), + Self::String(string) => write!(f, "{string}"), + } + } +} + /// The type of an expression. #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq, Eq, Copy)] @@ -100,6 +123,16 @@ impl Expression { pub fn can_yield_type(&self, r#type: ValueType) -> bool { match self { Self::Primary(primary) => primary.can_yield_type(r#type), + Self::Binary(_binary) => todo!(), + } + } + + /// Evaluate at compile-time. + #[must_use] + pub fn comptime_eval(&self) -> Option { + match self { + Self::Primary(primary) => primary.comptime_eval(), + Self::Binary(binary) => binary.comptime_eval(), } } } @@ -121,6 +154,86 @@ impl Primary { Self::StringLiteral(_) | Self::MacroStringLiteral(_) => false, } } + + /// Evaluate at compile-time. + #[must_use] + pub fn comptime_eval(&self) -> Option { + match self { + Self::Boolean(boolean) => Some(ComptimeValue::Boolean(boolean.value())), + Self::Integer(int) => Some(ComptimeValue::Integer(int.as_i64())), + Self::StringLiteral(string_literal) => Some(ComptimeValue::String( + string_literal.str_content().to_string(), + )), + // TODO: correctly evaluate lua code + Self::Lua(lua) => lua + .eval_string(&VoidHandler) + .ok() + .flatten() + .map(ComptimeValue::String), + Self::MacroStringLiteral(macro_string_literal) => { + // TODO: mark as containing macros + Some(ComptimeValue::String(macro_string_literal.str_content())) + } + // TODO: correctly evaluate function calls + Self::FunctionCall(_) => None, + } + } +} + +impl Binary { + /// Evaluate at compile-time. + #[must_use] + pub fn comptime_eval(&self) -> Option { + let left = self.left_operand().comptime_eval()?; + let right = self.right_operand().comptime_eval()?; + + match (left, right) { + (ComptimeValue::Boolean(left), ComptimeValue::Boolean(right)) => { + match self.operator() { + BinaryOperator::Equal(..) => Some(ComptimeValue::Boolean(left == right)), + BinaryOperator::NotEqual(..) => Some(ComptimeValue::Boolean(left != right)), + BinaryOperator::LogicalAnd(..) => Some(ComptimeValue::Boolean(left && right)), + BinaryOperator::LogicalOr(..) => Some(ComptimeValue::Boolean(left || right)), + _ => None, + } + } + (ComptimeValue::Integer(left), ComptimeValue::Integer(right)) => { + match self.operator() { + BinaryOperator::Add(..) => left.checked_add(right).map(ComptimeValue::Integer), + BinaryOperator::Subtract(..) => { + left.checked_sub(right).map(ComptimeValue::Integer) + } + BinaryOperator::Multiply(..) => { + left.checked_mul(right).map(ComptimeValue::Integer) + } + BinaryOperator::Divide(..) => { + left.checked_div(right).map(ComptimeValue::Integer) + } + BinaryOperator::Modulo(..) => { + left.checked_rem(right).map(ComptimeValue::Integer) + } + BinaryOperator::Equal(..) => Some(ComptimeValue::Boolean(left == right)), + BinaryOperator::NotEqual(..) => Some(ComptimeValue::Boolean(left != right)), + BinaryOperator::LessThan(..) => Some(ComptimeValue::Boolean(left < right)), + BinaryOperator::LessThanOrEqual(..) => { + Some(ComptimeValue::Boolean(left <= right)) + } + BinaryOperator::GreaterThan(..) => Some(ComptimeValue::Boolean(left > right)), + BinaryOperator::GreaterThanOrEqual(..) => { + Some(ComptimeValue::Boolean(left >= right)) + } + _ => None, + } + } + (ComptimeValue::String(left), ComptimeValue::String(right)) => match self.operator() { + BinaryOperator::Add(..) => Some(ComptimeValue::String(left + &right)), + BinaryOperator::Equal(..) => Some(ComptimeValue::Boolean(left == right)), + BinaryOperator::NotEqual(..) => Some(ComptimeValue::Boolean(left != right)), + _ => None, + }, + _ => None, + } + } } #[cfg(feature = "shulkerbox")] @@ -137,6 +250,9 @@ impl Transpiler { Expression::Primary(primary) => { self.transpile_primary_expression(primary, target, scope, handler) } + Expression::Binary(binary) => { + self.transpile_binary_expression(binary, target, scope, handler) + } } } @@ -301,4 +417,101 @@ impl Transpiler { } } } + + #[expect(clippy::needless_pass_by_ref_mut)] + fn transpile_binary_expression( + &mut self, + binary: &Binary, + target: &DataLocation, + _scope: &Arc, + _handler: &impl Handler, + ) -> TranspileResult> { + match binary.comptime_eval() { + Some(ComptimeValue::Integer(value)) => match target { + DataLocation::ScoreboardValue { objective, target } => Ok(vec![Command::Raw( + format!("scoreboard players set {target} {objective} {value}"), + )]), + DataLocation::Tag { .. } => Err(TranspileError::MismatchedTypes(MismatchedTypes { + expected_type: ValueType::Tag, + expression: binary.span(), + })), + DataLocation::Storage { + storage_name, + path, + r#type, + } => { + if matches!( + r#type, + StorageType::Byte + | StorageType::Double + | StorageType::Int + | StorageType::Long + ) { + Ok(vec![Command::Raw(format!( + "data modify storage {storage_name} {path} set value {value}{suffix}", + suffix = r#type.suffix(), + ))]) + } else { + Err(TranspileError::MismatchedTypes(MismatchedTypes { + expression: binary.span(), + expected_type: ValueType::NumberStorage, + })) + } + } + }, + Some(ComptimeValue::Boolean(value)) => match target { + DataLocation::ScoreboardValue { objective, target } => { + Ok(vec![Command::Raw(format!( + "scoreboard players set {target} {objective} {value}", + value = u8::from(value) + ))]) + } + DataLocation::Tag { tag_name, entity } => Ok(vec![Command::Raw(format!( + "tag {entity} {op} {tag_name}", + op = if value { "add" } else { "remove" } + ))]), + DataLocation::Storage { + storage_name, + path, + r#type, + } => { + if matches!(r#type, StorageType::Boolean) { + Ok(vec![Command::Raw(format!( + "data modify storage {storage_name} {path} set value {value}{suffix}", + value = u8::from(value), + suffix = r#type.suffix(), + ))]) + } else { + Err(TranspileError::MismatchedTypes(MismatchedTypes { + expression: binary.span(), + expected_type: ValueType::NumberStorage, + })) + } + } + }, + Some(ComptimeValue::String(_)) => { + Err(TranspileError::MismatchedTypes(MismatchedTypes { + expected_type: match target { + DataLocation::ScoreboardValue { .. } => ValueType::ScoreboardValue, + DataLocation::Tag { .. } => ValueType::Tag, + DataLocation::Storage { .. } => ValueType::NumberStorage, + }, + expression: binary.span(), + })) + } + None => { + let _left = binary.left_operand(); + let _right = binary.right_operand(); + let operator = binary.operator(); + + match operator { + BinaryOperator::Add(_) => { + // let temp_name + todo!() + } + _ => todo!(), + } + } + } + } } diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 0ec184a..ddebf39 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -323,8 +323,10 @@ impl Transpiler { }, |val| match val { TranspileAnnotationValue::None => Ok(identifier_span.str().to_string()), - TranspileAnnotationValue::Expression(expr) => { - expr.comptime_eval().ok_or_else(|| { + TranspileAnnotationValue::Expression(expr) => expr + .comptime_eval() + .map(|val| val.to_string()) + .ok_or_else(|| { let err = TranspileError::IllegalAnnotationContent( IllegalAnnotationContent { annotation: identifier_span.clone(), @@ -334,8 +336,7 @@ impl Transpiler { ); handler.receive(err.clone()); err - }) - } + }), TranspileAnnotationValue::Map(_) => { let err = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent { @@ -421,6 +422,7 @@ impl Transpiler { Expression::Primary(Primary::MacroStringLiteral(literal)) => { Ok(literal.str_content()) } + Expression::Binary(_) => todo!("allow binary expressions as arguments"), }; match value { @@ -507,6 +509,7 @@ impl Transpiler { Expression::Primary(Primary::Lua(code)) => Ok(code .eval_string(handler)? .map_or_else(Vec::new, |cmd| vec![Command::Raw(cmd)])), + Expression::Binary(_) => todo!("transpile binary expression in run statement"), }, Statement::Block(_) => { unreachable!("Only literal commands are allowed in functions at this time.") @@ -550,7 +553,6 @@ impl Transpiler { } } Statement::Semicolon(semi) => match semi.statement() { - #[expect(clippy::match_wildcard_for_single_variants)] SemicolonStatement::Expression(expr) => match expr { Expression::Primary(Primary::FunctionCall(func)) => self .transpile_function_call(func, scope, handler) diff --git a/src/transpile/variables.rs b/src/transpile/variables.rs index 580ce3e..bc34fa8 100644 --- a/src/transpile/variables.rs +++ b/src/transpile/variables.rs @@ -22,7 +22,7 @@ use crate::{ use super::{ error::{AssignmentError, IllegalAnnotationContent}, - expression::DataLocation, + expression::{ComptimeValue, DataLocation}, FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult, Transpiler, }; @@ -367,9 +367,10 @@ fn get_single_data_location_identifiers( TranspileAnnotationValue::Expression(target), ) = (name, target) { - if let (Some(name_eval), Some(target_eval)) = - (objective.comptime_eval(), target.comptime_eval()) - { + if let (Some(name_eval), Some(target_eval)) = ( + objective.comptime_eval().map(|val| val.to_string()), + target.comptime_eval().map(|val| val.to_string()), + ) { // TODO: change invalid criteria if boolean if !crate::util::is_valid_scoreboard_name(&name_eval) { let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {