diff --git a/src/transpile/checks.rs b/src/transpile/checks.rs index 48478b5..e696141 100644 --- a/src/transpile/checks.rs +++ b/src/transpile/checks.rs @@ -25,7 +25,7 @@ impl Block { { let err = TranspileError::AssignmentError(AssignmentError { identifier: ident.span.clone(), - message: "cannot assign to a compile-time variable in a conditional execute block" + message: "cannot assign to a compile-time variable declared before a runtime conditional block" .to_string(), }); handler.receive(Box::new(err.clone())); diff --git a/src/transpile/error.rs b/src/transpile/error.rs index b3ade30..0972f78 100644 --- a/src/transpile/error.rs +++ b/src/transpile/error.rs @@ -49,8 +49,6 @@ pub enum TranspileError { InvalidArgument(#[from] InvalidArgument), #[error(transparent)] NotComptime(#[from] NotComptime), - #[error(transparent)] - InfiniteLoop(#[from] InfiniteLoop), } /// The result of a transpilation operation. @@ -444,29 +442,3 @@ impl Display for NotComptime { } impl std::error::Error for NotComptime {} - -/// An error that occurs when a loop never terminates. -#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] -pub struct InfiniteLoop { - /// The condition making it not terminate. - pub span: Span, -} - -impl Display for InfiniteLoop { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - Message::new(Severity::Error, "Loop never terminates.") - )?; - - write!( - f, - "\n{}", - SourceCodeDisplay::new( - &self.span, - Some("You may want to use a separate function with the `#[tick]` annotation.") - ) - ) - } -} diff --git a/src/transpile/expression.rs b/src/transpile/expression.rs index 5128b41..6b7ac72 100644 --- a/src/transpile/expression.rs +++ b/src/transpile/expression.rs @@ -2139,6 +2139,14 @@ impl Transpiler { scope: &Arc, handler: &impl Handler, ) -> TranspileResult<(Vec, ShulkerboxMacroStringMap, ExtendedCondition)> { + if let Ok(ComptimeValue::Boolean(val)) = binary.comptime_eval(scope, handler) { + return Ok(( + Vec::new(), + BTreeMap::new(), + ExtendedCondition::Comptime(val), + )); + } + match binary.operator() { BinaryOperator::Equal(..) | BinaryOperator::NotEqual(..) diff --git a/src/transpile/transpiler.rs b/src/transpile/transpiler.rs index 9d5bdbc..dbd1b96 100644 --- a/src/transpile/transpiler.rs +++ b/src/transpile/transpiler.rs @@ -22,13 +22,13 @@ use crate::{ program::{Namespace, ProgramFile}, statement::{ execute_block::{Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockTail}, - Block, ReturnStatement, SemicolonStatement, Statement, + Block, ReturnStatement, SemicolonStatement, Statement, WhileLoop, }, AnnotationAssignment, }, transpile::{ conversions::ShulkerboxMacroStringMap, - error::{IllegalAnnotationContent, InfiniteLoop}, + error::IllegalAnnotationContent, expression::DataLocation, util::{MacroString, MacroStringPart}, variables::FunctionVariableDataType, @@ -42,6 +42,8 @@ use super::{ FunctionData, TranspileAnnotationValue, TranspiledFunctionArguments, }; +const LOOP_LIMIT: usize = 4_096; + /// A transpiler for `Shulkerscript`. #[derive(Debug)] pub struct Transpiler { @@ -429,37 +431,9 @@ impl Transpiler { } } Statement::WhileLoop(while_loop) => { - let (mut condition_commands, prepare_variables, condition) = - self.transpile_expression_as_condition(while_loop.condition(), scope, handler)?; - - match condition { - ExtendedCondition::Comptime(false) => Ok(Vec::new()), - ExtendedCondition::Comptime(true) => { - let err = TranspileError::InfiniteLoop(InfiniteLoop { - span: while_loop.condition().span(), - }); - handler.receive(Box::new(err.clone())); - Err(err) - } - ExtendedCondition::Runtime(condition) => { - let loop_commands = self.transpile_block( - while_loop.block(), - program_identifier, - scope, - handler, - )?; - - condition_commands - .push(Command::While(WhileCmd::new(condition, loop_commands))); - - self.transpile_commands_with_variable_macros( - condition_commands, - prepare_variables, - handler, - ) - } - } + self.transpile_while_loop(while_loop, program_identifier, scope, handler) } + Statement::Semicolon(semi) => match semi.statement() { SemicolonStatement::Expression(expr) => match expr { Expression::Primary(Primary::FunctionCall(func)) => { @@ -1422,6 +1396,60 @@ impl Transpiler { }) } + fn transpile_while_loop( + &mut self, + while_loop: &WhileLoop, + program_identifier: &str, + scope: &Arc, + handler: &impl Handler, + ) -> TranspileResult> { + let mut cmds = Vec::new(); + + for _ in 0..LOOP_LIMIT { + let (mut condition_commands, prepare_variables, condition) = + self.transpile_expression_as_condition(while_loop.condition(), scope, handler)?; + + match condition { + ExtendedCondition::Comptime(false) => { + break; + } + ExtendedCondition::Comptime(true) => { + let loop_commands = self.transpile_block( + while_loop.block(), + program_identifier, + scope, + handler, + )?; + cmds.extend(condition_commands.into_iter().chain(loop_commands)); + } + ExtendedCondition::Runtime(condition) => { + // TODO: allow comptime assignments when wrapped in comptime checks + while_loop + .block() + .check_no_comptime_assignments(scope, handler)?; + let loop_commands = self.transpile_block( + while_loop.block(), + program_identifier, + scope, + handler, + )?; + + condition_commands + .push(Command::While(WhileCmd::new(condition, loop_commands))); + + cmds.extend(self.transpile_commands_with_variable_macros( + condition_commands, + prepare_variables, + handler, + )?); + break; + } + } + } + + Ok(cmds) + } + pub(crate) fn transpile_commands_with_variable_macros( &mut self, cmds: Vec,