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
- 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`
### Removed

View File

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

View File

@ -2,10 +2,7 @@
#![allow(missing_docs)]
use std::{collections::HashSet, fmt::Display};
use getset::Getters;
use itertools::Itertools as _;
use std::fmt::Display;
use crate::{
base::{
@ -14,6 +11,10 @@ use crate::{
},
lexical::token::StringLiteral,
syntax::syntax_tree::expression::Expression,
transpile::error::{
AssignmentError, IllegalIndexing, MismatchedTypes, MissingFunctionDeclaration,
UnknownIdentifier,
},
};
#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
@ -31,77 +32,20 @@ pub enum Error {
UnresolvedMacroUsage(#[from] UnresolvedMacroUsage),
#[error(transparent)]
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.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
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
/// TagDeclaration:
/// 'tag' StringLiteral ('of' StringLiteral)? 'replace'? '[' (StringLiteral (',' StringLiteral)*)? ']'
/// 'tag' ('<' StringLiteral '>')? StringLiteral 'replace'? '[' (StringLiteral (',' StringLiteral)*)? ']'
/// ;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -262,9 +262,9 @@ pub struct Tag {
#[get = "pub"]
tag_keyword: Keyword,
#[get = "pub"]
name: StringLiteral,
of_type: Option<(Punctuation, StringLiteral, Punctuation)>,
#[get = "pub"]
of_type: Option<(Keyword, StringLiteral)>,
name: StringLiteral,
#[get = "pub"]
replace: Option<Keyword>,
#[get = "pub"]
@ -278,15 +278,15 @@ impl Tag {
self,
) -> (
Keyword,
Option<(Punctuation, StringLiteral, Punctuation)>,
StringLiteral,
Option<(Keyword, StringLiteral)>,
Option<Keyword>,
DelimitedList<StringLiteral>,
) {
(
self.tag_keyword,
self.name,
self.of_type,
self.name,
self.replace,
self.entries,
)
@ -299,7 +299,7 @@ impl Tag {
self.of_type
.as_ref()
.map_or(TagType::Function, |(_, tag_type)| {
.map_or(TagType::Function, |(_, tag_type, _)| {
match tag_type.str_content().as_ref() {
"function" => TagType::Function,
"block" => TagType::Block,
@ -419,18 +419,24 @@ impl Parser<'_> {
// eat the tag keyword
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
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
.try_parse(|parser| parser.parse_keyword(KeywordKind::Replace, &VoidHandler))
.ok();
@ -444,8 +450,8 @@ impl Parser<'_> {
Ok(Declaration::Tag(Tag {
tag_keyword,
name,
of_type,
name,
replace,
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.
///
/// Syntax Synopsis:

View File

@ -325,6 +325,7 @@ pub enum VariableDeclaration {
Array(ArrayVariableDeclaration),
Score(ScoreVariableDeclaration),
Tag(TagVariableDeclaration),
ComptimeValue(ComptimeValueDeclaration),
}
impl SourceElement for VariableDeclaration {
@ -334,6 +335,7 @@ impl SourceElement for VariableDeclaration {
Self::Array(declaration) => declaration.span(),
Self::Score(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::Score(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::Score(declaration) => &declaration.int_keyword,
Self::Tag(declaration) => &declaration.bool_keyword,
Self::ComptimeValue(declaration) => &declaration.val_keyword,
}
}
@ -383,6 +387,14 @@ impl VariableDeclaration {
declaration.annotations.push_front(annotation);
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.
///
/// Syntax Synopsis:
@ -883,7 +942,10 @@ impl Parser<'_> {
) -> ParseResult<Semicolon> {
let statement = match self.stop_at_significant() {
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)
.map(SemicolonStatement::VariableDeclaration)
@ -895,18 +957,20 @@ impl Parser<'_> {
let destination = {
let identifier = p.parse_identifier(&VoidHandler)?;
if let Ok(tree) = p.step_into(
match p.stop_at_significant() {
Reading::IntoDelimited(punc) if punc.punctuation == '[' => {
let tree = p.step_into(
Delimiter::Bracket,
|pp| pp.parse_expression(&VoidHandler),
&VoidHandler,
) {
)?;
let open = tree.open;
let close = tree.close;
let expression = tree.tree?;
AssignmentDestination::Indexed(identifier, open, expression, close)
} else {
AssignmentDestination::Identifier(identifier)
}
_ => AssignmentDestination::Identifier(identifier),
}
};
let equals = p.parse_punctuation('=', true, &VoidHandler)?;
@ -948,7 +1012,10 @@ impl Parser<'_> {
let variable_type = match self.stop_at_significant() {
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();
keyword
@ -983,7 +1050,10 @@ impl Parser<'_> {
let identifier = self.parse_identifier(handler)?;
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(
Delimiter::Bracket,
|p| {
@ -1071,6 +1141,16 @@ impl Parser<'_> {
let expression = self.parse_expression(handler)?;
let assignment = VariableDeclarationAssignment { equals, expression };
if variable_type.keyword == KeywordKind::Val {
Ok(VariableDeclaration::ComptimeValue(
ComptimeValueDeclaration {
val_keyword: variable_type,
identifier,
assignment: Some(assignment),
annotations: VecDeque::new(),
},
))
} else {
Ok(VariableDeclaration::Single(SingleVariableDeclaration {
variable_type,
identifier,
@ -1078,6 +1158,7 @@ impl Parser<'_> {
annotations: VecDeque::new(),
}))
}
}
// SingleVariableDeclaration without Assignment
_ => Ok(VariableDeclaration::Single(SingleVariableDeclaration {
variable_type,

View File

@ -1,7 +1,5 @@
//! Execute block statement syntax tree.
use std::collections::HashSet;
use derive_more::From;
use enum_as_inner::EnumAsInner;
use getset::Getters;
@ -998,10 +996,10 @@ pub trait ExecuteBlockHeadItem {
#[expect(clippy::missing_errors_doc)]
fn analyze_semantics(
&self,
macro_names: &HashSet<String>,
scope: &crate::semantic::SemanticScope,
handler: &impl Handler<base::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),
#[error(transparent)]
IllegalIndexing(#[from] IllegalIndexing),
#[error(transparent)]
InvalidArgument(#[from] InvalidArgument),
#[error(transparent)]
NotComptime(#[from] NotComptime),
}
/// 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.
#[derive(Debug, Clone, PartialEq, Eq, Getters)]
pub struct MissingFunctionDeclaration {
/// The span of the identifier that is missing.
#[get = "pub"]
span: Span,
/// Possible alternatives for the missing function declaration.
#[get = "pub"]
alternatives: Vec<FunctionData>,
}
@ -127,11 +133,26 @@ impl Display 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.
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LuaRuntimeError {
/// The span of the code block that caused the error.
pub code_block: Span,
/// The error message of the Lua runtime.
pub error_message: String,
}
@ -155,6 +176,8 @@ impl std::error::Error for LuaRuntimeError {}
#[cfg(feature = "lua")]
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 {
let err_string = err.to_string();
Self {
@ -168,9 +191,13 @@ impl LuaRuntimeError {
}
/// 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 {
/// The span of the annotation.
#[get = "pub"]
pub annotation: Span,
/// The error message.
#[get = "pub"]
pub message: String,
}
@ -194,9 +221,13 @@ impl Display for IllegalAnnotationContent {
impl std::error::Error for IllegalAnnotationContent {}
/// 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 {
/// The expression that can not evaluate to the wanted type.
#[get = "pub"]
pub expression: Span,
/// The expected type.
#[get = "pub"]
pub expected_type: ExpectedType,
}
@ -216,9 +247,13 @@ impl Display for MismatchedTypes {
impl std::error::Error for MismatchedTypes {}
/// 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 {
/// The arguments that are not allowed.
#[get = "pub"]
pub arguments: Span,
/// The error message.
#[get = "pub"]
pub message: String,
}
@ -237,9 +272,13 @@ impl Display for FunctionArgumentsNotAllowed {
impl std::error::Error for FunctionArgumentsNotAllowed {}
/// 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 {
/// The identifier that is assigned to.
#[get = "pub"]
pub identifier: Span,
/// The error message.
#[get = "pub"]
pub message: String,
}
@ -258,8 +297,10 @@ impl Display for AssignmentError {
impl std::error::Error for AssignmentError {}
/// 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 {
/// The unknown identifier.
#[get = "pub"]
pub identifier: Span,
}
@ -285,8 +326,10 @@ impl Display for UnknownIdentifier {
impl std::error::Error for UnknownIdentifier {}
/// 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 {
/// The expression that is missing a value.
#[get = "pub"]
pub expression: Span,
}
@ -312,9 +355,13 @@ impl Display for MissingValue {
impl std::error::Error for MissingValue {}
/// 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 {
/// The reason why the indexing operation is not permitted.
#[get = "pub"]
pub reason: IllegalIndexingReason,
/// The expression that is the reason for the indexing being illegal.
#[get = "pub"]
pub expression: Span,
}
@ -333,11 +380,24 @@ impl Display for IllegalIndexing {
impl std::error::Error for IllegalIndexing {}
/// The reason why an indexing operation is not permitted.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IllegalIndexingReason {
/// The expression is not an identifier.
NotIdentifier,
InvalidComptimeType { expected: ExpectedType },
IndexOutOfBounds { index: usize, length: usize },
/// The expression cannot be indexed.
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 {
@ -346,6 +406,9 @@ impl Display for IllegalIndexingReason {
Self::NotIdentifier => {
write!(f, "The expression is not an identifier.")
}
Self::NotIndexable => {
write!(f, "The expression cannot be indexed.")
}
Self::InvalidComptimeType { expected } => {
write!(
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.
use std::{fmt::Display, string::ToString, sync::Arc};
use std::{fmt::Display, string::ToString};
use super::{util::MacroString, Scope, VariableData};
use crate::{
base::{self, Handler, VoidHandler},
lexical::token::MacroStringLiteralPart,
syntax::syntax_tree::expression::{
Binary, BinaryOperator, Expression, PrefixOperator, Primary,
},
};
use super::util::MacroString;
#[cfg(feature = "shulkerbox")]
use enum_as_inner::EnumAsInner;
@ -19,17 +12,25 @@ use shulkerbox::prelude::{Command, Condition, Execute};
#[cfg(feature = "shulkerbox")]
use super::{
error::{IllegalIndexing, IllegalIndexingReason, MismatchedTypes, UnknownIdentifier},
TranspileResult, Transpiler,
error::{
IllegalIndexing, IllegalIndexingReason, MismatchedTypes, NotComptime, UnknownIdentifier,
},
Scope, TranspileResult, Transpiler, VariableData,
};
#[cfg(feature = "shulkerbox")]
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::{
error::{FunctionArgumentsNotAllowed, MissingValue},
TranspileError,
},
};
#[cfg(feature = "shulkerbox")]
use std::sync::Arc;
/// Compile-time evaluated value
#[allow(missing_docs)]
@ -85,7 +86,7 @@ impl Display for ValueType {
}
#[allow(missing_docs)]
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ExpectedType {
Boolean,
Integer,
@ -227,6 +228,7 @@ pub enum ExtendedCondition {
Comptime(bool),
}
#[cfg(feature = "shulkerbox")]
impl Expression {
/// Returns whether the expression can yield a certain type.
#[must_use]
@ -238,12 +240,14 @@ impl Expression {
}
/// Evaluate at compile-time.
#[must_use]
///
/// # Errors
/// - If the expression is not compile-time evaluatable.
pub fn comptime_eval(
&self,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> Option<ComptimeValue> {
) -> Result<ComptimeValue, NotComptime> {
match self {
Self::Primary(primary) => primary.comptime_eval(scope, handler),
Self::Binary(binary) => binary.comptime_eval(scope, handler),
@ -251,6 +255,7 @@ impl Expression {
}
}
#[cfg(feature = "shulkerbox")]
impl Primary {
/// Returns whether the primary can yield a certain type.
#[must_use]
@ -332,19 +337,40 @@ impl Primary {
}
/// Evaluate at compile-time.
#[must_use]
///
/// # Errors
/// - If the expression is not compile-time evaluatable.
pub fn comptime_eval(
&self,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> Option<ComptimeValue> {
) -> Result<ComptimeValue, NotComptime> {
match self {
Self::Boolean(boolean) => Some(ComptimeValue::Boolean(boolean.value())),
Self::Integer(int) => Some(ComptimeValue::Integer(int.as_i64())),
Self::StringLiteral(string_literal) => Some(ComptimeValue::String(
Self::Boolean(boolean) => Ok(ComptimeValue::Boolean(boolean.value())),
Self::Integer(int) => Ok(ComptimeValue::Integer(int.as_i64())),
Self::StringLiteral(string_literal) => Ok(ComptimeValue::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) => {
parenthesized.expression().comptime_eval(scope, handler)
}
@ -354,12 +380,14 @@ impl Primary {
.comptime_eval(scope, handler)
.and_then(|val| match (prefix.operator(), val) {
(PrefixOperator::LogicalNot(_), ComptimeValue::Boolean(boolean)) => {
Some(ComptimeValue::Boolean(!boolean))
Ok(ComptimeValue::Boolean(!boolean))
}
(PrefixOperator::Negate(_), ComptimeValue::Integer(int)) => {
Some(ComptimeValue::Integer(-int))
Ok(ComptimeValue::Integer(-int))
}
_ => None,
_ => Err(NotComptime {
expression: prefix.span(),
}),
})
}
Self::Lua(lua) => lua
@ -367,25 +395,28 @@ impl Primary {
.inspect_err(|err| {
handler.receive(err.clone());
})
.ok()
.flatten(),
.map_err(|_| NotComptime {
expression: lua.span(),
})
.and_then(|val| val),
Self::MacroStringLiteral(macro_string_literal) => {
if macro_string_literal
.parts()
.iter()
.any(|part| matches!(part, MacroStringLiteralPart::MacroUsage { .. }))
{
Some(ComptimeValue::MacroString(
Ok(ComptimeValue::MacroString(
macro_string_literal.clone().into(),
))
} else {
Some(ComptimeValue::String(macro_string_literal.str_content()))
Ok(ComptimeValue::String(macro_string_literal.str_content()))
}
}
}
}
}
#[cfg(feature = "shulkerbox")]
impl Binary {
/// Returns whether the binary can yield a certain type.
#[must_use]
@ -428,12 +459,14 @@ impl Binary {
}
/// Evaluate at compile-time.
#[must_use]
///
/// # Errors
/// - If the expression is not compile-time evaluatable.
pub fn comptime_eval(
&self,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> Option<ComptimeValue> {
) -> Result<ComptimeValue, NotComptime> {
let left = self.left_operand().comptime_eval(scope, handler)?;
let right = self.right_operand().comptime_eval(scope, handler)?;
@ -447,7 +480,7 @@ impl Binary {
.right_operand()
.can_yield_type(ValueType::Boolean, scope) =>
{
Some(ComptimeValue::Boolean(true))
Ok(ComptimeValue::Boolean(true))
}
(ComptimeValue::Boolean(false), _) | (_, ComptimeValue::Boolean(false))
if matches!(self.operator(), BinaryOperator::LogicalAnd(..))
@ -458,15 +491,17 @@ impl Binary {
.right_operand()
.can_yield_type(ValueType::Boolean, scope) =>
{
Some(ComptimeValue::Boolean(false))
Ok(ComptimeValue::Boolean(false))
}
(ComptimeValue::Boolean(left), ComptimeValue::Boolean(right)) => {
match self.operator() {
BinaryOperator::Equal(..) => Some(ComptimeValue::Boolean(left == right)),
BinaryOperator::NotEqual(..) => Some(ComptimeValue::Boolean(left != right)),
BinaryOperator::LogicalAnd(..) => Some(ComptimeValue::Boolean(left && right)),
BinaryOperator::LogicalOr(..) => Some(ComptimeValue::Boolean(left || right)),
_ => None,
BinaryOperator::Equal(..) => Ok(ComptimeValue::Boolean(left == right)),
BinaryOperator::NotEqual(..) => Ok(ComptimeValue::Boolean(left != right)),
BinaryOperator::LogicalAnd(..) => Ok(ComptimeValue::Boolean(left && right)),
BinaryOperator::LogicalOr(..) => Ok(ComptimeValue::Boolean(left || right)),
_ => Err(NotComptime {
expression: self.span(),
}),
}
}
(ComptimeValue::Integer(left), ComptimeValue::Integer(right)) => {
@ -496,14 +531,37 @@ impl Binary {
}
_ => None,
}
.ok_or_else(|| NotComptime {
expression: self.span(),
})
}
(ComptimeValue::String(left), ComptimeValue::String(right)) => match self.operator() {
BinaryOperator::Add(..) => Some(ComptimeValue::String(left + &right)),
BinaryOperator::Equal(..) => Some(ComptimeValue::Boolean(left == right)),
BinaryOperator::NotEqual(..) => Some(ComptimeValue::Boolean(left != right)),
_ => None,
BinaryOperator::Add(..) => Ok(ComptimeValue::String(left + &right)),
BinaryOperator::Equal(..) => Ok(ComptimeValue::Boolean(left == right)),
BinaryOperator::NotEqual(..) => Ok(ComptimeValue::Boolean(left != right)),
_ => 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) =>
{
#[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)
} else {
let err = TranspileError::MissingValue(MissingValue {
@ -801,7 +859,7 @@ impl Transpiler {
if let Some(variable) = variable.as_deref() {
let from = match variable {
VariableData::Scoreboard { objective } => {
if let Some(ComptimeValue::String(target)) =
if let Ok(ComptimeValue::String(target)) =
indexed.index().comptime_eval(scope, handler)
{
Ok(DataLocation::ScoreboardValue {
@ -820,7 +878,7 @@ impl Transpiler {
}
}
VariableData::ScoreboardArray { objective, targets } => {
if let Some(ComptimeValue::Integer(index)) =
if let Ok(ComptimeValue::Integer(index)) =
indexed.index().comptime_eval(scope, handler)
{
if let Some(target) = usize::try_from(index)
@ -858,7 +916,7 @@ impl Transpiler {
storage_name,
paths,
} => {
if let Some(ComptimeValue::Integer(index)) =
if let Ok(ComptimeValue::Integer(index)) =
indexed.index().comptime_eval(scope, handler)
{
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,
binary: &Binary,
target: &DataLocation,
scope: &Arc<super::Scope>,
handler: &impl Handler<base::Error>,
) -> 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)
} else {
match binary.operator() {
@ -1085,7 +1143,7 @@ impl Transpiler {
storage_name,
paths,
} => {
if let Some(ComptimeValue::Integer(index)) =
if let Ok(ComptimeValue::Integer(index)) =
indexed.index().comptime_eval(scope, handler)
{
if let Some(path) = usize::try_from(index)
@ -1166,15 +1224,15 @@ impl Transpiler {
}
},
Primary::Lua(lua) => match lua.eval_comptime(scope, handler)? {
Some(ComptimeValue::String(value)) => Ok((
Ok(ComptimeValue::String(value)) => Ok((
Vec::new(),
ExtendedCondition::Runtime(Condition::Atom(value.into())),
)),
Some(ComptimeValue::MacroString(value)) => Ok((
Ok(ComptimeValue::MacroString(value)) => Ok((
Vec::new(),
ExtendedCondition::Runtime(Condition::Atom(value.into())),
)),
Some(ComptimeValue::Boolean(boolean)) => {
Ok(ComptimeValue::Boolean(boolean)) => {
Ok((Vec::new(), ExtendedCondition::Comptime(boolean)))
}
_ => {

View File

@ -102,6 +102,7 @@ impl Transpiler {
TranspileAnnotationValue::None => Ok(identifier_span.str().to_string()),
TranspileAnnotationValue::Expression(expr) => expr
.comptime_eval(scope, handler)
.ok()
.and_then(|val| val.to_string_no_macro())
.ok_or_else(|| {
let err = TranspileError::IllegalAnnotationContent(
@ -293,13 +294,10 @@ impl Transpiler {
let value = match expression {
Expression::Primary(Primary::Lua(lua)) => {
lua.eval_comptime(scope, handler).and_then(|val| match val {
Some(ComptimeValue::MacroString(s)) => Ok(Parameter::Static(s)),
Some(val) => Ok(Parameter::Static(val.to_macro_string())),
None => {
let err = TranspileError::MismatchedTypes(MismatchedTypes {
expression: expression.span(),
expected_type: ExpectedType::String,
});
Ok(ComptimeValue::MacroString(s)) => Ok(Parameter::Static(s)),
Ok(val) => Ok(Parameter::Static(val.to_macro_string())),
Err(err) => {
let err = TranspileError::NotComptime(err);
handler.receive(err.clone());
Err(err)
}
@ -361,7 +359,18 @@ impl Transpiler {
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(

View File

@ -5,6 +5,7 @@ use std::{
sync::Arc,
};
use cfg_if::cfg_if;
use shulkerbox::prelude::{Command, Execute};
use serde_json::{json, Value as JsonValue};
@ -15,8 +16,8 @@ use crate::{
semantic::error::{InvalidFunctionArguments, UnexpectedExpression},
syntax::syntax_tree::expression::{Expression, FunctionCall, Primary},
transpile::{
error::{IllegalIndexing, IllegalIndexingReason, LuaRuntimeError, UnknownIdentifier},
expression::{ComptimeValue, DataLocation, StorageType},
error::{IllegalIndexing, IllegalIndexingReason, UnknownIdentifier},
expression::{ComptimeValue, DataLocation, ExpectedType, StorageType},
util::MacroString,
TranspileError,
},
@ -182,17 +183,17 @@ fn print_function(
let args = get_args_assert_in_range(call, 1..=2)?;
let first = args.first().expect("checked range");
let (target, message_expression) = args.get(1).map_or_else(
|| ("@a".into(), first),
|second| {
let (target, message_expression) = if let Some(second) = args.get(1) {
(
first
.comptime_eval(scope, &VoidHandler)
.map_or_else(|| "@a".into(), |val| val.to_macro_string()),
.map(|val| val.to_macro_string())
.map_err(TranspileError::NotComptime)?,
second,
)
},
);
} else {
("@a".into(), first)
};
let mut contains_macro = matches!(target, MacroString::MacroString(_));
@ -210,17 +211,24 @@ fn print_function(
Vec::new(),
vec![JsonValue::String(string.str_content().to_string())],
)),
#[cfg_attr(not(feature = "lua"), expect(unused_variables))]
Primary::Lua(lua) => {
cfg_if! {
if #[cfg(feature = "lua")] {
let (ret, _lua) = lua.eval(scope, &VoidHandler)?;
Ok((
Vec::new(),
vec![JsonValue::String(ret.to_string().map_err(|err| {
TranspileError::LuaRuntimeError(LuaRuntimeError::from_lua_err(
TranspileError::LuaRuntimeError(super::error::LuaRuntimeError::from_lua_err(
&err,
lua.span(),
))
})?)],
))
} else {
Err(TranspileError::LuaDisabled)
}
}
}
Primary::Identifier(ident) => {
let (cur_contains_macro, cmd, part) =
@ -232,7 +240,7 @@ fn print_function(
Primary::Identifier(ident) => {
match scope.get_variable(ident.span.str()).as_deref() {
Some(VariableData::Scoreboard { objective }) => {
if let Some(ComptimeValue::String(index)) =
if let Ok(ComptimeValue::String(index)) =
indexed.index().comptime_eval(scope, &VoidHandler)
{
let (cmd, value) = get_data_location(
@ -244,11 +252,17 @@ fn print_function(
);
Ok((cmd.into_iter().collect(), vec![value]))
} 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 }) => {
if let Some(ComptimeValue::Integer(index)) =
if let Ok(ComptimeValue::Integer(index)) =
indexed.index().comptime_eval(scope, &VoidHandler)
{
#[expect(clippy::option_if_let_else)]
@ -274,14 +288,19 @@ fn print_function(
}))
}
} 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 {
storage_name,
paths,
}) => {
if let Some(ComptimeValue::Integer(index)) =
if let Ok(ComptimeValue::Integer(index)) =
indexed.index().comptime_eval(scope, &VoidHandler)
{
#[expect(clippy::option_if_let_else)]
@ -308,10 +327,18 @@ fn print_function(
}))
}
} 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 {
@ -339,9 +366,43 @@ fn print_function(
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

View File

@ -1,6 +1,6 @@
//! Executes the Lua code and returns the resulting command.
#[cfg(feature = "lua")]
#[cfg(all(feature = "lua", feature = "shulkerbox"))]
mod enabled {
use std::sync::Arc;
@ -12,8 +12,8 @@ mod enabled {
syntax::syntax_tree::expression::LuaCode,
transpile::{
error::{
LuaRuntimeError, MismatchedTypes, TranspileError, TranspileResult,
UnknownIdentifier,
InvalidArgument, LuaRuntimeError, MismatchedTypes, NotComptime, TranspileError,
TranspileResult, UnknownIdentifier,
},
expression::{ComptimeValue, ExpectedType},
Scope, VariableData,
@ -86,11 +86,15 @@ mod enabled {
&self,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> TranspileResult<Option<ComptimeValue>> {
) -> TranspileResult<Result<ComptimeValue, NotComptime>> {
// required to keep the lua instance alive
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<()> {
@ -262,8 +266,28 @@ mod enabled {
.map_err(|err| LuaRuntimeError::from_lua_err(&err, self.span()))?;
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 { .. }) => {
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 => {
return Err(TranspileError::UnknownIdentifier(UnknownIdentifier {
@ -374,7 +398,7 @@ mod enabled {
}
}
#[cfg(not(feature = "lua"))]
#[cfg(all(not(feature = "lua"), feature = "shulkerbox"))]
mod disabled {
use std::sync::Arc;
@ -397,7 +421,7 @@ mod disabled {
&self,
scope: &Arc<Scope>,
handler: &impl Handler<base::Error>,
) -> TranspileResult<()> {
) -> TranspileResult<((), ())> {
let _ = scope;
handler.receive(TranspileError::LuaDisabled);
tracing::error!("Lua code evaluation is disabled");

View File

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

View File

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

View File

@ -30,17 +30,17 @@ use crate::{
use super::{
error::{
AssignmentError, IllegalAnnotationContent, IllegalIndexing, IllegalIndexingReason,
MismatchedTypes,
MismatchedTypes, NotComptime,
},
expression::{ComptimeValue, DataLocation, ExpectedType, StorageType},
internal_functions::InternalFunction,
FunctionData, TranspileAnnotationValue, TranspileError, TranspileResult,
};
#[cfg(feature = "shulkerbox")]
use super::Transpiler;
use super::{internal_functions::InternalFunction, Transpiler};
/// Stores the data required to access a variable.
#[cfg(feature = "shulkerbox")]
#[derive(Debug, Clone, EnumAsInner)]
pub enum VariableData {
/// A function.
@ -100,6 +100,11 @@ pub enum VariableData {
/// The implementation
implementation: InternalFunction,
},
/// Compiler internal variable.
ComptimeValue {
/// The value.
value: Arc<RwLock<Option<ComptimeValue>>>,
},
}
#[derive(Debug, Clone, Copy, EnumAsInner)]
@ -122,6 +127,7 @@ impl<'a> From<&'a AssignmentDestination> for TranspileAssignmentTarget<'a> {
}
/// A scope that stores variables.
#[cfg(feature = "shulkerbox")]
#[derive(Default)]
pub struct Scope<'a> {
/// Parent scope where variables are inherited from.
@ -132,6 +138,7 @@ pub struct Scope<'a> {
shadowed: RwLock<HashMap<String, usize>>,
}
#[cfg(feature = "shulkerbox")]
impl<'a> Scope<'a> {
/// Creates a new scope.
#[must_use]
@ -226,6 +233,7 @@ impl<'a> Scope<'a> {
}
}
#[cfg(feature = "shulkerbox")]
impl Debug for Scope<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
struct VariableWrapper<'a>(&'a RwLock<HashMap<String, Arc<VariableData>>>);
@ -279,6 +287,31 @@ impl Transpiler {
scope,
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 {
TranspileAssignmentTarget::Identifier(ident) => (ident, None),
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()) {
@ -489,7 +522,15 @@ impl Transpiler {
target: 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(_) => {
let err = TranspileError::IllegalIndexing(IllegalIndexing {
@ -621,7 +662,15 @@ impl Transpiler {
entity: 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(_) => {
let err = TranspileError::IllegalIndexing(IllegalIndexing {
@ -643,6 +692,16 @@ impl Transpiler {
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::MacroParameter { .. }
| VariableData::InternalFunction { .. } => {
@ -704,6 +763,7 @@ impl Transpiler {
TranspileAnnotationValue::Expression(expr) => {
if let Some(name_eval) = expr
.comptime_eval(scope, handler)
.ok()
.and_then(|val| val.to_string_no_macro())
{
// TODO: change invalid criteria if boolean
@ -802,9 +862,11 @@ impl Transpiler {
if let (Some(name_eval), Some(target_eval)) = (
objective
.comptime_eval(scope, handler)
.ok()
.and_then(|val| val.to_string_no_macro()),
target
.comptime_eval(scope, handler)
.ok()
.and_then(|val| val.to_string_no_macro()),
) {
// TODO: change invalid criteria if boolean
@ -931,7 +993,15 @@ impl Transpiler {
Ok((name, targets))
}
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(_) => {
let error =

View File

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