Compare commits

..

4 Commits

Author SHA1 Message Date
Moritz Hölting 469b8d3875 implement first version of compiler variables 2025-04-01 01:00:36 +02:00
Moritz Hölting ab76b1d43e change the syntax to set the type of tags 2025-03-31 23:01:27 +02:00
Moritz Hölting f808fef3f1 fix compile errors without shulkerbox feature 2025-03-31 22:47:16 +02:00
Moritz Hölting 32d453ebef reimplement semantic checking 2025-03-31 21:40:59 +02:00
18 changed files with 1439 additions and 624 deletions

View File

@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Change the syntax to set the type of a tag from `tag "[name]" of "[type]"` to `tag<"[type]"> "[name]"`
- Remove the keyword `of`
- Option to deduplicate source files during serialization when using `SerdeWrapper` - Option to deduplicate source files during serialization when using `SerdeWrapper`
### Removed ### Removed

View File

@ -48,11 +48,11 @@ pub enum KeywordKind {
From, From,
Import, Import,
Tag, Tag,
Of,
Replace, Replace,
Int, Int,
Bool, Bool,
Macro, Macro,
Val,
} }
impl Display for KeywordKind { impl Display for KeywordKind {
@ -114,11 +114,11 @@ impl KeywordKind {
Self::From => "from", Self::From => "from",
Self::Import => "import", Self::Import => "import",
Self::Tag => "tag", Self::Tag => "tag",
Self::Of => "of",
Self::Replace => "replace", Self::Replace => "replace",
Self::Int => "int", Self::Int => "int",
Self::Bool => "bool", Self::Bool => "bool",
Self::Macro => "macro", Self::Macro => "macro",
Self::Val => "val",
} }
} }

View File

@ -2,10 +2,7 @@
#![allow(missing_docs)] #![allow(missing_docs)]
use std::{collections::HashSet, fmt::Display}; use std::fmt::Display;
use getset::Getters;
use itertools::Itertools as _;
use crate::{ use crate::{
base::{ base::{
@ -14,6 +11,10 @@ use crate::{
}, },
lexical::token::StringLiteral, lexical::token::StringLiteral,
syntax::syntax_tree::expression::Expression, syntax::syntax_tree::expression::Expression,
transpile::error::{
AssignmentError, IllegalIndexing, MismatchedTypes, MissingFunctionDeclaration,
UnknownIdentifier,
},
}; };
#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)] #[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
@ -31,77 +32,20 @@ pub enum Error {
UnresolvedMacroUsage(#[from] UnresolvedMacroUsage), UnresolvedMacroUsage(#[from] UnresolvedMacroUsage),
#[error(transparent)] #[error(transparent)]
IncompatibleFunctionAnnotation(#[from] IncompatibleFunctionAnnotation), IncompatibleFunctionAnnotation(#[from] IncompatibleFunctionAnnotation),
#[error(transparent)]
IllegalIndexing(#[from] IllegalIndexing),
#[error(transparent)]
MismatchedTypes(#[from] MismatchedTypes),
#[error(transparent)]
UnknownIdentifier(#[from] UnknownIdentifier),
#[error(transparent)]
AssignmentError(#[from] AssignmentError),
#[error("Lua is disabled, but a Lua function was used.")]
LuaDisabled,
#[error("Other: {0}")]
Other(String),
} }
// TODO: remove duplicate error (also in transpile)
/// An error that occurs when a function declaration is missing.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)]
pub struct MissingFunctionDeclaration {
#[get = "pub"]
span: Span,
#[get = "pub"]
alternatives: Vec<String>,
}
impl MissingFunctionDeclaration {
#[expect(dead_code)]
pub(super) fn from_context(identifier_span: Span, functions: &HashSet<String>) -> Self {
let own_name = identifier_span.str();
let alternatives = functions
.iter()
.filter_map(|function_name| {
let normalized_distance =
strsim::normalized_damerau_levenshtein(own_name, function_name);
(normalized_distance > 0.8
|| strsim::damerau_levenshtein(own_name, function_name) < 3)
.then_some((normalized_distance, function_name))
})
.sorted_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal))
.map(|(_, data)| data)
.take(8)
.cloned()
.collect::<Vec<_>>();
Self {
alternatives,
span: identifier_span,
}
}
}
impl Display for MissingFunctionDeclaration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::fmt::Write;
let message = format!(
"no matching function declaration found for invocation of function `{}`",
self.span.str()
);
write!(f, "{}", Message::new(Severity::Error, message))?;
let help_message = if self.alternatives.is_empty() {
None
} else {
let mut message = String::from("did you mean ");
for (i, alternative) in self.alternatives.iter().enumerate() {
if i > 0 {
message.push_str(", ");
}
write!(message, "`{alternative}`")?;
}
Some(message + "?")
};
write!(
f,
"\n{}",
SourceCodeDisplay::new(&self.span, help_message.as_ref())
)
}
}
impl std::error::Error for MissingFunctionDeclaration {}
/// An error that occurs when a function declaration is missing. /// An error that occurs when a function declaration is missing.
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UnexpectedExpression(pub Expression); pub struct UnexpectedExpression(pub Expression);

File diff suppressed because it is too large Load Diff

97
src/semantic/scope.rs Normal file
View File

@ -0,0 +1,97 @@
use std::{collections::HashMap, sync::RwLock};
/// Type of variable
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub enum VariableType {
/// A function.
Function,
/// A macro function parameter.
MacroParameter,
/// A scoreboard.
Scoreboard,
/// A scoreboard value.
ScoreboardValue,
/// Multiple values stored in scoreboard.
ScoreboardArray,
/// A tag applied to entities.
Tag,
/// A boolean stored in a data storage.
BooleanStorage,
/// Multiple booleans stored in a data storage array.
BooleanStorageArray,
/// Compiler internal function.
InternalFunction,
/// Compiler internal value.
ComptimeValue,
}
/// A scope that stores variables.
#[derive(Debug, Default)]
pub struct SemanticScope<'a> {
/// Parent scope where variables are inherited from.
parent: Option<&'a Self>,
/// Variables stored in the scope.
variables: RwLock<HashMap<String, VariableType>>,
}
impl<'a> SemanticScope<'a> {
/// Creates a new scope.
#[must_use]
pub fn new() -> Self {
let scope = Self::default();
scope.set_variable("print", VariableType::InternalFunction);
scope
}
/// Creates a new scope with a parent.
#[must_use]
pub fn with_parent(parent: &'a Self) -> Self {
Self {
parent: Some(parent),
..Default::default()
}
}
/// Gets a variable from the scope.
pub fn get_variable(&self, name: &str) -> Option<VariableType> {
let var = self.variables.read().unwrap().get(name).copied();
if var.is_some() {
var
} else {
self.parent
.as_ref()
.and_then(|parent| parent.get_variable(name))
}
}
/// Sets a variable in the scope.
pub fn set_variable(&self, name: &str, var: VariableType) {
self.variables
.write()
.unwrap()
.insert(name.to_string(), var);
}
/// Gets the variables stored in the current scope.
pub fn get_local_variables(&self) -> &RwLock<HashMap<String, VariableType>> {
&self.variables
}
/// Gets all variables stored in the scope.
///
/// This function does not return a reference to the variables, but clones them.
pub fn get_all_variables(&self) -> HashMap<String, VariableType> {
let mut variables = self.variables.read().unwrap().clone();
if let Some(parent) = self.parent.as_ref() {
variables.extend(parent.get_all_variables());
}
variables
}
/// Gets the parent scope.
pub fn get_parent(&self) -> Option<&Self> {
self.parent
}
}

View File

@ -253,7 +253,7 @@ impl SourceElement for Import {
/// ///
/// ``` ebnf /// ``` ebnf
/// TagDeclaration: /// TagDeclaration:
/// 'tag' StringLiteral ('of' StringLiteral)? 'replace'? '[' (StringLiteral (',' StringLiteral)*)? ']' /// 'tag' ('<' StringLiteral '>')? StringLiteral 'replace'? '[' (StringLiteral (',' StringLiteral)*)? ']'
/// ; /// ;
/// ``` /// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -262,9 +262,9 @@ pub struct Tag {
#[get = "pub"] #[get = "pub"]
tag_keyword: Keyword, tag_keyword: Keyword,
#[get = "pub"] #[get = "pub"]
name: StringLiteral, of_type: Option<(Punctuation, StringLiteral, Punctuation)>,
#[get = "pub"] #[get = "pub"]
of_type: Option<(Keyword, StringLiteral)>, name: StringLiteral,
#[get = "pub"] #[get = "pub"]
replace: Option<Keyword>, replace: Option<Keyword>,
#[get = "pub"] #[get = "pub"]
@ -278,15 +278,15 @@ impl Tag {
self, self,
) -> ( ) -> (
Keyword, Keyword,
Option<(Punctuation, StringLiteral, Punctuation)>,
StringLiteral, StringLiteral,
Option<(Keyword, StringLiteral)>,
Option<Keyword>, Option<Keyword>,
DelimitedList<StringLiteral>, DelimitedList<StringLiteral>,
) { ) {
( (
self.tag_keyword, self.tag_keyword,
self.name,
self.of_type, self.of_type,
self.name,
self.replace, self.replace,
self.entries, self.entries,
) )
@ -299,7 +299,7 @@ impl Tag {
self.of_type self.of_type
.as_ref() .as_ref()
.map_or(TagType::Function, |(_, tag_type)| { .map_or(TagType::Function, |(_, tag_type, _)| {
match tag_type.str_content().as_ref() { match tag_type.str_content().as_ref() {
"function" => TagType::Function, "function" => TagType::Function,
"block" => TagType::Block, "block" => TagType::Block,
@ -419,18 +419,24 @@ impl Parser<'_> {
// eat the tag keyword // eat the tag keyword
self.forward(); self.forward();
let of_type = match self.stop_at_significant() {
Reading::Atomic(Token::Punctuation(punc)) if punc.punctuation == '<' => {
// eat the open bracket
self.forward();
let of_type = self.parse_string_literal(handler)?;
// eat the close bracket
let closing = self.parse_punctuation('>', true, handler)?;
Some((punc, of_type, closing))
}
_ => None,
};
// parse the name // parse the name
let name = self.parse_string_literal(handler)?; let name = self.parse_string_literal(handler)?;
let of_type = self
.try_parse(|parser| {
let of_keyword = parser.parse_keyword(KeywordKind::Of, &VoidHandler)?;
let of_type = parser.parse_string_literal(handler)?;
Ok((of_keyword, of_type))
})
.ok();
let replace = self let replace = self
.try_parse(|parser| parser.parse_keyword(KeywordKind::Replace, &VoidHandler)) .try_parse(|parser| parser.parse_keyword(KeywordKind::Replace, &VoidHandler))
.ok(); .ok();
@ -444,8 +450,8 @@ impl Parser<'_> {
Ok(Declaration::Tag(Tag { Ok(Declaration::Tag(Tag {
tag_keyword, tag_keyword,
name,
of_type, of_type,
name,
replace, replace,
entries, entries,
})) }))

View File

@ -247,6 +247,14 @@ impl SourceElement for Parenthesized {
} }
} }
impl std::ops::Deref for Parenthesized {
type Target = Expression;
fn deref(&self) -> &Self::Target {
&self.expression
}
}
/// Represents a indexed expression in the syntax tree. /// Represents a indexed expression in the syntax tree.
/// ///
/// Syntax Synopsis: /// Syntax Synopsis:

View File

@ -325,6 +325,7 @@ pub enum VariableDeclaration {
Array(ArrayVariableDeclaration), Array(ArrayVariableDeclaration),
Score(ScoreVariableDeclaration), Score(ScoreVariableDeclaration),
Tag(TagVariableDeclaration), Tag(TagVariableDeclaration),
ComptimeValue(ComptimeValueDeclaration),
} }
impl SourceElement for VariableDeclaration { impl SourceElement for VariableDeclaration {
@ -334,6 +335,7 @@ impl SourceElement for VariableDeclaration {
Self::Array(declaration) => declaration.span(), Self::Array(declaration) => declaration.span(),
Self::Score(declaration) => declaration.span(), Self::Score(declaration) => declaration.span(),
Self::Tag(declaration) => declaration.span(), Self::Tag(declaration) => declaration.span(),
Self::ComptimeValue(declaration) => declaration.span(),
} }
} }
} }
@ -347,6 +349,7 @@ impl VariableDeclaration {
Self::Array(declaration) => &declaration.identifier, Self::Array(declaration) => &declaration.identifier,
Self::Score(declaration) => &declaration.identifier, Self::Score(declaration) => &declaration.identifier,
Self::Tag(declaration) => &declaration.identifier, Self::Tag(declaration) => &declaration.identifier,
Self::ComptimeValue(declaration) => &declaration.identifier,
} }
} }
@ -358,6 +361,7 @@ impl VariableDeclaration {
Self::Array(declaration) => &declaration.variable_type, Self::Array(declaration) => &declaration.variable_type,
Self::Score(declaration) => &declaration.int_keyword, Self::Score(declaration) => &declaration.int_keyword,
Self::Tag(declaration) => &declaration.bool_keyword, Self::Tag(declaration) => &declaration.bool_keyword,
Self::ComptimeValue(declaration) => &declaration.val_keyword,
} }
} }
@ -383,6 +387,14 @@ impl VariableDeclaration {
declaration.annotations.push_front(annotation); declaration.annotations.push_front(annotation);
Ok(Self::Tag(declaration)) Ok(Self::Tag(declaration))
} }
Self::ComptimeValue(_) => {
let err = Error::InvalidAnnotation(InvalidAnnotation {
annotation: annotation.assignment.identifier.span,
target: "comptime values".to_string(),
});
Err(err)
}
} }
} }
} }
@ -681,6 +693,53 @@ impl TagVariableDeclaration {
} }
} }
/// Represents a compile time value declaration in the syntax tree.
///
/// Syntax Synopsis:
///
/// ```ebnf
/// ComptimeValueDeclaration:
/// 'val' identifier VariableDeclarationAssignment?
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Getters)]
pub struct ComptimeValueDeclaration {
/// The type of the variable.
#[get = "pub"]
val_keyword: Keyword,
/// The identifier of the variable.
#[get = "pub"]
identifier: Identifier,
/// The optional assignment of the variable.
#[get = "pub"]
assignment: Option<VariableDeclarationAssignment>,
/// The annotations of the variable declaration.
#[get = "pub"]
annotations: VecDeque<Annotation>,
}
impl SourceElement for ComptimeValueDeclaration {
fn span(&self) -> Span {
self.val_keyword
.span()
.join(
&self
.assignment
.as_ref()
.map_or_else(|| self.identifier.span(), SourceElement::span),
)
.expect("The span of the single variable declaration is invalid.")
}
}
impl ComptimeValueDeclaration {
/// Dissolves the [`ComptimeValueDeclaration`] into its components.
#[must_use]
pub fn dissolve(self) -> (Keyword, Identifier, Option<VariableDeclarationAssignment>) {
(self.val_keyword, self.identifier, self.assignment)
}
}
/// Represents an assignment in the syntax tree. /// Represents an assignment in the syntax tree.
/// ///
/// Syntax Synopsis: /// Syntax Synopsis:
@ -883,7 +942,10 @@ impl Parser<'_> {
) -> ParseResult<Semicolon> { ) -> ParseResult<Semicolon> {
let statement = match self.stop_at_significant() { let statement = match self.stop_at_significant() {
Reading::Atomic(Token::Keyword(keyword)) Reading::Atomic(Token::Keyword(keyword))
if matches!(keyword.keyword, KeywordKind::Int | KeywordKind::Bool) => if matches!(
keyword.keyword,
KeywordKind::Int | KeywordKind::Bool | KeywordKind::Val
) =>
{ {
self.parse_variable_declaration(handler) self.parse_variable_declaration(handler)
.map(SemicolonStatement::VariableDeclaration) .map(SemicolonStatement::VariableDeclaration)
@ -895,18 +957,20 @@ impl Parser<'_> {
let destination = { let destination = {
let identifier = p.parse_identifier(&VoidHandler)?; let identifier = p.parse_identifier(&VoidHandler)?;
if let Ok(tree) = p.step_into( match p.stop_at_significant() {
Delimiter::Bracket, Reading::IntoDelimited(punc) if punc.punctuation == '[' => {
|pp| pp.parse_expression(&VoidHandler), let tree = p.step_into(
&VoidHandler, Delimiter::Bracket,
) { |pp| pp.parse_expression(&VoidHandler),
let open = tree.open; &VoidHandler,
let close = tree.close; )?;
let expression = tree.tree?; let open = tree.open;
let close = tree.close;
let expression = tree.tree?;
AssignmentDestination::Indexed(identifier, open, expression, close) AssignmentDestination::Indexed(identifier, open, expression, close)
} else { }
AssignmentDestination::Identifier(identifier) _ => AssignmentDestination::Identifier(identifier),
} }
}; };
let equals = p.parse_punctuation('=', true, &VoidHandler)?; let equals = p.parse_punctuation('=', true, &VoidHandler)?;
@ -948,7 +1012,10 @@ impl Parser<'_> {
let variable_type = match self.stop_at_significant() { let variable_type = match self.stop_at_significant() {
Reading::Atomic(Token::Keyword(keyword)) Reading::Atomic(Token::Keyword(keyword))
if matches!(keyword.keyword, KeywordKind::Int | KeywordKind::Bool) => if matches!(
keyword.keyword,
KeywordKind::Int | KeywordKind::Bool | KeywordKind::Val
) =>
{ {
self.forward(); self.forward();
keyword keyword
@ -983,7 +1050,10 @@ impl Parser<'_> {
let identifier = self.parse_identifier(handler)?; let identifier = self.parse_identifier(handler)?;
match self.stop_at_significant() { match self.stop_at_significant() {
Reading::IntoDelimited(punc) if punc.punctuation == '[' => { Reading::IntoDelimited(punc)
if punc.punctuation == '['
&& matches!(variable_type.keyword, KeywordKind::Int | KeywordKind::Bool) =>
{
let tree = self.step_into( let tree = self.step_into(
Delimiter::Bracket, Delimiter::Bracket,
|p| { |p| {
@ -1071,12 +1141,23 @@ impl Parser<'_> {
let expression = self.parse_expression(handler)?; let expression = self.parse_expression(handler)?;
let assignment = VariableDeclarationAssignment { equals, expression }; let assignment = VariableDeclarationAssignment { equals, expression };
Ok(VariableDeclaration::Single(SingleVariableDeclaration { if variable_type.keyword == KeywordKind::Val {
variable_type, Ok(VariableDeclaration::ComptimeValue(
identifier, ComptimeValueDeclaration {
assignment: Some(assignment), val_keyword: variable_type,
annotations: VecDeque::new(), identifier,
})) assignment: Some(assignment),
annotations: VecDeque::new(),
},
))
} else {
Ok(VariableDeclaration::Single(SingleVariableDeclaration {
variable_type,
identifier,
assignment: Some(assignment),
annotations: VecDeque::new(),
}))
}
} }
// SingleVariableDeclaration without Assignment // SingleVariableDeclaration without Assignment
_ => Ok(VariableDeclaration::Single(SingleVariableDeclaration { _ => Ok(VariableDeclaration::Single(SingleVariableDeclaration {

View File

@ -1,7 +1,5 @@
//! Execute block statement syntax tree. //! Execute block statement syntax tree.
use std::collections::HashSet;
use derive_more::From; use derive_more::From;
use enum_as_inner::EnumAsInner; use enum_as_inner::EnumAsInner;
use getset::Getters; use getset::Getters;
@ -998,10 +996,10 @@ pub trait ExecuteBlockHeadItem {
#[expect(clippy::missing_errors_doc)] #[expect(clippy::missing_errors_doc)]
fn analyze_semantics( fn analyze_semantics(
&self, &self,
macro_names: &HashSet<String>, scope: &crate::semantic::SemanticScope,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> Result<(), crate::semantic::error::Error> { ) -> Result<(), crate::semantic::error::Error> {
self.selector().analyze_semantics(macro_names, handler) self.selector().analyze_semantics(scope, handler)
} }
} }

View File

@ -44,6 +44,10 @@ pub enum TranspileError {
MissingValue(#[from] MissingValue), MissingValue(#[from] MissingValue),
#[error(transparent)] #[error(transparent)]
IllegalIndexing(#[from] IllegalIndexing), IllegalIndexing(#[from] IllegalIndexing),
#[error(transparent)]
InvalidArgument(#[from] InvalidArgument),
#[error(transparent)]
NotComptime(#[from] NotComptime),
} }
/// The result of a transpilation operation. /// The result of a transpilation operation.
@ -52,8 +56,10 @@ pub type TranspileResult<T> = Result<T, TranspileError>;
/// An error that occurs when a function declaration is missing. /// An error that occurs when a function declaration is missing.
#[derive(Debug, Clone, PartialEq, Eq, Getters)] #[derive(Debug, Clone, PartialEq, Eq, Getters)]
pub struct MissingFunctionDeclaration { pub struct MissingFunctionDeclaration {
/// The span of the identifier that is missing.
#[get = "pub"] #[get = "pub"]
span: Span, span: Span,
/// Possible alternatives for the missing function declaration.
#[get = "pub"] #[get = "pub"]
alternatives: Vec<FunctionData>, alternatives: Vec<FunctionData>,
} }
@ -127,11 +133,26 @@ impl Display for MissingFunctionDeclaration {
impl std::error::Error for MissingFunctionDeclaration {} impl std::error::Error for MissingFunctionDeclaration {}
impl std::hash::Hash for MissingFunctionDeclaration {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.span.hash(state);
for alternative in &self.alternatives {
alternative.identifier_span.hash(state);
alternative.namespace.hash(state);
alternative.parameters.hash(state);
alternative.public.hash(state);
alternative.statements.hash(state);
}
}
}
/// An error that occurs when a function declaration is missing. /// An error that occurs when a function declaration is missing.
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct LuaRuntimeError { pub struct LuaRuntimeError {
/// The span of the code block that caused the error.
pub code_block: Span, pub code_block: Span,
/// The error message of the Lua runtime.
pub error_message: String, pub error_message: String,
} }
@ -155,6 +176,8 @@ impl std::error::Error for LuaRuntimeError {}
#[cfg(feature = "lua")] #[cfg(feature = "lua")]
impl LuaRuntimeError { impl LuaRuntimeError {
/// Creates a new Lua runtime error from an mlua error.
#[must_use]
pub fn from_lua_err(err: &mlua::Error, span: Span) -> Self { pub fn from_lua_err(err: &mlua::Error, span: Span) -> Self {
let err_string = err.to_string(); let err_string = err.to_string();
Self { Self {
@ -168,9 +191,13 @@ impl LuaRuntimeError {
} }
/// An error that occurs when an annotation has an illegal content. /// An error that occurs when an annotation has an illegal content.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Getters)]
pub struct IllegalAnnotationContent { pub struct IllegalAnnotationContent {
/// The span of the annotation.
#[get = "pub"]
pub annotation: Span, pub annotation: Span,
/// The error message.
#[get = "pub"]
pub message: String, pub message: String,
} }
@ -194,9 +221,13 @@ impl Display for IllegalAnnotationContent {
impl std::error::Error for IllegalAnnotationContent {} impl std::error::Error for IllegalAnnotationContent {}
/// An error that occurs when an expression can not evaluate to the wanted type. /// An error that occurs when an expression can not evaluate to the wanted type.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)]
pub struct MismatchedTypes { pub struct MismatchedTypes {
/// The expression that can not evaluate to the wanted type.
#[get = "pub"]
pub expression: Span, pub expression: Span,
/// The expected type.
#[get = "pub"]
pub expected_type: ExpectedType, pub expected_type: ExpectedType,
} }
@ -216,9 +247,13 @@ impl Display for MismatchedTypes {
impl std::error::Error for MismatchedTypes {} impl std::error::Error for MismatchedTypes {}
/// An error that occurs when an expression can not evaluate to the wanted type. /// An error that occurs when an expression can not evaluate to the wanted type.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Getters)]
pub struct FunctionArgumentsNotAllowed { pub struct FunctionArgumentsNotAllowed {
/// The arguments that are not allowed.
#[get = "pub"]
pub arguments: Span, pub arguments: Span,
/// The error message.
#[get = "pub"]
pub message: String, pub message: String,
} }
@ -237,9 +272,13 @@ impl Display for FunctionArgumentsNotAllowed {
impl std::error::Error for FunctionArgumentsNotAllowed {} impl std::error::Error for FunctionArgumentsNotAllowed {}
/// An error that occurs when an expression can not evaluate to the wanted type. /// An error that occurs when an expression can not evaluate to the wanted type.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)]
pub struct AssignmentError { pub struct AssignmentError {
/// The identifier that is assigned to.
#[get = "pub"]
pub identifier: Span, pub identifier: Span,
/// The error message.
#[get = "pub"]
pub message: String, pub message: String,
} }
@ -258,8 +297,10 @@ impl Display for AssignmentError {
impl std::error::Error for AssignmentError {} impl std::error::Error for AssignmentError {}
/// An error that occurs when an unknown identifier is used. /// An error that occurs when an unknown identifier is used.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)]
pub struct UnknownIdentifier { pub struct UnknownIdentifier {
/// The unknown identifier.
#[get = "pub"]
pub identifier: Span, pub identifier: Span,
} }
@ -285,8 +326,10 @@ impl Display for UnknownIdentifier {
impl std::error::Error for UnknownIdentifier {} impl std::error::Error for UnknownIdentifier {}
/// An error that occurs when there is a value expected but none provided. /// An error that occurs when there is a value expected but none provided.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Getters)]
pub struct MissingValue { pub struct MissingValue {
/// The expression that is missing a value.
#[get = "pub"]
pub expression: Span, pub expression: Span,
} }
@ -312,9 +355,13 @@ impl Display for MissingValue {
impl std::error::Error for MissingValue {} impl std::error::Error for MissingValue {}
/// An error that occurs when an indexing operation is not permitted. /// An error that occurs when an indexing operation is not permitted.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Getters)]
pub struct IllegalIndexing { pub struct IllegalIndexing {
/// The reason why the indexing operation is not permitted.
#[get = "pub"]
pub reason: IllegalIndexingReason, pub reason: IllegalIndexingReason,
/// The expression that is the reason for the indexing being illegal.
#[get = "pub"]
pub expression: Span, pub expression: Span,
} }
@ -333,11 +380,24 @@ impl Display for IllegalIndexing {
impl std::error::Error for IllegalIndexing {} impl std::error::Error for IllegalIndexing {}
/// The reason why an indexing operation is not permitted. /// The reason why an indexing operation is not permitted.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IllegalIndexingReason { pub enum IllegalIndexingReason {
/// The expression is not an identifier.
NotIdentifier, NotIdentifier,
InvalidComptimeType { expected: ExpectedType }, /// The expression cannot be indexed.
IndexOutOfBounds { index: usize, length: usize }, NotIndexable,
/// The expression can only be indexed with a specific type that can be evaluated at compile time.
InvalidComptimeType {
/// The expected type.
expected: ExpectedType,
},
/// The index is out of bounds.
IndexOutOfBounds {
/// The index that is out of bounds.
index: usize,
/// The length indexed object.
length: usize,
},
} }
impl Display for IllegalIndexingReason { impl Display for IllegalIndexingReason {
@ -346,6 +406,9 @@ impl Display for IllegalIndexingReason {
Self::NotIdentifier => { Self::NotIdentifier => {
write!(f, "The expression is not an identifier.") write!(f, "The expression is not an identifier.")
} }
Self::NotIndexable => {
write!(f, "The expression cannot be indexed.")
}
Self::InvalidComptimeType { expected } => { Self::InvalidComptimeType { expected } => {
write!( write!(
f, f,
@ -361,3 +424,57 @@ impl Display for IllegalIndexingReason {
} }
} }
} }
/// An error that occurs when an indexing operation is not permitted.
#[derive(Debug, Clone, PartialEq, Eq, Getters)]
pub struct InvalidArgument {
/// The span of the argument.
#[get = "pub"]
pub span: Span,
/// The reason why the argument is invalid.
#[get = "pub"]
pub reason: String,
}
impl Display for InvalidArgument {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", Message::new(Severity::Error, &self.reason))?;
write!(
f,
"\n{}",
SourceCodeDisplay::new(&self.span, Option::<u8>::None)
)
}
}
impl std::error::Error for InvalidArgument {}
/// An error that occurs when an indexing operation is not permitted.
#[derive(Debug, Clone, PartialEq, Eq, Getters)]
pub struct NotComptime {
/// The expression that cannot be evaluated at compile time.
#[get = "pub"]
pub expression: Span,
}
impl Display for NotComptime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
Message::new(
Severity::Error,
"The expression cannot be evaluated at compile time but is required to."
)
)?;
write!(
f,
"\n{}",
SourceCodeDisplay::new(&self.expression, Option::<u8>::None)
)
}
}
impl std::error::Error for NotComptime {}

View File

@ -1,15 +1,8 @@
//! The expression transpiler. //! The expression transpiler.
use std::{fmt::Display, string::ToString, sync::Arc}; use std::{fmt::Display, string::ToString};
use super::{util::MacroString, Scope, VariableData}; use super::util::MacroString;
use crate::{
base::{self, Handler, VoidHandler},
lexical::token::MacroStringLiteralPart,
syntax::syntax_tree::expression::{
Binary, BinaryOperator, Expression, PrefixOperator, Primary,
},
};
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
use enum_as_inner::EnumAsInner; use enum_as_inner::EnumAsInner;
@ -19,17 +12,25 @@ use shulkerbox::prelude::{Command, Condition, Execute};
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
use super::{ use super::{
error::{IllegalIndexing, IllegalIndexingReason, MismatchedTypes, UnknownIdentifier}, error::{
TranspileResult, Transpiler, IllegalIndexing, IllegalIndexingReason, MismatchedTypes, NotComptime, UnknownIdentifier,
},
Scope, TranspileResult, Transpiler, VariableData,
}; };
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
use crate::{ use crate::{
base::source_file::SourceElement, base::{self, source_file::SourceElement, Handler, VoidHandler},
lexical::token::MacroStringLiteralPart,
syntax::syntax_tree::expression::{
Binary, BinaryOperator, Expression, PrefixOperator, Primary,
},
transpile::{ transpile::{
error::{FunctionArgumentsNotAllowed, MissingValue}, error::{FunctionArgumentsNotAllowed, MissingValue},
TranspileError, TranspileError,
}, },
}; };
#[cfg(feature = "shulkerbox")]
use std::sync::Arc;
/// Compile-time evaluated value /// Compile-time evaluated value
#[allow(missing_docs)] #[allow(missing_docs)]
@ -85,7 +86,7 @@ impl Display for ValueType {
} }
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ExpectedType { pub enum ExpectedType {
Boolean, Boolean,
Integer, Integer,
@ -227,6 +228,7 @@ pub enum ExtendedCondition {
Comptime(bool), Comptime(bool),
} }
#[cfg(feature = "shulkerbox")]
impl Expression { impl Expression {
/// Returns whether the expression can yield a certain type. /// Returns whether the expression can yield a certain type.
#[must_use] #[must_use]
@ -238,12 +240,14 @@ impl Expression {
} }
/// Evaluate at compile-time. /// Evaluate at compile-time.
#[must_use] ///
/// # Errors
/// - If the expression is not compile-time evaluatable.
pub fn comptime_eval( pub fn comptime_eval(
&self, &self,
scope: &Arc<Scope>, scope: &Arc<Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> Option<ComptimeValue> { ) -> Result<ComptimeValue, NotComptime> {
match self { match self {
Self::Primary(primary) => primary.comptime_eval(scope, handler), Self::Primary(primary) => primary.comptime_eval(scope, handler),
Self::Binary(binary) => binary.comptime_eval(scope, handler), Self::Binary(binary) => binary.comptime_eval(scope, handler),
@ -251,6 +255,7 @@ impl Expression {
} }
} }
#[cfg(feature = "shulkerbox")]
impl Primary { impl Primary {
/// Returns whether the primary can yield a certain type. /// Returns whether the primary can yield a certain type.
#[must_use] #[must_use]
@ -332,19 +337,40 @@ impl Primary {
} }
/// Evaluate at compile-time. /// Evaluate at compile-time.
#[must_use] ///
/// # Errors
/// - If the expression is not compile-time evaluatable.
pub fn comptime_eval( pub fn comptime_eval(
&self, &self,
scope: &Arc<Scope>, scope: &Arc<Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> Option<ComptimeValue> { ) -> Result<ComptimeValue, NotComptime> {
match self { match self {
Self::Boolean(boolean) => Some(ComptimeValue::Boolean(boolean.value())), Self::Boolean(boolean) => Ok(ComptimeValue::Boolean(boolean.value())),
Self::Integer(int) => Some(ComptimeValue::Integer(int.as_i64())), Self::Integer(int) => Ok(ComptimeValue::Integer(int.as_i64())),
Self::StringLiteral(string_literal) => Some(ComptimeValue::String( Self::StringLiteral(string_literal) => Ok(ComptimeValue::String(
string_literal.str_content().to_string(), string_literal.str_content().to_string(),
)), )),
Self::Identifier(_) | Self::FunctionCall(_) | Self::Indexed(_) => None, Self::Identifier(ident) => scope.get_variable(ident.span.str()).map_or_else(
|| {
Err(NotComptime {
expression: self.span(),
})
},
|var| match var.as_ref() {
VariableData::ComptimeValue { value } => {
value.read().unwrap().clone().ok_or_else(|| NotComptime {
expression: ident.span.clone(),
})
}
_ => Err(NotComptime {
expression: self.span(),
}),
},
),
Self::FunctionCall(_) | Self::Indexed(_) => Err(NotComptime {
expression: self.span(),
}),
Self::Parenthesized(parenthesized) => { Self::Parenthesized(parenthesized) => {
parenthesized.expression().comptime_eval(scope, handler) parenthesized.expression().comptime_eval(scope, handler)
} }
@ -354,12 +380,14 @@ impl Primary {
.comptime_eval(scope, handler) .comptime_eval(scope, handler)
.and_then(|val| match (prefix.operator(), val) { .and_then(|val| match (prefix.operator(), val) {
(PrefixOperator::LogicalNot(_), ComptimeValue::Boolean(boolean)) => { (PrefixOperator::LogicalNot(_), ComptimeValue::Boolean(boolean)) => {
Some(ComptimeValue::Boolean(!boolean)) Ok(ComptimeValue::Boolean(!boolean))
} }
(PrefixOperator::Negate(_), ComptimeValue::Integer(int)) => { (PrefixOperator::Negate(_), ComptimeValue::Integer(int)) => {
Some(ComptimeValue::Integer(-int)) Ok(ComptimeValue::Integer(-int))
} }
_ => None, _ => Err(NotComptime {
expression: prefix.span(),
}),
}) })
} }
Self::Lua(lua) => lua Self::Lua(lua) => lua
@ -367,25 +395,28 @@ impl Primary {
.inspect_err(|err| { .inspect_err(|err| {
handler.receive(err.clone()); handler.receive(err.clone());
}) })
.ok() .map_err(|_| NotComptime {
.flatten(), expression: lua.span(),
})
.and_then(|val| val),
Self::MacroStringLiteral(macro_string_literal) => { Self::MacroStringLiteral(macro_string_literal) => {
if macro_string_literal if macro_string_literal
.parts() .parts()
.iter() .iter()
.any(|part| matches!(part, MacroStringLiteralPart::MacroUsage { .. })) .any(|part| matches!(part, MacroStringLiteralPart::MacroUsage { .. }))
{ {
Some(ComptimeValue::MacroString( Ok(ComptimeValue::MacroString(
macro_string_literal.clone().into(), macro_string_literal.clone().into(),
)) ))
} else { } else {
Some(ComptimeValue::String(macro_string_literal.str_content())) Ok(ComptimeValue::String(macro_string_literal.str_content()))
} }
} }
} }
} }
} }
#[cfg(feature = "shulkerbox")]
impl Binary { impl Binary {
/// Returns whether the binary can yield a certain type. /// Returns whether the binary can yield a certain type.
#[must_use] #[must_use]
@ -428,12 +459,14 @@ impl Binary {
} }
/// Evaluate at compile-time. /// Evaluate at compile-time.
#[must_use] ///
/// # Errors
/// - If the expression is not compile-time evaluatable.
pub fn comptime_eval( pub fn comptime_eval(
&self, &self,
scope: &Arc<Scope>, scope: &Arc<Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> Option<ComptimeValue> { ) -> Result<ComptimeValue, NotComptime> {
let left = self.left_operand().comptime_eval(scope, handler)?; let left = self.left_operand().comptime_eval(scope, handler)?;
let right = self.right_operand().comptime_eval(scope, handler)?; let right = self.right_operand().comptime_eval(scope, handler)?;
@ -447,7 +480,7 @@ impl Binary {
.right_operand() .right_operand()
.can_yield_type(ValueType::Boolean, scope) => .can_yield_type(ValueType::Boolean, scope) =>
{ {
Some(ComptimeValue::Boolean(true)) Ok(ComptimeValue::Boolean(true))
} }
(ComptimeValue::Boolean(false), _) | (_, ComptimeValue::Boolean(false)) (ComptimeValue::Boolean(false), _) | (_, ComptimeValue::Boolean(false))
if matches!(self.operator(), BinaryOperator::LogicalAnd(..)) if matches!(self.operator(), BinaryOperator::LogicalAnd(..))
@ -458,15 +491,17 @@ impl Binary {
.right_operand() .right_operand()
.can_yield_type(ValueType::Boolean, scope) => .can_yield_type(ValueType::Boolean, scope) =>
{ {
Some(ComptimeValue::Boolean(false)) Ok(ComptimeValue::Boolean(false))
} }
(ComptimeValue::Boolean(left), ComptimeValue::Boolean(right)) => { (ComptimeValue::Boolean(left), ComptimeValue::Boolean(right)) => {
match self.operator() { match self.operator() {
BinaryOperator::Equal(..) => Some(ComptimeValue::Boolean(left == right)), BinaryOperator::Equal(..) => Ok(ComptimeValue::Boolean(left == right)),
BinaryOperator::NotEqual(..) => Some(ComptimeValue::Boolean(left != right)), BinaryOperator::NotEqual(..) => Ok(ComptimeValue::Boolean(left != right)),
BinaryOperator::LogicalAnd(..) => Some(ComptimeValue::Boolean(left && right)), BinaryOperator::LogicalAnd(..) => Ok(ComptimeValue::Boolean(left && right)),
BinaryOperator::LogicalOr(..) => Some(ComptimeValue::Boolean(left || right)), BinaryOperator::LogicalOr(..) => Ok(ComptimeValue::Boolean(left || right)),
_ => None, _ => Err(NotComptime {
expression: self.span(),
}),
} }
} }
(ComptimeValue::Integer(left), ComptimeValue::Integer(right)) => { (ComptimeValue::Integer(left), ComptimeValue::Integer(right)) => {
@ -496,14 +531,37 @@ impl Binary {
} }
_ => None, _ => None,
} }
.ok_or_else(|| NotComptime {
expression: self.span(),
})
} }
(ComptimeValue::String(left), ComptimeValue::String(right)) => match self.operator() { (ComptimeValue::String(left), ComptimeValue::String(right)) => match self.operator() {
BinaryOperator::Add(..) => Some(ComptimeValue::String(left + &right)), BinaryOperator::Add(..) => Ok(ComptimeValue::String(left + &right)),
BinaryOperator::Equal(..) => Some(ComptimeValue::Boolean(left == right)), BinaryOperator::Equal(..) => Ok(ComptimeValue::Boolean(left == right)),
BinaryOperator::NotEqual(..) => Some(ComptimeValue::Boolean(left != right)), BinaryOperator::NotEqual(..) => Ok(ComptimeValue::Boolean(left != right)),
_ => None, _ => Err(NotComptime {
expression: self.span(),
}),
}, },
_ => None, // TODO: also allow macro strings
(
left @ ComptimeValue::String(_),
right @ (ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)),
)
| (
left @ (ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)),
right @ ComptimeValue::String(_),
) => match self.operator() {
BinaryOperator::Add(_) => Ok(ComptimeValue::String(
left.to_string_no_macro().unwrap() + &right.to_string_no_macro().unwrap(),
)),
_ => Err(NotComptime {
expression: self.span(),
}),
},
_ => Err(NotComptime {
expression: self.span(),
}),
} }
} }
} }
@ -639,7 +697,7 @@ impl Transpiler {
Primary::Lua(lua) => Primary::Lua(lua) =>
{ {
#[expect(clippy::option_if_let_else)] #[expect(clippy::option_if_let_else)]
if let Some(value) = lua.eval_comptime(scope, handler)? { if let Ok(value) = lua.eval_comptime(scope, handler)? {
self.store_comptime_value(&value, target, lua, handler) self.store_comptime_value(&value, target, lua, handler)
} else { } else {
let err = TranspileError::MissingValue(MissingValue { let err = TranspileError::MissingValue(MissingValue {
@ -801,7 +859,7 @@ impl Transpiler {
if let Some(variable) = variable.as_deref() { if let Some(variable) = variable.as_deref() {
let from = match variable { let from = match variable {
VariableData::Scoreboard { objective } => { VariableData::Scoreboard { objective } => {
if let Some(ComptimeValue::String(target)) = if let Ok(ComptimeValue::String(target)) =
indexed.index().comptime_eval(scope, handler) indexed.index().comptime_eval(scope, handler)
{ {
Ok(DataLocation::ScoreboardValue { Ok(DataLocation::ScoreboardValue {
@ -820,7 +878,7 @@ impl Transpiler {
} }
} }
VariableData::ScoreboardArray { objective, targets } => { VariableData::ScoreboardArray { objective, targets } => {
if let Some(ComptimeValue::Integer(index)) = if let Ok(ComptimeValue::Integer(index)) =
indexed.index().comptime_eval(scope, handler) indexed.index().comptime_eval(scope, handler)
{ {
if let Some(target) = usize::try_from(index) if let Some(target) = usize::try_from(index)
@ -858,7 +916,7 @@ impl Transpiler {
storage_name, storage_name,
paths, paths,
} => { } => {
if let Some(ComptimeValue::Integer(index)) = if let Ok(ComptimeValue::Integer(index)) =
indexed.index().comptime_eval(scope, handler) indexed.index().comptime_eval(scope, handler)
{ {
if let Some(path) = usize::try_from(index) if let Some(path) = usize::try_from(index)
@ -915,14 +973,14 @@ impl Transpiler {
} }
} }
fn transpile_binary_expression( pub(super) fn transpile_binary_expression(
&mut self, &mut self,
binary: &Binary, binary: &Binary,
target: &DataLocation, target: &DataLocation,
scope: &Arc<super::Scope>, scope: &Arc<super::Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Vec<Command>> { ) -> TranspileResult<Vec<Command>> {
if let Some(value) = binary.comptime_eval(scope, handler) { if let Ok(value) = binary.comptime_eval(scope, handler) {
self.store_comptime_value(&value, target, binary, handler) self.store_comptime_value(&value, target, binary, handler)
} else { } else {
match binary.operator() { match binary.operator() {
@ -1085,7 +1143,7 @@ impl Transpiler {
storage_name, storage_name,
paths, paths,
} => { } => {
if let Some(ComptimeValue::Integer(index)) = if let Ok(ComptimeValue::Integer(index)) =
indexed.index().comptime_eval(scope, handler) indexed.index().comptime_eval(scope, handler)
{ {
if let Some(path) = usize::try_from(index) if let Some(path) = usize::try_from(index)
@ -1166,15 +1224,15 @@ impl Transpiler {
} }
}, },
Primary::Lua(lua) => match lua.eval_comptime(scope, handler)? { Primary::Lua(lua) => match lua.eval_comptime(scope, handler)? {
Some(ComptimeValue::String(value)) => Ok(( Ok(ComptimeValue::String(value)) => Ok((
Vec::new(), Vec::new(),
ExtendedCondition::Runtime(Condition::Atom(value.into())), ExtendedCondition::Runtime(Condition::Atom(value.into())),
)), )),
Some(ComptimeValue::MacroString(value)) => Ok(( Ok(ComptimeValue::MacroString(value)) => Ok((
Vec::new(), Vec::new(),
ExtendedCondition::Runtime(Condition::Atom(value.into())), ExtendedCondition::Runtime(Condition::Atom(value.into())),
)), )),
Some(ComptimeValue::Boolean(boolean)) => { Ok(ComptimeValue::Boolean(boolean)) => {
Ok((Vec::new(), ExtendedCondition::Comptime(boolean))) Ok((Vec::new(), ExtendedCondition::Comptime(boolean)))
} }
_ => { _ => {

View File

@ -102,6 +102,7 @@ impl Transpiler {
TranspileAnnotationValue::None => Ok(identifier_span.str().to_string()), TranspileAnnotationValue::None => Ok(identifier_span.str().to_string()),
TranspileAnnotationValue::Expression(expr) => expr TranspileAnnotationValue::Expression(expr) => expr
.comptime_eval(scope, handler) .comptime_eval(scope, handler)
.ok()
.and_then(|val| val.to_string_no_macro()) .and_then(|val| val.to_string_no_macro())
.ok_or_else(|| { .ok_or_else(|| {
let err = TranspileError::IllegalAnnotationContent( let err = TranspileError::IllegalAnnotationContent(
@ -293,13 +294,10 @@ impl Transpiler {
let value = match expression { let value = match expression {
Expression::Primary(Primary::Lua(lua)) => { Expression::Primary(Primary::Lua(lua)) => {
lua.eval_comptime(scope, handler).and_then(|val| match val { lua.eval_comptime(scope, handler).and_then(|val| match val {
Some(ComptimeValue::MacroString(s)) => Ok(Parameter::Static(s)), Ok(ComptimeValue::MacroString(s)) => Ok(Parameter::Static(s)),
Some(val) => Ok(Parameter::Static(val.to_macro_string())), Ok(val) => Ok(Parameter::Static(val.to_macro_string())),
None => { Err(err) => {
let err = TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::NotComptime(err);
expression: expression.span(),
expected_type: ExpectedType::String,
});
handler.receive(err.clone()); handler.receive(err.clone());
Err(err) Err(err)
} }
@ -361,7 +359,18 @@ impl Transpiler {
path: std::mem::take(&mut temp_path[0]), path: std::mem::take(&mut temp_path[0]),
}) })
} }
_ => todo!("other variable types"), _ => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: expression.span(),
expected_type: ExpectedType::AnyOf(vec![
ExpectedType::Integer,
ExpectedType::Boolean,
ExpectedType::String,
]),
});
handler.receive(err.clone());
Err(err)
}
} }
} }
Expression::Primary( Expression::Primary(

View File

@ -5,6 +5,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use cfg_if::cfg_if;
use shulkerbox::prelude::{Command, Execute}; use shulkerbox::prelude::{Command, Execute};
use serde_json::{json, Value as JsonValue}; use serde_json::{json, Value as JsonValue};
@ -15,8 +16,8 @@ use crate::{
semantic::error::{InvalidFunctionArguments, UnexpectedExpression}, semantic::error::{InvalidFunctionArguments, UnexpectedExpression},
syntax::syntax_tree::expression::{Expression, FunctionCall, Primary}, syntax::syntax_tree::expression::{Expression, FunctionCall, Primary},
transpile::{ transpile::{
error::{IllegalIndexing, IllegalIndexingReason, LuaRuntimeError, UnknownIdentifier}, error::{IllegalIndexing, IllegalIndexingReason, UnknownIdentifier},
expression::{ComptimeValue, DataLocation, StorageType}, expression::{ComptimeValue, DataLocation, ExpectedType, StorageType},
util::MacroString, util::MacroString,
TranspileError, TranspileError,
}, },
@ -182,17 +183,17 @@ fn print_function(
let args = get_args_assert_in_range(call, 1..=2)?; let args = get_args_assert_in_range(call, 1..=2)?;
let first = args.first().expect("checked range"); let first = args.first().expect("checked range");
let (target, message_expression) = args.get(1).map_or_else( let (target, message_expression) = if let Some(second) = args.get(1) {
|| ("@a".into(), first), (
|second| { first
( .comptime_eval(scope, &VoidHandler)
first .map(|val| val.to_macro_string())
.comptime_eval(scope, &VoidHandler) .map_err(TranspileError::NotComptime)?,
.map_or_else(|| "@a".into(), |val| val.to_macro_string()), second,
second, )
) } else {
}, ("@a".into(), first)
); };
let mut contains_macro = matches!(target, MacroString::MacroString(_)); let mut contains_macro = matches!(target, MacroString::MacroString(_));
@ -210,17 +211,24 @@ fn print_function(
Vec::new(), Vec::new(),
vec![JsonValue::String(string.str_content().to_string())], vec![JsonValue::String(string.str_content().to_string())],
)), )),
#[cfg_attr(not(feature = "lua"), expect(unused_variables))]
Primary::Lua(lua) => { Primary::Lua(lua) => {
let (ret, _lua) = lua.eval(scope, &VoidHandler)?; cfg_if! {
Ok(( if #[cfg(feature = "lua")] {
Vec::new(), let (ret, _lua) = lua.eval(scope, &VoidHandler)?;
vec![JsonValue::String(ret.to_string().map_err(|err| { Ok((
TranspileError::LuaRuntimeError(LuaRuntimeError::from_lua_err( Vec::new(),
&err, vec![JsonValue::String(ret.to_string().map_err(|err| {
lua.span(), TranspileError::LuaRuntimeError(super::error::LuaRuntimeError::from_lua_err(
&err,
lua.span(),
))
})?)],
)) ))
})?)], } else {
)) Err(TranspileError::LuaDisabled)
}
}
} }
Primary::Identifier(ident) => { Primary::Identifier(ident) => {
let (cur_contains_macro, cmd, part) = let (cur_contains_macro, cmd, part) =
@ -232,7 +240,7 @@ fn print_function(
Primary::Identifier(ident) => { Primary::Identifier(ident) => {
match scope.get_variable(ident.span.str()).as_deref() { match scope.get_variable(ident.span.str()).as_deref() {
Some(VariableData::Scoreboard { objective }) => { Some(VariableData::Scoreboard { objective }) => {
if let Some(ComptimeValue::String(index)) = if let Ok(ComptimeValue::String(index)) =
indexed.index().comptime_eval(scope, &VoidHandler) indexed.index().comptime_eval(scope, &VoidHandler)
{ {
let (cmd, value) = get_data_location( let (cmd, value) = get_data_location(
@ -244,11 +252,17 @@ fn print_function(
); );
Ok((cmd.into_iter().collect(), vec![value])) Ok((cmd.into_iter().collect(), vec![value]))
} else { } else {
todo!("allow macro string, but throw error when index is not constant string") // TODO: allow macro string, but throw error when index is not constant string
Err(TranspileError::IllegalIndexing(IllegalIndexing {
reason: IllegalIndexingReason::InvalidComptimeType {
expected: ExpectedType::String,
},
expression: indexed.index().span(),
}))
} }
} }
Some(VariableData::ScoreboardArray { objective, targets }) => { Some(VariableData::ScoreboardArray { objective, targets }) => {
if let Some(ComptimeValue::Integer(index)) = if let Ok(ComptimeValue::Integer(index)) =
indexed.index().comptime_eval(scope, &VoidHandler) indexed.index().comptime_eval(scope, &VoidHandler)
{ {
#[expect(clippy::option_if_let_else)] #[expect(clippy::option_if_let_else)]
@ -274,14 +288,19 @@ fn print_function(
})) }))
} }
} else { } else {
todo!("throw error when index is not constant integer") Err(TranspileError::IllegalIndexing(IllegalIndexing {
reason: IllegalIndexingReason::InvalidComptimeType {
expected: ExpectedType::Integer,
},
expression: indexed.index().span(),
}))
} }
} }
Some(VariableData::BooleanStorageArray { Some(VariableData::BooleanStorageArray {
storage_name, storage_name,
paths, paths,
}) => { }) => {
if let Some(ComptimeValue::Integer(index)) = if let Ok(ComptimeValue::Integer(index)) =
indexed.index().comptime_eval(scope, &VoidHandler) indexed.index().comptime_eval(scope, &VoidHandler)
{ {
#[expect(clippy::option_if_let_else)] #[expect(clippy::option_if_let_else)]
@ -308,10 +327,18 @@ fn print_function(
})) }))
} }
} else { } else {
todo!("throw error when index is not constant integer") Err(TranspileError::IllegalIndexing(IllegalIndexing {
reason: IllegalIndexingReason::InvalidComptimeType {
expected: ExpectedType::Integer,
},
expression: indexed.index().span(),
}))
} }
} }
_ => todo!("catch illegal indexing"), _ => Err(TranspileError::IllegalIndexing(IllegalIndexing {
reason: IllegalIndexingReason::NotIndexable,
expression: indexed.object().span(),
})),
} }
} }
_ => Err(TranspileError::IllegalIndexing(IllegalIndexing { _ => Err(TranspileError::IllegalIndexing(IllegalIndexing {
@ -339,9 +366,43 @@ fn print_function(
Ok((cmds, parts)) Ok((cmds, parts))
} }
_ => todo!("print_function Primary"), primary => {
let (storage_name, mut storage_paths) = transpiler.get_temp_storage_locations(1);
let location = DataLocation::Storage {
storage_name,
path: std::mem::take(&mut storage_paths[0]),
r#type: StorageType::Int,
};
let cmds = transpiler.transpile_primary_expression(
primary,
&location,
scope,
&VoidHandler,
)?;
let (cmd, part) = get_data_location(&location, transpiler);
Ok((
cmds.into_iter().chain(cmd.into_iter()).collect(),
vec![part],
))
}
}, },
Expression::Binary(_) => todo!("print_function Binary"), Expression::Binary(binary) => {
let (storage_name, mut storage_paths) = transpiler.get_temp_storage_locations(1);
let location = DataLocation::Storage {
storage_name,
path: std::mem::take(&mut storage_paths[0]),
r#type: StorageType::Int,
};
let cmds =
transpiler.transpile_binary_expression(binary, &location, scope, &VoidHandler)?;
let (cmd, part) = get_data_location(&location, transpiler);
Ok((
cmds.into_iter().chain(cmd.into_iter()).collect(),
vec![part],
))
}
}?; }?;
// TODO: prepend prefix with datapack name to parts and remove following // TODO: prepend prefix with datapack name to parts and remove following

View File

@ -1,6 +1,6 @@
//! Executes the Lua code and returns the resulting command. //! Executes the Lua code and returns the resulting command.
#[cfg(feature = "lua")] #[cfg(all(feature = "lua", feature = "shulkerbox"))]
mod enabled { mod enabled {
use std::sync::Arc; use std::sync::Arc;
@ -12,8 +12,8 @@ mod enabled {
syntax::syntax_tree::expression::LuaCode, syntax::syntax_tree::expression::LuaCode,
transpile::{ transpile::{
error::{ error::{
LuaRuntimeError, MismatchedTypes, TranspileError, TranspileResult, InvalidArgument, LuaRuntimeError, MismatchedTypes, NotComptime, TranspileError,
UnknownIdentifier, TranspileResult, UnknownIdentifier,
}, },
expression::{ComptimeValue, ExpectedType}, expression::{ComptimeValue, ExpectedType},
Scope, VariableData, Scope, VariableData,
@ -86,11 +86,15 @@ mod enabled {
&self, &self,
scope: &Arc<Scope>, scope: &Arc<Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<Option<ComptimeValue>> { ) -> TranspileResult<Result<ComptimeValue, NotComptime>> {
// required to keep the lua instance alive // required to keep the lua instance alive
let (lua_result, _lua) = self.eval(scope, handler)?; let (lua_result, _lua) = self.eval(scope, handler)?;
self.handle_lua_result(lua_result, handler) self.handle_lua_result(lua_result, handler).map(|res| {
res.ok_or_else(|| NotComptime {
expression: self.span(),
})
})
} }
fn add_globals(&self, lua: &Lua, scope: &Arc<Scope>) -> TranspileResult<()> { fn add_globals(&self, lua: &Lua, scope: &Arc<Scope>) -> TranspileResult<()> {
@ -262,8 +266,28 @@ mod enabled {
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?; .map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
Value::Table(table) Value::Table(table)
} }
Some(VariableData::ComptimeValue { value }) => {
let value = value.read().unwrap();
match &*value {
Some(ComptimeValue::Boolean(b)) => Value::Boolean(*b),
Some(ComptimeValue::Integer(i)) => Value::Integer(*i),
Some(ComptimeValue::String(s)) => Value::String(
lua.create_string(s)
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?,
),
Some(ComptimeValue::MacroString(s)) => Value::String(
lua.create_string(s.to_string())
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?,
),
None => Value::Nil,
}
}
Some(VariableData::Function { .. } | VariableData::InternalFunction { .. }) => { Some(VariableData::Function { .. } | VariableData::InternalFunction { .. }) => {
todo!("(internal) functions are not supported yet"); // TODO: add support for functions
return Err(TranspileError::InvalidArgument(InvalidArgument {
reason: "functions cannot be passed to Lua".to_string(),
span: identifier.span(),
}));
} }
None => { None => {
return Err(TranspileError::UnknownIdentifier(UnknownIdentifier { return Err(TranspileError::UnknownIdentifier(UnknownIdentifier {
@ -374,7 +398,7 @@ mod enabled {
} }
} }
#[cfg(not(feature = "lua"))] #[cfg(all(not(feature = "lua"), feature = "shulkerbox"))]
mod disabled { mod disabled {
use std::sync::Arc; use std::sync::Arc;
@ -397,7 +421,7 @@ mod disabled {
&self, &self,
scope: &Arc<Scope>, scope: &Arc<Scope>,
handler: &impl Handler<base::Error>, handler: &impl Handler<base::Error>,
) -> TranspileResult<()> { ) -> TranspileResult<((), ())> {
let _ = scope; let _ = scope;
handler.receive(TranspileError::LuaDisabled); handler.receive(TranspileError::LuaDisabled);
tracing::error!("Lua code evaluation is disabled"); tracing::error!("Lua code evaluation is disabled");

View File

@ -16,7 +16,7 @@ use crate::{
#[doc(hidden)] #[doc(hidden)]
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
pub mod conversions; pub mod conversions;
mod error; pub mod error;
pub mod expression; pub mod expression;
@ -38,9 +38,12 @@ pub mod internal_functions;
#[doc(hidden)] #[doc(hidden)]
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
pub mod function; pub mod function;
#[doc(inline)]
#[cfg(feature = "shulkerbox")]
pub use function::TranspiledFunctionArguments; pub use function::TranspiledFunctionArguments;
mod variables; mod variables;
#[cfg(feature = "shulkerbox")]
pub use variables::{Scope, VariableData}; pub use variables::{Scope, VariableData};
pub mod util; pub mod util;
@ -58,7 +61,7 @@ pub struct FunctionData {
/// Possible values for an annotation. /// Possible values for an annotation.
#[expect(clippy::module_name_repetitions)] #[expect(clippy::module_name_repetitions)]
#[derive(Debug, Clone, PartialEq, Eq, EnumIs)] #[derive(Debug, Clone, PartialEq, Eq, Hash, EnumIs)]
pub enum TranspileAnnotationValue { pub enum TranspileAnnotationValue {
/// No value. /// No value.
None, None,

View File

@ -25,7 +25,7 @@ use crate::{
}; };
use super::{ use super::{
error::{MismatchedTypes, TranspileError, TranspileResult}, error::{MismatchedTypes, MissingValue, TranspileError, TranspileResult, UnknownIdentifier},
expression::{ComptimeValue, ExpectedType, ExtendedCondition}, expression::{ComptimeValue, ExpectedType, ExtendedCondition},
variables::{Scope, TranspileAssignmentTarget, VariableData}, variables::{Scope, TranspileAssignmentTarget, VariableData},
FunctionData, TranspileAnnotationValue, TranspiledFunctionArguments, FunctionData, TranspileAnnotationValue, TranspiledFunctionArguments,
@ -342,11 +342,46 @@ 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::Identifier(ident)) => {
match scope.get_variable(ident.span.str()).as_deref() {
Some(VariableData::ComptimeValue { value }) => {
value.read().unwrap().as_ref().map_or_else(
|| {
let error = TranspileError::MissingValue(MissingValue {
expression: ident.span.clone(),
});
handler.receive(error.clone());
Err(error)
},
|val| {
let cmd = val.to_string_no_macro().map_or_else(
|| Command::UsesMacro(val.to_macro_string().into()),
Command::Raw,
);
Ok(vec![cmd])
},
)
}
Some(_) => {
let error = TranspileError::UnexpectedExpression(UnexpectedExpression(
expression.clone(),
));
handler.receive(error.clone());
Err(error)
}
None => {
let error = TranspileError::UnknownIdentifier(UnknownIdentifier {
identifier: ident.span.clone(),
});
handler.receive(error.clone());
Err(error)
}
}
}
expression @ Expression::Primary( expression @ Expression::Primary(
Primary::Integer(_) Primary::Integer(_)
| Primary::Boolean(_) | Primary::Boolean(_)
| Primary::Prefix(_) | Primary::Prefix(_)
| Primary::Identifier(_)
| Primary::Indexed(_), | Primary::Indexed(_),
) => { ) => {
let error = let error =
@ -361,9 +396,9 @@ impl Transpiler {
Ok(vec![Command::UsesMacro(string.into())]) Ok(vec![Command::UsesMacro(string.into())])
} }
Expression::Primary(Primary::Lua(code)) => match code.eval_comptime(scope, handler)? { Expression::Primary(Primary::Lua(code)) => match code.eval_comptime(scope, handler)? {
Some(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]), Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
Some(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]), Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]),
Some(ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)) => { Ok(ComptimeValue::Boolean(_) | ComptimeValue::Integer(_)) => {
let err = TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
expected_type: ExpectedType::String, expected_type: ExpectedType::String,
expression: code.span(), expression: code.span(),
@ -371,7 +406,13 @@ impl Transpiler {
handler.receive(err.clone()); handler.receive(err.clone());
Err(err) Err(err)
} }
None => Ok(Vec::new()), Err(_) => {
let err = TranspileError::MissingValue(MissingValue {
expression: code.span(),
});
handler.receive(err.clone());
Err(err)
}
}, },
Expression::Primary(Primary::Parenthesized(parenthesized)) => self Expression::Primary(Primary::Parenthesized(parenthesized)) => self
@ -382,8 +423,8 @@ impl Transpiler {
handler, handler,
), ),
Expression::Binary(bin) => match bin.comptime_eval(scope, handler) { Expression::Binary(bin) => match bin.comptime_eval(scope, handler) {
Some(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]), Ok(ComptimeValue::String(cmd)) => Ok(vec![Command::Raw(cmd)]),
Some(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]), Ok(ComptimeValue::MacroString(cmd)) => Ok(vec![Command::UsesMacro(cmd.into())]),
_ => { _ => {
let err = TranspileError::MismatchedTypes(MismatchedTypes { let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: bin.span(), expression: bin.span(),
@ -621,7 +662,7 @@ impl Transpiler {
} }
}); });
if let Some(ComptimeValue::Boolean(value)) = cond_expression.comptime_eval(scope, handler) { if let Ok(ComptimeValue::Boolean(value)) = cond_expression.comptime_eval(scope, handler) {
if value { if value {
Ok(Some((Vec::new(), then))) Ok(Some((Vec::new(), then)))
} else { } else {

View File

@ -30,17 +30,17 @@ use crate::{
use super::{ use super::{
error::{ error::{
AssignmentError, IllegalAnnotationContent, IllegalIndexing, IllegalIndexingReason, AssignmentError, IllegalAnnotationContent, IllegalIndexing, IllegalIndexingReason,
MismatchedTypes, MismatchedTypes, NotComptime,
}, },
expression::{ComptimeValue, DataLocation, ExpectedType, StorageType}, expression::{ComptimeValue, DataLocation, ExpectedType, StorageType},
internal_functions::InternalFunction,
FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult, FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult,
}; };
#[cfg(feature = "shulkerbox")] #[cfg(feature = "shulkerbox")]
use super::Transpiler; use super::{internal_functions::InternalFunction, Transpiler};
/// Stores the data required to access a variable. /// Stores the data required to access a variable.
#[cfg(feature = "shulkerbox")]
#[derive(Debug, Clone, EnumAsInner)] #[derive(Debug, Clone, EnumAsInner)]
pub enum VariableData { pub enum VariableData {
/// A function. /// A function.
@ -100,6 +100,11 @@ pub enum VariableData {
/// The implementation /// The implementation
implementation: InternalFunction, implementation: InternalFunction,
}, },
/// Compiler internal variable.
ComptimeValue {
/// The value.
value: Arc<RwLock<Option<ComptimeValue>>>,
},
} }
#[derive(Debug, Clone, Copy, EnumAsInner)] #[derive(Debug, Clone, Copy, EnumAsInner)]
@ -122,6 +127,7 @@ impl<'a> From<&'a AssignmentDestination> for TranspileAssignmentTarget<'a> {
} }
/// A scope that stores variables. /// A scope that stores variables.
#[cfg(feature = "shulkerbox")]
#[derive(Default)] #[derive(Default)]
pub struct Scope<'a> { pub struct Scope<'a> {
/// Parent scope where variables are inherited from. /// Parent scope where variables are inherited from.
@ -132,6 +138,7 @@ pub struct Scope<'a> {
shadowed: RwLock<HashMap<String, usize>>, shadowed: RwLock<HashMap<String, usize>>,
} }
#[cfg(feature = "shulkerbox")]
impl<'a> Scope<'a> { impl<'a> Scope<'a> {
/// Creates a new scope. /// Creates a new scope.
#[must_use] #[must_use]
@ -226,6 +233,7 @@ impl<'a> Scope<'a> {
} }
} }
#[cfg(feature = "shulkerbox")]
impl Debug for Scope<'_> { impl Debug for Scope<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
struct VariableWrapper<'a>(&'a RwLock<HashMap<String, Arc<VariableData>>>); struct VariableWrapper<'a>(&'a RwLock<HashMap<String, Arc<VariableData>>>);
@ -279,6 +287,31 @@ impl Transpiler {
scope, scope,
handler, handler,
), ),
VariableDeclaration::ComptimeValue(declaration) => {
let value = if let Some(assignment) = declaration.assignment() {
Some(
assignment
.expression()
.comptime_eval(scope, handler)
.map_err(|err| {
let err = TranspileError::NotComptime(err);
handler.receive(err.clone());
err
})?,
)
} else {
None
};
scope.set_variable(
declaration.identifier().span.str(),
VariableData::ComptimeValue {
value: Arc::new(RwLock::new(value)),
},
);
Ok(Vec::new())
}
} }
} }
@ -463,7 +496,7 @@ impl Transpiler {
let (identifier, indexing_value) = match destination { let (identifier, indexing_value) = match destination {
TranspileAssignmentTarget::Identifier(ident) => (ident, None), TranspileAssignmentTarget::Identifier(ident) => (ident, None),
TranspileAssignmentTarget::Indexed(ident, expression) => { TranspileAssignmentTarget::Indexed(ident, expression) => {
(ident, expression.comptime_eval(scope, handler)) (ident, expression.comptime_eval(scope, handler).ok())
} }
}; };
if let Some(target) = scope.get_variable(identifier.span.str()) { if let Some(target) = scope.get_variable(identifier.span.str()) {
@ -489,7 +522,15 @@ impl Transpiler {
target: s, target: s,
}), }),
Some(ComptimeValue::MacroString(s)) => { Some(ComptimeValue::MacroString(s)) => {
todo!("indexing scoreboard with macro string: {s:?}") // TODO: allow indexing with macro string
let err = TranspileError::IllegalIndexing(IllegalIndexing {
reason: IllegalIndexingReason::InvalidComptimeType {
expected: ExpectedType::String,
},
expression: expression.span(),
});
handler.receive(err.clone());
return Err(err);
} }
Some(_) => { Some(_) => {
let err = TranspileError::IllegalIndexing(IllegalIndexing { let err = TranspileError::IllegalIndexing(IllegalIndexing {
@ -621,7 +662,15 @@ impl Transpiler {
entity: s, entity: s,
}), }),
Some(ComptimeValue::MacroString(s)) => { Some(ComptimeValue::MacroString(s)) => {
todo!("indexing tag with macro string: {s:?}") // TODO: allow indexing tag with macro string
let err = TranspileError::IllegalIndexing(IllegalIndexing {
expression: expression.span(),
reason: IllegalIndexingReason::InvalidComptimeType {
expected: ExpectedType::String,
},
});
handler.receive(err.clone());
return Err(err);
} }
Some(_) => { Some(_) => {
let err = TranspileError::IllegalIndexing(IllegalIndexing { let err = TranspileError::IllegalIndexing(IllegalIndexing {
@ -643,6 +692,16 @@ impl Transpiler {
return Err(err); return Err(err);
} }
}, },
VariableData::ComptimeValue { value } => {
let comptime_value =
expression.comptime_eval(scope, handler).map_err(|err| {
let err = TranspileError::NotComptime(err);
handler.receive(err.clone());
err
})?;
*value.write().unwrap() = Some(comptime_value);
return Ok(Vec::new());
}
VariableData::Function { .. } VariableData::Function { .. }
| VariableData::MacroParameter { .. } | VariableData::MacroParameter { .. }
| VariableData::InternalFunction { .. } => { | VariableData::InternalFunction { .. } => {
@ -704,6 +763,7 @@ impl Transpiler {
TranspileAnnotationValue::Expression(expr) => { TranspileAnnotationValue::Expression(expr) => {
if let Some(name_eval) = expr if let Some(name_eval) = expr
.comptime_eval(scope, handler) .comptime_eval(scope, handler)
.ok()
.and_then(|val| val.to_string_no_macro()) .and_then(|val| val.to_string_no_macro())
{ {
// TODO: change invalid criteria if boolean // TODO: change invalid criteria if boolean
@ -802,9 +862,11 @@ 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)
.ok()
.and_then(|val| val.to_string_no_macro()), .and_then(|val| val.to_string_no_macro()),
target target
.comptime_eval(scope, handler) .comptime_eval(scope, handler)
.ok()
.and_then(|val| val.to_string_no_macro()), .and_then(|val| val.to_string_no_macro()),
) { ) {
// TODO: change invalid criteria if boolean // TODO: change invalid criteria if boolean
@ -931,7 +993,15 @@ impl Transpiler {
Ok((name, targets)) Ok((name, targets))
} }
TranspileAnnotationValue::Map(map) => { TranspileAnnotationValue::Map(map) => {
todo!("allow map deobfuscate annotation for array variables") // TODO: implement when map deobfuscate annotation is implemented
let error =
TranspileError::IllegalAnnotationContent(IllegalAnnotationContent {
annotation: deobfuscate_annotation.span(),
message: "Deobfuscate annotation value must be a string or none."
.to_string(),
});
handler.receive(error.clone());
Err(error)
} }
TranspileAnnotationValue::Expression(_) => { TranspileAnnotationValue::Expression(_) => {
let error = let error =

View File

@ -107,7 +107,7 @@ pub fn identifier_to_scoreboard_target(ident: &str) -> std::borrow::Cow<str> {
let new_ident = ident let new_ident = ident
.chars() .chars()
.map(|c| { .map(|c| {
if *c != '_' && !c.is_ascii_alphanumeric() { if c != '_' && !c.is_ascii_alphanumeric() {
'_' '_'
} else { } else {
c c