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 = { version = "1.0.217", features = ["derive"], optional = true }
serde_json = { version = "1.0.138", optional = true } serde_json = { version = "1.0.138", optional = true }
# shulkerbox = { version = "0.1.0", default-features = false, 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" strsim = "0.11.1"
strum = { version = "0.27.0", features = ["derive"] } strum = { version = "0.27.0", features = ["derive"] }
thiserror = "2.0.11" thiserror = "2.0.11"

View File

@ -53,6 +53,7 @@ pub enum KeywordKind {
Bool, Bool,
Macro, Macro,
Val, Val,
Return,
} }
impl Display for KeywordKind { impl Display for KeywordKind {
@ -119,6 +120,7 @@ impl KeywordKind {
Self::Bool => "bool", Self::Bool => "bool",
Self::Macro => "macro", Self::Macro => "macro",
Self::Val => "val", Self::Val => "val",
Self::Return => "return",
} }
} }

View File

@ -14,7 +14,7 @@ use crate::{
Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockHeadItem as _, Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockHeadItem as _,
ExecuteBlockTail, ExecuteBlockTail,
}, },
Assignment, AssignmentDestination, Block, Grouping, Run, Semicolon, SemicolonStatement, Assignment, AssignmentDestination, Block, Grouping, Semicolon, SemicolonStatement,
Statement, VariableDeclaration, Statement, VariableDeclaration,
}, },
AnyStringLiteral, AnyStringLiteral,
@ -228,7 +228,6 @@ impl Statement {
let child_scope = SemanticScope::with_parent(scope); let child_scope = SemanticScope::with_parent(scope);
group.analyze_semantics(&child_scope, handler) group.analyze_semantics(&child_scope, handler)
} }
Self::Run(run) => run.analyze_semantics(scope, handler),
Self::Semicolon(sem) => sem.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 { impl Semicolon {
fn analyze_semantics( fn analyze_semantics(
&self, &self,
@ -364,6 +353,7 @@ impl Semicolon {
} }
SemicolonStatement::Expression(expr) => expr.analyze_semantics(scope, handler), SemicolonStatement::Expression(expr) => expr.analyze_semantics(scope, handler),
SemicolonStatement::VariableDeclaration(decl) => decl.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) 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), Self::Lua(lua) => lua.analyze_semantics(scope, handler),
} }
@ -701,12 +706,24 @@ impl Primary {
_ => false, _ => false,
}, },
Self::Prefix(prefixed) => match prefixed.operator() { Self::Prefix(prefixed) => match prefixed.operator() {
PrefixOperator::LogicalNot(_) => prefixed PrefixOperator::LogicalNot(_) => {
expected == ValueType::Boolean
&& prefixed
.operand() .operand()
.can_yield_type_semantics(ValueType::Boolean, scope), .can_yield_type_semantics(ValueType::Boolean, scope)
PrefixOperator::Negate(_) => prefixed }
PrefixOperator::Negate(_) => {
expected == ValueType::Integer
&& prefixed
.operand() .operand()
.can_yield_type_semantics(ValueType::Integer, scope), .can_yield_type_semantics(ValueType::Integer, scope)
}
PrefixOperator::Run(_) => {
expected == ValueType::String
&& prefixed
.operand()
.can_yield_type_semantics(ValueType::String, scope)
}
}, },
Self::Parenthesized(parenthesized) => { Self::Parenthesized(parenthesized) => {
parenthesized.can_yield_type_semantics(expected, scope) parenthesized.can_yield_type_semantics(expected, scope)
@ -807,8 +824,18 @@ impl Binary {
#[must_use] #[must_use]
fn can_yield_type_semantics(&self, expected: ValueType, scope: &SemanticScope) -> bool { fn can_yield_type_semantics(&self, expected: ValueType, scope: &SemanticScope) -> bool {
match self.operator() { match self.operator() {
BinaryOperator::Add(_) BinaryOperator::Add(_) => {
| BinaryOperator::Subtract(_) 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::Multiply(_)
| BinaryOperator::Divide(_) | BinaryOperator::Divide(_)
| BinaryOperator::Modulo(_) => { | BinaryOperator::Modulo(_) => {

View File

@ -56,7 +56,7 @@ impl SyntaxKind {
} else if variants.len() == 1 { } else if variants.len() == 1 {
variants[0].expected_binding_str() variants[0].expected_binding_str()
} else { } else {
let comma_range = ..variants.len() - 2; let comma_range = ..variants.len() - 1;
let comma_elements = variants[comma_range] let comma_elements = variants[comma_range]
.iter() .iter()
.map(Self::expected_binding_str) .map(Self::expected_binding_str)

View File

@ -357,11 +357,16 @@ impl Parser<'_> {
Reading::Atomic(Token::Keyword(pub_keyword)) Reading::Atomic(Token::Keyword(pub_keyword))
if pub_keyword.keyword == KeywordKind::Pub => if pub_keyword.keyword == KeywordKind::Pub =>
{ {
if let Ok(function) = self.try_parse(|parser| parser.parse_function(&VoidHandler)) { 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()); tracing::trace!("Parsed function '{:?}'", function.identifier.span.str());
Ok(Declaration::Function(function)) Ok(Declaration::Function(function))
} else { }
_ => {
// eat the pub keyword // eat the pub keyword
self.forward(); self.forward();
@ -371,6 +376,7 @@ impl Parser<'_> {
Ok(Declaration::GlobalVariable((Some(pub_keyword), var, semi))) Ok(Declaration::GlobalVariable((Some(pub_keyword), var, semi)))
} }
} }
}
// parse annotations // parse annotations
Reading::Atomic(Token::Punctuation(punctuation)) if punctuation.punctuation == '#' => { Reading::Atomic(Token::Punctuation(punctuation)) if punctuation.punctuation == '#' => {

View File

@ -314,12 +314,15 @@ pub enum PrefixOperator {
LogicalNot(Punctuation), LogicalNot(Punctuation),
/// The negate operator '-'. /// The negate operator '-'.
Negate(Punctuation), Negate(Punctuation),
/// The run keyword 'run'.
Run(Keyword),
} }
impl SourceElement for PrefixOperator { impl SourceElement for PrefixOperator {
fn span(&self) -> Span { fn span(&self) -> Span {
match self { match self {
Self::LogicalNot(token) | Self::Negate(token) => token.span.clone(), 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)] #[expect(clippy::too_many_lines)]
pub fn parse_primary(&mut self, handler: &impl Handler<base::Error>) -> ParseResult<Primary> { pub fn parse_primary(&mut self, handler: &impl Handler<base::Error>) -> ParseResult<Primary> {
match self.stop_at_significant() { match self.stop_at_significant() {
// prefixed expression // prefixed expression with '!' or '-'
Reading::Atomic(Token::Punctuation(punc)) if matches!(punc.punctuation, '!' | '-') => { Reading::Atomic(Token::Punctuation(punc)) if matches!(punc.punctuation, '!' | '-') => {
// eat the prefix // eat the prefix
self.forward(); 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 // parenthesized expression
Reading::IntoDelimited(left_parenthesis) if left_parenthesis.punctuation == '(' => self Reading::IntoDelimited(left_parenthesis) if left_parenthesis.punctuation == '(' => self
.parse_parenthesized(handler) .parse_parenthesized(handler)

View File

@ -43,7 +43,6 @@ use super::{expression::Expression, Annotation, AnyStringLiteral};
/// | Grouping /// | Grouping
/// | DocComment /// | DocComment
/// | Semicolon /// | Semicolon
/// | Run
/// ; /// ;
/// ``` /// ```
#[allow(missing_docs)] #[allow(missing_docs)]
@ -56,7 +55,6 @@ pub enum Statement {
Grouping(Grouping), Grouping(Grouping),
DocComment(DocComment), DocComment(DocComment),
Semicolon(Semicolon), Semicolon(Semicolon),
Run(Run),
} }
impl SourceElement for Statement { impl SourceElement for Statement {
@ -68,7 +66,6 @@ impl SourceElement for Statement {
Self::Grouping(grouping) => grouping.span(), Self::Grouping(grouping) => grouping.span(),
Self::DocComment(doc_comment) => doc_comment.span(), Self::DocComment(doc_comment) => doc_comment.span(),
Self::Semicolon(semi) => semi.span(), Self::Semicolon(semi) => semi.span(),
Self::Run(run) => run.span(),
} }
} }
} }
@ -107,6 +104,14 @@ impl Statement {
target: "expressions".to_string(), 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) 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. /// Represents a grouping statement in the syntax tree.
/// ///
/// Syntax Synopsis: /// Syntax Synopsis:
@ -293,6 +258,8 @@ pub enum SemicolonStatement {
VariableDeclaration(VariableDeclaration), VariableDeclaration(VariableDeclaration),
/// An assignment. /// An assignment.
Assignment(Assignment), Assignment(Assignment),
/// A return statement.
Return(ReturnStatement),
} }
impl SourceElement for SemicolonStatement { impl SourceElement for SemicolonStatement {
@ -301,10 +268,46 @@ impl SourceElement for SemicolonStatement {
Self::Expression(expression) => expression.span(), Self::Expression(expression) => expression.span(),
Self::VariableDeclaration(declaration) => declaration.span(), Self::VariableDeclaration(declaration) => declaration.span(),
Self::Assignment(assignment) => assignment.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. /// Represents a variable declaration in the syntax tree.
/// ///
/// Syntax Synopsis: /// 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 // semicolon statement
_ => self.parse_semicolon(handler).map(Statement::Semicolon), _ => self.parse_semicolon(handler).map(Statement::Semicolon),
} }
@ -941,6 +925,17 @@ impl Parser<'_> {
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> ParseResult<Semicolon> { ) -> ParseResult<Semicolon> {
let statement = match self.stop_at_significant() { 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)) Reading::Atomic(Token::Keyword(keyword))
if matches!( if matches!(
keyword.keyword, keyword.keyword,
@ -1025,6 +1020,7 @@ impl Parser<'_> {
expected: SyntaxKind::Either(&[ expected: SyntaxKind::Either(&[
SyntaxKind::Keyword(KeywordKind::Int), SyntaxKind::Keyword(KeywordKind::Int),
SyntaxKind::Keyword(KeywordKind::Bool), SyntaxKind::Keyword(KeywordKind::Bool),
SyntaxKind::Keyword(KeywordKind::Val),
]), ]),
found: unexpected.into_token(), found: unexpected.into_token(),
}); });

View File

@ -289,6 +289,10 @@ impl Primary {
matches!(r#type, ValueType::Integer) matches!(r#type, ValueType::Integer)
&& prefix.operand().can_yield_type(r#type, scope) && 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) => { Self::Indexed(indexed) => {
if let Self::Identifier(ident) = indexed.object().as_ref() { 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( pub(super) fn transpile_primary_expression(
&mut self, &mut self,
primary: &Primary, primary: &Primary,
@ -760,8 +764,8 @@ impl Transpiler {
StorageType::Byte | StorageType::Int | StorageType::Long StorageType::Byte | StorageType::Int | StorageType::Long
) => ) =>
{ {
let (target_objective, mut targets) = self.get_temp_scoreboard_locations(1); let (target_objective, [target_ident]) =
let target_ident = targets.pop().expect("at least size 1"); self.get_temp_scoreboard_locations_array();
let score_to_storage_cmd = Command::Execute(Execute::Store( let score_to_storage_cmd = Command::Execute(Execute::Store(
format!( format!(
@ -807,6 +811,65 @@ impl Transpiler {
Ok(cmds) 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) => { Primary::Identifier(ident) => {
let variable = scope.get_variable(ident.span.str()); 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(_) => { PrefixOperator::Negate(_) => {
let err = TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ExpectedType::Boolean, expected_type: ExpectedType::Boolean,
@ -1289,11 +1371,12 @@ impl Transpiler {
let right = binary.right_operand(); let right = binary.right_operand();
let operator = binary.operator(); 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 { let score_target_location = match target {
DataLocation::ScoreboardValue { objective, target } => (objective, target), DataLocation::ScoreboardValue { objective, target } => (objective, target),
_ => (&temp_objective, &temp_locations[0]), _ => (&temp_objective, &temp_location_a),
}; };
let left_cmds = self.transpile_expression( let left_cmds = self.transpile_expression(
@ -1318,7 +1401,7 @@ impl Transpiler {
right, right,
&DataLocation::ScoreboardValue { &DataLocation::ScoreboardValue {
objective: temp_objective.clone(), objective: temp_objective.clone(),
target: temp_locations[1].clone(), target: temp_location_b.clone(),
}, },
scope, scope,
handler, handler,
@ -1328,7 +1411,7 @@ impl Transpiler {
right_cmds, right_cmds,
( (
temp_objective.as_str(), 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."), _ => 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( let condition = Condition::Atom(
format!( format!(
"score {target} {temp_objective} {operator} {source} {temp_objective}", "score {temp_location_a} {temp_objective} {operator} {temp_location_b} {temp_objective}"
target = temp_locations[0],
source = temp_locations[1]
) )
.into(), .into(),
); );
@ -1434,7 +1516,7 @@ impl Transpiler {
binary.left_operand(), binary.left_operand(),
&DataLocation::ScoreboardValue { &DataLocation::ScoreboardValue {
objective: temp_objective.clone(), objective: temp_objective.clone(),
target: std::mem::take(&mut temp_locations[0]), target: temp_location_a,
}, },
scope, scope,
handler, handler,
@ -1443,7 +1525,7 @@ impl Transpiler {
binary.right_operand(), binary.right_operand(),
&DataLocation::ScoreboardValue { &DataLocation::ScoreboardValue {
objective: temp_objective, objective: temp_objective,
target: std::mem::take(&mut temp_locations[1]), target: temp_location_b,
}, },
scope, scope,
handler, handler,
@ -1707,6 +1789,17 @@ impl Transpiler {
(objective, targets) (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. /// Get temporary storage locations.
pub(super) fn get_temp_storage_locations(&mut self, amount: usize) -> (String, Vec<String>) { pub(super) fn get_temp_storage_locations(&mut self, amount: usize) -> (String, Vec<String>) {
let storage_name = "shulkerscript:temp_".to_string() let storage_name = "shulkerscript:temp_".to_string()
@ -1734,4 +1827,15 @@ impl Transpiler {
(storage_name, paths) (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::BooleanStorage { .. }
| VariableData::ScoreboardValue { .. } => { | VariableData::ScoreboardValue { .. } => {
let (temp_storage, mut temp_path) = let (temp_storage, [temp_path]) =
self.get_temp_storage_locations(1); self.get_temp_storage_locations_array();
let prepare_cmds = self.transpile_primary_expression( let prepare_cmds = self.transpile_primary_expression(
primary, primary,
&super::expression::DataLocation::Storage { &super::expression::DataLocation::Storage {
storage_name: temp_storage.clone(), storage_name: temp_storage.clone(),
path: temp_path[0].clone(), path: temp_path.clone(),
r#type: match var.as_ref() { r#type: match var.as_ref() {
VariableData::BooleanStorage { .. } => { VariableData::BooleanStorage { .. } => {
StorageType::Boolean StorageType::Boolean
@ -340,7 +340,7 @@ impl Transpiler {
Ok(Parameter::Storage { Ok(Parameter::Storage {
prepare_cmds, prepare_cmds,
storage_name: temp_storage, storage_name: temp_storage,
path: std::mem::take(&mut temp_path[0]), path: temp_path,
}) })
} }
_ => { _ => {
@ -364,12 +364,13 @@ impl Transpiler {
| Primary::FunctionCall(_), | Primary::FunctionCall(_),
) )
| Expression::Binary(_) => { | 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( let prepare_cmds = self.transpile_expression(
expression, expression,
&super::expression::DataLocation::Storage { &super::expression::DataLocation::Storage {
storage_name: temp_storage.clone(), storage_name: temp_storage.clone(),
path: temp_path[0].clone(), path: temp_path.clone(),
r#type: StorageType::Int, r#type: StorageType::Int,
}, },
scope, scope,
@ -379,7 +380,7 @@ impl Transpiler {
Ok(Parameter::Storage { Ok(Parameter::Storage {
prepare_cmds, prepare_cmds,
storage_name: temp_storage, storage_name: temp_storage,
path: std::mem::take(&mut temp_path[0]), path: temp_path,
}) })
} }
}; };

View File

@ -158,15 +158,12 @@ fn print_function(
json!({"nbt": path, "storage": storage_name, "color": PARAM_COLOR}), json!({"nbt": path, "storage": storage_name, "color": PARAM_COLOR}),
), ),
DataLocation::Tag { tag_name, entity } => { DataLocation::Tag { tag_name, entity } => {
let (temp_storage_name, temp_storage_paths) = let (temp_storage_name, [temp_storage_path]) =
transpiler.get_temp_storage_locations(1); transpiler.get_temp_storage_locations_array();
let selector = let selector =
super::util::add_to_entity_selector(entity, &format!("tag={tag_name}")); super::util::add_to_entity_selector(entity, &format!("tag={tag_name}"));
let cmd = Command::Execute(Execute::Store( let cmd = Command::Execute(Execute::Store(
format!( format!("success storage {temp_storage_name} {temp_storage_path} byte 1.0")
"success storage {temp_storage_name} {path} byte 1.0",
path = temp_storage_paths[0]
)
.into(), .into(),
Box::new(Execute::Run(Box::new(Command::Raw(format!( Box::new(Execute::Run(Box::new(Command::Raw(format!(
"execute if entity {selector}" "execute if entity {selector}"
@ -175,7 +172,7 @@ fn print_function(
( (
Some(cmd), 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 => { 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 { let location = DataLocation::Storage {
storage_name, storage_name,
path: std::mem::take(&mut storage_paths[0]), path: storage_path,
r#type: StorageType::Int, r#type: StorageType::Int,
}; };
let cmds = transpiler.transpile_primary_expression( let cmds = transpiler.transpile_primary_expression(
@ -388,10 +385,10 @@ fn print_function(
} }
}, },
Expression::Binary(binary) => { 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 { let location = DataLocation::Storage {
storage_name, storage_name,
path: std::mem::take(&mut storage_paths[0]), path: storage_path,
r#type: StorageType::Int, r#type: StorageType::Int,
}; };
let cmds = let cmds =

View File

@ -14,11 +14,11 @@ use crate::{
semantic::error::UnexpectedExpression, semantic::error::UnexpectedExpression,
syntax::syntax_tree::{ syntax::syntax_tree::{
declaration::{Declaration, ImportItems}, declaration::{Declaration, ImportItems},
expression::{Expression, FunctionCall, Primary}, expression::{Expression, FunctionCall, PrefixOperator, Primary},
program::{Namespace, ProgramFile}, program::{Namespace, ProgramFile},
statement::{ statement::{
execute_block::{Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockTail}, execute_block::{Conditional, Else, ExecuteBlock, ExecuteBlockHead, ExecuteBlockTail},
SemicolonStatement, Statement, ReturnStatement, SemicolonStatement, Statement,
}, },
AnnotationAssignment, AnnotationAssignment,
}, },
@ -359,9 +359,6 @@ impl Transpiler {
Statement::LiteralCommand(literal_command) => { Statement::LiteralCommand(literal_command) => {
Ok(vec![literal_command.clean_command().into()]) Ok(vec![literal_command.clean_command().into()])
} }
Statement::Run(run) => {
self.transpile_run_expression(run.expression(), program_identifier, scope, handler)
}
Statement::Block(_) => { Statement::Block(_) => {
unreachable!("Only literal commands are allowed in functions at this time.") unreachable!("Only literal commands are allowed in functions at this time.")
} }
@ -411,6 +408,11 @@ impl Transpiler {
Expression::Primary(Primary::FunctionCall(func)) => { Expression::Primary(Primary::FunctionCall(func)) => {
self.transpile_function_call(func, scope, handler) 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 => { unexpected => {
let error = TranspileError::UnexpectedExpression(UnexpectedExpression( let error = TranspileError::UnexpectedExpression(UnexpectedExpression(
unexpected.clone(), unexpected.clone(),
@ -433,24 +435,155 @@ impl Transpiler {
scope, scope,
handler, handler,
), ),
SemicolonStatement::Return(ret) => {
self.transpile_return_statement(ret, program_identifier, scope, handler)
}
}, },
} }
} }
#[expect(clippy::only_used_in_recursion)] #[expect(clippy::too_many_lines)]
fn transpile_run_expression( fn transpile_return_statement(
&mut self, &mut self,
expression: &Expression, ret: &ReturnStatement,
program_identifier: &str, _program_identifier: &str,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> {
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());
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)
}
}
};
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>, scope: &Arc<Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> { ) -> TranspileResult<Vec<Command>> {
match expression { match expression {
Expression::Primary(Primary::FunctionCall(func)) => { Primary::FunctionCall(func) => self.transpile_function_call(func, scope, handler),
self.transpile_function_call(func, scope, handler) Primary::Identifier(ident) => match scope.get_variable(ident.span.str()).as_deref() {
}
Expression::Primary(Primary::Identifier(ident)) => {
match scope.get_variable(ident.span.str()).as_deref() {
Some(VariableData::ComptimeValue { Some(VariableData::ComptimeValue {
value, value,
read_only: _, read_only: _,
@ -472,7 +605,7 @@ impl Transpiler {
), ),
Some(_) => { Some(_) => {
let error = TranspileError::UnexpectedExpression(UnexpectedExpression( let error = TranspileError::UnexpectedExpression(UnexpectedExpression(
expression.clone(), Expression::Primary(expression.clone()),
)); ));
handler.receive(error.clone()); handler.receive(error.clone());
Err(error) Err(error)
@ -484,26 +617,23 @@ impl Transpiler {
handler.receive(error.clone()); handler.receive(error.clone());
Err(error) Err(error)
} }
} },
}
expression @ Expression::Primary(
Primary::Integer(_) Primary::Integer(_)
| Primary::Boolean(_) | Primary::Boolean(_)
| Primary::Prefix(_) | Primary::Prefix(_)
| Primary::Indexed(_), | Primary::Indexed(_) => {
) => { let error = TranspileError::UnexpectedExpression(UnexpectedExpression(
let error = Expression::Primary(expression.clone()),
TranspileError::UnexpectedExpression(UnexpectedExpression(expression.clone())); ));
handler.receive(error.clone()); handler.receive(error.clone());
Err(error) Err(error)
} }
Expression::Primary(Primary::StringLiteral(string)) => { Primary::StringLiteral(string) => {
Ok(vec![Command::Raw(string.str_content().to_string())]) Ok(vec![Command::Raw(string.str_content().to_string())])
} }
Expression::Primary(Primary::MacroStringLiteral(string)) => { Primary::MacroStringLiteral(string) => Ok(vec![Command::UsesMacro(string.into())]),
Ok(vec![Command::UsesMacro(string.into())]) Primary::Lua(code) => match code.eval_comptime(scope, handler)? {
}
Expression::Primary(Primary::Lua(code)) => match code.eval_comptime(scope, handler)? {
Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]), Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]), Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]),
Ok(ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)) => { Ok(ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)) => {
@ -523,13 +653,10 @@ impl Transpiler {
} }
}, },
Expression::Primary(Primary::Parenthesized(parenthesized)) => self Primary::Parenthesized(parenthesized) => match parenthesized.expression().as_ref() {
.transpile_run_expression( Expression::Primary(expression) => {
parenthesized.expression(), self.transpile_run_expression(expression, scope, handler)
program_identifier, }
scope,
handler,
),
Expression::Binary(bin) => match bin.comptime_eval(scope, handler) { Expression::Binary(bin) => match bin.comptime_eval(scope, handler) {
Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]), Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]), Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]),
@ -542,6 +669,7 @@ impl Transpiler {
Err(err) Err(err)
} }
}, },
},
} }
} }

View File

@ -381,8 +381,7 @@ impl Transpiler {
handler, handler,
)?; )?;
if is_global { if is_global {
let (temp_objective, temp_targets) = self.get_temp_scoreboard_locations(1); let (temp_objective, [temp_target]) = self.get_temp_scoreboard_locations_array();
let temp_target = &temp_targets[0];
let test_cmd = match declaration.variable_type().keyword { let test_cmd = match declaration.variable_type().keyword {
KeywordKind::Int => { KeywordKind::Int => {
Command::Raw(format!("scoreboard players get {name} {target}")) Command::Raw(format!("scoreboard players get {name} {target}"))