implement binary expression parsing and transpiling if possible at compile time

- many TODOs
- transpilation for runtime evaluation missing
This commit is contained in:
Moritz Hölting 2025-03-08 13:23:59 +01:00
parent e772c4b2c2
commit 14b371b3b6
6 changed files with 553 additions and 169 deletions

View File

@ -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<base::Error>,
) -> 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<String>,
macro_names: &HashSet<String>,
handler: &impl Handler<base::Error>,
_function_names: &HashSet<String>,
_macro_names: &HashSet<String>,
_handler: &impl Handler<base::Error>,
) -> 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(())
}
}

View File

@ -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(),

View File

@ -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<Expression>,
/// The operator of the binary expression.
#[get = "pub"]
operator: BinaryOperator,
/// The right operand of the binary expression.
#[get = "pub"]
right_operand: Box<Expression>,
}
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<String> {
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<String> {
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<base::Error>,
) -> ParseResult<Expression> {
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(&current_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<base::Error>,
) -> ParseResult<BinaryOperator> {
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)
}
}
}
}

View File

@ -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<ComptimeValue> {
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<ComptimeValue> {
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<ComptimeValue> {
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<super::Scope>,
_handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> {
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!(),
}
}
}
}
}

View File

@ -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)

View File

@ -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 {