diff --git a/Cargo.toml b/Cargo.toml index ec83427..5f1e4fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ pathdiff = "0.2.3" serde = { version = "1.0.217", features = ["derive"], optional = true } serde_json = { version = "1.0.138", optional = true } # shulkerbox = { version = "0.1.0", default-features = false, optional = true } -shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "7392887c127b1aae0d78d573a02332c2fa2591c8", default-features = false, optional = true } +shulkerbox = { git = "https://github.com/moritz-hoelting/shulkerbox", rev = "5c4d340162fc16065add448ed387a1ce481c27d6", default-features = false, optional = true } strsim = "0.11.1" strum = { version = "0.27.0", features = ["derive"] } thiserror = "2.0.11" diff --git a/src/lexical/token.rs b/src/lexical/token.rs index 0db18b8..3c902ac 100644 --- a/src/lexical/token.rs +++ b/src/lexical/token.rs @@ -53,6 +53,7 @@ pub enum KeywordKind { Bool, Macro, Val, + Return, } impl Display for KeywordKind { @@ -119,6 +120,7 @@ impl KeywordKind { Self::Bool => "bool", Self::Macro => "macro", Self::Val => "val", + Self::Return => "return", } } diff --git a/src/semantic/mod.rs b/src/semantic/mod.rs index 8266fb3..95cb737 100644 --- a/src/semantic/mod.rs +++ b/src/semantic/mod.rs @@ -14,7 +14,7 @@ use crate::{ Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockHeadItem as _, ExecuteBlockTail, }, - Assignment, AssignmentDestination, Block, Grouping, Run, Semicolon, SemicolonStatement, + Assignment, AssignmentDestination, Block, Grouping, Semicolon, SemicolonStatement, Statement, VariableDeclaration, }, AnyStringLiteral, @@ -228,7 +228,6 @@ impl Statement { let child_scope = SemanticScope::with_parent(scope); group.analyze_semantics(&child_scope, handler) } - Self::Run(run) => run.analyze_semantics(scope, handler), Self::Semicolon(sem) => sem.analyze_semantics(scope, handler), } } @@ -342,16 +341,6 @@ impl Grouping { } } -impl Run { - fn analyze_semantics( - &self, - scope: &SemanticScope, - handler: &impl Handler, - ) -> Result<(), error::Error> { - self.expression().analyze_semantics(scope, handler) - } -} - impl Semicolon { fn analyze_semantics( &self, @@ -364,6 +353,7 @@ impl Semicolon { } SemicolonStatement::Expression(expr) => expr.analyze_semantics(scope, handler), SemicolonStatement::VariableDeclaration(decl) => decl.analyze_semantics(scope, handler), + SemicolonStatement::Return(ret) => ret.expression().analyze_semantics(scope, handler), } } } @@ -665,6 +655,21 @@ impl Primary { Err(err) } } + PrefixOperator::Run(_) => { + if prefixed + .operand() + .can_yield_type_semantics(ValueType::String, scope) + { + prefixed.operand().analyze_semantics(scope, handler) + } else { + let err = error::Error::MismatchedTypes(MismatchedTypes { + expected_type: ExpectedType::String, + expression: prefixed.operand().span(), + }); + handler.receive(err.clone()); + Err(err) + } + } }, Self::Lua(lua) => lua.analyze_semantics(scope, handler), } @@ -701,12 +706,24 @@ impl Primary { _ => false, }, Self::Prefix(prefixed) => match prefixed.operator() { - PrefixOperator::LogicalNot(_) => prefixed - .operand() - .can_yield_type_semantics(ValueType::Boolean, scope), - PrefixOperator::Negate(_) => prefixed - .operand() - .can_yield_type_semantics(ValueType::Integer, scope), + PrefixOperator::LogicalNot(_) => { + expected == ValueType::Boolean + && prefixed + .operand() + .can_yield_type_semantics(ValueType::Boolean, scope) + } + PrefixOperator::Negate(_) => { + expected == ValueType::Integer + && prefixed + .operand() + .can_yield_type_semantics(ValueType::Integer, scope) + } + PrefixOperator::Run(_) => { + expected == ValueType::String + && prefixed + .operand() + .can_yield_type_semantics(ValueType::String, scope) + } }, Self::Parenthesized(parenthesized) => { parenthesized.can_yield_type_semantics(expected, scope) @@ -807,8 +824,18 @@ impl Binary { #[must_use] fn can_yield_type_semantics(&self, expected: ValueType, scope: &SemanticScope) -> bool { match self.operator() { - BinaryOperator::Add(_) - | BinaryOperator::Subtract(_) + BinaryOperator::Add(_) => { + if expected == ValueType::Integer { + self.left_operand() + .can_yield_type_semantics(ValueType::Integer, scope) + && self + .right_operand() + .can_yield_type_semantics(ValueType::Integer, scope) + } else { + expected == ValueType::String + } + } + BinaryOperator::Subtract(_) | BinaryOperator::Multiply(_) | BinaryOperator::Divide(_) | BinaryOperator::Modulo(_) => { diff --git a/src/syntax/error.rs b/src/syntax/error.rs index c0483cf..a3cb9e2 100644 --- a/src/syntax/error.rs +++ b/src/syntax/error.rs @@ -56,7 +56,7 @@ impl SyntaxKind { } else if variants.len() == 1 { variants[0].expected_binding_str() } else { - let comma_range = ..variants.len() - 2; + let comma_range = ..variants.len() - 1; let comma_elements = variants[comma_range] .iter() .map(Self::expected_binding_str) diff --git a/src/syntax/syntax_tree/declaration.rs b/src/syntax/syntax_tree/declaration.rs index fe2baac..8c7c16b 100644 --- a/src/syntax/syntax_tree/declaration.rs +++ b/src/syntax/syntax_tree/declaration.rs @@ -357,18 +357,24 @@ impl Parser<'_> { Reading::Atomic(Token::Keyword(pub_keyword)) if pub_keyword.keyword == KeywordKind::Pub => { - if let Ok(function) = self.try_parse(|parser| parser.parse_function(&VoidHandler)) { - tracing::trace!("Parsed function '{:?}'", function.identifier.span.str()); + match self.peek_offset(2) { + Some(Reading::Atomic(Token::Keyword(function_keyword))) + if function_keyword.keyword == KeywordKind::Function => + { + let function = self.parse_function(handler)?; + tracing::trace!("Parsed function '{:?}'", function.identifier.span.str()); - Ok(Declaration::Function(function)) - } else { - // eat the pub keyword - self.forward(); + Ok(Declaration::Function(function)) + } + _ => { + // eat the pub keyword + self.forward(); - let var = self.parse_variable_declaration(handler)?; - let semi = self.parse_punctuation(';', true, handler)?; + let var = self.parse_variable_declaration(handler)?; + let semi = self.parse_punctuation(';', true, handler)?; - Ok(Declaration::GlobalVariable((Some(pub_keyword), var, semi))) + Ok(Declaration::GlobalVariable((Some(pub_keyword), var, semi))) + } } } diff --git a/src/syntax/syntax_tree/expression.rs b/src/syntax/syntax_tree/expression.rs index eb5bc1f..2c663d1 100644 --- a/src/syntax/syntax_tree/expression.rs +++ b/src/syntax/syntax_tree/expression.rs @@ -314,12 +314,15 @@ pub enum PrefixOperator { LogicalNot(Punctuation), /// The negate operator '-'. Negate(Punctuation), + /// The run keyword 'run'. + Run(Keyword), } impl SourceElement for PrefixOperator { fn span(&self) -> Span { match self { Self::LogicalNot(token) | Self::Negate(token) => token.span.clone(), + Self::Run(token) => token.span.clone(), } } } @@ -507,7 +510,7 @@ impl Parser<'_> { #[expect(clippy::too_many_lines)] pub fn parse_primary(&mut self, handler: &impl Handler) -> ParseResult { match self.stop_at_significant() { - // prefixed expression + // prefixed expression with '!' or '-' Reading::Atomic(Token::Punctuation(punc)) if matches!(punc.punctuation, '!' | '-') => { // eat the prefix self.forward(); @@ -526,6 +529,23 @@ impl Parser<'_> { })) } + // prefixed expression with 'run' + Reading::Atomic(Token::Keyword(run_keyword)) + if run_keyword.keyword == KeywordKind::Run => + { + // eat the run keyword + self.forward(); + + let expression = self.parse_primary(handler)?; + + tracing::trace!("Parsed run expression: {:?}", expression); + + Ok(Primary::Prefix(Prefix { + operator: PrefixOperator::Run(run_keyword), + operand: Box::new(expression), + })) + } + // parenthesized expression Reading::IntoDelimited(left_parenthesis) if left_parenthesis.punctuation == '(' => self .parse_parenthesized(handler) diff --git a/src/syntax/syntax_tree/statement.rs b/src/syntax/syntax_tree/statement.rs index eb567b1..518be87 100644 --- a/src/syntax/syntax_tree/statement.rs +++ b/src/syntax/syntax_tree/statement.rs @@ -43,7 +43,6 @@ use super::{expression::Expression, Annotation, AnyStringLiteral}; /// | Grouping /// | DocComment /// | Semicolon -/// | Run /// ; /// ``` #[allow(missing_docs)] @@ -56,7 +55,6 @@ pub enum Statement { Grouping(Grouping), DocComment(DocComment), Semicolon(Semicolon), - Run(Run), } impl SourceElement for Statement { @@ -68,7 +66,6 @@ impl SourceElement for Statement { Self::Grouping(grouping) => grouping.span(), Self::DocComment(doc_comment) => doc_comment.span(), Self::Semicolon(semi) => semi.span(), - Self::Run(run) => run.span(), } } } @@ -107,6 +104,14 @@ impl Statement { target: "expressions".to_string(), }); + Err(err) + } + SemicolonStatement::Return(_) => { + let err = Error::InvalidAnnotation(InvalidAnnotation { + annotation: annotation.assignment.identifier.span, + target: "return statements".to_string(), + }); + Err(err) } }, @@ -162,46 +167,6 @@ impl SourceElement for Block { } } -/// Represents a run statement in the syntax tree. -/// -/// Syntax Synopsis: -/// -/// ``` ebnf -/// Run: -/// 'run' Expression ';' -/// ; -/// ``` -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] -pub struct Run { - /// The `run` keyword. - #[get = "pub"] - run_keyword: Keyword, - /// The expression of the run statement. - #[get = "pub"] - expression: Expression, - /// The semicolon of the run statement. - #[get = "pub"] - semicolon: Punctuation, -} - -impl SourceElement for Run { - fn span(&self) -> Span { - self.run_keyword - .span() - .join(&self.semicolon.span()) - .expect("The span of the run statement is invalid.") - } -} - -impl Run { - /// Dissolves the [`Run`] into its components. - #[must_use] - pub fn dissolve(self) -> (Keyword, Expression, Punctuation) { - (self.run_keyword, self.expression, self.semicolon) - } -} - /// Represents a grouping statement in the syntax tree. /// /// Syntax Synopsis: @@ -293,6 +258,8 @@ pub enum SemicolonStatement { VariableDeclaration(VariableDeclaration), /// An assignment. Assignment(Assignment), + /// A return statement. + Return(ReturnStatement), } impl SourceElement for SemicolonStatement { @@ -301,10 +268,46 @@ impl SourceElement for SemicolonStatement { Self::Expression(expression) => expression.span(), Self::VariableDeclaration(declaration) => declaration.span(), Self::Assignment(assignment) => assignment.span(), + Self::Return(ret) => ret.span(), } } } +/// Represents a return statement in the syntax tree. +/// +/// Syntax Synopsis: +/// ```ebnf +/// ReturnStatement: +/// `return` Expression ; +/// ``` +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)] +pub struct ReturnStatement { + /// The `return` keyword. + #[get = "pub"] + return_keyword: Keyword, + /// The expression of the return statement. + #[get = "pub"] + expression: Expression, +} + +impl SourceElement for ReturnStatement { + fn span(&self) -> Span { + self.return_keyword + .span() + .join(&self.expression.span()) + .expect("The span of the return statement is invalid.") + } +} + +impl ReturnStatement { + /// Dissolves the [`ReturnStatement`] into its components. + #[must_use] + pub fn dissolve(self) -> (Keyword, Expression) { + (self.return_keyword, self.expression) + } +} + /// Represents a variable declaration in the syntax tree. /// /// Syntax Synopsis: @@ -910,25 +913,6 @@ impl Parser<'_> { })) } - // run statement - Reading::Atomic(Token::Keyword(run_keyword)) - if run_keyword.keyword == KeywordKind::Run => - { - // eat the run keyword - self.forward(); - - let expression = self.parse_expression(handler)?; - let semicolon = self.parse_punctuation(';', true, handler)?; - - tracing::trace!("Parsed run statement: {:?}", expression); - - Ok(Statement::Run(Run { - run_keyword, - expression, - semicolon, - })) - } - // semicolon statement _ => self.parse_semicolon(handler).map(Statement::Semicolon), } @@ -941,6 +925,17 @@ impl Parser<'_> { handler: &impl Handler, ) -> ParseResult { let statement = match self.stop_at_significant() { + Reading::Atomic(Token::Keyword(keyword)) if keyword.keyword == KeywordKind::Return => { + // eat the return keyword + self.forward(); + + let expression = self.parse_expression(handler)?; + + Ok(SemicolonStatement::Return(ReturnStatement { + return_keyword: keyword, + expression, + })) + } Reading::Atomic(Token::Keyword(keyword)) if matches!( keyword.keyword, @@ -1025,6 +1020,7 @@ impl Parser<'_> { expected: SyntaxKind::Either(&[ SyntaxKind::Keyword(KeywordKind::Int), SyntaxKind::Keyword(KeywordKind::Bool), + SyntaxKind::Keyword(KeywordKind::Val), ]), found: unexpected.into_token(), }); diff --git a/src/transpile/expression.rs b/src/transpile/expression.rs index 729ff83..36814d4 100644 --- a/src/transpile/expression.rs +++ b/src/transpile/expression.rs @@ -289,6 +289,10 @@ impl Primary { matches!(r#type, ValueType::Integer) && prefix.operand().can_yield_type(r#type, scope) } + PrefixOperator::Run(_) => { + matches!(r#type, ValueType::Integer | ValueType::Boolean) + && prefix.operand().can_yield_type(ValueType::String, scope) + } }, Self::Indexed(indexed) => { if let Self::Identifier(ident) = indexed.object().as_ref() { @@ -601,7 +605,7 @@ impl Transpiler { } } - #[expect(clippy::too_many_lines)] + #[expect(clippy::too_many_lines, clippy::cognitive_complexity)] pub(super) fn transpile_primary_expression( &mut self, primary: &Primary, @@ -760,8 +764,8 @@ impl Transpiler { StorageType::Byte | StorageType::Int | StorageType::Long ) => { - let (target_objective, mut targets) = self.get_temp_scoreboard_locations(1); - let target_ident = targets.pop().expect("at least size 1"); + let (target_objective, [target_ident]) = + self.get_temp_scoreboard_locations_array(); let score_to_storage_cmd = Command::Execute(Execute::Store( format!( @@ -807,6 +811,65 @@ impl Transpiler { Ok(cmds) } + PrefixOperator::Run(_) => { + let run_cmds = + self.transpile_run_expression(prefix.operand(), scope, handler)?; + let run_cmd = if run_cmds.len() == 1 { + run_cmds.into_iter().next().expect("length is 1") + } else { + Command::Group(run_cmds) + }; + match target { + DataLocation::ScoreboardValue { objective, target } => { + let store = format!("result score {target} {objective}"); + let exec = Command::Execute(Execute::Store( + store.into(), + Box::new(Execute::Run(Box::new(run_cmd))), + )); + Ok(vec![exec]) + } + DataLocation::Storage { + storage_name, + path, + r#type, + } => { + let store = format!( + "{result_success} storage {storage_name} {path} {t} 1.0", + t = r#type.as_str(), + result_success = if matches!(r#type, StorageType::Boolean) { + "success" + } else { + "result" + } + ); + let exec = Command::Execute(Execute::Store( + store.into(), + Box::new(Execute::Run(Box::new(run_cmd))), + )); + Ok(vec![exec]) + } + DataLocation::Tag { tag_name, entity } => { + let prepare_cmd = + Command::Raw(format!("tag {entity} remove {tag_name}")); + let success_cmd = Command::Raw(format!("tag {entity} add {tag_name}")); + let (temp_storage_name, [temp_storage_path]) = + self.get_temp_storage_locations_array(); + + let store_cmd = Command::Execute(Execute::Store( + format!("success storage {temp_storage_name} {temp_storage_path} boolean 1.0").into(), + Box::new(Execute::Run(Box::new(run_cmd))) + )); + + let if_cmd = Command::Execute(Execute::If( + Condition::Atom(format!("data storage {temp_storage_name} {{{temp_storage_name}:1b}}").into()), + Box::new(Execute::Run(Box::new(success_cmd))), + None, + )); + + Ok(vec![store_cmd, prepare_cmd, if_cmd]) + } + } + } }, Primary::Identifier(ident) => { let variable = scope.get_variable(ident.span.str()); @@ -1215,6 +1278,25 @@ impl Transpiler { }, )) } + PrefixOperator::Run(_) => { + let (temp_storage_name, [temp_storage_path]) = + self.get_temp_storage_locations_array(); + let cond = ExtendedCondition::Runtime(Condition::Atom( + format!("data storage {temp_storage_name} {{{temp_storage_path}:1b}}") + .into(), + )); + let store_cmds = self.transpile_primary_expression( + primary, + &DataLocation::Storage { + storage_name: temp_storage_name, + path: temp_storage_path, + r#type: StorageType::Boolean, + }, + scope, + handler, + )?; + Ok((store_cmds, cond)) + } PrefixOperator::Negate(_) => { let err = TranspileError::MismatchedTypes(MismatchedTypes { expected_type: ExpectedType::Boolean, @@ -1289,11 +1371,12 @@ impl Transpiler { let right = binary.right_operand(); let operator = binary.operator(); - let (temp_objective, temp_locations) = self.get_temp_scoreboard_locations(2); + let (temp_objective, [temp_location_a, temp_location_b]) = + self.get_temp_scoreboard_locations_array(); let score_target_location = match target { DataLocation::ScoreboardValue { objective, target } => (objective, target), - _ => (&temp_objective, &temp_locations[0]), + _ => (&temp_objective, &temp_location_a), }; let left_cmds = self.transpile_expression( @@ -1318,7 +1401,7 @@ impl Transpiler { right, &DataLocation::ScoreboardValue { objective: temp_objective.clone(), - target: temp_locations[1].clone(), + target: temp_location_b.clone(), }, scope, handler, @@ -1328,7 +1411,7 @@ impl Transpiler { right_cmds, ( temp_objective.as_str(), - std::borrow::Cow::Borrowed(&temp_locations[1]), + std::borrow::Cow::Borrowed(&temp_location_b), ), ) }; @@ -1419,13 +1502,12 @@ impl Transpiler { _ => unreachable!("This function should only be called for comparison operators."), }; - let (temp_objective, mut temp_locations) = self.get_temp_scoreboard_locations(2); + let (temp_objective, [temp_location_a, temp_location_b]) = + self.get_temp_scoreboard_locations_array(); let condition = Condition::Atom( format!( - "score {target} {temp_objective} {operator} {source} {temp_objective}", - target = temp_locations[0], - source = temp_locations[1] + "score {temp_location_a} {temp_objective} {operator} {temp_location_b} {temp_objective}" ) .into(), ); @@ -1434,7 +1516,7 @@ impl Transpiler { binary.left_operand(), &DataLocation::ScoreboardValue { objective: temp_objective.clone(), - target: std::mem::take(&mut temp_locations[0]), + target: temp_location_a, }, scope, handler, @@ -1443,7 +1525,7 @@ impl Transpiler { binary.right_operand(), &DataLocation::ScoreboardValue { objective: temp_objective, - target: std::mem::take(&mut temp_locations[1]), + target: temp_location_b, }, scope, handler, @@ -1707,6 +1789,17 @@ impl Transpiler { (objective, targets) } + /// Get temporary scoreboard locations. + pub(super) fn get_temp_scoreboard_locations_array( + &mut self, + ) -> (String, [String; N]) { + let (objective, targets) = self.get_temp_scoreboard_locations(N); + + let targets = targets.try_into().expect("build from range of type"); + + (objective, targets) + } + /// Get temporary storage locations. pub(super) fn get_temp_storage_locations(&mut self, amount: usize) -> (String, Vec) { let storage_name = "shulkerscript:temp_".to_string() @@ -1734,4 +1827,15 @@ impl Transpiler { (storage_name, paths) } + + /// Get temporary storage locations. + pub(super) fn get_temp_storage_locations_array( + &mut self, + ) -> (String, [String; N]) { + let (storage_name, paths) = self.get_temp_storage_locations(N); + + let paths = paths.try_into().expect("build from range of type"); + + (storage_name, paths) + } } diff --git a/src/transpile/function.rs b/src/transpile/function.rs index 994c9f2..cfd871e 100644 --- a/src/transpile/function.rs +++ b/src/transpile/function.rs @@ -316,13 +316,13 @@ impl Transpiler { VariableData::BooleanStorage { .. } | VariableData::ScoreboardValue { .. } => { - let (temp_storage, mut temp_path) = - self.get_temp_storage_locations(1); + let (temp_storage, [temp_path]) = + self.get_temp_storage_locations_array(); let prepare_cmds = self.transpile_primary_expression( primary, &super::expression::DataLocation::Storage { storage_name: temp_storage.clone(), - path: temp_path[0].clone(), + path: temp_path.clone(), r#type: match var.as_ref() { VariableData::BooleanStorage { .. } => { StorageType::Boolean @@ -340,7 +340,7 @@ impl Transpiler { Ok(Parameter::Storage { prepare_cmds, storage_name: temp_storage, - path: std::mem::take(&mut temp_path[0]), + path: temp_path, }) } _ => { @@ -364,12 +364,13 @@ impl Transpiler { | Primary::FunctionCall(_), ) | Expression::Binary(_) => { - let (temp_storage, mut temp_path) = self.get_temp_storage_locations(1); + let (temp_storage, [temp_path]) = + self.get_temp_storage_locations_array(); let prepare_cmds = self.transpile_expression( expression, &super::expression::DataLocation::Storage { storage_name: temp_storage.clone(), - path: temp_path[0].clone(), + path: temp_path.clone(), r#type: StorageType::Int, }, scope, @@ -379,7 +380,7 @@ impl Transpiler { Ok(Parameter::Storage { prepare_cmds, storage_name: temp_storage, - path: std::mem::take(&mut temp_path[0]), + path: temp_path, }) } }; diff --git a/src/transpile/internal_functions.rs b/src/transpile/internal_functions.rs index fb0dfad..5fb51ef 100644 --- a/src/transpile/internal_functions.rs +++ b/src/transpile/internal_functions.rs @@ -158,16 +158,13 @@ fn print_function( json!({"nbt": path, "storage": storage_name, "color": PARAM_COLOR}), ), DataLocation::Tag { tag_name, entity } => { - let (temp_storage_name, temp_storage_paths) = - transpiler.get_temp_storage_locations(1); + let (temp_storage_name, [temp_storage_path]) = + transpiler.get_temp_storage_locations_array(); let selector = super::util::add_to_entity_selector(entity, &format!("tag={tag_name}")); let cmd = Command::Execute(Execute::Store( - format!( - "success storage {temp_storage_name} {path} byte 1.0", - path = temp_storage_paths[0] - ) - .into(), + format!("success storage {temp_storage_name} {temp_storage_path} byte 1.0") + .into(), Box::new(Execute::Run(Box::new(Command::Raw(format!( "execute if entity {selector}" ))))), @@ -175,7 +172,7 @@ fn print_function( ( Some(cmd), - json!({"nbt": temp_storage_paths[0], "storage": temp_storage_name, "color": PARAM_COLOR}), + json!({"nbt": temp_storage_path, "storage": temp_storage_name, "color": PARAM_COLOR}), ) } } @@ -367,10 +364,10 @@ fn print_function( } primary => { - let (storage_name, mut storage_paths) = transpiler.get_temp_storage_locations(1); + let (storage_name, [storage_path]) = transpiler.get_temp_storage_locations_array(); let location = DataLocation::Storage { storage_name, - path: std::mem::take(&mut storage_paths[0]), + path: storage_path, r#type: StorageType::Int, }; let cmds = transpiler.transpile_primary_expression( @@ -388,10 +385,10 @@ fn print_function( } }, Expression::Binary(binary) => { - let (storage_name, mut storage_paths) = transpiler.get_temp_storage_locations(1); + let (storage_name, [storage_path]) = transpiler.get_temp_storage_locations_array(); let location = DataLocation::Storage { storage_name, - path: std::mem::take(&mut storage_paths[0]), + path: storage_path, r#type: StorageType::Int, }; let cmds = diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 83e79f5..033566f 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -14,11 +14,11 @@ use crate::{ semantic::error::UnexpectedExpression, syntax::syntax_tree::{ declaration::{Declaration, ImportItems}, - expression::{Expression, FunctionCall, Primary}, + expression::{Expression, FunctionCall, PrefixOperator, Primary}, program::{Namespace, ProgramFile}, statement::{ execute_block::{Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockTail}, - SemicolonStatement, Statement, + ReturnStatement, SemicolonStatement, Statement, }, AnnotationAssignment, }, @@ -359,9 +359,6 @@ impl Transpiler { Statement::LiteralCommand(literal_command) => { Ok(vec![literal_command.clean_command().into()]) } - Statement::Run(run) => { - self.transpile_run_expression(run.expression(), program_identifier, scope, handler) - } Statement::Block(_) => { unreachable!("Only literal commands are allowed in functions at this time.") } @@ -411,6 +408,11 @@ impl Transpiler { Expression::Primary(Primary::FunctionCall(func)) => { self.transpile_function_call(func, scope, handler) } + Expression::Primary(Primary::Prefix(prefix)) + if matches!(prefix.operator(), PrefixOperator::Run(_)) => + { + self.transpile_run_expression(prefix.operand(), scope, handler) + } unexpected => { let error = TranspileError::UnexpectedExpression(UnexpectedExpression( unexpected.clone(), @@ -433,77 +435,205 @@ impl Transpiler { scope, handler, ), + SemicolonStatement::Return(ret) => { + self.transpile_return_statement(ret, program_identifier, scope, handler) + } }, } } - #[expect(clippy::only_used_in_recursion)] - fn transpile_run_expression( + #[expect(clippy::too_many_lines)] + fn transpile_return_statement( &mut self, - expression: &Expression, - program_identifier: &str, + ret: &ReturnStatement, + _program_identifier: &str, scope: &Arc, handler: &impl Handler, ) -> TranspileResult> { - match expression { - Expression::Primary(Primary::FunctionCall(func)) => { - self.transpile_function_call(func, scope, handler) - } - Expression::Primary(Primary::Identifier(ident)) => { - match scope.get_variable(ident.span.str()).as_deref() { - Some(VariableData::ComptimeValue { - value, - read_only: _, - }) => value.read().unwrap().as_ref().map_or_else( - || { - let error = TranspileError::MissingValue(MissingValue { - expression: ident.span.clone(), - }); - handler.receive(error.clone()); - Err(error) - }, - |val| { - let cmd = val.to_string_no_macro().map_or_else( - || Command::UsesMacro(val.to_macro_string().into()), - Command::Raw, - ); - Ok(vec![cmd]) - }, - ), - Some(_) => { - let error = TranspileError::UnexpectedExpression(UnexpectedExpression( - expression.clone(), - )); - handler.receive(error.clone()); - Err(error) - } - None => { + let comptime_val = ret + .expression() + .comptime_eval(scope, handler) + .map(|val| val.to_macro_string()); + + let (prepare_cmds, ret_cmd) = if let Ok(val) = comptime_val { + (Vec::new(), datapack::ReturnCommand::Value(val.into())) + } else { + match ret.expression() { + Expression::Primary(Primary::Prefix(prefix)) + if matches!(prefix.operator(), PrefixOperator::Run(_)) => + { + let ret_cmds = + self.transpile_run_expression(prefix.operand(), scope, handler)?; + let cmd = if ret_cmds.len() == 1 { + ret_cmds.into_iter().next().unwrap() + } else { + Command::Group(ret_cmds) + }; + (Vec::new(), datapack::ReturnCommand::Command(Box::new(cmd))) + } + Expression::Primary(Primary::FunctionCall(func)) => { + let ret_cmds = self.transpile_function_call(func, scope, handler)?; + let cmd = if ret_cmds.len() == 1 { + ret_cmds.into_iter().next().unwrap() + } else { + Command::Group(ret_cmds) + }; + (Vec::new(), datapack::ReturnCommand::Command(Box::new(cmd))) + } + Expression::Primary(Primary::Identifier(ident)) => { + if let Some(var) = scope.get_variable(ident.span.str()) { + match var.as_ref() { + VariableData::BooleanStorage { storage_name, path } => ( + Vec::new(), + datapack::ReturnCommand::Command(Box::new(Command::Raw(format!( + "data get storage {storage_name} {path}" + )))), + ), + VariableData::ComptimeValue { + value, + read_only: _, + } => value.read().unwrap().as_ref().map_or_else( + || { + let error = TranspileError::MissingValue(MissingValue { + expression: ident.span.clone(), + }); + handler.receive(error.clone()); + Err(error) + }, + |val| { + let cmd = val.to_string_no_macro().map_or_else( + || Command::UsesMacro(val.to_macro_string().into()), + Command::Raw, + ); + Ok(( + Vec::new(), + datapack::ReturnCommand::Command(Box::new(cmd)), + )) + }, + )?, + VariableData::MacroParameter { + index: _, + macro_name, + } => ( + Vec::new(), + datapack::ReturnCommand::Command(Box::new(Command::UsesMacro( + shulkerbox::util::MacroString::MacroString(vec![ + shulkerbox::util::MacroStringPart::MacroUsage( + macro_name.clone(), + ), + ]), + ))), + ), + VariableData::ScoreboardValue { objective, target } => ( + Vec::new(), + datapack::ReturnCommand::Command(Box::new(Command::Raw(format!( + "scoreboard players get {target} {objective}" + )))), + ), + _ => { + let error = + TranspileError::UnexpectedExpression(UnexpectedExpression( + Expression::Primary(Primary::Identifier(ident.clone())), + )); + handler.receive(error.clone()); + return Err(error); + } + } + } else { let error = TranspileError::UnknownIdentifier(UnknownIdentifier { identifier: ident.span.clone(), }); handler.receive(error.clone()); - Err(error) + return Err(error); } } + _ => { + let (temp_objective, [temp_target]) = + self.get_temp_scoreboard_locations_array(); + let ret_cmd = datapack::ReturnCommand::Command(Box::new(Command::Raw( + format!("scoreboard players get {temp_target} {temp_objective}"), + ))); + let cmds = self.transpile_expression( + ret.expression(), + &super::expression::DataLocation::ScoreboardValue { + objective: temp_objective, + target: temp_target, + }, + scope, + handler, + )?; + (cmds, ret_cmd) + } } - expression @ Expression::Primary( - Primary::Integer(_) - | Primary::Boolean(_) - | Primary::Prefix(_) - | Primary::Indexed(_), - ) => { - let error = - TranspileError::UnexpectedExpression(UnexpectedExpression(expression.clone())); + }; + + let cmds = prepare_cmds + .into_iter() + .chain(std::iter::once(Command::Return(ret_cmd))) + .collect(); + + Ok(cmds) + } + + pub(super) fn transpile_run_expression( + &mut self, + expression: &Primary, + scope: &Arc, + handler: &impl Handler, + ) -> TranspileResult> { + match expression { + Primary::FunctionCall(func) => self.transpile_function_call(func, scope, handler), + Primary::Identifier(ident) => match scope.get_variable(ident.span.str()).as_deref() { + Some(VariableData::ComptimeValue { + value, + read_only: _, + }) => value.read().unwrap().as_ref().map_or_else( + || { + let error = TranspileError::MissingValue(MissingValue { + expression: ident.span.clone(), + }); + handler.receive(error.clone()); + Err(error) + }, + |val| { + let cmd = val.to_string_no_macro().map_or_else( + || Command::UsesMacro(val.to_macro_string().into()), + Command::Raw, + ); + Ok(vec![cmd]) + }, + ), + Some(_) => { + let error = TranspileError::UnexpectedExpression(UnexpectedExpression( + Expression::Primary(expression.clone()), + )); + handler.receive(error.clone()); + Err(error) + } + None => { + let error = TranspileError::UnknownIdentifier(UnknownIdentifier { + identifier: ident.span.clone(), + }); + handler.receive(error.clone()); + Err(error) + } + }, + + Primary::Integer(_) + | Primary::Boolean(_) + | Primary::Prefix(_) + | Primary::Indexed(_) => { + let error = TranspileError::UnexpectedExpression(UnexpectedExpression( + Expression::Primary(expression.clone()), + )); handler.receive(error.clone()); Err(error) } - Expression::Primary(Primary::StringLiteral(string)) => { + Primary::StringLiteral(string) => { Ok(vec![Command::Raw(string.str_content().to_string())]) } - Expression::Primary(Primary::MacroStringLiteral(string)) => { - Ok(vec![Command::UsesMacro(string.into())]) - } - Expression::Primary(Primary::Lua(code)) => match code.eval_comptime(scope, handler)? { + Primary::MacroStringLiteral(string) => Ok(vec![Command::UsesMacro(string.into())]), + Primary::Lua(code) => match code.eval_comptime(scope, handler)? { Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]), Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]), Ok(ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)) => { @@ -523,24 +653,22 @@ impl Transpiler { } }, - Expression::Primary(Primary::Parenthesized(parenthesized)) => self - .transpile_run_expression( - parenthesized.expression(), - program_identifier, - scope, - handler, - ), - Expression::Binary(bin) => match bin.comptime_eval(scope, handler) { - Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]), - Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]), - _ => { - let err = TranspileError::MismatchedTypes(MismatchedTypes { - expression: bin.span(), - expected_type: ExpectedType::String, - }); - handler.receive(err.clone()); - Err(err) + Primary::Parenthesized(parenthesized) => match parenthesized.expression().as_ref() { + Expression::Primary(expression) => { + self.transpile_run_expression(expression, scope, handler) } + Expression::Binary(bin) => match bin.comptime_eval(scope, handler) { + Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]), + Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]), + _ => { + let err = TranspileError::MismatchedTypes(MismatchedTypes { + expression: bin.span(), + expected_type: ExpectedType::String, + }); + handler.receive(err.clone()); + Err(err) + } + }, }, } } diff --git a/src/transpile/variables.rs b/src/transpile/variables.rs index a397c79..57e7c2b 100644 --- a/src/transpile/variables.rs +++ b/src/transpile/variables.rs @@ -381,8 +381,7 @@ impl Transpiler { handler, )?; if is_global { - let (temp_objective, temp_targets) = self.get_temp_scoreboard_locations(1); - let temp_target = &temp_targets[0]; + let (temp_objective, [temp_target]) = self.get_temp_scoreboard_locations_array(); let test_cmd = match declaration.variable_type().keyword { KeywordKind::Int => { Command::Raw(format!("scoreboard players get {name} {target}"))