run as expression, implement return statement

This commit is contained in:
Moritz Hölting 2025-04-07 18:01:35 +02:00
parent 68149d9ddf
commit dd97937feb
12 changed files with 481 additions and 201 deletions

View File

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

View File

@ -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",
}
}

View File

@ -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<base::Error>,
) -> 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(_) => {

View File

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

View File

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

View File

@ -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<base::Error>) -> ParseResult<Primary> {
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)

View File

@ -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<base::Error>,
) -> ParseResult<Semicolon> {
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(),
});

View File

@ -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<const N: usize>(
&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<String>) {
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<const N: usize>(
&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)
}
}

View File

@ -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,
})
}
};

View File

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

View File

@ -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<Scope>,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> {
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<Scope>,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> {
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)
}
},
},
}
}

View File

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