implement comptime while loops

This commit is contained in:
Moritz Hölting 2025-10-08 15:04:57 +02:00
parent e367920922
commit 389d791ac1
4 changed files with 69 additions and 61 deletions

View File

@ -25,7 +25,7 @@ impl Block {
{ {
let err = TranspileError::AssignmentError(AssignmentError { let err = TranspileError::AssignmentError(AssignmentError {
identifier: ident.span.clone(), 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(), .to_string(),
}); });
handler.receive(Box::new(err.clone())); handler.receive(Box::new(err.clone()));

View File

@ -49,8 +49,6 @@ pub enum TranspileError {
InvalidArgument(#[from] InvalidArgument), InvalidArgument(#[from] InvalidArgument),
#[error(transparent)] #[error(transparent)]
NotComptime(#[from] NotComptime), NotComptime(#[from] NotComptime),
#[error(transparent)]
InfiniteLoop(#[from] InfiniteLoop),
} }
/// The result of a transpilation operation. /// The result of a transpilation operation.
@ -444,29 +442,3 @@ impl Display for NotComptime {
} }
impl std::error::Error 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.")
)
)
}
}

View File

@ -2139,6 +2139,14 @@ impl Transpiler {
scope: &Arc<super::Scope>, scope: &Arc<super::Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<(Vec<Command>, ShulkerboxMacroStringMap, ExtendedCondition)> { ) -> TranspileResult<(Vec<Command>, 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() { match binary.operator() {
BinaryOperator::Equal(..) BinaryOperator::Equal(..)
| BinaryOperator::NotEqual(..) | BinaryOperator::NotEqual(..)

View File

@ -22,13 +22,13 @@ use crate::{
program::{Namespace, ProgramFile}, program::{Namespace, ProgramFile},
statement::{ statement::{
execute_block::{Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockTail}, execute_block::{Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockTail},
Block, ReturnStatement, SemicolonStatement, Statement, Block, ReturnStatement, SemicolonStatement, Statement, WhileLoop,
}, },
AnnotationAssignment, AnnotationAssignment,
}, },
transpile::{ transpile::{
conversions::ShulkerboxMacroStringMap, conversions::ShulkerboxMacroStringMap,
error::{IllegalAnnotationContent, InfiniteLoop}, error::IllegalAnnotationContent,
expression::DataLocation, expression::DataLocation,
util::{MacroString, MacroStringPart}, util::{MacroString, MacroStringPart},
variables::FunctionVariableDataType, variables::FunctionVariableDataType,
@ -42,6 +42,8 @@ use super::{
FunctionData, TranspileAnnotationValue, TranspiledFunctionArguments, FunctionData, TranspileAnnotationValue, TranspiledFunctionArguments,
}; };
const LOOP_LIMIT: usize = 4_096;
/// A transpiler for `Shulkerscript`. /// A transpiler for `Shulkerscript`.
#[derive(Debug)] #[derive(Debug)]
pub struct Transpiler { pub struct Transpiler {
@ -429,37 +431,9 @@ impl Transpiler {
} }
} }
Statement::WhileLoop(while_loop) => { Statement::WhileLoop(while_loop) => {
let (mut condition_commands, prepare_variables, condition) = self.transpile_while_loop(while_loop, program_identifier, scope, handler)
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,
)
}
}
}
Statement::Semicolon(semi) => match semi.statement() { Statement::Semicolon(semi) => match semi.statement() {
SemicolonStatement::Expression(expr) => match expr { SemicolonStatement::Expression(expr) => match expr {
Expression::Primary(Primary::FunctionCall(func)) => { 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<Scope>,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> {
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( pub(crate) fn transpile_commands_with_variable_macros(
&mut self, &mut self,
cmds: Vec<Command>, cmds: Vec<Command>,