check that assignments to comptime variables are only done in comptime conditionals
This commit is contained in:
parent
7f276a4139
commit
0f6d9b301f
|
@ -42,8 +42,6 @@ mod scope;
|
||||||
use error::{IncompatibleFunctionAnnotation, InvalidNamespaceName, UnexpectedExpression};
|
use error::{IncompatibleFunctionAnnotation, InvalidNamespaceName, UnexpectedExpression};
|
||||||
pub use scope::{SemanticScope, VariableType};
|
pub use scope::{SemanticScope, VariableType};
|
||||||
|
|
||||||
use super::syntax::syntax_tree::ConnectedList;
|
|
||||||
|
|
||||||
impl ProgramFile {
|
impl ProgramFile {
|
||||||
/// Analyzes the semantics of the program.
|
/// Analyzes the semantics of the program.
|
||||||
pub fn analyze_semantics(
|
pub fn analyze_semantics(
|
||||||
|
@ -172,8 +170,7 @@ impl Function {
|
||||||
if let Some(parameters) = self
|
if let Some(parameters) = self
|
||||||
.parameters()
|
.parameters()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(ConnectedList::elements)
|
.map(|l| l.elements().collect::<Vec<_>>())
|
||||||
.map(Iterator::collect::<Vec<_>>)
|
|
||||||
{
|
{
|
||||||
if let Some(incompatible) = self.annotations().iter().find(|a| {
|
if let Some(incompatible) = self.annotations().iter().find(|a| {
|
||||||
["tick", "load", "uninstall"].contains(&a.assignment().identifier.span.str())
|
["tick", "load", "uninstall"].contains(&a.assignment().identifier.span.str())
|
||||||
|
|
|
@ -473,7 +473,7 @@ impl SourceElement for TemplateStringLiteralPart {
|
||||||
/// 'lua' '(' (Expression (',' Expression)*)? ')' '{' (.*?)* '}'
|
/// 'lua' '(' (Expression (',' Expression)*)? ')' '{' (.*?)* '}'
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
|
#[derive(Debug, Clone, Getters)]
|
||||||
pub struct LuaCode {
|
pub struct LuaCode {
|
||||||
/// The `lua` keyword.
|
/// The `lua` keyword.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
|
@ -496,6 +496,12 @@ pub struct LuaCode {
|
||||||
/// The right brace of the lua code.
|
/// The right brace of the lua code.
|
||||||
#[get = "pub"]
|
#[get = "pub"]
|
||||||
right_brace: Punctuation,
|
right_brace: Punctuation,
|
||||||
|
/// Result of the evaluation
|
||||||
|
#[cfg(all(feature = "lua", feature = "shulkerbox"))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
|
pub(crate) eval_result: std::sync::OnceLock<
|
||||||
|
Result<crate::transpile::expression::ComptimeValue, crate::transpile::error::NotComptime>,
|
||||||
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceElement for LuaCode {
|
impl SourceElement for LuaCode {
|
||||||
|
@ -520,6 +526,69 @@ impl LuaCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PartialEq, Eq, PartialOrd, Ord, Hash
|
||||||
|
|
||||||
|
impl PartialEq for LuaCode {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.lua_keyword == other.lua_keyword
|
||||||
|
&& self.left_parenthesis == other.left_parenthesis
|
||||||
|
&& self.inputs == other.inputs
|
||||||
|
&& self.right_parenthesis == other.right_parenthesis
|
||||||
|
&& self.left_brace == other.left_brace
|
||||||
|
&& self.code == other.code
|
||||||
|
&& self.right_brace == other.right_brace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Eq for LuaCode {}
|
||||||
|
|
||||||
|
impl Ord for LuaCode {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
let cmp_lua = self.lua_keyword.cmp(&other.lua_keyword);
|
||||||
|
if cmp_lua != Ordering::Equal {
|
||||||
|
return cmp_lua;
|
||||||
|
}
|
||||||
|
let cmp_left_paren = self.left_parenthesis.cmp(&other.left_parenthesis);
|
||||||
|
if cmp_left_paren != Ordering::Equal {
|
||||||
|
return cmp_left_paren;
|
||||||
|
}
|
||||||
|
let cmp_inputs = self.inputs.cmp(&other.inputs);
|
||||||
|
if cmp_inputs != Ordering::Equal {
|
||||||
|
return cmp_inputs;
|
||||||
|
}
|
||||||
|
let cmp_right_paren = self.right_parenthesis.cmp(&other.right_parenthesis);
|
||||||
|
if cmp_right_paren != Ordering::Equal {
|
||||||
|
return cmp_right_paren;
|
||||||
|
}
|
||||||
|
let cmp_left_brace = self.left_brace.cmp(&other.left_brace);
|
||||||
|
if cmp_left_brace != Ordering::Equal {
|
||||||
|
return cmp_left_brace;
|
||||||
|
}
|
||||||
|
let cmp_code = self.code.cmp(&other.code);
|
||||||
|
if cmp_code != Ordering::Equal {
|
||||||
|
return cmp_code;
|
||||||
|
}
|
||||||
|
self.right_brace.cmp(&other.right_brace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for LuaCode {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for LuaCode {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.lua_keyword.hash(state);
|
||||||
|
self.left_parenthesis.hash(state);
|
||||||
|
self.inputs.hash(state);
|
||||||
|
self.right_parenthesis.hash(state);
|
||||||
|
self.left_brace.hash(state);
|
||||||
|
self.code.hash(state);
|
||||||
|
self.right_brace.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a member access in the syntax tree.
|
/// Represents a member access in the syntax tree.
|
||||||
///
|
///
|
||||||
/// Syntax Synopsis:
|
/// Syntax Synopsis:
|
||||||
|
@ -802,6 +871,8 @@ impl Parser<'_> {
|
||||||
left_brace: tree.open,
|
left_brace: tree.open,
|
||||||
code: tree.tree?,
|
code: tree.tree?,
|
||||||
right_brace: tree.close,
|
right_brace: tree.close,
|
||||||
|
#[cfg(all(feature = "lua", feature = "shulkerbox"))]
|
||||||
|
eval_result: std::sync::OnceLock::new(),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
base::{self, Handler},
|
||||||
|
syntax::syntax_tree::statement::{
|
||||||
|
execute_block::{ExecuteBlock, ExecuteBlockTail},
|
||||||
|
AssignmentDestination, Block, SemicolonStatement, Statement,
|
||||||
|
},
|
||||||
|
transpile::{error::AssignmentError, Scope, TranspileError, TranspileResult, VariableData},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Block {
|
||||||
|
pub(super) fn check_no_comptime_assignments(
|
||||||
|
&self,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> TranspileResult<()> {
|
||||||
|
for statement in self.statements() {
|
||||||
|
match statement {
|
||||||
|
Statement::Semicolon(sem) => {
|
||||||
|
if let SemicolonStatement::Assignment(assignment) = sem.statement() {
|
||||||
|
if let AssignmentDestination::Identifier(ident) = assignment.destination() {
|
||||||
|
if let Some(VariableData::ComptimeValue { .. }) =
|
||||||
|
scope.get_variable(ident.span.str()).as_deref()
|
||||||
|
{
|
||||||
|
let err = TranspileError::AssignmentError(AssignmentError {
|
||||||
|
identifier: ident.span.clone(),
|
||||||
|
message: "cannot assign to a compile-time variable in a conditional execute block"
|
||||||
|
.to_string(),
|
||||||
|
});
|
||||||
|
handler.receive(Box::new(err.clone()));
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Statement::Grouping(group) => {
|
||||||
|
group
|
||||||
|
.block()
|
||||||
|
.check_no_comptime_assignments(scope, handler)?;
|
||||||
|
}
|
||||||
|
Statement::ExecuteBlock(ex) => {
|
||||||
|
ex.check_no_comptime_assignments(scope, handler)?;
|
||||||
|
}
|
||||||
|
Statement::WhileLoop(while_loop) => {
|
||||||
|
while_loop
|
||||||
|
.block()
|
||||||
|
.check_no_comptime_assignments(scope, handler)?;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExecuteBlock {
|
||||||
|
pub(super) fn check_no_comptime_assignments(
|
||||||
|
&self,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> TranspileResult<()> {
|
||||||
|
match self {
|
||||||
|
Self::HeadTail(_, tail) => match tail {
|
||||||
|
ExecuteBlockTail::Block(block) => {
|
||||||
|
block.check_no_comptime_assignments(scope, handler)?;
|
||||||
|
}
|
||||||
|
ExecuteBlockTail::ExecuteBlock(_, ex) => {
|
||||||
|
ex.check_no_comptime_assignments(scope, handler)?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Self::IfElse(_, then, el) => {
|
||||||
|
then.check_no_comptime_assignments(scope, handler)?;
|
||||||
|
el.block().check_no_comptime_assignments(scope, handler)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -87,14 +87,22 @@ mod enabled {
|
||||||
scope: &Arc<Scope>,
|
scope: &Arc<Scope>,
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
) -> TranspileResult<Result<ComptimeValue, NotComptime>> {
|
) -> TranspileResult<Result<ComptimeValue, NotComptime>> {
|
||||||
|
if let Some(res) = self.eval_result.get().cloned() {
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
// required to keep the lua instance alive
|
// required to keep the lua instance alive
|
||||||
let (lua_result, _lua) = self.eval(scope, handler)?;
|
let (lua_result, _lua) = self.eval(scope, handler)?;
|
||||||
|
|
||||||
self.handle_lua_result(lua_result, handler).map(|res| {
|
let res = self.handle_lua_result(lua_result, handler).map(|res| {
|
||||||
res.ok_or_else(|| NotComptime {
|
res.ok_or_else(|| NotComptime {
|
||||||
expression: self.span(),
|
expression: self.span(),
|
||||||
})
|
})
|
||||||
})
|
})?;
|
||||||
|
|
||||||
|
self.eval_result.set(res.clone()).ok();
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_globals(&self, lua: &Lua, scope: &Arc<Scope>) -> TranspileResult<()> {
|
fn add_globals(&self, lua: &Lua, scope: &Arc<Scope>) -> TranspileResult<()> {
|
||||||
|
|
|
@ -49,6 +49,9 @@ pub use variables::{Scope, VariableData};
|
||||||
|
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
|
#[cfg(feature = "shulkerbox")]
|
||||||
|
mod checks;
|
||||||
|
|
||||||
/// Data of a function.
|
/// Data of a function.
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub struct FunctionData {
|
pub struct FunctionData {
|
||||||
|
|
|
@ -943,14 +943,33 @@ impl Transpiler {
|
||||||
) -> TranspileResult<Option<(Vec<Command>, ShulkerboxMacroStringMap, Execute)>> {
|
) -> TranspileResult<Option<(Vec<Command>, ShulkerboxMacroStringMap, Execute)>> {
|
||||||
match execute {
|
match execute {
|
||||||
ExecuteBlock::HeadTail(head, tail) => {
|
ExecuteBlock::HeadTail(head, tail) => {
|
||||||
let tail = match tail {
|
if let ExecuteBlockHead::Conditional(cond) = head {
|
||||||
|
let cond_eval = cond.condition().expression().comptime_eval(scope, handler);
|
||||||
|
if cond_eval.is_err()
|
||||||
|
|| cond_eval.is_ok_and(|val| !matches!(val, ComptimeValue::Boolean(_)))
|
||||||
|
{
|
||||||
|
match tail {
|
||||||
|
ExecuteBlockTail::Block(block) => {
|
||||||
|
block.check_no_comptime_assignments(scope, handler)?;
|
||||||
|
}
|
||||||
|
ExecuteBlockTail::ExecuteBlock(_, ex) => {
|
||||||
|
ex.check_no_comptime_assignments(scope, handler)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.combine_execute_head_tail(
|
||||||
|
head,
|
||||||
|
|transpiler, program_identifier, scope, handler| match tail {
|
||||||
ExecuteBlockTail::Block(block) => {
|
ExecuteBlockTail::Block(block) => {
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
let commands = block
|
let commands = block
|
||||||
.statements()
|
.statements()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|s| {
|
.flat_map(|s| {
|
||||||
self.transpile_statement(s, program_identifier, scope, handler)
|
transpiler
|
||||||
|
.transpile_statement(s, program_identifier, scope, handler)
|
||||||
.unwrap_or_else(|err| {
|
.unwrap_or_else(|err| {
|
||||||
errors.push(err);
|
errors.push(err);
|
||||||
Vec::new()
|
Vec::new()
|
||||||
|
@ -967,21 +986,58 @@ impl Transpiler {
|
||||||
Ok(Some((Vec::new(), BTreeMap::new(), Execute::Runs(commands))))
|
Ok(Some((Vec::new(), BTreeMap::new(), Execute::Runs(commands))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExecuteBlockTail::ExecuteBlock(_, execute_block) => self
|
ExecuteBlockTail::ExecuteBlock(_, execute_block) => transpiler
|
||||||
.transpile_execute_block_internal(
|
.transpile_execute_block_internal(
|
||||||
execute_block,
|
execute_block,
|
||||||
program_identifier,
|
program_identifier,
|
||||||
scope,
|
scope,
|
||||||
handler,
|
handler,
|
||||||
),
|
),
|
||||||
}?;
|
},
|
||||||
|
program_identifier,
|
||||||
self.combine_execute_head_tail(head, tail, program_identifier, scope, handler)
|
scope,
|
||||||
|
handler,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ExecuteBlock::IfElse(cond, block, el) => {
|
ExecuteBlock::IfElse(cond, block, el) => {
|
||||||
|
let cond_eval = cond.condition().expression().comptime_eval(scope, handler);
|
||||||
|
if cond_eval.is_err()
|
||||||
|
|| cond_eval.is_ok_and(|val| !matches!(val, ComptimeValue::Boolean(_)))
|
||||||
|
{
|
||||||
|
block.check_no_comptime_assignments(scope, handler)?;
|
||||||
|
el.block().check_no_comptime_assignments(scope, handler)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.transpile_conditional(
|
||||||
|
cond,
|
||||||
|
|transpiler, program_identifier, scope, handler| {
|
||||||
|
let ex = transpiler.transpile_conditional_block(
|
||||||
|
block,
|
||||||
|
program_identifier,
|
||||||
|
scope,
|
||||||
|
handler,
|
||||||
|
)?;
|
||||||
|
Ok(Some((Vec::new(), BTreeMap::new(), ex)))
|
||||||
|
},
|
||||||
|
Some(el),
|
||||||
|
program_identifier,
|
||||||
|
scope,
|
||||||
|
handler,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transpile_conditional_block(
|
||||||
|
&mut self,
|
||||||
|
block: &Block,
|
||||||
|
program_identifier: &str,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
handler: &impl Handler<base::Error>,
|
||||||
|
) -> TranspileResult<Execute> {
|
||||||
let statements = block.statements();
|
let statements = block.statements();
|
||||||
let then = if statements.is_empty() {
|
if statements.is_empty() {
|
||||||
Some(Execute::Runs(Vec::new()))
|
Ok(Execute::Runs(Vec::new()))
|
||||||
} else if statements.len() > 1 {
|
} else if statements.len() > 1 {
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
let commands = statements
|
let commands = statements
|
||||||
|
@ -997,83 +1053,70 @@ impl Transpiler {
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
return Err(errors.remove(0));
|
return Err(errors.remove(0));
|
||||||
}
|
}
|
||||||
Some(Execute::Runs(commands))
|
Ok(Execute::Runs(commands))
|
||||||
} else {
|
} else {
|
||||||
let cmds = self.transpile_statement(
|
let cmds =
|
||||||
&statements[0],
|
self.transpile_statement(&statements[0], program_identifier, scope, handler)?;
|
||||||
program_identifier,
|
|
||||||
scope,
|
|
||||||
handler,
|
|
||||||
)?;
|
|
||||||
if cmds.len() > 1 {
|
if cmds.len() > 1 {
|
||||||
Some(Execute::Runs(cmds))
|
Ok(Execute::Runs(cmds))
|
||||||
} else {
|
} else {
|
||||||
cmds.into_iter()
|
Ok(cmds.into_iter().next().map_or_else(
|
||||||
.next()
|
|| Execute::Runs(Vec::new()),
|
||||||
.map(|cmd| Execute::Run(Box::new(cmd)))
|
|cmd| Execute::Run(Box::new(cmd)),
|
||||||
}
|
))
|
||||||
};
|
|
||||||
|
|
||||||
then.map_or_else(
|
|
||||||
|| Ok(None),
|
|
||||||
|then| {
|
|
||||||
self.transpile_conditional(
|
|
||||||
cond,
|
|
||||||
then,
|
|
||||||
Some(el),
|
|
||||||
program_identifier,
|
|
||||||
scope,
|
|
||||||
handler,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transpile_conditional(
|
fn transpile_conditional<F, H>(
|
||||||
&mut self,
|
&mut self,
|
||||||
cond: &Conditional,
|
cond: &Conditional,
|
||||||
then: Execute,
|
compile_then: F,
|
||||||
el: Option<&Else>,
|
el: Option<&Else>,
|
||||||
program_identifier: &str,
|
program_identifier: &str,
|
||||||
scope: &Arc<Scope>,
|
scope: &Arc<Scope>,
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &H,
|
||||||
) -> TranspileResult<Option<(Vec<Command>, ShulkerboxMacroStringMap, Execute)>> {
|
) -> TranspileResult<Option<(Vec<Command>, ShulkerboxMacroStringMap, Execute)>>
|
||||||
|
where
|
||||||
|
H: Handler<base::Error>,
|
||||||
|
F: FnOnce(
|
||||||
|
&mut Self,
|
||||||
|
&str,
|
||||||
|
&Arc<Scope>,
|
||||||
|
&H,
|
||||||
|
)
|
||||||
|
-> TranspileResult<Option<(Vec<Command>, ShulkerboxMacroStringMap, Execute)>>,
|
||||||
|
{
|
||||||
let cond_expression = cond.condition().expression().as_ref();
|
let cond_expression = cond.condition().expression().as_ref();
|
||||||
|
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
let el = el.and_then(|el| {
|
|
||||||
let (_, block) = el.clone().dissolve();
|
|
||||||
let statements = block.statements();
|
|
||||||
let cmds = statements
|
|
||||||
.iter()
|
|
||||||
.flat_map(|statement| {
|
|
||||||
self.transpile_statement(statement, program_identifier, scope, handler)
|
|
||||||
.unwrap_or_else(|err| {
|
|
||||||
errors.push(err);
|
|
||||||
Vec::new()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
match cmds.len() {
|
|
||||||
0 => None,
|
|
||||||
1 => Some(Execute::Run(Box::new(
|
|
||||||
cmds.into_iter().next().expect("length is 1"),
|
|
||||||
))),
|
|
||||||
_ => Some(Execute::Runs(cmds)),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Ok(ComptimeValue::Boolean(value)) = cond_expression.comptime_eval(scope, handler) {
|
if let Ok(ComptimeValue::Boolean(value)) = cond_expression.comptime_eval(scope, handler) {
|
||||||
if value {
|
return if value {
|
||||||
Ok(Some((Vec::new(), BTreeMap::new(), then)))
|
let then = compile_then(self, program_identifier, scope, handler)?;
|
||||||
|
|
||||||
|
Ok(then)
|
||||||
} else {
|
} else {
|
||||||
Ok(el.map(|el| (Vec::new(), BTreeMap::new(), el)))
|
let (el, mut else_errors) = el.map_or_else(
|
||||||
|
|| (None, Vec::new()),
|
||||||
|
|el| self.transpile_else(el, program_identifier, scope, handler),
|
||||||
|
);
|
||||||
|
if !else_errors.is_empty() {
|
||||||
|
return Err(else_errors.remove(0));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
Ok(el.map(|el| (Vec::new(), BTreeMap::new(), el)))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let then = compile_then(self, program_identifier, scope, handler)?;
|
||||||
|
let (el, else_errors) = el.map_or_else(
|
||||||
|
|| (None, Vec::new()),
|
||||||
|
|el| self.transpile_else(el, program_identifier, scope, handler),
|
||||||
|
);
|
||||||
|
errors.extend(else_errors);
|
||||||
|
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
return Err(errors.remove(0));
|
return Err(errors.remove(0));
|
||||||
}
|
}
|
||||||
|
@ -1082,92 +1125,150 @@ impl Transpiler {
|
||||||
self.transpile_expression_as_condition(cond_expression, scope, handler)?;
|
self.transpile_expression_as_condition(cond_expression, scope, handler)?;
|
||||||
|
|
||||||
match cond {
|
match cond {
|
||||||
ExtendedCondition::Runtime(cond) => Ok(Some((
|
ExtendedCondition::Runtime(cond) => {
|
||||||
|
if let Some((mut prepare_cmds, mut then_prepare_variables, then)) = then {
|
||||||
|
prepare_cmds.extend(pre_cond_cmds);
|
||||||
|
then_prepare_variables.extend(prepare_variables);
|
||||||
|
Ok(Some((
|
||||||
|
prepare_cmds,
|
||||||
|
then_prepare_variables,
|
||||||
|
Execute::If(cond, Box::new(then), el.map(Box::new)),
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Ok(Some((
|
||||||
pre_cond_cmds,
|
pre_cond_cmds,
|
||||||
prepare_variables,
|
prepare_variables,
|
||||||
Execute::If(cond, Box::new(then), el.map(Box::new)),
|
Execute::If(cond, Box::new(Execute::Runs(Vec::new())), el.map(Box::new)),
|
||||||
))),
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
ExtendedCondition::Comptime(cond) => {
|
ExtendedCondition::Comptime(cond) => {
|
||||||
if cond {
|
if cond {
|
||||||
Ok(Some((Vec::new(), prepare_variables, then)))
|
then.map_or_else(
|
||||||
|
|| {
|
||||||
|
Ok(Some((
|
||||||
|
Vec::new(),
|
||||||
|
BTreeMap::new(),
|
||||||
|
Execute::Runs(Vec::new()),
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
|then| Ok(Some(then)),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Ok(el.map(|el| (Vec::new(), prepare_variables, el)))
|
Ok(el.map(|el| (Vec::new(), prepare_variables, el)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(clippy::too_many_lines)]
|
fn transpile_else(
|
||||||
fn combine_execute_head_tail(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
head: &ExecuteBlockHead,
|
el: &Else,
|
||||||
tail: Option<(Vec<Command>, ShulkerboxMacroStringMap, Execute)>,
|
|
||||||
program_identifier: &str,
|
program_identifier: &str,
|
||||||
scope: &Arc<Scope>,
|
scope: &Arc<Scope>,
|
||||||
handler: &impl Handler<base::Error>,
|
handler: &impl Handler<base::Error>,
|
||||||
) -> TranspileResult<Option<(Vec<Command>, ShulkerboxMacroStringMap, Execute)>> {
|
) -> (Option<Execute>, Vec<TranspileError>) {
|
||||||
|
let mut else_errors = Vec::new();
|
||||||
|
|
||||||
|
let block = el.block();
|
||||||
|
let statements = block.statements();
|
||||||
|
let cmds = statements
|
||||||
|
.iter()
|
||||||
|
.flat_map(|statement| {
|
||||||
|
self.transpile_statement(statement, program_identifier, scope, handler)
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
else_errors.push(err);
|
||||||
|
Vec::new()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let el = match cmds.len() {
|
||||||
|
0 => None,
|
||||||
|
1 => Some(Execute::Run(Box::new(
|
||||||
|
cmds.into_iter().next().expect("length is 1"),
|
||||||
|
))),
|
||||||
|
_ => Some(Execute::Runs(cmds)),
|
||||||
|
};
|
||||||
|
|
||||||
|
(el, else_errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
|
fn combine_execute_head_tail<H, F>(
|
||||||
|
&mut self,
|
||||||
|
head: &ExecuteBlockHead,
|
||||||
|
compile_tail: F,
|
||||||
|
program_identifier: &str,
|
||||||
|
scope: &Arc<Scope>,
|
||||||
|
handler: &H,
|
||||||
|
) -> TranspileResult<Option<(Vec<Command>, ShulkerboxMacroStringMap, Execute)>>
|
||||||
|
where
|
||||||
|
H: Handler<base::Error>,
|
||||||
|
F: FnOnce(
|
||||||
|
&mut Self,
|
||||||
|
&str,
|
||||||
|
&Arc<Scope>,
|
||||||
|
&H,
|
||||||
|
)
|
||||||
|
-> TranspileResult<Option<(Vec<Command>, ShulkerboxMacroStringMap, Execute)>>,
|
||||||
|
{
|
||||||
Ok(match head {
|
Ok(match head {
|
||||||
ExecuteBlockHead::Conditional(cond) => {
|
ExecuteBlockHead::Conditional(cond) => self.transpile_conditional(
|
||||||
if let Some((mut pre_cmds, prepare_variables, tail)) = tail {
|
|
||||||
self.transpile_conditional(
|
|
||||||
cond,
|
cond,
|
||||||
tail,
|
compile_tail,
|
||||||
None,
|
None,
|
||||||
program_identifier,
|
program_identifier,
|
||||||
scope,
|
scope,
|
||||||
handler,
|
handler,
|
||||||
)?
|
)?,
|
||||||
.map(|(pre_cond_cmds, mut prep_variables, cond)| {
|
|
||||||
pre_cmds.extend(pre_cond_cmds);
|
|
||||||
prep_variables.extend(prepare_variables);
|
|
||||||
(pre_cmds, prep_variables, cond)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ExecuteBlockHead::As(r#as) => {
|
ExecuteBlockHead::As(r#as) => {
|
||||||
let selector = r#as
|
let selector = r#as
|
||||||
.as_selector()
|
.as_selector()
|
||||||
.to_macro_string(Some(self), scope, handler)?;
|
.to_macro_string(Some(self), scope, handler)?;
|
||||||
let (macro_string, prepare_variables) = selector.into_sb();
|
let (macro_string, prepare_variables) = selector.into_sb();
|
||||||
tail.map(|(pre_cmds, mut prep_variables, tail)| {
|
compile_tail(self, program_identifier, scope, handler)?.map(
|
||||||
|
|(pre_cmds, mut prep_variables, tail)| {
|
||||||
prep_variables.extend(prepare_variables);
|
prep_variables.extend(prepare_variables);
|
||||||
(
|
(
|
||||||
pre_cmds,
|
pre_cmds,
|
||||||
prep_variables,
|
prep_variables,
|
||||||
Execute::As(macro_string, Box::new(tail)),
|
Execute::As(macro_string, Box::new(tail)),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::At(at) => {
|
ExecuteBlockHead::At(at) => {
|
||||||
let selector = at
|
let selector = at
|
||||||
.at_selector()
|
.at_selector()
|
||||||
.to_macro_string(Some(self), scope, handler)?;
|
.to_macro_string(Some(self), scope, handler)?;
|
||||||
let (macro_string, prepare_variables) = selector.into_sb();
|
let (macro_string, prepare_variables) = selector.into_sb();
|
||||||
tail.map(|(pre_cmds, mut prep_variables, tail)| {
|
compile_tail(self, program_identifier, scope, handler)?.map(
|
||||||
|
|(pre_cmds, mut prep_variables, tail)| {
|
||||||
prep_variables.extend(prepare_variables);
|
prep_variables.extend(prepare_variables);
|
||||||
(
|
(
|
||||||
pre_cmds,
|
pre_cmds,
|
||||||
prep_variables,
|
prep_variables,
|
||||||
Execute::At(macro_string, Box::new(tail)),
|
Execute::At(macro_string, Box::new(tail)),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Align(align) => {
|
ExecuteBlockHead::Align(align) => {
|
||||||
let align = align
|
let align = align
|
||||||
.align_selector()
|
.align_selector()
|
||||||
.to_macro_string(Some(self), scope, handler)?;
|
.to_macro_string(Some(self), scope, handler)?;
|
||||||
let (macro_string, prepare_variables) = align.into_sb();
|
let (macro_string, prepare_variables) = align.into_sb();
|
||||||
tail.map(|(pre_cmds, mut prep_variables, tail)| {
|
compile_tail(self, program_identifier, scope, handler)?.map(
|
||||||
|
|(pre_cmds, mut prep_variables, tail)| {
|
||||||
prep_variables.extend(prepare_variables);
|
prep_variables.extend(prepare_variables);
|
||||||
(
|
(
|
||||||
pre_cmds,
|
pre_cmds,
|
||||||
prep_variables,
|
prep_variables,
|
||||||
Execute::Align(macro_string, Box::new(tail)),
|
Execute::Align(macro_string, Box::new(tail)),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Anchored(anchored) => {
|
ExecuteBlockHead::Anchored(anchored) => {
|
||||||
let anchor =
|
let anchor =
|
||||||
|
@ -1175,28 +1276,32 @@ impl Transpiler {
|
||||||
.anchored_selector()
|
.anchored_selector()
|
||||||
.to_macro_string(Some(self), scope, handler)?;
|
.to_macro_string(Some(self), scope, handler)?;
|
||||||
let (macro_string, prepare_variables) = anchor.into_sb();
|
let (macro_string, prepare_variables) = anchor.into_sb();
|
||||||
tail.map(|(pre_cmds, mut prep_variables, tail)| {
|
compile_tail(self, program_identifier, scope, handler)?.map(
|
||||||
|
|(pre_cmds, mut prep_variables, tail)| {
|
||||||
prep_variables.extend(prepare_variables);
|
prep_variables.extend(prepare_variables);
|
||||||
(
|
(
|
||||||
pre_cmds,
|
pre_cmds,
|
||||||
prep_variables,
|
prep_variables,
|
||||||
Execute::Anchored(macro_string, Box::new(tail)),
|
Execute::Anchored(macro_string, Box::new(tail)),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::In(r#in) => {
|
ExecuteBlockHead::In(r#in) => {
|
||||||
let dimension = r#in
|
let dimension = r#in
|
||||||
.in_selector()
|
.in_selector()
|
||||||
.to_macro_string(Some(self), scope, handler)?;
|
.to_macro_string(Some(self), scope, handler)?;
|
||||||
let (macro_string, prepare_variables) = dimension.into_sb();
|
let (macro_string, prepare_variables) = dimension.into_sb();
|
||||||
tail.map(|(pre_cmds, mut prep_variables, tail)| {
|
compile_tail(self, program_identifier, scope, handler)?.map(
|
||||||
|
|(pre_cmds, mut prep_variables, tail)| {
|
||||||
prep_variables.extend(prepare_variables);
|
prep_variables.extend(prepare_variables);
|
||||||
(
|
(
|
||||||
pre_cmds,
|
pre_cmds,
|
||||||
prep_variables,
|
prep_variables,
|
||||||
Execute::In(macro_string, Box::new(tail)),
|
Execute::In(macro_string, Box::new(tail)),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Positioned(positioned) => {
|
ExecuteBlockHead::Positioned(positioned) => {
|
||||||
let position =
|
let position =
|
||||||
|
@ -1204,14 +1309,16 @@ impl Transpiler {
|
||||||
.positioned_selector()
|
.positioned_selector()
|
||||||
.to_macro_string(Some(self), scope, handler)?;
|
.to_macro_string(Some(self), scope, handler)?;
|
||||||
let (macro_string, prepare_variables) = position.into_sb();
|
let (macro_string, prepare_variables) = position.into_sb();
|
||||||
tail.map(|(pre_cmds, mut prep_variables, tail)| {
|
compile_tail(self, program_identifier, scope, handler)?.map(
|
||||||
|
|(pre_cmds, mut prep_variables, tail)| {
|
||||||
prep_variables.extend(prepare_variables);
|
prep_variables.extend(prepare_variables);
|
||||||
(
|
(
|
||||||
pre_cmds,
|
pre_cmds,
|
||||||
prep_variables,
|
prep_variables,
|
||||||
Execute::Positioned(macro_string, Box::new(tail)),
|
Execute::Positioned(macro_string, Box::new(tail)),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Rotated(rotated) => {
|
ExecuteBlockHead::Rotated(rotated) => {
|
||||||
let rotation =
|
let rotation =
|
||||||
|
@ -1219,14 +1326,16 @@ impl Transpiler {
|
||||||
.rotated_selector()
|
.rotated_selector()
|
||||||
.to_macro_string(Some(self), scope, handler)?;
|
.to_macro_string(Some(self), scope, handler)?;
|
||||||
let (macro_string, prepare_variables) = rotation.into_sb();
|
let (macro_string, prepare_variables) = rotation.into_sb();
|
||||||
tail.map(|(pre_cmds, mut prep_variables, tail)| {
|
compile_tail(self, program_identifier, scope, handler)?.map(
|
||||||
|
|(pre_cmds, mut prep_variables, tail)| {
|
||||||
prep_variables.extend(prepare_variables);
|
prep_variables.extend(prepare_variables);
|
||||||
(
|
(
|
||||||
pre_cmds,
|
pre_cmds,
|
||||||
prep_variables,
|
prep_variables,
|
||||||
Execute::Rotated(macro_string, Box::new(tail)),
|
Execute::Rotated(macro_string, Box::new(tail)),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Facing(facing) => {
|
ExecuteBlockHead::Facing(facing) => {
|
||||||
let facing =
|
let facing =
|
||||||
|
@ -1234,56 +1343,64 @@ impl Transpiler {
|
||||||
.facing_selector()
|
.facing_selector()
|
||||||
.to_macro_string(Some(self), scope, handler)?;
|
.to_macro_string(Some(self), scope, handler)?;
|
||||||
let (macro_string, prepare_variables) = facing.into_sb();
|
let (macro_string, prepare_variables) = facing.into_sb();
|
||||||
tail.map(|(pre_cmds, mut prep_variables, tail)| {
|
compile_tail(self, program_identifier, scope, handler)?.map(
|
||||||
|
|(pre_cmds, mut prep_variables, tail)| {
|
||||||
prep_variables.extend(prepare_variables);
|
prep_variables.extend(prepare_variables);
|
||||||
(
|
(
|
||||||
pre_cmds,
|
pre_cmds,
|
||||||
prep_variables,
|
prep_variables,
|
||||||
Execute::Facing(macro_string, Box::new(tail)),
|
Execute::Facing(macro_string, Box::new(tail)),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::AsAt(as_at) => {
|
ExecuteBlockHead::AsAt(as_at) => {
|
||||||
let selector = as_at
|
let selector = as_at
|
||||||
.asat_selector()
|
.asat_selector()
|
||||||
.to_macro_string(Some(self), scope, handler)?;
|
.to_macro_string(Some(self), scope, handler)?;
|
||||||
let (macro_string, prepare_variables) = selector.into_sb();
|
let (macro_string, prepare_variables) = selector.into_sb();
|
||||||
tail.map(|(pre_cmds, mut prep_variables, tail)| {
|
compile_tail(self, program_identifier, scope, handler)?.map(
|
||||||
|
|(pre_cmds, mut prep_variables, tail)| {
|
||||||
prep_variables.extend(prepare_variables);
|
prep_variables.extend(prepare_variables);
|
||||||
(
|
(
|
||||||
pre_cmds,
|
pre_cmds,
|
||||||
prep_variables,
|
prep_variables,
|
||||||
Execute::AsAt(macro_string, Box::new(tail)),
|
Execute::AsAt(macro_string, Box::new(tail)),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::On(on) => {
|
ExecuteBlockHead::On(on) => {
|
||||||
let dimension = on
|
let dimension = on
|
||||||
.on_selector()
|
.on_selector()
|
||||||
.to_macro_string(Some(self), scope, handler)?;
|
.to_macro_string(Some(self), scope, handler)?;
|
||||||
let (macro_string, prepare_variables) = dimension.into_sb();
|
let (macro_string, prepare_variables) = dimension.into_sb();
|
||||||
tail.map(|(pre_cmds, mut prep_variables, tail)| {
|
compile_tail(self, program_identifier, scope, handler)?.map(
|
||||||
|
|(pre_cmds, mut prep_variables, tail)| {
|
||||||
prep_variables.extend(prepare_variables);
|
prep_variables.extend(prepare_variables);
|
||||||
(
|
(
|
||||||
pre_cmds,
|
pre_cmds,
|
||||||
prep_variables,
|
prep_variables,
|
||||||
Execute::On(macro_string, Box::new(tail)),
|
Execute::On(macro_string, Box::new(tail)),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Store(store) => {
|
ExecuteBlockHead::Store(store) => {
|
||||||
let store = store
|
let store = store
|
||||||
.store_selector()
|
.store_selector()
|
||||||
.to_macro_string(Some(self), scope, handler)?;
|
.to_macro_string(Some(self), scope, handler)?;
|
||||||
let (macro_string, prepare_variables) = store.into_sb();
|
let (macro_string, prepare_variables) = store.into_sb();
|
||||||
tail.map(|(pre_cmds, mut prep_variables, tail)| {
|
compile_tail(self, program_identifier, scope, handler)?.map(
|
||||||
|
|(pre_cmds, mut prep_variables, tail)| {
|
||||||
prep_variables.extend(prepare_variables);
|
prep_variables.extend(prepare_variables);
|
||||||
(
|
(
|
||||||
pre_cmds,
|
pre_cmds,
|
||||||
prep_variables,
|
prep_variables,
|
||||||
Execute::Store(macro_string, Box::new(tail)),
|
Execute::Store(macro_string, Box::new(tail)),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ExecuteBlockHead::Summon(summon) => {
|
ExecuteBlockHead::Summon(summon) => {
|
||||||
let entity =
|
let entity =
|
||||||
|
@ -1291,14 +1408,16 @@ impl Transpiler {
|
||||||
.summon_selector()
|
.summon_selector()
|
||||||
.to_macro_string(Some(self), scope, handler)?;
|
.to_macro_string(Some(self), scope, handler)?;
|
||||||
let (macro_string, prepare_variables) = entity.into_sb();
|
let (macro_string, prepare_variables) = entity.into_sb();
|
||||||
tail.map(|(pre_cmds, mut prep_variables, tail)| {
|
compile_tail(self, program_identifier, scope, handler)?.map(
|
||||||
|
|(pre_cmds, mut prep_variables, tail)| {
|
||||||
prep_variables.extend(prepare_variables);
|
prep_variables.extend(prepare_variables);
|
||||||
(
|
(
|
||||||
pre_cmds,
|
pre_cmds,
|
||||||
prep_variables,
|
prep_variables,
|
||||||
Execute::Summon(macro_string, Box::new(tail)),
|
Execute::Summon(macro_string, Box::new(tail)),
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue