Compare commits
2 Commits
469b8d3875
...
1e82d2321f
Author | SHA1 | Date |
---|---|---|
|
1e82d2321f | |
|
0a8bf37e40 |
13
src/lib.rs
13
src/lib.rs
|
@ -115,9 +115,6 @@ pub fn parse(
|
|||
|
||||
/// Transpiles the given source code into a shulkerbox [`Datapack`].
|
||||
///
|
||||
/// # Parameters:
|
||||
/// - `script_paths`: A list of tuples containing the identifier and the path of each script file.
|
||||
///
|
||||
/// # Errors
|
||||
/// - If an error occurs during [`parse()`]
|
||||
/// - If an error occurs while transpiling the source code.
|
||||
|
@ -164,15 +161,7 @@ where
|
|||
|
||||
Ok(program)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if programs.iter().any(Result::is_err) {
|
||||
return Err(programs.into_iter().find_map(Result::err).unwrap());
|
||||
}
|
||||
let programs = programs
|
||||
.into_iter()
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
tracing::info!("Transpiling the source code.");
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ fn get_global_scope(declarations: &[Declaration]) -> SemanticScope<'static> {
|
|||
let scope = SemanticScope::new();
|
||||
|
||||
for declaration in declarations {
|
||||
declaration.add_to_scope(&scope);
|
||||
declaration.add_to_semantic_scope(&scope);
|
||||
}
|
||||
|
||||
scope
|
||||
|
@ -83,23 +83,32 @@ impl Namespace {
|
|||
}
|
||||
|
||||
impl Declaration {
|
||||
fn add_to_scope(&self, scope: &SemanticScope) {
|
||||
fn add_to_semantic_scope(&self, scope: &SemanticScope) {
|
||||
match self {
|
||||
Self::Function(func) => {
|
||||
let name = func.identifier();
|
||||
scope.set_variable(name.span.str(), VariableType::Function);
|
||||
}
|
||||
Self::Import(imp) => match imp.items() {
|
||||
ImportItems::All(_) => {}
|
||||
ImportItems::Named(items) => {
|
||||
for item in items.elements() {
|
||||
if scope.get_variable(item.span.str()) != Some(VariableType::Function) {
|
||||
scope.set_variable(item.span.str(), VariableType::Function);
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::GlobalVariable((_, var, _)) => {
|
||||
let name = var.identifier();
|
||||
let var_type = match var {
|
||||
VariableDeclaration::Array(arr) => match arr.variable_type().keyword {
|
||||
KeywordKind::Bool => VariableType::BooleanStorageArray,
|
||||
KeywordKind::Int => VariableType::ScoreboardArray,
|
||||
_ => unreachable!("variable type is not a valid type"),
|
||||
},
|
||||
Self::Tag(_) => {}
|
||||
VariableDeclaration::Score(_) => VariableType::Scoreboard,
|
||||
VariableDeclaration::Tag(_) => VariableType::Tag,
|
||||
VariableDeclaration::Single(single) => match single.variable_type().keyword {
|
||||
KeywordKind::Bool => VariableType::BooleanStorage,
|
||||
KeywordKind::Int => VariableType::ScoreboardValue,
|
||||
_ => unreachable!("variable type is not a valid type"),
|
||||
},
|
||||
VariableDeclaration::ComptimeValue(_) => VariableType::ComptimeValue,
|
||||
};
|
||||
scope.set_variable(name.span.str(), var_type);
|
||||
}
|
||||
Self::Import(_) | Self::Tag(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,6 +146,8 @@ impl Declaration {
|
|||
}
|
||||
},
|
||||
|
||||
Self::GlobalVariable((_, var, _)) => var.analyze_semantics(scope, handler),
|
||||
|
||||
Self::Tag(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
@ -549,16 +560,26 @@ impl Primary {
|
|||
Self::Boolean(_) | Self::Integer(_) | Self::StringLiteral(_) => Ok(()),
|
||||
Self::MacroStringLiteral(lit) => lit.analyze_semantics(scope, handler),
|
||||
Self::FunctionCall(call) => {
|
||||
if scope.get_variable(call.identifier().span.str()) == Some(VariableType::Function)
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
let var = scope.get_variable(call.identifier().span.str());
|
||||
var.map_or_else(
|
||||
|| {
|
||||
let err = error::Error::UnknownIdentifier(UnknownIdentifier {
|
||||
identifier: call.identifier().span.clone(),
|
||||
});
|
||||
handler.receive(err.clone());
|
||||
Err(err)
|
||||
},
|
||||
|var| match var {
|
||||
VariableType::Function | VariableType::InternalFunction => Ok(()),
|
||||
_ => {
|
||||
let err = error::Error::UnexpectedExpression(UnexpectedExpression(
|
||||
Expression::Primary(self.clone()),
|
||||
));
|
||||
handler.receive(err.clone());
|
||||
Err(err)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
Self::Indexed(indexed) => {
|
||||
if let Self::Identifier(ident) = indexed.object().as_ref() {
|
||||
|
|
|
@ -23,7 +23,10 @@ use crate::{
|
|||
},
|
||||
};
|
||||
|
||||
use super::{statement::Block, Annotation, ConnectedList, DelimitedList};
|
||||
use super::{
|
||||
statement::{Block, VariableDeclaration},
|
||||
Annotation, ConnectedList, DelimitedList,
|
||||
};
|
||||
|
||||
/// Represents a declaration in the syntax tree.
|
||||
///
|
||||
|
@ -33,6 +36,8 @@ use super::{statement::Block, Annotation, ConnectedList, DelimitedList};
|
|||
/// Declaration:
|
||||
/// Function
|
||||
/// | Import
|
||||
/// | Tag
|
||||
/// | ('pub'? VariableDeclaration ';')
|
||||
/// ;
|
||||
/// ```
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
@ -41,6 +46,7 @@ pub enum Declaration {
|
|||
Function(Function),
|
||||
Import(Import),
|
||||
Tag(Tag),
|
||||
GlobalVariable((Option<Keyword>, VariableDeclaration, Punctuation)),
|
||||
}
|
||||
|
||||
impl SourceElement for Declaration {
|
||||
|
@ -49,6 +55,11 @@ impl SourceElement for Declaration {
|
|||
Self::Function(function) => function.span(),
|
||||
Self::Import(import) => import.span(),
|
||||
Self::Tag(tag) => tag.span(),
|
||||
Self::GlobalVariable((pub_kw, variable, semicolon)) => pub_kw
|
||||
.as_ref()
|
||||
.map_or_else(|| variable.span(), SourceElement::span)
|
||||
.join(&semicolon.span)
|
||||
.expect("invalid declaration span"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,13 +70,17 @@ impl Declaration {
|
|||
/// # Errors
|
||||
/// - if the annotation is invalid for the target declaration.
|
||||
pub fn with_annotation(self, annotation: Annotation) -> ParseResult<Self> {
|
||||
#[expect(clippy::single_match_else)]
|
||||
match self {
|
||||
Self::Function(mut function) => {
|
||||
function.annotations.push_front(annotation);
|
||||
|
||||
Ok(Self::Function(function))
|
||||
}
|
||||
Self::GlobalVariable((pub_kw, var, semi)) => {
|
||||
let var_with_annotation = var.with_annotation(annotation)?;
|
||||
|
||||
Ok(Self::GlobalVariable((pub_kw, var_with_annotation, semi)))
|
||||
}
|
||||
_ => {
|
||||
let err = Error::InvalidAnnotation(InvalidAnnotation {
|
||||
annotation: annotation.assignment.identifier.span,
|
||||
|
@ -342,11 +357,19 @@ impl Parser<'_> {
|
|||
Reading::Atomic(Token::Keyword(pub_keyword))
|
||||
if pub_keyword.keyword == KeywordKind::Pub =>
|
||||
{
|
||||
let function = self.parse_function(handler)?;
|
||||
|
||||
if let Ok(function) = self.try_parse(|parser| parser.parse_function(&VoidHandler)) {
|
||||
tracing::trace!("Parsed function '{:?}'", function.identifier.span.str());
|
||||
|
||||
Ok(Declaration::Function(function))
|
||||
} else {
|
||||
// eat the pub keyword
|
||||
self.forward();
|
||||
|
||||
let var = self.parse_variable_declaration(handler)?;
|
||||
let semi = self.parse_punctuation(';', true, handler)?;
|
||||
|
||||
Ok(Declaration::GlobalVariable((Some(pub_keyword), var, semi)))
|
||||
}
|
||||
}
|
||||
|
||||
// parse annotations
|
||||
|
@ -457,6 +480,18 @@ impl Parser<'_> {
|
|||
}))
|
||||
}
|
||||
|
||||
Reading::Atomic(Token::Keyword(keyword))
|
||||
if matches!(
|
||||
keyword.keyword,
|
||||
KeywordKind::Int | KeywordKind::Bool | KeywordKind::Val
|
||||
) =>
|
||||
{
|
||||
let var = self.parse_variable_declaration(handler)?;
|
||||
let semi = self.parse_punctuation(';', true, handler)?;
|
||||
|
||||
Ok(Declaration::GlobalVariable((None, var, semi)))
|
||||
}
|
||||
|
||||
unexpected => {
|
||||
// make progress
|
||||
self.forward();
|
||||
|
|
|
@ -358,11 +358,12 @@ impl Primary {
|
|||
})
|
||||
},
|
||||
|var| match var.as_ref() {
|
||||
VariableData::ComptimeValue { value } => {
|
||||
value.read().unwrap().clone().ok_or_else(|| NotComptime {
|
||||
VariableData::ComptimeValue {
|
||||
value,
|
||||
read_only: _,
|
||||
} => value.read().unwrap().clone().ok_or_else(|| NotComptime {
|
||||
expression: ident.span.clone(),
|
||||
})
|
||||
}
|
||||
}),
|
||||
_ => Err(NotComptime {
|
||||
expression: self.span(),
|
||||
}),
|
||||
|
@ -1276,6 +1277,7 @@ impl Transpiler {
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn transpile_scoreboard_operation(
|
||||
&mut self,
|
||||
binary: &Binary,
|
||||
|
@ -1303,6 +1305,15 @@ impl Transpiler {
|
|||
scope,
|
||||
handler,
|
||||
)?;
|
||||
|
||||
let (right_cmds, rhs_score) =
|
||||
if let Ok(ComptimeValue::Integer(int)) = right.comptime_eval(scope, handler) {
|
||||
self.initialize_constant_score(int);
|
||||
(
|
||||
Vec::new(),
|
||||
("shu_constants", std::borrow::Cow::Owned(int.to_string())),
|
||||
)
|
||||
} else {
|
||||
let right_cmds = self.transpile_expression(
|
||||
right,
|
||||
&DataLocation::ScoreboardValue {
|
||||
|
@ -1313,10 +1324,19 @@ impl Transpiler {
|
|||
handler,
|
||||
)?;
|
||||
|
||||
(
|
||||
right_cmds,
|
||||
(
|
||||
temp_objective.as_str(),
|
||||
std::borrow::Cow::Borrowed(&temp_locations[1]),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let calc_cmds = {
|
||||
let (target_objective, target) = score_target_location;
|
||||
let source = &temp_locations[1];
|
||||
let source_objective = &temp_objective;
|
||||
let source = rhs_score.1.as_ref();
|
||||
let source_objective = rhs_score.0;
|
||||
|
||||
let operation = match operator {
|
||||
BinaryOperator::Add(_) => "+=",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use chksum_md5 as md5;
|
||||
use enum_as_inner::EnumAsInner;
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
use std::{borrow::ToOwned, collections::BTreeMap, sync::Arc};
|
||||
|
||||
use shulkerbox::datapack::{Command, Execute};
|
||||
|
||||
|
@ -48,31 +48,16 @@ impl Transpiler {
|
|||
handler: &impl Handler<base::Error>,
|
||||
) -> TranspileResult<(String, TranspiledFunctionArguments)> {
|
||||
let program_identifier = identifier_span.source_file().identifier();
|
||||
let program_query = (
|
||||
program_identifier.to_string(),
|
||||
identifier_span.str().to_string(),
|
||||
);
|
||||
let alias_query = self.aliases.get(&program_query).cloned();
|
||||
let already_transpiled = scope
|
||||
.get_variable(identifier_span.str())
|
||||
let function = scope.get_variable(identifier_span.str());
|
||||
let already_transpiled = function
|
||||
.as_ref()
|
||||
.expect("called function should be in scope")
|
||||
.as_ref()
|
||||
.as_function()
|
||||
.map(|(_, path)| path.get().is_some())
|
||||
.map(|(_, path, _)| path.get().is_some())
|
||||
.expect("called variable should be of type function");
|
||||
|
||||
let function_data = scope
|
||||
.get_variable(identifier_span.str())
|
||||
.or_else(|| {
|
||||
alias_query
|
||||
.clone()
|
||||
.and_then(|(alias_program_identifier, alias_function_name)| {
|
||||
self.scopes
|
||||
.get(&alias_program_identifier)
|
||||
.and_then(|s| s.get_variable(&alias_function_name))
|
||||
})
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
let function_data = function.ok_or_else(|| {
|
||||
let error = TranspileError::MissingFunctionDeclaration(
|
||||
MissingFunctionDeclaration::from_scope(identifier_span.clone(), scope),
|
||||
);
|
||||
|
@ -83,6 +68,7 @@ impl Transpiler {
|
|||
let VariableData::Function {
|
||||
function_data,
|
||||
path: function_path,
|
||||
function_scope,
|
||||
} = function_data.as_ref()
|
||||
else {
|
||||
unreachable!("must be of correct type, otherwise errored out before");
|
||||
|
@ -134,8 +120,6 @@ impl Transpiler {
|
|||
|
||||
function_path.set(function_location.clone()).unwrap();
|
||||
|
||||
let function_scope = Scope::with_parent(scope);
|
||||
|
||||
for (i, param) in function_data.parameters.iter().enumerate() {
|
||||
let param_str = param.identifier().span.str();
|
||||
match param.variable_type() {
|
||||
|
@ -181,7 +165,7 @@ impl Transpiler {
|
|||
}
|
||||
|
||||
let commands =
|
||||
self.transpile_function(&statements, program_identifier, &function_scope, handler)?;
|
||||
self.transpile_function(&statements, program_identifier, function_scope, handler)?;
|
||||
|
||||
let namespace = self.datapack.namespace_mut(&function_data.namespace);
|
||||
|
||||
|
|
|
@ -266,7 +266,10 @@ mod enabled {
|
|||
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
|
||||
Value::Table(table)
|
||||
}
|
||||
Some(VariableData::ComptimeValue { value }) => {
|
||||
Some(VariableData::ComptimeValue {
|
||||
value,
|
||||
read_only: _,
|
||||
}) => {
|
||||
let value = value.read().unwrap();
|
||||
match &*value {
|
||||
Some(ComptimeValue::Boolean(b)) => Value::Boolean(*b),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Transpiler for `Shulkerscript`
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
collections::{BTreeMap, HashSet},
|
||||
ops::Deref,
|
||||
sync::{Arc, OnceLock},
|
||||
};
|
||||
|
@ -40,11 +40,7 @@ pub struct Transpiler {
|
|||
pub(super) initialized_constant_scores: HashSet<i64>,
|
||||
pub(super) temp_counter: usize,
|
||||
/// Top-level [`Scope`] for each program identifier
|
||||
pub(super) scopes: BTreeMap<String, Arc<Scope<'static>>>,
|
||||
/// Key: (program identifier, function name)
|
||||
pub(super) functions: BTreeMap<(String, String), FunctionData>,
|
||||
/// Key: alias, Value: target
|
||||
pub(super) aliases: HashMap<(String, String), (String, String)>,
|
||||
pub(super) scopes: BTreeMap<String, Arc<Scope>>,
|
||||
}
|
||||
|
||||
impl Transpiler {
|
||||
|
@ -59,8 +55,6 @@ impl Transpiler {
|
|||
initialized_constant_scores: HashSet::new(),
|
||||
temp_counter: 0,
|
||||
scopes: BTreeMap::new(),
|
||||
functions: BTreeMap::new(),
|
||||
aliases: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +69,11 @@ impl Transpiler {
|
|||
handler: &impl Handler<base::Error>,
|
||||
) -> Result<Datapack, TranspileError> {
|
||||
tracing::trace!("Transpiling program declarations");
|
||||
|
||||
let mut aliases = Vec::new();
|
||||
|
||||
for program in programs {
|
||||
let mut program_aliases = BTreeMap::new();
|
||||
let program_identifier = program
|
||||
.namespace()
|
||||
.span()
|
||||
|
@ -84,17 +82,48 @@ impl Transpiler {
|
|||
.clone();
|
||||
let scope = self
|
||||
.scopes
|
||||
.entry(program_identifier)
|
||||
.entry(program_identifier.clone())
|
||||
.or_insert_with(Scope::with_internal_functions)
|
||||
.to_owned();
|
||||
self.transpile_program_declarations(program, &scope, handler);
|
||||
self.transpile_program_declarations(program, &mut program_aliases, &scope, handler)?;
|
||||
aliases.push((scope.clone(), program_aliases));
|
||||
}
|
||||
|
||||
for (scope, program_aliases) in aliases {
|
||||
for (alias_name, (alias_program_identifier, actual_name)) in program_aliases {
|
||||
if let Some(alias_scope) = self.scopes.get(&alias_program_identifier) {
|
||||
if let Some(var_data) = alias_scope.get_variable(&actual_name) {
|
||||
scope.set_arc_variable(&alias_name, var_data);
|
||||
} else {
|
||||
tracing::error!(
|
||||
"Importing a non-existent variable: {} from {}",
|
||||
actual_name,
|
||||
alias_program_identifier
|
||||
);
|
||||
}
|
||||
} else {
|
||||
tracing::error!(
|
||||
"Importing from a non-existent program: {}",
|
||||
alias_program_identifier
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut always_transpile_functions = Vec::new();
|
||||
|
||||
{
|
||||
let functions = &mut self.functions;
|
||||
for (_, data) in functions.iter() {
|
||||
let functions = self.scopes.iter().flat_map(|(_, scope)| {
|
||||
scope
|
||||
.get_local_variables()
|
||||
.read()
|
||||
.unwrap()
|
||||
.values()
|
||||
.filter_map(|data| data.as_function().map(|(data, _, _)| data))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
for data in functions {
|
||||
let always_transpile_function = data.annotations.contains_key("tick")
|
||||
|| data.annotations.contains_key("load")
|
||||
|| data.annotations.contains_key("deobfuscate");
|
||||
|
@ -141,25 +170,28 @@ impl Transpiler {
|
|||
fn transpile_program_declarations(
|
||||
&mut self,
|
||||
program: &ProgramFile,
|
||||
program_aliases: &mut BTreeMap<String, (String, String)>,
|
||||
scope: &Arc<Scope>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) {
|
||||
) -> TranspileResult<()> {
|
||||
let namespace = program.namespace();
|
||||
|
||||
for declaration in program.declarations() {
|
||||
self.transpile_declaration(declaration, namespace, scope, handler);
|
||||
self.transpile_declaration(declaration, namespace, program_aliases, scope, handler)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transpiles the given declaration.
|
||||
#[allow(clippy::needless_pass_by_ref_mut)]
|
||||
fn transpile_declaration(
|
||||
&mut self,
|
||||
declaration: &Declaration,
|
||||
namespace: &Namespace,
|
||||
program_aliases: &mut BTreeMap<String, (String, String)>,
|
||||
scope: &Arc<Scope>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
) {
|
||||
) -> TranspileResult<()> {
|
||||
let program_identifier = declaration.span().source_file().identifier().clone();
|
||||
match declaration {
|
||||
Declaration::Function(function) => {
|
||||
|
@ -195,19 +227,18 @@ impl Transpiler {
|
|||
scope.set_variable(
|
||||
&name,
|
||||
VariableData::Function {
|
||||
function_data: function_data.clone(),
|
||||
function_data,
|
||||
path: OnceLock::new(),
|
||||
function_scope: scope.clone(),
|
||||
},
|
||||
);
|
||||
self.functions
|
||||
.insert((program_identifier, name), function_data);
|
||||
}
|
||||
Declaration::Import(import) => {
|
||||
let path = import.module().str_content();
|
||||
let import_identifier =
|
||||
super::util::calculate_import_identifier(&program_identifier, path);
|
||||
|
||||
let aliases = &mut self.aliases;
|
||||
// let aliases = &mut self.aliases;
|
||||
|
||||
match import.items() {
|
||||
ImportItems::All(_) => {
|
||||
|
@ -218,8 +249,8 @@ impl Transpiler {
|
|||
ImportItems::Named(list) => {
|
||||
for item in list.elements() {
|
||||
let name = item.span.str();
|
||||
aliases.insert(
|
||||
(program_identifier.clone(), name.to_string()),
|
||||
program_aliases.insert(
|
||||
name.to_string(),
|
||||
(import_identifier.clone(), name.to_string()),
|
||||
);
|
||||
}
|
||||
|
@ -241,9 +272,21 @@ impl Transpiler {
|
|||
if tag.replace().is_some() {
|
||||
sb_tag.set_replace(true);
|
||||
}
|
||||
// TODO: handle global variables
|
||||
}
|
||||
Declaration::GlobalVariable((_, declaration, _)) => {
|
||||
let setup_variable_cmds = self.transpile_variable_declaration(
|
||||
declaration,
|
||||
true,
|
||||
&program_identifier,
|
||||
scope,
|
||||
handler,
|
||||
)?;
|
||||
|
||||
self.setup_cmds.extend(setup_variable_cmds);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn transpile_statement(
|
||||
|
@ -264,7 +307,7 @@ impl Transpiler {
|
|||
unreachable!("Only literal commands are allowed in functions at this time.")
|
||||
}
|
||||
Statement::ExecuteBlock(execute) => {
|
||||
let child_scope = Scope::with_parent(scope);
|
||||
let child_scope = Scope::with_parent(scope.clone());
|
||||
Ok(self.transpile_execute_block(
|
||||
execute,
|
||||
program_identifier,
|
||||
|
@ -277,7 +320,7 @@ impl Transpiler {
|
|||
Ok(vec![Command::Comment(content.to_string())])
|
||||
}
|
||||
Statement::Grouping(group) => {
|
||||
let child_scope = Scope::with_parent(scope);
|
||||
let child_scope = Scope::with_parent(scope.clone());
|
||||
let statements = group.block().statements();
|
||||
let mut errors = Vec::new();
|
||||
let commands = statements
|
||||
|
@ -317,9 +360,14 @@ impl Transpiler {
|
|||
Err(error)
|
||||
}
|
||||
},
|
||||
SemicolonStatement::VariableDeclaration(decl) => {
|
||||
self.transpile_variable_declaration(decl, program_identifier, scope, handler)
|
||||
}
|
||||
SemicolonStatement::VariableDeclaration(decl) => self
|
||||
.transpile_variable_declaration(
|
||||
decl,
|
||||
false,
|
||||
program_identifier,
|
||||
scope,
|
||||
handler,
|
||||
),
|
||||
SemicolonStatement::Assignment(assignment) => self.transpile_assignment(
|
||||
TranspileAssignmentTarget::from(assignment.destination()),
|
||||
assignment.expression(),
|
||||
|
@ -344,8 +392,10 @@ impl Transpiler {
|
|||
}
|
||||
Expression::Primary(Primary::Identifier(ident)) => {
|
||||
match scope.get_variable(ident.span.str()).as_deref() {
|
||||
Some(VariableData::ComptimeValue { value }) => {
|
||||
value.read().unwrap().as_ref().map_or_else(
|
||||
Some(VariableData::ComptimeValue {
|
||||
value,
|
||||
read_only: _,
|
||||
}) => value.read().unwrap().as_ref().map_or_else(
|
||||
|| {
|
||||
let error = TranspileError::MissingValue(MissingValue {
|
||||
expression: ident.span.clone(),
|
||||
|
@ -360,8 +410,7 @@ impl Transpiler {
|
|||
);
|
||||
Ok(vec![cmd])
|
||||
},
|
||||
)
|
||||
}
|
||||
),
|
||||
Some(_) => {
|
||||
let error = TranspileError::UnexpectedExpression(UnexpectedExpression(
|
||||
expression.clone(),
|
||||
|
|
|
@ -49,6 +49,8 @@ pub enum VariableData {
|
|||
function_data: FunctionData,
|
||||
/// The path to the function once it is generated.
|
||||
path: OnceLock<String>,
|
||||
/// The scope of the function.
|
||||
function_scope: Arc<Scope>,
|
||||
},
|
||||
/// A macro function parameter.
|
||||
MacroParameter {
|
||||
|
@ -104,6 +106,8 @@ pub enum VariableData {
|
|||
ComptimeValue {
|
||||
/// The value.
|
||||
value: Arc<RwLock<Option<ComptimeValue>>>,
|
||||
/// Whether the value is read-only.
|
||||
read_only: bool,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -129,9 +133,9 @@ impl<'a> From<&'a AssignmentDestination> for TranspileAssignmentTarget<'a> {
|
|||
/// A scope that stores variables.
|
||||
#[cfg(feature = "shulkerbox")]
|
||||
#[derive(Default)]
|
||||
pub struct Scope<'a> {
|
||||
pub struct Scope {
|
||||
/// Parent scope where variables are inherited from.
|
||||
parent: Option<&'a Arc<Self>>,
|
||||
parent: Option<Arc<Self>>,
|
||||
/// Variables stored in the scope.
|
||||
variables: RwLock<HashMap<String, Arc<VariableData>>>,
|
||||
/// How many times the variable has been shadowed in the current scope.
|
||||
|
@ -139,7 +143,7 @@ pub struct Scope<'a> {
|
|||
}
|
||||
|
||||
#[cfg(feature = "shulkerbox")]
|
||||
impl<'a> Scope<'a> {
|
||||
impl Scope {
|
||||
/// Creates a new scope.
|
||||
#[must_use]
|
||||
pub fn new() -> Arc<Self> {
|
||||
|
@ -161,7 +165,7 @@ impl<'a> Scope<'a> {
|
|||
|
||||
/// Creates a new scope with a parent.
|
||||
#[must_use]
|
||||
pub fn with_parent(parent: &'a Arc<Self>) -> Arc<Self> {
|
||||
pub fn with_parent(parent: Arc<Self>) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
parent: Some(parent),
|
||||
..Default::default()
|
||||
|
@ -198,11 +202,17 @@ impl<'a> Scope<'a> {
|
|||
|
||||
/// Sets a variable in the scope.
|
||||
pub fn set_variable(&self, name: &str, var: VariableData) {
|
||||
self.set_arc_variable(name, Arc::new(var));
|
||||
}
|
||||
|
||||
/// Sets a variable in the scope.
|
||||
/// This function is used to set a variable that is already wrapped in an `Arc`.
|
||||
pub fn set_arc_variable(&self, name: &str, var: Arc<VariableData>) {
|
||||
let prev = self
|
||||
.variables
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(name.to_string(), Arc::new(var));
|
||||
.insert(name.to_string(), var);
|
||||
*self
|
||||
.shadowed
|
||||
.write()
|
||||
|
@ -229,12 +239,12 @@ impl<'a> Scope<'a> {
|
|||
|
||||
/// Gets the parent scope.
|
||||
pub fn get_parent(&self) -> Option<Arc<Self>> {
|
||||
self.parent.cloned()
|
||||
self.parent.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "shulkerbox")]
|
||||
impl Debug for Scope<'_> {
|
||||
impl Debug for Scope {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
struct VariableWrapper<'a>(&'a RwLock<HashMap<String, Arc<VariableData>>>);
|
||||
impl Debug for VariableWrapper<'_> {
|
||||
|
@ -258,6 +268,7 @@ impl Transpiler {
|
|||
pub(super) fn transpile_variable_declaration(
|
||||
&mut self,
|
||||
declaration: &VariableDeclaration,
|
||||
is_global: bool,
|
||||
program_identifier: &str,
|
||||
scope: &Arc<Scope>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
|
@ -265,6 +276,7 @@ impl Transpiler {
|
|||
match declaration {
|
||||
VariableDeclaration::Single(declaration) => self.transpile_single_variable_declaration(
|
||||
declaration,
|
||||
is_global,
|
||||
program_identifier,
|
||||
scope,
|
||||
handler,
|
||||
|
@ -277,6 +289,7 @@ impl Transpiler {
|
|||
),
|
||||
VariableDeclaration::Array(declaration) => self.transpile_array_variable_declaration(
|
||||
declaration,
|
||||
is_global,
|
||||
program_identifier,
|
||||
scope,
|
||||
handler,
|
||||
|
@ -307,6 +320,7 @@ impl Transpiler {
|
|||
declaration.identifier().span.str(),
|
||||
VariableData::ComptimeValue {
|
||||
value: Arc::new(RwLock::new(value)),
|
||||
read_only: is_global,
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -318,6 +332,7 @@ impl Transpiler {
|
|||
fn transpile_single_variable_declaration(
|
||||
&mut self,
|
||||
declaration: &SingleVariableDeclaration,
|
||||
is_global: bool,
|
||||
program_identifier: &str,
|
||||
scope: &Arc<Scope>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
|
@ -342,7 +357,7 @@ impl Transpiler {
|
|||
declaration.identifier().span.str(),
|
||||
VariableData::ScoreboardValue {
|
||||
objective: name.clone(),
|
||||
target,
|
||||
target: target.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -350,25 +365,49 @@ impl Transpiler {
|
|||
scope.set_variable(
|
||||
declaration.identifier().span.str(),
|
||||
VariableData::BooleanStorage {
|
||||
storage_name: name,
|
||||
path: target,
|
||||
storage_name: name.clone(),
|
||||
path: target.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => unreachable!("no other variable types"),
|
||||
}
|
||||
|
||||
declaration.assignment().as_ref().map_or_else(
|
||||
|| Ok(Vec::new()),
|
||||
|assignment| {
|
||||
self.transpile_assignment(
|
||||
if let Some(assignment) = declaration.assignment().as_ref() {
|
||||
let cmds = self.transpile_assignment(
|
||||
TranspileAssignmentTarget::Identifier(declaration.identifier()),
|
||||
assignment.expression(),
|
||||
scope,
|
||||
handler,
|
||||
)
|
||||
},
|
||||
)
|
||||
)?;
|
||||
if is_global {
|
||||
let (temp_objective, temp_targets) = self.get_temp_scoreboard_locations(1);
|
||||
let temp_target = &temp_targets[0];
|
||||
let test_cmd = match declaration.variable_type().keyword {
|
||||
KeywordKind::Int => {
|
||||
Command::Raw(format!("scoreboard players get {name} {target}"))
|
||||
}
|
||||
KeywordKind::Bool => Command::Raw(format!("data get storage {name} {target}")),
|
||||
_ => unreachable!("no other variable types"),
|
||||
};
|
||||
let test_exists_cmd = Command::Execute(Execute::Store(
|
||||
format!("success score {temp_target} {temp_objective}").into(),
|
||||
Box::new(Execute::Run(Box::new(test_cmd))),
|
||||
));
|
||||
let cond_cmd = Command::Execute(Execute::If(
|
||||
Condition::Atom(
|
||||
format!("score {temp_target} {temp_objective} matches 0").into(),
|
||||
),
|
||||
Box::new(Execute::Run(Box::new(Command::Group(cmds)))),
|
||||
None,
|
||||
));
|
||||
Ok(vec![test_exists_cmd, cond_cmd])
|
||||
} else {
|
||||
Ok(cmds)
|
||||
}
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
fn transpile_score_variable_declaration(
|
||||
|
@ -411,6 +450,7 @@ impl Transpiler {
|
|||
fn transpile_array_variable_declaration(
|
||||
&mut self,
|
||||
declaration: &ArrayVariableDeclaration,
|
||||
_is_global: bool,
|
||||
program_identifier: &str,
|
||||
scope: &Arc<Scope>,
|
||||
handler: &impl Handler<base::Error>,
|
||||
|
@ -450,6 +490,7 @@ impl Transpiler {
|
|||
declaration.assignment().as_ref().map_or_else(
|
||||
|| Ok(Vec::new()),
|
||||
|assignment| {
|
||||
// TODO: implement global already exists check when array assignments are implemented
|
||||
self.transpile_assignment(
|
||||
TranspileAssignmentTarget::Identifier(declaration.identifier()),
|
||||
assignment.expression(),
|
||||
|
@ -692,7 +733,15 @@ impl Transpiler {
|
|||
return Err(err);
|
||||
}
|
||||
},
|
||||
VariableData::ComptimeValue { value } => {
|
||||
VariableData::ComptimeValue { value, read_only } => {
|
||||
if *read_only {
|
||||
let err = TranspileError::AssignmentError(AssignmentError {
|
||||
identifier: identifier.span(),
|
||||
message: "Cannot assign to a read-only value.".to_string(),
|
||||
});
|
||||
handler.receive(err.clone());
|
||||
return Err(err);
|
||||
}
|
||||
let comptime_value =
|
||||
expression.comptime_eval(scope, handler).map_err(|err| {
|
||||
let err = TranspileError::NotComptime(err);
|
||||
|
@ -1097,9 +1146,14 @@ impl Transpiler {
|
|||
DataLocation::Storage {
|
||||
storage_name: target_storage_name,
|
||||
path: target_path,
|
||||
r#type,
|
||||
r#type: target_type,
|
||||
} => {
|
||||
if matches!(r#type, StorageType::Boolean) {
|
||||
if storage_name == target_storage_name
|
||||
&& path == target_path
|
||||
&& r#type == target_type
|
||||
{
|
||||
Ok(Vec::new())
|
||||
} else if matches!(target_type, StorageType::Boolean) {
|
||||
let cmd = Command::Raw(format!(
|
||||
"data modify storage {target_storage_name} {target_path} set from storage {storage_name} {path}"
|
||||
));
|
||||
|
@ -1123,11 +1177,15 @@ impl Transpiler {
|
|||
objective: target_objective,
|
||||
target: target_target,
|
||||
} => {
|
||||
if objective == target_objective && score_target == target_target {
|
||||
Ok(Vec::new())
|
||||
} else {
|
||||
let cmd = Command::Raw(format!(
|
||||
"scoreboard players operation {target_target} {target_objective} = {score_target} {objective}"
|
||||
));
|
||||
Ok(vec![cmd])
|
||||
}
|
||||
}
|
||||
DataLocation::Storage {
|
||||
storage_name,
|
||||
path,
|
||||
|
@ -1213,7 +1271,7 @@ mod tests {
|
|||
objective: "test".to_string(),
|
||||
},
|
||||
);
|
||||
let child = Scope::with_parent(&scope);
|
||||
let child = Scope::with_parent(scope);
|
||||
if let Some(var) = child.get_variable("test") {
|
||||
match var.as_ref() {
|
||||
VariableData::Scoreboard { objective } => assert_eq!(objective, "test"),
|
||||
|
@ -1233,7 +1291,7 @@ mod tests {
|
|||
objective: "test1".to_string(),
|
||||
},
|
||||
);
|
||||
let child = Scope::with_parent(&scope);
|
||||
let child = Scope::with_parent(scope);
|
||||
child.set_variable(
|
||||
"test",
|
||||
VariableData::Scoreboard {
|
||||
|
|
Loading…
Reference in New Issue