Compare commits

..

No commits in common. "0885665bafc42ca717a32b872f3936ae19de7289" and "713977390d8211a31e0c0abb54d9265bcccf3b53" have entirely different histories.

7 changed files with 179 additions and 704 deletions

View File

@ -444,7 +444,7 @@ impl Primary {
} }
Self::Lua(_) | Self::StringLiteral(_) | Self::Integer(_) | Self::Boolean(_) => Ok(()), Self::Lua(_) | Self::StringLiteral(_) | Self::Integer(_) | Self::Boolean(_) => Ok(()),
Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler), Self::MacroStringLiteral(literal) => literal.analyze_semantics(macro_names, handler),
Self::Identifier(_) | Self::Parenthesized(_) | Self::Prefix(_) | Self::Indexed(_) => { Self::Identifier(_) | Self::Parenthesized(_) | Self::Prefix(_) => {
// TODO: correctly analyze the semantics of the primary expression // TODO: correctly analyze the semantics of the primary expression
Ok(()) Ok(())
} }

View File

@ -185,7 +185,6 @@ pub enum Primary {
Identifier(Identifier), Identifier(Identifier),
Prefix(Prefix), Prefix(Prefix),
Parenthesized(Parenthesized), Parenthesized(Parenthesized),
Indexed(Indexed),
Integer(Integer), Integer(Integer),
Boolean(Boolean), Boolean(Boolean),
StringLiteral(StringLiteral), StringLiteral(StringLiteral),
@ -200,7 +199,6 @@ impl SourceElement for Primary {
Self::Identifier(identifier) => identifier.span(), Self::Identifier(identifier) => identifier.span(),
Self::Prefix(prefix) => prefix.span(), Self::Prefix(prefix) => prefix.span(),
Self::Parenthesized(parenthesized) => parenthesized.span(), Self::Parenthesized(parenthesized) => parenthesized.span(),
Self::Indexed(indexed) => indexed.span(),
Self::Integer(int) => int.span(), Self::Integer(int) => int.span(),
Self::Boolean(bool) => bool.span(), Self::Boolean(bool) => bool.span(),
Self::StringLiteral(string_literal) => string_literal.span(), Self::StringLiteral(string_literal) => string_literal.span(),
@ -247,50 +245,6 @@ impl SourceElement for Parenthesized {
} }
} }
/// Represents a indexed expression in the syntax tree.
///
/// Syntax Synopsis:
/// ```ebnf
/// Indexed:
/// PrimaryExpression '[' Expression ']'
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct Indexed {
/// The object that is indexed.
#[get = "pub"]
object: Box<Primary>,
/// The left bracket.
#[get = "pub"]
left_bracket: Punctuation,
/// The index expression.
#[get = "pub"]
index: Box<Expression>,
/// The right bracket.
#[get = "pub"]
right_bracket: Punctuation,
}
impl Indexed {
/// Dissolves the indexed expression into its components
#[must_use]
pub fn dissolve(self) -> (Primary, Punctuation, Expression, Punctuation) {
(
*self.object,
self.left_bracket,
*self.index,
self.right_bracket,
)
}
}
impl SourceElement for Indexed {
fn span(&self) -> Span {
self.object.span().join(&self.right_bracket.span).unwrap()
}
}
/// Represents a prefix operator in the syntax tree. /// Represents a prefix operator in the syntax tree.
/// ///
/// Syntax Synopsis: /// Syntax Synopsis:

View File

@ -17,7 +17,7 @@ use crate::{
lexical::{ lexical::{
token::{ token::{
CommandLiteral, DocComment, Identifier, Integer, Keyword, KeywordKind, Punctuation, CommandLiteral, DocComment, Identifier, Integer, Keyword, KeywordKind, Punctuation,
StringLiteral, Token, Token,
}, },
token_stream::Delimiter, token_stream::Delimiter,
}, },
@ -542,7 +542,7 @@ impl ArrayVariableDeclaration {
} }
} }
type CriteriaSelection = (Punctuation, StringLiteral, Punctuation); type CriteriaSelection = (Punctuation, AnyStringLiteral, Punctuation);
/// Represents a scoreboard variable declaration in the syntax tree. /// Represents a scoreboard variable declaration in the syntax tree.
/// ///
@ -550,7 +550,7 @@ type CriteriaSelection = (Punctuation, StringLiteral, Punctuation);
/// ///
/// ```ebnf /// ```ebnf
/// ScoreVariableDeclaration: /// ScoreVariableDeclaration:
/// 'int' ('<' StringLiteral '>')? identifier '[' AnyStringLiteral? ']' VariableDeclarationAssignment? /// 'int' ('<' AnyStringLiteral '>')? identifier '[' AnyStringLiteral? ']' VariableDeclarationAssignment?
/// ``` /// ```
#[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, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
@ -685,14 +685,14 @@ impl TagVariableDeclaration {
/// Syntax Synopsis: /// Syntax Synopsis:
/// ```ebnf /// ```ebnf
/// Assignment: /// Assignment:
/// AssignmentDestination '=' Expression /// Identifier '=' 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, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct Assignment { pub struct Assignment {
/// The identifier of the assignment. /// The identifier of the assignment.
#[get = "pub"] #[get = "pub"]
destination: AssignmentDestination, identifier: Identifier,
/// The equals sign of the assignment. /// The equals sign of the assignment.
#[get = "pub"] #[get = "pub"]
equals: Punctuation, equals: Punctuation,
@ -703,7 +703,7 @@ pub struct Assignment {
impl SourceElement for Assignment { impl SourceElement for Assignment {
fn span(&self) -> Span { fn span(&self) -> Span {
self.destination self.identifier
.span() .span()
.join(&self.expression.span()) .join(&self.expression.span())
.expect("The span of the assignment is invalid.") .expect("The span of the assignment is invalid.")
@ -713,38 +713,8 @@ impl SourceElement for Assignment {
impl Assignment { impl Assignment {
/// Dissolves the [`Assignment`] into its components. /// Dissolves the [`Assignment`] into its components.
#[must_use] #[must_use]
pub fn dissolve(self) -> (AssignmentDestination, Punctuation, Expression) { pub fn dissolve(self) -> (Identifier, Punctuation, Expression) {
(self.destination, self.equals, self.expression) (self.identifier, self.equals, self.expression)
}
}
/// Represents an assignment destination in the syntax tree.
///
/// Syntax Synopsis:
/// ```ebnf
/// AssignmentDestination:
/// Identifier
/// | Identifier '[' Expression ']'
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AssignmentDestination {
/// Assignment to an identifier.
Identifier(Identifier),
/// Assignment to an indexed identifier.
Indexed(Identifier, Punctuation, Expression, Punctuation),
}
impl SourceElement for AssignmentDestination {
fn span(&self) -> Span {
match self {
Self::Identifier(identifier) => identifier.span(),
Self::Indexed(identifier, _, _, close) => identifier
.span()
.join(&close.span())
.expect("The span of the indexed assignment destination is invalid."),
}
} }
} }
@ -889,30 +859,15 @@ impl<'a> Parser<'a> {
} }
_ => { _ => {
// try to parse assignment // try to parse assignment
// TODO: improve
#[expect(clippy::option_if_let_else)] #[expect(clippy::option_if_let_else)]
if let Ok(assignment) = self.try_parse(|p| { if let Ok(assignment) = self.try_parse(|p| {
let destination = { let identifier = p.parse_identifier(&VoidHandler)?;
let identifier = p.parse_identifier(&VoidHandler)?;
if let Ok(tree) = p.step_into(
Delimiter::Bracket,
|pp| pp.parse_expression(&VoidHandler),
&VoidHandler,
) {
let open = tree.open;
let close = tree.close;
let expression = tree.tree?;
AssignmentDestination::Indexed(identifier, open, expression, close)
} else {
AssignmentDestination::Identifier(identifier)
}
};
let equals = p.parse_punctuation('=', true, &VoidHandler)?; let equals = p.parse_punctuation('=', true, &VoidHandler)?;
let expression = p.parse_expression(&VoidHandler)?; let expression = p.parse_expression(&VoidHandler)?;
Ok(SemicolonStatement::Assignment(Assignment { Ok(SemicolonStatement::Assignment(Assignment {
destination, identifier,
equals, equals,
expression, expression,
})) }))
@ -968,7 +923,7 @@ impl<'a> Parser<'a> {
let criteria_selection = if variable_type.keyword == KeywordKind::Int { let criteria_selection = if variable_type.keyword == KeywordKind::Int {
self.try_parse(|p| { self.try_parse(|p| {
let open = p.parse_punctuation('<', true, &VoidHandler)?; let open = p.parse_punctuation('<', true, &VoidHandler)?;
let criteria = p.parse_string_literal(&VoidHandler)?; let criteria = p.parse_any_string_literal(&VoidHandler)?;
let close = p.parse_punctuation('>', true, &VoidHandler)?; let close = p.parse_punctuation('>', true, &VoidHandler)?;
Ok((open, criteria, close)) Ok((open, criteria, close))
}) })

View File

@ -1,6 +1,6 @@
//! The expression transpiler. //! The expression transpiler.
use std::{fmt::Display, string::ToString, sync::Arc}; use std::{fmt::Display, sync::Arc};
use super::{util::MacroString, Scope, VariableData}; use super::{util::MacroString, Scope, VariableData};
use crate::{ use crate::{
@ -265,7 +265,10 @@ impl Primary {
.get_variable(ident.span.str()) .get_variable(ident.span.str())
.map_or(false, |variable| match r#type { .map_or(false, |variable| match r#type {
ValueType::Boolean => { ValueType::Boolean => {
matches!(variable.as_ref(), VariableData::BooleanStorage { .. }) matches!(
variable.as_ref(),
VariableData::Tag { .. } | VariableData::BooleanStorage { .. }
)
} }
ValueType::Integer => { ValueType::Integer => {
matches!(variable.as_ref(), VariableData::ScoreboardValue { .. }) matches!(variable.as_ref(), VariableData::ScoreboardValue { .. })
@ -286,29 +289,6 @@ impl Primary {
&& prefix.operand().can_yield_type(r#type, scope) && prefix.operand().can_yield_type(r#type, scope)
} }
}, },
Self::Indexed(indexed) => {
let Self::Identifier(ident) = indexed.object().as_ref() else {
todo!("throw error: cannot index anything except identifiers")
};
scope
.get_variable(ident.span.str())
.map_or(false, |variable| match r#type {
ValueType::Boolean => {
matches!(
variable.as_ref(),
VariableData::Tag { .. } | VariableData::BooleanStorageArray { .. }
)
}
ValueType::Integer => {
matches!(
variable.as_ref(),
VariableData::Scoreboard { .. }
| VariableData::ScoreboardArray { .. }
)
}
ValueType::String => false,
})
}
#[cfg_attr(not(feature = "lua"), expect(unused_variables))] #[cfg_attr(not(feature = "lua"), expect(unused_variables))]
Self::Lua(lua) => { Self::Lua(lua) => {
cfg_if::cfg_if! { cfg_if::cfg_if! {
@ -343,7 +323,7 @@ impl Primary {
Self::StringLiteral(string_literal) => Some(ComptimeValue::String( Self::StringLiteral(string_literal) => Some(ComptimeValue::String(
string_literal.str_content().to_string(), string_literal.str_content().to_string(),
)), )),
Self::Identifier(_) | Self::FunctionCall(_) | Self::Indexed(_) => None, Self::Identifier(_) | Self::FunctionCall(_) => None,
Self::Parenthesized(parenthesized) => { Self::Parenthesized(parenthesized) => {
parenthesized.expression().comptime_eval(scope, handler) parenthesized.expression().comptime_eval(scope, handler)
} }
@ -749,105 +729,110 @@ impl Transpiler {
} }
}, },
Primary::Identifier(ident) => { Primary::Identifier(ident) => {
let variable = scope.get_variable(ident.span.str());
if let Some(variable) = variable.as_deref() {
let from = match variable {
VariableData::BooleanStorage { storage_name, path } => {
Ok(DataLocation::Storage {
storage_name: storage_name.to_string(),
path: path.to_string(),
r#type: StorageType::Boolean,
})
}
VariableData::ScoreboardValue { objective, target } => {
Ok(DataLocation::ScoreboardValue {
objective: objective.to_string(),
target: target.to_string(),
})
}
_ => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: target.value_type().into(),
expression: primary.span(),
});
handler.receive(err.clone());
Err(err)
}
}?;
self.move_data(&from, target, primary, handler)
} else {
let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span.clone(),
});
handler.receive(err.clone());
Err(err)
}
}
Primary::Indexed(indexed) => {
let Primary::Identifier(ident) = indexed.object().as_ref() else {
todo!("can only index identifier")
};
let variable = scope.get_variable(ident.span.str()); let variable = scope.get_variable(ident.span.str());
#[expect(clippy::option_if_let_else)] #[expect(clippy::option_if_let_else)]
if let Some(variable) = variable.as_deref() { if let Some(variable) = variable.as_deref() {
let from = match variable { match variable {
VariableData::Scoreboard { objective } => { VariableData::BooleanStorage { storage_name, path } => match target {
if let Some(ComptimeValue::String(target)) = DataLocation::ScoreboardValue { objective, target } => {
indexed.index().comptime_eval(scope, handler) let cmd = Command::Execute(Execute::Store(
{ format!("store result score {target} {objective}").into(),
Ok(DataLocation::ScoreboardValue { Box::new(Execute::Run(Box::new(Command::Raw(format!(
objective: objective.to_string(), "data get storage {storage_name} {path}"
target, ))))),
}) ));
} else { Ok(vec![cmd])
todo!("can only index scoreboard with comptime string")
} }
} DataLocation::Tag { tag_name, entity } => {
VariableData::ScoreboardArray { objective, targets } => { let cmd = Command::Execute(Execute::If(
if let Some(ComptimeValue::Integer(index)) = Condition::Atom(
indexed.index().comptime_eval(scope, handler) format!("data storage {storage_name} {{{path}: 1b}}")
{ .into(),
if let Some(target) = usize::try_from(index) ),
.ok() Box::new(Execute::Run(Box::new(Command::Raw(format!(
.and_then(|index| targets.get(index)) "tag {entity} add {tag_name}"
.map(ToString::to_string) ))))),
{ Some(Box::new(Execute::Run(Box::new(Command::Raw(format!(
Ok(DataLocation::ScoreboardValue { "tag {entity} remove {tag_name}"
objective: objective.to_string(), )))))),
target, ));
})
Ok(vec![cmd])
}
DataLocation::Storage {
storage_name: target_storage_name,
path: target_path,
r#type,
} => {
if matches!(r#type, StorageType::Boolean) {
let cmd = Command::Raw(format!(
"data modify storage {target_storage_name} {target_path} set from storage {storage_name} {path}"
));
Ok(vec![cmd])
} else { } else {
todo!("index out of bounds") let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: primary.span(),
expected_type: target.value_type().into(),
});
handler.receive(err.clone());
Err(err)
} }
} else {
todo!("can only index array with comptime integer")
} }
} },
VariableData::BooleanStorageArray { VariableData::ScoreboardValue {
storage_name, objective,
paths, target: score_target,
} => { } => match target {
if let Some(ComptimeValue::Integer(index)) = DataLocation::ScoreboardValue {
indexed.index().comptime_eval(scope, handler) objective: target_objective,
{ target: target_target,
if let Some(path) = usize::try_from(index) } => {
.ok() let cmd = Command::Raw(format!(
.and_then(|index| paths.get(index)) "scoreboard players operation {target_target} {target_objective} = {score_target} {objective}"
.map(ToString::to_string) ));
{ Ok(vec![cmd])
Ok(DataLocation::Storage { }
storage_name: storage_name.to_string(), DataLocation::Storage {
path, storage_name,
r#type: StorageType::Boolean, path,
}) r#type,
} => {
if matches!(
r#type,
StorageType::Byte
| StorageType::Double
| StorageType::Int
| StorageType::Long
) {
let cmd = Command::Execute(Execute::Store(
format!(
"result storage {storage_name} {path} {t} 1.0",
t = r#type.as_str()
)
.into(),
Box::new(Execute::Run(Box::new(Command::Raw(format!(
"scoreboard players get {score_target} {objective}"
))))),
));
Ok(vec![cmd])
} else { } else {
todo!("index out of bounds") let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: primary.span(),
expected_type: target.value_type().into(),
});
handler.receive(err.clone());
Err(err)
} }
} else {
todo!("can only index array with comptime integer")
} }
} DataLocation::Tag { .. } => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ExpectedType::Boolean,
expression: primary.span(),
});
handler.receive(err.clone());
Err(err)
}
},
_ => { _ => {
let err = TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: target.value_type().into(), expected_type: target.value_type().into(),
@ -856,9 +841,7 @@ impl Transpiler {
handler.receive(err.clone()); handler.receive(err.clone());
Err(err) Err(err)
} }
}?; }
self.move_data(&from, target, primary, handler)
} else { } else {
let err = TranspileError::UnknownIdentifier(UnknownIdentifier { let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span.clone(), identifier: ident.span.clone(),
@ -1021,57 +1004,6 @@ impl Transpiler {
Primary::Parenthesized(parenthesized) => { Primary::Parenthesized(parenthesized) => {
self.transpile_expression_as_condition(parenthesized.expression(), scope, handler) self.transpile_expression_as_condition(parenthesized.expression(), scope, handler)
} }
Primary::Indexed(indexed) => {
let Primary::Identifier(ident) = indexed.object().as_ref() else {
todo!("can only index identifier")
};
#[expect(clippy::option_if_let_else)]
if let Some(variable) = scope.get_variable(ident.span.str()).as_deref() {
#[expect(clippy::single_match_else)]
match variable {
VariableData::BooleanStorageArray {
storage_name,
paths,
} => {
if let Some(ComptimeValue::Integer(index)) =
indexed.index().comptime_eval(scope, handler)
{
if let Some(path) = usize::try_from(index)
.ok()
.and_then(|index| paths.get(index))
.map(ToString::to_string)
{
Ok((
Vec::new(),
ExtendedCondition::Runtime(Condition::Atom(
format!("data storage {storage_name} {{{path}: 1b}}")
.into(),
)),
))
} else {
todo!("index out of bounds")
}
} else {
todo!("can only index array with comptime integer")
}
}
_ => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ExpectedType::Boolean,
expression: primary.span(),
});
handler.receive(err.clone());
Err(err)
}
}
} else {
let err = TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span.clone(),
});
handler.receive(err.clone());
Err(err)
}
}
Primary::Prefix(prefix) => match prefix.operator() { Primary::Prefix(prefix) => match prefix.operator() {
PrefixOperator::LogicalNot(_) => { PrefixOperator::LogicalNot(_) => {
let (cmds, cond) = self.transpile_primary_expression_as_condition( let (cmds, cond) = self.transpile_primary_expression_as_condition(

View File

@ -98,37 +98,12 @@ mod enabled {
fn add_globals(&self, lua: &Lua, scope: &Arc<Scope>) -> mlua::Result<()> { fn add_globals(&self, lua: &Lua, scope: &Arc<Scope>) -> mlua::Result<()> {
let globals = lua.globals(); let globals = lua.globals();
let shulkerscript_globals = { let location = {
let table = lua.create_table()?; let span = self.span();
let file = span.source_file();
let (location_path, location_start, location_end) = { file.path_relative().unwrap_or_else(|| file.path().clone())
let span = self.span();
let file = span.source_file();
let path = file.path().to_owned();
let start_location = span.start_location();
let end_location = span.end_location().unwrap_or_else(|| {
let line_amount = file.line_amount();
let column = file.get_line(line_amount).expect("line amount used").len();
crate::base::source_file::Location {
line: line_amount,
column,
}
});
(path, start_location, end_location)
};
table.set("file_path", location_path.to_string_lossy())?;
table.set("start_line", location_start.line)?;
table.set("start_column", location_start.column)?;
table.set("end_line", location_end.line)?;
table.set("end_column", location_end.column)?;
table.set("version", crate::VERSION)?;
table
}; };
globals.set("shu_location", location.to_string_lossy())?;
if let Some(inputs) = self.inputs() { if let Some(inputs) = self.inputs() {
for x in inputs.elements() { for x in inputs.elements() {
@ -137,66 +112,25 @@ mod enabled {
Some(VariableData::MacroParameter { macro_name, .. }) => { Some(VariableData::MacroParameter { macro_name, .. }) => {
Value::String(lua.create_string(format!("$({macro_name})"))?) Value::String(lua.create_string(format!("$({macro_name})"))?)
} }
Some(VariableData::Scoreboard { objective }) => {
let table = lua.create_table()?;
table.set("objective", objective.as_str())?;
Value::Table(table)
}
Some(VariableData::ScoreboardValue { objective, target }) => { Some(VariableData::ScoreboardValue { objective, target }) => {
let table = lua.create_table()?; let table = lua.create_table()?;
table.set("objective", lua.create_string(objective)?)?; table.set("objective", lua.create_string(objective)?)?;
table.set("target", lua.create_string(target)?)?; table.set("target", lua.create_string(target)?)?;
Value::Table(table) Value::Table(table)
} }
Some(VariableData::ScoreboardArray { objective, targets }) => {
let table = lua.create_table()?;
table.set("objective", objective.as_str())?;
let values = lua.create_table_from(
targets
.iter()
.enumerate()
.map(|(i, target)| (i + 1, target.as_str())),
)?;
table.set("targets", values)?;
Value::Table(table)
}
Some(VariableData::BooleanStorage { storage_name, path }) => { Some(VariableData::BooleanStorage { storage_name, path }) => {
let table = lua.create_table()?; let table = lua.create_table()?;
table.set("storage", lua.create_string(storage_name)?)?; table.set("storage", lua.create_string(storage_name)?)?;
table.set("path", lua.create_string(path)?)?; table.set("path", lua.create_string(path)?)?;
Value::Table(table) Value::Table(table)
} }
Some(VariableData::BooleanStorageArray { Some(_) => todo!("allow other variable types"),
storage_name,
paths,
}) => {
let table = lua.create_table()?;
table.set("storage", storage_name.as_str())?;
let values = lua.create_table_from(
paths
.iter()
.enumerate()
.map(|(i, path)| (i + 1, path.as_str())),
)?;
table.set("paths", values)?;
Value::Table(table)
}
Some(VariableData::Tag { tag_name }) => {
let table = lua.create_table()?;
table.set("name", tag_name.as_str())?;
Value::Table(table)
}
Some(VariableData::Function { .. }) => {
todo!("allow function variable type")
}
None => todo!("throw correct error"), None => todo!("throw correct error"),
}; };
globals.set(name, value)?; globals.set(name, value)?;
} }
} }
globals.set("shulkerscript", shulkerscript_globals)?;
Ok(()) Ok(())
} }

View File

@ -36,7 +36,7 @@ use crate::{
use super::{ use super::{
error::{MismatchedTypes, TranspileError, TranspileResult, UnknownIdentifier}, error::{MismatchedTypes, TranspileError, TranspileResult, UnknownIdentifier},
expression::{ComptimeValue, ExpectedType, ExtendedCondition, StorageType}, expression::{ComptimeValue, ExpectedType, ExtendedCondition, StorageType},
variables::{Scope, TranspileAssignmentTarget, VariableData}, variables::{Scope, VariableData},
FunctionData, TranspileAnnotationValue, FunctionData, TranspileAnnotationValue,
}; };
@ -514,7 +514,6 @@ impl Transpiler {
Expression::Primary( Expression::Primary(
Primary::Parenthesized(_) Primary::Parenthesized(_)
| Primary::Prefix(_) | Primary::Prefix(_)
| Primary::Indexed(_)
| Primary::FunctionCall(_), | Primary::FunctionCall(_),
) )
| Expression::Binary(_) => { | Expression::Binary(_) => {
@ -746,7 +745,7 @@ impl Transpiler {
self.transpile_variable_declaration(decl, program_identifier, scope, handler) self.transpile_variable_declaration(decl, program_identifier, scope, handler)
} }
SemicolonStatement::Assignment(assignment) => self.transpile_assignment( SemicolonStatement::Assignment(assignment) => self.transpile_assignment(
TranspileAssignmentTarget::from(assignment.destination()), assignment.identifier(),
assignment.expression(), assignment.expression(),
scope, scope,
handler, handler,
@ -771,8 +770,7 @@ impl Transpiler {
Primary::Integer(_) Primary::Integer(_)
| Primary::Boolean(_) | Primary::Boolean(_)
| Primary::Prefix(_) | Primary::Prefix(_)
| Primary::Identifier(_) | Primary::Identifier(_),
| Primary::Indexed(_),
) => { ) => {
let error = let error =
TranspileError::UnexpectedExpression(UnexpectedExpression(expression.clone())); TranspileError::UnexpectedExpression(UnexpectedExpression(expression.clone()));

View File

@ -15,20 +15,17 @@ use shulkerbox::prelude::{Command, Condition, Execute};
use enum_as_inner::EnumAsInner; use enum_as_inner::EnumAsInner;
use crate::{ use crate::{
base::{self, source_file::SourceElement, Handler}, base::{self, source_file::SourceElement as _, Handler},
lexical::token::{Identifier, KeywordKind}, lexical::token::{Identifier, KeywordKind},
syntax::syntax_tree::{ syntax::syntax_tree::{
expression::{Expression, Primary}, expression::{Expression, Primary},
statement::{ statement::{SingleVariableDeclaration, VariableDeclaration},
AssignmentDestination, ScoreVariableDeclaration, SingleVariableDeclaration,
VariableDeclaration,
},
}, },
}; };
use super::{ use super::{
error::{AssignmentError, IllegalAnnotationContent, MismatchedTypes}, error::{AssignmentError, IllegalAnnotationContent},
expression::{ComptimeValue, DataLocation, ExpectedType, StorageType}, expression::{ComptimeValue, DataLocation},
FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult, FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult,
}; };
@ -92,25 +89,6 @@ pub enum VariableData {
}, },
} }
#[derive(Debug, Clone, Copy, EnumAsInner)]
pub enum TranspileAssignmentTarget<'a> {
Identifier(&'a Identifier),
Indexed(&'a Identifier, &'a Expression),
}
impl<'a> From<&'a AssignmentDestination> for TranspileAssignmentTarget<'a> {
fn from(destination: &'a AssignmentDestination) -> Self {
match destination {
AssignmentDestination::Identifier(ident) => {
TranspileAssignmentTarget::Identifier(ident)
}
AssignmentDestination::Indexed(ident, _, expr, _) => {
TranspileAssignmentTarget::Indexed(ident, expr)
}
}
}
}
/// A scope that stores variables. /// A scope that stores variables.
#[derive(Default)] #[derive(Default)]
pub struct Scope<'a> { pub struct Scope<'a> {
@ -232,14 +210,8 @@ impl Transpiler {
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> { ) -> TranspileResult<Vec<Command>> {
match declaration { match declaration {
VariableDeclaration::Single(declaration) => self.transpile_single_variable_declaration( VariableDeclaration::Single(single) => self.transpile_single_variable_declaration(
declaration, single,
program_identifier,
scope,
handler,
),
VariableDeclaration::Score(declaration) => self.transpile_score_variable_declaration(
declaration,
program_identifier, program_identifier,
scope, scope,
handler, handler,
@ -250,72 +222,18 @@ impl Transpiler {
fn transpile_single_variable_declaration( fn transpile_single_variable_declaration(
&mut self, &mut self,
declaration: &SingleVariableDeclaration, single: &SingleVariableDeclaration,
program_identifier: &str, program_identifier: &str,
scope: &Arc<Scope>, scope: &Arc<Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> { ) -> TranspileResult<Vec<Command>> {
let variable_type = declaration.variable_type().keyword; let mut deobfuscate_annotations = single
let (name, target) = self.get_data_location_identifier_pair(
declaration,
program_identifier,
scope,
handler,
)?;
match variable_type {
KeywordKind::Int => {
if !self.datapack.scoreboards().contains_key(&name) {
self.datapack
.register_scoreboard(&name, None::<&str>, None::<&str>);
}
scope.set_variable(
declaration.identifier().span.str(),
VariableData::ScoreboardValue {
objective: name.clone(),
target,
},
);
}
KeywordKind::Bool => {
scope.set_variable(
declaration.identifier().span.str(),
VariableData::BooleanStorage {
storage_name: name,
path: target,
},
);
}
_ => unreachable!("no other variable types"),
}
declaration.assignment().as_ref().map_or_else(
|| Ok(Vec::new()),
|assignment| {
self.transpile_assignment(
TranspileAssignmentTarget::Identifier(declaration.identifier()),
assignment.expression(),
scope,
handler,
)
},
)
}
fn transpile_score_variable_declaration(
&mut self,
declaration: &ScoreVariableDeclaration,
program_identifier: &str,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> {
let mut deobfuscate_annotations = declaration
.annotations() .annotations()
.iter() .iter()
.filter(|a| a.has_identifier("deobfuscate")); .filter(|a| a.has_identifier("deobfuscate"));
let variable_type = single.variable_type().keyword;
let deobfuscate_annotation = deobfuscate_annotations.next(); let deobfuscate_annotation = deobfuscate_annotations.next();
if let Some(duplicate) = deobfuscate_annotations.next() { if let Some(duplicate) = deobfuscate_annotations.next() {
@ -326,73 +244,69 @@ impl Transpiler {
handler.receive(error.clone()); handler.receive(error.clone());
return Err(error); return Err(error);
} }
let name = let (name, target) =
self.get_data_location_identifier(declaration, program_identifier, scope, handler)?; self.get_single_data_location_identifiers(single, program_identifier, scope, handler)?;
let criteria = declaration match variable_type {
.criteria() KeywordKind::Int => {
.as_ref() if !self.datapack.scoreboards().contains_key(&name) {
.map(|(_, c, _)| c.str_content()); self.datapack
.register_scoreboard(&name, None::<&str>, None::<&str>);
}
if !self.datapack.scoreboards().contains_key(&name) { scope.set_variable(
self.datapack single.identifier().span.str(),
.register_scoreboard(&name, criteria, None::<&str>); VariableData::ScoreboardValue {
objective: name.clone(),
target,
},
);
}
KeywordKind::Bool => {
scope.set_variable(
single.identifier().span.str(),
VariableData::BooleanStorage {
storage_name: name,
path: target,
},
);
}
_ => unreachable!("no other variable types"),
} }
scope.set_variable( single.assignment().as_ref().map_or_else(
declaration.identifier().span.str(), || Ok(Vec::new()),
VariableData::Scoreboard { |assignment| {
objective: name.clone(), self.transpile_assignment(
single.identifier(),
assignment.expression(),
scope,
handler,
)
}, },
); )
// TODO: implement assignment when map literal is implemented
Ok(Vec::new())
} }
pub(super) fn transpile_assignment( pub(super) fn transpile_assignment(
&mut self, &mut self,
destination: TranspileAssignmentTarget, identifier: &Identifier,
expression: &crate::syntax::syntax_tree::expression::Expression, expression: &crate::syntax::syntax_tree::expression::Expression,
scope: &Arc<Scope>, scope: &Arc<Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> { ) -> TranspileResult<Vec<Command>> {
let (identifier, indexing_value) = match destination {
TranspileAssignmentTarget::Identifier(ident) => (ident, None),
TranspileAssignmentTarget::Indexed(ident, expression) => {
(ident, expression.comptime_eval(scope, handler))
}
};
if let Some(target) = scope.get_variable(identifier.span.str()) { if let Some(target) = scope.get_variable(identifier.span.str()) {
let data_location = match target.as_ref() { let data_location = match target.as_ref() {
VariableData::BooleanStorage { storage_name, path } => { VariableData::BooleanStorage { storage_name, path } => Ok(DataLocation::Storage {
// TODO: make sure that no indexing is done storage_name: storage_name.to_owned(),
Ok(DataLocation::Storage { path: path.to_owned(),
storage_name: storage_name.to_owned(), r#type: super::expression::StorageType::Boolean,
path: path.to_owned(), }),
r#type: super::expression::StorageType::Boolean,
})
}
VariableData::ScoreboardValue { objective, target } => { VariableData::ScoreboardValue { objective, target } => {
// TODO: make sure that no indexing is done
Ok(DataLocation::ScoreboardValue { Ok(DataLocation::ScoreboardValue {
objective: objective.to_owned(), objective: objective.to_owned(),
target: target.to_owned(), target: target.to_owned(),
}) })
} }
VariableData::Scoreboard { objective } => match indexing_value {
Some(ComptimeValue::String(s)) => Ok(DataLocation::ScoreboardValue {
objective: objective.clone(),
target: s,
}),
Some(ComptimeValue::MacroString(s)) => {
todo!("indexing scoreboard with macro string: {s}")
}
Some(_) => todo!("invalid indexing value"),
None => {
todo!("cannot assign to scoreboard without indexing")
}
},
VariableData::Function { .. } | VariableData::MacroParameter { .. } => { VariableData::Function { .. } | VariableData::MacroParameter { .. } => {
let err = TranspileError::AssignmentError(AssignmentError { let err = TranspileError::AssignmentError(AssignmentError {
identifier: identifier.span(), identifier: identifier.span(),
@ -421,88 +335,9 @@ impl Transpiler {
} }
} }
fn get_data_location_identifier(
&mut self,
declaration: &ScoreVariableDeclaration,
program_identifier: &str,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> TranspileResult<String> {
let mut deobfuscate_annotations = declaration
.annotations()
.iter()
.filter(|a| a.has_identifier("deobfuscate"));
let deobfuscate_annotation = deobfuscate_annotations.next();
if let Some(duplicate) = deobfuscate_annotations.next() {
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: duplicate.span(),
message: "Multiple deobfuscate annotations are not allowed.".to_string(),
});
handler.receive(error.clone());
return Err(error);
}
if let Some(deobfuscate_annotation) = deobfuscate_annotation {
let deobfuscate_annotation_value =
TranspileAnnotationValue::from(deobfuscate_annotation.assignment().value.clone());
match deobfuscate_annotation_value {
TranspileAnnotationValue::Expression(expr) => {
if let Some(name_eval) = expr
.comptime_eval(scope, handler)
.and_then(|val| val.to_string_no_macro())
{
if !crate::util::is_valid_scoreboard_objective_name(&name_eval) {
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation must be a valid scoreboard objective name.".to_string()
});
handler.receive(error.clone());
return Err(error);
}
Ok(name_eval)
} else {
let error = TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation could not have been evaluated at compile time.".to_string()
});
handler.receive(error.clone());
Err(error)
}
}
TranspileAnnotationValue::None => {
Ok(declaration.identifier().span.str().to_string())
}
TranspileAnnotationValue::Map(_) => {
let error =
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation must have no value or must be string."
.to_string(),
});
handler.receive(error.clone());
Err(error)
}
}
} else {
let hashed = md5::hash(program_identifier).to_hex_lowercase();
let name = "shu_values_".to_string() + &hashed;
let identifier_name = declaration.identifier().span.str();
let scope_ident = self.temp_counter;
self.temp_counter = self.temp_counter.wrapping_add(1);
let mut target = md5::hash(format!(
"{scope_ident}\0{identifier_name}\0{shadowed}",
shadowed = scope.get_variable_shadow_count(identifier_name)
))
.to_hex_lowercase();
Ok(name)
}
}
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
fn get_data_location_identifier_pair( #[cfg(feature = "shulkerbox")]
fn get_single_data_location_identifiers(
&mut self, &mut self,
single: &SingleVariableDeclaration, single: &SingleVariableDeclaration,
program_identifier: &str, program_identifier: &str,
@ -550,10 +385,10 @@ impl Transpiler {
if let (Some(name_eval), Some(target_eval)) = ( if let (Some(name_eval), Some(target_eval)) = (
objective objective
.comptime_eval(scope, handler) .comptime_eval(scope, handler)
.and_then(|val| val.to_string_no_macro()), .map(|val| val.to_string()),
target target
.comptime_eval(scope, handler) .comptime_eval(scope, handler)
.and_then(|val| val.to_string_no_macro()), .map(|val| val.to_string()),
) { ) {
// TODO: change invalid criteria if boolean // TODO: change invalid criteria if boolean
if !crate::util::is_valid_scoreboard_objective_name(&name_eval) { if !crate::util::is_valid_scoreboard_objective_name(&name_eval) {
@ -633,139 +468,6 @@ impl Transpiler {
Ok((name, target)) Ok((name, target))
} }
} }
/// Move data from location `from` to location `to`.
///
/// # Errors
/// - if the data type does not match
#[expect(clippy::too_many_lines)]
pub fn move_data(
&mut self,
from: &DataLocation,
to: &DataLocation,
expression: &impl SourceElement,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> {
match from {
DataLocation::Storage {
storage_name,
path,
r#type,
} => match r#type {
StorageType::Boolean
| StorageType::Byte
| StorageType::Int
| StorageType::Long
| StorageType::Double => match to {
DataLocation::ScoreboardValue { objective, target } => {
let cmd = Command::Execute(Execute::Store(
format!("store result score {target} {objective}").into(),
Box::new(Execute::Run(Box::new(Command::Raw(format!(
"data get storage {storage_name} {path}"
))))),
));
Ok(vec![cmd])
}
DataLocation::Tag { tag_name, entity } => {
let cmd = Command::Execute(Execute::If(
Condition::Atom(
format!("data storage {storage_name} {{{path}: 1b}}").into(),
),
Box::new(Execute::Run(Box::new(Command::Raw(format!(
"tag {entity} add {tag_name}"
))))),
Some(Box::new(Execute::Run(Box::new(Command::Raw(format!(
"tag {entity} remove {tag_name}"
)))))),
));
Ok(vec![cmd])
}
DataLocation::Storage {
storage_name: target_storage_name,
path: target_path,
r#type,
} => {
if matches!(r#type, StorageType::Boolean) {
let cmd = Command::Raw(format!(
"data modify storage {target_storage_name} {target_path} set from storage {storage_name} {path}"
));
Ok(vec![cmd])
} else {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: expression.span(),
expected_type: to.value_type().into(),
});
handler.receive(err.clone());
Err(err)
}
}
},
},
DataLocation::ScoreboardValue {
objective,
target: score_target,
} => match to {
DataLocation::ScoreboardValue {
objective: target_objective,
target: target_target,
} => {
let cmd = Command::Raw(format!(
"scoreboard players operation {target_target} {target_objective} = {score_target} {objective}"
));
Ok(vec![cmd])
}
DataLocation::Storage {
storage_name,
path,
r#type,
} => {
if matches!(
r#type,
StorageType::Byte
| StorageType::Double
| StorageType::Int
| StorageType::Long
) {
let cmd = Command::Execute(Execute::Store(
format!(
"result storage {storage_name} {path} {t} 1.0",
t = r#type.as_str()
)
.into(),
Box::new(Execute::Run(Box::new(Command::Raw(format!(
"scoreboard players get {score_target} {objective}"
))))),
));
Ok(vec![cmd])
} else {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: expression.span(),
expected_type: to.value_type().into(),
});
handler.receive(err.clone());
Err(err)
}
}
DataLocation::Tag { .. } => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ExpectedType::Boolean,
expression: expression.span(),
});
handler.receive(err.clone());
Err(err)
}
},
DataLocation::Tag { .. } => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: to.value_type().into(),
expression: expression.span(),
});
handler.receive(err.clone());
Err(err)
}
}
}
} }
#[cfg(test)] #[cfg(test)]